header

Microprograms

A microinstruction is a bit pattern in which each bit (or combination of bits) drives the control signals of the hardware. A microprogram is a series of microinstructions that accomplishes some specific task. On this web page we will look at some very simple microprograms. We will assume that every microprogram begins at address 0x000 in the control store. A microprogram doesn't terminate unless the computer is turned off. For the sake of simplicity, however, we will assume that there is a "halt" microinstruction. This instruction is all zeroes except the 8 low-order bits of the next address field.

Accessing Registers

Write a program that reads the value from CPP and stores it in both SP and H at the same time.

As a general rule, it's best to start with a microinstruction consisting of all zeroes (see below) and then set only the bits that need to be set to perform the desired operation.

All Zeroes

Work through each section one at a time.

B Bus: Since we want to read the data from CPP (R6), the value in the B Bus field will need to be 0110.

Mem: No change.

C Bus: We want to write to SP and H so those bits are set in the C Bus field.

ALU: We want the ALU result to be equal to the value on the B bus. One way to do this is to perform the operation 0 OR B. Leaving ENA and INVA at 0 will assure that the ALU sees a 0 on the A input. In order for the ALU to see the value on the B bus, we need to set ENB high. To perform 0 OR B, set F1 to 1. Final value: 00010100

JMP: No change.

Next Address: 0x001 in binary is 000000001

The second instruction (at 0x001) is the halt instruction. Here is our complete microprogram:

Microprogram

Accessing Memory

Example 1: Read

Write a program that reads the value from memory address 0 and stores it in register SP.

This program will require 4 instructions:

Technically, the CPU never does nothing. However, when all of the control bits are zeroes, the only thing that happens is that the ALU produces a value of zero (ALU = 0 AND 0). Since this result is totally ignored, the CPU hasn't done anything at all useful while it waits for the memory read to complete.

The logic above can be expressed more succinctly:

  1. 0x000 MAR = 0; RD; goto 0x001
  2. 0x001 ALU = 0; goto 0x002
  3. 0x002 SP = MDR; goto 0x003
  4. 0x003 Halt

Microprogram 

Example 2: Write (and Arithmetic)

Write a program that writes the value 1 to memory address 2.

The task seems relatively simple. Load the MDR with the value 1, load the MAR with the value 2 and assert the WR signal. There are, however a couple of challenges. The first is how to generate a value of 1. We've already seen how to do this when we discussed the ALU control signals on the CPU page (find 0 + 0 and increment the result). How, though, do we generate a value of 2? One obvious answer is to perform the arithmetic 1 + 1. Let's adopt this strategy:

Put more succinctly:

  1. 0x000 MDR=H=1; goto 0x001
  2. 0x001 MAR=H+MDR; WR; goto 0x002
  3. 0x002 ALU = 0; goto 0x003
  4. 0x003 Halt

Microprogram 

Branching (and Arithmetic)

Example 1

Write a program to replace the 2's complement integer value stored in TOS with its absolute value.

If TOS is positive there is nothing to do. If TOS is negative then we need to replace TOS with the negative of TOS

The trick here is to understand how the JMPN bit affects the program. When the JMPN bit and the N flag are both 1, the high order bit of the MPC will be 1 otherwise the high-order bit is determined by the value in the next address field. Since this is a two-way branch, it follows that the address in the next address field must have a high-order bit of 0. Otherwise, the high-order bit would already be one and it wouldn't matter what the values of the JMPN and N bits were. As a general rule, when a conditional jump is executed, the high-order bit of the next address must be zero and represents the address of the next instruction if the jump fails. If the jump succeeds, the high-order bit will be set to 1 to form the address of the next instruction when the jump succeeds.

  1. 0x000:  ALU = TOS; if N goto 0x101 else goto 0x001
  2. 0x001:  Halt
  3. 0x101:  H = TOS; goto 0x102
  4. 0x102:  TOS = -H; goto 0x001

Microprogram 

Now let's think about this program a little. In the first instruction we retrieve the value in TOS in order to look at it. If it is negative, then we retrieve it again in order to copy it into register H. Wouldn't it be smarter to put the value of TOS into H in the first instruction? That way, it would already be in H if the jump succeeds and we wouldn't need to retrieve it again. As a result, we can reduce the number of instructions from 4 to 3. (If the jump fails, then moving TOS to H wasn't necessary but there are no undesirable consequences for having done so.)

  1. 0x000:  H = TOS ; if N goto 0x101 else goto 0x001
  2. 0x001:  Halt
  3. 0x101:  TOS = -H; goto 0x001

Microprogram

Example 2

Write a micro program that looks at the first two words of memory (mem[0] and mem[1]) and puts the larger value in mem[2]:

More precisely:

0x000: MAR = 0; RD; goto 0x001
0x001: MAR = H = 1; RD; goto 0x002 (Set up 2nd read as we wait for 1st.)
0x002: SP = MDR; goto 0x003 (The 1st read is done.)
0x003: LV = MDR; goto 0x004 (The 2nd read is done.)
0x004: MAR = H + 1; goto 0x005 (Set up MAR for write.)
0x005: H = LV; goto 0x006
0x006: ALU = SP - H; if N goto 0x107 else goto 0x007
0x007: MDR = SP; WR; goto 0x008
0x008: ALU = 0; goto 0x009 (Do nothing while write completes.)
0x009: Halt
0x107: MDR = LV; WR; goto 0x008

Microprogram