public void OutputGen_Returns_Correctly_Formatted_Processor_State() { // Arrange var cpu = new CPU(); cpu.PC = 0x1000; cpu.A = 0x00; cpu.X = 0x00; cpu.Y = 0x00; cpu.ProcessorStatus.Value = 0x00; cpu.SP = 0xff; // Act var outputString = OutputGen.GetProcessorState(cpu, true); // Assert Assert.Equal("A=00 X=00 Y=00 PS=[--------] SP=FF PC=1000 CY=0", outputString); }
public void OutputGen_Returns_Correctly_Formatted_Processor_State_When_All_Status_Flags_Are_Set() { // Arrange var cpu = new CPU(); cpu.PC = 0x2000; cpu.A = 0x01; cpu.X = 0xff; cpu.Y = 0x7f; cpu.ProcessorStatus.Value = 0xff; cpu.SP = 0x80; // Act var outputString = OutputGen.GetProcessorState(cpu, false); // Assert Assert.Equal("A=01 X=FF Y=7F PS=[NVUBDIZC] SP=80 PC=2000", outputString); }
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 void Run() { Console.WriteLine($"------------------------------------------------------------------"); Console.WriteLine($"Run 6502 functional test program with decimal tests disabled"); Console.WriteLine("Functional test downloaded and compiled from:"); Console.WriteLine($"------------------------------------------------------------------"); Console.WriteLine($"Downloading functional test source code from"); Console.WriteLine("https://github.com/Klaus2m5/6502_65C02_functional_tests"); Console.WriteLine($"Modifying it to set source code line 'disable_decimal = 0' to 'disable_decimal = 1'"); Console.WriteLine($"And compiling it to a binary that can be loaded into the 6502 emulator..."); // Set the download directory to the same directory where current application runs in. var currentAssemblyLocation = System.Reflection.Assembly.GetEntryAssembly().Location; var downloadDir = System.IO.Path.GetDirectoryName(currentAssemblyLocation); var functionalTestBinary = _functionalTestCompiler.Get6502FunctionalTestBinary( disableDecimalTests: true, downloadDir: downloadDir ); Console.WriteLine($"Download and compilation complete."); Console.WriteLine($"Binary location (as well as .lst file):"); Console.WriteLine($"{functionalTestBinary}"); // There is no 2 byte header in the 6502_functional_test.bin file. // It's supposed to be loaded to memory at 0x0000, and started at 0x0400 Console.WriteLine(""); Console.WriteLine($"Loading binary into emulator memory..."); ushort loadAddress = 0x000A; ushort startAddress = 0x0400; var mem = BinaryLoader.Load( functionalTestBinary, out ushort loadedAtAddress, out int fileLength, forceLoadAddress: loadAddress); Console.WriteLine($"Loading done."); // The rest of the bytes are considered the code Console.WriteLine(""); Console.WriteLine($"Data & code load address: {loadAddress.ToHex(), 10} ({loadAddress})"); Console.WriteLine($"Code+data length (bytes): 0x{fileLength, -8:X8} ({fileLength})"); Console.WriteLine($"Code start address: {startAddress.ToHex(), 10} ({startAddress})"); //Console.WriteLine("Press Enter to start"); //Console.ReadLine(); // Initialize CPU, set PC to start position var computerBuilder = new ComputerBuilder(); computerBuilder .WithCPU() .WithStartAddress(0x400) .WithMemory(mem) //.WithInstructionAboutToBeExecutedEventHandler(OnInstructionToBeExecuted) .WithInstructionExecutedEventHandler(OnInstructionExecuted) .WithUnknownInstructionEventHandler(OnUnknownOpCodeDetected) .WithExecOptions(options => { options.ExecuteUntilExecutedInstructionAtPC = 0x336d; // A successful run has about 26765880 instructions (the version that was run 2021-02-06, that may change) // We increase to almost double, and will exit if not finished then. options.MaxNumberOfInstructions = 50000000; options.UnknownInstructionThrowsException = false; }); var computer = computerBuilder.Build(); Console.WriteLine(""); Console.WriteLine($"If test logic succeeds, the test program will reach a specific memory location: {computer.DefaultExecOptions.ExecuteUntilExecutedInstructionAtPC.Value.ToHex()}, and the emulator will then stop processing."); Console.WriteLine($"If test logic fails, the test program will loop forever at the location the error was found. The emulator will try executing a maximum #instructions {computer.DefaultExecOptions.MaxNumberOfInstructions.Value} before giving up."); Console.WriteLine($"If unknown opcode is found, it's logged and ignored, and processing continues on next instruction."); // Execute program Console.WriteLine(""); Console.WriteLine("Starting code execution..."); computer.Run(); Console.WriteLine(""); Console.WriteLine("Code execution done."); var cpu = computer.CPU; var execState = cpu.ExecState; Console.WriteLine(""); Console.WriteLine($"Last instruction: {OutputGen.BuildInstructionString(computer.CPU, computer.Mem, computer.CPU.ExecState.PCBeforeLastOpCodeExecuted.Value)}"); Console.WriteLine($"CPU state: {OutputGen.GetProcessorState(computer.CPU)}"); Console.WriteLine($"Total # CPU instructions executed: {execState.InstructionsExecutionCount}"); Console.WriteLine($"Total # CPU cycles consumed: {execState.CyclesConsumed}"); Console.WriteLine(""); // Evaluate success/failure if (cpu.PC == computer.DefaultExecOptions.ExecuteUntilExecutedInstructionAtPC.Value) { Console.WriteLine($"Success!"); Console.WriteLine($"PC reached expected success memory location: {computer.DefaultExecOptions.ExecuteUntilExecutedInstructionAtPC.Value.ToHex()}"); } else { Console.WriteLine($"Probably failure"); Console.WriteLine($"The emulator executer a maximum #instructions {computer.DefaultExecOptions.MaxNumberOfInstructions.Value}, and did not manage to get PC to the configured success location: {computer.DefaultExecOptions.ExecuteUntilExecutedInstructionAtPC.Value.ToHex()}"); Console.WriteLine($"The functional test program would end in a forever-loop on the same memory location if it fails."); Console.WriteLine($"Verify the last PC location against the functional test program's .lst file to find out which logic test failed."); } }
public static void Run() { Console.Clear(); string prgFileName = "../../.cache/Examples/ConsoleTestPrograms/AssemblerSource/hostinteraction_scroll_text.prg"; Console.WriteLine($"Loading 6502 machine code binary file."); Console.WriteLine($"{prgFileName}"); if (!File.Exists(prgFileName)) { Console.WriteLine($"File does not exist."); return; } var mem = BinaryLoader.Load( prgFileName, out ushort loadedAtAddress, out int fileLength); // Initialize emulator with CPU, memory, and execution parameters var computerBuilder = new ComputerBuilder(); computerBuilder .WithCPU() .WithStartAddress(loadedAtAddress) .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(); // The shared memory location in the emulator that the 6502 program writes to to update screen. // 80 columns and 25 rows, 1 byte per character = 2000 (0x07d0) bytes const ushort SCREEN_MEM = 0x1000; const int SCREEN_MEM_COLS = 80; // const int SCREEN_MEM_ROWS = 25; const ushort SCREEN_REFRESH_STATUS = 0xf000; const int SCREEN_REFRESH_STATUS_HOST_REFRESH_BIT = 0; const int SCREEN_REFRESH_STATUS_EMULATOR_DONE_BIT = 1; Console.WriteLine(""); Console.WriteLine("Screen being updated indirectly by 6502 machine code running emulator!"); Console.WriteLine(""); bool cont = true; while (cont) { // Set emulator Refresh bit (maybe controlled by host frame counter in future?) // Emulator will wait until this bit is set until "redrawing" new data into memory mem.SetBit(SCREEN_REFRESH_STATUS, SCREEN_REFRESH_STATUS_HOST_REFRESH_BIT); bool shouldExecuteEmulator = true; while (shouldExecuteEmulator) { // Execute a number of instructions computer.Run(new ExecOptions { MaxNumberOfInstructions = 10 }); shouldExecuteEmulator = !mem.IsBitSet(SCREEN_REFRESH_STATUS, SCREEN_REFRESH_STATUS_EMULATOR_DONE_BIT); } RenderRow(mem, SCREEN_MEM, SCREEN_MEM_COLS); //RenderScreen(mem, SCREEN_MEM, SCREEN_MEM_COLS, SCREEN_MEM_ROWS); // Clear the flag that the emulator set to indicate it's done. mem.ClearBit(SCREEN_REFRESH_STATUS, SCREEN_REFRESH_STATUS_EMULATOR_DONE_BIT); bool shouldExecuteHost = true; while (shouldExecuteHost) { Thread.Sleep(80); // Control speed of screen update shouldExecuteHost = false; } } 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."); }
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); }