CSE141L Lab 2: Single cycle datapath!

Due July 7th (11:00pm)


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

Single-Cycle MIPS Datapath Implementation

The datapath schematic for Lab 2.

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.

The processor module:
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.

The inst_rom module:
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.

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.

The alu module:
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

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:

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:

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:

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 #(
) 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.