示例#1
0
        public void OutputGen_Returns_Correctly_Formatted_Operand_String_For_AddrMode(AddrMode addrMode, byte[] operand, string expectedOutput)
        {
            // Act
            var outputString = OutputGen.BuildOperandString(addrMode, operand);

            // Assert
            Assert.Equal(expectedOutput, outputString);
        }
示例#2
0
        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})");
            }
        }
示例#3
0
        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]);
        }
示例#4
0
        public void OutputGen_Returns_Correctly_Formatted_Disassembly_If_OpCode_Is_Unknown()
        {
            // Arrange
            var cpu = new CPU();
            var mem = new Memory();

            mem[0x1000] = 0xff; // 0xff is not a (official) 6502 opcode

            // Act
            var outputString = OutputGen.GetInstructionDisassembly(cpu, mem, 0x1000);

            // Assert
            Assert.Equal("1000  ff        ???        ", outputString);
        }
示例#5
0
        public void OutputGen_Returns_Correctly_Formatted_Disassembly_If_OpCode_Is_Known()
        {
            // Arrange
            var cpu = new CPU();
            var mem = new Memory();

            mem[0x1000] = OpCodeId.LDX_I.ToByte();
            mem[0x1001] = 0xee;

            // Act
            var outputString = OutputGen.GetInstructionDisassembly(cpu, mem, 0x1000);

            // Assert
            Assert.Equal("1000  a2 ee     LDX #$EE   ", outputString);
        }
示例#6
0
        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);
        }
示例#7
0
        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();
        }
示例#8
0
        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);
        }
示例#9
0
        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);
        }
示例#10
0
        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}");
        }
示例#11
0
        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.");
            }
        }
示例#12
0
        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.");
        }
示例#13
0
        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);
        }