This page does not represent the most current semester of this course; it is present merely as an archive.
As discussed in class, register-transfer level design can be relatively simply described as
In this lab, and the subsequent programming assignment, you will expand on these basic pieces to build your own machine simulator and write binary code for it.
We’ve written a basic simulator in python (sim_base.py) and java (SimBase.java). This works from a command-line interface, expecting memory byte values (either directly or in a file) as a command-line argument:
# runs SimBase in java with 8 bytes of memory set
javac SimBase.java
java SimBase 01 23 45 67 89 ab cd ef
# runs SimBase in python with 8 bytes of memory set
python3 sim_base.py 01 23 45 67 89 ab cd ef
# runs SimBase using the contents of memory.txt to set memory
python3 sim_base.py memory.txt
java SimBase memory.txt
Memory contents must be specified in hexadecimal bytes, separated by whitespace.
Each file begins with a function or method named execute
which is given two arguments (the current instruction in ir
and the PC of this instruction in oldPC
) and returns one value (the pc
to execute next). The skeleton code just returns oldPC + 1
.
Each file also has two global values you can access: R
, an array of 4 register values, and M
, an array of 256 memory values.
We also provide a helper function for getting a range of bits from a number.
Add code to execute
(do not edit other parts of the file) to do the following:
Treat the instruction as having four parts:
bits | name | meaning |
---|---|---|
7 | reserved | If set, an invalid instruction. Do not do work or advance the PC if this bit is 1. |
[4, 7) | icode |
Specifies what action to take |
[2, 4) | a |
The index of a register |
[0, 2) | b |
The index of another register, or details about icode |
(You will probably need to be familiar with the Language nuances of your implementation language of choice before starting on this section.)
Change execute
to do different things for different icode
s, as follows:
If reserved
is 1, set the next PC to the current PC instead of advancing it and do nothing else.
Otherwise, see the following table, where rA
means “the value stored in register number a
” and rB
means “the value stored in register number b
.” Unless a different value of the pc
is specified in the table, also add one to the pc
for each instruction.
icode |
Behavior | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
0 | rA = rB |
||||||||||
1 | rA += rB |
||||||||||
2 | rA &= rB |
||||||||||
3 | rA = read from memory at address rB |
||||||||||
4 | write rA to memory at address rB |
||||||||||
5 | do different things for different values of
|
||||||||||
6 | do different things for different values of
In all 4 cases, increase |
||||||||||
7 | Compare rA (as an 8-bit 2’s-complement number) to 0 ; if rA <= 0 , set pc = rB otherwise, increment pc like normal. |
Python’s syntax for !x
is not x
instead.
Java treats bytes (like R[i]
) as signed integers, not unsigned. That means they are not good indices (e.g., M[R[i]]
might throw an exception if R[i]
is negative). However, R[i] & 0xFF
treats it as unsigned instead, so M[R[i] & 0xFF]
should work.
Python treats bytes as unsigned, so R[i] <= 0
is always true. It can be re-written to work correctly as R[i] == 0 or R[i] >= 0x80
The code
00 00 00 80
should advance to pc
3 and then stay there.
Try finding other code that will also do that.
Consider the following code
Bytes | Activity |
---|---|
68 23 | move 0x23 into register 2 |
06 | move from register 2 to register 1 |
60 20 | move 0x20 into register 0 |
44 | move from register 1 to memory at address from register 0 (i.e., move 0x23 to address 0x20) |
80 | halt |
If your simulator works, then 68 23 06 60 20 44 80
should end with registers being [20, 23, 23, 00]
and a 0x23 in memory at address 0x20.
Try adding code that will read from memory into register 3.
Consider the following code
Bytes | Activity |
---|---|
68 10 | move 0x10 into R2 |
32 | move value from address R2 into R0 |
68 11 | move 0x11 into R2 |
36 | move value from address R2 into R1 |
14 | R1 += R0 |
68 12 | move 0x12 into R2 |
46 | move R1 into address R2 |
80 | halt |
five 00s | (padding) |
23 A1 | some numbers to add |
If your simulator works, you should end up with 0xC4 (i.e., 0x23 + 0xA1) in address 0x12.
Try adding code that can sum more numbers from memory, not just those two.
The following should not jump, so three steps should end up with the PC at address 5, not 20:
pseudocode | parts | bytes |
---|---|---|
R0 = 10 | (6, 0, 0) 10 | 60 0A |
R1 = 20 | (6, 1, 0) 20 | 64 14 |
if R0 <= 0, jump to R1 | (7, 0, 1) | 71 |
The following should jump, so three steps should end with the PC at address 20, not 5:
pseudocode | parts | bytes |
---|---|---|
R0 = −10 | (6, 0, 0) −10 | 60 F6 |
R1 = 20 | (6, 1, 0) 20 | 64 14 |
if R0 <= 0, jump to R1 | (7, 0, 1) | 71 |
The following should load 20 into R1
pseudocode | parts | bytes |
---|---|---|
R2 = 10 | (6, 2, 0) 10 | 68 0A |
R3 = 20 | (6, 3, 0) 20 | 6C 14 |
write R3 to address R2 | (4, 3, 2) | 4E |
read address 10 into R1 | (6, 1, 3) 10 | 67 0A |
You could also do this without using instruction 4 by setting enough memory that there was already data in address 10:
pseudocode | parts | bytes |
---|---|---|
read address 10 into R1 | (6, 1, 3) 10 | 67 0A |
intialize address 10 with 20 | 0s, then 20 | 00 00 00 00 00 00 00 14 |
64 14
09
64 14
68 20
19
64 14
68 20
29
64 02
39
64 02
68 20
46
First load a value into R1 and then do something to it:
64 89
54
(result: 76
)!
: 64 89
55
(result: 00
; try also 55
by itself to result in 01
)64 89
56
(result: 77
)64 89
57
(result: 02
)First load a value into R1 and then use an immediate:
64 14
64 20
(result: 20
)64 14
65 20
(result: 34
)64 14
66 24
(result: 04
)64 14
67 02
(result: 67
)First load an address into R1, a value into R2, and then conditionally jump:
64 14
68 ff
79
64 14
68 80
79
64 14
68 00
79
64 14
68 01
79
64 14
68 7f
79
To check-off this lab, show a TA your simulator and the memory contents that do the tasks listed above.