Eroxl's Notes
Assignment 2 - CPU & Static Variables

Problem 1

The SM213 distribution you downloaded in Assignment 0 (from Lab 0) contains the reference implementation (i.e., solution) of the machine simulator you will build over the next few assignments. You can execute this version by double-clicking on the file SimpleMachine213.jar on your machine, or from the command line by typing:

java -jar SimpleMachine213.jar

The file download for this assignment (click the link to download) contains two code snippets: S1-global-static and S2-global-dyn-array, as discussed in class. Your first task is to examine these files (there are C, Java and sm213-assembly versions for each) and run the assembly versions in the SM213 simulator.

To run the assembly programs in the simulator, click "Show Animations" to enable animations and click "Run Slowly" to begin the execution. The line for the next instruction to execute is coloured green. When an instruction reads a register or memory location it turns blue, and when it writes one of these it turns red. You can pause the animation by clicking "Pause". You can continue execution at any instruction by double clicking on that instruction's address.

More Tips for Using the Sm213 Simulator

You will get help using the simulator in lab, but here are a few quick things that you will find helpful:

  1. You can edit instructions and data values directly in the simulator (including adding new lines or deleting them).
  2. The simulator allows you to place "labels" on code and data lines. This label can then be used as a substitute for the address of those lines. For example, the variable's a and b are at addresses 0x1000 and 0x2000 respectively, but can just be referred to using the labels a and b. Keep in mind, however, that this is just a simulator/assembly-code trick, the machine instructions still have the address hardcoded in them. You can see the machine code of each instruction to the left of the instructions in the *memory image* portion of the instruction pane.
  3. You can change the program counter (i.e., pc) value by double-clicking on an instruction. And so, if you want to execute a particular instruction, double click it and then press the "Step" button. The instruction pointed to by the pc is coloured green.
  4. Memory locations and registers read by the execution of an instruction are coloured blue and those written are coloured red. With each step of the machine the colours from previous steps fade so that you can see locations read/written by the past few instructions while distinguishing the cycle in which they were accessed.
  5. Instruction execution can be animated by clicking on the "Show Animation" button and then single stepping or running slowing.

After running S1-global-static.s in the SM213 simulator (and letting it halt), what is the hexadecimal value of the PC register?

0x00000120

Problem 2

Using S1-global-static.s as a guide (see instructions in the previous question), implement the following C program in assembly and place the code in a file named swap.s.

Your program should thus just do two things: (1) allocate the global variables (using the variable names specified in the question) and (2) swap the two array elements. If you'd like, you can avoid using memory for the variable orig by just using a register in place of the variable. Load your program into the simulator. Assign different values to the two elements of array and run the program to confirm that it works.

int orig;
int array[9];

void swap() {
    orig = array[6];
    array[6] = array[1];
    array[1] = orig;
}

Make sure to include the halt instruction at the bottom of your assembly code. Your submission will timeout on the autograder otherwise.

Just as in the reference snippet, please ignore the procedure declaration for this question. We will come back to procedures later in the course.

ld $orig, r0
ld $array, r1

ld $0x6, r2
ld (r1, r2, 4), r2
st r2, (r0)

ld $0x1, r2
ld (r1, r2, 4), r2
ld $0x6, r3
st r2, (r1, r3, 4)

ld (r0), r2
ld $0x1, r3
st r2, (r1, r3, 4)

halt

.pos 0x2000
	orig:       .long 0
	array:      .long 1
	            .long 2
	            .long 3
	            .long 4
	            .long 5
	            .long 6
	            .long 7
	            .long 8
	            .long 9

Problem 3

Now let's bring in the math instructions. Implement the following C program in assembly, using your previous work as a guide. Place your code in a file named math.s.

int h, p;

void math() {
    h = ((((p + 1) + 4) << 1) & p) / 4;
}

Make sure to include the halt instruction at the bottom of your assembly code. Your submission will timeout on the autograder otherwise.

Again, skip the procedure itself; just show the code inside of the procedure. Write, run, and test your program.

Both h and p must be global variables in memory and the variable h must store the correct value when the execution completes; that's what we'll check to determine if your code works.

ld $p, r0
ld (r0), r1

mov r1, r2

