I haven’t started making the CPU yet, but I’ve written an assembler and emulator and made a git repo for it.
The file assembler.py
is the assembler, while emulator.py
is the emulator.
Here’s the two working together to run an assembly program:
martin@magni ~/cloud/dev/octornado-cpu main* $ cat test.s
mov r7 stack_start
st retaddr
mov r1 data
jmp strlen
retaddr:
halt
strlen:
mov r0 0
mov r2 r7
mov r7 r1
strlen_loop:
ld r1
cmp r1 0
jeq strlen_exit
add r0 r0 1
add r7 r7 1
jmp strlen_loop
strlen_exit:
mov r7 r2
ld r1
jmp r1
data:
byte 'H'
byte 'e'
byte 'l'
byte 'l'
byte 'o'
byte ' '
byte 'w'
byte 'o'
byte 'r'
byte 'l'
byte 'd'
byte '\0'
stack_start:
martin@magni ~/cloud/dev/octornado-cpu main* $ ./assembler.py test.s test.bin
martin@magni ~/cloud/dev/octornado-cpu main* $ ./emulator.py test.bin
Registers after execution:
[11, 8, 46, 0, 0, 0, 0, 46]
With my calling convention, register 0 is the return register, so strlen(data)
correctly returns 11. (Registers 1, 2 and 3 are arguments and caller-saved, register 7 is the RAM address register and used by the calling convention as a stack pointer.)