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