CSE141L Lab 2: Single cycle datapath!
Due August 16th (5:00pm)
Overview
In this lab, you will implement a very simple processor that supports a subset of the MIPS ISA. The processor does not support a branch instruction, so it cannot execute most programs. However, the processor has most parts that a real processor has: A fetch unit, decode logic, functional units, a register file, IO support and access to memory.
All of you will be implementing the same datapath, so you will all run into similar problems. Learn from your classmates, and then go apply what you learn to your own design.
NOTE: You should be work in groups of two or three for this lab. We encourage groups to help each other out (especially with tool problems).
To make your code easier to read, please follow the guideline here.
Getting Started
General Notes
As in Lab 1, please use the "Cyclone IV E EP4CE40F29C6" as the target device.
In order to be consistent with good coding practices in Verilog, we require that each student design in a modular fashion. What we mean by this is that each module in the datapath diagram have all of the "logic" for that block within it and any larger modules (e.g. the entire datapath) should consist of only instantiations and interconnections. For a complete discussion of required coding standards for this class go here: 141L Verilog Coding Standards.
All Verilog used in these labs should be synthesizable.
IMPORTANT: It is recommended that you read through the entire lab then begin your implementation from the beginning. Not doing so may make this lab more difficult than it needs to be.
There's a good verilog tutorial here (skip the registration) that covers the basics of writing verilog up through the more advanced topics. We've also put together a guide that tells you which parts of the tutorial you can skip - here on the wiki.
Single-Cycle MIPS Datapath Implementation

