Feature: WIP: Replace CHP and CFC instructions with FHP and FTG (carry flag is implemented, other flags are not)

This commit is contained in:
n loewen 2023-08-15 12:04:48 +01:00
parent 2d38e28957
commit b1dc2a7c7e
8 changed files with 107 additions and 50 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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