Feature: Keypad simulation!
Refactor: Tidy up constants use for machine configuration
This commit is contained in:
parent
b8265409cf
commit
501910b17f
|
|
@ -1,5 +1,11 @@
|
||||||
const { logMemory, num2hex } = require('./logging.js');
|
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
|
// 1 = verbose
|
||||||
// 2 = what i'm currently focusing on
|
// 2 = what i'm currently focusing on
|
||||||
|
|
|
||||||
45
cpu.js
45
cpu.js
|
|
@ -1,7 +1,17 @@
|
||||||
|
const readline = require('readline');
|
||||||
const readlineSync = require('readline-sync');
|
const readlineSync = require('readline-sync');
|
||||||
|
|
||||||
const { INITIAL_IP_ADDRESS, CYCLE_LIMIT } = require('./machine.config');
|
const {
|
||||||
const { num2hex, num2bin_4bit } = require('./logging.js');
|
INITIAL_IP_ADDRESS,
|
||||||
|
CYCLE_LIMIT,
|
||||||
|
KEYPAD_ADDR,
|
||||||
|
KEY_MAP,
|
||||||
|
} = require('./machine.config');
|
||||||
|
|
||||||
|
const {
|
||||||
|
num2hex,
|
||||||
|
num2bin_4bit,
|
||||||
|
} = require('./logging.js');
|
||||||
const display = require('./display.js');
|
const display = require('./display.js');
|
||||||
|
|
||||||
// STATE
|
// STATE
|
||||||
|
|
@ -272,6 +282,32 @@ function startCPU(code) {
|
||||||
CPU.loadMemory(code);
|
CPU.loadMemory(code);
|
||||||
CPU.cycleCounter = 0;
|
CPU.cycleCounter = 0;
|
||||||
CPU.running = true;
|
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 {Boolean} [debug] - Enable/disable debugging printouts
|
||||||
* @param {Number} [clockSpeed] - CPU clock speed in milliseconds
|
* @param {Number} [clockSpeed] - CPU clock speed in milliseconds
|
||||||
**/
|
**/
|
||||||
exports.runProgram = (code, debug = false, clockSpeed = 10) => {
|
exports.runProgram = (code, debug = false, clockSpeed = 100) => {
|
||||||
startCPU(code);
|
startCPU(code);
|
||||||
// Animate the output by pausing between steps
|
// Animate the output by pausing between steps
|
||||||
const loop = setInterval(async () => {
|
const loop = setInterval(async () => {
|
||||||
|
|
@ -348,7 +384,8 @@ function logCPUState(debug = false) {
|
||||||
console.log('Mnemonic:', CPU.currentInstruction.mnemonic);
|
console.log('Mnemonic:', CPU.currentInstruction.mnemonic);
|
||||||
console.log(`Machine: $${num2hex(CPU.currentInstruction.opcode)} $${num2hex(CPU.currentInstruction.operand)}`);
|
console.log(`Machine: $${num2hex(CPU.currentInstruction.opcode)} $${num2hex(CPU.currentInstruction.operand)}`);
|
||||||
console.log();
|
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.log();
|
||||||
console.groupEnd();
|
console.groupEnd();
|
||||||
};
|
};
|
||||||
|
|
@ -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');
|
const { num2hex } = require('./logging.js');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -6,7 +6,7 @@ const { num2hex } = require('./logging.js');
|
||||||
* @param {Uint8Array} mem - CPU memory
|
* @param {Uint8Array} mem - CPU memory
|
||||||
**/
|
**/
|
||||||
const printDisplay = (mem) => {
|
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) {
|
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])}`);
|
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
|
* @param {Uint8Array} mem - CPU memory
|
||||||
**/
|
**/
|
||||||
const prettyPrintDisplay = (mem) => {
|
const prettyPrintDisplay = (mem) => {
|
||||||
const disp = mem[POINTER_TO_START_OF_DISPLAY_MEM];
|
const disp = mem[POINTER_TO_DISPLAY];
|
||||||
const num2pic = (n) => n > 0 ? '⚫' : '⚪';
|
const num2pic = (n) => n > 0 ? '⚫' : '⚪';
|
||||||
for (let i = disp; i < disp + 16; i += 4) {
|
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])}`);
|
console.log(`${num2pic(mem[i])}${num2pic(mem[i+1])}${num2pic(mem[i+2])}${num2pic(mem[i+3])}`);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,26 @@
|
||||||
module.exports = {
|
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,
|
"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,
|
// max number of times to step the CPU,
|
||||||
// to stop endless loops
|
// to stop endless loops
|
||||||
// 0 = infinite
|
// 0 = infinite
|
||||||
"CYCLE_LIMIT": 128,
|
"CYCLE_LIMIT": 256,
|
||||||
}
|
}
|
||||||
31
readme.md
31
readme.md
|
|
@ -69,13 +69,36 @@ With single stepping + verbose debugging output:
|
||||||
## Memory map / Peripherals
|
## Memory map / Peripherals
|
||||||
|
|
||||||
- `00-0F` - display (4x4)
|
- `00-0F` - display (4x4)
|
||||||
- `10-1F` - keypad? (details TBD)
|
- `10-19` - reserved for future use
|
||||||
- `20 ` - pointer to display memory
|
- `20 ` - keypad - value of the most recent keypress
|
||||||
- `21 ` - pointer to keypad memory
|
- `21 ` - pointer to display memory
|
||||||
- `22-2F` - reserved for future use / variable storage
|
- `22 ` - pointer to keypad memory
|
||||||
|
- `23-2F` - reserved for future use / variable storage
|
||||||
- `30 ` - initial value for IP
|
- `30 ` - initial value for IP
|
||||||
- `30-FF` - free
|
- `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
|
## Assembly language
|
||||||
|
|
||||||
ADD $01 ; comments follow a `;`
|
ADD $01 ; comments follow a `;`
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in New Issue