ilo-vm/source/ilo.retro

181 lines
5.4 KiB
Text

This is an implementation of the ilo computer. It's written in
RetroForth. I wrote this mostly to satisfy a personal desire to
run Konilo under RetroForth.
The ilo computer is quite similar to the nga computer that Retro
runs on. Both are dual stack minimal instruction set computers
with similar instruction sets. But ilo is, by design, a smaller,
simpler system.
ilo presents the following:
- 65,5536 cells of memory
- 32-bit cells are the only addressable memory unit
- data stack of 32 values
- address stack of 256 addresses
- keyboard input
- serial display
- block storage
- blocks are 1,024 cells in size
- 30 instructions
# Memory And Loading The ROM
A standard ilo system will provide exactly 65,536 cells of RAM.
I create a label pointing to this and allocate the space.
~~~
'IMAGE d:create #65536 allot
~~~
On startup, ilo loads a ROM (typically named "ilo.rom") into
memory. This will always be a full memory image, so the size
will be 65,536 cells. Loading this takes advantage of Retro's
`block:` vocabulary, reading in the ROM as a series of 64 1K
blocks. Doing this is significantly faster than reading the
ROM in byte by byte and assembling the bytes into cells.
~~~
:load-image (s-)
block:set-file
#64 [ I IMAGE I #1024 * + block:read ] indexed-times ;
'ilo.rom load-image
~~~
# Stacks & Registers
I create labels and allocate space for the two stacks. And also
create registers for the stack pointers and instruction pointer.
Using these, I then implement several words for moving values
to and from these. The `>s` and `s>` operate on the data stack
whereas `>r` and `r>` operate on the address stack.
It'd be faster to just use the RetroForth stacks directly, but
this is cleaner and less error prone. It also makes debugging
easier as the ilo stacks are now separate entities.
The last thing I define here is `[IP]`, which returns the value
in memory at the instruction pointer. This is strictly for
readability and could be inlined in the two places it's used.
~~~
'DataStack d:create #33 allot
'ReturnStack d:create #257 allot
'SP var
'RP var
'IP var
:>s (n-) &DataStack @SP + store &SP v:inc ;
:s> (-n) &SP v:dec &DataStack @SP + fetch ;
:>r (n-) &ReturnStack @RP + store &RP v:inc ;
:r> (-n) &RP v:dec &ReturnStack @RP + fetch ;
:[IP] IMAGE @IP + fetch ;
~~~
# A Utility Word
RetroForth doesn't have a word to directly compare two blocks
of memory. Until I rectify this, I define one here.
~~~
:compare (sdl-f)
#-1 swap
[ [ dup-pair &fetch bi@ eq? ] dip and [ &n:inc bi@ ] dip ]
times &drop-pair dip ;
~~~
# The ilo Instructions
Now I'm ready to implement the ilo instruction set. I chose to
follow my (very) similar approach from retro-extend(1) and the
Autopsy debugger. This creates one word per instruction and then
fills in a jump table of pointers.
If you are familiar with Retro, it should be pretty easy to
follow these. Mostly just move values onto the RetroForth stack,
do an operation, then put results back.
The longest one of these is the I/O instruction, which has 8
possible actions. I implemented this using a `case` structure.
~~~
:i:no ;
:i:li &IP v:inc [IP] >s ;
:i:du s> dup >s >s ;
:i:dr s> drop ;
:i:sw s> s> swap >s >s ;
:i:pu s> >r ;
:i:po r> >s ;
:i:ju s> n:dec !IP ;
:i:ca @IP >r i:ju ;
:i:cc s> s> [ >s i:ca ] &drop choose ;
:i:cj s> s> [ >s i:ju ] &drop choose ;
:i:re r> !IP ;
:i:eq s> s> eq? >s ;
:i:ne s> s> -eq? >s ;
:i:lt s> s> swap lt? >s ;
:i:gt s> s> swap gt? >s ;
:i:fe s> IMAGE + fetch >s ;
:i:st s> s> swap IMAGE + store ;
:i:ad s> s> + >s ;
:i:su s> s> swap - >s ;
:i:mu s> s> * >s ;
:i:di s> s> swap /mod swap >s >s ;
:i:an s> s> and >s ;
:i:or s> s> or >s ;
:i:xo s> s> xor >s ;
:i:sl s> s> swap n:abs n:negate shift >s ;
:i:sr s> s> swap n:abs shift >s ;
:i:cp s> s> s> [ IMAGE + ] bi@ 'abc 'cba reorder compare >s ;
:i:cy s> s> s> [ IMAGE + ] bi@ 'abc 'cba reorder copy ;
:i:io s>
#0 [ s> c:put ] case
#1 [ c:get >s ] case
#2 [ s> s> swap IMAGE + block:read ] case
#3 [ s> s> swap IMAGE + block:write ] case
#4 [ dump-stack ] case
#5 [ #-1 !IP ] case
#6 [ #65536 !IP ] case
#7 [ @SP >s @RP >s ] case
drop ;
'Instructions d:create
&i:no , &i:li , &i:du , &i:dr , &i:sw , &i:pu ,
&i:po , &i:ju , &i:ca , &i:cc , &i:cj , &i:re ,
&i:eq , &i:ne , &i:lt , &i:gt , &i:fe , &i:st ,
&i:ad , &i:su , &i:mu , &i:di , &i:an , &i:or ,
&i:xo , &i:sl , &i:sr , &i:cp , &i:cy , &i:io ,
~~~
# Instruction Processor
~~~
{{
:mask #255 and ;
:next #8 shift ;
---reveal---
:unpack (n-dcba)
dup mask swap next
dup mask swap next
dup mask swap next
'abcd 'dcba reorder ;
}}
:process-opcodes (n-)
unpack
&Instructions + fetch call
&Instructions + fetch call
&Instructions + fetch call
&Instructions + fetch call ;
:process (-)
[ [IP] process-opcodes &IP v:inc @IP #0 #65535 n:between? ] while ;
'ilo.blocks block:set-file
process
~~~