SICO Online Editor
The Single Instruction Computer
sico.py with demos can be downloaded here: sico.zip
SICO is a Single Instruction COmputer that mimics the functionality of a normal computer while using only one computing instruction. This is like going into a forest with no tools and trying to build a house. Since we only have one instruction, most modern conveniences are gone. Things like multiplying numbers or memory allocation need to be built from scratch using SICO's instruction.
The instruction is simple: Given A, B, and C, compute mem[A]-=mem[B]. Then, if mem[A] was less than or equal to mem[B], jump to C. Otherwise, jump by 3. We use the instruction pointer to keep track of our place in memory. The pseudocode below shows a SICO instruction:
The instruction pointer and memory values are all 64 bit unsigned integers. Interaction with the host environment is done by reading and writing from special memory addresses. For example: writing anything to -1 will end execution.
SICO Assembly Language
Whatever the instruction pointer is pointing to is what gets executed, so there's no need to write out instructions like mov, jmp, etc. We only need an assembly language to help write memory values.
A "Hello, World!" program in assembly:
The syntax of the assembly language
Component | Description |
Line Comment | # comment |
Block Comment | #| comment |# |
Label Declaration | label: |
Label Recall | label |
Sublabel | label: .sub: is treated as label.sub: |
Current Address | ? |
Number | 123 or 0xabc |
ASCII Literal | 'A 'B 'C evaluates to 65 66 67 |
Operator | + or -. Ex: 1+2-3 |
Input / Output | Read or write to addresses above 2^63 |
IO addresses (mod 2^64)
-1 | Writing ends execution |
-2 | Writing prints to stdout |
-3 | Read from stdin |
-4 | Read timing frequency |
-5 | Read system time |
-6 | Writing sleeps for mem[B]/freq seconds |
To print the letter 'A' to stdout:
Synthesized Instructions
Although printing text to the screen is easy, we will need to synthesize more complicated instructions to serve as building blocks when we make more complicated programs. In future articles, we will also show how to turn these synthesized instructions into easy to call functions from within SICO. For now, we are only focusing on basic instructions in order to show how the SICO architecture works.
One of the most common instructions: an unconditional jump to jmp. It also sets [tmp]=0.
We can abort a SICO program by writing any value to -1. We need to calculate -1 as "0-1" due to the syntax our assembly language.
Set [a]=[b]. The series of ?+1 expressions points to the next memory address after the instruction. They simply serve to force the instruction pointer to go to the next instruction regardless of whether or not the instruction would jump.
Jump to jmp if [a]=[b].
We can print the character "A" to the screen by writing it to the special address -2.
Increment [a].
Decrement [a].
Set [C]=[[A]+[B]]. This is the same as getting the value at an array index, as in C=arr[i] in other languages. This will form the backbone of functions in SICO.
Set [[A]+[B]]=[C]. This is the same as assigning a value to an array, as in arr[i]=C.
If we allow a SICO instruction to be atomic, we can actually create a spinlock. When the lock is first acquired, the value of [lock+1] is overwritten from z-1 to z-1-[z-1]=z and we jump to the critical section. When a second thread tries to acquire the lock, it will subtract [z]=0 from z, which will fail to jump, and the thread will be caught by the next instruction, z z lock. When the owning thread is done with the lock, it just needs to subtract [z+1]=1 from [lock] to allow the lock to be acquired by a new thread.
Notes
SICO belongs to the family of one instruction architectures, like the subleq architecture it's based off of.
Whereas subleq uses 2's complement signed arithmetic, SICO uses unsigned arithmetic. There are several reasons for this:
SICO offers 3 ways to guarantee a jump
1. Zeroing an address: | A A jmp |
2. Subtracting anything from 0: | Z A jmp |
3. Subtracting -1 from anything: | A Z-1 jmp |
Subleq can only guarantee a jump by zeroing an address. This gives SICO more options when deciding program flow, since these are all common operations.
Using the instruction A Z jmp, we can test A for equality with 0 using only one instruction and without modifying any variables. This a common value to test for. Subleq, would require 1 to 2 instructions and modifying A for this test.
SICO also uses a different order for operands. For operands A, B, and C, SICO takes [A]=[A]-[B] where subleq takes [B]=[B]-[A]. The order of operands in subleq is perfectly valid, of course, but goes against the left-to-right ordering used in programming. For example, we usually write a=b+c instead of b+c=a. In an early version of SICO, I used the subleq order of operands and found myself constantly having to reorder the operands in my head. Thus I decided swap the roles of A and B.
sico.zip contains sico.py, demos, and interpreters.