Feature: WIP: Replace CHP and CFC instructions with FHP and FTG (carry flag is implemented, other flags are not)
This commit is contained in:
parent
2d38e28957
commit
b1dc2a7c7e
|
|
@ -13,7 +13,7 @@ exports.assemble = (str, debug = false) => {
|
|||
return decodeInstructions(str);
|
||||
}
|
||||
|
||||
const mnemonicsWithOptionalArgs = ['end', 'cfc', 'chp', 'nop'];
|
||||
const mnemonicsWithOptionalArgs = ['end', 'nop'];
|
||||
const mnemonics2opcodes = {
|
||||
end: { direct: 0, indirect: 0 },
|
||||
sto: { direct: 1, indirect: 2 },
|
||||
|
|
@ -22,8 +22,8 @@ const mnemonics2opcodes = {
|
|||
sub: { direct: 7, indirect: 8 },
|
||||
hop: { direct: 9, indirect: 10 },
|
||||
jmp: { direct: 11, indirect: 12 },
|
||||
cfc: { direct: 13, indirect: 13 },
|
||||
chp: { direct: 14, indirect: 14 },
|
||||
ftg: { direct: 13, indirect: 13 },
|
||||
fhp: { direct: 14, indirect: 14 },
|
||||
nop: { direct: 15, indirect: 15 },
|
||||
};
|
||||
|
||||
|
|
|
|||
19
logging.js
19
logging.js
|
|
@ -50,17 +50,34 @@ const num2hex = (num) => num.toString(16).toUpperCase().padStart(2, "0");
|
|||
const hex2num = (hex) => parseInt(hex, 16);
|
||||
|
||||
/**
|
||||
* Convert a number to binary
|
||||
* Convert a number to binary, padded to 8 bits
|
||||
* See here for an explanation: https://stackoverflow.com/questions/9939760/how-do-i-convert-an-integer-to-binary-in-javascript
|
||||
* @param {number} num
|
||||
* @returns {string} binary representation of the input
|
||||
**/
|
||||
const num2bin = (num) => (num >>> 0).toString(2).padStart(8, "0");
|
||||
|
||||
/**
|
||||
* Convert a number to binary, padded to 4 bits
|
||||
* See here for an explanation: https://stackoverflow.com/questions/9939760/how-do-i-convert-an-integer-to-binary-in-javascript
|
||||
* @param {number} num
|
||||
* @returns {string} binary representation of the input
|
||||
**/
|
||||
const num2bin_4bit = (num) => (num >>> 0).toString(2).padStart(4, "0");
|
||||
|
||||
/**
|
||||
* @param {string} bin
|
||||
* @returns {number}
|
||||
*/
|
||||
const bin2num = (bin) => parseInt(bin, 2)
|
||||
|
||||
|
||||
module.exports = {
|
||||
"logMemory": logMemory,
|
||||
"logRunningHeader": logRunningHeader,
|
||||
"num2hex": num2hex,
|
||||
"hex2num": hex2num,
|
||||
"num2bin": num2bin,
|
||||
"num2bin_4bit": num2bin_4bit,
|
||||
"bin2num": bin2num,
|
||||
}
|
||||
|
|
@ -11,8 +11,9 @@
|
|||
- Steve Losh: https://stevelosh.com/blog/2016/12/chip8-input/
|
||||
- https://tonisagrista.com/blog/2021/chip8-spec/
|
||||
|
||||
## Misc
|
||||
## Documentation
|
||||
|
||||
- [ ] Improve docs for flags register
|
||||
- [ ] Play with JSDoc
|
||||
|
||||
## Design
|
||||
|
|
|
|||
20
readme.md
20
readme.md
|
|
@ -36,12 +36,12 @@ With verbose debugging output:
|
|||
06 ADD addr ; add ... A <- A + mem[addr] ... and un/set carry flag
|
||||
07 SUB lit# ; sub ... A <- A - lit# ... and un/set carry flag
|
||||
08 SUB addr ; sub ... A <- A - mem[addr] ... and un/set carry flag
|
||||
09 HOP lit# ; hop ... skip next instruction if A == lit# ... when true: IP <- PC + 2
|
||||
0A HOP addr ; hop ... skip next instruction if A == addr ... when true: IP <- PC + 2
|
||||
09 HOP lit# ; hop ... skip next instruction if A == lit# ... when true: IP <- PC + 4
|
||||
0A HOP addr ; hop ... skip next instruction if A == addr ... when true: IP <- PC + 4
|
||||
0B JMP lit# ; jump ... IP <- lit#
|
||||
0C JMP addr ; jump ... IP <- addr
|
||||
0D CCF ———— ; clear Carry Flag ... CF = 0
|
||||
0E CHP ———— ; carry hop ... skip next instruction if Carry Flag is set ... when true: IP <- PC + 2
|
||||
0D FTG lit# ; toggle flag by number (see details below)
|
||||
0E FHP lit# ; flag hop ... skip next instruction if flag is set ... when true: IP <- PC + 4
|
||||
0F NOP ———— ; no operation
|
||||
|
||||
- Instructions are two bytes long:
|
||||
|
|
@ -52,7 +52,13 @@ With verbose debugging output:
|
|||
|
||||
- `A` - accumulator
|
||||
- `IP` - instruction pointer (aka program counter)
|
||||
- `CF` - carry flag
|
||||
- `FLAGS` - flags: **N**egative, **Z**ero, **O**verflow, **C**arry
|
||||
- in machine language, each flag is given a number:
|
||||
- N = 3
|
||||
Z = 2
|
||||
O = 1
|
||||
C = 0
|
||||
- (bitwise, `0000 = NZOC`)
|
||||
|
||||
## Memory map / Peripherals
|
||||
|
||||
|
|
@ -71,8 +77,8 @@ With verbose debugging output:
|
|||
ADD $FF ; this is direct addressing
|
||||
ADD ($CC) ; this is indirect addressing
|
||||
|
||||
END ; END, CFC, and CHP don't require operands
|
||||
; (a default value of 0 will be used as their operand)
|
||||
END ; END and NOP don't require operands
|
||||
; (the assembler will fill in a default value of 0)
|
||||
|
||||
@subroutine ; create a label
|
||||
ADD $01 ; (it must be on the line before the code it names)
|
||||
|
|
|
|||
86
simulator.js
86
simulator.js
|
|
@ -1,26 +1,41 @@
|
|||
const { INITIAL_IP_ADDRESS, CYCLE_LIMIT } = require('./machine.config');
|
||||
const { num2hex } = require('./logging.js');
|
||||
const { num2hex, num2bin_4bit } = require('./logging.js');
|
||||
const display = require('./display.js');
|
||||
|
||||
// TODO:
|
||||
// implement setting and clearing of:
|
||||
// - [ ] overflow flag
|
||||
// - [ ] zero flag
|
||||
// - [ ] negative flag
|
||||
|
||||
// STATE
|
||||
const CPU = {
|
||||
running: false,
|
||||
IP: INITIAL_IP_ADDRESS,
|
||||
CF: 0,
|
||||
FLAGS: 0, // A bit field! 0000 = NZOC
|
||||
Acc: 0,
|
||||
memory: null,
|
||||
|
||||
currentInstruction: {
|
||||
opcode: null,
|
||||
operand: null,
|
||||
mnemonic: null,
|
||||
},
|
||||
|
||||
/** @param {Uint8Array} data */
|
||||
loadMemory: (data) => {
|
||||
if (data.length > 256) { throw new Error("Out of memory error (program too long)"); }
|
||||
CPU.memory = data;
|
||||
},
|
||||
|
||||
currentInstruction: {
|
||||
opcode: null,
|
||||
operand: null,
|
||||
mnemonic: null,
|
||||
}
|
||||
setFlagNegative: () => { CPU.FLAGS |= 8 },
|
||||
setFlagZero: () => { CPU.FLAGS |= 4 },
|
||||
setFlagOverflow: () => { CPU.FLAGS |= 2 },
|
||||
setFlagCarry: () => { CPU.FLAGS |= 1 },
|
||||
unsetFlagNegative: () => { CPU.FLAGS &= ~8 },
|
||||
unsetFlagZero: () => { CPU.FLAGS &= ~4 },
|
||||
unsetFlagOverflow: () => { CPU.FLAGS &= ~2 },
|
||||
unsetFlagCarry: () => { CPU.FLAGS &= ~1 },
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -51,7 +66,7 @@ const Instructions = {
|
|||
},
|
||||
|
||||
load_addr: (addr) => {
|
||||
CPU.currentInstruction.mnemonic = `LDA addr \n mem at addr: ${num2hex(CPU.memory[addr])}`;
|
||||
CPU.currentInstruction.mnemonic = `LDA addr; @ addr: ${num2hex(CPU.memory[addr])}`;
|
||||
CPU.Acc = CPU.memory[addr];
|
||||
CPU.IP = CPU.IP += 2;
|
||||
},
|
||||
|
|
@ -59,11 +74,12 @@ const Instructions = {
|
|||
add_lit: (lit) => {
|
||||
CPU.currentInstruction.mnemonic = 'ADD lit';
|
||||
let sum = CPU.Acc + lit;
|
||||
// TODO: implement NZO flags
|
||||
if (sum > 255) {
|
||||
CPU.CF = 1;
|
||||
CPU.setFlagCarry();
|
||||
CPU.Acc = (sum % 255) - 1;
|
||||
} else {
|
||||
CPU.CF = 0;
|
||||
CPU.unsetFlagCarry();
|
||||
CPU.Acc = sum;
|
||||
}
|
||||
CPU.IP = CPU.IP += 2;
|
||||
|
|
@ -72,11 +88,12 @@ const Instructions = {
|
|||
add_addr: (addr) => {
|
||||
CPU.currentInstruction.mnemonic = 'ADD addr';
|
||||
let sum = CPU.Acc + CPU.memory[addr];
|
||||
// TODO: implement NZO flags
|
||||
if (sum > 255) {
|
||||
CPU.CF = 1;
|
||||
CPU.setFlagCarry();
|
||||
CPU.Acc = (sum % 255) - 1;
|
||||
} else {
|
||||
CPU.CF = 0;
|
||||
CPU.unsetFlagCarry();
|
||||
CPU.Acc = sum;
|
||||
}
|
||||
CPU.IP = CPU.IP += 2;
|
||||
|
|
@ -85,11 +102,12 @@ const Instructions = {
|
|||
sub_lit: (lit) => {
|
||||
CPU.currentInstruction.mnemonic = 'SUB lit';
|
||||
let sum = CPU.Acc - lit;
|
||||
// TODO: implement NZO flags
|
||||
if (sum < 0) {
|
||||
CPU.CF = 1;
|
||||
CPU.setFlagCarry();
|
||||
CPU.Acc = 255 + sum + 1;
|
||||
} else {
|
||||
CPU.CF = 0;
|
||||
CPU.unsetFlagCarry();
|
||||
CPU.Acc = sum;
|
||||
}
|
||||
CPU.IP = CPU.IP += 2;
|
||||
|
|
@ -98,18 +116,19 @@ const Instructions = {
|
|||
sub_addr: (addr) => {
|
||||
CPU.currentInstruction.mnemonic = 'SUB addr';
|
||||
let sum = CPU.Acc - CPU.memory[addr];
|
||||
// TODO: implement NZO flags
|
||||
if (sum < 0) {
|
||||
CPU.CF = 1;
|
||||
CPU.setFlagCarry();
|
||||
CPU.Acc = 255 + sum + 1;
|
||||
} else {
|
||||
CPU.CF = 0;
|
||||
CPU.unsetFlagCarry();
|
||||
CPU.Acc = sum;
|
||||
}
|
||||
CPU.IP = CPU.IP += 2;
|
||||
},
|
||||
|
||||
hop_lit: (lit) => {
|
||||
CPU.currentInstruction.mnemonic = `HOP lit \n ↳ Memory at IP+2 and +3: ${CPU.memory[CPU.IP+2]}, ${CPU.memory[CPU.IP+3]}`;
|
||||
CPU.currentInstruction.mnemonic = `HOP lit; IP+2: ${CPU.memory[CPU.IP+2]}, IP+3: ${CPU.memory[CPU.IP+3]}`;
|
||||
if (CPU.Acc === lit) {
|
||||
CPU.IP += 4;
|
||||
} else {
|
||||
|
|
@ -136,15 +155,30 @@ const Instructions = {
|
|||
CPU.IP = CPU.memory[addr];
|
||||
},
|
||||
|
||||
carry_clear: () => {
|
||||
CPU.currentInstruction.mnemonic = 'CFC';
|
||||
CPU.CF = 0;
|
||||
flag_toggle: (flagNum) => {
|
||||
CPU.currentInstruction.mnemonic = 'FTG';
|
||||
let mask = null;
|
||||
if (flagNum === 0) { mask = 1; }
|
||||
if (flagNum === 1) { mask = 2; }
|
||||
if (flagNum === 2) { mask = 4; }
|
||||
if (flagNum === 3) { mask = 8; }
|
||||
if (mask === null) { throw new Error('Invalid flag number'); }
|
||||
console.log('flag mask:', mask);
|
||||
CPU.FLAGS = CPU.FLAGS ^= mask;
|
||||
CPU.IP += 2;
|
||||
},
|
||||
|
||||
carry_hop: () => {
|
||||
CPU.currentInstruction.mnemonic = `CHP \n ↳ Memory at IP+2 and +3: ${CPU.memory[CPU.IP+2]}, ${CPU.memory[CPU.IP+3]}`;
|
||||
if (CPU.CF != 0) {
|
||||
flag_hop: (flagNum) => {
|
||||
CPU.currentInstruction.mnemonic = `FHP; IP+2: ${CPU.memory[CPU.IP+2]}, IP+3: ${CPU.memory[CPU.IP+3]}`;
|
||||
console.log('flag number:', flagNum);
|
||||
let mask = null;
|
||||
if (flagNum === 0) { mask = 1; }
|
||||
if (flagNum === 1) { mask = 2; }
|
||||
if (flagNum === 2) { mask = 4; }
|
||||
if (flagNum === 3) { mask = 8; }
|
||||
if (mask === null) { throw new Error('Invalid flag number'); }
|
||||
console.log('flag mask:', mask);
|
||||
if (CPU.FLAGS & mask) {
|
||||
CPU.IP += 4;
|
||||
} else {
|
||||
CPU.IP += 2;
|
||||
|
|
@ -171,8 +205,8 @@ const opcodes2mnemonics = {
|
|||
10: (operand) => Instructions.hop_addr(operand),
|
||||
11: (operand) => Instructions.jump_lit(operand),
|
||||
12: (operand) => Instructions.jump_addr(operand),
|
||||
13: (operand) => Instructions.carry_clear(),
|
||||
14: (operand) => Instructions.carry_hop(),
|
||||
13: (operand) => Instructions.flag_toggle(operand),
|
||||
14: (operand) => Instructions.flag_hop(operand),
|
||||
15: (operand) => Instructions.no_op(),
|
||||
};
|
||||
|
||||
|
|
@ -230,7 +264,7 @@ async 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)} CF: ${CPU.CF} ${CPU.running ? "running" : "halted" }`);
|
||||
console.log(`IP: $${num2hex(CPU.IP)} Acc: $${num2hex(CPU.Acc)} FLAGS: ${num2bin_4bit(CPU.FLAGS)} ${CPU.running ? "running" : "halted" }`);
|
||||
console.log();
|
||||
// Pause to show animated display:
|
||||
if (!debug) await new Promise(resolve => setTimeout(resolve, 75));
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
;; test CHP instruction
|
||||
|
||||
ADD 8 ; Acc = 8
|
||||
CHP 0 ; shouldn't hop (CF = 0)
|
||||
ADD 1 ; Acc = 9
|
||||
ADD 8 ; Acc = 1 and CF = 1
|
||||
CHP 0 ; hop! (CF = 1)
|
||||
END
|
||||
SUB 1 ; Acc = 0
|
||||
CFC 0 ; CF = 0
|
||||
END
|
||||
|
|
@ -17,7 +17,7 @@ ADD $01
|
|||
STO $21
|
||||
|
||||
; if CF is set, then the display is full and we're done
|
||||
CHP
|
||||
FHP 0
|
||||
JMP @copy-to-display
|
||||
|
||||
END
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
;; test FHP and FTG for carry
|
||||
ADD $FF ; Acc = $FF
|
||||
FHP 0 ; shouldn't hop (CF = 0)
|
||||
ADD 2 ; Acc = $01 and CF = 1
|
||||
FHP 0 ; hop! (CF = 1)
|
||||
END
|
||||
SUB 1 ; Acc = $00, CF = 0
|
||||
SUB 1 ; Acc = $FF, CF = 1
|
||||
FTG 0 ; CF = 0
|
||||
END
|
||||
Loading…
Reference in New Issue