Feature: Keypad simulation!

Refactor: Tidy up constants use for machine configuration
This commit is contained in:
n loewen 2023-08-16 16:23:59 +01:00
parent b8265409cf
commit 501910b17f
7 changed files with 147 additions and 16 deletions

View File

@ -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

45
cpu.js
View File

@ -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();
};

View File

@ -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])}`);

View File

@ -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,
}

View File

@ -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 `;`

20
sketches/stdin-stream.js Normal file
View File

@ -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);

30
test-programs/keypad.asm Normal file
View File

@ -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