inc r1
inca r1
shl $0x1, r1
and r2, r1
shr $0x2, r1

ld $h, r2
st r1, (r2)

halt

.pos 0x2000
	h:       .long 10
	p:       .long 9

Problem 4

Finally, let's put it all together. Implement the following C program in assembly, again using your previous work as a guide. Place your code in a file named calc.s. Again, skip the procedure itself; just show the code inside of the procedure. Write, run, and test your program.

int y[9];
int z, g, p;

void calc() {
    p = y[z] + y[z + 1];
    g = p & 0xf;
}

Make sure to include the halt instruction at the bottom of your assembly code. Your submission will timeout on the autograder otherwise.

y, z, g, and p must all be global variables in your implementation.

ld $z, r0

ld (r0), r1

mov r1, r2
inc r2

ld $y, r3

ld (r3, r1, 4), r4
ld (r3, r2, 4), r5

add r4, r5

ld $p, r6
st r5, (r6)

ld $g, r0
ld $0xf, r1
and r5, r1

st r1, (r0) 

halt

.pos 0x2000
	y:      .long 0
			.long 1
			.long 2
			.long 3
			.long 4
			.long 5
			.long 6
			.long 7
			.long 8
	z:      .long 0
	g:      .long 0
	p:      .long 0

Problem 5

It is now time to implement the set of sm213 instructions we have thus far identitied. You will do this by modifying the CPU class in the student simulator code.

Note that this code uses the Memory class that you implemented in Assignment 1. If your implementation has bugs, you'll likely have to fix them before you can test your CPU code. If necessary, you can reference (or use) the Assignment 1 solution, which is now available on in Assignment 1 in Prairie Learn.

Each of the sm213 instructions we've identified so far are listed in the switch statement in CPU. The first instruction is implement for you. The rest have a TODO comment where you need to do the work.

Notice that each step (i.e., cycle) of the processor has two stages fetch and execute, which are represented in CPU as methods. The fetch state is fully implemented. What it does is retrieve an instruction from memory and place it in the instruction register. In the simulator this register is available to you as a set of variables that make it convenient to access the different parts of the instruction, as discussed in class, listed in the Companion and shown in the following table.

Variable Description
insOpCode op code
insOp0 hexit operand 0
insOp1 hexit operand 1
insOp2 hexit operand 2
insOpImm operands 1 and 2 combined into a single, signed byte
insOpExt the option four-byte constant value

In the switch statement, each instruction's format is listed as a comment to simplify your work. These comments use the same format described in class and in the Companion.

For example, the first instruction (the one that is implemented for you) is load immediate. It's hardware instruction format 0d-- vvvv vvvv. Each of these characters represents a hexit of the instruction. The first is in insOpCode The value in the d hexit is in insOp0. In this instruction, the next two hexits are not used, but if they were they would be in insOp1 and insOp2 and also in insOpImm. Finally this instruction is six bytes long and the last four bytes are the constant (i.e., immediate) value to load into register d. This value is in insOpExt.

Your instruction implementation will need to read the instruction registers and read and/or write the general purpose register file and, in some cases, main memory. Both the register file and main memory are represented by the variables reg and mem respectively.

All of these variables have getters and setters. When accessing a register, you specify a register number; and when accessing main memory, you specify a target memory address. Memory values are four-byte signed numbers.

You can find semantic RTL descriptions of the SM213 instructions in the appendix of the Companion. This specification will help you understand what each instruction should do.

Tips of Coding and Testing

We suggest you implement and test each instruction one at a time. Finish one before proceeding to the next one. You will find that once you have completed one or two of these (correctly) the rest will be fairly easy.

In order to test an instruction, you'll need to create a test assembly file that uses the instruction in various ways. We have provided you with the file test.s for this purpose, but you could also write your own.

