cardiograph-computer/javascript-old
n loewen ceb6019a1c Merge branch 'rearchitect' 2025-04-17 10:29:20 +01:00
..
test-programs Catch git up to current work eek 2025-04-17 10:21:56 +01:00
assembler.js Catch git up to current work eek 2025-04-17 10:21:56 +01:00
cardiograph.js Catch git up to current work eek 2025-04-17 10:21:56 +01:00
conversions.js Catch git up to current work eek 2025-04-17 10:21:56 +01:00
cpu.js Catch git up to current work eek 2025-04-17 10:21:56 +01:00
dbg.js Catch git up to current work eek 2025-04-17 10:21:56 +01:00
io.js Catch git up to current work eek 2025-04-17 10:21:56 +01:00
jsconfig.json Catch git up to current work eek 2025-04-17 10:21:56 +01:00
logging.js Catch git up to current work eek 2025-04-17 10:21:56 +01:00
machine.config.js Catch git up to current work eek 2025-04-17 10:21:56 +01:00
package-lock.json Catch git up to current work eek 2025-04-17 10:21:56 +01:00
package.json Catch git up to current work eek 2025-04-17 10:21:56 +01:00
readme.md Merge branch 'rearchitect' 2025-04-17 10:29:20 +01:00

readme.md

Cardiograph Mark I — simulator for an imaginary computer

Cardiograph is an imaginary computer. It has three main components:

  1. the CPU, Card (short for 'Completely Analogue Risc Machine')
  2. an input-output processor, IO
  3. a display, Graph

Simulator

Dependencies

Cardiograph is an imaginary computer. It has three main components:

  1. the CPU, Card (short for 'Completely Analogue Risc Machine')
  2. an input-output processor, IO
  3. 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 annotate flag 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:

  1. A, an 8-bit accumulator
  2. IP, an 8-bit instruction pointer (aka program counter)
  3. 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 $ (or 0x)
  • 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   =