SIC-1 Manual
The SIC-1 (Super Inefficient Computer 1) is the first model in the very short line of computers without a CPU chip, instead built out of stock transistors and custom PCBs. The reason it’s so inefficient is because of the large scale and longer time intervals between transistors requiring a lower clock speed of around ___ hz.
Memory and Registers
The SIC comes with two 8KiB RAM chips (see exact spec here), they can each store 1 byte at a time, so to store an instruction they are stored in a more "RAID" fashion with one byte going in each chip. The system allows for a maximum of 128k of RAM due to the 16 bit pointers, but only has 8k and so currently pointers are only 14 bits long (000xxxxxxxxxxxxx).
Memory is addressed in one chip at 000xxxxxxxxxxxxx and the other byte is stored at that location on the other chip, but since memory is 16 bit, this byte can not be addressed on it's own.
The system comes equipped with 16 16 bit registers capable of registering a value from the CPU. Each resister can be used for one purpose, the registers (0-F) go as follows:
Reg # │ Meaning │ shortened
──────┼──────────────────────────┼───────────
0 │ All zeros │ zeros
1 │ All ones │ ones
2 │ Carry In │ CI
3 │ Z flag │ Z
4 │ N flag │ N
5 │ O flag │ O
6 │ E flag │ E
7 │ reserved │ -
8 │ Instruction pointer │ IP
9 │ Body Code pointer │ BP
A │ General purpose register │ #A
B │ General purpose register │ #B
C │ General purpose register │ #C
D │ General purpose register │ #D
E │ General purpose register │ #E
F │ General purpose register │ #F
Only the general purpose registers can be used to store your values for your programs, however, you can still read from 0-5 and 8-9. there is one more register, but it can't be indexed. It's "Load immediate" (IMM) which stores a loaded value before passing it onto a computation or register
Operations
There are 16 possible opcodes (operation codes) for operations, however, only 6 are currently used; they go as follows:
opcode │ mnemonic | name | format |
───────┼──────────┼──────────────────┼───────────┼
0 │ nand │ bitwize nand │ 3-operand │
1 │ nandl │ logical nand │ 3-operand │
2 │ - │ - │ - │
3 │ add │ add │ 3-operand │
4 │ - │ - │ - │
5 │ - │ - │ - │
6 │ - │ - │ - │
7 │ - │ - │ - │
8 │ - │ - │ - │
9 │ skip │ skip next │ 1-operand │
A │ jump │ - │ 2-operand │
B │ storep │ store by pointer │ 2-operand │
C │ loadp │ load by pointer │ 2-operand │
D │ store │ store │ 2-operand │
E │ load │ load │ 2-operand │
F │ halt │ halt │ 0-operand │
The full extent of an operation takes up 16 bits:
BIT │0│1│2│3│4│5│6│7│8│9│A│B│C│D│E│F│
──────────┼───────┼───────┼───────┼───────┤
3-OPERAND │OPCODE │ R │ S │ T │
──────────┼───────┼───────┴───────┼───────┤
2-OPERAND │OPCODE │RESERVE│ S │ T │
──────────┼───────┼───────┴───────┼───────┤
2-OPERAND │OPCODE │ M │ T │
──────────┼───────┼───────────────┼───────┤
1-OPERAND │OPCODE │ RESERVED │ T │
──────────┼───────┼───────────────┴───────┤
0-OPERAND │OPCODE │ RESRVED │
──────────┴───────┴───────────────────────┘
For 3-Operand #R and #S are passed through a calculation and stored to #T
2-Operand deal with a memory location $M and a register #T moving values one way or the other. Or it takes in two registers, #S and #T. Note that the memory address can only address 256 bytes of memory so addressing via pointer is the best option.
1-Operand use a register (#T) for a purpose.
0-Operand preform an operation that doesn’t require any pointers.
Here’s an example operation that adds the value stored at register #A to #B and stores it in register #C:
0100 1010 1011 1100
Assembly Code
Assembly code for the SIC is written like this:
$4 add #A + #B → #C
The "$" at the start means the operation is stored at that location in memory, "#" refers to a register. You can also show what’s in a register:
#1 111111111111111 FFFF
#A 000000001101001 105
"$" can also be used in an operation.
$0 load $F3 → #C
Here’s an example program:
$0 load $16 → #A // Load memory $16 into register #A
$1 load $17 → #B // Load memory $17 into register #B
$2 add #A + #B → #C // Add #A and #B and load it into #C
$3 store #C → $18 // Store register #C into memory $18
$4 halt // Halt the program
You can also use "p" to signify that the following register holds a pointer:
$0 0000000001100101 101 // Points here
$1 0000000000000000 0 // Pointer
$2 load $1 → #A // Load memory $1 into register #A
$3 loadp pA → #B // Load the the memory address it pointer #A and load it into #B
$4 halt // Halt the program
Getting More Operations
To get the most out of the 6 operations you have to work with, for example, you can emulate logical operations using just nand. Input 1 is stored in #A, input 2 is in #B and the result is stored in #F:
// Or
$3 nandl #A !& #A → #C
$4 nandl #B !& #B → #D
$5 nandl #C !& #D → #F
// And
$3 nandl #A !& #B → #C
$4 nandl #C !& #C → #F
// Not
$3 nandl #A !& #A → #F
// Xor
$3 nandl #A !& #B → #C
$4 nandl #A !& #C → #D
$5 nandl #B !& #C → #E
$6 nandl #D !& #E → #F
// Nor
$3 nandl #A !& #A → #C
$4 nandl #B !& #B → #D
$5 nandl #C !& #D → #E
$6 nandl #E !& #E → #F
// Nand
$3 nandl #A !& #B → #F
// Xnor
$3 nandl #A !& #B → #C
$4 nandl #A !& #C → #D
$5 nandl #B !& #C → #E
$6 nandl #D !& #E → #C
$7 nandl #C !& #C → #F
If you’re writing a program that uses logical operations you can borrow these for your projects.
You can also emulate moving a value from one register to another using store and load.
$0 store #A → $FF
$2 load $FF → #B
Jump
In the skip operation if the lowest bit of T is 1, next instruction executed is IP + 2. Otherwise, next instruction executed is IP + 1. An example skip looks like this:
$0 load $10 → #A
$2 load $11 → #B
$4 load $12 → #F
$8 add #A + #B → #C
$A skip #F // If the last bit of #F is 1, skip
// the next line.
$C store #C → $18
$E halt
#F 0000001 1
The jump operation jumps to a pointer to a memory location and a boolean value. Examlple:
$0 load $23 → #D // Load pointer
$2 load $20 → #A
$4 load $21 → #B
$6 load $22 → #F
$A add #A + #B → #C
$C store #C → pE
$E add #E + #F → #E // Add one to E
$E add #A + #F → #A // Add one to A
$10 add #A + #F → #A // Add one to A
$12 jump #1 pD // If the last bit of #1 is 1, jump to the line pointed to at pD
$14 halt
.
.
.
$20 0000000000000000 0 // A
$21 0000000000010110 22 // B
$22 0000000000000001 1 // 1
$23 0000000000001010 10 // pointer
$24 0000000000010111 23 // saves here
#0 0000000000000000 0
#1 1111111111111111 FFFF
#2 0000000000000000 0
#3 0000000000000000 0
#4 0000000000000000 0
#5 0000000000000000 0
#6 0000000000000000 0
#7 0000000000000000 0
#8 0000000000001010 A
#9 0000000000000000 0
#A 0000000000000001 1
#B 0000000000010110 20
#C 0000000000010111 21
#D 0000000000001010 A
#E 0000000000000000 0
#F 0000000000000001 1
Conditional Jumps
Conditional jumps are pretty much the same as unconditional jumps, even using the same operation. You just need a value at a register, for unconditional jumps, use #1 (all ones) so the last bit is always a 1. Example:
$0 load $20 → #A // Load pointer
$2 load $21 → #B // Load starting value (set #B to 0)
$8 load $22 → #F
$A load $20 → #A // Load pointer
$C add #B + #F → #B // Increment By 1
$E nandl #B !& #B → #C // Apply logical operation (in this case, NOT)
$10 jump #C pA // If the last bit of #C is 1, jump to the line pointed to at pD
$12 halt
.
.
.
$20 0000000000000000 0 // pointer
$21 0000000000000000 0 // starting value
$22 0000000000000001 1
Operating System
Script (uninished):
// System Pointers
$0 0010000000000000 2000 // Key Pressed Pointer
$1 0001111111111111 1FFF // Cusror X Pointer
$2 0000111111111101 0FFE // Cusror Y Pointer
$3 0000111111111101 0FFD // Pointer to start of Screen Memory
$4 0000000000000001 1 // 1
$5 0001000000000101 1005
$6 0000000000000000 0
$7 0000000000000000 0
$8 0000000000000000 0
$9 0000000000000000 0
$A 0000000000000000 0
$B 0000000000000000 0
$C 0000000000000000 0
$D 0000000000000000 0
$E 0000000000000000 0
$F 0000000000000000 0
// Your Pointers go Here
$10 0000000000000000 0
$11 0000000000000000 0
$12 0000000000000000 0
. . .
. . .
. . .
$FC 0000000000000000 0
$FD 0000000000000000 0
$FF 0000000000000000 0
// Your Code goes Here
$0100 0000000000000000 0
$0101 0000000000000000 0
$0102 0000000000000000 0
$0103 0000000000000000 0
$0104 0000000000000000 0
. . .
. . .
. . .
$0F0B 0000000000000000 0
$0F0C 0000000000000000 0
$0F0D 0000000000000000 0
$0F0E 0000000000000000 0
$0F0F 0000000000000000 0
// OS
$1000 load $0 → #A // Load pointer
$1001 loadp pA → #A // Load Key Pressed From Pointer
$1002 load $3 → #D // Load pointer for Screen Memory
$1003 store #0 → $F
$1004 load $F → #F // Move zeros to register F.
$1005 add #F + #A → #E // This is to find the E flag
$1006 store #6 → $F
$1007 load $F → #E // Copy the E flag to register E.
$1008 nandl #E !∧#E → #E // Take the not
$1009 load $5 → #B // Load jump pointer
$100A jump #E // if(keyPressed == counter){}
Character Codes
These codes are used for the display.
0000000 0 0000001 1 0000010 2 0000011 3 0000100 4
0000101 5 0000110 6 0000111 7 0001000 8 0001001 9
0001010 A 0001011 B 0001100 C 0001101 D 0001110 E
0001111 F 0010000 G 0010001 H 0010010 I 0010011 J
0010100 K 0010101 L 0010110 M 0011000 O 0011001 P
0011010 Q 0011011 R 0011100 S 0011101 T 0011110 U
0011111 V 0100000 W 0100001 X 0100010 Y 0100011 Z
0100100 SP 0100101 # 0100110 $ 0100111 → 0101000 +
0101001 ! 0101010 & 0101011 | 0101100 | 0101101 _
0101110 - 0101111 ̅ 0110000 CR 0110001 DEL 0110010 RUN
0110011 UA 0110010 DA 0110010 LA 0110010 RA