In this lab, you are going to build the above schematic. You will implement some of these modules and wire up all of the components to match this schematic. Start by making a new project in Quartus II (targeting the Cyclone IV E EP4CE40F29C6, as we did in the previous lab). In this lab, we provide the Instruction Rom, Data Memory and ALU. We also provide a basic test bench that you can use later to run some simple programs. You can get these files here: processor components. Download the files and add inst_rom.v, alu.v, data_mem.v to your project. Also add async_memory.v and serial_buf.v to your project - these are modules that data_mem.v depends on.
It's your work now to create the top-level file of datapath for this lab: processor.v.
Here are the port definitions for processor.v.
NOTE: Your processor needs to use the exact same module name and port names as specified here or your processor may not work in our testing framework.
![]() |
module processor( input clock, input reset, //these ports are used for serial IO and //must be wired up to the data_memory module input [7:0] serial_in, input serial_valid_in, input serial_ready_in, output [7:0] serial_out, output serial_rden_out, output serial_wren_out ); |
Provided Component Interfaces
The instruction rom provides a read-only memory which your processor will get its instructions from. Our version is slightly different than what you will find in the textbook, because we require that the address input be connected to PC_next (the input of the PC register), instead of PC (the output of the PC register). We will use the parameter in a later lab so you can specify what application you want to run.
![]() |
module inst_rom( input clock, input reset, input [31:0] addr_in, //Connect to PC_next output [31:0] data_out //Fetched instruction ); parameter INIT_PROGRAM; //we will use this parameter to specify a file to preload into the instruction rom. |
The data memory module provides a small amount of RAM for your processor to read and write to (4KB for the stack, and 4KB for everything else). The data_memory module also allows your processor to interface with I/O devices. It currently connects to a serial port so you can get some output from programs that you execute. If you're interested, take a look at the module source code and have a look in the textbook about interfacing I/O devices with processors.
Our data_memory module has more ports than the textbook, specifically we have a size_in input and the serial_* inputs and outputs. The size_in input will be used in a later lab and should be tied to a value of 2'b11 (3 in decimal) instead of attaching a wire to it. The serial_* wires need to be wired to the ports on the processor module, so that the testbench has access to them. As on the inst_rom module, the parameters will be used to specify a program to run. We'll cover that more when the time comes.
Why does the data memory have ports for a serial port? An excellent question, and one that we'll discuss later in 141. If your curious now, take a look ate Section B.8, and then take a look at the insides of the data_memory module.
![]() |
module data_memory( input clock, input reset, input [31:0] addr_in, //Read/Write address input [31:0] writedata_in, //Data to write to memory input re_in, //Read Enable - set high when reading from memory input we_in, //Write Enable - set high when writing to memory output [31:0] readdata_out, //Data output for reads from memory input [1:0] size_in, //Not used yet - hardwire to 2'b11 //these are used to let your processor print things to a serial port (e.g. "Hello World") //wire them up to the same ports in your processor module so they can talk to the test bench input [7:0] serial_in, input serial_ready_in, input serial_valid_in, output [7:0] serial_out, output serial_rden_out, output serial_wren_out ); parameter INIT_PROGRAM0; //we will use these 4 parameters to preload data into RAM parameter INIT_PROGRAM1; parameter INIT_PROGRAM2; parameter INIT_PROGRAM3; |
The final module we provide is an ALU. The alu is responsible for doing all of the calculations in your processor. This one supports more operations that the ALU described in the book and has slightly different inputs and outputs.
![]() |
module alu( input [5:0] Func_in, input [31:0] A_in, input [31:0] B_in, output [31:0] O_out, output Branch_out, output Jump_out ); |
Because our ALU has different inputs and outputs, here's a list of operations it can perform and what value of Func_in each corresponds to. You don't need this information now, but it will be helpful in later labs. Its included here for completeness.
| Func_in (X=don't care) | Operation | O_out value | Branch_out | Jump_out |
| 10000X | ADD | A+B | 0 | 0 |
| 10001X | SUB | A-B | 0 | 0 |
| 100100 | AND | A AND B | 0 | 0 |
| 100101 | OR | A OR B | 0 | 0 |
| 100110 | XOR | A XOR B | 0 | 0 |
| 100111 | NOR | A NOR B | 0 | 0 |
| 101XX0 | Set-Less-Than Signed | (signed(A) < signed(B)) | 0 | 0 |
| 101XX1 | Set-Less-Than Unsigned | (A < B) | 0 | 0 |
| 111000 | Branch Less Than Zero | A | (A < 0) | 0 |
| 111001 | Branch Greater Than or Equal to Zero | A | (A >= 0) | 0 |
| 11101X | Jump | A | 0 | 1 |
| 111100 | Branch Equal | A | (A == B) | 0 |
| 111101 | Branch Not Equal | A | (A != B) | 0 |
| 111110 | Branch Less Than or Equal to Zero | A | (A <= 0) | 0 |
| 111111 | Branch Greater Than Zero | A | (A > 0) | 0 |
| 000000 | Shift Left Logical | A | 0 | 0 |
| 000011 | Shift Right Arithmetic | A | 0 | 0 |
Additional Components
Now its your turn to code up the remaining components that you need to complete the schematic above.
You will need the following components:
A register file
A Program Counter (PC) register
A 2-input mux parameterized to support multiple widths
An adder
A sign-extender
The Register File
The MIPS ISA defines 32 32-bit general purpose registers (GPR) that most intructions read and write data from and to. The first component you need to build is that collection of registers, called the register file. As you can see from the datapath schematic, the register file has two Read Address ports and two Read Data ports. This means we need to be able to read two values at once. There is also a Write Address port, a Write Data port, and a Write Enable port. These are used for writing the result from completed instructions back into the register file.
Here is a list of requirements for the register file:
Must be able to read two values simulataneously, and asynchronously (no clock required for reading).
Writes must happen only on the positive clock edge and only when write enable is asserted.
Correctly handles reads and writes from register zero.
After you finished this lab
You need to answer the following questions and give us a copy of your code during the interview.
Question 1: Show us your register file works!
Code up your register file. Write a test-bench to make sure your register file behaves as expected. Check that register zero behaves as expected by the MIPS ISA. Show us the waveform of your register file in action,
showing all of the inputs and outputs of your register file.
Now we will code up the remaining modules. These are pretty basic and you can get an idea of what each piece does from the schematic and the textbook. Here are a few notes about some of the modules to help you out:
The Program Counter (PC) is just a regular 32-bit register. As with all registers, make sure you give it a reset value - or even a parameterized reset value - with a default of 32'h00400000.
Don't waste time writing two different muxes, make a single one with parameterized width
Remember that sign extending means to replicate the top bit of the input value.
Question 2: Putting it All Together
Now that we have all of the components we need to create the datapath, go ahead and instantiate all of the modules inside your processor.v top-level file. Declare all of the wires that you need (use descriptive names to help your Make sure you connect the serial_* ports of the data_memory module to the same ports on your processor module. Make sure you get the correct bit-widths for each wire. Since we don't have the control signals yet you won't be able to test your processor. After the next step you should be able to run the Quartus sythensis without any errors and without any of the "worrisome" warnings listed on the wiki. We'll count on your component test benches catching the logic bugs for now. The rest of the bugs will show up in Lab 3.
To make quartus happy, we need to specify a program to load into the instruction rom. This file: blank.memh (yes, it should be an empty, zero-length file) will be used as the "program" and "data" files for this lab and loads all "NOP" instructions into the instruction rom.
To "load" this program into your processor we'll change some parameters on the provided init_rom and data_memory modules. First, download the file somewhere where there are no spaces in the file name and remember the full path to the files. On the inst_rom module add the following parameter: INIT_PROGRAM. You should set the value of this parameter to the full path for the blank.memh file. For example:
inst_rom #(
.INIT_PROGRAM("c:/myfiles/blank.memh")
) myInstructionRom (
...
);
Similarly, set the INIT_PROGRAM0, INIT_PROGRAM1, INIT_PROGRAM2, INIT_PROGRAM3 parameters on the data_memory module. All of them should be set to point to the blank.memh file.
Question 3: Show us the schematic.
Show us the schematic for processor.v. Include the names and bit-widths of the wires, and the names of the modules.
For the next lab we'll be adding in all of the control signals and the additional modules that they will need. If you want to get a head start, create a control module and wire it up to the remaining ports on the modules you just created. We'll go over what goes in that module, and the additional muxes and modules you'll need for the control paths in the next lab.