Note 3: Mic1 Tools Manual
I SCO, kap. 4, afsnit 4.1, An Example Microarchitecture, defineres en simpel mikroprogrammerbar maskine Mic1. Et mikroprogram skrevet i mikroprogrammeringssproget MAL er vist i figur 4-17. Dette program implementerer IJVM på Mic1. Ønsker man at forsøge sig med at køre mikroprogrammer på Mic1 og se en detaljeret kørselsbeskrivelse, kan man bruge værktøjer beskrevet i følgende vejledning.
Overview
The Mic1 tools consist of an assembler for the Micro Assembly Language (MAL) specified in Structured Computer Organization (Tanenbaum, 2005), section 4.3.1 and an interpreter for the Mic1 microarchitechture specified in section 4.1.
The assembler, mic1-asm, translates a MAL program into a 512 word image for the Mic1 control store. The interpreter, mic1, loads this control store image and optionally an IJVM bytecode file and then simulates the Mic1 machine.
An Example
The Micro Assembly Language (MAL) as specified by Tanenbaum is in fact a general purpose programming language. As such, it is possible to express simple algorithms in MAL. Consider greatest common divisor:
# GCD on Mic1
H = SP = 1
H = SP = SP + H + 1
H = SP = SP + H + 1 # SP = 7;
LV = H + SP
LV = LV - 1 # LV = 13;
while: # while (SP != LV) {
H = LV
Z = SP - H; if (Z) goto while_end; else goto while_body
while_body: # if (SP < LV)
H = LV
N = SP - H; if (N) goto A; else goto B
A:
H = SP # LV = LV - SP;
LV = LV - H; goto if_end
B: # else
H = LV # SP = SP - LV;
SP = SP - H
if_end: # }
goto while
while_end:
goto while_end
Notice how the Mic1 registers SP and LV are used as ordinary variables in the program. The example also illustrates how it is cumbersome to specify constants in MAL; the values 7 and 13 are build by repeated shifting and adding.
Suppose the file gcd.mal contains this program. To translate gcd.mal into a Mic1 memory image stored in the file gcd.mic1 use the assembler mic1-asm as follows:
mic1-asm gcd.mal gcd.mic1
The control store image in gcd.mic1 is easily readable:
entry: 002
000: 0048148005 H = LV; goto 0x009;
001: 0058148005 H = LV; goto 0x00b;
002: 0018118400 SP = H = 1; goto 0x003;
003: 00203d8404 SP = H = H + SP + 1; goto 0x004;
004: 00283d8404 SP = H = H + SP + 1; goto 0x005;
005: 00303c0804 LV = H + SP; goto 0x006;
006: 0038370805 LV = LV - 1; goto 0x007;
007: 0040148005 H = LV; goto 0x008;
008: 00013f0004 Z = SP - H; if (Z) goto 0x100; else goto 0x000;
009: 000a3f0004 N = SP - H; if (N) goto 0x101; else goto 0x001;
00a: 00603f0805 LV = LV - H; goto 0x00c;
00b: 00603f0404 SP = SP - H; goto 0x00c;
00c: 0038000000 goto 0x007;
00d: 0000000000 goto 0x000;
...
100: 0800000000 goto 0x100;
101: 0050148004 H = SP; goto 0x00a;
...
1ff: 0000000000 goto 0x000;
The first line specifies the entry point of the microprogram, i.e. the initial value of MPC. Following the first line are 512 lines, each corresponding to a 36-bit word in the control store. Each line consists of 3 parts; first part is the address in the Mic1 control store, second part is the microinstruction, consisting of 10 hex digits, the first of which is always 0. The rest of the line is the microinstruction disassembled. Only the 10 hex digits are significant, the rest is provided for readability.
To simulate the Mic1 with the above program as the contents of the control store, invoke the simulator as follows:
mic1 test.mic1
As a result, a detailed execution trace is printed on standard output:
Mic1 Trace of gcd.mic
MAR=0 MDR=0 PC=0 MBR=0 SP=0 LV=0 CPP=0 TOS=0 OPC=0 H=0
0x002: SP = H = 1; goto 0x003;
MAR=0 MDR=0 PC=0 MBR=0 SP=1 LV=0 CPP=0 TOS=0 OPC=0 H=1
0x003: SP = H = H + SP + 1; goto 0x004;
MAR=0 MDR=0 PC=0 MBR=0 SP=3 LV=0 CPP=0 TOS=0 OPC=0 H=3
...
The simulated microinstructions are disassembled and printed along with their position in the control store. For each microcycle, the contents of the Mic1 registers are printed.
The Mic1 Assembly Language
Syntax
This section describes the syntax for the MAL assembly language in a modified Backus-Naur form. In particular the notation line+ means one or more occurrences of
line. Certain restrictions and features that are not directly visible from the syntax, are summarised here:
- Literals are not case-sensitive. Thus it is legal to write, say, LV = inv (Sp)
- An arbitrary number of ``white-space'' characters (ie. space, tab and newline) are allowed between terminals and non-terminals on the right side of productions.
- A symbol can be arbitrarily long, but must start with a letter, possibly followed by more letters, digits or "_". These are examples of valid symbols:
fibonacci, then23 and
MyMult_32.
- An integer is either specified in decimal notation (eg. 143, 45 or -31) or hexadecimal notation (eg. 0xf000, 0xbeef or -0x34).
- Comments start with "#" and extend to the end of the line.
In the following literals are written in boldface:
program: line+
line : label insns
| label
| insns
|
label : symbol :
| symbol = integer :
insns : insns ';' insn
| insn
insn : rd
| wr
| fetch
| empty
| goto symbol
| goto ( mbr )
| goto ( mbr or integer )
| if ( cond ) goto symbol ; else goto symbol
| assign
cond : n | z
assign : vreg = assign
| vreg = alu
| vreg = alu >> 1
| vreg = alu << 8
alu : reg
| inv ( reg )
| - reg
| reg + reg
| reg + reg + 1
| reg + 1
| reg - reg
| reg - 1
| reg and reg
| reg or reg
| 1
| 0
| -1
reg : mar | mbr | mbru | mdr | pc | sp
| lv | cpp | tos | opc | h
vreg : reg | n | z
The contents of a MAL program is a list of lines containing instructions and labels. The program may contain blank lines, which are just ignored. Each line of instructions is assembled into a Mic1 control store word. The entry point for the simulator is the Mic1 instruction corresponding to the first line in the MAL file.
The grammar given above is a bit too liberal, that is, the parser accepts expressions that can't be computed using the Mic1 ALU, for example MBR = H + H. This is detected in a later pass in the assembler, though. The list of expressions accepted by the assembler is
DEST = H
DEST = SOURCE
DEST = inv (H)
DEST = inv (SOURCE)
DEST = H + SOURCE
DEST = H + SOURCE + 1
DEST = H + 1
DEST = SOURCE + 1
DEST = SOURCE - H
DEST = SOURCE - 1
DEST = -H
DEST = H and SOURCE
DEST = H or SOURCE
DEST = 0
DEST = 1
DEST = -1
where SOURCE is any register that can be enabled onto the B bus, and DEST is any register that can be loaded from the C bus.
A label is declared by writing its name followed by a colon in front of the instruction they refer to. To specify that a label should be placed at an absolute position in the control store, use the absolute label syntax: label-name =
position:. Here's an example of using the two kinds of labels:
iload = 0x15:
H = LV
MAR = MBRU + H; rd
iload_cont:
MAR = SP = SP + 1
PC = PC + 1; fetch; wr
TOS = MDR; goto main
It is possible to use an absolute label as a branch target in a conditional branch, provided the addresses can be resolved correctly. As an example, the following is not allowed:
A = 2: fetch
B: MAR = MBRU
N = MDR; if (N) goto A; else goto B
since the branch target for the case when the N bit is set, should be above 0x100.
Additions to MAL
Two new instructions empty and halt have been added to MAL:
empty:
Since the Mic1 assembler ignores blank lines, another way to specify an empty cycle is needed, which is why empty is introduced. The empty instruction can only occur on a line by itself, and it specifies that the assembler should generate an empty cycle. Functionally it is equivalent to
goto next-line, that is
empty
MAR = SP = SP - 1; ...
...
is equivalent to
goto next_line:
next_line:
MAR = SP = SP - 1; ...
...
halt:
Normally an instruction like halt wouldn't be available in a real microarchitecture, since it should just run forever. However, it is convenient to be able to stop the simulator. Like empty, halt can only occur on a line by itself. It is assembled into a microinstruction that enables register 15 on the B bus, an otherwise illegal operation.
Running IJVM programs with the Mic1
simulator
Even if the simulator can run standalone Mic1 programs, it is most useful when running the microprogrammed IJVM interpreter with an IJVM program loaded in the memory. This can be achieved by specifying a second argument when invoking the Mic1 simulator, namely the name of an IJVM bytecode file.
For this purpose, the IJVM interpreter from SCO pages 272-274 has been adapted to the MAL syntax described above and is available as ijvm.mal. Using the Mic1 assembler, the IJVM interpreter can be translated into a control store image in the file ijvm.mic1:
mic1-asm ijvm.mal ijvm.mic1
Now suppose that the example from the IJVM Tools manual,
min.j has been translated into bytecode in the file min.bc. Invoking the Mic1 simulator as follows:
mic1 ijvm.mic1 min.bc 53 174
will initialize the Mic1 simulator with the contents of
ijvm.mic1 in the control store and load the bytecode file min.bc into memory. When the simulator is used to run Mic1 standalone programs, the Mic1 registers are all initialized to zero, and the contents of the memory is unspecified. However, if an IJVM bytecode file is loaded into memory, the initialization process is a bit more complex:
- The index of the main method in the bytecode file is loaded into H.
- The bytes in the method area are loaded into memory from address 0 and up.
- The words in the constant pool are loaded into memory at the first 4 byte aligned address after the method area. CPP is initialised with this address.
- The stack is placed immediately following the constant pool. An (arbitrary) object reference and the trailing command line arguments are placed on the stak, and SP is initialized to point to the top of the stack.
The upshot of this is that Tanenbaums IJVM implementation (with minor modifications, see Appendix B) will work exactly as the C implementation of IJVM. In the example above we get the following execution trace:
Mic1 Trace of ijvm.mic1 with min.bc
stack = 0, 1, 174, 53, 15
bipush 88 [10 58] stack = 88, 0, 1, 174, 53, 15
iload 1 [15 01] stack = 53, 88, 0, 1, 174, 53, 15
iload 2 [15 02] stack = 174, 53, 88, 0, 1, 174, 53, 15
invokevirtual 1 [b6 00 01] stack = 12, 13, 0, 174, 53, 21, 0, 1
iload 1 [15 01] stack = 53, 12, 13, 0, 174, 53, 21, 0
iload 2 [15 02] stack = 174, 53, 12, 13, 0, 174, 53, 21
isub [64] stack = -121, 12, 13, 0, 174, 53, 21, 0
iflt 10 [9b 00 0a] stack = 12, 13, 0, 174, 53, 21, 0, 1
iload 1 [15 01] stack = 53, 12, 13, 0, 174, 53, 21, 0
istore 3 [36 03] stack = 12, 13, 53, 174, 53, 21, 0, 1
iload 3 [15 03] stack = 53, 12, 13, 53, 174, 53, 21, 0
ireturn [ac] stack = 53, 0, 1, 174, 53, 15
ireturn [ac] stack = 53
return value: 53
The Mic1 simulator hides the microinstructions and shows only the disassembled IJVM instructions, when running with an IJVM bytecode program. As a result, the trace is identical to the trace produced by the C-implementation of the IJVM simulator, except for the header. However, the simulator wouldn't be too useful, if it wasn't somehow possible to see the microinstructions that are executed. This is implemented as a feature called breakpoints. Setting breakpoints is a way to inform the simulator, that a detailed microtrace for one or several IJVM instructions is desired. This is accomplished using the -b option when invoking the simulator.
Suppose we wanted to study the implementation of
istore. We would then invoke the simulator as follows:
mic1 -b istore ijvm.mic1 min.bc 53 174
which would result in this trace:
Mic1 Trace of ijvm.mic1 with min.bc
stack = 0, 1, 174, 53, 15
bipush 88 [10 58] stack = 88, 0, 1, 174, 53, 15
iload 1 [15 01] stack = 53, 88, 0, 1, 174, 53, 15
iload 2 [15 02] stack = 174, 53, 88, 0, 1, 174, 53, 15
invokevirtual 1 [b6 00 01] stack = 12, 13, 0, 174, 53, 21, 0, 1
iload 1 [15 01] stack = 53, 12, 13, 0, 174, 53, 21, 0
iload 2 [15 02] stack = 174, 53, 12, 13, 0, 174, 53, 21
isub [64] stack = -121, 12, 13, 0, 174, 53, 21, 0
iflt 10 [9b 00 0a] stack = 12, 13, 0, 174, 53, 21, 0, 1
iload 1 [15 01] stack = 53, 12, 13, 0, 174, 53, 21, 0
istore 3 [36 03]
MAR=23 MDR=53 PC=35 MBR=54 SP=23 LV=17 CPP=10 TOS=53 OPC=23 H=17
0x002: PC = PC + 1; fetch; goto (MBR);
MAR=23 MDR=53 PC=36 MBR=54 SP=23 LV=17 CPP=10 TOS=53 OPC=23 H=17
0x036: H = LV; goto 0x01d;
MAR=23 MDR=53 PC=36 MBR=3 SP=23 LV=17 CPP=10 TOS=53 OPC=23 H=17
0x01d: MAR = H + MBRU; goto 0x01e;
MAR=20 MDR=53 PC=36 MBR=3 SP=23 LV=17 CPP=10 TOS=53 OPC=23 H=17
0x01e: MDR = TOS; wr; goto 0x01f;
MAR=20 MDR=53 PC=36 MBR=3 SP=23 LV=17 CPP=10 TOS=53 OPC=23 H=17
0x01f: MAR = SP = SP - 1; rd; goto 0x020;
MAR=22 MDR=53 PC=36 MBR=3 SP=22 LV=17 CPP=10 TOS=53 OPC=23 H=17
0x020: PC = PC + 1; fetch; goto 0x021;
MAR=22 MDR=12 PC=37 MBR=3 SP=22 LV=17 CPP=10 TOS=53 OPC=23 H=17
0x021: TOS = MDR; goto 0x002;
MAR=22 MDR=12 PC=37 MBR=21 SP=22 LV=17 CPP=10 TOS=12 OPC=23 H=17
stack = 12, 13, 53, 174, 53, 21, 0, 1
iload 3 [15 03] stack = 53, 12, 13, 53, 174, 53, 21, 0
ireturn [ac] stack = 53, 0, 1, 174, 53, 15
ireturn [ac] stack = 53
return value: 53
At the time the simulator reaches the implementation of
istore, it starts to show the microinstructions and microregisters. When istore finishes, the simulator returns to just showing the IJVM instructions. Notice how the stacktrace doesn't occur until after execution of the
istore microinstructions, the stacktraces show the contents of the stack after execution of the IJVM instruction that preceeds them.
It is possible to set several breakpoints at a time; just use the -b options several times:
mic1 -b istore -b isub ijvm.mic1 min.bc 53 174
would allow us to inspect the istore and the
isub implementations. Another useful option, to be used with the breakpoint feature is -t that makes the simulator wait for a keypress after each line of microtrace.
Finally, -b all will instruct the simulator to show the microtrace for all IJVM instructions.
Extending the IJVM
Program
The Mic1 simulator uses the same specification file mechanism as the C implementation of IJVM to retrieve information about opcodes, mnemonics and arguments for IJVM instructions. Thus, if the Mic1 implementation of IJVM is extended with extra instructions, you should define these in the specification file.
As an example, consider again the instruction
iconst_0 (see Extending
IJVM in the IJVM Tools manual). First, we add the line:
0x03 iconst_0
to the file ijvm.spec so that the Mic1 simulator will be able to recognize and disassemble the new instruction.
Then we add the actual implementation of iconst_0 to the IJVM implementation, ijvm.mal:
iconst_0 = 0x03:
MAR = SP = SP + 1
MDR = 0; wr; goto main
To test our implementation, we use this test program:
.method main
iconst_0
bipush 14
iadd
ireturn
Having set the environment variable IJVM_SPEC_FILE to
./ijvm.spec as described in the IJVM Tools manual, we assemble the test program and get the following bytecode:
main index: 0
method area: 9 bytes
00 01 00 00 03 10 05 60 ac
constant pool: 1 words
00000000
We invoke the Mic1 simulator as described in the previous section, with a breakpoint at iconst_0:
mic1 -b iconst_0 ijvm-iconst-0.mic1 test-iconst-0.bc
and get the following excution trace:
Mic1 Trace of ijvm-iconst-0.mic1 with test-iconst-0.bc
stack = 0, 1, 5
iconst_0 [03]
MAR=6 MDR=0 PC=4 MBR=3 SP=6 LV=4 CPP=3 TOS=4 OPC=1 H=0
0x002: PC = PC + 1; fetch; goto (MBR);
MAR=6 MDR=0 PC=5 MBR=3 SP=6 LV=4 CPP=3 TOS=4 OPC=1 H=0
0x003: MAR = SP = SP + 1; goto 0x066;
MAR=7 MDR=0 PC=5 MBR=16 SP=7 LV=4 CPP=3 TOS=4 OPC=1 H=0
0x066: MDR = 0; wr; goto 0x002;
MAR=7 MDR=0 PC=5 MBR=16 SP=7 LV=4 CPP=3 TOS=4 OPC=1 H=0
stack = 0, 0, 1, 5
bipush 14 [10 0e] stack = 14, 0, 0, 1, 5
iadd [60] stack = 14, 0, 1, 5
ireturn [ac] stack = 14
return value: 14
Appendix A: Command
line syntax for the Mic1 simulator
This appendix details the options understood by the Mic1 simulator. The simulator expects a commandine of the form
mic1 [OPTION] MIC1-FILENAME [IJVM-FILENAME PARAMETERS ...]
That is, options (if any) should come first, then the filename of the assembled microprogram (a control store image). If an IJVM bytecode file should be loaded as well, its filename and any parameters for the main method should come after the Mic1 filename.
The Mic1 simulator accepts three kinds of options:
Command line option |
Result |
-s |
Silent mode. No snapshot is produced |
-f SPEC-FILE |
The IJVM specification file to use. If this option is used it overrides any value the environment variable
IJVM_SPEC_FILE may have. |
-t |
When this option is set, the simulator will wait for a keypress after each line of microtrace. |
-v |
Display version and build info |
-b INSN |
Show microtrace for the IJVM instruction INSN.
INSN must be defined in the IJVM specification file. As a special case, -b all will cause the simulator to show microtrace for all instructions. |
If you pass `-' as the Mic1 filename, the simulator will read the control store image from stdin.
You must specify as many arguments as your IJVM main method requires, except one; the simulator will pass the initial object reference for you.
Appendix B: The IJVM
microprogram
In order to make the microprogrammed IJVM interpreter act as the C version, some startup and exit code has been added. As described above, an object reference and the command line arguments are placed on the stack and the index of the address of the main method is placed in H.
At the label mic1_entry in invokevirtual the microprogrammed IJVM has decoded the method index from the bytecode and placed it in H. Then the program proceeds to look up the address in the constant pool and decode the number of arguments and local variables.
Thus, in the initial configuration described above, by jumping to mic1_entry, the microprogrammed IJVM simulates an initial invokevirtual main, since the stack is set up for a procedure call and H contains the index of the function to call. This initial invokevirtual leaves a return address of 1 (OPC + 1) on the stack.
The extra exit code is found in the last 4 lines of the microprogram. All they do is check wether ireturn is trying to return to address 1. This return address will never occur in a wellbehaved IJVM program, since address 1 does not contain bytecode, but rather the lower 8 bits of the number of arguments to main. If the return address is 1, the microprogram is halted. Otherwise it is assumed that the
ireturn corresponds to an ordinary
invokevirtual and the program continues as usual.
goto mic1_entry
main:
PC = PC + 1; fetch; goto (MBR)
nop = 0x00:
goto main
iadd = 0x60:
MAR = SP = SP - 1; rd
H = TOS
MDR = TOS = MDR + H; wr; goto main
isub = 0x64:
MAR = SP = SP - 1; rd
H = TOS
MDR = TOS = MDR - H; wr; goto main
iand = 0x7E:
MAR = SP = SP - 1; rd
H = TOS
MDR = TOS = MDR AND H; wr; goto main
ior = 0x80:
MAR = SP = SP - 1; rd
H = TOS
MDR = TOS = MDR OR H; wr; goto main
dup = 0x59:
MAR = SP = SP + 1
MDR = TOS; wr; goto main
pop = 0x57:
MAR = SP = SP - 1; rd
empty
TOS = MDR; goto main
swap = 0x5F:
MAR = SP - 1; rd
MAR = SP
H = MDR; wr
MDR = TOS
MAR = SP - 1; wr
TOS = H; goto main
bipush = 0x10:
SP = MAR = SP + 1
PC = PC + 1; fetch
MDR = TOS = MBR; wr; goto main
iload = 0x15:
H = LV
MAR = MBRU + H; rd
iload_cont:
MAR = SP = SP + 1
PC = PC + 1; fetch; wr
TOS = MDR; goto main
istore = 0x36:
H = LV
MAR = MBRU + H
istore_cont:
MDR = TOS; wr
SP = MAR = SP - 1; rd
PC = PC + 1; fetch
TOS = MDR; goto main
wide = 0xC4:
PC = PC + 1; fetch; goto (MBR OR 0x100)
wide_iload = 0x115:
PC = PC + 1; fetch
H = MBRU << 8
H = MBRU OR H
MAR = LV + H; rd; goto iload_cont
wide_istore = 0x136:
PC = PC + 1; fetch
H = MBRU << 8
H = MBRU OR H
MAR = LV + H; goto istore_cont
ldc_w = 0x13:
PC = PC + 1; fetch
H = MBRU << 8
H = MBRU OR H
MAR = H + CPP; rd; goto iload_cont
iinc = 0x84:
H = LV
MAR = MBRU + H; rd
PC = PC + 1; fetch
H = MDR
PC = PC + 1; fetch
MDR = MBR + H; wr; goto main
ijvm_goto = 0xA7:
OPC = PC - 1
goto_cont:
PC = PC + 1; fetch
H = MBR << 8
H = MBRU OR H
PC = OPC + H; fetch
goto main
iflt = 0x9B:
MAR = SP = SP - 1; rd
OPC = TOS
TOS = MDR
N = OPC; if (N) goto T; else goto F
ifeq = 0x99:
MAR = SP = SP - 1; rd
OPC = TOS
TOS = MDR
Z = OPC; if (Z) goto T; else goto F
if_icmpeq = 0x9F:
MAR = SP = SP - 1; rd
MAR = SP = SP - 1
H = MDR; rd
OPC = TOS
TOS = MDR
Z = OPC - H; if (Z) goto T; else goto F
T:
OPC = PC - 1; fetch; goto goto_cont
F:
PC = PC + 1
PC = PC + 1; fetch
goto main
invokevirtual = 0xB6:
PC = PC + 1; fetch
H = MBRU << 8
H = MBRU OR H
mic1_entry:
MAR = CPP + H; rd
OPC = PC + 1
PC = MDR; fetch
PC = PC + 1; fetch
H = MBRU << 8
H = MBRU OR H
PC = PC + 1; fetch
TOS = SP - H
TOS = MAR = TOS + 1
PC = PC + 1; fetch
H = MBRU << 8
H = MBRU OR H
MDR = SP + H + 1; wr
MAR = SP = MDR
MDR = OPC; wr
MAR = SP = SP + 1
MDR = LV; wr
PC = PC + 1; fetch
LV = TOS; goto main
ireturn = 0xAC:
MAR = SP = LV; rd
empty
LV = MAR = MDR; rd
MAR = LV + 1
PC = MDR; rd; fetch
MAR = SP
LV = MDR
MDR = TOS; wr
Z = PC - 1; if (Z) goto mic1_exit; else goto main
mic1_exit:
halt