To test your implementation load your test file into your simulator (be sure it's the one you are editing and not the reference implementation), run it, and examine the contents of the register file (and, where appropriate, main memory) to see that it did the right thing. It might be helpful when doing this to single step the execution. Note also that you can start execution at an arbitrary instruction by double clicking on its address. The instruction highlihgted in green is the next instruction to execute when you click Step or Run. Note that our test.s file has a halt instruction after the test code for each different instruction, so you will have to use this double-click technique to tests each particular instruction (except the first one).

Note also that, just like with any Java program, you can set breakpoints in your code so that you can examine the simultor's internal state during execution (e.g., to read the value of the ins registers, the reg registers or mem).

switch (insOpCode.get()) {
  case 0x0: // ld $v, d .............. 0d-- vvvv vvvv
	reg.set (insOp0.get(), insOpExt.get());
	break;
  case 0x1: // ld o(rs), rd .......... 1psd  (p = o / 4)
	int baseAddress1 = reg.get(insOp1.get());
	int effectiveAddress1 = baseAddress1 + (insOp0.get() << 2);
	int loadedValue1 = mem.readInteger(effectiveAddress1);

	reg.set(insOp2.get(), loadedValue1);
	break;
  case 0x2: // ld (rs, ri, 4), rd .... 2sid
	int baseAddress2 = reg.get(insOp0.get());
	int indexValue2 = reg.get(insOp1.get());
	int effectiveAddress2 = baseAddress2 + (indexValue2 << 2);
	int loadedValue2 = mem.readInteger(effectiveAddress2);

	reg.set(insOp2.get(), loadedValue2);
	break;
  case 0x3: // st rs, o(rd) .......... 3spd  (p = o / 4)
	int baseAddress3 = reg.get(insOp2.get());
	int effectiveAddress3 = baseAddress3 + (insOp1.get() << 2);
	int valueToStore3 = reg.get(insOp0.get());

	mem.writeInteger(effectiveAddress3, valueToStore3);

	break;
  case 0x4: // st rs, (rd, ri, 4) .... 4sdi
	int baseAddress4 = reg.get(insOp1.get());
	int indexValue4 = reg.get(insOp2.get());
	int effectiveAddress4 = baseAddress4 + (indexValue4 << 2);
	int valueToStore4 = reg.get(insOp0.get());

	mem.writeInteger(effectiveAddress4, valueToStore4);
	break;
  case 0x6: // ALU ................... 6-sd
	switch (insOp0.get()) {
	  case 0x0: // mov rs, rd ........ 60sd
		int value0 = reg.get(insOp1.get());
		reg.set(insOp2.get(), value0);
		break;
	  case 0x1: // add rs, rd ........ 61sd
		int value1_1 = reg.get(insOp1.get());
		int value1_2 = reg.get(insOp2.get());
		int sum1 = value1_1 + value1_2;

		reg.set(insOp2.get(), sum1);
		break;
	  case 0x2: // and rs, rd ........ 62sd
		int value2_1 = reg.get(insOp1.get());
		int value2_2 = reg.get(insOp2.get());

		reg.set(insOp2.get(), value2_1 & value2_2);
		break;
	  case 0x3: // inc rr ............ 63-r
		int value3 = reg.get(insOp2.get());
		
		reg.set(insOp2.get(), value3 + 1);
		break;
	  case 0x4: // inca rr ........... 64-r
		int value4 = reg.get(insOp2.get());
		
		reg.set(insOp2.get(), value4 + 4);
		break;
	  case 0x5: // dec rr ............ 65-r
		int value5 = reg.get(insOp2.get());

		reg.set(insOp2.get(), value5 - 1);
		break;
	  case 0x6: // deca rr ........... 66-r
		int value6 = reg.get(insOp2.get());

		reg.set(insOp2.get(), value6 - 4);
		break;
	  case 0x7: // not ............... 67-r
		int value7 = reg.get(insOp2.get());

		reg.set(insOp2.get(), ~value7);
		break;
	  default:
		throw new InvalidInstructionException();
	}
	break;
  case 0x7: // sh? $i,rd ............. 7dii
	int value = reg.get(insOp0.get());
	int shiftAmount = insOpImm.get();

	if (shiftAmount > 0) {
	  reg.set(insOp0.get(), value << shiftAmount);
	} else {
	  reg.set(insOp0.get(), value >> -shiftAmount);
	}

	break;
  case 0xf: // halt or nop ............. f?--
	if (insOp0.get() == 0)
	  // halt .......................... f0--
	  throw new MachineHaltException();
	else if (insOp0.getUnsigned() == 0xf)
	  // nop ........................... ff--
	  break;
	break;
  default:
	throw new InvalidInstructionException();
}