From e2568226af4af2d3b27e9e1a775e7244c5ef7191 Mon Sep 17 00:00:00 2001 From: WlodekM Date: Thu, 3 Apr 2025 21:51:53 +0300 Subject: more progress on trying to get wozmon running --- 65c02.ts | 16 ++++ eater.ts | 76 +++++++++++++++++++ instructions/ASL.ts | 1 + runtime.ts | 130 +++++++++++++++++++++----------- thing.s | 6 +- wozmon.s | 208 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 391 insertions(+), 46 deletions(-) create mode 100644 eater.ts create mode 100644 wozmon.s diff --git a/65c02.ts b/65c02.ts index f1368a9..fb8f050 100644 --- a/65c02.ts +++ b/65c02.ts @@ -157,6 +157,22 @@ export default class The65c02 { this.programCounter.set(resetVector); return; } + if (this.io.interruptRequest.high && !this.IRQBDisable) { + console.log('interrupt!') + this.IRQBDisable = true; + this.push(this.programCounter.num() & 0x00FF) + this.push(this.programCounter.num() & 0xFF00) + this.push(this.status.num()) + let interruptVector = 0; + this.io.address.set(0xFFFE); + this.read() + interruptVector |= this.io.data.num(); + this.io.address.set(0xFFFF); + this.read() + interruptVector |= this.io.data.num() << 8; + this.programCounter.set(interruptVector); + return; + } this.io.address.set(this.programCounter.num()); this.read(); const instruction = this.io.data diff --git a/eater.ts b/eater.ts new file mode 100644 index 0000000..4440635 --- /dev/null +++ b/eater.ts @@ -0,0 +1,76 @@ +// deno-lint-ignore-file no-process-globals +import The65c02 from "./65c02.ts"; +// eater.ts +// a runtime meant to mimic ben eater's 65c02 computer + +if (!process.stdin.isRaw) + process.stdin.setRawMode(true) + +// the thing used for ram +const ram = new Uint8Array(2**16); + +// initiate the emulator +const cpu = new The65c02(function (this: The65c02) { + // since this is controlled by the user it's easy + // to map things into address space + this.io.data.set(ram[this.io.address.num()] ?? 0) + if (this.io.address.num() == 0x5000 + && ram[0x5001]) + ram[0x5001] = 0; +}, function (this: The65c02) { + if (this.io.address.num() == 0x5000) { + return Deno.stdout.write(new Uint8Array([this.io.data.num()])) + } + // write + ram[this.io.address.num()] = this.io.data.num() +}) + +const binStart = 0; + +await cpu.loadInstructions() + +const code = Deno.readFileSync('a.out') + +// mem address $0000 +ram[0xFFFC] = binStart & 0x00FF +ram[0xFFFD] = (binStart & 0xFF00) >> 8 + +// write code to ram before execution +for (let offset = 0; offset < code.length; offset++) { + const byte = code[offset]; + ram[binStart + offset] = byte; +} + +// pull RESB low to reset the 65c02 +cpu.io.reset.LO() +cpu.cycle() + +//the cpu reset, pull RESB high and start execution! +cpu.io.reset.HI() + + +let running = true; + +process.stdin.on('data', (data) => { + if (data[0] == 3) + return running = false; + // console.log(`uh`, data[0], data[0].toString(16)) + ram[0x5000] = data[0]; + ram[0x5001] = 0x08; + // cpu.io.interruptRequest.HI(); +}) + +// repeat until the cpu requests an interrupt +const clock = setInterval(() => { + if (!running) { + Deno.writeFileSync('ram.bin', ram) + clearInterval(clock) + + process.stdin.setRawMode(false); + Deno.exit(0) + } + if(cpu.io.interruptRequest.high && !cpu.IRQBDisable) + cpu.io.interruptRequest.LO(); + cpu.cycle(); +// 1MHz i think +}, 1) diff --git a/instructions/ASL.ts b/instructions/ASL.ts index 1f3fcbb..d65df3f 100644 --- a/instructions/ASL.ts +++ b/instructions/ASL.ts @@ -7,6 +7,7 @@ export default function (this: The65c02, mode: string) { this.negative = (mem & 128) != 0 this.carry = (mem & 256) != 0 this.regA.set(result & 0xFF); + this.programCounter.increment() } else { const addr = this.getAddr(mode) this.io.address.set(addr); diff --git a/runtime.ts b/runtime.ts index 8ecfe1b..1527c62 100644 --- a/runtime.ts +++ b/runtime.ts @@ -59,11 +59,11 @@ function inspect() { }).join('\n')); console.log('\nregisters:') - console.log(` A: ${cpu.regA.bits.reverse().map(k=>+k).join('')} (0x${cpu.regA.num().toString(16)})`) - console.log(` X: ${cpu.regX.bits.reverse().map(k=>+k).join('')} (0x${cpu.regX.num().toString(16)})`) - console.log(` Y: ${cpu.regY.bits.reverse().map(k=>+k).join('')} (0x${cpu.regY.num().toString(16)})`) - console.log(` SP: ${cpu.stackPointer.bits.reverse().map(k=>+k).join('')} (0x${cpu.stackPointer.num().toString(16)})`) - console.log(` PC: ${cpu.programCounter.bits.reverse().map(k=>+k).join('')} (0x${cpu.programCounter.num().toString(16)})`) + console.log(` A: ${cpu.regA.bits.reverse().map(k=>+k).join('')} (0x${cpu.regA.num().toString(16)})`) + console.log(` X: ${cpu.regX.bits.reverse().map(k=>+k).join('')} (0x${cpu.regX.num().toString(16)})`) + console.log(` Y: ${cpu.regY.bits.reverse().map(k=>+k).join('')} (0x${cpu.regY.num().toString(16)})`) + console.log(` SP: ${cpu.stackPointer.bits.reverse().map(k=>+k).join('')} (0x${cpu.stackPointer.num().toString(16)})`) + console.log(` PC: ${cpu.programCounter.bits.reverse().map(k=>+k).join('')} (0x${cpu.programCounter.num().toString(16)})`) } const debug = Deno.args.includes('-d') @@ -77,6 +77,7 @@ if (debug) { } // repeat until the cpu requests an interrupt +mainloop: while (!cpu.io.interruptRequest.high) { const instId = ram[cpu.programCounter.num()] .toString(16) @@ -106,33 +107,39 @@ while (!cpu.io.interruptRequest.high) { skip-- break dbg; } - const i = new Uint8Array(8); - Deno.stdout.write(Uint8Array.from(['.'.charCodeAt(0)])) - await Deno.stdin.read(i); - if (i[0] == 'b'.charCodeAt(0)) { - console.log('BREAK!!') - break; - } else if (i[0] == 'i'.charCodeAt(0)) { - inspect() - } else if (i[0] == 's'.charCodeAt(0)) { - console.log('stack:') - for (let i = 0; i < cpu.stackPointer.num(); i++) { - console.log(` ${i.toString(16).padStart(2, '0')} ${ram[0x01FF - i].toString(2).padStart(8, '0')} (0x${ram[0x01FF - i].toString(16).padStart(4, '0')} ${ram[0x01FF - i].toString().padStart(3)})`) - } - } else if (i[0] == 'k'.charCodeAt(0)) { - const num = +(new TextDecoder().decode(i.slice(1, 7)).replaceAll('\0', '')); - if (Number.isNaN(num)) { - console.log('NaN') - break dbg; - } - skip = num; - console.log(`skipping ${num} cycles`) - } else if (i[0] == 'r'.charCodeAt(0)) { - const num = i[2] ? parseInt(new TextDecoder().decode(i.slice(1, 7)).replace('\n', '').replaceAll('\0', ''), 16) : cpu.programCounter.num(); - console.log(`set breakpoint on`, num.toString(16)) - breakpoints.push(num) - } else if (i[0] == '?'.charCodeAt(0)) { - console.log(`b - break, exit + dbgl: + while (true) { + const i = new Uint8Array(16); + Deno.stdout.write(Uint8Array.from(['.'.charCodeAt(0)])) + await Deno.stdin.read(i); + if (i[0] == 'b'.charCodeAt(0)) { + console.log('BREAK!!') + break mainloop; + } else if (i[0] == 'i'.charCodeAt(0)) { + inspect() + continue; + } else if (i[0] == 's'.charCodeAt(0)) { + console.log('stack:') + for (let i = 0; i < cpu.stackPointer.num(); i++) { + console.log(` ${i.toString(16).padStart(2, '0')} ${ram[0x01FF - i].toString(2).padStart(8, '0')} (0x${ram[0x01FF - i].toString(16).padStart(4, '0')} ${ram[0x01FF - i].toString().padStart(3)})`) + } + continue; + } else if (i[0] == 'k'.charCodeAt(0)) { + const num = +(new TextDecoder().decode(i.slice(1, 7)).replaceAll('\0', '')); + if (Number.isNaN(num)) { + console.log('NaN') + break dbg; + } + skip = num; + console.log(`skipping ${num} cycles`) + break dbgl; + } else if (i[0] == 'r'.charCodeAt(0)) { + const num = i[2] ? parseInt(new TextDecoder().decode(i.slice(1, 7)).replace('\n', '').replaceAll('\0', ''), 16) : cpu.programCounter.num(); + console.log(`set breakpoint on`, num.toString(16)) + breakpoints.push(num) + continue; + } else if (i[0] == '?'.charCodeAt(0)) { + console.log(`b - break, exit i - inspect s - inspect stack c - continue @@ -140,17 +147,54 @@ k[NUM] - skip r[ADR] - breakpoint g[ADR] - goto, change PC I[INS] - breakpoint instruction`); - } else if (i[0] == 'g'.charCodeAt(0)) { - const num = i[2] ? parseInt(new TextDecoder().decode(i.slice(1, 7)).replace('\n', '').replaceAll('\0', ''), 16) : cpu.programCounter.num(); - console.log(`PC set to`, num.toString(16)) - cpu.programCounter.set(num) - } else if (i[0] == 'I'.charCodeAt(0)) { - const instr = new TextDecoder().decode(i.slice(1, 7)).replace('\n', '').replaceAll('\0', '') - console.log(`instruction breakpoint set on`, instr) - instBreakpoints.push(instr) - } else if (i[0] == 'c'.charCodeAt(0)) { - skip = -1 - console.log(`continuing execution`) + continue; + } else if (i[0] == 'g'.charCodeAt(0)) { + const num = i[2] ? parseInt(new TextDecoder().decode(i.slice(1, 7)).replace('\n', '').replaceAll('\0', ''), 16) : cpu.programCounter.num(); + console.log(`PC set to`, num.toString(16)) + cpu.programCounter.set(num) + continue; + } else if (i[0] == 'I'.charCodeAt(0)) { + const instr = new TextDecoder().decode(i.slice(1, 7)).replace('\n', '').replaceAll('\0', '') + console.log(`instruction breakpoint set on`, instr) + instBreakpoints.push(instr) + continue; + } else if (i[0] == 'c'.charCodeAt(0)) { + skip = -1 + console.log(`continuing execution`) + } else if (i[0] == ':'.charCodeAt(0)) { + const instr = new TextDecoder().decode(i.slice(1, 15)).replace('\n', '').replaceAll('\0', '') + const match = [...instr.matchAll(/^([a-fA-F0-9]{1,4})=([a-fA-F0-9]{1,2})$/g)]; + if (!match || !match[0]) { + console.log('not matched, uhoh') + continue; + } + const [_, addr, data] = match[0] + console.log(`set $${addr} to 0x${data}`) + ram[parseInt(addr, 16)] = parseInt(data, 16) + continue; + } else if (i[0] == '\\'.charCodeAt(0)) { + const num = parseInt(new TextDecoder().decode(i.slice(1, 7)).replace('\n', '').replaceAll('\0', ''), 16); + if (Number.isNaN(num)) + continue; + console.log(`${num.toString(16).padStart(4, '0')}: ${ram[num].toString(16).padStart(2, '0')}`) + continue; + } else if (i[0] == '\''.charCodeAt(0)) { + if (i[1] == '\n'.charCodeAt(0)) { + ram[0x5000] = 0; + ram[0x5001] = 0; + console.log('reset') + continue; + } + const num = parseInt(new TextDecoder().decode(i.slice(1, 3)).replace('\n', '').replaceAll('\0', ''), 16); + if (Number.isNaN(num)) + continue; + ram[0x5000] = num; + ram[0x5001] = 0x08; + console.log(`set $5000 to 0x${num.toString(16).padStart(2, '0')} and $5001 to 08`) + console.log('skipping 8 cycles'); + skip = 8; + } + break; } } cpu.cycle(); diff --git a/thing.s b/thing.s index e5df832..115f596 100644 --- a/thing.s +++ b/thing.s @@ -11,8 +11,8 @@ _start: CHROUT: pha sta ACIA_DATA - lda #$FF -.txdelay: dec - bne .txdelay +; lda #$FF +; .txdelay: dec +; bne .txdelay pla rts \ No newline at end of file diff --git a/wozmon.s b/wozmon.s new file mode 100644 index 0000000..4fd7d10 --- /dev/null +++ b/wozmon.s @@ -0,0 +1,208 @@ +ACIA_DATA = $5000 +ACIA_STATUS = $5001 +ACIA_CMD = $5002 +ACIA_CTRL = $5003 + +XAML = $24 ; Last "opened" location Low +XAMH = $25 ; Last "opened" location High +STL = $26 ; Store address Low +STH = $27 ; Store address High +L = $28 ; Hex value parsing Low +H = $29 ; Hex value parsing High +YSAV = $2A ; Used to see if hex value is given +MODE = $2B ; $00=XAM, $7F=STOR, $AE=BLOCK XAM + +IN = $0200 ; Input buffer + +RESET: + CLD ; Clear decimal arithmetic mode. + CLI + LDA #CHRIN + STA $FFFF + LDA #$1F ; 8-N-1, 19200 bps + STA ACIA_CTRL + LDY #$8B ; No parity, no echo, no interrupts. + STY ACIA_CMD + +NOTCR: + CMP #$08 ; Backspace key? + BEQ BACKSPACE ; Yes. + CMP #$1B ; ESC? + BEQ ESCAPE ; Yes. + INY ; Advance text index. + BPL NEXTCHAR ; Auto ESC if line longer than 127. + +ESCAPE: + LDA #$5C ; "\". + JSR ECHO ; Output it. + +GETLINE: + LDA #$0D ; Send CR + JSR ECHO + LDA #$0A ; Send LF + JSR ECHO + + LDY #$01 ; Initialize text index. +BACKSPACE: DEY ; Back up text index. + BMI GETLINE ; Beyond start of line, reinitialize. + +NEXTCHAR: + LDA ACIA_STATUS ; Check status. + AND #$08 ; Key ready? + BEQ NEXTCHAR ; Loop until ready. + LDA ACIA_DATA ; Load character. B7 will be '0'. + STA IN,Y ; Add to text buffer. + JSR ECHO ; Display character. + CMP #$0D ; CR? + BNE NOTCR ; No. + + LDY #$FF ; Reset text index. + LDA #$00 ; For XAM mode. + TAX ; X=0. +SETBLOCK: + ASL +SETSTOR: + ASL ; Leaves $7B if setting STOR mode. +SETMODE: + STA MODE ; $00 = XAM, $74 = STOR, $B8 = BLOK XAM. +BLSKIP: + INY ; Advance text index. +NEXTITEM: + LDA IN,Y ; Get character. + CMP #$0D ; CR? + BEQ GETLINE ; Yes, done this line. + CMP #$2E ; "."? + BCC BLSKIP ; Skip delimiter. + BEQ SETBLOCK ; Set BLOCK XAM mode. + CMP #$3A ; ":"? + BEQ SETSTOR ; Yes, set STOR mode. + CMP #$52 ; "R"? + BEQ RUNPROG ; Yes, run user program. + STX L ; $00 -> L. + STX H ; and H. + STY YSAV ; Save Y for comparison + +NEXTHEX: + LDA IN,Y ; Get character for hex test. + EOR #$30 ; Map digits to $0-9. + CMP #$0A ; Digit? + BCC DIG ; Yes. + ADC #$88 ; Map letter "A"-"F" to $FA-FF. + CMP #$FA ; Hex letter? + BCC NOTHEX ; No, character not hex. +DIG: + ASL + ASL ; Hex digit to MSD of A. + ASL + ASL + + LDX #$04 ; Shift count. +HEXSHIFT: + ASL ; Hex digit left, MSB to carry. + ROL L ; Rotate into LSD. + ROL H ; Rotate into MSD's. + DEX ; Done 4 shifts? + BNE HEXSHIFT ; No, loop. + INY ; Advance text index. + BNE NEXTHEX ; Always taken. Check next character for hex. + +NOTHEX: + CPY YSAV ; Check if L, H empty (no hex digits). + BEQ ESCAPE ; Yes, generate ESC sequence. + + BIT MODE ; Test MODE byte. + BVC NOTSTOR ; B6=0 is STOR, 1 is XAM and BLOCK XAM. + + LDA L ; LSD's of hex data. + STA (STL,X) ; Store current 'store index'. + INC STL ; Increment store index. + BNE NEXTITEM ; Get next item (no carry). + INC STH ; Add carry to 'store index' high order. +TONEXTITEM: JMP NEXTITEM ; Get next command item. + +RUNPROG: + JMP (XAML) ; Run at current XAM index. + +NOTSTOR: + BMI XAMNEXT ; B7 = 0 for XAM, 1 for BLOCK XAM. + + LDX #$02 ; Byte count. +SETADR: LDA L-1,X ; Copy hex data to + STA STL-1,X ; 'store index'. + STA XAML-1,X ; And to 'XAM index'. + DEX ; Next of 2 bytes. + BNE SETADR ; Loop unless X = 0. + +NXTPRNT: + BNE PRDATA ; NE means no address to print. + LDA #$0D ; CR. + JSR ECHO ; Output it. + LDA #$0A ; LF. + JSR ECHO ; Output it. + LDA XAMH ; 'Examine index' high-order byte. + JSR PRBYTE ; Output it in hex format. + LDA XAML ; Low-order 'examine index' byte. + JSR PRBYTE ; Output it in hex format. + LDA #$3A ; ":". + JSR ECHO ; Output it. + +PRDATA: + LDA #$20 ; Blank. + JSR ECHO ; Output it. + LDA (XAML,X) ; Get data byte at 'examine index'. + JSR PRBYTE ; Output it in hex format. +XAMNEXT: STX MODE ; 0 -> MODE (XAM mode). + LDA XAML + CMP L ; Compare 'examine index' to hex data. + LDA XAMH + SBC H + BCS TONEXTITEM ; Not less, so no more data to output. + + INC XAML + BNE MOD8CHK ; Increment 'examine index'. + INC XAMH + +MOD8CHK: + LDA XAML ; Check low-order 'examine index' byte + AND #$07 ; For MOD 8 = 0 + BPL NXTPRNT ; Always taken. + +PRBYTE: + PHA ; Save A for LSD. + LSR + LSR + LSR ; MSD to LSD position. + LSR + JSR PRHEX ; Output hex digit. + PLA ; Restore A. + +PRHEX: + AND #$0F ; Mask LSD for hex print. + ORA #$30 ; Add "0". + CMP #$3A ; Digit? + BCC ECHO ; Yes, output it. + ADC #$06 ; Add offset for letter. + +ECHO: + STA ACIA_DATA ; Output character. + PHA ; Save A. +; LDA #$FF ; Initialize delay loop. +; TXDELAY: DEC ; Decrement A. +; BNE TXDELAY ; Until A gets to 0. + PLA ; Restore A. + RTS ; Return. + +CHRIN: + RTI + +CHROUT: + pha + sta ACIA_DATA +; lda #$FF +; .txdelay: dec +; bne .txdelay + pla + rts + -- cgit 1.4.1-2-gfad0