Jay McCarthy
The mic1 package provides tools for working with the MIC-1 processor architecture that appears in Andrew S. Tanenbaum’s textbook Structured Computer Organization.
The MIC-1 is a CPU with 16 general purpose 16-bit registers. Registers 5, 6, 7, 8, and 9 have default values 0000000000000000, 0000000000000001, 1111111111111111, 0000111111111111, and 0000000011111111 respectively.
It runs a single 256-instruction microprogram embedded in a control store ROM. Its ALU supports addition, bitwise AND, and bitwise negation. The ALU outputs flags for whether its result was negative or zero. The ALU is connected to a 1-bit shifter that can shift left, right, or not at all.
Its memory interface is two flags (one for reading, one for writing) as well as two 16-bit registers for interfacing with memory (the MAR–Memory Address Register–and MBR–Memory Buffer Register.) The top 4 bits of the MAR is ignored, so the MIC-1 has a 12-bit address space. Memory access is delayed by one cycle, during which the appropriate flag must be asserted. If both flags are asserted, then the external controller halts the machine.
The ALU’s A side is either a register or the MBR. The shifter result may be output to the MBR or any register. The MAR may be written from the ALU’s B side.
The top four words of memory (4092-4095) are wired to a UART. The first two connect to the receiver and the second two connect to the transmitter. The first of each holds an 8-bit character to be outputed in the bottom 8 bits. The second of each holds a 4 bit control flag in its lowest bits. The control bits are (from most to least significant): On, Interrupt, Done, Busy. The control bits are initialized to all zero. If the microprogram sets the On bit, then the component is enabled and stabilizes. The receiver stabilizes to not Done and Busy, while the transmitter stabilizes to Done and not Busy. When the receiver receives a character, it switches to Done and not Busy until the character is read by the CPU. When the program writes a character to the transmit buffer while the transmitter is On, then the transmitter switches to not Done and Busy, until the transmission is finished. The Interrupt flag is currently ignored by both components.
raco mic1 ‹option› ... ‹microcode-path›‹memory-image-path› simulates the execution of the MIC-1.
‹microcode-path› must be a path to a file. If the extension is .prom, then it must be in the Microcode Image format. If the extension is .mc, then it must be in the MAL microcode language format and it will be compiled before loading.
‹memory-image-path› must be a path to a file. If the extension is .o, then it must be in the Memory Image format. If the extension is .s, then it must be in the MAC-1 macro-assembly format and it will be compiled before loading.
It accepts the following ‹option›s:
--ll — simulates at the NAND gate level via compilation to a C program using cc.
--lli — simulates at the NAND gate level via an interpreter.
--hl — simulates at a high-level (default)
--pc ‹pc-str› — specifies the initial value of the register 0, the Program Counter (default: 0)
--sp ‹sp-str› — specifies the initial value of the register 2, the Stack Pointer (default: 1024)
A microcode image matches the grammar ‹PROM›.
‹PROM›
::=
‹Line› ...
‹Line›
::=
‹Entry›\n
‹Entry›
::=
‹MIR›
|
# any sequence of character except \n
‹MIR›
::=
‹AMUX›‹COND›‹ALU›‹SH›‹MBR›‹MAR›‹RD›‹WR›‹ENC›‹C›‹B›‹A›‹ADDR›
‹AMUX›
::=
0 — ALU A side holds A register
|
1 — ALU A side holds MBR
‹COND›
::=
00 — Never jump
|
01 — Jump on negative ALU output
|
10 — Jump on zero ALU output
|
11 — Always jump
‹ALU›
::=
00 — A + B
|
01 — A & B
|
10 — A
|
11 — ! A
‹SH›
::=
00 — No shift
|
01 — Right shift
|
10 — Left shift
‹MBR›
::=
0 — Leave MBR unchanged
|
1 — Write shifter output to MBR
‹MAR›
::=
0 — Leave MAR unchanged
|
1 — Write ALU B side to MAR
‹RD›
::=
0 — Do not enable memory read
|
1 — Read from memory
‹WR›
::=
0 — Do not enable memory write
|
1 — Write to memory
‹ENC›
::=
0 — Do not save shifter output
|
1 — Write shifter output to ‹C› register
‹C›
::=
4-bit register label
‹B›
::=
4-bit register label
‹A›
::=
4-bit register label
‹ADDR›
::=
8-bit microaddress
In addition, a microcode image may only contain up to 256 ‹mir› lines.
A memory image matches the grammar ‹Image›.
‹Image›
::=
‹Line› ...
‹Line›
::=
‹Entry›\n
‹Entry›
::=
‹Value›
|
# any sequence of character except \n
‹Value›
::=
16-bit value written using the characters 0 and 1
In addition, a memory image may only contain up to 4096 ‹value› lines.
raco mcc ‹microcode-path› compiles MAL microcode language into the Microcode Image format.
‹microcode-path› must be a path to a file in the MAL microcode language format. raco mcc replaces the extension of this path with .prom and writes the corresponding Microcode Image.
While it is possible to directly write in the Microcode Image format, it is extremely error-prone and tedious. MAL provides a convenient way to write microprograms.
MAL supports block comments in between { and }. Labels are sequences of any characters except (,:;).
A MAL program matches the following grammar ‹Program›:
‹Program›
::=
|
\n ‹Program›
|
‹Instruction› \n ‹Program›
‹Instruction›
::=
|
‹Component› ; ‹Instruction›
‹Instruction›s are composed of multiple ‹Component›s. Each ‹Component› determines some fields of the Microcode Image. If two ‹Component›s assign the same field differently, then a compilation error is raised. The following grammar specifies the various ‹Component›s:
‹Component›
::=
mar := ‹BExpr›— Writes the ALU B side to MAR
|
‹Register› := ‹ShExpr›— Writes the shifter output to given register
|
mbr := ‹ShExpr›— Writes the shifter output to MBR
|
alu := ‹AluExpr›— Sets the ALU output
|
if ‹Cond› then goto ‹Label›— Sets the COND flag and the ADDR value
|
goto ‹Label›— Sets the COND flag to 11 and the ADDR value
|
rd— Sets the RD flag
|
wr— Sets the WR flag
The remaining nonterminals are specified by the following grammar:
‹Cond›
::=
n — Jump on negative ALU output
|
z — Jump on zero ALU output
‹ShExpr›
::=
‹AluExpr› — Do not shift
|
lshift( ‹AluExpr› ) — Left shift
|
rshift( ‹AluExpr› ) — Right shift
‹AluExpr›
::=
‹AExpr› + ‹BExpr› — Addition
|
band( ‹AExpr› , ‹BExpr› ) — Bitwise And
|
‹AExpr› — Identity
|
inv( ‹AExpr› ) — Bitwise Negation
‹AExpr›
::=
‹Register›
|
mbr
‹BExpr›
::=
‹Register›
‹Register›
::=
pc
|
ac
|
sp
|
sp
|
ir
|
tir
|
0
|
1
|
(-1)
|
amask
|
smask
|
a
|
b
|
c
|
d
|
e
|
f
If a MAL program produces an image greater than 256 instructions, then no error is raised during compilation.
For examples see the Github repository, specifically: fib.mc implements Fibonacci and macro-v1.mc implements an interpreter for compiled MAC-1 macro-assembly.
raco masm ‹asm-path› compiles MAC-1 macro-assembly into the Memory Image format.
‹asm-path› must be a path to a file in the MAC-1 macro-assembly format. raco masm replaces the extension of this path with .o and writes the corresponding Memory Image.
The MAC-1 is a low-level virtual machine implemented by a MIC-1 microprogram. It exposes a single register (AC) to programmers and has an internal state defined by two other registers (PC and SP).
The assembly language supports line comments starting with the ; character. Whitespace is never significant. Literal integers are supported in decimal format. Literal strings compile to packed 16-bit words with early characters in least significant bits.
Labels are any alphanumeric character sequence starting with an alphabetic character and ending in :. A label definition is a label not in an argument position or immediately after a label definition.
The character sequence .LOC followed by a literal nonnegative integer skips the given amount of space in the resulting image, filling it with 1111111111111111.
The following instructions are recognized:
Mnemonic
Encoding
Instruction
Semantics
LODD ‹Arg›
0000xxxxxxxxxxxx
Load Direct
AC := Mem[X]
STOD ‹Arg›
0001xxxxxxxxxxxx
Store Direct
Mem[X] := AC
ADDD ‹Arg›
0010xxxxxxxxxxxx
Add Direct
AC := AC + Mem[X]
SUBD ‹Arg›
0011xxxxxxxxxxxx
Subtract Direct
AC := AC - Mem[X]
JPOS ‹Arg›
0100xxxxxxxxxxxx
Jump on non-negative
If AC ≥ 0, PC := X
JZER ‹Arg›
0101xxxxxxxxxxxx
Jump on zero
If AC = 0, PC := X
JUMP ‹Arg›
0110xxxxxxxxxxxx
Jump
PC := X
LOCO ‹Arg›
0111xxxxxxxxxxxx
Load Constant
AC := X
LODL ‹Arg›
1000xxxxxxxxxxxx
Load Local
AC := Mem[SP + X]
STOL ‹Arg›
1001xxxxxxxxxxxx
Store Local
Mem[SP + X] := AC
ADDL ‹Arg›
1010xxxxxxxxxxxx
Add Local
AC := AC + Mem[SP + X]
SUBL ‹Arg›
1011xxxxxxxxxxxx
Subtract Local
AC := AC - Mem[SP + X]
JNEG ‹Arg›
1100xxxxxxxxxxxx
Jump on negative
If AC < 0, PC := X
JNZE ‹Arg›
1101xxxxxxxxxxxx
Jump unless zero
If AC ≠ 0, PC := X
CALL ‹Arg›
1110xxxxxxxxxxxx
Call
SP := SP - 1; Mem[SP] := PC; PC := X
PSHI
1111000000000000
Push Indirect
SP := SP - 1; Mem[SP] := Mem[AC]
POPI
1111001000000000
Pop Indirect
Mem[AC] := Mem[SP]; SP := SP + 1
PUSH
1111010000000000
Push
SP := SP - 1; Mem[SP] := AC
POP
1111011000000000
Pop
AC := Mem[SP]; SP := SP + 1
RETN
1111100000000000
Return
PC := Mem[SP]; SP := SP + 1
SWAP
1111101000000000
Swap AC & SP
AC :=: SP
INSP ‹Arg›
11111100yyyyyyyy
Increment SP
SP := SP + Y
DESP ‹Arg›
11111110yyyyyyyy
Decrement SP
SP := SP - Y
HALT
1111111100000000
Halt
Halt processor
If a MAC-1 program produces an image greater than 4096 instructions, then no error is raised during compilation.
For examples see the Github repository, specifically: fib.s implements Fibonacci and IO_str_and_echo.s implements an echo program.
The implementation contains a general purpose hardware description language that compiles circuits to networks of NAND gates. The network can be simulated in Racket or via C compilation. In the future it may be documented.