From 501910b17fc57f96c40e2b7dd03ea1b001897240 Mon Sep 17 00:00:00 2001 From: n loewen Date: Wed, 16 Aug 2023 16:23:59 +0100 Subject: [PATCH] Feature: Keypad simulation! Refactor: Tidy up constants use for machine configuration --- assembler.js | 8 ++++++- cpu.js | 45 ++++++++++++++++++++++++++++++++++++---- display.js | 6 +++--- machine.config.js | 23 ++++++++++++++++---- readme.md | 31 +++++++++++++++++++++++---- sketches/stdin-stream.js | 20 ++++++++++++++++++ test-programs/keypad.asm | 30 +++++++++++++++++++++++++++ 7 files changed, 147 insertions(+), 16 deletions(-) create mode 100644 sketches/stdin-stream.js create mode 100644 test-programs/keypad.asm diff --git a/assembler.js b/assembler.js index bb7c293..9c6b8e8 100644 --- a/assembler.js +++ b/assembler.js @@ -1,5 +1,11 @@ const { logMemory, num2hex } = require('./logging.js'); -const { INITIAL_IP_ADDRESS, START_OF_DISPLAY_MEM, POINTER_TO_START_OF_DISPLAY_MEM } = require('./machine.config.js'); +const { + INITIAL_IP_ADDRESS, + DISPLAY_ADDR, + KEYPAD_ADDR, + POINTER_TO_DISPLAY, + POINTER_TO_KEYPAD +} = require('./machine.config.js'); // 1 = verbose // 2 = what i'm currently focusing on diff --git a/cpu.js b/cpu.js index fa3ebdb..91a9d60 100644 --- a/cpu.js +++ b/cpu.js @@ -1,7 +1,17 @@ +const readline = require('readline'); const readlineSync = require('readline-sync'); -const { INITIAL_IP_ADDRESS, CYCLE_LIMIT } = require('./machine.config'); -const { num2hex, num2bin_4bit } = require('./logging.js'); +const { + INITIAL_IP_ADDRESS, + CYCLE_LIMIT, + KEYPAD_ADDR, + KEY_MAP, +} = require('./machine.config'); + +const { + num2hex, + num2bin_4bit, +} = require('./logging.js'); const display = require('./display.js'); // STATE @@ -272,6 +282,32 @@ function startCPU(code) { CPU.loadMemory(code); CPU.cycleCounter = 0; CPU.running = true; + + // FIXME: This conflicts with single-stepping + // (you can single-step, but keys aren't passed + // through to the Cardiograph) + // + // -> The fix is maybe to remove readlineSync, + // and instead stash the keypress into a buffer variable.* + // Then have the stepping function check that buffer, + // and then clear the buffer, each time it runs. + // + // * If it's in the set of keys that are relevant + // to single-stepping. + + // Start listening for keypresses... + readline.emitKeypressEvents(process.stdin); + if (process.stdin.setRawMode != null) { + process.stdin.setRawMode(true); + } + process.stdin.on('keypress', (str, key) => { // TODO: is it possible to turn this off again? + if (key.sequence === '\x03') process.exit(); + + str = str.toUpperCase(); + if (str in KEY_MAP) { + CPU.memory[KEYPAD_ADDR] = KEY_MAP[str]; + } + }); } /** @@ -304,7 +340,7 @@ async function stepCPU(debug = false) { * @param {Boolean} [debug] - Enable/disable debugging printouts * @param {Number} [clockSpeed] - CPU clock speed in milliseconds **/ -exports.runProgram = (code, debug = false, clockSpeed = 10) => { +exports.runProgram = (code, debug = false, clockSpeed = 100) => { startCPU(code); // Animate the output by pausing between steps const loop = setInterval(async () => { @@ -348,7 +384,8 @@ function logCPUState(debug = false) { console.log('Mnemonic:', CPU.currentInstruction.mnemonic); console.log(`Machine: $${num2hex(CPU.currentInstruction.opcode)} $${num2hex(CPU.currentInstruction.operand)}`); console.log(); - console.log(`IP: $${num2hex(CPU.IP)} Acc: $${num2hex(CPU.Acc)} NZOC: ${num2bin_4bit(CPU.FLAGS)}  ${CPU.running ? "running" : "halted" }`); + console.log(`IP: $${num2hex(CPU.IP)} Acc: $${num2hex(CPU.Acc)} NZOC: ${num2bin_4bit(CPU.FLAGS)}`); + console.log(`KEY: $${num2hex(CPU.memory[KEYPAD_ADDR])}  ${CPU.running ? "running" : "halted" }`); console.log(); console.groupEnd(); }; \ No newline at end of file diff --git a/display.js b/display.js index 9fe6303..5ae6999 100644 --- a/display.js +++ b/display.js @@ -1,4 +1,4 @@ -const { POINTER_TO_START_OF_DISPLAY_MEM } = require('./machine.config'); +const { POINTER_TO_DISPLAY } = require('./machine.config'); const { num2hex } = require('./logging.js'); /** @@ -6,7 +6,7 @@ const { num2hex } = require('./logging.js'); * @param {Uint8Array} mem - CPU memory **/ const printDisplay = (mem) => { - const disp = mem[POINTER_TO_START_OF_DISPLAY_MEM]; + const disp = mem[POINTER_TO_DISPLAY]; for (let i = disp; i < disp + 16; i += 4) { console.log(`${num2hex(mem[i])} ${num2hex(mem[i+1])} ${num2hex(mem[i+2])} ${num2hex(mem[i+3])}`); } @@ -17,7 +17,7 @@ const printDisplay = (mem) => { * @param {Uint8Array} mem - CPU memory **/ const prettyPrintDisplay = (mem) => { - const disp = mem[POINTER_TO_START_OF_DISPLAY_MEM]; + const disp = mem[POINTER_TO_DISPLAY]; const num2pic = (n) => n > 0 ? '⚫' : '⚪'; for (let i = disp; i < disp + 16; i += 4) { console.log(`${num2pic(mem[i])}${num2pic(mem[i+1])}${num2pic(mem[i+2])}${num2pic(mem[i+3])}`); diff --git a/machine.config.js b/machine.config.js index b526793..dd73423 100644 --- a/machine.config.js +++ b/machine.config.js @@ -1,11 +1,26 @@ module.exports = { - "START_OF_DISPLAY_MEM": 0, - "POINTER_TO_START_OF_DISPLAY_MEM": 32, - "POINTER_TO_START_OF_KEYPAD_MEM": 33, "INITIAL_IP_ADDRESS": 48, + // Use these in CPU: + "DISPLAY_ADDR": 0, + "KEYPAD_ADDR": 32, + // Store the `X_ADDR`s at these addresses when assembling: + "POINTER_TO_DISPLAY": 33, + "POINTER_TO_KEYPAD": 34, + + "KEY_MAP": { + // Same layout as COSMAC VIP / CHIP-8 + // (This object maps qwerty keys to hex keys + // so that they are arranged in the same layout + // as the real keypad) + '1':'1', '2':'2', '3':'3', '4':'C', + 'Q':'4', 'W':'5', 'E':'6', 'R':'D', + 'A':'7', 'S':'8', 'D':'9', 'F':'E', + 'Z':'A', 'X':'0', 'C':'B', 'V':'F', + }, + // max number of times to step the CPU, // to stop endless loops // 0 = infinite - "CYCLE_LIMIT": 128, + "CYCLE_LIMIT": 256, } \ No newline at end of file diff --git a/readme.md b/readme.md index 4f594d5..b121320 100644 --- a/readme.md +++ b/readme.md @@ -69,13 +69,36 @@ With single stepping + verbose debugging output: ## Memory map / Peripherals - `00-0F` - display (4x4) -- `10-1F` - keypad? (details TBD) -- `20 ` - pointer to display memory -- `21 ` - pointer to keypad memory -- `22-2F` - reserved for future use / variable storage +- `10-19` - reserved for future use +- `20 ` - keypad - value of the most recent keypress +- `21 ` - pointer to display memory +- `22 ` - pointer to keypad memory +- `23-2F` - reserved for future use / variable storage - `30 ` - initial value for IP - `30-FF` - free +### Keypad + +The value of the latest keypress on a hex keypad is stored at `$20`. +(The keypad can also be relocated by changing the value of the pointer-to-keypad at `$22`.) + +The keypad uses the same layout as the COSMAC VIP (and CHIP-8): + +``` +1 2 3 C +4 5 6 D +7 8 9 E +A 0 B F +``` +The CPU simulator maps the following Qwerty keys onto those values: + +``` +1 2 3 4 +Q W E R +A S D F +Z X C V +``` + ## Assembly language ADD $01 ; comments follow a `;` diff --git a/sketches/stdin-stream.js b/sketches/stdin-stream.js new file mode 100644 index 0000000..0ca87ab --- /dev/null +++ b/sketches/stdin-stream.js @@ -0,0 +1,20 @@ +const readline = require('readline'); + +readline.emitKeypressEvents(process.stdin); + +if (process.stdin.setRawMode != null) { + process.stdin.setRawMode(true); +} + +process.stdin.on('keypress', (str, key) => { + console.log(str) + console.log(key) + if (key.sequence === '\x03') process.exit(); +}) + +let i = 0; +const loop = setInterval(async () => { + console.log('loop #', i); + if (i > 10) clearInterval(loop); + i += 1; +}, 250); \ No newline at end of file diff --git a/test-programs/keypad.asm b/test-programs/keypad.asm new file mode 100644 index 0000000..79b9cbe --- /dev/null +++ b/test-programs/keypad.asm @@ -0,0 +1,30 @@ +;; Test keypad input +;; 2023-08-16 + +; The latest keypress is shown in the top left corner of the display. +; A loop counter is shown in the top right corner; the program ends when it reaches zero. +; (For a 4x4 display.) + +#LOOPCOUNT $80 + +#Z 2 ; the zero flag is #2 +#keypad $20 ; magic memory location containing latest key pressed +#loopIter $FF ; address of loop iterator +#iterPx $03 ; where to display iterator +#keyPx $00 ; where to display key + +LDA #LOOPCOUNT +STO #loopIter + +@loop + LDA (#loopIter) + STO #iterPx ; display loop iterator + LDA (#keypad) + STO #keyPx ; display latest keypress + LDA (#loopIter) + SUB 1 + STO #loopIter + FTG #Z + FHP #Z + END + JMP @loop \ No newline at end of file