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.

Contents

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:

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 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