5.1 KiB
Cardiograph Mark I — simulator for an imaginary computer
Cardiograph is an imaginary computer. It has three main components:
- the CPU, Card (short for 'Completely Analogue Risc Machine')
- an input-output processor, IO
- a display, Graph
Simulator
Dependencies
- Node.js
Quick examples
Assemble and run:
./assembler.js -i <source.asm> | ./cardiograph.js
Assemble to a file:
./assembler.js -i <source.asm> -o <machinecode.out>
Run from a file:
./cardiograph.js -i <machinecode.out>
Assembler: assembler.js
Usage: ./assembler.js [-a] -i <input-file> [-o <output-file>]
-a, --annotate Output code with debugging annotations
-i, --in <file> Assembly-language input
-o, --out <file> Machine-code output
-
If an output file is not provided, the output is printed to stdout
-
If the
annotateflag is not set, the machine code is returned as a string of space-separated decimal numbers
Simulator: cardiograph.js
Usage: ./cardiograph.js [-i <file>]
-i, --in <file> Machine-code input
- If an input file is not provided, the input is read from stdin
CPU
Registers and Flags
There are three registers:
- A, an 8-bit accumulator
- IP, an 8-bit instruction pointer (aka program counter)
- flags, a 4-bit flag register
The four flags are Overflow, Negative, Zero, and Carry.
(Overflow is the high bit and carry is the low bit.)
In decimal:
| O | N | Z | C |
|---|---|---|---|
| 3 | 2 | 1 | 0 |
Instruction set
Operations
Hex Mnem. Operand Effect
00 END (ignored) Halt CPU
01 STO literal # mem[lit#] = A
02 STO address mem[mem[addr]] = A
03 LDA literal # A = lit#
04 LDA address A = addr
05 ADD literal # A = A + lit#
06 ADD address A = A + mem[addr]
07 SUB literal # A = A - lit#
08 SUB address A = A - mem[addr]
09 HOP literal # If A == lit#, skip next op (IP += 4)
0A HOP address If A == mem[addr], skip next instruction (IP += 4)
0B JMP literal # IP = lit#
0C JMP address IP = mem[addr]
0D FTG literal # Toggle flag, where flag number == lit#
0E FHP literal # Skip next op if flag is set, where flag number == lit#
0F NOP (ignored) None
- Instructions are two bytes long: one byte for the opcode, one for the operand
Effects on memory, flags, registers
op mem flags IP
END +2
NOP +2
STO w +2
LDA r NZ +2
ADD ONZC +2
SUB ONZC +2
HOP +2/+4
JMP arg
FTG ONZC +2
FHP ONZC +2/+4
STO r,w +2
LDA r,r NZ +2
ADD r ONZC +2
SUB r ONZC +2
HOP r +2/+4
JMP r arg
FTG r ONZC +2
FHP r ONZC +2/+4
Start-up
When starting up, the CPU executes a JMP $FF.
Put differently: it starts executing instructions at the address contained in $FF.
TODO: currently the simulator doesn't actually do this
Assembly language
ADD $01 ; comments follow a `;`
ADD $FF ; this is direct addressing
ADD ($CC) ; this is indirect addressing
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)
ADD $02
JMP @subroutine ; use a label as operand
; the label will be replaced with
; the address of the label
#foo $FF ; define a constant
; (must be defined before it is referenced)
ADD #foo ; use a constant as an operand
LDA * ; `*` is a special label referencing the memory address
; where the current line will be stored after assembly
- Prefix hexadecimal numbers with
$(or0x) - Prefix binary numbers with
0b - Whitespace is ignored
Cardiograph memory map
| Address | Used for... |
|---|---|
| 00 to 19 | display (5x5) |
| 1A | pointer to display memory |
| 1B | keypad: value of latest key pressed |
| 1C | reserved for future use (bank switching flag) |
| 1D | initial IP |
| 1D to FE | free |
| FF | * ROM (unwriteable) pointer to initial IP |
* Not implemented yet
Cardiograph peripherals
Keypad
The value of the latest keypress on a hex keypad is stored at $1B.
The keypad uses the same layout as the COSMAC VIP (and CHIP-8). The CPU simulator maps those keys onto a Qwerty set:
1 2 3 C = 1 2 3 4
4 5 6 D = Q W E R
7 8 9 E = A S D F
A 0 B F = Z X C V
The arrow keys are also mapped onto the hex keypad:
5 = ↑
7 8 9 = ← ↓ →