Lab 4 - Simulator
Introduction
As discussed in class, register-transfer level design can be described as
- clock signal has a rising edge
- register outputs change
- logic works, which can be thought of as arbitrary acyclic code that assigns each variable only once
- logic results in register inputs changing
- repeat
In this lab, and the subsequent homework assignment, you will expand on these basic pieces to build your own machine simulator and write binary code for it.
Simulator Skeleton Code
For this lab, we’re providing starter code for a basic simulator in both python (sim_base.py
) and java (SimBase.java
). Pick one of the two to work with (whichever language you find more comfortable).
The program 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 programs/halt to set memory
python3 sim_base.py programs/halt
java SimBase programs/halt
Memory contents must be specified in hexadecimal bytes, separated by whitespace.
Working on your own laptop
If you already have either a Java or Python environment on your laptop, you are welcome and encouraged to work on this lab locally.
You may download the starter code, unzip and open the simulator code in your chosen environment.
Working on the portal
If you’d prefer to work in the portal environment, then you can get a copy of the starter code from our course directory by cd
-ing into the directory you wish to use and then issuing the following command to unzip the code there:
unzip /p/cso1/lab4.zip
It should create a lab4-simulator
directory with all the starter code to open in vim
, nano
, or your chosen command-line editor.
Hint: remember that if you write Java on the portal, you will need to compile your Java before running it. Once you’ve edited SimBase.java
, compile the code with javac SimBase.java
before running as described above.
Write the simulator functionality
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 get_bits
helper function for getting a range of bits from a number which you may use if you wish.
Add code to
execute
(do not edit other parts of the file) to do the following:Separate the instruction into parts
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 Execute the instruction
(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 differenticode
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 numbera
” andrB
means “the value stored in register numberb
.” Unless a different value of thepc
is specified in the table, also add one to thepc
for each instruction.
icode
Behaviors: for each icode (3-bit) value, which operation to perform given the operands.
icode operation 0
rA = rB
1
rA &= rB
2
rA += rB
3 do different things for different values of
b
:
b
action 0 rA = ~rA
1 rA = !rA
2 rA = -rA
3 rA = pc
4
rA =
read from memory at addressrB
5 write
rA
to memory at addressrB
6 do different things for different values of
b
:
b
action 0 rA =
read from memory atpc + 1
1 rA &=
read from memory atpc + 1
2 rA +=
read from memory atpc + 1
3 rA =
read from memory at the address stored atpc + 1
In all 4 cases, increase
pc
by 2, not 1, at the end of this instruction7 Compare
rA
(as an 8-bit 2’s-complement number) to0
; - ifrA <= 0
, setpc = rB
- otherwise, incrementpc
like normal.Test your simulator
See Example programs below for suggestions.
Language nuances
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
acts like R[i] == 0
, which is not what we want. It can be re-written to work correctly as R[i] == 0 or R[i] >= 0x80
Example programs
- Halt
- The code
00 00 00 80
should advance to
pc
3 and then stay there.Try finding other code that will also do that.
- Move
- 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 54 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 54 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.
- Math
- Consider the following code
Bytes Activity 68 10 move 0x10 into \(R_2\) 42 move value from address \(R_2\) into \(R_0\) 68 11 move 0x11 into \(R_2\) 46 move value from address \(R_2\) into \(R_1\) 24 \(R_1\) += \(R_0\) 68 12 move 0x12 into \(R_2\) 56 move \(R_1\) into address \(R_2\) 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.
- Conditional jump
- The following should not jump, so three steps should end up with the PC at address 5, not 20:
pseudocode parts bytes \(R_0\) = 10 (6, 0, 0) 10 60 0A \(R_1\) = 20 (6, 1, 0) 20 64 14 if \(R_0\) <= 0, jump to \(R_1\) (7, 0, 1) 71 The following should jump, so three steps should end with the PC at address 20, not 5:
pseudocode parts bytes \(R_0\) = −10 (6, 0, 0) −10 60 F6 \(R_1\) = 20 (6, 1, 0) 20 64 14 if \(R_0\) <= 0, jump to \(R_1\) (7, 0, 1) 71 - Read from immediate address
- The following should load 20 into \(R_1\)
pseudocode parts bytes \(R_2\) = 10 (6, 2, 0) 10 68 0A \(R_3\) = 20 (6, 3, 0) 20 6C 14 write \(R_3\) to address \(R_2\) (4, 3, 2) 5E read address 10 into \(R_1\) (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 \(R_1\) (6, 1, 3) 10 67 0A intialize address 10 with 20 0s, then 20 00 00 00 00 00 00 00 14
More, less-explained examples
- icode = 0
- First load a value into \(R_1\) then set \(R_2\) to equal \(R_1\):
64 14
09
- icode = 1
- First load a value into \(R_1\) and \(R_2\) then and them together:
64 14
68 20
19
- icode = 2
- First load a value into \(R_1\) and \(R_2\) then add them together:
64 14
68 20
29
- icode = 3
- First load a value into \(R_1\) and then do something to it:
- flip all bits:
64 89
34
(result:76
) - perform
!
:64 89
35
(result:00
; try also35
by itself to result in01
) - negate:
64 89
36
(result:77
) - replace with the PC:
64 89
37
(result:02
)
- flip all bits:
- icode = 4
- First load a value into \(R_1\) and then load that memory address into \(R_2\):
64 02
49
- icode = 5
- First load a value into \(R_1\) and \(R_2\) and then store \(R_1\) into memory at \(R_2\):
64 02
68 20
56
- icode = 6
- First load a value into \(R_1\) and then use an immediate:
- replace with immediate:
64 14
64 20
(result:20
) - and immediate:
64 14
65 24
(result:04
) - add immediate:
64 14
66 20
(result:34
) - replace with memory contents at immediate:
64 14
67 02
(result:67
)
- replace with immediate:
- icode = 7
- First load an address into \(R_1\), a value into \(R_2\), and then conditionally jump:
- jumps:
64 14
68 ff
79
- jumps:
64 14
68 80
79
- jumps:
64 14
68 00
79
- does not jump:
64 14
68 01
79
- does not jump:
64 14
68 7f
79
- jumps:
Check-off
To check-off this lab, show a TA your simulator and the memory contents that do the tasks listed above.