From c081d7943f2e839052b80aa603a54280a2987e59 Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Tue, 14 Oct 2025 13:18:53 -0700 Subject: [PATCH 01/26] speed up dosbox, addresses issue #30 --- test/as65.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/as65.sh b/test/as65.sh index 20059f4..0753879 100755 --- a/test/as65.sh +++ b/test/as65.sh @@ -41,7 +41,14 @@ cp "$src_file" "$dest_dir/" || { echo "Copy failed"; exit 1; } ( cd "$(dirname "$dest_dir")" echo "Running assembler under DOSBox..." - dosbox -c "mount c $(pwd)" \ + dosbox \ + -c "config -set core=dynamic" \ + -c "config -set cycles=max" \ + -c "config -set cycleup=100000" \ + -c "config -set cycledown=100000" \ + -c "config -set frameskip=5" \ + -c "config -set output=surface" \ + -c "mount c $(pwd)" \ -c "c:" \ -c "cd as65_142" \ -c "AS65-DOS.EXE -x1 -o${base_name}.hex -l -m -s2 -w -h0 -c -i -t -u -z $(basename "$src_file")" \ From 19a099ee04e42beb478c845fff326289e9f71d41 Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Tue, 14 Oct 2025 14:37:44 -0700 Subject: [PATCH 02/26] minor indentation tweaks --- test/main.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/test/main.cpp b/test/main.cpp index b0e211c..8955224 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -15,15 +15,18 @@ int start = -1; int success = -1; int retaddr = -1; -void writeRam(uint16_t addr, uint8_t val) { +void writeRam(uint16_t addr, uint8_t val) +{ ram[addr] = val; } -uint8_t readRam(uint16_t addr) { +uint8_t readRam(uint16_t addr) +{ return ram[addr]; } -void tick(mos6502*) { +void tick(mos6502*) +{ static uint16_t lastpc = 0xFFFF; static int count = 0; uint16_t pc = cpu->GetPC(); @@ -40,11 +43,12 @@ void tick(mos6502*) { if (retaddr != -1) { if (ram[retaddr]) { printf("\ncode %02X\n", ram[retaddr]); - printf("Y=%02x\n",cpu->GetY()); + printf("Y=%02x\n", cpu->GetY()); printf("N1=%02x N2=%02x\n", ram[0], ram[1]); printf("HA=%02x HNVZC=%02x\n", ram[2], ram[3]); printf("DA=%02x DNVZC=%02x\n", ram[4], ram[5]); - printf("AR=%02x NF=%02x VF=%02x ZF=%02x CF=%02x\n", ram[6], ram[7], ram[8], ram[9], ram[10]); + printf("AR=%02x NF=%02x VF=%02x ZF=%02x CF=%02x\n", ram[6], ram[7], ram[8], ram[9], + ram[10]); printf("FAIL\n"); exit(-1); } @@ -65,12 +69,14 @@ void tick(mos6502*) { lastpc = pc; } -void bail(const char *s) { +void bail(const char *s) +{ fprintf(stderr, "%s\n", s); exit(-1); } -uint32_t fetch(const char *s, uint16_t offset, uint8_t count) { +uint32_t fetch(const char *s, uint16_t offset, uint8_t count) +{ uint32_t ret = 0; uint32_t val; for (int i = 0; i < count; i++) { @@ -89,7 +95,8 @@ uint32_t fetch(const char *s, uint16_t offset, uint8_t count) { return ret; } -void handle_hex(const char *fname) { +void handle_hex(const char *fname) +{ char buf[1024]; FILE *f = fopen(fname, "r"); if (!f) { @@ -120,7 +127,8 @@ void handle_hex(const char *fname) { fclose(f); } -int main(int argc, char **argv) { +int main(int argc, char **argv) +{ if (argc != 4) { fprintf(stderr, "Usage: %s .hex \n", argv[0]); return -1; From a5433cb84999782d2f5352105e26c7449af68602 Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Tue, 14 Oct 2025 16:11:52 -0700 Subject: [PATCH 03/26] fixed IRQ / NMI implementation, working 6502_interrupt_test --- mos6502.cpp | 92 ++++++++++++++++++++++++++++++++++++++++++--------- mos6502.h | 25 ++++++++++++-- test/Makefile | 12 ++++--- test/main.cpp | 7 ++++ 4 files changed, 115 insertions(+), 21 deletions(-) diff --git a/mos6502.cpp b/mos6502.cpp index 3daca39..2238917 100644 --- a/mos6502.cpp +++ b/mos6502.cpp @@ -35,6 +35,11 @@ mos6502::mos6502(BusRead r, BusWrite w, ClockCycle c) , reset_Y(0x00) , reset_sp(0xFD) , reset_status(CONSTANT) + , irq_handling(false) + , irq_line(true) + , nmi_pending(false) + , nmi_handling(false) + , nmi_line(true) { Write = (BusWrite)w; Read = (BusRead)r; @@ -399,6 +404,13 @@ uint16_t mos6502::Addr_INY() void mos6502::Reset() { + // do not set or clear irq_line, that's external to us + irq_handling = false; + + // do not set or clear nmi_line, that's external to us + nmi_pending = false; + nmi_handling = false; + A = reset_A; Y = reset_Y; X = reset_X; @@ -431,25 +443,22 @@ uint8_t mos6502::StackPop() return Read(0x0100 + sp); } -void mos6502::IRQ() +void mos6502::Svc_IRQ() { - if(!IF_INTERRUPT()) - { - //SET_BREAK(0); - StackPush((pc >> 8) & 0xFF); - StackPush(pc & 0xFF); - StackPush((status & ~BREAK) | CONSTANT); - SET_INTERRUPT(1); - - // load PC from interrupt request vector - uint8_t pcl = Read(irqVectorL); - uint8_t pch = Read(irqVectorH); - pc = (pch << 8) + pcl; - } + //SET_BREAK(0); + StackPush((pc >> 8) & 0xFF); + StackPush(pc & 0xFF); + StackPush((status & ~BREAK) | CONSTANT); + SET_INTERRUPT(1); + + // load PC from interrupt request vector + uint8_t pcl = Read(irqVectorL); + uint8_t pch = Read(irqVectorH); + pc = (pch << 8) + pcl; return; } -void mos6502::NMI() +void mos6502::Svc_NMI() { //SET_BREAK(0); StackPush((pc >> 8) & 0xFF); @@ -464,6 +473,29 @@ void mos6502::NMI() return; } +bool mos6502::CheckInterrupts() { + + // NMI is edge triggered + if (nmi_pending && !nmi_handling) { + nmi_pending = false; + nmi_handling = true; + Svc_NMI(); + return true; + } + + // check disabled bit + if(!IF_INTERRUPT()) { + // IRQ is level triggered + if (irq_line == false && !nmi_handling && !irq_handling) { + irq_handling = true; + Svc_IRQ(); + return true; + } + } + + return false; +} + void mos6502::Run( int32_t cyclesRemaining, uint64_t& cycleCount, @@ -474,6 +506,10 @@ void mos6502::Run( while(cyclesRemaining > 0 && !illegalOpcode) { + if (CheckInterrupts()) { + cycleCount += 6; // TODO FIX verify this is correct + } + // fetch opcode = Read(pc++); @@ -505,6 +541,8 @@ void mos6502::RunEternally() while(!illegalOpcode) { + CheckInterrupts(); + // fetch opcode = Read(pc++); @@ -558,6 +596,22 @@ uint8_t mos6502::GetY() return Y; } +void mos6502::IRQ(bool line) +{ + irq_line = line; +} + +void mos6502::NMI(bool line) +{ + // falling edge triggered + if (nmi_line == true && line == false) { + if (!nmi_handling) { + nmi_pending = true; + } + } + nmi_line = line; +} + void mos6502::SetResetS(uint8_t value) { reset_sp = value; @@ -1073,6 +1127,14 @@ void mos6502::Op_RTI(uint16_t src) hi = StackPop(); pc = (hi << 8) | lo; + + if (nmi_handling) { + nmi_handling = false; + } + else if (irq_handling) { + irq_handling = false; + } + return; } diff --git a/mos6502.h b/mos6502.h index 6977beb..d5f3068 100644 --- a/mos6502.h +++ b/mos6502.h @@ -55,6 +55,15 @@ class mos6502 bool crossed; + bool irq_handling; // are we currently handling an IRQ? + bool irq_line; // current state of the line + + bool nmi_pending; // is there an NMI pending? + bool nmi_handling; // are we currently handling an NMI? + bool nmi_line; // current state of the NMI line + + bool CheckInterrupts(); + // addressing modes uint16_t Addr_ACC(); // ACCUMULATOR uint16_t Addr_IMM(); // IMMEDIATE @@ -140,6 +149,9 @@ class mos6502 void Op_ILLEGAL(uint16_t src); + void Svc_NMI(); + void Svc_IRQ(); + // IRQ, reset, NMI vectors static const uint16_t irqVectorH = 0xFFFF; static const uint16_t irqVectorL = 0xFFFE; @@ -166,8 +178,17 @@ class mos6502 CYCLE_COUNT, }; mos6502(BusRead r, BusWrite w, ClockCycle c = nullptr); - void NMI(); - void IRQ(); + + // set or clear the NMI line. this is an input to the processor. + // a high to low edge transition will trigger an interrupt. + // line state is NOT cleared by Reset() + void NMI(bool line); + + // set or clear the IRQ line. this is an input to the processor. + // a low level will trigger an interrupt. + // line state is NOT cleared by Reset() + void IRQ(bool line); + void Reset(); void Run( int32_t cycles, diff --git a/test/Makefile b/test/Makefile index 2a55354..0999699 100644 --- a/test/Makefile +++ b/test/Makefile @@ -37,7 +37,7 @@ ft.hex: 6502_functional_test: main ft.hex @echo "================ Running $@" - ./main ft.hex 0x400 0x3469 + ./main ft.hex 0x400 0x3469 # magic numbers come from comments and examination of *.lst dt.hex: cp $(BASE)/6502_decimal_test.a65 dt.a65 @@ -46,8 +46,12 @@ dt.hex: 6502_decimal_test: main dt.hex @echo "================ Running $@" - ./main dt.hex 0x200 ?0x000b + ./main dt.hex 0x200 ?0x000b # magic numbers come from comments and examination of *.lst -6502_interrupt_test: +it.hex: + cp $(BASE)/6502_interrupt_test.a65 it.a65 + ./as65.sh it.a65 + +6502_interrupt_test: main it.hex @echo "================ Running $@" - @echo "(not yet implemented)" + ./main it.hex 0x400 0x06f5 # magic numbers come from comments and examination of *.lst diff --git a/test/main.cpp b/test/main.cpp index 8955224..096ec81 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -18,6 +18,13 @@ int retaddr = -1; void writeRam(uint16_t addr, uint8_t val) { ram[addr] = val; + + // feedback + if (addr == 0xbffc) { + // things are inverted here + cpu->IRQ((val & 1) ? false : true); + cpu->NMI((val & 2) ? false : true); + } } uint8_t readRam(uint16_t addr) From ac4b72660964f5041e70614d965963788cd6165a Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Wed, 15 Oct 2025 10:21:26 -0700 Subject: [PATCH 04/26] compile test harness with -O3 for more speed (saves 1 second) --- test/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Makefile b/test/Makefile index 2a55354..104d70a 100644 --- a/test/Makefile +++ b/test/Makefile @@ -27,7 +27,7 @@ $(BASE)/as65_142: ( cd $(BASE)/as65_142 && unzip ../as65_142.zip ) main: main.cpp ../mos6502.cpp ../mos6502.h - g++ -g -o main ../mos6502.cpp main.cpp + g++ -O3 -o main ../mos6502.cpp main.cpp tests: 6502_functional_test 6502_decimal_test 6502_interrupt_test From a276560c8826d93f28543f4722db5467e7b34871 Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Wed, 15 Oct 2025 11:50:40 -0700 Subject: [PATCH 05/26] adds setters for internal registers --- mos6502.cpp | 39 ++++++++++++++++++++++++++++++++++----- mos6502.h | 16 ++++++++++++++-- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/mos6502.cpp b/mos6502.cpp index 3daca39..cc3987c 100644 --- a/mos6502.cpp +++ b/mos6502.cpp @@ -558,14 +558,39 @@ uint8_t mos6502::GetY() return Y; } -void mos6502::SetResetS(uint8_t value) +void mos6502::SetPC(uint16_t n) { - reset_sp = value; + pc = n; } -void mos6502::SetResetP(uint8_t value) +void mos6502::SetS (uint8_t n) { - reset_status = value | CONSTANT | BREAK; + sp = n; +} + +void mos6502::SetP (uint8_t n) +{ + status = n; +} + +void mos6502::SetA (uint8_t n) +{ + A = n; +} + +void mos6502::SetX (uint8_t n) +{ + X = n; +} + +void mos6502::SetY (uint8_t n) +{ + Y = n; +} + +void mos6502::SetResetS(uint8_t value) +{ + reset_sp = value; } void mos6502::SetResetA(uint8_t value) @@ -583,6 +608,11 @@ void mos6502::SetResetY(uint8_t value) reset_Y = value; } +void mos6502::SetResetP(uint8_t value) +{ + reset_status = value | CONSTANT | BREAK; +} + uint8_t mos6502::GetResetS() { return reset_sp; @@ -613,7 +643,6 @@ void mos6502::Op_ILLEGAL(uint16_t src) illegalOpcode = true; } - void mos6502::Op_ADC(uint16_t src) { uint8_t m = Read(src); diff --git a/mos6502.h b/mos6502.h index 6977beb..bd3bc4f 100644 --- a/mos6502.h +++ b/mos6502.h @@ -12,7 +12,7 @@ class mos6502 { -private: + private: // register reset values uint8_t reset_A; uint8_t reset_X; @@ -160,7 +160,7 @@ class mos6502 inline void StackPush(uint8_t byte); inline uint8_t StackPop(); -public: + public: enum CycleMethod { INST_COUNT, CYCLE_COUNT, @@ -177,17 +177,29 @@ class mos6502 // useful when running e.g. WOZ Monitor // no need to worry about cycle exhaus- // tion + + // Various getter/setters + uint16_t GetPC(); uint8_t GetS(); uint8_t GetP(); uint8_t GetA(); uint8_t GetX(); uint8_t GetY(); + + void SetPC(uint16_t n); + void SetS(uint8_t n); + void SetP(uint8_t n); + void SetA(uint8_t n); + void SetX(uint8_t n); + void SetY(uint8_t n); + void SetResetS(uint8_t value); void SetResetP(uint8_t value); void SetResetA(uint8_t value); void SetResetX(uint8_t value); void SetResetY(uint8_t value); + uint8_t GetResetS(); uint8_t GetResetP(); uint8_t GetResetA(); From 62b452ac049d21e71c4a65d7eece87fc9fb44528 Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Wed, 15 Oct 2025 12:05:26 -0700 Subject: [PATCH 06/26] moved IRQ/NMI to avoid future merge conflict --- mos6502.cpp | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/mos6502.cpp b/mos6502.cpp index 2238917..7651c5b 100644 --- a/mos6502.cpp +++ b/mos6502.cpp @@ -596,22 +596,6 @@ uint8_t mos6502::GetY() return Y; } -void mos6502::IRQ(bool line) -{ - irq_line = line; -} - -void mos6502::NMI(bool line) -{ - // falling edge triggered - if (nmi_line == true && line == false) { - if (!nmi_handling) { - nmi_pending = true; - } - } - nmi_line = line; -} - void mos6502::SetResetS(uint8_t value) { reset_sp = value; @@ -667,6 +651,21 @@ void mos6502::Op_ILLEGAL(uint16_t src) illegalOpcode = true; } +void mos6502::IRQ(bool line) +{ + irq_line = line; +} + +void mos6502::NMI(bool line) +{ + // falling edge triggered + if (nmi_line == true && line == false) { + if (!nmi_handling) { + nmi_pending = true; + } + } + nmi_line = line; +} void mos6502::Op_ADC(uint16_t src) { From 538631a45e35e865cc2e28bf36f42cb480002541 Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Wed, 15 Oct 2025 12:11:41 -0700 Subject: [PATCH 07/26] code move anticipating possible future merge conflict --- mos6502.cpp | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/mos6502.cpp b/mos6502.cpp index 7651c5b..b6c14a7 100644 --- a/mos6502.cpp +++ b/mos6502.cpp @@ -402,6 +402,22 @@ uint16_t mos6502::Addr_INY() return addr; } +void mos6502::IRQ(bool line) +{ + irq_line = line; +} + +void mos6502::NMI(bool line) +{ + // falling edge triggered + if (nmi_line == true && line == false) { + if (!nmi_handling) { + nmi_pending = true; + } + } + nmi_line = line; +} + void mos6502::Reset() { // do not set or clear irq_line, that's external to us @@ -651,21 +667,6 @@ void mos6502::Op_ILLEGAL(uint16_t src) illegalOpcode = true; } -void mos6502::IRQ(bool line) -{ - irq_line = line; -} - -void mos6502::NMI(bool line) -{ - // falling edge triggered - if (nmi_line == true && line == false) { - if (!nmi_handling) { - nmi_pending = true; - } - } - nmi_line = line; -} void mos6502::Op_ADC(uint16_t src) { From 003df4e0ef2a6814b031efd3109ea40a9487e7a7 Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Wed, 15 Oct 2025 12:42:25 -0700 Subject: [PATCH 08/26] issue #35 ; Guard for the terminally creative user --- test/Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/Makefile b/test/Makefile index 2a55354..5062035 100644 --- a/test/Makefile +++ b/test/Makefile @@ -17,7 +17,11 @@ BASE := 6502_65C02_functional_tests all: $(BASE) $(BASE)/as65_142 main tests tests @echo TEST COMPLETE: success +clean: + rm -rf $(BASE) ft.* dt.* it.* + $(BASE): + @test ! -e "$@" || { echo "do NOT use 'make -B', use 'make clean ; make' instead"; exit 1; } @echo "Fetching functional tests from GitHub..." git clone https://github.com/Klaus2m5/6502_65C02_functional_tests From d040713056d43522bd7cf8da81aac4903532421c Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Wed, 15 Oct 2025 14:19:16 -0700 Subject: [PATCH 09/26] added "quiet" flag --- test/main.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/test/main.cpp b/test/main.cpp index b0e211c..f9c1bbf 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -3,9 +3,13 @@ #include "../mos6502.h" #include +#include #include #include #include +#include + +bool quiet = false; uint8_t ram[65536] = {0}; @@ -28,7 +32,9 @@ void tick(mos6502*) { static int count = 0; uint16_t pc = cpu->GetPC(); if (pc != lastpc) { - printf("PC=%04x\r", pc); + if (!quiet) { + printf("PC=%04x\r", pc); + } } if (pc == success) { printf("\nsuccess\n"); @@ -121,11 +127,15 @@ void handle_hex(const char *fname) { } int main(int argc, char **argv) { - if (argc != 4) { - fprintf(stderr, "Usage: %s .hex \n", argv[0]); + if (argc != 4 && argc != 5) { + fprintf(stderr, "Usage: %s .hex [quiet]\n", argv[0]); return -1; } + if (argc == 5 && !strcmp(argv[4], "quiet")) { + quiet = true; + } + handle_hex(argv[1]); start = strtoul(argv[2], NULL, 0); From cb4beea2673fe375e9e2f3bce00bf83a7e5ca0e8 Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Wed, 15 Oct 2025 15:13:14 -0700 Subject: [PATCH 10/26] cleaned up IRQ/NMI handling removed unncessary internal state used more common request/inhibit nomenclature for NMI --- mos6502.cpp | 31 +++++++++++-------------------- mos6502.h | 5 ++--- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/mos6502.cpp b/mos6502.cpp index b6c14a7..3562e0a 100644 --- a/mos6502.cpp +++ b/mos6502.cpp @@ -35,10 +35,9 @@ mos6502::mos6502(BusRead r, BusWrite w, ClockCycle c) , reset_Y(0x00) , reset_sp(0xFD) , reset_status(CONSTANT) - , irq_handling(false) , irq_line(true) - , nmi_pending(false) - , nmi_handling(false) + , nmi_request(false) + , nmi_inhibit(false) , nmi_line(true) { Write = (BusWrite)w; @@ -411,8 +410,8 @@ void mos6502::NMI(bool line) { // falling edge triggered if (nmi_line == true && line == false) { - if (!nmi_handling) { - nmi_pending = true; + if (!nmi_inhibit) { + nmi_request = true; } } nmi_line = line; @@ -421,11 +420,9 @@ void mos6502::NMI(bool line) void mos6502::Reset() { // do not set or clear irq_line, that's external to us - irq_handling = false; - // do not set or clear nmi_line, that's external to us - nmi_pending = false; - nmi_handling = false; + nmi_request = false; + nmi_inhibit = false; A = reset_A; Y = reset_Y; @@ -492,9 +489,9 @@ void mos6502::Svc_NMI() bool mos6502::CheckInterrupts() { // NMI is edge triggered - if (nmi_pending && !nmi_handling) { - nmi_pending = false; - nmi_handling = true; + if (nmi_request && !nmi_inhibit) { + nmi_request = false; + nmi_inhibit = true; Svc_NMI(); return true; } @@ -502,8 +499,7 @@ bool mos6502::CheckInterrupts() { // check disabled bit if(!IF_INTERRUPT()) { // IRQ is level triggered - if (irq_line == false && !nmi_handling && !irq_handling) { - irq_handling = true; + if (irq_line == false && !nmi_inhibit) { Svc_IRQ(); return true; } @@ -1128,12 +1124,7 @@ void mos6502::Op_RTI(uint16_t src) pc = (hi << 8) | lo; - if (nmi_handling) { - nmi_handling = false; - } - else if (irq_handling) { - irq_handling = false; - } + nmi_inhibit = false; // always, more efficient that if() return; } diff --git a/mos6502.h b/mos6502.h index d5f3068..25eea5c 100644 --- a/mos6502.h +++ b/mos6502.h @@ -55,11 +55,10 @@ class mos6502 bool crossed; - bool irq_handling; // are we currently handling an IRQ? bool irq_line; // current state of the line - bool nmi_pending; // is there an NMI pending? - bool nmi_handling; // are we currently handling an NMI? + bool nmi_request; // is there an NMI pending? + bool nmi_inhibit; // are we currently handling an NMI? bool nmi_line; // current state of the NMI line bool CheckInterrupts(); From 94e58894b08c6e4ad7241eb930431c70c1af3703 Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Fri, 17 Oct 2025 14:24:59 -0700 Subject: [PATCH 11/26] initial check in, simple windows --- dbg6502/Makefile | 2 + dbg6502/dbg6502.cpp | 144 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 dbg6502/Makefile create mode 100644 dbg6502/dbg6502.cpp diff --git a/dbg6502/Makefile b/dbg6502/Makefile new file mode 100644 index 0000000..5f76c64 --- /dev/null +++ b/dbg6502/Makefile @@ -0,0 +1,2 @@ +dbg6502: ../mos6502.cpp ../mos6502.h dbg6502.cpp + g++ -o $@ dbg6502.cpp ../mos6502.cpp -lncurses diff --git a/dbg6502/dbg6502.cpp b/dbg6502/dbg6502.cpp new file mode 100644 index 0000000..fdf9109 --- /dev/null +++ b/dbg6502/dbg6502.cpp @@ -0,0 +1,144 @@ +#include +#include +#include +#include +#include + +#include "../mos6502.h" + +uint8_t ram[65536] = { 0x4C, 0, 0 } ; +uint8_t write_protect[65536 / 8] = { 0 }; +uint8_t breakpoint[65536 / 8] = { 0 }; +mos6502 *cpu = NULL; + +class NcWin { + public: + NcWin(int x, int y, int w, int h) : win(newwin(h, w, y, x)) { + if (!win) return; + box(win, 0, 0); + wrefresh(win); + } + ~NcWin() { if (win) delwin(win); } + + void xyprintf(int x, int y, const char *fmt, ...) { + if (!win) return; + va_list args; va_start(args, fmt); + wmove(win, y, x); + vw_printw(win, fmt, args); + va_end(args); + wrefresh(win); + } + + protected: + WINDOW *win{}; +}; + +class RegisterWin : public NcWin { + public: + RegisterWin(int x, int y, int w, int h) + : NcWin(x, y, w, h) { + update(); + } + + void update() { + static char srtext[] = { 'N', 'V', '-', 'B', 'D', 'I', 'Z', 'C', 0 }; + uint8_t a = cpu->GetA(); + uint8_t x = cpu->GetX(); + uint8_t y = cpu->GetY(); + uint8_t pc = cpu->GetPC(); + uint8_t sr = cpu->GetP(); + uint8_t sp = cpu->GetS(); + for (int i = 0; i < 8; i++) { + if (sr & (1 << (7 - i))) { + srtext[i] = toupper(srtext[i]); + } + else { + srtext[i] = tolower(srtext[i]); + } + } + xyprintf(1, 1, "PC:%04x SP:%02x A:%02x X:%02x Y:%02x SR:%s", pc, sp, a, x, y, srtext); + } + +}; + +class MemoryWin : public NcWin { + public: + MemoryWin(int x, int y, int w, int h) + : NcWin(x, y, w, h), address(0) { + set_address(0); + } + + void set_address(uint16_t addr) { + int h, w; getmaxyx(win, h, w); + address = addr; + for (int i = 0; i < h - 2; i++) { + xyprintf(1, i + 1, "%04x: ", addr + 16 * i); + for (int j = 0; j < 16; j++) { + xyprintf(1 + 7 + j * 3, i + 1, "%02x", + ram[addr + 16 * i + j]); + } + } + } + + private: + uint16_t address; +}; + +class TerminalWin : public NcWin { + public: + TerminalWin(int x, int y, int w, int h) : NcWin(x, y, w, h) { + // Create an inner window that excludes the border (1px margin all around) + int H, W; getmaxyx(win, H, W); + + inner = derwin(win, H - 2, W - 2, 1, 1); + scrollok(inner, TRUE); + // Optional: define explicit scroll region matching inner’s full height + wsetscrreg(inner, 0, (H - 2) - 1); + wmove(inner, 0, 0); + wrefresh(win); + wrefresh(inner); + } + + ~TerminalWin() { + if (inner) { delwin(inner); inner = nullptr; } + } + + // Print a formatted line; first at top, subsequent below; scrolls when needed. + void printf(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + vw_printw(inner, fmt, args); + va_end(args); + // Force a new line to advance; scrollok() will scroll when needed. + waddch(inner, '\n'); + // Refresh inner so content appears without redrawing the border. + wrefresh(inner); + } + + private: + WINDOW* inner{}; +}; + +int main() { + setlocale(LC_ALL, ""); // enable line/box chars in UTF-8 terminals + initscr(); + noecho(); curs_set(0); + refresh(); // ensure base screen is pushed once + + int H, W; getmaxyx(stdscr, H, W); + TerminalWin box1(0, H - 8, W, 8); // guaranteed on-screen + box1.printf("Hello, ncurses!"); + + MemoryWin *memwin = new MemoryWin(W - 57, H - 18 - 8, 57, 18); + + cpu = new mos6502(NULL, NULL, NULL); + + RegisterWin *regwin = new RegisterWin(W - 57, H - 18 - 8 - 3, 57, 3); + + mvprintw(H-1, 2, "Press any key..."); + refresh(); + getch(); + endwin(); + + return 0; +} From 0b723439e97e439b6b9ec4bb65106df94d61f803 Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Fri, 17 Oct 2025 14:29:55 -0700 Subject: [PATCH 12/26] user input, with scrollback --- dbg6502/dbg6502.cpp | 191 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 158 insertions(+), 33 deletions(-) diff --git a/dbg6502/dbg6502.cpp b/dbg6502/dbg6502.cpp index fdf9109..c22e87f 100644 --- a/dbg6502/dbg6502.cpp +++ b/dbg6502/dbg6502.cpp @@ -1,8 +1,11 @@ +#include +#include +#include #include #include -#include #include -#include +#include +#include #include "../mos6502.h" @@ -84,39 +87,153 @@ class MemoryWin : public NcWin { uint16_t address; }; +// A simple terminal-like window with scrolling + input history. class TerminalWin : public NcWin { - public: - TerminalWin(int x, int y, int w, int h) : NcWin(x, y, w, h) { - // Create an inner window that excludes the border (1px margin all around) - int H, W; getmaxyx(win, H, W); - - inner = derwin(win, H - 2, W - 2, 1, 1); - scrollok(inner, TRUE); - // Optional: define explicit scroll region matching inner’s full height - wsetscrreg(inner, 0, (H - 2) - 1); - wmove(inner, 0, 0); - wrefresh(win); - wrefresh(inner); - } +public: + TerminalWin(int x, int y, int w, int h) : NcWin(x, y, w, h) { + int H, W; getmaxyx(win, H, W); + inner = derwin(win, H - 2, W - 2, 1, 1); // safe drawing region + scrollok(inner, TRUE); + keypad(inner, TRUE); + wsetscrreg(inner, 0, (H - 2) - 1); + wmove(inner, 0, 0); + wrefresh(win); + wrefresh(inner); + } - ~TerminalWin() { - if (inner) { delwin(inner); inner = nullptr; } - } + ~TerminalWin() { + if (inner) { delwin(inner); inner = nullptr; } + } - // Print a formatted line; first at top, subsequent below; scrolls when needed. - void printf(const char* fmt, ...) { - va_list args; - va_start(args, fmt); - vw_printw(inner, fmt, args); - va_end(args); - // Force a new line to advance; scrollok() will scroll when needed. - waddch(inner, '\n'); - // Refresh inner so content appears without redrawing the border. - wrefresh(inner); - } + void printf(const char* fmt, ...) { + va_list args; va_start(args, fmt); + vw_printw(inner, fmt, args); + va_end(args); + waddch(inner, '\n'); + wrefresh(inner); + } - private: - WINDOW* inner{}; + // Reads a line with prompt; echoes input; supports backspace and ↑/↓ history (64 lines). + // NOTE: Caller should provide a buffer large enough for expected input. + void getline(const char* prompt, char* buffer) { + // Ensure cursor visible (blinking is terminal-dependent) + curs_set(1); + + // Move to next line if not at column 0 + int cy, cx; getyx(inner, cy, cx); + if (cx != 0) { waddch(inner, '\n'); getyx(inner, cy, cx); } + + // Write prompt and start fresh input + std::string current; + int H, W; getmaxyx(inner, H, W); + draw_line(prompt, current); + + // history navigation: index -1 means editing current line + int hist_index = -1; // 0 = oldest, size()-1 = newest + + while (true) { + int ch = wgetch(inner); + if (ch == '\n' || ch == '\r') { + // finalize line + waddch(inner, '\n'); + // store into history (skip empty duplicates at tail) + if (!current.empty()) { + if (history.empty() || history.back() != current) { + history.push_back(current); + if (history.size() > kMaxHistory) history.erase(history.begin()); + } + } + // copy to caller buffer + std::strcpy(buffer, current.c_str()); + wrefresh(inner); + curs_set(0); + return; + } + + switch (ch) { + case KEY_BACKSPACE: + case 127: + case 8: + if (!current.empty()) { + current.pop_back(); + draw_line(prompt, current); + } + break; + + case KEY_UP: + if (!history.empty()) { + if (hist_index < 0) hist_index = static_cast(history.size()) - 1; + else if (hist_index > 0) hist_index--; + current = history[hist_index]; + draw_line(prompt, current); + } + break; + + case KEY_DOWN: + if (!history.empty() && hist_index >= 0) { + if (hist_index < static_cast(history.size()) - 1) { + hist_index++; + current = history[hist_index]; + } else { + hist_index = -1; + current.clear(); + } + draw_line(prompt, current); + } + break; + + case KEY_LEFT: + case KEY_RIGHT: + case KEY_HOME: + case KEY_END: + // Keep it simple: no in-line editing; ignore navigation keys. + // (Could be added later by tracking an insertion cursor.) + break; + + default: + if (is_printable(ch)) { + // Prevent drawing beyond the line width; wrap naturally by newline if needed. + // We’ll clamp visual width to W-1 (prompt + text). + int prompt_len = visual_len(prompt); + if (prompt_len + (int)current.size() < W - 1) { + current.push_back(static_cast(ch)); + draw_line(prompt, current); + } else { + // If full, behave like a bell + beep(); + } + } + break; + } + } + } + +private: + WINDOW* inner{}; + static constexpr size_t kMaxHistory = 64; + std::vector history; + + static bool is_printable(int ch) { + return ch >= 32 && ch <= 126; + } + + static int visual_len(const char* s) { + // ASCII only here; if you need UTF-8 width, use wcwidth/mbstowcs. + int n = 0; while (s && *s) { n++; s++; } return n; + } + + void draw_line(const char* prompt, const std::string& text) { + int y, x; getyx(inner, y, x); + // Move to start of current line (create one if we’re at end) + // If we’re beyond last line, inner will scroll automatically. + wmove(inner, y, 0); + wclrtoeol(inner); + waddstr(inner, prompt); + waddnstr(inner, text.c_str(), (int)text.size()); + // Place cursor at end of input + getyx(inner, y, x); + wrefresh(inner); + } }; int main() { @@ -126,8 +243,8 @@ int main() { refresh(); // ensure base screen is pushed once int H, W; getmaxyx(stdscr, H, W); - TerminalWin box1(0, H - 8, W, 8); // guaranteed on-screen - box1.printf("Hello, ncurses!"); + TerminalWin term(0, H - 8, W, 8); // guaranteed on-screen + term.printf("Hello, ncurses!"); MemoryWin *memwin = new MemoryWin(W - 57, H - 18 - 8, 57, 18); @@ -135,6 +252,14 @@ int main() { RegisterWin *regwin = new RegisterWin(W - 57, H - 18 - 8 - 3, 57, 3); + term.printf("Welcome. Type lines; use Up/Down to browse history. Enter accepts."); + char buf[1024]; + + for (int i = 0; i < 10; ++i) { + term.getline(">>> ", buf); + term.printf("You typed: %s", buf); + } + mvprintw(H-1, 2, "Press any key..."); refresh(); getch(); From 9e84c6552f84965c5ca11509d50596a8c30b7adf Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Fri, 17 Oct 2025 14:31:24 -0700 Subject: [PATCH 13/26] virtual destructor / override in Terminal --- dbg6502/dbg6502.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dbg6502/dbg6502.cpp b/dbg6502/dbg6502.cpp index c22e87f..3703611 100644 --- a/dbg6502/dbg6502.cpp +++ b/dbg6502/dbg6502.cpp @@ -21,7 +21,7 @@ class NcWin { box(win, 0, 0); wrefresh(win); } - ~NcWin() { if (win) delwin(win); } + virtual ~NcWin() { if (win) delwin(win); } void xyprintf(int x, int y, const char *fmt, ...) { if (!win) return; @@ -101,7 +101,7 @@ class TerminalWin : public NcWin { wrefresh(inner); } - ~TerminalWin() { + ~TerminalWin() override { if (inner) { delwin(inner); inner = nullptr; } } From afdb28edad01164a9206bd7330bbb3e307180a1c Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Fri, 17 Oct 2025 15:46:29 -0700 Subject: [PATCH 14/26] working reset --- dbg6502/Makefile | 2 +- dbg6502/dbg6502.cpp | 386 ++++++++++++++++++++++++++------------------ 2 files changed, 232 insertions(+), 156 deletions(-) diff --git a/dbg6502/Makefile b/dbg6502/Makefile index 5f76c64..74412b8 100644 --- a/dbg6502/Makefile +++ b/dbg6502/Makefile @@ -1,2 +1,2 @@ dbg6502: ../mos6502.cpp ../mos6502.h dbg6502.cpp - g++ -o $@ dbg6502.cpp ../mos6502.cpp -lncurses + g++ -g -o $@ dbg6502.cpp ../mos6502.cpp -lncurses diff --git a/dbg6502/dbg6502.cpp b/dbg6502/dbg6502.cpp index 3703611..9e65298 100644 --- a/dbg6502/dbg6502.cpp +++ b/dbg6502/dbg6502.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -78,7 +79,7 @@ class MemoryWin : public NcWin { xyprintf(1, i + 1, "%04x: ", addr + 16 * i); for (int j = 0; j < 16; j++) { xyprintf(1 + 7 + j * 3, i + 1, "%02x", - ram[addr + 16 * i + j]); + ram[addr + 16 * i + j]); } } } @@ -89,181 +90,256 @@ class MemoryWin : public NcWin { // A simple terminal-like window with scrolling + input history. class TerminalWin : public NcWin { -public: - TerminalWin(int x, int y, int w, int h) : NcWin(x, y, w, h) { - int H, W; getmaxyx(win, H, W); - inner = derwin(win, H - 2, W - 2, 1, 1); // safe drawing region - scrollok(inner, TRUE); - keypad(inner, TRUE); - wsetscrreg(inner, 0, (H - 2) - 1); - wmove(inner, 0, 0); - wrefresh(win); - wrefresh(inner); - } - - ~TerminalWin() override { - if (inner) { delwin(inner); inner = nullptr; } - } - - void printf(const char* fmt, ...) { - va_list args; va_start(args, fmt); - vw_printw(inner, fmt, args); - va_end(args); - waddch(inner, '\n'); - wrefresh(inner); - } - - // Reads a line with prompt; echoes input; supports backspace and ↑/↓ history (64 lines). - // NOTE: Caller should provide a buffer large enough for expected input. - void getline(const char* prompt, char* buffer) { - // Ensure cursor visible (blinking is terminal-dependent) - curs_set(1); - - // Move to next line if not at column 0 - int cy, cx; getyx(inner, cy, cx); - if (cx != 0) { waddch(inner, '\n'); getyx(inner, cy, cx); } - - // Write prompt and start fresh input - std::string current; - int H, W; getmaxyx(inner, H, W); - draw_line(prompt, current); - - // history navigation: index -1 means editing current line - int hist_index = -1; // 0 = oldest, size()-1 = newest - - while (true) { + public: + TerminalWin(int x, int y, int w, int h) : NcWin(x, y, w, h) { + int H, W; getmaxyx(win, H, W); + inner = derwin(win, H - 2, W - 2, 1, 1); // safe drawing region + scrollok(inner, TRUE); + keypad(inner, TRUE); + wsetscrreg(inner, 0, (H - 2) - 1); + wmove(inner, 0, 0); + wrefresh(win); + wrefresh(inner); + } + + ~TerminalWin() override { + if (inner) { delwin(inner); inner = nullptr; } + } + + void printf(const char* fmt, ...) { + va_list args; va_start(args, fmt); + vw_printw(inner, fmt, args); + va_end(args); + waddch(inner, '\n'); + wrefresh(inner); + } + + // Reads a line with prompt; echoes input; supports backspace and ↑/↓ history (64 lines). + // NOTE: Caller should provide a buffer large enough for expected input. + void getline(const char* prompt, char* buffer) { + // Ensure cursor visible (blinking is terminal-dependent) + curs_set(1); + + // Move to next line if not at column 0 + int cy, cx; getyx(inner, cy, cx); + if (cx != 0) { waddch(inner, '\n'); getyx(inner, cy, cx); } + + // Write prompt and start fresh input + std::string current; + int H, W; getmaxyx(inner, H, W); + draw_line(prompt, current); + + // history navigation: index -1 means editing current line + int hist_index = -1; // 0 = oldest, size()-1 = newest + + while (true) { int ch = wgetch(inner); if (ch == '\n' || ch == '\r') { - // finalize line - waddch(inner, '\n'); - // store into history (skip empty duplicates at tail) - if (!current.empty()) { - if (history.empty() || history.back() != current) { - history.push_back(current); - if (history.size() > kMaxHistory) history.erase(history.begin()); - } - } - // copy to caller buffer - std::strcpy(buffer, current.c_str()); - wrefresh(inner); - curs_set(0); - return; + // finalize line + waddch(inner, '\n'); + // store into history (skip empty duplicates at tail) + if (!current.empty()) { + if (history.empty() || history.back() != current) { + history.push_back(current); + if (history.size() > kMaxHistory) history.erase(history.begin()); + } + } + // copy to caller buffer + std::strcpy(buffer, current.c_str()); + wrefresh(inner); + curs_set(0); + return; } switch (ch) { - case KEY_BACKSPACE: - case 127: - case 8: - if (!current.empty()) { - current.pop_back(); - draw_line(prompt, current); - } - break; - - case KEY_UP: - if (!history.empty()) { - if (hist_index < 0) hist_index = static_cast(history.size()) - 1; - else if (hist_index > 0) hist_index--; + case KEY_BACKSPACE: + case 127: + case 8: + if (!current.empty()) { + current.pop_back(); + draw_line(prompt, current); + } + break; + + case KEY_UP: + if (!history.empty()) { + if (hist_index < 0) hist_index = static_cast(history.size()) - 1; + else if (hist_index > 0) hist_index--; + current = history[hist_index]; + draw_line(prompt, current); + } + break; + + case KEY_DOWN: + if (!history.empty() && hist_index >= 0) { + if (hist_index < static_cast(history.size()) - 1) { + hist_index++; current = history[hist_index]; + } else { + hist_index = -1; + current.clear(); + } + draw_line(prompt, current); + } + break; + + case KEY_LEFT: + case KEY_RIGHT: + case KEY_HOME: + case KEY_END: + // Keep it simple: no in-line editing; ignore navigation keys. + // (Could be added later by tracking an insertion cursor.) + break; + + default: + if (is_printable(ch)) { + // Prevent drawing beyond the line width; wrap naturally by newline if needed. + // We’ll clamp visual width to W-1 (prompt + text). + int prompt_len = visual_len(prompt); + if (prompt_len + (int)current.size() < W - 1) { + current.push_back(static_cast(ch)); draw_line(prompt, current); - } - break; - - case KEY_DOWN: - if (!history.empty() && hist_index >= 0) { - if (hist_index < static_cast(history.size()) - 1) { - hist_index++; - current = history[hist_index]; - } else { - hist_index = -1; - current.clear(); - } - draw_line(prompt, current); - } - break; - - case KEY_LEFT: - case KEY_RIGHT: - case KEY_HOME: - case KEY_END: - // Keep it simple: no in-line editing; ignore navigation keys. - // (Could be added later by tracking an insertion cursor.) - break; - - default: - if (is_printable(ch)) { - // Prevent drawing beyond the line width; wrap naturally by newline if needed. - // We’ll clamp visual width to W-1 (prompt + text). - int prompt_len = visual_len(prompt); - if (prompt_len + (int)current.size() < W - 1) { - current.push_back(static_cast(ch)); - draw_line(prompt, current); - } else { - // If full, behave like a bell - beep(); - } - } - break; + } else { + // If full, behave like a bell + beep(); + } + } + break; } - } - } - -private: - WINDOW* inner{}; - static constexpr size_t kMaxHistory = 64; - std::vector history; - - static bool is_printable(int ch) { - return ch >= 32 && ch <= 126; - } - - static int visual_len(const char* s) { - // ASCII only here; if you need UTF-8 width, use wcwidth/mbstowcs. - int n = 0; while (s && *s) { n++; s++; } return n; - } - - void draw_line(const char* prompt, const std::string& text) { - int y, x; getyx(inner, y, x); - // Move to start of current line (create one if we’re at end) - // If we’re beyond last line, inner will scroll automatically. - wmove(inner, y, 0); - wclrtoeol(inner); - waddstr(inner, prompt); - waddnstr(inner, text.c_str(), (int)text.size()); - // Place cursor at end of input - getyx(inner, y, x); - wrefresh(inner); - } + } + } + + private: + WINDOW* inner{}; + static constexpr size_t kMaxHistory = 64; + std::vector history; + + static bool is_printable(int ch) { + return ch >= 32 && ch <= 126; + } + + static int visual_len(const char* s) { + // ASCII only here; if you need UTF-8 width, use wcwidth/mbstowcs. + int n = 0; while (s && *s) { n++; s++; } return n; + } + + void draw_line(const char* prompt, const std::string& text) { + int y, x; getyx(inner, y, x); + // Move to start of current line (create one if we’re at end) + // If we’re beyond last line, inner will scroll automatically. + wmove(inner, y, 0); + wclrtoeol(inner); + waddstr(inner, prompt); + waddnstr(inner, text.c_str(), (int)text.size()); + // Place cursor at end of input + getyx(inner, y, x); + wrefresh(inner); + } }; -int main() { +void setup_ncurses(void) { setlocale(LC_ALL, ""); // enable line/box chars in UTF-8 terminals initscr(); noecho(); curs_set(0); refresh(); // ensure base screen is pushed once +} - int H, W; getmaxyx(stdscr, H, W); - TerminalWin term(0, H - 8, W, 8); // guaranteed on-screen - term.printf("Hello, ncurses!"); +void teardown_ncurses(void) { + refresh(); + endwin(); +} + +bool done = false; +bool bus_error = false; +MemoryWin *memwin = NULL; +TerminalWin *termwin = NULL; +RegisterWin *regwin = NULL; +TerminalWin *displaywin = NULL; + +void bus_write(uint16_t addr, uint8_t data) { + displaywin->printf("write %04x %02x", addr, data); + if (write_protect[addr / 8] & (1 << (addr & 0x7))) { + bus_error = true; + displaywin->printf("BUS ERROR"); + } + else { + ram[addr] = data; + } +} + +uint8_t bus_read(uint16_t addr) { + displaywin->printf("read %04x %02x", addr, ram[addr]); + return ram[addr]; +} - MemoryWin *memwin = new MemoryWin(W - 57, H - 18 - 8, 57, 18); +void tick(mos6502 *) { +} - cpu = new mos6502(NULL, NULL, NULL); +typedef void (*handler)(char *p); - RegisterWin *regwin = new RegisterWin(W - 57, H - 18 - 8 - 3, 57, 3); +void do_quit(char *) { + done = 1; +} - term.printf("Welcome. Type lines; use Up/Down to browse history. Enter accepts."); - char buf[1024]; +void do_reset(char *) { + cpu->Reset(); +} - for (int i = 0; i < 10; ++i) { - term.getline(">>> ", buf); - term.printf("You typed: %s", buf); - } +void do_help(char *); + +struct Commands { + const char *cmd; + const char *help; + handler exe; +} commands[] = { + { "reset", "reset the cpu", do_reset }, + { "quit", "quit the program", do_quit }, + { "exit", "same as quit", do_quit }, + { "help", "print help", do_help }, + { "?", "same as help", do_help }, +}; - mvprintw(H-1, 2, "Press any key..."); - refresh(); - getch(); - endwin(); +void do_help(char *) { + displaywin->printf("=== COMMANDS ==="); + for (int i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) { + displaywin->printf("%s\t%s", commands[i].cmd, commands[i].help); + } + displaywin->printf("================"); +} + +void command(char *p) { + for (int i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) { + if (!strncmp(p, commands[i].cmd, strlen(commands[i].cmd))) { + commands[i].exe(p); + return; + } + } + termwin->printf("huh? type '?' for help"); +} + +int main() { + setup_ncurses(); + + cpu = new mos6502(bus_read, bus_write, tick); + + int H, W; getmaxyx(stdscr, H, W); + + termwin = new TerminalWin(0, H - 8, W, 8); + memwin = new MemoryWin(W - 57, H - 18 - 8, 57, 18); + regwin = new RegisterWin(W - 57, H - 18 - 8 - 3, 57, 3); + displaywin = new TerminalWin(0, 0, W-57, H - 8); + + termwin->printf("Welcome. Type lines; use Up/Down to browse history. Enter accepts. ? for help"); + char buf[1024]; + + while (!done) { + termwin->getline(">>> ", buf); + command(buf); + + regwin->update(); // make sure it is up to date no matter what + } + + teardown_ncurses(); return 0; } From ce875bdabdf82ad9c7c8fdfa2dee5497772abdfc Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Fri, 17 Oct 2025 16:02:51 -0700 Subject: [PATCH 15/26] more simple functionality --- dbg6502/dbg6502.cpp | 89 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 7 deletions(-) diff --git a/dbg6502/dbg6502.cpp b/dbg6502/dbg6502.cpp index 9e65298..6d27861 100644 --- a/dbg6502/dbg6502.cpp +++ b/dbg6502/dbg6502.cpp @@ -285,31 +285,106 @@ void do_reset(char *) { cpu->Reset(); } +int parsenum(char *p) { + switch (*p) { + case 'x': + case '$': + return strtoul(p + 1, NULL, 16); + case 'o': + case '@': + return strtoul(p + 1, NULL, 8); + case 'b': + case '%': + return strtoul(p + 1, NULL, 2); + default: + return strtoul(p, NULL, 0); // intentional, to allow 0x hex, 0b binary, and 0 octal + } +} + +void do_a(char *p) { + p = strchr(p, '='); + if (p) { + p++; + cpu->SetA(parsenum(p)); + } +} + +void do_x(char *p) { + p = strchr(p, '='); + if (p) { + p++; + cpu->SetX(parsenum(p)); + } +} + +void do_y(char *p) { + p = strchr(p, '='); + if (p) { + p++; + cpu->SetY(parsenum(p)); + } +} + +void do_pc(char *p) { + p = strchr(p, '='); + if (p) { + p++; + cpu->SetPC(parsenum(p)); + } +} + +void do_sp(char *p) { + p = strchr(p, '='); + if (p) { + p++; + cpu->SetS(parsenum(p)); + } +} + +void do_sr(char *p) { + p = strchr(p, '='); + if (p) { + p++; + cpu->SetP(parsenum(p)); + } +} + void do_help(char *); struct Commands { const char *cmd; + const char *args; const char *help; handler exe; } commands[] = { - { "reset", "reset the cpu", do_reset }, - { "quit", "quit the program", do_quit }, - { "exit", "same as quit", do_quit }, - { "help", "print help", do_help }, - { "?", "same as help", do_help }, + { "A=", "", "set the A register", do_a }, + { "X=", "", "set the X register", do_x }, + { "Y=", "", "set the Y register", do_y }, + { "PC=", "", "set the PC", do_pc }, + { "SR=", "", "set the STATUS", do_sr }, + { "SP=", "", "set the STACK POINTER", do_sp }, + { "reset", NULL, "reset the cpu", do_reset }, + { "reset", NULL, "reset the cpu", do_reset }, + { "quit", NULL, "quit the program", do_quit }, + { "exit", NULL, "same as quit", do_quit }, + { "help", NULL, "print help", do_help }, + { "?", NULL, "same as help", do_help }, }; void do_help(char *) { displaywin->printf("=== COMMANDS ==="); for (int i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) { - displaywin->printf("%s\t%s", commands[i].cmd, commands[i].help); + displaywin->printf("%s%s\t%s", commands[i].cmd, commands[i].args ? commands[i].args : "", commands[i].help); } displaywin->printf("================"); + displaywin->printf("numbers may be entered as $xx (hex), %%bbbbbbbb (binary),"); + displaywin->printf(" @ooo (octal) or decimal (no prefix)"); + displaywin->printf("================"); } void command(char *p) { for (int i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) { - if (!strncmp(p, commands[i].cmd, strlen(commands[i].cmd))) { + if (!strncasecmp(p, commands[i].cmd, strlen(commands[i].cmd))) { commands[i].exe(p); return; } From 631f1db90c0277ced4d7983887c914324b4e73f3 Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Fri, 17 Oct 2025 16:39:22 -0700 Subject: [PATCH 16/26] filenam tab completion --- dbg6502/dbg6502.cpp | 386 +++++++++++++++++++++++++++++--------------- 1 file changed, 252 insertions(+), 134 deletions(-) diff --git a/dbg6502/dbg6502.cpp b/dbg6502/dbg6502.cpp index 6d27861..2fed87e 100644 --- a/dbg6502/dbg6502.cpp +++ b/dbg6502/dbg6502.cpp @@ -1,11 +1,14 @@ +#include #include #include #include +#include #include #include #include #include #include +#include #include #include "../mos6502.h" @@ -88,159 +91,274 @@ class MemoryWin : public NcWin { uint16_t address; }; -// A simple terminal-like window with scrolling + input history. +// --- TerminalWin: scrolling printf + getline with history and TAB completion --- class TerminalWin : public NcWin { - public: - TerminalWin(int x, int y, int w, int h) : NcWin(x, y, w, h) { - int H, W; getmaxyx(win, H, W); - inner = derwin(win, H - 2, W - 2, 1, 1); // safe drawing region - scrollok(inner, TRUE); - keypad(inner, TRUE); - wsetscrreg(inner, 0, (H - 2) - 1); - wmove(inner, 0, 0); - wrefresh(win); - wrefresh(inner); - } - - ~TerminalWin() override { - if (inner) { delwin(inner); inner = nullptr; } - } - - void printf(const char* fmt, ...) { - va_list args; va_start(args, fmt); - vw_printw(inner, fmt, args); - va_end(args); - waddch(inner, '\n'); - wrefresh(inner); - } - - // Reads a line with prompt; echoes input; supports backspace and ↑/↓ history (64 lines). - // NOTE: Caller should provide a buffer large enough for expected input. - void getline(const char* prompt, char* buffer) { - // Ensure cursor visible (blinking is terminal-dependent) - curs_set(1); - - // Move to next line if not at column 0 - int cy, cx; getyx(inner, cy, cx); - if (cx != 0) { waddch(inner, '\n'); getyx(inner, cy, cx); } - - // Write prompt and start fresh input - std::string current; - int H, W; getmaxyx(inner, H, W); - draw_line(prompt, current); - - // history navigation: index -1 means editing current line - int hist_index = -1; // 0 = oldest, size()-1 = newest - - while (true) { +public: + TerminalWin(int x, int y, int w, int h) : NcWin(x, y, w, h) { + int H, W; getmaxyx(win, H, W); + inner = derwin(win, H - 2, W - 2, 1, 1); // draw inside border + scrollok(inner, TRUE); + keypad(inner, TRUE); + wsetscrreg(inner, 0, (H - 2) - 1); + wmove(inner, 0, 0); + wrefresh(win); + wrefresh(inner); + } + + ~TerminalWin() override { if (inner) { delwin(inner); inner = nullptr; } } + + void printf(const char* fmt, ...) { + va_list args; va_start(args, fmt); + vw_printw(inner, fmt, args); + va_end(args); + waddch(inner, '\n'); + wrefresh(inner); + } + + // getline: prompt + echo + backspace + 64-entry history + DOS-style TAB cycling + void getline(const char* prompt, char* buffer) { + curs_set(1); + + // Start new line if cursor not at column 0 + int cy, cx; getyx(inner, cy, cx); + if (cx != 0) { waddch(inner, '\n'); } + + std::string current; + draw_line(prompt, current); + + int hist_index = -1; + + // Completion session state + bool comp_active = false; // true after first TAB until any edit + int comp_word_start = 0; + std::string comp_base; + std::vector comp_matches; + int comp_index = 0; + + auto reset_completion = [&](){ + comp_active = false; + comp_matches.clear(); + comp_index = 0; + }; + + while (true) { int ch = wgetch(inner); + + // ENTER: accept input if (ch == '\n' || ch == '\r') { - // finalize line - waddch(inner, '\n'); - // store into history (skip empty duplicates at tail) - if (!current.empty()) { - if (history.empty() || history.back() != current) { - history.push_back(current); - if (history.size() > kMaxHistory) history.erase(history.begin()); - } - } - // copy to caller buffer - std::strcpy(buffer, current.c_str()); - wrefresh(inner); - curs_set(0); - return; + waddch(inner, '\n'); + if (!current.empty()) { + if (history.empty() || history.back() != current) { + history.push_back(current); + if (history.size() > kMaxHistory) history.erase(history.begin()); + } + } + std::strcpy(buffer, current.c_str()); + wrefresh(inner); + curs_set(0); + return; + } + + // TAB: compute or cycle filename completions for last token + if (ch == '\t') { + prepare_completion_state(current, + comp_active, + comp_word_start, + comp_base, + comp_matches, + comp_index); + if (!comp_matches.empty()) { + // Replace token with the candidate at comp_index, then advance index + current.replace(comp_word_start, current.size() - comp_word_start, + comp_matches[comp_index]); + draw_line(prompt, current); + comp_index = (comp_index + 1) % static_cast(comp_matches.size()); + } else { + beep(); + } + continue; } - switch (ch) { - case KEY_BACKSPACE: - case 127: - case 8: - if (!current.empty()) { - current.pop_back(); - draw_line(prompt, current); - } - break; - - case KEY_UP: - if (!history.empty()) { - if (hist_index < 0) hist_index = static_cast(history.size()) - 1; - else if (hist_index > 0) hist_index--; - current = history[hist_index]; - draw_line(prompt, current); - } - break; - - case KEY_DOWN: - if (!history.empty() && hist_index >= 0) { - if (hist_index < static_cast(history.size()) - 1) { + // History navigation + if (ch == KEY_UP) { + if (!history.empty()) { + if (hist_index < 0) hist_index = static_cast(history.size()) - 1; + else if (hist_index > 0) hist_index--; + current = history[hist_index]; + draw_line(prompt, current); + } + reset_completion(); + continue; + } + if (ch == KEY_DOWN) { + if (!history.empty() && hist_index >= 0) { + if (hist_index < static_cast(history.size()) - 1) { hist_index++; current = history[hist_index]; - } else { + } else { hist_index = -1; current.clear(); - } - draw_line(prompt, current); - } - break; - - case KEY_LEFT: - case KEY_RIGHT: - case KEY_HOME: - case KEY_END: - // Keep it simple: no in-line editing; ignore navigation keys. - // (Could be added later by tracking an insertion cursor.) - break; - - default: - if (is_printable(ch)) { - // Prevent drawing beyond the line width; wrap naturally by newline if needed. - // We’ll clamp visual width to W-1 (prompt + text). - int prompt_len = visual_len(prompt); - if (prompt_len + (int)current.size() < W - 1) { - current.push_back(static_cast(ch)); - draw_line(prompt, current); - } else { - // If full, behave like a bell - beep(); - } - } - break; + } + draw_line(prompt, current); + } + reset_completion(); + continue; } - } - } - private: - WINDOW* inner{}; - static constexpr size_t kMaxHistory = 64; - std::vector history; + // Backspace + if (ch == KEY_BACKSPACE || ch == 127 || ch == 8) { + if (!current.empty()) { + current.pop_back(); + draw_line(prompt, current); + } else { + beep(); + } + reset_completion(); + continue; + } - static bool is_printable(int ch) { - return ch >= 32 && ch <= 126; - } + // Ignore lateral nav for now (keeps logic simple) + if (ch == KEY_LEFT || ch == KEY_RIGHT || ch == KEY_HOME || ch == KEY_END) { + reset_completion(); + continue; + } - static int visual_len(const char* s) { - // ASCII only here; if you need UTF-8 width, use wcwidth/mbstowcs. - int n = 0; while (s && *s) { n++; s++; } return n; - } + // Printable ASCII + if (is_printable(ch)) { + int H, W; getmaxyx(inner, H, W); + int prompt_len = (int)std::strlen(prompt); + if (prompt_len + (int)current.size() < W - 1) { + current.push_back((char)ch); + draw_line(prompt, current); + } else { + beep(); + } + reset_completion(); + continue; + } - void draw_line(const char* prompt, const std::string& text) { - int y, x; getyx(inner, y, x); - // Move to start of current line (create one if we’re at end) - // If we’re beyond last line, inner will scroll automatically. - wmove(inner, y, 0); - wclrtoeol(inner); - waddstr(inner, prompt); - waddnstr(inner, text.c_str(), (int)text.size()); - // Place cursor at end of input - getyx(inner, y, x); - wrefresh(inner); - } + // Unknown key: ignore, but end any completion session + reset_completion(); + } + } + +private: + WINDOW* inner{}; + static constexpr size_t kMaxHistory = 64; + std::vector history; + + static bool is_printable(int ch) { return ch >= 32 && ch <= 126; } + + void draw_line(const char* prompt, const std::string& text) { + int y, x; getyx(inner, y, x); + wmove(inner, y, 0); + wclrtoeol(inner); + waddstr(inner, prompt); + waddnstr(inner, text.c_str(), (int)text.size()); + wrefresh(inner); + } + + // -------- Completion core -------- + + // Build candidates once per TAB session; do NOT rebuild while cycling. + void prepare_completion_state(const std::string& current, + bool& comp_active, + int& comp_word_start, + std::string& comp_base, + std::vector& comp_matches, + int& comp_index) + { + if (comp_active) return; // already cycling on this input + + comp_matches.clear(); + comp_index = 0; + + // Last space-delimited token + comp_word_start = (int)current.find_last_of(' '); + comp_word_start = (comp_word_start == (int)std::string::npos) ? 0 : comp_word_start + 1; + comp_base = current.substr(comp_word_start); + + // Split into dir + base; if token ends with slash, browse that dir (base="") + std::string dirpath, base; + bool ends_with_slash = !comp_base.empty() && + (comp_base.back() == '/' || comp_base.back() == '\\'); + split_path(comp_base, dirpath, base); + if (ends_with_slash) base.clear(); + + // List matches; when browsing a directory after trailing slash, list all entries + comp_matches = list_matches(dirpath, base, /*browse_all=*/ends_with_slash); + + // Convert to replacements relative to original token + for (std::string& m : comp_matches) m = join_path(dirpath, m); + std::sort(comp_matches.begin(), comp_matches.end()); + + comp_active = true; // start cycling + } + + static void split_path(const std::string& token, std::string& dir, std::string& base) { + auto pos = token.find_last_of("/\\"); + if (pos == std::string::npos) { + dir = "."; + base = token; + } else { + dir = token.substr(0, pos + 1); + base = token.substr(pos + 1); + if (dir.empty()) dir = "/"; + } + } + + static std::string join_path(const std::string& dir, const std::string& name) { + if (dir == "." || dir.empty()) return name; + char last = dir.back(); + if (last == '/' || last == '\\') return dir + name; + return dir + "/" + name; + } + + static bool is_dir(const std::string& path) { + struct stat st{}; + if (stat(path.c_str(), &st) == 0) return S_ISDIR(st.st_mode); + return false; + } + + // List directory entries starting with base; if browse_all, list everything. + // Skip "." and ".." to avoid creeping with "../". + static std::vector list_matches(const std::string& dir, + const std::string& base, + bool browse_all) + { + std::vector out; + DIR* d = opendir(dir.c_str()); + if (!d) return out; + + bool show_hidden = browse_all ? true : (!base.empty() && base[0] == '.'); + + for (dirent* ent; (ent = readdir(d)); ) { + std::string name = ent->d_name; + if (name == "." || name == "..") continue; + if (!show_hidden && !name.empty() && name[0] == '.') continue; + + if (browse_all || starts_with(name, base)) { + std::string full = (dir == ".") ? name : join_path(dir, name); + if (is_dir((dir == ".") ? name : full)) name += "/"; + out.push_back(name); + } + } + closedir(d); + return out; + } + + static bool starts_with(const std::string& s, const std::string& prefix) { + if (prefix.size() > s.size()) return false; + return std::equal(prefix.begin(), prefix.end(), s.begin()); + } }; void setup_ncurses(void) { setlocale(LC_ALL, ""); // enable line/box chars in UTF-8 terminals initscr(); - noecho(); curs_set(0); + noecho(); + keypad(stdscr, TRUE); + curs_set(0); refresh(); // ensure base screen is pushed once } From 5be8b9a5e29c011f63e9d10bd192a5acfb418148 Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Fri, 17 Oct 2025 17:00:49 -0700 Subject: [PATCH 17/26] more commands, fixes to tab completion --- dbg6502/dbg6502.cpp | 563 +++++++++++++++++++++++++++----------------- 1 file changed, 342 insertions(+), 221 deletions(-) diff --git a/dbg6502/dbg6502.cpp b/dbg6502/dbg6502.cpp index 2fed87e..4db461e 100644 --- a/dbg6502/dbg6502.cpp +++ b/dbg6502/dbg6502.cpp @@ -93,264 +93,264 @@ class MemoryWin : public NcWin { // --- TerminalWin: scrolling printf + getline with history and TAB completion --- class TerminalWin : public NcWin { -public: - TerminalWin(int x, int y, int w, int h) : NcWin(x, y, w, h) { - int H, W; getmaxyx(win, H, W); - inner = derwin(win, H - 2, W - 2, 1, 1); // draw inside border - scrollok(inner, TRUE); - keypad(inner, TRUE); - wsetscrreg(inner, 0, (H - 2) - 1); - wmove(inner, 0, 0); - wrefresh(win); - wrefresh(inner); - } - - ~TerminalWin() override { if (inner) { delwin(inner); inner = nullptr; } } - - void printf(const char* fmt, ...) { - va_list args; va_start(args, fmt); - vw_printw(inner, fmt, args); - va_end(args); - waddch(inner, '\n'); - wrefresh(inner); - } - - // getline: prompt + echo + backspace + 64-entry history + DOS-style TAB cycling - void getline(const char* prompt, char* buffer) { - curs_set(1); - - // Start new line if cursor not at column 0 - int cy, cx; getyx(inner, cy, cx); - if (cx != 0) { waddch(inner, '\n'); } - - std::string current; - draw_line(prompt, current); - - int hist_index = -1; - - // Completion session state - bool comp_active = false; // true after first TAB until any edit - int comp_word_start = 0; - std::string comp_base; - std::vector comp_matches; - int comp_index = 0; - - auto reset_completion = [&](){ + public: + TerminalWin(int x, int y, int w, int h) : NcWin(x, y, w, h) { + int H, W; getmaxyx(win, H, W); + inner = derwin(win, H - 2, W - 2, 1, 1); // draw inside border + scrollok(inner, TRUE); + keypad(inner, TRUE); + wsetscrreg(inner, 0, (H - 2) - 1); + wmove(inner, 0, 0); + wrefresh(win); + wrefresh(inner); + } + + ~TerminalWin() override { if (inner) { delwin(inner); inner = nullptr; } } + + void printf(const char* fmt, ...) { + va_list args; va_start(args, fmt); + vw_printw(inner, fmt, args); + va_end(args); + waddch(inner, '\n'); + wrefresh(inner); + } + + // getline: prompt + echo + backspace + 64-entry history + DOS-style TAB cycling + void getline(const char* prompt, char* buffer) { + curs_set(1); + + // Start new line if cursor not at column 0 + int cy, cx; getyx(inner, cy, cx); + if (cx != 0) { waddch(inner, '\n'); } + + std::string current; + draw_line(prompt, current); + + int hist_index = -1; + + // Completion session state + bool comp_active = false; // true after first TAB until any edit + int comp_word_start = 0; + std::string comp_base; + std::vector comp_matches; + int comp_index = 0; + + auto reset_completion = [&](){ comp_active = false; comp_matches.clear(); comp_index = 0; - }; + }; - while (true) { + while (true) { int ch = wgetch(inner); // ENTER: accept input if (ch == '\n' || ch == '\r') { - waddch(inner, '\n'); - if (!current.empty()) { - if (history.empty() || history.back() != current) { - history.push_back(current); - if (history.size() > kMaxHistory) history.erase(history.begin()); - } - } - std::strcpy(buffer, current.c_str()); - wrefresh(inner); - curs_set(0); - return; + waddch(inner, '\n'); + if (!current.empty()) { + if (history.empty() || history.back() != current) { + history.push_back(current); + if (history.size() > kMaxHistory) history.erase(history.begin()); + } + } + std::strcpy(buffer, current.c_str()); + wrefresh(inner); + curs_set(0); + return; } // TAB: compute or cycle filename completions for last token if (ch == '\t') { - prepare_completion_state(current, - comp_active, - comp_word_start, - comp_base, - comp_matches, - comp_index); - if (!comp_matches.empty()) { - // Replace token with the candidate at comp_index, then advance index - current.replace(comp_word_start, current.size() - comp_word_start, - comp_matches[comp_index]); - draw_line(prompt, current); - comp_index = (comp_index + 1) % static_cast(comp_matches.size()); - } else { - beep(); - } - continue; + prepare_completion_state(current, + comp_active, + comp_word_start, + comp_base, + comp_matches, + comp_index); + if (!comp_matches.empty()) { + // Replace token with the candidate at comp_index, then advance index + current.replace(comp_word_start, current.size() - comp_word_start, + comp_matches[comp_index]); + draw_line(prompt, current); + comp_index = (comp_index + 1) % static_cast(comp_matches.size()); + } else { + beep(); + } + continue; } // History navigation if (ch == KEY_UP) { - if (!history.empty()) { - if (hist_index < 0) hist_index = static_cast(history.size()) - 1; - else if (hist_index > 0) hist_index--; - current = history[hist_index]; - draw_line(prompt, current); - } - reset_completion(); - continue; + if (!history.empty()) { + if (hist_index < 0) hist_index = static_cast(history.size()) - 1; + else if (hist_index > 0) hist_index--; + current = history[hist_index]; + draw_line(prompt, current); + } + reset_completion(); + continue; } if (ch == KEY_DOWN) { - if (!history.empty() && hist_index >= 0) { - if (hist_index < static_cast(history.size()) - 1) { - hist_index++; - current = history[hist_index]; - } else { - hist_index = -1; - current.clear(); - } - draw_line(prompt, current); - } - reset_completion(); - continue; + if (!history.empty() && hist_index >= 0) { + if (hist_index < static_cast(history.size()) - 1) { + hist_index++; + current = history[hist_index]; + } else { + hist_index = -1; + current.clear(); + } + draw_line(prompt, current); + } + reset_completion(); + continue; } // Backspace if (ch == KEY_BACKSPACE || ch == 127 || ch == 8) { - if (!current.empty()) { - current.pop_back(); - draw_line(prompt, current); - } else { - beep(); - } - reset_completion(); - continue; + if (!current.empty()) { + current.pop_back(); + draw_line(prompt, current); + } else { + beep(); + } + reset_completion(); + continue; } // Ignore lateral nav for now (keeps logic simple) if (ch == KEY_LEFT || ch == KEY_RIGHT || ch == KEY_HOME || ch == KEY_END) { - reset_completion(); - continue; + reset_completion(); + continue; } // Printable ASCII if (is_printable(ch)) { - int H, W; getmaxyx(inner, H, W); - int prompt_len = (int)std::strlen(prompt); - if (prompt_len + (int)current.size() < W - 1) { - current.push_back((char)ch); - draw_line(prompt, current); - } else { - beep(); - } - reset_completion(); - continue; + int H, W; getmaxyx(inner, H, W); + int prompt_len = (int)std::strlen(prompt); + if (prompt_len + (int)current.size() < W - 1) { + current.push_back((char)ch); + draw_line(prompt, current); + } else { + beep(); + } + reset_completion(); + continue; } // Unknown key: ignore, but end any completion session reset_completion(); - } - } - -private: - WINDOW* inner{}; - static constexpr size_t kMaxHistory = 64; - std::vector history; - - static bool is_printable(int ch) { return ch >= 32 && ch <= 126; } - - void draw_line(const char* prompt, const std::string& text) { - int y, x; getyx(inner, y, x); - wmove(inner, y, 0); - wclrtoeol(inner); - waddstr(inner, prompt); - waddnstr(inner, text.c_str(), (int)text.size()); - wrefresh(inner); - } - - // -------- Completion core -------- - - // Build candidates once per TAB session; do NOT rebuild while cycling. - void prepare_completion_state(const std::string& current, - bool& comp_active, - int& comp_word_start, - std::string& comp_base, - std::vector& comp_matches, - int& comp_index) - { - if (comp_active) return; // already cycling on this input - - comp_matches.clear(); - comp_index = 0; - - // Last space-delimited token - comp_word_start = (int)current.find_last_of(' '); - comp_word_start = (comp_word_start == (int)std::string::npos) ? 0 : comp_word_start + 1; - comp_base = current.substr(comp_word_start); - - // Split into dir + base; if token ends with slash, browse that dir (base="") - std::string dirpath, base; - bool ends_with_slash = !comp_base.empty() && - (comp_base.back() == '/' || comp_base.back() == '\\'); - split_path(comp_base, dirpath, base); - if (ends_with_slash) base.clear(); - - // List matches; when browsing a directory after trailing slash, list all entries - comp_matches = list_matches(dirpath, base, /*browse_all=*/ends_with_slash); - - // Convert to replacements relative to original token - for (std::string& m : comp_matches) m = join_path(dirpath, m); - std::sort(comp_matches.begin(), comp_matches.end()); - - comp_active = true; // start cycling - } - - static void split_path(const std::string& token, std::string& dir, std::string& base) { - auto pos = token.find_last_of("/\\"); - if (pos == std::string::npos) { + } + } + + private: + WINDOW* inner{}; + static constexpr size_t kMaxHistory = 64; + std::vector history; + + static bool is_printable(int ch) { return ch >= 32 && ch <= 126; } + + void draw_line(const char* prompt, const std::string& text) { + int y, x; getyx(inner, y, x); + wmove(inner, y, 0); + wclrtoeol(inner); + waddstr(inner, prompt); + waddnstr(inner, text.c_str(), (int)text.size()); + wrefresh(inner); + } + + // -------- Completion core -------- + + // Build candidates once per TAB session; do NOT rebuild while cycling. + void prepare_completion_state(const std::string& current, + bool& comp_active, + int& comp_word_start, + std::string& comp_base, + std::vector& comp_matches, + int& comp_index) + { + if (comp_active) return; // already cycling on this input + + comp_matches.clear(); + comp_index = 0; + + // Last space-delimited token + comp_word_start = (int)current.find_last_of(' '); + comp_word_start = (comp_word_start == (int)std::string::npos) ? 0 : comp_word_start + 1; + comp_base = current.substr(comp_word_start); + + // Split into dir + base; if token ends with slash, browse that dir (base="") + std::string dirpath, base; + bool ends_with_slash = !comp_base.empty() && + (comp_base.back() == '/' || comp_base.back() == '\\'); + split_path(comp_base, dirpath, base); + if (ends_with_slash) base.clear(); + + // List matches; when browsing a directory after trailing slash, list all entries + comp_matches = list_matches(dirpath, base, /*browse_all=*/ends_with_slash); + + // Convert to replacements relative to original token + for (std::string& m : comp_matches) m = join_path(dirpath, m); + std::sort(comp_matches.begin(), comp_matches.end()); + + comp_active = true; // start cycling + } + + static void split_path(const std::string& token, std::string& dir, std::string& base) { + auto pos = token.find_last_of("/\\"); + if (pos == std::string::npos) { dir = "."; base = token; - } else { + } else { dir = token.substr(0, pos + 1); base = token.substr(pos + 1); if (dir.empty()) dir = "/"; - } - } - - static std::string join_path(const std::string& dir, const std::string& name) { - if (dir == "." || dir.empty()) return name; - char last = dir.back(); - if (last == '/' || last == '\\') return dir + name; - return dir + "/" + name; - } - - static bool is_dir(const std::string& path) { - struct stat st{}; - if (stat(path.c_str(), &st) == 0) return S_ISDIR(st.st_mode); - return false; - } - - // List directory entries starting with base; if browse_all, list everything. - // Skip "." and ".." to avoid creeping with "../". - static std::vector list_matches(const std::string& dir, - const std::string& base, - bool browse_all) - { - std::vector out; - DIR* d = opendir(dir.c_str()); - if (!d) return out; - - bool show_hidden = browse_all ? true : (!base.empty() && base[0] == '.'); - - for (dirent* ent; (ent = readdir(d)); ) { + } + } + + static std::string join_path(const std::string& dir, const std::string& name) { + if (dir == "." || dir.empty()) return name; + char last = dir.back(); + if (last == '/' || last == '\\') return dir + name; + return dir + "/" + name; + } + + static bool is_dir(const std::string& path) { + struct stat st{}; + if (stat(path.c_str(), &st) == 0) return S_ISDIR(st.st_mode); + return false; + } + + // List directory entries starting with base; if browse_all, list everything. + // Skip "." and ".." to avoid creeping with "../". + static std::vector list_matches(const std::string& dir, + const std::string& base, + bool browse_all) + { + std::vector out; + DIR* d = opendir(dir.c_str()); + if (!d) return out; + + bool show_hidden = browse_all ? true : (!base.empty() && base[0] == '.'); + + for (dirent* ent; (ent = readdir(d)); ) { std::string name = ent->d_name; if (name == "." || name == "..") continue; if (!show_hidden && !name.empty() && name[0] == '.') continue; if (browse_all || starts_with(name, base)) { - std::string full = (dir == ".") ? name : join_path(dir, name); - if (is_dir((dir == ".") ? name : full)) name += "/"; - out.push_back(name); + std::string full = (dir == ".") ? name : join_path(dir, name); + if (is_dir((dir == ".") ? name : full)) name += "/"; + out.push_back(name); } - } - closedir(d); - return out; - } - - static bool starts_with(const std::string& s, const std::string& prefix) { - if (prefix.size() > s.size()) return false; - return std::equal(prefix.begin(), prefix.end(), s.begin()); - } + } + closedir(d); + return out; + } + + static bool starts_with(const std::string& s, const std::string& prefix) { + if (prefix.size() > s.size()) return false; + return std::equal(prefix.begin(), prefix.end(), s.begin()); + } }; void setup_ncurses(void) { @@ -376,7 +376,7 @@ TerminalWin *displaywin = NULL; void bus_write(uint16_t addr, uint8_t data) { displaywin->printf("write %04x %02x", addr, data); - if (write_protect[addr / 8] & (1 << (addr & 0x7))) { + if (write_protect[addr >> 3] & (1 << (addr & 0x7))) { bus_error = true; displaywin->printf("BUS ERROR"); } @@ -425,6 +425,9 @@ void do_a(char *p) { p++; cpu->SetA(parsenum(p)); } + else { + termwin->printf("parse error"); + } } void do_x(char *p) { @@ -433,6 +436,9 @@ void do_x(char *p) { p++; cpu->SetX(parsenum(p)); } + else { + termwin->printf("parse error"); + } } void do_y(char *p) { @@ -441,6 +447,9 @@ void do_y(char *p) { p++; cpu->SetY(parsenum(p)); } + else { + termwin->printf("parse error"); + } } void do_pc(char *p) { @@ -449,6 +458,9 @@ void do_pc(char *p) { p++; cpu->SetPC(parsenum(p)); } + else { + termwin->printf("parse error"); + } } void do_sp(char *p) { @@ -457,6 +469,9 @@ void do_sp(char *p) { p++; cpu->SetS(parsenum(p)); } + else { + termwin->printf("parse error"); + } } void do_sr(char *p) { @@ -465,6 +480,109 @@ void do_sr(char *p) { p++; cpu->SetP(parsenum(p)); } + else { + termwin->printf("parse error"); + } +} + +void do_wpset(char *p) { + int begin, end; + p = strchr(p, ' '); + if (p) { + p++; + char *q = strchr(p, '-'); + if (q) { + *q = 0; + q++; + begin = parsenum(p); + end = parsenum(q); + + displaywin->printf("wp set %04x to %04x", begin, end); + while (begin <= end && (begin & 0x7)) { + write_protect[begin >> 3] |= (1 << (begin & 0x7)); + begin++; + } + while (!(begin & 0x7) && (begin + 8) <= end) { + write_protect[begin >> 3] = 0xFF; + begin += 8; + } + while (begin <= end && (begin & 0x7)) { + write_protect[begin >> 3] |= (1 << (begin & 0x7)); + begin++; + } + } + else { + displaywin->printf("wp set %04x", begin); + begin = parsenum(p); + write_protect[begin >> 3] |= (1 << (begin & 0x7)); + } + } + else { + termwin->printf("parse error"); + } +} + +void do_wpclr(char *p) { + int begin, end; + p = strchr(p, ' '); + if (p) { + p++; + char *q = strchr(p, '-'); + if (q) { + *q = 0; + q++; + begin = parsenum(p); + end = parsenum(q); + + displaywin->printf("wp clr %04x to %04x", begin, end); + while (begin <= end && (begin & 0x7)) { + write_protect[begin >> 3] &= ~(1 << (begin & 0x7)); + begin++; + } + while (!(begin & 0x7) && (begin + 8) <= end) { + write_protect[begin >> 3] = 0x00; + begin += 8; + } + while (begin <= end && (begin & 0x7)) { + write_protect[begin >> 3] &= ~(1 << (begin & 0x7)); + begin++; + } + } + else { + begin = parsenum(p); + displaywin->printf("wp clr %04x", begin); + write_protect[begin >> 3] &= ~(1 << (begin & 0x7)); + } + } + else { + termwin->printf("parse error"); + } +} + +void do_breakset(char *p) { + p = strchr(p, ' '); + if (p) { + p++; + int pc = parsenum(p); + displaywin->printf("break set %04x", pc); + breakpoint[pc >> 3] |= (1 << (pc & 0x7)); + } + else { + termwin->printf("parse error"); + } +} + +void do_breakclr(char *p) { + p = strchr(p, ' '); + if (p) { + p++; + int pc = parsenum(p); + displaywin->printf("break clr %04x", pc); + breakpoint[pc >> 3] &= ~(1 << (pc & 0x7)); + } + else { + termwin->printf("parse error"); + } } void do_help(char *); @@ -475,13 +593,16 @@ struct Commands { const char *help; handler exe; } commands[] = { - { "A=", "", "set the A register", do_a }, - { "X=", "", "set the X register", do_x }, - { "Y=", "", "set the Y register", do_y }, - { "PC=", "", "set the PC", do_pc }, - { "SR=", "", "set the STATUS", do_sr }, - { "SP=", "", "set the STACK POINTER", do_sp }, - { "reset", NULL, "reset the cpu", do_reset }, + { "A=", "", "set the A register", do_a }, + { "X=", "", "set the X register", do_x }, + { "Y=", "", "set the Y register", do_y }, + { "PC=", "", "set the PC", do_pc }, + { "SR=", "", "set the STATUS", do_sr }, + { "SP=", "", "set the STACK POINTER", do_sp }, + { "wpset", " [-]", "set write protect on address or range of addresses", do_wpset }, + { "wpclr", " [-]", "clr write protect on address or range of addresses", do_wpclr }, + { "break", " ", "set a breakpoint", do_breakset }, + { "unbreak", " ", "clr a breakpoint", do_breakclr }, { "reset", NULL, "reset the cpu", do_reset }, { "quit", NULL, "quit the program", do_quit }, { "exit", NULL, "same as quit", do_quit }, From 270070f2a3ca14c7aa1a557a92888f89a3b1a543 Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Fri, 17 Oct 2025 17:05:10 -0700 Subject: [PATCH 18/26] mem command --- dbg6502/dbg6502.cpp | 845 ++++++++++++++++++++++---------------------- 1 file changed, 431 insertions(+), 414 deletions(-) diff --git a/dbg6502/dbg6502.cpp b/dbg6502/dbg6502.cpp index 4db461e..239b52c 100644 --- a/dbg6502/dbg6502.cpp +++ b/dbg6502/dbg6502.cpp @@ -87,498 +87,513 @@ class MemoryWin : public NcWin { } } - private: - uint16_t address; -}; - -// --- TerminalWin: scrolling printf + getline with history and TAB completion --- -class TerminalWin : public NcWin { - public: - TerminalWin(int x, int y, int w, int h) : NcWin(x, y, w, h) { - int H, W; getmaxyx(win, H, W); - inner = derwin(win, H - 2, W - 2, 1, 1); // draw inside border - scrollok(inner, TRUE); - keypad(inner, TRUE); - wsetscrreg(inner, 0, (H - 2) - 1); - wmove(inner, 0, 0); - wrefresh(win); - wrefresh(inner); - } - - ~TerminalWin() override { if (inner) { delwin(inner); inner = nullptr; } } - - void printf(const char* fmt, ...) { - va_list args; va_start(args, fmt); - vw_printw(inner, fmt, args); - va_end(args); - waddch(inner, '\n'); - wrefresh(inner); - } + void update(void) { + set_address(address); // minor kludge, but gets the job done + } - // getline: prompt + echo + backspace + 64-entry history + DOS-style TAB cycling - void getline(const char* prompt, char* buffer) { - curs_set(1); + private: + uint16_t address; + }; + + // --- TerminalWin: scrolling printf + getline with history and TAB completion --- + class TerminalWin : public NcWin { + public: + TerminalWin(int x, int y, int w, int h) : NcWin(x, y, w, h) { + int H, W; getmaxyx(win, H, W); + inner = derwin(win, H - 2, W - 2, 1, 1); // draw inside border + scrollok(inner, TRUE); + keypad(inner, TRUE); + wsetscrreg(inner, 0, (H - 2) - 1); + wmove(inner, 0, 0); + wrefresh(win); + wrefresh(inner); + } - // Start new line if cursor not at column 0 - int cy, cx; getyx(inner, cy, cx); - if (cx != 0) { waddch(inner, '\n'); } + ~TerminalWin() override { if (inner) { delwin(inner); inner = nullptr; } } - std::string current; - draw_line(prompt, current); + void printf(const char* fmt, ...) { + va_list args; va_start(args, fmt); + vw_printw(inner, fmt, args); + va_end(args); + waddch(inner, '\n'); + wrefresh(inner); + } - int hist_index = -1; + // getline: prompt + echo + backspace + 64-entry history + DOS-style TAB cycling + void getline(const char* prompt, char* buffer) { + curs_set(1); + + // Start new line if cursor not at column 0 + int cy, cx; getyx(inner, cy, cx); + if (cx != 0) { waddch(inner, '\n'); } + + std::string current; + draw_line(prompt, current); + + int hist_index = -1; + + // Completion session state + bool comp_active = false; // true after first TAB until any edit + int comp_word_start = 0; + std::string comp_base; + std::vector comp_matches; + int comp_index = 0; + + auto reset_completion = [&](){ + comp_active = false; + comp_matches.clear(); + comp_index = 0; + }; + + while (true) { + int ch = wgetch(inner); + + // ENTER: accept input + if (ch == '\n' || ch == '\r') { + waddch(inner, '\n'); + if (!current.empty()) { + if (history.empty() || history.back() != current) { + history.push_back(current); + if (history.size() > kMaxHistory) history.erase(history.begin()); + } + } + std::strcpy(buffer, current.c_str()); + wrefresh(inner); + curs_set(0); + return; + } - // Completion session state - bool comp_active = false; // true after first TAB until any edit - int comp_word_start = 0; - std::string comp_base; - std::vector comp_matches; - int comp_index = 0; + // TAB: compute or cycle filename completions for last token + if (ch == '\t') { + prepare_completion_state(current, + comp_active, + comp_word_start, + comp_base, + comp_matches, + comp_index); + if (!comp_matches.empty()) { + // Replace token with the candidate at comp_index, then advance index + current.replace(comp_word_start, current.size() - comp_word_start, + comp_matches[comp_index]); + draw_line(prompt, current); + comp_index = (comp_index + 1) % static_cast(comp_matches.size()); + } else { + beep(); + } + continue; + } - auto reset_completion = [&](){ - comp_active = false; - comp_matches.clear(); - comp_index = 0; - }; - - while (true) { - int ch = wgetch(inner); - - // ENTER: accept input - if (ch == '\n' || ch == '\r') { - waddch(inner, '\n'); - if (!current.empty()) { - if (history.empty() || history.back() != current) { - history.push_back(current); - if (history.size() > kMaxHistory) history.erase(history.begin()); + // History navigation + if (ch == KEY_UP) { + if (!history.empty()) { + if (hist_index < 0) hist_index = static_cast(history.size()) - 1; + else if (hist_index > 0) hist_index--; + current = history[hist_index]; + draw_line(prompt, current); } + reset_completion(); + continue; + } + if (ch == KEY_DOWN) { + if (!history.empty() && hist_index >= 0) { + if (hist_index < static_cast(history.size()) - 1) { + hist_index++; + current = history[hist_index]; + } else { + hist_index = -1; + current.clear(); + } + draw_line(prompt, current); + } + reset_completion(); + continue; } - std::strcpy(buffer, current.c_str()); - wrefresh(inner); - curs_set(0); - return; - } - // TAB: compute or cycle filename completions for last token - if (ch == '\t') { - prepare_completion_state(current, - comp_active, - comp_word_start, - comp_base, - comp_matches, - comp_index); - if (!comp_matches.empty()) { - // Replace token with the candidate at comp_index, then advance index - current.replace(comp_word_start, current.size() - comp_word_start, - comp_matches[comp_index]); - draw_line(prompt, current); - comp_index = (comp_index + 1) % static_cast(comp_matches.size()); - } else { - beep(); + // Backspace + if (ch == KEY_BACKSPACE || ch == 127 || ch == 8) { + if (!current.empty()) { + current.pop_back(); + draw_line(prompt, current); + } else { + beep(); + } + reset_completion(); + continue; } - continue; - } - // History navigation - if (ch == KEY_UP) { - if (!history.empty()) { - if (hist_index < 0) hist_index = static_cast(history.size()) - 1; - else if (hist_index > 0) hist_index--; - current = history[hist_index]; - draw_line(prompt, current); + // Ignore lateral nav for now (keeps logic simple) + if (ch == KEY_LEFT || ch == KEY_RIGHT || ch == KEY_HOME || ch == KEY_END) { + reset_completion(); + continue; } - reset_completion(); - continue; - } - if (ch == KEY_DOWN) { - if (!history.empty() && hist_index >= 0) { - if (hist_index < static_cast(history.size()) - 1) { - hist_index++; - current = history[hist_index]; + + // Printable ASCII + if (is_printable(ch)) { + int H, W; getmaxyx(inner, H, W); + int prompt_len = (int)std::strlen(prompt); + if (prompt_len + (int)current.size() < W - 1) { + current.push_back((char)ch); + draw_line(prompt, current); } else { - hist_index = -1; - current.clear(); + beep(); } - draw_line(prompt, current); + reset_completion(); + continue; } - reset_completion(); - continue; - } - // Backspace - if (ch == KEY_BACKSPACE || ch == 127 || ch == 8) { - if (!current.empty()) { - current.pop_back(); - draw_line(prompt, current); - } else { - beep(); - } + // Unknown key: ignore, but end any completion session reset_completion(); - continue; } + } - // Ignore lateral nav for now (keeps logic simple) - if (ch == KEY_LEFT || ch == KEY_RIGHT || ch == KEY_HOME || ch == KEY_END) { - reset_completion(); - continue; - } + private: + WINDOW* inner{}; + static constexpr size_t kMaxHistory = 64; + std::vector history; - // Printable ASCII - if (is_printable(ch)) { - int H, W; getmaxyx(inner, H, W); - int prompt_len = (int)std::strlen(prompt); - if (prompt_len + (int)current.size() < W - 1) { - current.push_back((char)ch); - draw_line(prompt, current); - } else { - beep(); - } - reset_completion(); - continue; - } + static bool is_printable(int ch) { return ch >= 32 && ch <= 126; } - // Unknown key: ignore, but end any completion session - reset_completion(); + void draw_line(const char* prompt, const std::string& text) { + int y, x; getyx(inner, y, x); + wmove(inner, y, 0); + wclrtoeol(inner); + waddstr(inner, prompt); + waddnstr(inner, text.c_str(), (int)text.size()); + wrefresh(inner); } - } - private: - WINDOW* inner{}; - static constexpr size_t kMaxHistory = 64; - std::vector history; + // -------- Completion core -------- - static bool is_printable(int ch) { return ch >= 32 && ch <= 126; } + // Build candidates once per TAB session; do NOT rebuild while cycling. + void prepare_completion_state(const std::string& current, + bool& comp_active, + int& comp_word_start, + std::string& comp_base, + std::vector& comp_matches, + int& comp_index) + { + if (comp_active) return; // already cycling on this input - void draw_line(const char* prompt, const std::string& text) { - int y, x; getyx(inner, y, x); - wmove(inner, y, 0); - wclrtoeol(inner); - waddstr(inner, prompt); - waddnstr(inner, text.c_str(), (int)text.size()); - wrefresh(inner); - } + comp_matches.clear(); + comp_index = 0; - // -------- Completion core -------- - - // Build candidates once per TAB session; do NOT rebuild while cycling. - void prepare_completion_state(const std::string& current, - bool& comp_active, - int& comp_word_start, - std::string& comp_base, - std::vector& comp_matches, - int& comp_index) - { - if (comp_active) return; // already cycling on this input - - comp_matches.clear(); - comp_index = 0; - - // Last space-delimited token - comp_word_start = (int)current.find_last_of(' '); - comp_word_start = (comp_word_start == (int)std::string::npos) ? 0 : comp_word_start + 1; - comp_base = current.substr(comp_word_start); - - // Split into dir + base; if token ends with slash, browse that dir (base="") - std::string dirpath, base; - bool ends_with_slash = !comp_base.empty() && - (comp_base.back() == '/' || comp_base.back() == '\\'); - split_path(comp_base, dirpath, base); - if (ends_with_slash) base.clear(); - - // List matches; when browsing a directory after trailing slash, list all entries - comp_matches = list_matches(dirpath, base, /*browse_all=*/ends_with_slash); - - // Convert to replacements relative to original token - for (std::string& m : comp_matches) m = join_path(dirpath, m); - std::sort(comp_matches.begin(), comp_matches.end()); - - comp_active = true; // start cycling - } + // Last space-delimited token + comp_word_start = (int)current.find_last_of(' '); + comp_word_start = (comp_word_start == (int)std::string::npos) ? 0 : comp_word_start + 1; + comp_base = current.substr(comp_word_start); - static void split_path(const std::string& token, std::string& dir, std::string& base) { - auto pos = token.find_last_of("/\\"); - if (pos == std::string::npos) { - dir = "."; - base = token; - } else { - dir = token.substr(0, pos + 1); - base = token.substr(pos + 1); - if (dir.empty()) dir = "/"; - } - } + // Split into dir + base; if token ends with slash, browse that dir (base="") + std::string dirpath, base; + bool ends_with_slash = !comp_base.empty() && + (comp_base.back() == '/' || comp_base.back() == '\\'); + split_path(comp_base, dirpath, base); + if (ends_with_slash) base.clear(); - static std::string join_path(const std::string& dir, const std::string& name) { - if (dir == "." || dir.empty()) return name; - char last = dir.back(); - if (last == '/' || last == '\\') return dir + name; - return dir + "/" + name; - } + // List matches; when browsing a directory after trailing slash, list all entries + comp_matches = list_matches(dirpath, base, /*browse_all=*/ends_with_slash); - static bool is_dir(const std::string& path) { - struct stat st{}; - if (stat(path.c_str(), &st) == 0) return S_ISDIR(st.st_mode); - return false; - } + // Convert to replacements relative to original token + for (std::string& m : comp_matches) m = join_path(dirpath, m); + std::sort(comp_matches.begin(), comp_matches.end()); - // List directory entries starting with base; if browse_all, list everything. - // Skip "." and ".." to avoid creeping with "../". - static std::vector list_matches(const std::string& dir, - const std::string& base, - bool browse_all) - { - std::vector out; - DIR* d = opendir(dir.c_str()); - if (!d) return out; - - bool show_hidden = browse_all ? true : (!base.empty() && base[0] == '.'); - - for (dirent* ent; (ent = readdir(d)); ) { - std::string name = ent->d_name; - if (name == "." || name == "..") continue; - if (!show_hidden && !name.empty() && name[0] == '.') continue; - - if (browse_all || starts_with(name, base)) { - std::string full = (dir == ".") ? name : join_path(dir, name); - if (is_dir((dir == ".") ? name : full)) name += "/"; - out.push_back(name); + comp_active = true; // start cycling + } + + static void split_path(const std::string& token, std::string& dir, std::string& base) { + auto pos = token.find_last_of("/\\"); + if (pos == std::string::npos) { + dir = "."; + base = token; + } else { + dir = token.substr(0, pos + 1); + base = token.substr(pos + 1); + if (dir.empty()) dir = "/"; } } - closedir(d); - return out; - } - static bool starts_with(const std::string& s, const std::string& prefix) { - if (prefix.size() > s.size()) return false; - return std::equal(prefix.begin(), prefix.end(), s.begin()); - } -}; + static std::string join_path(const std::string& dir, const std::string& name) { + if (dir == "." || dir.empty()) return name; + char last = dir.back(); + if (last == '/' || last == '\\') return dir + name; + return dir + "/" + name; + } -void setup_ncurses(void) { - setlocale(LC_ALL, ""); // enable line/box chars in UTF-8 terminals - initscr(); - noecho(); - keypad(stdscr, TRUE); - curs_set(0); - refresh(); // ensure base screen is pushed once -} + static bool is_dir(const std::string& path) { + struct stat st{}; + if (stat(path.c_str(), &st) == 0) return S_ISDIR(st.st_mode); + return false; + } -void teardown_ncurses(void) { - refresh(); - endwin(); -} + // List directory entries starting with base; if browse_all, list everything. + // Skip "." and ".." to avoid creeping with "../". + static std::vector list_matches(const std::string& dir, + const std::string& base, + bool browse_all) + { + std::vector out; + DIR* d = opendir(dir.c_str()); + if (!d) return out; + + bool show_hidden = browse_all ? true : (!base.empty() && base[0] == '.'); + + for (dirent* ent; (ent = readdir(d)); ) { + std::string name = ent->d_name; + if (name == "." || name == "..") continue; + if (!show_hidden && !name.empty() && name[0] == '.') continue; + + if (browse_all || starts_with(name, base)) { + std::string full = (dir == ".") ? name : join_path(dir, name); + if (is_dir((dir == ".") ? name : full)) name += "/"; + out.push_back(name); + } + } + closedir(d); + return out; + } -bool done = false; -bool bus_error = false; -MemoryWin *memwin = NULL; -TerminalWin *termwin = NULL; -RegisterWin *regwin = NULL; -TerminalWin *displaywin = NULL; - -void bus_write(uint16_t addr, uint8_t data) { - displaywin->printf("write %04x %02x", addr, data); - if (write_protect[addr >> 3] & (1 << (addr & 0x7))) { - bus_error = true; - displaywin->printf("BUS ERROR"); - } - else { - ram[addr] = data; + static bool starts_with(const std::string& s, const std::string& prefix) { + if (prefix.size() > s.size()) return false; + return std::equal(prefix.begin(), prefix.end(), s.begin()); + } + }; + + void setup_ncurses(void) { + setlocale(LC_ALL, ""); // enable line/box chars in UTF-8 terminals + initscr(); + noecho(); + keypad(stdscr, TRUE); + curs_set(0); + refresh(); // ensure base screen is pushed once } -} -uint8_t bus_read(uint16_t addr) { - displaywin->printf("read %04x %02x", addr, ram[addr]); - return ram[addr]; -} + void teardown_ncurses(void) { + refresh(); + endwin(); + } -void tick(mos6502 *) { -} + bool done = false; + bool bus_error = false; + MemoryWin *memwin = NULL; + TerminalWin *termwin = NULL; + RegisterWin *regwin = NULL; + TerminalWin *displaywin = NULL; + + void bus_write(uint16_t addr, uint8_t data) { + displaywin->printf("write %04x %02x", addr, data); + if (write_protect[addr >> 3] & (1 << (addr & 0x7))) { + bus_error = true; + displaywin->printf("BUS ERROR"); + } + else { + ram[addr] = data; + } + } -typedef void (*handler)(char *p); + uint8_t bus_read(uint16_t addr) { + displaywin->printf("read %04x %02x", addr, ram[addr]); + return ram[addr]; + } -void do_quit(char *) { - done = 1; -} + void tick(mos6502 *) { + } -void do_reset(char *) { - cpu->Reset(); -} + typedef void (*handler)(char *p); -int parsenum(char *p) { - switch (*p) { - case 'x': - case '$': - return strtoul(p + 1, NULL, 16); - case 'o': - case '@': - return strtoul(p + 1, NULL, 8); - case 'b': - case '%': - return strtoul(p + 1, NULL, 2); - default: - return strtoul(p, NULL, 0); // intentional, to allow 0x hex, 0b binary, and 0 octal + void do_quit(char *) { + done = 1; } -} -void do_a(char *p) { - p = strchr(p, '='); - if (p) { - p++; - cpu->SetA(parsenum(p)); - } - else { - termwin->printf("parse error"); + void do_reset(char *) { + cpu->Reset(); } -} -void do_x(char *p) { - p = strchr(p, '='); - if (p) { - p++; - cpu->SetX(parsenum(p)); - } - else { - termwin->printf("parse error"); + int parsenum(char *p) { + switch (*p) { + case 'x': + case '$': + return strtoul(p + 1, NULL, 16); + case 'o': + case '@': + return strtoul(p + 1, NULL, 8); + case 'b': + case '%': + return strtoul(p + 1, NULL, 2); + default: + return strtoul(p, NULL, 0); // intentional, to allow 0x hex, 0b binary, and 0 octal + } } -} -void do_y(char *p) { - p = strchr(p, '='); - if (p) { - p++; - cpu->SetY(parsenum(p)); - } - else { - termwin->printf("parse error"); + void do_a(char *p) { + p = strchr(p, '='); + if (p) { + p++; + cpu->SetA(parsenum(p)); + } + else { + termwin->printf("parse error"); + } } -} -void do_pc(char *p) { - p = strchr(p, '='); - if (p) { - p++; - cpu->SetPC(parsenum(p)); - } - else { - termwin->printf("parse error"); + void do_x(char *p) { + p = strchr(p, '='); + if (p) { + p++; + cpu->SetX(parsenum(p)); + } + else { + termwin->printf("parse error"); + } } -} -void do_sp(char *p) { - p = strchr(p, '='); - if (p) { - p++; - cpu->SetS(parsenum(p)); + void do_y(char *p) { + p = strchr(p, '='); + if (p) { + p++; + cpu->SetY(parsenum(p)); + } + else { + termwin->printf("parse error"); + } } - else { - termwin->printf("parse error"); + + void do_pc(char *p) { + p = strchr(p, '='); + if (p) { + p++; + cpu->SetPC(parsenum(p)); + } + else { + termwin->printf("parse error"); + } } -} -void do_sr(char *p) { - p = strchr(p, '='); - if (p) { - p++; - cpu->SetP(parsenum(p)); + void do_sp(char *p) { + p = strchr(p, '='); + if (p) { + p++; + cpu->SetS(parsenum(p)); + } + else { + termwin->printf("parse error"); + } } - else { - termwin->printf("parse error"); + + void do_sr(char *p) { + p = strchr(p, '='); + if (p) { + p++; + cpu->SetP(parsenum(p)); + } + else { + termwin->printf("parse error"); + } } -} -void do_wpset(char *p) { - int begin, end; - p = strchr(p, ' '); - if (p) { - p++; - char *q = strchr(p, '-'); - if (q) { - *q = 0; - q++; - begin = parsenum(p); - end = parsenum(q); - - displaywin->printf("wp set %04x to %04x", begin, end); - while (begin <= end && (begin & 0x7)) { - write_protect[begin >> 3] |= (1 << (begin & 0x7)); - begin++; - } - while (!(begin & 0x7) && (begin + 8) <= end) { - write_protect[begin >> 3] = 0xFF; - begin += 8; + void do_wpset(char *p) { + int begin, end; + p = strchr(p, ' '); + if (p) { + p++; + char *q = strchr(p, '-'); + if (q) { + *q = 0; + q++; + begin = parsenum(p); + end = parsenum(q); + + displaywin->printf("wp set %04x to %04x", begin, end); + while (begin <= end && (begin & 0x7)) { + write_protect[begin >> 3] |= (1 << (begin & 0x7)); + begin++; + } + while (!(begin & 0x7) && (begin + 8) <= end) { + write_protect[begin >> 3] = 0xFF; + begin += 8; + } + while (begin <= end && (begin & 0x7)) { + write_protect[begin >> 3] |= (1 << (begin & 0x7)); + begin++; + } } - while (begin <= end && (begin & 0x7)) { + else { + displaywin->printf("wp set %04x", begin); + begin = parsenum(p); write_protect[begin >> 3] |= (1 << (begin & 0x7)); - begin++; } } else { - displaywin->printf("wp set %04x", begin); - begin = parsenum(p); - write_protect[begin >> 3] |= (1 << (begin & 0x7)); + termwin->printf("parse error"); } } - else { - termwin->printf("parse error"); - } -} -void do_wpclr(char *p) { - int begin, end; - p = strchr(p, ' '); - if (p) { - p++; - char *q = strchr(p, '-'); - if (q) { - *q = 0; - q++; - begin = parsenum(p); - end = parsenum(q); - - displaywin->printf("wp clr %04x to %04x", begin, end); - while (begin <= end && (begin & 0x7)) { - write_protect[begin >> 3] &= ~(1 << (begin & 0x7)); - begin++; - } - while (!(begin & 0x7) && (begin + 8) <= end) { - write_protect[begin >> 3] = 0x00; - begin += 8; + void do_wpclr(char *p) { + int begin, end; + p = strchr(p, ' '); + if (p) { + p++; + char *q = strchr(p, '-'); + if (q) { + *q = 0; + q++; + begin = parsenum(p); + end = parsenum(q); + + displaywin->printf("wp clr %04x to %04x", begin, end); + while (begin <= end && (begin & 0x7)) { + write_protect[begin >> 3] &= ~(1 << (begin & 0x7)); + begin++; + } + while (!(begin & 0x7) && (begin + 8) <= end) { + write_protect[begin >> 3] = 0x00; + begin += 8; + } + while (begin <= end && (begin & 0x7)) { + write_protect[begin >> 3] &= ~(1 << (begin & 0x7)); + begin++; + } } - while (begin <= end && (begin & 0x7)) { + else { + begin = parsenum(p); + displaywin->printf("wp clr %04x", begin); write_protect[begin >> 3] &= ~(1 << (begin & 0x7)); - begin++; } } else { - begin = parsenum(p); - displaywin->printf("wp clr %04x", begin); - write_protect[begin >> 3] &= ~(1 << (begin & 0x7)); + termwin->printf("parse error"); } } - else { - termwin->printf("parse error"); - } -} -void do_breakset(char *p) { - p = strchr(p, ' '); - if (p) { - p++; - int pc = parsenum(p); - displaywin->printf("break set %04x", pc); - breakpoint[pc >> 3] |= (1 << (pc & 0x7)); + void do_breakset(char *p) { + p = strchr(p, ' '); + if (p) { + p++; + int pc = parsenum(p); + displaywin->printf("break set %04x", pc); + breakpoint[pc >> 3] |= (1 << (pc & 0x7)); + } + else { + termwin->printf("parse error"); + } } - else { - termwin->printf("parse error"); + + void do_breakclr(char *p) { + p = strchr(p, ' '); + if (p) { + p++; + int pc = parsenum(p); + displaywin->printf("break clr %04x", pc); + breakpoint[pc >> 3] &= ~(1 << (pc & 0x7)); + } + else { + termwin->printf("parse error"); + } } -} -void do_breakclr(char *p) { - p = strchr(p, ' '); - if (p) { - p++; - int pc = parsenum(p); - displaywin->printf("break clr %04x", pc); - breakpoint[pc >> 3] &= ~(1 << (pc & 0x7)); + void do_mem(char *p) { + p = strchr(p, ' '); + if (p) { + p++; + memwin->set_address(parsenum(p) & ~0x0F); } else { termwin->printf("parse error"); @@ -601,6 +616,7 @@ struct Commands { { "SP=", "", "set the STACK POINTER", do_sp }, { "wpset", " [-]", "set write protect on address or range of addresses", do_wpset }, { "wpclr", " [-]", "clr write protect on address or range of addresses", do_wpclr }, + { "mem", " ", "show memory at addr", do_mem }, { "break", " ", "set a breakpoint", do_breakset }, { "unbreak", " ", "clr a breakpoint", do_breakclr }, { "reset", NULL, "reset the cpu", do_reset }, @@ -651,6 +667,7 @@ int main() { command(buf); regwin->update(); // make sure it is up to date no matter what + memwin->update(); // make sure it is up to date no matter what } teardown_ncurses(); From a6bbf8a7983e410b14a32a15c850fec6adf6ad2d Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Fri, 17 Oct 2025 18:03:42 -0700 Subject: [PATCH 19/26] more functionality --- dbg6502/dbg6502.cpp | 984 +++++++++++++++++++++++++------------------- 1 file changed, 563 insertions(+), 421 deletions(-) diff --git a/dbg6502/dbg6502.cpp b/dbg6502/dbg6502.cpp index 239b52c..16f9060 100644 --- a/dbg6502/dbg6502.cpp +++ b/dbg6502/dbg6502.cpp @@ -52,7 +52,7 @@ class RegisterWin : public NcWin { uint8_t a = cpu->GetA(); uint8_t x = cpu->GetX(); uint8_t y = cpu->GetY(); - uint8_t pc = cpu->GetPC(); + uint16_t pc = cpu->GetPC(); uint8_t sr = cpu->GetP(); uint8_t sp = cpu->GetS(); for (int i = 0; i < 8; i++) { @@ -88,516 +88,653 @@ class MemoryWin : public NcWin { } void update(void) { - set_address(address); // minor kludge, but gets the job done - } + set_address(address); // minor kludge, but gets the job done + } - private: - uint16_t address; - }; - - // --- TerminalWin: scrolling printf + getline with history and TAB completion --- - class TerminalWin : public NcWin { - public: - TerminalWin(int x, int y, int w, int h) : NcWin(x, y, w, h) { - int H, W; getmaxyx(win, H, W); - inner = derwin(win, H - 2, W - 2, 1, 1); // draw inside border - scrollok(inner, TRUE); - keypad(inner, TRUE); - wsetscrreg(inner, 0, (H - 2) - 1); - wmove(inner, 0, 0); - wrefresh(win); - wrefresh(inner); - } + private: + uint16_t address; +}; - ~TerminalWin() override { if (inner) { delwin(inner); inner = nullptr; } } +// --- TerminalWin: scrolling printf + getline with history and TAB completion --- +class TerminalWin : public NcWin { + public: + TerminalWin(int x, int y, int w, int h) : NcWin(x, y, w, h) { + int H, W; getmaxyx(win, H, W); + inner = derwin(win, H - 2, W - 2, 1, 1); // draw inside border + scrollok(inner, TRUE); + keypad(inner, TRUE); + wsetscrreg(inner, 0, (H - 2) - 1); + wmove(inner, 0, 0); + wrefresh(win); + wrefresh(inner); + } - void printf(const char* fmt, ...) { - va_list args; va_start(args, fmt); - vw_printw(inner, fmt, args); - va_end(args); - waddch(inner, '\n'); - wrefresh(inner); - } + ~TerminalWin() override { if (inner) { delwin(inner); inner = nullptr; } } - // getline: prompt + echo + backspace + 64-entry history + DOS-style TAB cycling - void getline(const char* prompt, char* buffer) { - curs_set(1); - - // Start new line if cursor not at column 0 - int cy, cx; getyx(inner, cy, cx); - if (cx != 0) { waddch(inner, '\n'); } - - std::string current; - draw_line(prompt, current); - - int hist_index = -1; - - // Completion session state - bool comp_active = false; // true after first TAB until any edit - int comp_word_start = 0; - std::string comp_base; - std::vector comp_matches; - int comp_index = 0; - - auto reset_completion = [&](){ - comp_active = false; - comp_matches.clear(); - comp_index = 0; - }; - - while (true) { - int ch = wgetch(inner); - - // ENTER: accept input - if (ch == '\n' || ch == '\r') { - waddch(inner, '\n'); - if (!current.empty()) { - if (history.empty() || history.back() != current) { - history.push_back(current); - if (history.size() > kMaxHistory) history.erase(history.begin()); - } - } - std::strcpy(buffer, current.c_str()); - wrefresh(inner); - curs_set(0); - return; - } + void printf(const char* fmt, ...) { + va_list args; va_start(args, fmt); + vw_printw(inner, fmt, args); + va_end(args); + waddch(inner, '\n'); + wrefresh(inner); + } - // TAB: compute or cycle filename completions for last token - if (ch == '\t') { - prepare_completion_state(current, - comp_active, - comp_word_start, - comp_base, - comp_matches, - comp_index); - if (!comp_matches.empty()) { - // Replace token with the candidate at comp_index, then advance index - current.replace(comp_word_start, current.size() - comp_word_start, - comp_matches[comp_index]); - draw_line(prompt, current); - comp_index = (comp_index + 1) % static_cast(comp_matches.size()); - } else { - beep(); - } - continue; - } + // getline: prompt + echo + backspace + 64-entry history + DOS-style TAB cycling + void getline(const char* prompt, char* buffer) { + curs_set(1); - // History navigation - if (ch == KEY_UP) { - if (!history.empty()) { - if (hist_index < 0) hist_index = static_cast(history.size()) - 1; - else if (hist_index > 0) hist_index--; - current = history[hist_index]; - draw_line(prompt, current); + // Start new line if cursor not at column 0 + int cy, cx; getyx(inner, cy, cx); + if (cx != 0) { waddch(inner, '\n'); } + + std::string current; + draw_line(prompt, current); + + int hist_index = -1; + + // Completion session state + bool comp_active = false; // true after first TAB until any edit + int comp_word_start = 0; + std::string comp_base; + std::vector comp_matches; + int comp_index = 0; + + auto reset_completion = [&](){ + comp_active = false; + comp_matches.clear(); + comp_index = 0; + }; + + while (true) { + int ch = wgetch(inner); + + // ENTER: accept input + if (ch == '\n' || ch == '\r') { + waddch(inner, '\n'); + if (!current.empty()) { + if (history.empty() || history.back() != current) { + history.push_back(current); + if (history.size() > kMaxHistory) history.erase(history.begin()); } - reset_completion(); - continue; } - if (ch == KEY_DOWN) { - if (!history.empty() && hist_index >= 0) { - if (hist_index < static_cast(history.size()) - 1) { - hist_index++; - current = history[hist_index]; - } else { - hist_index = -1; - current.clear(); - } - draw_line(prompt, current); - } - reset_completion(); - continue; + std::strcpy(buffer, current.c_str()); + wrefresh(inner); + curs_set(0); + return; + } + + // TAB: compute or cycle filename completions for last token + if (ch == '\t') { + prepare_completion_state(current, + comp_active, + comp_word_start, + comp_base, + comp_matches, + comp_index); + if (!comp_matches.empty()) { + // Replace token with the candidate at comp_index, then advance index + current.replace(comp_word_start, current.size() - comp_word_start, + comp_matches[comp_index]); + draw_line(prompt, current); + comp_index = (comp_index + 1) % static_cast(comp_matches.size()); + } else { + beep(); } + continue; + } - // Backspace - if (ch == KEY_BACKSPACE || ch == 127 || ch == 8) { - if (!current.empty()) { - current.pop_back(); - draw_line(prompt, current); + // History navigation + if (ch == KEY_UP) { + if (!history.empty()) { + if (hist_index < 0) hist_index = static_cast(history.size()) - 1; + else if (hist_index > 0) hist_index--; + current = history[hist_index]; + draw_line(prompt, current); + } + reset_completion(); + continue; + } + if (ch == KEY_DOWN) { + if (!history.empty() && hist_index >= 0) { + if (hist_index < static_cast(history.size()) - 1) { + hist_index++; + current = history[hist_index]; } else { - beep(); + hist_index = -1; + current.clear(); } - reset_completion(); - continue; + draw_line(prompt, current); } + reset_completion(); + continue; + } - // Ignore lateral nav for now (keeps logic simple) - if (ch == KEY_LEFT || ch == KEY_RIGHT || ch == KEY_HOME || ch == KEY_END) { - reset_completion(); - continue; + // Backspace + if (ch == KEY_BACKSPACE || ch == 127 || ch == 8) { + if (!current.empty()) { + current.pop_back(); + draw_line(prompt, current); + } else { + beep(); } + reset_completion(); + continue; + } - // Printable ASCII - if (is_printable(ch)) { - int H, W; getmaxyx(inner, H, W); - int prompt_len = (int)std::strlen(prompt); - if (prompt_len + (int)current.size() < W - 1) { - current.push_back((char)ch); - draw_line(prompt, current); - } else { - beep(); - } - reset_completion(); - continue; - } + // Ignore lateral nav for now (keeps logic simple) + if (ch == KEY_LEFT || ch == KEY_RIGHT || ch == KEY_HOME || ch == KEY_END) { + reset_completion(); + continue; + } - // Unknown key: ignore, but end any completion session + // Printable ASCII + if (is_printable(ch)) { + int H, W; getmaxyx(inner, H, W); + int prompt_len = (int)std::strlen(prompt); + if (prompt_len + (int)current.size() < W - 1) { + current.push_back((char)ch); + draw_line(prompt, current); + } else { + beep(); + } reset_completion(); + continue; } + + // Unknown key: ignore, but end any completion session + reset_completion(); } + } + + private: + WINDOW* inner{}; + static constexpr size_t kMaxHistory = 64; + std::vector history; - private: - WINDOW* inner{}; - static constexpr size_t kMaxHistory = 64; - std::vector history; + static bool is_printable(int ch) { return ch >= 32 && ch <= 126; } - static bool is_printable(int ch) { return ch >= 32 && ch <= 126; } + void draw_line(const char* prompt, const std::string& text) { + int y, x; getyx(inner, y, x); + wmove(inner, y, 0); + wclrtoeol(inner); + waddstr(inner, prompt); + waddnstr(inner, text.c_str(), (int)text.size()); + wrefresh(inner); + } + + // -------- Completion core -------- + + // Build candidates once per TAB session; do NOT rebuild while cycling. + void prepare_completion_state(const std::string& current, + bool& comp_active, + int& comp_word_start, + std::string& comp_base, + std::vector& comp_matches, + int& comp_index) + { + if (comp_active) return; // already cycling on this input + + comp_matches.clear(); + comp_index = 0; + + // Last space-delimited token + comp_word_start = (int)current.find_last_of(' '); + comp_word_start = (comp_word_start == (int)std::string::npos) ? 0 : comp_word_start + 1; + comp_base = current.substr(comp_word_start); + + // Split into dir + base; if token ends with slash, browse that dir (base="") + std::string dirpath, base; + bool ends_with_slash = !comp_base.empty() && + (comp_base.back() == '/' || comp_base.back() == '\\'); + split_path(comp_base, dirpath, base); + if (ends_with_slash) base.clear(); + + // List matches; when browsing a directory after trailing slash, list all entries + comp_matches = list_matches(dirpath, base, /*browse_all=*/ends_with_slash); + + // Convert to replacements relative to original token + for (std::string& m : comp_matches) m = join_path(dirpath, m); + std::sort(comp_matches.begin(), comp_matches.end()); + + comp_active = true; // start cycling + } - void draw_line(const char* prompt, const std::string& text) { - int y, x; getyx(inner, y, x); - wmove(inner, y, 0); - wclrtoeol(inner); - waddstr(inner, prompt); - waddnstr(inner, text.c_str(), (int)text.size()); - wrefresh(inner); + static void split_path(const std::string& token, std::string& dir, std::string& base) { + auto pos = token.find_last_of("/\\"); + if (pos == std::string::npos) { + dir = "."; + base = token; + } else { + dir = token.substr(0, pos + 1); + base = token.substr(pos + 1); + if (dir.empty()) dir = "/"; } + } - // -------- Completion core -------- + static std::string join_path(const std::string& dir, const std::string& name) { + if (dir == "." || dir.empty()) return name; + char last = dir.back(); + if (last == '/' || last == '\\') return dir + name; + return dir + "/" + name; + } - // Build candidates once per TAB session; do NOT rebuild while cycling. - void prepare_completion_state(const std::string& current, - bool& comp_active, - int& comp_word_start, - std::string& comp_base, - std::vector& comp_matches, - int& comp_index) - { - if (comp_active) return; // already cycling on this input + static bool is_dir(const std::string& path) { + struct stat st{}; + if (stat(path.c_str(), &st) == 0) return S_ISDIR(st.st_mode); + return false; + } - comp_matches.clear(); - comp_index = 0; + // List directory entries starting with base; if browse_all, list everything. + // Skip "." and ".." to avoid creeping with "../". + static std::vector list_matches(const std::string& dir, + const std::string& base, + bool browse_all) + { + std::vector out; + DIR* d = opendir(dir.c_str()); + if (!d) return out; + + bool show_hidden = browse_all ? true : (!base.empty() && base[0] == '.'); + + for (dirent* ent; (ent = readdir(d)); ) { + std::string name = ent->d_name; + if (name == "." || name == "..") continue; + if (!show_hidden && !name.empty() && name[0] == '.') continue; + + if (browse_all || starts_with(name, base)) { + std::string full = (dir == ".") ? name : join_path(dir, name); + if (is_dir((dir == ".") ? name : full)) name += "/"; + out.push_back(name); + } + } + closedir(d); + return out; + } - // Last space-delimited token - comp_word_start = (int)current.find_last_of(' '); - comp_word_start = (comp_word_start == (int)std::string::npos) ? 0 : comp_word_start + 1; - comp_base = current.substr(comp_word_start); + static bool starts_with(const std::string& s, const std::string& prefix) { + if (prefix.size() > s.size()) return false; + return std::equal(prefix.begin(), prefix.end(), s.begin()); + } +}; - // Split into dir + base; if token ends with slash, browse that dir (base="") - std::string dirpath, base; - bool ends_with_slash = !comp_base.empty() && - (comp_base.back() == '/' || comp_base.back() == '\\'); - split_path(comp_base, dirpath, base); - if (ends_with_slash) base.clear(); +void setup_ncurses(void) { + setlocale(LC_ALL, ""); // enable line/box chars in UTF-8 terminals + initscr(); + noecho(); + keypad(stdscr, TRUE); + curs_set(0); + refresh(); // ensure base screen is pushed once +} - // List matches; when browsing a directory after trailing slash, list all entries - comp_matches = list_matches(dirpath, base, /*browse_all=*/ends_with_slash); +void teardown_ncurses(void) { + refresh(); + endwin(); +} - // Convert to replacements relative to original token - for (std::string& m : comp_matches) m = join_path(dirpath, m); - std::sort(comp_matches.begin(), comp_matches.end()); +bool done = false; +bool bus_error = false; +bool breakpoint_hit = false; +MemoryWin *memwin = NULL; +TerminalWin *termwin = NULL; +RegisterWin *regwin = NULL; +TerminalWin *displaywin = NULL; + +void bus_write(uint16_t addr, uint8_t data) { + displaywin->printf("write %04x %02x", addr, data); + if (write_protect[addr >> 3] & (1 << (addr & 0x7))) { + bus_error = true; + displaywin->printf("BUS ERROR"); + } + else { + ram[addr] = data; + } +} - comp_active = true; // start cycling - } +uint8_t bus_read(uint16_t addr) { + displaywin->printf("read %04x %02x", addr, ram[addr]); + return ram[addr]; +} - static void split_path(const std::string& token, std::string& dir, std::string& base) { - auto pos = token.find_last_of("/\\"); - if (pos == std::string::npos) { - dir = "."; - base = token; - } else { - dir = token.substr(0, pos + 1); - base = token.substr(pos + 1); - if (dir.empty()) dir = "/"; - } - } +void tick(mos6502 *) { +} - static std::string join_path(const std::string& dir, const std::string& name) { - if (dir == "." || dir.empty()) return name; - char last = dir.back(); - if (last == '/' || last == '\\') return dir + name; - return dir + "/" + name; - } +typedef void (*handler)(char *p); - static bool is_dir(const std::string& path) { - struct stat st{}; - if (stat(path.c_str(), &st) == 0) return S_ISDIR(st.st_mode); - return false; - } +void do_quit(char *) { + done = 1; +} - // List directory entries starting with base; if browse_all, list everything. - // Skip "." and ".." to avoid creeping with "../". - static std::vector list_matches(const std::string& dir, - const std::string& base, - bool browse_all) - { - std::vector out; - DIR* d = opendir(dir.c_str()); - if (!d) return out; - - bool show_hidden = browse_all ? true : (!base.empty() && base[0] == '.'); - - for (dirent* ent; (ent = readdir(d)); ) { - std::string name = ent->d_name; - if (name == "." || name == "..") continue; - if (!show_hidden && !name.empty() && name[0] == '.') continue; - - if (browse_all || starts_with(name, base)) { - std::string full = (dir == ".") ? name : join_path(dir, name); - if (is_dir((dir == ".") ? name : full)) name += "/"; - out.push_back(name); - } - } - closedir(d); - return out; - } +void do_reset(char *) { + cpu->Reset(); +} - static bool starts_with(const std::string& s, const std::string& prefix) { - if (prefix.size() > s.size()) return false; - return std::equal(prefix.begin(), prefix.end(), s.begin()); - } - }; - - void setup_ncurses(void) { - setlocale(LC_ALL, ""); // enable line/box chars in UTF-8 terminals - initscr(); - noecho(); - keypad(stdscr, TRUE); - curs_set(0); - refresh(); // ensure base screen is pushed once - } - - void teardown_ncurses(void) { - refresh(); - endwin(); - } - - bool done = false; - bool bus_error = false; - MemoryWin *memwin = NULL; - TerminalWin *termwin = NULL; - RegisterWin *regwin = NULL; - TerminalWin *displaywin = NULL; - - void bus_write(uint16_t addr, uint8_t data) { - displaywin->printf("write %04x %02x", addr, data); - if (write_protect[addr >> 3] & (1 << (addr & 0x7))) { - bus_error = true; - displaywin->printf("BUS ERROR"); - } - else { - ram[addr] = data; - } +int parsenum(char *p) { + switch (*p) { + case 'x': + case '$': + return strtoul(p + 1, NULL, 16); + case 'o': + case '@': + return strtoul(p + 1, NULL, 8); + case 'b': + case '%': + return strtoul(p + 1, NULL, 2); + default: + return strtoul(p, NULL, 0); // intentional, to allow 0x hex, 0b binary, and 0 octal } +} - uint8_t bus_read(uint16_t addr) { - displaywin->printf("read %04x %02x", addr, ram[addr]); - return ram[addr]; +void do_a(char *p) { + p = strchr(p, '='); + if (p) { + p++; + cpu->SetA(parsenum(p)); } + else { + termwin->printf("parse error"); + } +} - void tick(mos6502 *) { +void do_x(char *p) { + p = strchr(p, '='); + if (p) { + p++; + cpu->SetX(parsenum(p)); + } + else { + termwin->printf("parse error"); } +} - typedef void (*handler)(char *p); +void do_y(char *p) { + p = strchr(p, '='); + if (p) { + p++; + cpu->SetY(parsenum(p)); + } + else { + termwin->printf("parse error"); + } +} - void do_quit(char *) { - done = 1; +void do_pc(char *p) { + p = strchr(p, '='); + if (p) { + p++; + cpu->SetPC(parsenum(p)); + } + else { + termwin->printf("parse error"); } +} - void do_reset(char *) { - cpu->Reset(); +void do_sp(char *p) { + p = strchr(p, '='); + if (p) { + p++; + cpu->SetS(parsenum(p)); + } + else { + termwin->printf("parse error"); } +} - int parsenum(char *p) { - switch (*p) { - case 'x': - case '$': - return strtoul(p + 1, NULL, 16); - case 'o': - case '@': - return strtoul(p + 1, NULL, 8); - case 'b': - case '%': - return strtoul(p + 1, NULL, 2); - default: - return strtoul(p, NULL, 0); // intentional, to allow 0x hex, 0b binary, and 0 octal - } +void do_sr(char *p) { + p = strchr(p, '='); + if (p) { + p++; + cpu->SetP(parsenum(p)); } + else { + termwin->printf("parse error"); + } +} - void do_a(char *p) { - p = strchr(p, '='); - if (p) { - p++; - cpu->SetA(parsenum(p)); +void do_wpset(char *p) { + int begin, end; + p = strchr(p, ' '); + if (p) { + p++; + char *q = strchr(p, '-'); + if (q) { + *q = 0; + q++; + begin = parsenum(p); + end = parsenum(q); + + displaywin->printf("wp set %04x to %04x", begin, end); + while (begin <= end && (begin & 0x7)) { + write_protect[begin >> 3] |= (1 << (begin & 0x7)); + begin++; + } + while (!(begin & 0x7) && (begin + 8) <= end) { + write_protect[begin >> 3] = 0xFF; + begin += 8; + } + while (begin <= end && (begin & 0x7)) { + write_protect[begin >> 3] |= (1 << (begin & 0x7)); + begin++; + } } else { - termwin->printf("parse error"); + displaywin->printf("wp set %04x", begin); + begin = parsenum(p); + write_protect[begin >> 3] |= (1 << (begin & 0x7)); } } + else { + termwin->printf("parse error"); + } +} - void do_x(char *p) { - p = strchr(p, '='); - if (p) { - p++; - cpu->SetX(parsenum(p)); +void do_wpclr(char *p) { + int begin, end; + p = strchr(p, ' '); + if (p) { + p++; + char *q = strchr(p, '-'); + if (q) { + *q = 0; + q++; + begin = parsenum(p); + end = parsenum(q); + + displaywin->printf("wp clr %04x to %04x", begin, end); + while (begin <= end && (begin & 0x7)) { + write_protect[begin >> 3] &= ~(1 << (begin & 0x7)); + begin++; + } + while (!(begin & 0x7) && (begin + 8) <= end) { + write_protect[begin >> 3] = 0x00; + begin += 8; + } + while (begin <= end && (begin & 0x7)) { + write_protect[begin >> 3] &= ~(1 << (begin & 0x7)); + begin++; + } } else { - termwin->printf("parse error"); + begin = parsenum(p); + displaywin->printf("wp clr %04x", begin); + write_protect[begin >> 3] &= ~(1 << (begin & 0x7)); } } + else { + termwin->printf("parse error"); + } +} - void do_y(char *p) { - p = strchr(p, '='); - if (p) { - p++; - cpu->SetY(parsenum(p)); - } - else { - termwin->printf("parse error"); - } +void do_breakset(char *p) { + p = strchr(p, ' '); + if (p) { + p++; + int pc = parsenum(p); + displaywin->printf("break set %04x", pc); + breakpoint[pc >> 3] |= (1 << (pc & 0x7)); } + else { + termwin->printf("parse error"); + } +} - void do_pc(char *p) { - p = strchr(p, '='); - if (p) { - p++; - cpu->SetPC(parsenum(p)); - } - else { - termwin->printf("parse error"); - } +void do_breakclr(char *p) { + p = strchr(p, ' '); + if (p) { + p++; + int pc = parsenum(p); + displaywin->printf("break clr %04x", pc); + breakpoint[pc >> 3] &= ~(1 << (pc & 0x7)); + } + else { + termwin->printf("parse error"); } +} - void do_sp(char *p) { - p = strchr(p, '='); - if (p) { - p++; - cpu->SetS(parsenum(p)); - } - else { - termwin->printf("parse error"); - } +void do_mem(char *p) { + p = strchr(p, ' '); + if (p) { + p++; + memwin->set_address(parsenum(p) & ~0x0F); + } + else { + termwin->printf("parse error"); } +} - void do_sr(char *p) { - p = strchr(p, '='); - if (p) { - p++; - cpu->SetP(parsenum(p)); +uint32_t fetch(const char *s, uint16_t offset, uint8_t count) +{ + uint32_t ret = 0; + uint32_t val; + for (int i = 0; i < count; i++) { + ret <<= 4; + if (s[offset + i] <= '9') { + val = s[offset + i] - '0'; } - else { - termwin->printf("parse error"); + else if (s[offset + i] <= 'F') { + val = s[offset + i] - 'A' + 10; + } + else if (s[offset + i] <= 'f') { + val = s[offset + i] - 'a' + 10; } + ret |= val; } + return ret; +} - void do_wpset(char *p) { - int begin, end; - p = strchr(p, ' '); - if (p) { - p++; - char *q = strchr(p, '-'); - if (q) { - *q = 0; - q++; - begin = parsenum(p); - end = parsenum(q); +void do_ihex(char *p) { + p = strchr(p, ' '); + if (p) { + p++; + + char buf[1024]; + FILE *f = fopen(p, "r"); + if (f) { + while (NULL != fgets(buf, sizeof(buf), f)) { + if (buf[0] != ':') { + termwin->printf("unexpected start code in hex file"); + return; + } + + // TODO FIX verify checksum for line here! - displaywin->printf("wp set %04x to %04x", begin, end); - while (begin <= end && (begin & 0x7)) { - write_protect[begin >> 3] |= (1 << (begin & 0x7)); - begin++; + int length = fetch(buf, 1, 2); + int address = fetch(buf, 3, 4); + int record = fetch(buf, 7, 2); + if (record == 0x00) { + for (int i = 0; i < length; i++) { + ram[address + i] = fetch(buf, 9 + 2 * i, 2); + } } - while (!(begin & 0x7) && (begin + 8) <= end) { - write_protect[begin >> 3] = 0xFF; - begin += 8; + else if (record == 0x01) { + // do nothing } - while (begin <= end && (begin & 0x7)) { - write_protect[begin >> 3] |= (1 << (begin & 0x7)); - begin++; + else { + termwin->printf("unexpected record type in hex file"); + return; } } - else { - displaywin->printf("wp set %04x", begin); - begin = parsenum(p); - write_protect[begin >> 3] |= (1 << (begin & 0x7)); - } + fclose(f); } else { - termwin->printf("parse error"); + termwin->printf("error opening file"); } } + else { + termwin->printf("parse error"); + } +} - void do_wpclr(char *p) { - int begin, end; - p = strchr(p, ' '); - if (p) { - p++; - char *q = strchr(p, '-'); +void do_load(char *p) { + p = strchr(p, ' '); + if (p) { + p++; + int addr = parsenum(p); + if (addr < 65536) { + char *q = strchr(p, ' '); if (q) { - *q = 0; q++; - begin = parsenum(p); - end = parsenum(q); - - displaywin->printf("wp clr %04x to %04x", begin, end); - while (begin <= end && (begin & 0x7)) { - write_protect[begin >> 3] &= ~(1 << (begin & 0x7)); - begin++; - } - while (!(begin & 0x7) && (begin + 8) <= end) { - write_protect[begin >> 3] = 0x00; - begin += 8; + FILE *f = fopen(q, "r"); + if (f) { + int ret = fread(ram + addr, 1, sizeof(ram) - addr, f); + termwin->printf("loaded %d bytes at %04x", ret, addr); } - while (begin <= end && (begin & 0x7)) { - write_protect[begin >> 3] &= ~(1 << (begin & 0x7)); - begin++; + else { + termwin->printf("file open error %s", q); } } else { - begin = parsenum(p); - displaywin->printf("wp clr %04x", begin); - write_protect[begin >> 3] &= ~(1 << (begin & 0x7)); + termwin->printf("parse error"); } } else { - termwin->printf("parse error"); + termwin->printf("address out of range"); } } - - void do_breakset(char *p) { - p = strchr(p, ' '); - if (p) { - p++; - int pc = parsenum(p); - displaywin->printf("break set %04x", pc); - breakpoint[pc >> 3] |= (1 << (pc & 0x7)); - } - else { - termwin->printf("parse error"); - } + else { + termwin->printf("parse error"); } +} - void do_breakclr(char *p) { - p = strchr(p, ' '); - if (p) { - p++; - int pc = parsenum(p); - displaywin->printf("break clr %04x", pc); - breakpoint[pc >> 3] &= ~(1 << (pc & 0x7)); - } - else { - termwin->printf("parse error"); - } +void step(void) { + bus_error = false; + breakpoint_hit = false; + + // TODO test for and break on illegal instructions + + uint64_t dummy; + cpu->Run(1, dummy, mos6502::INST_COUNT); + + uint16_t pc = cpu->GetPC(); + if (breakpoint[pc >> 3] & (1 << (pc & 0x7))) { + displaywin->printf("break at %04x", pc); + breakpoint_hit = true; } - void do_mem(char *p) { - p = strchr(p, ' '); - if (p) { - p++; - memwin->set_address(parsenum(p) & ~0x0F); + // also break on eternal loops + if (ram[pc] == 0x4C && ram[pc+1] == (pc & 0xFF) && ram[pc+2] == (pc >> 8)) { + displaywin->printf("self jmp break at %04x", pc); + breakpoint_hit = true; } - else { - termwin->printf("parse error"); +} + +void do_run(char *) { + termwin->printf("running, hit space to break"); + nodelay(stdscr, TRUE); // make getch() non-blocking + while (!bus_error && !breakpoint_hit) { + step(); + regwin->update(); + memwin->update(); + + int ch = getch(); + if (ch != ERR && ch == ' ') { + break; + } } + nodelay(stdscr, FALSE); // make getch() blocking again +} + +void do_step(char *) { + step(); } void do_help(char *); @@ -609,17 +746,22 @@ struct Commands { handler exe; } commands[] = { { "A=", "", "set the A register", do_a }, - { "X=", "", "set the X register", do_x }, - { "Y=", "", "set the Y register", do_y }, { "PC=", "", "set the PC", do_pc }, - { "SR=", "", "set the STATUS", do_sr }, { "SP=", "", "set the STACK POINTER", do_sp }, - { "wpset", " [-]", "set write protect on address or range of addresses", do_wpset }, - { "wpclr", " [-]", "clr write protect on address or range of addresses", do_wpclr }, - { "mem", " ", "show memory at addr", do_mem }, + { "SR=", "", "set the STATUS", do_sr }, + { "X=", "", "set the X register", do_x }, + { "Y=", "", "set the Y register", do_y }, { "break", " ", "set a breakpoint", do_breakset }, - { "unbreak", " ", "clr a breakpoint", do_breakclr }, + { "ihex", " ", "load ihex file", do_ihex }, + { "load", " ", "load binary file at addr", do_load }, + { "mem", " ", "show memory at addr", do_mem }, { "reset", NULL, "reset the cpu", do_reset }, + { "run", NULL, "run program to breakpoint", do_run }, + { "step", NULL, "single step program", do_step }, + { "unbreak", " ", "clr a breakpoint", do_breakclr }, + { "wpclr", " [-]", "clr write protect on address or range of addresses", do_wpclr }, + { "wpset", " [-]", "set write protect on address or range of addresses", do_wpset }, + { "quit", NULL, "quit the program", do_quit }, { "exit", NULL, "same as quit", do_quit }, { "help", NULL, "print help", do_help }, From aa4df7ba1dfbad1257595993f2c97e528c5eacda Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Fri, 17 Oct 2025 18:06:40 -0700 Subject: [PATCH 20/26] added verbose flag to speed execution --- dbg6502/dbg6502.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/dbg6502/dbg6502.cpp b/dbg6502/dbg6502.cpp index 16f9060..e9a1eda 100644 --- a/dbg6502/dbg6502.cpp +++ b/dbg6502/dbg6502.cpp @@ -13,6 +13,8 @@ #include "../mos6502.h" +bool verbose = false; + uint8_t ram[65536] = { 0x4C, 0, 0 } ; uint8_t write_protect[65536 / 8] = { 0 }; uint8_t breakpoint[65536 / 8] = { 0 }; @@ -380,7 +382,9 @@ RegisterWin *regwin = NULL; TerminalWin *displaywin = NULL; void bus_write(uint16_t addr, uint8_t data) { - displaywin->printf("write %04x %02x", addr, data); + if (verbose) { + displaywin->printf("write %04x %02x", addr, data); + } if (write_protect[addr >> 3] & (1 << (addr & 0x7))) { bus_error = true; displaywin->printf("BUS ERROR"); @@ -391,7 +395,9 @@ void bus_write(uint16_t addr, uint8_t data) { } uint8_t bus_read(uint16_t addr) { - displaywin->printf("read %04x %02x", addr, ram[addr]); + if (verbose) { + displaywin->printf("read %04x %02x", addr, ram[addr]); + } return ram[addr]; } @@ -722,8 +728,10 @@ void do_run(char *) { nodelay(stdscr, TRUE); // make getch() non-blocking while (!bus_error && !breakpoint_hit) { step(); - regwin->update(); - memwin->update(); + if (verbose) { + regwin->update(); + memwin->update(); + } int ch = getch(); if (ch != ERR && ch == ' ') { From 8041e807eaced0b776b14da775f65a522582fda3 Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Sat, 18 Oct 2025 00:24:28 -0700 Subject: [PATCH 21/26] accept SR=NV, et. al. --- dbg6502/dbg6502.cpp | 86 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 7 deletions(-) diff --git a/dbg6502/dbg6502.cpp b/dbg6502/dbg6502.cpp index e9a1eda..99b6c49 100644 --- a/dbg6502/dbg6502.cpp +++ b/dbg6502/dbg6502.cpp @@ -13,7 +13,7 @@ #include "../mos6502.h" -bool verbose = false; +uint8_t verbosity = 0; uint8_t ram[65536] = { 0x4C, 0, 0 } ; uint8_t write_protect[65536 / 8] = { 0 }; @@ -80,11 +80,21 @@ class MemoryWin : public NcWin { void set_address(uint16_t addr) { int h, w; getmaxyx(win, h, w); address = addr; + + addr &= 0xFFF0; + addr -= 32; + for (int i = 0; i < h - 2; i++) { - xyprintf(1, i + 1, "%04x: ", addr + 16 * i); + xyprintf(1, i + 1, "%04x: ", (uint16_t)(addr + 16 * i)); for (int j = 0; j < 16; j++) { + if ((uint16_t) (addr + 16 * i + j) == address) { + wattron(win, A_REVERSE); + } xyprintf(1 + 7 + j * 3, i + 1, "%02x", - ram[addr + 16 * i + j]); + ram[(uint16_t)(addr + 16 * i + j)]); + if ((uint16_t) (addr + 16 * i + j) == address) { + wattroff(win, A_REVERSE); + } } } } @@ -382,9 +392,12 @@ RegisterWin *regwin = NULL; TerminalWin *displaywin = NULL; void bus_write(uint16_t addr, uint8_t data) { - if (verbose) { + if (verbosity >= 3) { displaywin->printf("write %04x %02x", addr, data); } + if (verbosity >= 2) { + memwin->set_address(addr); + } if (write_protect[addr >> 3] & (1 << (addr & 0x7))) { bus_error = true; displaywin->printf("BUS ERROR"); @@ -395,9 +408,12 @@ void bus_write(uint16_t addr, uint8_t data) { } uint8_t bus_read(uint16_t addr) { - if (verbose) { + if (verbosity >= 3) { displaywin->printf("read %04x %02x", addr, ram[addr]); } + if (verbosity >= 2) { + memwin->set_address(addr); + } return ram[addr]; } @@ -485,11 +501,53 @@ void do_sp(char *p) { } } +uint8_t flagmask(char *p) { + static const char *match1 = "nv-bdizc"; + static const char *match2 = "NV-BDIZC"; + uint8_t ret = 0; + + while (*p) { + char *q = strchr((char *)match1, *p); + if (q) { + ret |= 1 << (7 - (q - match1)); + } + q = strchr((char *)match2, *p); + if (q) { + ret |= 1 << (7 - (q - match1)); + } + p++; + } + + return ret; +} + +uint8_t flagval(char *p) { + static const char *match = "NV-BDIZC"; + uint8_t ret = 0; + + while (*p) { + const char *q = strchr((char *)match, *p); + if (q) { + ret |= 1 << (7 - (q - match)); + } + p++; + } + + return ret; +} + void do_sr(char *p) { + static const char *match = "nvbdizcNVBDIZC"; + p = strchr(p, '='); if (p) { p++; - cpu->SetP(parsenum(p)); + if (strchr(match, *p)) { + cpu->SetP((cpu->GetP() & ~flagmask(p)) | flagval(p)); + } + else { + cpu->SetP(parsenum(p)); + } } else { termwin->printf("parse error"); @@ -728,8 +786,10 @@ void do_run(char *) { nodelay(stdscr, TRUE); // make getch() non-blocking while (!bus_error && !breakpoint_hit) { step(); - if (verbose) { + if (verbosity >= 1) { regwin->update(); + } + if (verbosity >= 2) { memwin->update(); } @@ -745,6 +805,17 @@ void do_step(char *) { step(); } +void do_verbose(char *p) { + p = strchr(p, ' '); + if (p) { + p++; + verbosity = parsenum(p); + } + else { + termwin->printf("parse error"); + } +} + void do_help(char *); struct Commands { @@ -767,6 +838,7 @@ struct Commands { { "run", NULL, "run program to breakpoint", do_run }, { "step", NULL, "single step program", do_step }, { "unbreak", " ", "clr a breakpoint", do_breakclr }, + { "verbose", " ", "set verbosity (0-3)", do_verbose }, { "wpclr", " [-]", "clr write protect on address or range of addresses", do_wpclr }, { "wpset", " [-]", "set write protect on address or range of addresses", do_wpset }, From 155ea97fed7761633a5da8f02802c18077d9aeb5 Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Sat, 18 Oct 2025 00:27:35 -0700 Subject: [PATCH 22/26] indentation --- dbg6502/dbg6502.cpp | 755 +++++++++++++++++++++++++------------------- 1 file changed, 429 insertions(+), 326 deletions(-) diff --git a/dbg6502/dbg6502.cpp b/dbg6502/dbg6502.cpp index 99b6c49..e80cc1b 100644 --- a/dbg6502/dbg6502.cpp +++ b/dbg6502/dbg6502.cpp @@ -15,361 +15,434 @@ uint8_t verbosity = 0; -uint8_t ram[65536] = { 0x4C, 0, 0 } ; +uint8_t ram[65536] = { 0x4C, 0, 0 }; uint8_t write_protect[65536 / 8] = { 0 }; uint8_t breakpoint[65536 / 8] = { 0 }; mos6502 *cpu = NULL; class NcWin { - public: - NcWin(int x, int y, int w, int h) : win(newwin(h, w, y, x)) { - if (!win) return; - box(win, 0, 0); - wrefresh(win); +public: + NcWin(int x, int y, int w, int h) + : win(newwin(h, w, y, x)) + { + if (!win) { + return; + } + box(win, 0, 0); + wrefresh(win); + } + virtual ~NcWin() + { + if (win) { + delwin(win); } - virtual ~NcWin() { if (win) delwin(win); } + } - void xyprintf(int x, int y, const char *fmt, ...) { - if (!win) return; - va_list args; va_start(args, fmt); - wmove(win, y, x); - vw_printw(win, fmt, args); - va_end(args); - wrefresh(win); + void xyprintf(int x, int y, const char *fmt, ...) + { + if (!win) { + return; } + va_list args; va_start(args, fmt); + wmove(win, y, x); + vw_printw(win, fmt, args); + va_end(args); + wrefresh(win); + } - protected: - WINDOW *win{}; +protected: + WINDOW *win{}; }; class RegisterWin : public NcWin { - public: - RegisterWin(int x, int y, int w, int h) - : NcWin(x, y, w, h) { - update(); +public: + RegisterWin(int x, int y, int w, int h) + : NcWin(x, y, w, h) + { + update(); + } + + void update() + { + static char srtext[] = { 'N', 'V', '-', 'B', 'D', 'I', 'Z', 'C', 0 }; + uint8_t a = cpu->GetA(); + uint8_t x = cpu->GetX(); + uint8_t y = cpu->GetY(); + uint16_t pc = cpu->GetPC(); + uint8_t sr = cpu->GetP(); + uint8_t sp = cpu->GetS(); + for (int i = 0; i < 8; i++) { + if (sr & (1 << (7 - i))) { + srtext[i] = toupper(srtext[i]); } - - void update() { - static char srtext[] = { 'N', 'V', '-', 'B', 'D', 'I', 'Z', 'C', 0 }; - uint8_t a = cpu->GetA(); - uint8_t x = cpu->GetX(); - uint8_t y = cpu->GetY(); - uint16_t pc = cpu->GetPC(); - uint8_t sr = cpu->GetP(); - uint8_t sp = cpu->GetS(); - for (int i = 0; i < 8; i++) { - if (sr & (1 << (7 - i))) { - srtext[i] = toupper(srtext[i]); - } - else { - srtext[i] = tolower(srtext[i]); - } + else { + srtext[i] = tolower(srtext[i]); } - xyprintf(1, 1, "PC:%04x SP:%02x A:%02x X:%02x Y:%02x SR:%s", pc, sp, a, x, y, srtext); } + xyprintf(1, 1, "PC:%04x SP:%02x A:%02x X:%02x Y:%02x SR:%s", pc, sp, a, x, y, srtext); + } }; class MemoryWin : public NcWin { - public: - MemoryWin(int x, int y, int w, int h) - : NcWin(x, y, w, h), address(0) { - set_address(0); - } - - void set_address(uint16_t addr) { - int h, w; getmaxyx(win, h, w); - address = addr; - - addr &= 0xFFF0; - addr -= 32; - - for (int i = 0; i < h - 2; i++) { - xyprintf(1, i + 1, "%04x: ", (uint16_t)(addr + 16 * i)); - for (int j = 0; j < 16; j++) { - if ((uint16_t) (addr + 16 * i + j) == address) { - wattron(win, A_REVERSE); - } - xyprintf(1 + 7 + j * 3, i + 1, "%02x", +public: + MemoryWin(int x, int y, int w, int h) + : NcWin(x, y, w, h) + , address(0) + { + set_address(0); + } + + void set_address(uint16_t addr) + { + int h, w; getmaxyx(win, h, w); + address = addr; + + addr &= 0xFFF0; + addr -= 32; + + for (int i = 0; i < h - 2; i++) { + xyprintf(1, i + 1, "%04x: ", (uint16_t)(addr + 16 * i)); + for (int j = 0; j < 16; j++) { + if ((uint16_t) (addr + 16 * i + j) == address) { + wattron(win, A_REVERSE); + } + xyprintf(1 + 7 + j * 3, i + 1, "%02x", ram[(uint16_t)(addr + 16 * i + j)]); - if ((uint16_t) (addr + 16 * i + j) == address) { - wattroff(win, A_REVERSE); - } + if ((uint16_t) (addr + 16 * i + j) == address) { + wattroff(win, A_REVERSE); } } } + } - void update(void) { - set_address(address); // minor kludge, but gets the job done - } + void update(void) + { + set_address(address); // minor kludge, but gets the job done + } - private: - uint16_t address; +private: + uint16_t address; }; // --- TerminalWin: scrolling printf + getline with history and TAB completion --- class TerminalWin : public NcWin { - public: - TerminalWin(int x, int y, int w, int h) : NcWin(x, y, w, h) { - int H, W; getmaxyx(win, H, W); - inner = derwin(win, H - 2, W - 2, 1, 1); // draw inside border - scrollok(inner, TRUE); - keypad(inner, TRUE); - wsetscrreg(inner, 0, (H - 2) - 1); - wmove(inner, 0, 0); - wrefresh(win); - wrefresh(inner); - } - - ~TerminalWin() override { if (inner) { delwin(inner); inner = nullptr; } } - - void printf(const char* fmt, ...) { - va_list args; va_start(args, fmt); - vw_printw(inner, fmt, args); - va_end(args); +public: + TerminalWin(int x, int y, int w, int h) + : NcWin(x, y, w, h) + { + int H, W; getmaxyx(win, H, W); + inner = derwin(win, H - 2, W - 2, 1, 1); // draw inside border + scrollok(inner, TRUE); + keypad(inner, TRUE); + wsetscrreg(inner, 0, (H - 2) - 1); + wmove(inner, 0, 0); + wrefresh(win); + wrefresh(inner); + } + + ~TerminalWin() override + { + if (inner) { + delwin(inner); inner = nullptr; + } + } + + void printf(const char* fmt, ...) + { + va_list args; va_start(args, fmt); + vw_printw(inner, fmt, args); + va_end(args); + waddch(inner, '\n'); + wrefresh(inner); + } + + // getline: prompt + echo + backspace + 64-entry history + DOS-style TAB cycling + void getline(const char* prompt, char* buffer) + { + curs_set(1); + + // Start new line if cursor not at column 0 + int cy, cx; getyx(inner, cy, cx); + if (cx != 0) { waddch(inner, '\n'); - wrefresh(inner); } - // getline: prompt + echo + backspace + 64-entry history + DOS-style TAB cycling - void getline(const char* prompt, char* buffer) { - curs_set(1); + std::string current; + draw_line(prompt, current); - // Start new line if cursor not at column 0 - int cy, cx; getyx(inner, cy, cx); - if (cx != 0) { waddch(inner, '\n'); } + int hist_index = -1; - std::string current; - draw_line(prompt, current); + // Completion session state + bool comp_active = false; // true after first TAB until any edit + int comp_word_start = 0; + std::string comp_base; + std::vector comp_matches; + int comp_index = 0; - int hist_index = -1; + auto reset_completion = [&](){ + comp_active = false; + comp_matches.clear(); + comp_index = 0; + }; - // Completion session state - bool comp_active = false; // true after first TAB until any edit - int comp_word_start = 0; - std::string comp_base; - std::vector comp_matches; - int comp_index = 0; + while (true) { + int ch = wgetch(inner); - auto reset_completion = [&](){ - comp_active = false; - comp_matches.clear(); - comp_index = 0; - }; - - while (true) { - int ch = wgetch(inner); - - // ENTER: accept input - if (ch == '\n' || ch == '\r') { - waddch(inner, '\n'); - if (!current.empty()) { - if (history.empty() || history.back() != current) { - history.push_back(current); - if (history.size() > kMaxHistory) history.erase(history.begin()); + // ENTER: accept input + if (ch == '\n' || ch == '\r') { + waddch(inner, '\n'); + if (!current.empty()) { + if (history.empty() || history.back() != current) { + history.push_back(current); + if (history.size() > kMaxHistory) { + history.erase(history.begin()); } } - std::strcpy(buffer, current.c_str()); - wrefresh(inner); - curs_set(0); - return; } + std::strcpy(buffer, current.c_str()); + wrefresh(inner); + curs_set(0); + return; + } - // TAB: compute or cycle filename completions for last token - if (ch == '\t') { - prepare_completion_state(current, - comp_active, - comp_word_start, - comp_base, - comp_matches, - comp_index); - if (!comp_matches.empty()) { - // Replace token with the candidate at comp_index, then advance index - current.replace(comp_word_start, current.size() - comp_word_start, - comp_matches[comp_index]); - draw_line(prompt, current); - comp_index = (comp_index + 1) % static_cast(comp_matches.size()); - } else { - beep(); - } - continue; + // TAB: compute or cycle filename completions for last token + if (ch == '\t') { + prepare_completion_state(current, + comp_active, + comp_word_start, + comp_base, + comp_matches, + comp_index); + if (!comp_matches.empty()) { + // Replace token with the candidate at comp_index, then advance index + current.replace(comp_word_start, current.size() - comp_word_start, + comp_matches[comp_index]); + draw_line(prompt, current); + comp_index = (comp_index + 1) % static_cast(comp_matches.size()); + } + else { + beep(); } + continue; + } - // History navigation - if (ch == KEY_UP) { - if (!history.empty()) { - if (hist_index < 0) hist_index = static_cast(history.size()) - 1; - else if (hist_index > 0) hist_index--; - current = history[hist_index]; - draw_line(prompt, current); + // History navigation + if (ch == KEY_UP) { + if (!history.empty()) { + if (hist_index < 0) { + hist_index = static_cast(history.size()) - 1; } - reset_completion(); - continue; - } - if (ch == KEY_DOWN) { - if (!history.empty() && hist_index >= 0) { - if (hist_index < static_cast(history.size()) - 1) { - hist_index++; - current = history[hist_index]; - } else { - hist_index = -1; - current.clear(); - } - draw_line(prompt, current); + else if (hist_index > 0) { + hist_index--; } - reset_completion(); - continue; + current = history[hist_index]; + draw_line(prompt, current); } - - // Backspace - if (ch == KEY_BACKSPACE || ch == 127 || ch == 8) { - if (!current.empty()) { - current.pop_back(); - draw_line(prompt, current); - } else { - beep(); + reset_completion(); + continue; + } + if (ch == KEY_DOWN) { + if (!history.empty() && hist_index >= 0) { + if (hist_index < static_cast(history.size()) - 1) { + hist_index++; + current = history[hist_index]; } - reset_completion(); - continue; + else { + hist_index = -1; + current.clear(); + } + draw_line(prompt, current); } + reset_completion(); + continue; + } - // Ignore lateral nav for now (keeps logic simple) - if (ch == KEY_LEFT || ch == KEY_RIGHT || ch == KEY_HOME || ch == KEY_END) { - reset_completion(); - continue; + // Backspace + if (ch == KEY_BACKSPACE || ch == 127 || ch == 8) { + if (!current.empty()) { + current.pop_back(); + draw_line(prompt, current); } - - // Printable ASCII - if (is_printable(ch)) { - int H, W; getmaxyx(inner, H, W); - int prompt_len = (int)std::strlen(prompt); - if (prompt_len + (int)current.size() < W - 1) { - current.push_back((char)ch); - draw_line(prompt, current); - } else { - beep(); - } - reset_completion(); - continue; + else { + beep(); } + reset_completion(); + continue; + } + + // Ignore lateral nav for now (keeps logic simple) + if (ch == KEY_LEFT || ch == KEY_RIGHT || ch == KEY_HOME || ch == KEY_END) { + reset_completion(); + continue; + } - // Unknown key: ignore, but end any completion session + // Printable ASCII + if (is_printable(ch)) { + int H, W; getmaxyx(inner, H, W); + int prompt_len = (int)std::strlen(prompt); + if (prompt_len + (int)current.size() < W - 1) { + current.push_back((char)ch); + draw_line(prompt, current); + } + else { + beep(); + } reset_completion(); + continue; } + + // Unknown key: ignore, but end any completion session + reset_completion(); } + } + +private: + WINDOW* inner{}; + static constexpr size_t kMaxHistory = 64; + std::vector history; + + static bool is_printable(int ch) + { + return ch >= 32 && ch <= 126; + } + + void draw_line(const char* prompt, const std::string& text) + { + int y, x; getyx(inner, y, x); + wmove(inner, y, 0); + wclrtoeol(inner); + waddstr(inner, prompt); + waddnstr(inner, text.c_str(), (int)text.size()); + wrefresh(inner); + } - private: - WINDOW* inner{}; - static constexpr size_t kMaxHistory = 64; - std::vector history; - - static bool is_printable(int ch) { return ch >= 32 && ch <= 126; } - - void draw_line(const char* prompt, const std::string& text) { - int y, x; getyx(inner, y, x); - wmove(inner, y, 0); - wclrtoeol(inner); - waddstr(inner, prompt); - waddnstr(inner, text.c_str(), (int)text.size()); - wrefresh(inner); - } - - // -------- Completion core -------- - - // Build candidates once per TAB session; do NOT rebuild while cycling. - void prepare_completion_state(const std::string& current, - bool& comp_active, - int& comp_word_start, - std::string& comp_base, - std::vector& comp_matches, - int& comp_index) - { - if (comp_active) return; // already cycling on this input - - comp_matches.clear(); - comp_index = 0; - - // Last space-delimited token - comp_word_start = (int)current.find_last_of(' '); - comp_word_start = (comp_word_start == (int)std::string::npos) ? 0 : comp_word_start + 1; - comp_base = current.substr(comp_word_start); - - // Split into dir + base; if token ends with slash, browse that dir (base="") - std::string dirpath, base; - bool ends_with_slash = !comp_base.empty() && - (comp_base.back() == '/' || comp_base.back() == '\\'); - split_path(comp_base, dirpath, base); - if (ends_with_slash) base.clear(); - - // List matches; when browsing a directory after trailing slash, list all entries - comp_matches = list_matches(dirpath, base, /*browse_all=*/ends_with_slash); - - // Convert to replacements relative to original token - for (std::string& m : comp_matches) m = join_path(dirpath, m); - std::sort(comp_matches.begin(), comp_matches.end()); - - comp_active = true; // start cycling - } - - static void split_path(const std::string& token, std::string& dir, std::string& base) { - auto pos = token.find_last_of("/\\"); - if (pos == std::string::npos) { - dir = "."; - base = token; - } else { - dir = token.substr(0, pos + 1); - base = token.substr(pos + 1); - if (dir.empty()) dir = "/"; + // -------- Completion core -------- + + // Build candidates once per TAB session; do NOT rebuild while cycling. + void prepare_completion_state(const std::string& current, + bool& comp_active, + int& comp_word_start, + std::string& comp_base, + std::vector& comp_matches, + int& comp_index) + { + if (comp_active) { + return; // already cycling on this input + + } + comp_matches.clear(); + comp_index = 0; + + // Last space-delimited token + comp_word_start = (int)current.find_last_of(' '); + comp_word_start = (comp_word_start == (int)std::string::npos) ? 0 : comp_word_start + 1; + comp_base = current.substr(comp_word_start); + + // Split into dir + base; if token ends with slash, browse that dir (base="") + std::string dirpath, base; + bool ends_with_slash = !comp_base.empty() && + (comp_base.back() == '/' || comp_base.back() == '\\'); + split_path(comp_base, dirpath, base); + if (ends_with_slash) { + base.clear(); + } + + // List matches; when browsing a directory after trailing slash, list all entries + comp_matches = list_matches(dirpath, base, /*browse_all=*/ ends_with_slash); + + // Convert to replacements relative to original token + for (std::string& m : comp_matches) { + m = join_path(dirpath, m); + } + std::sort(comp_matches.begin(), comp_matches.end()); + + comp_active = true; // start cycling + } + + static void split_path(const std::string& token, std::string& dir, std::string& base) + { + auto pos = token.find_last_of("/\\"); + if (pos == std::string::npos) { + dir = "."; + base = token; + } + else { + dir = token.substr(0, pos + 1); + base = token.substr(pos + 1); + if (dir.empty()) { + dir = "/"; } } + } - static std::string join_path(const std::string& dir, const std::string& name) { - if (dir == "." || dir.empty()) return name; - char last = dir.back(); - if (last == '/' || last == '\\') return dir + name; - return dir + "/" + name; + static std::string join_path(const std::string& dir, const std::string& name) + { + if (dir == "." || dir.empty()) { + return name; } + char last = dir.back(); + if (last == '/' || last == '\\') { + return dir + name; + } + return dir + "/" + name; + } - static bool is_dir(const std::string& path) { - struct stat st{}; - if (stat(path.c_str(), &st) == 0) return S_ISDIR(st.st_mode); - return false; + static bool is_dir(const std::string& path) + { + struct stat st {}; + if (stat(path.c_str(), &st) == 0) { + return S_ISDIR(st.st_mode); } + return false; + } - // List directory entries starting with base; if browse_all, list everything. - // Skip "." and ".." to avoid creeping with "../". - static std::vector list_matches(const std::string& dir, - const std::string& base, - bool browse_all) - { - std::vector out; - DIR* d = opendir(dir.c_str()); - if (!d) return out; + // List directory entries starting with base; if browse_all, list everything. + // Skip "." and ".." to avoid creeping with "../". + static std::vector list_matches(const std::string& dir, + const std::string& base, + bool browse_all) + { + std::vector out; + DIR* d = opendir(dir.c_str()); + if (!d) { + return out; + } - bool show_hidden = browse_all ? true : (!base.empty() && base[0] == '.'); + bool show_hidden = browse_all ? true : (!base.empty() && base[0] == '.'); - for (dirent* ent; (ent = readdir(d)); ) { - std::string name = ent->d_name; - if (name == "." || name == "..") continue; - if (!show_hidden && !name.empty() && name[0] == '.') continue; + for (dirent* ent; (ent = readdir(d)); ) { + std::string name = ent->d_name; + if (name == "." || name == "..") { + continue; + } + if (!show_hidden && !name.empty() && name[0] == '.') { + continue; + } - if (browse_all || starts_with(name, base)) { - std::string full = (dir == ".") ? name : join_path(dir, name); - if (is_dir((dir == ".") ? name : full)) name += "/"; - out.push_back(name); + if (browse_all || starts_with(name, base)) { + std::string full = (dir == ".") ? name : join_path(dir, name); + if (is_dir((dir == ".") ? name : full)) { + name += "/"; } + out.push_back(name); } - closedir(d); - return out; } + closedir(d); + return out; + } - static bool starts_with(const std::string& s, const std::string& prefix) { - if (prefix.size() > s.size()) return false; - return std::equal(prefix.begin(), prefix.end(), s.begin()); + static bool starts_with(const std::string& s, const std::string& prefix) + { + if (prefix.size() > s.size()) { + return false; } + return std::equal(prefix.begin(), prefix.end(), s.begin()); + } }; -void setup_ncurses(void) { +void setup_ncurses(void) +{ setlocale(LC_ALL, ""); // enable line/box chars in UTF-8 terminals initscr(); noecho(); @@ -378,7 +451,8 @@ void setup_ncurses(void) { refresh(); // ensure base screen is pushed once } -void teardown_ncurses(void) { +void teardown_ncurses(void) +{ refresh(); endwin(); } @@ -391,7 +465,8 @@ TerminalWin *termwin = NULL; RegisterWin *regwin = NULL; TerminalWin *displaywin = NULL; -void bus_write(uint16_t addr, uint8_t data) { +void bus_write(uint16_t addr, uint8_t data) +{ if (verbosity >= 3) { displaywin->printf("write %04x %02x", addr, data); } @@ -407,7 +482,8 @@ void bus_write(uint16_t addr, uint8_t data) { } } -uint8_t bus_read(uint16_t addr) { +uint8_t bus_read(uint16_t addr) +{ if (verbosity >= 3) { displaywin->printf("read %04x %02x", addr, ram[addr]); } @@ -417,36 +493,41 @@ uint8_t bus_read(uint16_t addr) { return ram[addr]; } -void tick(mos6502 *) { +void tick(mos6502 *) +{ } typedef void (*handler)(char *p); -void do_quit(char *) { +void do_quit(char *) +{ done = 1; } -void do_reset(char *) { +void do_reset(char *) +{ cpu->Reset(); } -int parsenum(char *p) { +int parsenum(char *p) +{ switch (*p) { - case 'x': - case '$': - return strtoul(p + 1, NULL, 16); - case 'o': - case '@': - return strtoul(p + 1, NULL, 8); - case 'b': - case '%': - return strtoul(p + 1, NULL, 2); - default: - return strtoul(p, NULL, 0); // intentional, to allow 0x hex, 0b binary, and 0 octal + case 'x': + case '$': + return strtoul(p + 1, NULL, 16); + case 'o': + case '@': + return strtoul(p + 1, NULL, 8); + case 'b': + case '%': + return strtoul(p + 1, NULL, 2); + default: + return strtoul(p, NULL, 0); // intentional, to allow 0x hex, 0b binary, and 0 octal } } -void do_a(char *p) { +void do_a(char *p) +{ p = strchr(p, '='); if (p) { p++; @@ -457,7 +538,8 @@ void do_a(char *p) { } } -void do_x(char *p) { +void do_x(char *p) +{ p = strchr(p, '='); if (p) { p++; @@ -468,7 +550,8 @@ void do_x(char *p) { } } -void do_y(char *p) { +void do_y(char *p) +{ p = strchr(p, '='); if (p) { p++; @@ -479,7 +562,8 @@ void do_y(char *p) { } } -void do_pc(char *p) { +void do_pc(char *p) +{ p = strchr(p, '='); if (p) { p++; @@ -490,7 +574,8 @@ void do_pc(char *p) { } } -void do_sp(char *p) { +void do_sp(char *p) +{ p = strchr(p, '='); if (p) { p++; @@ -501,7 +586,8 @@ void do_sp(char *p) { } } -uint8_t flagmask(char *p) { +uint8_t flagmask(char *p) +{ static const char *match1 = "nv-bdizc"; static const char *match2 = "NV-BDIZC"; uint8_t ret = 0; @@ -521,7 +607,8 @@ uint8_t flagmask(char *p) { return ret; } -uint8_t flagval(char *p) { +uint8_t flagval(char *p) +{ static const char *match = "NV-BDIZC"; uint8_t ret = 0; @@ -536,7 +623,8 @@ uint8_t flagval(char *p) { return ret; } -void do_sr(char *p) { +void do_sr(char *p) +{ static const char *match = "nvbdizcNVBDIZC"; p = strchr(p, '='); @@ -554,7 +642,8 @@ void do_sr(char *p) { } } -void do_wpset(char *p) { +void do_wpset(char *p) +{ int begin, end; p = strchr(p, ' '); if (p) { @@ -564,7 +653,7 @@ void do_wpset(char *p) { *q = 0; q++; begin = parsenum(p); - end = parsenum(q); + end = parsenum(q); displaywin->printf("wp set %04x to %04x", begin, end); while (begin <= end && (begin & 0x7)) { @@ -591,7 +680,8 @@ void do_wpset(char *p) { } } -void do_wpclr(char *p) { +void do_wpclr(char *p) +{ int begin, end; p = strchr(p, ' '); if (p) { @@ -601,7 +691,7 @@ void do_wpclr(char *p) { *q = 0; q++; begin = parsenum(p); - end = parsenum(q); + end = parsenum(q); displaywin->printf("wp clr %04x to %04x", begin, end); while (begin <= end && (begin & 0x7)) { @@ -628,7 +718,8 @@ void do_wpclr(char *p) { } } -void do_breakset(char *p) { +void do_breakset(char *p) +{ p = strchr(p, ' '); if (p) { p++; @@ -641,7 +732,8 @@ void do_breakset(char *p) { } } -void do_breakclr(char *p) { +void do_breakclr(char *p) +{ p = strchr(p, ' '); if (p) { p++; @@ -654,7 +746,8 @@ void do_breakclr(char *p) { } } -void do_mem(char *p) { +void do_mem(char *p) +{ p = strchr(p, ' '); if (p) { p++; @@ -685,7 +778,8 @@ uint32_t fetch(const char *s, uint16_t offset, uint8_t count) return ret; } -void do_ihex(char *p) { +void do_ihex(char *p) +{ p = strchr(p, ' '); if (p) { p++; @@ -728,7 +822,8 @@ void do_ihex(char *p) { } } -void do_load(char *p) { +void do_load(char *p) +{ p = strchr(p, ' '); if (p) { p++; @@ -751,7 +846,7 @@ void do_load(char *p) { } } else { - termwin->printf("address out of range"); + termwin->printf("address out of range"); } } else { @@ -759,7 +854,8 @@ void do_load(char *p) { } } -void step(void) { +void step(void) +{ bus_error = false; breakpoint_hit = false; @@ -781,7 +877,8 @@ void step(void) { } } -void do_run(char *) { +void do_run(char *) +{ termwin->printf("running, hit space to break"); nodelay(stdscr, TRUE); // make getch() non-blocking while (!bus_error && !breakpoint_hit) { @@ -801,11 +898,13 @@ void do_run(char *) { nodelay(stdscr, FALSE); // make getch() blocking again } -void do_step(char *) { +void do_step(char *) +{ step(); } -void do_verbose(char *p) { +void do_verbose(char *p) +{ p = strchr(p, ' '); if (p) { p++; @@ -848,10 +947,12 @@ struct Commands { { "?", NULL, "same as help", do_help }, }; -void do_help(char *) { +void do_help(char *) +{ displaywin->printf("=== COMMANDS ==="); for (int i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) { - displaywin->printf("%s%s\t%s", commands[i].cmd, commands[i].args ? commands[i].args : "", commands[i].help); + displaywin->printf("%s%s\t%s", commands[i].cmd, commands[i].args ? commands[i].args : "", + commands[i].help); } displaywin->printf("================"); displaywin->printf("numbers may be entered as $xx (hex), %%bbbbbbbb (binary),"); @@ -859,7 +960,8 @@ void do_help(char *) { displaywin->printf("================"); } -void command(char *p) { +void command(char *p) +{ for (int i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) { if (!strncasecmp(p, commands[i].cmd, strlen(commands[i].cmd))) { commands[i].exe(p); @@ -869,7 +971,8 @@ void command(char *p) { termwin->printf("huh? type '?' for help"); } -int main() { +int main() +{ setup_ncurses(); cpu = new mos6502(bus_read, bus_write, tick); From d17d197f8da5c5ffc07fb64044ac7dc873075829 Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Sat, 18 Oct 2025 00:42:16 -0700 Subject: [PATCH 23/26] window shuffle --- dbg6502/dbg6502.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/dbg6502/dbg6502.cpp b/dbg6502/dbg6502.cpp index e80cc1b..5993099 100644 --- a/dbg6502/dbg6502.cpp +++ b/dbg6502/dbg6502.cpp @@ -951,12 +951,13 @@ void do_help(char *) { displaywin->printf("=== COMMANDS ==="); for (int i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) { - displaywin->printf("%s%s\t%s", commands[i].cmd, commands[i].args ? commands[i].args : "", - commands[i].help); + char buf[1024]; + sprintf(buf, "%s%s", commands[i].cmd, commands[i].args ? commands[i].args : ""); + displaywin->printf("%-30s %s", buf, commands[i].help); } displaywin->printf("================"); - displaywin->printf("numbers may be entered as $xx (hex), %%bbbbbbbb (binary),"); - displaywin->printf(" @ooo (octal) or decimal (no prefix)"); + displaywin->printf("numbers may be entered as $/0x (hex), %%/0b (binary),"); + displaywin->printf(" @/0 (octal) or decimal (no prefix)"); displaywin->printf("================"); } @@ -979,10 +980,10 @@ int main() int H, W; getmaxyx(stdscr, H, W); - termwin = new TerminalWin(0, H - 8, W, 8); - memwin = new MemoryWin(W - 57, H - 18 - 8, 57, 18); - regwin = new RegisterWin(W - 57, H - 18 - 8 - 3, 57, 3); - displaywin = new TerminalWin(0, 0, W-57, H - 8); + regwin = new RegisterWin(0, 0, 57, 3); + memwin = new MemoryWin(0, 3, 57, 18); + termwin = new TerminalWin(0, 3 + 18, 57, H - 3 - 18); + displaywin = new TerminalWin(58, 0, W-57, H); termwin->printf("Welcome. Type lines; use Up/Down to browse history. Enter accepts. ? for help"); char buf[1024]; From 4935cf56f6ea394f1b38a98c8261552e8df05687 Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Sat, 18 Oct 2025 01:06:55 -0700 Subject: [PATCH 24/26] added readme --- dbg6502/readme.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 dbg6502/readme.md diff --git a/dbg6502/readme.md b/dbg6502/readme.md new file mode 100644 index 0000000..f9da394 --- /dev/null +++ b/dbg6502/readme.md @@ -0,0 +1,57 @@ +this is a generic debugger based on the mos6502 class. + +when you run it, 4 windows appear. +the top left window shows processor registers. +the window below that shows memory. +the window below that allows commands. +the window on the right hand side shows various output. + +get a list of commands by typing "help" or "?" and pressing . +most of these are self explanatory............ + +A= set the A register +PC= set the PC +SP= set the STACK POINTER +SR= set the STATUS +X= set the X register +Y= set the Y register +break set a breakpoint +ihex load ihex file +load load binary file at addr +mem show memory at addr +reset reset the cpu +run run program to breakpoint +step single step program +unbreak clr a breakpoint +verbose set verbosity (0-3) +wpclr [-] clr write protect on address or range of addresses +wpset [-] set write protect on address or range of addresses +quit quit the program +exit same as quit +help print help +? same as help + +these commands are case insensitve. + +any place you see , the following formats are accepted: +$?? :: a hex number, like $55 +0x?? :: a hex number, like 0x55 +x?? :: a hex number, like x55 +%???????? :: a binary number, like %01010101 +b???????? :: a binary number, like b01010101 +0b???????? :: a binary number, like 0b01010101 +@??? :: an octal number, like @125 +o??? :: an octal number, like o125 +0??? :: an octal number, like 0125 +anything else is considered decimal. + +SR= is a special case: +the letters [nvbdizcNVBDIZC] are accepted. +SR=NV will set the N and V bits of the SR, leaving other bits alone. +SR=d will clear the D bit of the SR, leaving other bits alone. +SR=Zc will set the Z bit and clear the C bit of the SR, leaving other bits alone. + +for , tab completion should work as expected. + + +disassembly and lst file input is planned, but not yet implemented. From 21ca09c8a72ec6debbacd54e7d2a6ebb8fd1ed13 Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Sat, 18 Oct 2025 01:07:45 -0700 Subject: [PATCH 25/26] readme --- dbg6502/readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/dbg6502/readme.md b/dbg6502/readme.md index f9da394..32a224c 100644 --- a/dbg6502/readme.md +++ b/dbg6502/readme.md @@ -55,3 +55,4 @@ for , tab completion should work as expected. disassembly and lst file input is planned, but not yet implemented. +IRQ and NMI handling is also on the future roadmap. From f45027f1a87c6d454c1ff0989e563a10075bab76 Mon Sep 17 00:00:00 2001 From: Gorilla Sapiens Date: Sat, 18 Oct 2025 01:09:59 -0700 Subject: [PATCH 26/26] more prefixes in help output --- dbg6502/dbg6502.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dbg6502/dbg6502.cpp b/dbg6502/dbg6502.cpp index 5993099..322da98 100644 --- a/dbg6502/dbg6502.cpp +++ b/dbg6502/dbg6502.cpp @@ -956,8 +956,8 @@ void do_help(char *) displaywin->printf("%-30s %s", buf, commands[i].help); } displaywin->printf("================"); - displaywin->printf("numbers may be entered as $/0x (hex), %%/0b (binary),"); - displaywin->printf(" @/0 (octal) or decimal (no prefix)"); + displaywin->printf("numbers may be entered as $/x/0x (hex), %%/b/0b (binary),"); + displaywin->printf(" @/o/0 (octal) or decimal (no prefix)"); displaywin->printf("================"); }