void OnInstructionExecuted(object sender, CPUInstructionExecutedEventArgs e) { const int showEveryXInstructions = 2000000; if (e.CPU.ExecState.InstructionsExecutionCount % showEveryXInstructions == 0) { Console.WriteLine($"{OutputGen.GetLastInstructionDisassembly(e.CPU, e.Mem)} (ins. count: {e.CPU.ExecState.InstructionsExecutionCount})"); } }
public void Can_Change_Segment_Memory_Bank_By_Setting_Special_Memory_Location() { // Arrange var mem = new Memory(enableBankSwitching: true); // Add a new bank to segment 1 with blank RAM. Segment 1 starts at 8192 (0x2000). // Adding a new bank does not change current bank number (which will still be 0) mem.AddMemorySegmentBank(1); // Fill some data in to segment 1 in current bank number (0). mem[0x2000] = 0x42; mem[0x2001] = 0x21; // Load machine code into memory that will switch segment 1 bank from 0 to 1 ushort codeAddress = 0xc000; ushort codeInsAddress = codeAddress; // Prepare memory address 0x02 with memory segment bank number to use. Writing to 0x02 will not actually do the change. mem[codeInsAddress++] = (byte)OpCodeId.LDA_I; // LDA (Load Accumulator) mem[codeInsAddress++] = 0x01; // |-Value: The memory segment bank number to put in actual memory mem[codeInsAddress++] = (byte)OpCodeId.STA_ZP; // STA (Store Accumulator) mem[codeInsAddress++] = 0x02; // |-ZeroPage address $0002 // Write the segment number to address 0x01, which will trigger the bank number specified in 0x01 to be loaded in to the segment number written to 0x01. mem[codeInsAddress++] = (byte)OpCodeId.LDA_I; // LDA (Load Accumulator) mem[codeInsAddress++] = 0x01; // |-Value: The memory segment number to change. mem[codeInsAddress++] = (byte)OpCodeId.STA_ZP; // STA (Store Accumulator) mem[codeInsAddress++] = 0x01; // |-ZeroPage address $0001 mem[codeInsAddress++] = 0x00; // BRK (Break/Force Interrupt) - emulator configured to stop execution when reaching this instruction // Initialize emulator with CPU, memory, and execution parameters var computerBuilder = new ComputerBuilder(); computerBuilder .WithCPU() .WithStartAddress(codeAddress) .WithMemory(mem) .WithInstructionExecutedEventHandler( (s, e) => Debug.WriteLine(OutputGen.GetLastInstructionDisassembly(e.CPU, e.Mem))) .WithExecOptions(options => { options.ExecuteUntilInstruction = OpCodeId.BRK; // Emulator will stop executing when a BRK instruction is reached. }); var computer = computerBuilder.Build(); // Act computer.Run(); // Assert // Check that segment 1 now has changed bank number to 1. Assert.Equal(1, mem.MemorySegments[1].CurrentBankNumber); // Check that content of the memory in segment 1 now is blank (bank 1 was blank when we added it) Assert.Equal(0x00, mem[0x2000]); Assert.Equal(0x00, mem[0x2001]); }
public void OutputGen_Returns_Correctly_Formatted_Disassembly_For_Last_Executed_Instruction() { // Arrange var cpu = new CPU(ExecState.ExecStateAfterInstruction(0, false, OpCodeId.LDX_I.ToByte(), 0xc0a0)); var mem = new Memory(); mem[0xc0a0] = OpCodeId.LDX_I.ToByte(); mem[0xc0a1] = 0xee; // Act var outputString = OutputGen.GetLastInstructionDisassembly(cpu, mem); // Assert Assert.Equal("c0a0 a2 ee LDX #$EE ", outputString); }
public Mon() { var mem = new Memory(); var computerBuilder = new ComputerBuilder(); computerBuilder .WithCPU() //.WithStartAddress() .WithMemory(mem) .WithInstructionExecutedEventHandler( (s, e) => Debug.WriteLine(OutputGen.GetLastInstructionDisassembly(e.CPU, e.Mem))); // .WithExecOptions(options => // { // }); Computer = computerBuilder.Build(); }
public static void Run() { // Test program // - adds values from two memory location // - divides it by 2 (rotate right one bit position) // - stores it in another memory location // Load input data into memory byte value1 = 12; byte value2 = 30; ushort value1Address = 0xd000; ushort value2Address = 0xd001; ushort resultAddress = 0xd002; var mem = new Memory(); mem[value1Address] = value1; mem[value2Address] = value2; // Load machine code into memory ushort codeAddress = 0xc000; ushort codeInsAddress = codeAddress; mem[codeInsAddress++] = 0xad; // LDA (Load Accumulator) mem[codeInsAddress++] = 0x00; // |-Lowbyte of $d000 mem[codeInsAddress++] = 0xd0; // |-Highbyte of $d000 mem[codeInsAddress++] = 0x18; // CLC (Clear Carry flag) mem[codeInsAddress++] = 0x6d; // ADC (Add with Carry, adds memory to accumulator) mem[codeInsAddress++] = 0x01; // |-Lowbyte of $d001 mem[codeInsAddress++] = 0xd0; // |-Highbyte of $d001 mem[codeInsAddress++] = 0x6a; // ROR (Rotate Right, rotates accumulator right one bit position) mem[codeInsAddress++] = 0x8d; // STA (Store Accumulator, store to accumulator to memory) mem[codeInsAddress++] = 0x02; // |-Lowbyte of $d002 mem[codeInsAddress++] = 0xd0; // |-Highbyte of $d002 mem[codeInsAddress++] = 0x00; // BRK (Break/Force Interrupt) - emulator configured to stop execution when reaching this instruction // Initialize emulator with CPU, memory, and execution parameters var computerBuilder = new ComputerBuilder(); computerBuilder .WithCPU() .WithStartAddress(codeAddress) .WithMemory(mem) .WithInstructionExecutedEventHandler( (s, e) => Console.WriteLine(OutputGen.GetLastInstructionDisassembly(e.CPU, e.Mem))) .WithExecOptions(options => { options.ExecuteUntilInstruction = OpCodeId.BRK; // Emulator will stop executing when a BRK instruction is reached. }); var computer = computerBuilder.Build(); // Run program computer.Run(); Console.WriteLine($"Execution stopped"); Console.WriteLine($"CPU state: {OutputGen.GetProcessorState(computer.CPU)}"); Console.WriteLine($"Stats: {computer.CPU.ExecState.InstructionsExecutionCount} instruction(s) processed, and used {computer.CPU.ExecState.CyclesConsumed} cycles."); // Print result byte result = mem[resultAddress]; Console.WriteLine($"Result: ({value1} + {value2}) / 2 = {result}"); }
public static CommandLineApplication Configure(Mon mon) { var app = new CommandLineApplication { Name = "", Description = "DotNet 6502 machine code monitor for the DotNet 6502 emulator library." + Environment.NewLine + "By Highbyte 2021" + Environment.NewLine + "Source at: https://github.com/highbyte/dotnet-6502", UnrecognizedArgumentHandling = UnrecognizedArgumentHandling.StopParsingAndCollect }; // Fix: Use custom HelpTextGentorator to avoid name/description of the application to be shown each time help text is shown. app.HelpTextGenerator = new CustomHelpTextGenerator(); // Fix: To avoid CommandLineUtils to the name of the application at the end of the help text: Don't use HelpOption on app-level, instead set it on each command below. //app.HelpOption(inherited: true); app.Command("l", cmd => { cmd.HelpOption(inherited: true); cmd.Description = "Load a 6502 binary into emulator memory."; cmd.AddName("load"); var fileName = cmd.Argument("filename", "Name of the binary file.") .IsRequired() .Accepts(v => v.ExistingFile()); var address = cmd.Argument("address", "Memory address (hex) to load the file into. If not specified, it's assumed the first two bytes of the file contains the load address."); address.Validators.Add(new MustBe16BitHexValueValidator()); cmd.OnExecute(() => { ushort loadedAtAddres; if (string.IsNullOrEmpty(address.Value)) { mon.LoadBinary(fileName.Value, out loadedAtAddres); } else { mon.LoadBinary(fileName.Value, out loadedAtAddres, forceLoadAddress: ushort.Parse(address.Value)); } Console.WriteLine($"File loaded at {loadedAtAddres.ToHex()}"); return(0); }); }); app.Command("d", cmd => { cmd.HelpOption(inherited: true); cmd.Description = "Disassembles 6502 code from emulator memory."; var start = cmd.Argument("start", "Start address (hex). If not specified, the current PC address is used."); start.Validators.Add(new MustBe16BitHexValueValidator()); var end = cmd.Argument("end", "End address (hex). If not specified, a default number of addresses will be shown from start."); end.Validators.Add(new MustBe16BitHexValueValidator()); cmd.OnExecute(() => { ushort startAddress; if (string.IsNullOrEmpty(start.Value)) { startAddress = mon.Cpu.PC; } else { startAddress = ushort.Parse(start.Value, NumberStyles.AllowHexSpecifier, null); } ushort endAddress; if (string.IsNullOrEmpty(end.Value)) { endAddress = (ushort)(startAddress + 0x10); } else { endAddress = ushort.Parse(end.Value, NumberStyles.AllowHexSpecifier, null); if (endAddress < startAddress) { endAddress = startAddress; } } ushort currentAddress = startAddress; while (currentAddress <= endAddress) { Console.WriteLine(OutputGen.GetInstructionDisassembly(mon.Cpu, mon.Mem, currentAddress)); var opCodeByte = mon.Mem[currentAddress]; int insSize; if (!mon.Cpu.InstructionList.OpCodeDictionary.ContainsKey(opCodeByte)) { insSize = 1; } else { insSize = mon.Cpu.InstructionList.GetOpCode(opCodeByte).Size; } currentAddress += (ushort)insSize; } return(0); }); }); app.Command("m", cmd => { cmd.HelpOption(inherited: true); cmd.Description = "Show contents of emulator memory in bytes."; cmd.AddName("mem"); var start = cmd.Argument("start", "Start address (hex). If not specified, the 0000 address is used."); start.Validators.Add(new MustBe16BitHexValueValidator()); var end = cmd.Argument("end", "End address (hex). If not specified, a default number of memory locations will be shown from start."); end.Validators.Add(new MustBe16BitHexValueValidator()); cmd.OnExecute(() => { ushort startAddress; if (string.IsNullOrEmpty(start.Value)) { startAddress = 0x0000; } else { startAddress = ushort.Parse(start.Value, NumberStyles.AllowHexSpecifier, null); } ushort endAddress; if (string.IsNullOrEmpty(end.Value)) { endAddress = (ushort)(startAddress + (16 * 8) - 1); } else { endAddress = ushort.Parse(end.Value, NumberStyles.AllowHexSpecifier, null); if (endAddress < startAddress) { endAddress = startAddress; } } var list = OutputMemoryGen.GetFormattedMemoryList(mon.Mem, startAddress, endAddress); foreach (var line in list) { Console.WriteLine(line); } return(0); }); }); app.Command("r", cmd => { cmd.HelpOption(inherited: true); cmd.Description = "Show processor status and registers. CY = #cycles executed."; cmd.AddName("reg"); cmd.Command("a", setRegisterCmd => { setRegisterCmd.Description = "Sets A register."; var regVal = setRegisterCmd.Argument("value", "Value of A register (hex).").IsRequired(); regVal.Validators.Add(new MustBe8BitHexValueValidator()); setRegisterCmd.OnExecute(() => { var value = regVal.Value; mon.Cpu.A = byte.Parse(value, NumberStyles.AllowHexSpecifier, null); Console.WriteLine($"{OutputGen.GetRegisters(mon.Cpu)}"); return(0); }); }); cmd.Command("x", setRegisterCmd => { setRegisterCmd.Description = "Sets X register."; var regVal = setRegisterCmd.Argument("value", "Value of X register (hex).").IsRequired(); regVal.Validators.Add(new MustBe8BitHexValueValidator()); setRegisterCmd.OnExecute(() => { var value = regVal.Value; mon.Cpu.X = byte.Parse(value, NumberStyles.AllowHexSpecifier, null); Console.WriteLine($"{OutputGen.GetRegisters(mon.Cpu)}"); return(0); }); }); cmd.Command("y", setRegisterCmd => { setRegisterCmd.Description = "Sets Y register."; var regVal = setRegisterCmd.Argument("value", "Value of Y register (hex).").IsRequired(); regVal.Validators.Add(new MustBe8BitHexValueValidator()); setRegisterCmd.OnExecute(() => { var value = regVal.Value; mon.Cpu.Y = byte.Parse(value, NumberStyles.AllowHexSpecifier, null); Console.WriteLine($"{OutputGen.GetRegisters(mon.Cpu)}"); return(0); }); }); cmd.Command("sp", setRegisterCmd => { setRegisterCmd.Description = "Sets SP (Stack Pointer)."; var regVal = setRegisterCmd.Argument("value", "Value of SP (hex).").IsRequired(); regVal.Validators.Add(new MustBe8BitHexValueValidator()); setRegisterCmd.OnExecute(() => { var value = regVal.Value; mon.Cpu.SP = byte.Parse(value, NumberStyles.AllowHexSpecifier, null); Console.WriteLine($"{OutputGen.GetPCandSP(mon.Cpu)}"); return(0); }); }); cmd.Command("ps", setRegisterCmd => { setRegisterCmd.Description = "Sets processor status register."; var regVal = setRegisterCmd.Argument("value", "Value of processor status register (hex).").IsRequired(); regVal.Validators.Add(new MustBe8BitHexValueValidator()); setRegisterCmd.OnExecute(() => { var value = regVal.Value; mon.Cpu.ProcessorStatus.Value = byte.Parse(value, NumberStyles.AllowHexSpecifier, null); Console.WriteLine($"PS={value}"); Console.WriteLine($"{OutputGen.GetStatus(mon.Cpu)}"); return(0); }); }); cmd.Command("pc", setRegisterCmd => { setRegisterCmd.Description = "Sets PC (Program Counter)."; var regVal = setRegisterCmd.Argument("value", "Value of PC (hex).").IsRequired(); regVal.Validators.Add(new MustBe16BitHexValueValidator()); setRegisterCmd.OnExecute(() => { var value = regVal.Value; mon.Cpu.PC = ushort.Parse(value, NumberStyles.AllowHexSpecifier, null); Console.WriteLine($"{OutputGen.GetPCandSP(mon.Cpu)}"); return(0); }); }); cmd.OnExecute(() => { Console.WriteLine(OutputGen.GetProcessorState(mon.Cpu, includeCycles: true)); return(0); }); }); app.Command("g", cmd => { cmd.HelpOption(inherited: true); cmd.Description = "Change the PC (Program Counter) to the specified address and execute code."; cmd.AddName("goto"); var address = cmd.Argument("address", "The address (hex) to start executing code at.").IsRequired(); address.Validators.Add(new MustBe16BitHexValueValidator()); var dontStopOnBRK = cmd.Option("--no-brk|-nb", "Prevent execution stop when BRK instruction encountered.", CommandOptionType.NoValue); cmd.OnExecute(() => { mon.Cpu.PC = ushort.Parse(address.Value, NumberStyles.AllowHexSpecifier, null); ExecOptions execOptions; if (dontStopOnBRK.HasValue()) { execOptions = new ExecOptions(); Console.WriteLine($"Will never stop."); } else { execOptions = new ExecOptions { ExecuteUntilInstruction = OpCodeId.BRK, }; Console.WriteLine($"Will stop on BRK instruction."); } Console.WriteLine($"Staring executing code at {mon.Cpu.PC.ToHex("",lowerCase:true)}"); mon.Computer.Run(execOptions); Console.WriteLine($"Stopped at {mon.Cpu.PC.ToHex("",lowerCase:true)}"); Console.WriteLine($"{OutputGen.GetLastInstructionDisassembly(mon.Cpu, mon.Mem)}"); return(0); }); }); app.Command("z", cmd => { cmd.HelpOption(inherited: true); cmd.Description = "Single step through instructions. Optionally execute a specified number of instructions."; var inscount = cmd.Argument <ulong>("inscount", "Number of instructions to execute. Defaults to 1."); inscount.DefaultValue = 1; cmd.OnExecute(() => { Console.WriteLine($"Executing code at {mon.Cpu.PC.ToHex("",lowerCase:true)} for {inscount.Value} instruction(s)."); var execOptions = new ExecOptions { MaxNumberOfInstructions = ulong.Parse(inscount.Value), }; mon.Computer.Run(execOptions); Console.WriteLine($"Last instruction:"); Console.WriteLine($"{OutputGen.GetLastInstructionDisassembly(mon.Cpu, mon.Mem)}"); return(0); }); }); app.Command("f", cmd => { cmd.HelpOption(inherited: true); cmd.Description = "Fill memory att specified address with a list of bytes. Example: f 1000 20 ff ab 30"; cmd.AddName("fill"); var memAddress = cmd.Argument("address", "Memory address (hex).").IsRequired(); memAddress.Validators.Add(new MustBe16BitHexValueValidator()); var memValues = cmd.Argument("values", "List of byte values (hex). Example: 20 ff ab 30").IsRequired(); memValues.MultipleValues = true; memValues.Validators.Add(new MustBe8BitHexValueValidator()); cmd.OnExecute(() => { var address = ushort.Parse(memAddress.Value, NumberStyles.AllowHexSpecifier, null); List <byte> bytes = new(); foreach (var val in memValues.Values) { bytes.Add(byte.Parse(val, NumberStyles.AllowHexSpecifier, null)); } foreach (var val in bytes) { mon.Mem[address++] = val; } return(0); }); }); app.Command("q", cmd => { cmd.HelpOption(inherited: true); cmd.Description = "Quit monitor."; cmd.AddName("quit"); cmd.AddName("x"); cmd.AddName("exit"); cmd.OnExecute(() => { Console.WriteLine($"Quiting."); return(2); }); }); app.OnExecute(() => { Console.WriteLine("Unknown command."); Console.WriteLine("Help: ?|help|-?|--help"); //app.ShowHelp(); return(1); }); return(app); }