CS233 Lab 5 Handout
Learning Objectives
1. Building an instruction decoder
2. Understanding a simple computer datapath
Work that needs to be handed in (via SVN)
By the first deadline
1. decoder.v: This file contains the module mips_decode, which takes an instruction’s opcode
and funct fields and produces all of the control signals needed by the data path. You should
use combinational design to do the implementation, much like you did for the steering circuit.
2. decoder_tb.v: A test bench for your decoder. As before, we can’t autograde this, but we
can use it to determine partial credit.
By the second deadline
1. arith_machine.v: This file contains the module arith_machine, which implements the data
path for the arithmetic machine. Your implementation will largely be instantiating modules
and wiring them together. There is little or no need for logical elements outside the various
registers, memories, adders, muxes, and ALUs that we provide and the decoder that you are
building above.
2. test_alu.s: A “test bench” for your arithmetic machine – more details in the next section.
Not autograded (see decoder_tb.v). This file contains comments indicating the value each
register should contain after execution.
That’s it as far as grading is concerned! (arith_machine_tb.v does not need to be modified – more
on that in the Compiling, Running and Testing section below – and therefore does not need to be
committed.)
We’ve provided a bunch of files for your use. None of these need to be committed.
• mips_defines.v: definition of various MIPS related instruction fields.
• rom.v: holds the instruction memory.
• alu32.v: provides an ALU.
• mux_lib.v: the same mux library from Lab4.
• rf.v: a 32 x 32b MIPS register file.
Compiling, Running and Testing
We’ve provided a Makefile you can use for compiling and running your code. The main rules of
interest are make decoder and make arith_machine, and their names should be self-explanatory.
The decoder is tested traditionally; decoder_tb.v contains a small set of test cases, which you need
to extend. On the other hand, arith_machine_tb.v is already complete; it runs all the instructions
in memory.text.dat, which is compiled from test_alu.s, and prints out the subsequent register
values. If you want to add more tests for your arithmetic machine, which we highly recommend,
here’s how to do it:
1. Modify test_alu.s to your heart’s content.
2. The next time you run make arith_machine, it will automatically attempt to recompile
1
CS233 Lab 5 Handout
test_alu.s using spim-vasm. You can install spim-vasm on your own machine following the
instructions on the wiki:
https://wiki.cites.illinois.edu/wiki/display/cs233fa16/CS+233+on+Your+Own+Machine
Incremental Testing
The wrong way to do this assignment, or any:
Step 1. Write all of the code
Step 2. Compile all of the code (and debug the compiler errors)
Step 3. Debug all of the code
One right way to do this assignment: (there are many)
Step 1. Implement and test your decoder. Make sure the right control signals are generated for all
valid instructions, and invalid instructions are handled correctly (that is, except should be 1
on an invalid instruction, and writing should not be enabled).
Step 2. Wire up the PC register, the PC+4 circuit, and the instruction memory. Hard-wire the
except output to 0, so the simulation doesn’t end prematurely. Notice that you do not need to
connect all the output ports to a signal. For example, in the ALU that performs the operation
PC+4, since we do not need the values of the zero, negative and overflow flags we can leave
those ports unconnected by simply using spaces, as shown below:
alu32 pcplus4(nextPC, , , , PC, 32’h4, `ALU_ADD);
Step 3. Compile and debug this circuit. It should start at address 0, stepping by 4 each cycle.
Check to make sure that the instructions printed out match those in memory.text.dat.
Step 4. Connect the MIPS decoder to the output of the instruction memory. At this point, since the
the MIPS instruction decoder provides the value for the except output, you should connect it
appropriately and remove the hard-wiring you did in Step 2.
Step 5. Compile and debug the full circuit.
Step 6. Implement the rest of the datapath.
Step 7. Compile and debug the complete design. Use the console output to validate whether your
design is correct, but if it isn’t use gtkwave to trace the signals at each step to see what you
aren’t getting right. gtkwave gives you a lot of visibility, so you should use it!
Debugging with gtkwave
When your design isn’t doing what you think it should, the best way to figure out what is going
wrong is with gtkwave. Here’s how you should be using it (assuming you are already testing the
smallest piece of the design that you haven’t already validated).
1. Load your simulation output into gtkwave.
2. Plot all of the top level signals.
3. Find the first point in time that your circuit isn’t doing what you think it should be doing. If
you debug a later point, some of the inputs might be wonky.
4. Find the smallest circuit in the design where the inputs to the circuit are correct but the
outputs are wrong.
5. If that circuit is a module, display the internal signals of that module and repeat step 4.
6. If that circuit is just logic, find the bug!
2
https://wiki.cites.illinois.edu/wiki/display/cs233fa16/CS+233+on+Your+Own+Machine
CS233 Lab 5 Handout
Arithmetic machine circuit
R-type format:
7
R-type format
Register-to-register arithmetic instructions use the R-type format.
This format includes six different fields.
— op is an operation code or opcode that selects a specific operation.
— rs and rt are the first and second source registers.
— rd is the destination register.
— shamt is only used for shift instructions.
— func is used together with op to select an arithmetic instruction.
op rs rt rd shamt func
6 bits 5 bits 5 bits 5 bits 5 bits 6 bits
I-type format:
10
I-type format
Instructions with immediates all use the I-type format.
Example
op rs rt imm
6 bits 5 bits 5 bits 16 bits
ori $7, $2, 0xff!
3
CS233 Lab 5 Handout
2 (Add)
32
alu_op[2:0]
write_enable
alu_src2
except
opcode[5:0]
funct[5:0]
MIPS decoder
A[31:0]
alu_op[2:0]
out[31:0]
B[31:0]
ALU
rsDatarsNum
reset
rdNum
rtDatartNum
rdWriteEnable
rdData
Register File
reset
clk
wr_enable
Rdest
Rt
Rs
zero
negative
overflow
0
1
wr_enable
alu_src2
alu_src2
alu_op[2:0]
reset
enable
Q[31:0]
D[31:0]
PC Register
Sign
Extender
out[31:0]
in[15:0]
data[31:0]
addr[29:0]
Instruction
Memory
4
inst[31:0]
PC[31:0]
ALU1
nextPC[31:0]
0
1
rd_src
inst[25:21]
inst[20:16]
inst[15:11]
inst[20:16]
inst[5:0]
inst[15:0]
inst[31:26]
except
P
C
[3
1:
2]
Rt
Rd
imm16
imm32
32
30
32
3
5
5
5
16
32
6
6
32
32
32
32
32
3
3
rd_src rd_src
4
CS233 Lab 5 Handout
Useful Verilog Syntax
We have a number of parts of the design where we are creating and selecting from groups of wires
(called buses). For example:
2 (Add)
32
reset
enable
Q[31:0]
D[31:0]
PC Register
data[31:0]
addr[29:0]
Instruction
Memory
4
inst[31:0]
PC[31:0]
ALU1
nextPC[31:0]
P
C
[3
1:
2]
32
30
32
3
32
opcode[5:0]
funct[5:0]
Rt
Rs
inst[31:0]
inst[25:21]
inst[20:16]
inst[15:11]
inst[5:0]
inst[15:0]
inst[31:26]
Rd
imm16
32
Sign
Extender
out[31:0]
in[15:0]imm16
imm32
16
32
Useful syntax for working with buses:
1. Declaring a bus:
wire [17:2] busname; // defines a 16-bit wide bus with indexes from 2 to 17, inclusive.
2. Attaching individual wires between buses:
assign x[15] = y[17]; // connects wire 15 of bus x to wire 17 of bus y.
3. Extracting some wires from a bus:
assign foo[4:0] = bar[15:11]; // connects wires 0-4 of bus foo to wires 11-15 of bus bar.
4. Replicating some wires:
assign foo[2:0] = {3{bus[4]}}; // replicates bus[4] 3 times.
5. Concatenating some wires:
assign foo[1:0] = {bus[3],inst[4]}; // concatenates bus[3] and inst[4].
6. Checking opcode and funct:
We have provided a file mips_defines.v with the values for the opcode and function code of
all the instructions you will need. Below is an example of how you can use the values defined
in that file:
opcode == `OP_ADDI // a 1 bit signal with value 1 if opcode matches the opcode of addi.
7. Declare and assign in one go:
(see next page)
5
CS233 Lab 5 Handout
For the decoder, you’ll have to create a lot of internal wires which will then be assigned to later.
Instead of declaring and assigning separately, you can do it in one go, which is slightly more
convenient. In other words, you can use the version on the right instead of the version on the left.
// don’t do this
wire blah1, blah2, blah3;
// and then later
assign blah1 = …;
assign blah2 = …;
assign blah3 = …;
// do this instead
wire blah1 = …;
wire blah2 = …;
wire blah3 = …;
6