Made a couple changes to the IO module. Please take a look if you've begun work on it.
Removed the IO module all together, updated the Datapath PDF, updated Verilog interfaces to reflect new Datapath PDF, and updated Register File diagram. I would just be sure that everything that you've completed thus far is consistent with what's written here.
Due: April 15
In this lab, you will implement a datapath for a really simple processor that implements a very simple instruction set. It is so simple, in fact, that it does not even have a branch instruction, so it cannot execute most programs. The processor does, however, have all the parts that a real processor has: A fetch unit, decode logic, functional units, a register file, IO support, and access to memory.
This lab is mostly about learning the skills you will need in the labs 4-6, and we structured it so that you can learn from each other. 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.
Once you have mastered the skills here, you will go and apply them creatively to implementing your processor in labs 4 and 5.
NOTE: This lab can be performed in groups of two or three, but your groups must remain consistent throughout the duration of Labs 2 and 3. On that same note, we encourage groups to help each other out (especially with tool problems).
Please remember that you must conform to the class coding standards. They are available here. If you find a bug in them (e.g., they are causing you to do something horribly ugly), please let the professor or one of the TAs know.
The greatest common divisor example is available here. Create a new project and import all the files. If you simulate using gdb_tbw.tbw, it will run a short test. This example demonstrates the coding standards for the datapath and the modules therein.
resetsignals should be asynchronous.
We'll start by implementing the top-level schematic for this lab: datapath.v.
module datapath ( // All of these signals originate from the control section of the processor // Controls the operation of the processor, determining if it should be stalled, reset or running input [1:0] run_stall_reset, // Tells the register file that we want to write this cycle input write_en_regfile, // Tells the dmem wrapper if we're going to read or write this cycle input read_write_req_dmem, // Selects from where the register file should derive its write input input [1:0] reg_sel, // Signals DRAM that it should write at the end of this cycle input write_en_dmem, // Tells the ALU which operation (ADD, SUB, MULT) it should perform this cycle input [1:0] op_code, // This signal allows the control section of the processor access to the current instruction output [15:0] inst, // Global clock input clk, // Global reset input reset, // All of these signals come from or go to a module external to the processor // These signals are described in the IO section input [7:0] in_data, // From IO Device output [7:0] out_data // To IO Device );
This is the top-level interface for your datapath. You should use this to connect to the control portion of your processor and to the outer processor interface. You should just instantiate and connect all of the inner pieces that you'll be designing below in this module.
Question 1: Using the definitions of the different modules below, go ahead and construct our top-level verilog file (datapath.v).
// Parameters: // INST_WIDTH -> Width of an instruction // LOG_NUM_REGS -> Log-base-2 of the number of registers (number of bits needed to identify a register) // IMM_WIDTH -> Width of an immediate module decode#(parameter INST_WIDTH = 16, LOG_NUM_REGS = 2, IMM_WIDTH = 8) ( // Incoming instruction from the Imem input [INST_WIDTH - 1 : 0] inst, // The encoded register bits (going to the register file) output [LOG_NUM_REGS - 1 : 0] r1, output [LOG_NUM_REGS - 1 : 0] r2, // The immediate (possibly ending up at the register file) output [IMM_WIDTH - 1 : 0] imm );
The general ISA format is as follows:
<4 bit opcode><2 bit R1><2 bit R2><8 bit Immediate>
With this in mind, there will be 9 different instructions and 4 different registers to choose from:
|Opcode||Instruction Format||Instruction Definition|
|1||ADD R1 R2||R1 = R1 + R2|
|2||SUB R1 R2||R1 = R1 - R2|
|3||MULT R1 R2||R1 = R1 * R2|
|4||LD R1 R2||R1 = MEM[R2]|
|5||ST R1 R2||MEM[R2] = R1|
|6||LI R1 <Immediate>||R1 = <Immediate>|
|7||READ R1||R1 = Input from IO|
|8||WRITE R1||Output to IO = R1|
|9||HALT||End of execution|
The decode module should just correctly parse the operands from each instruction and feed them into their specific modules.
Question 2: Design the decode.v file. It should handle each case listed in the table above.
// Parameters: // WIDTH -> Width of the incoming data (via the register file) module alu#(parameter WIDTH = 8) ( // Incoming inputs from the register file input [WIDTH - 1 : 0] v1, input [WIDTH - 1 : 0] v2, // Opcode of the operation to perform (derived from the ISA) input [1 : 0] op_code, // Result of the given operation output reg [WIDTH - 1 : 0] result );
The ALU for this processor is fairly straightforward, as it only needs to implement three separate instructions: ADD, SUB, and MULT. Using the opcodes and other ISA information listed in the Decode section, go ahead and design this module.
Question 3: Implement alu.v using the table in the Decode section and the information above.
There are two memory modules to be generated for this processor. One will be the instruction memory(
imem), which will be a ROM, and the other
will be a data memory (
dmem), which will be a RAM. You should use the provided verilog wrapper to access the
those of you unfamiliar to wrappers, it will just act as a method for us to access the
dmem. The wrapper is located
here. The wrapper is what you should be using throughout your code, and not the RAM module you are
about to generate. Also, please do not change the contents of the wrapper. If you find it not working properly, please notify one of the TAs
through email or the webboard.
Here's the interface for instantiating and communicating with a
module dmem#(parameter A_WIDTH = 8, D_WIDTH = 8) ( // Global reset input reset, // Global clock input clk, // Used to specify either a read or a write // request. // 1 -> read/write request // 0 -> no requests input read_write_req, // Assert for writing (write enable) input write_en, // Address for data we'd like to retrieve input [A_WIDTH - 1 : 0] addr, // Data input for writing input [D_WIDTH - 1 : 0] din, // Data output for reading output [D_WIDTH - 1 : 0] dout, // Don't worry about this output output refused );
Interfacing with the
dmem should be fairly straightforward. All signals are edge-triggered.
Here's a basic overview of how each operation works:
read_write_reqmust be asserted (
1) and the
addrbus must be set to the address we would like from the RAM. Your data should be available on the
doutbus on the next edge.
write_enmust be asserted and the
addrbus must be set to the address we would like to write to in the RAM.
Question 4: Go through the following tutorial to generate both the
Be on the lookout for separate instructions regarding each module. In your report, please generate
a diagram showing the interface you'll be using for the two modules (
NOTE: The Xilinx Core Generator may not function correctly in Linux. Our experiences have yielded errors late in the generation verison. You can either brave it in Linux, or you can use the Windows version (in the lab, if need be). If you've had success with the Core Generator in Linux, please let one of the TAs know.
dmemmodule, as the wrapper depends on it being consistent. Click next at this point.
dmemOnly) Be sure that "Single Port RAM" is selected and then click next.
imemOnly) Select "Single Port ROM" and then click next.
dmemOnly) Set "Write Width" to 8 and "Write Depth" to 256, meaning that you are generating a 8-bit wide, 256-entry memory module.
imemOnly) Set "Write Width" to 16 and "Write Depth" to 8192, meaning that you are generating a 16-bit wide, 8k-entry memory module.
imemOnly) Check "Load Init File", and set "Coe File" to the name of the coe file you want to initialize the memory with. This dictates in what state the memory will begin operation in.
// Parameters: // WIDTH -> Width of the data stored in each register // LOG_NUM_REGS -> Log-base-2 of the number of registers (number of bits needed to specify a register) module reg_file#(parameter WIDTH = 8, LOG_NUM_REGS = 2) ( // Global clock input clk, // Global reset input reset, // Encoded register inputs input [LOG_NUM_REGS - 1 : 0] r1, input [LOG_NUM_REGS - 1 : 0] r2, // Data to be written on the next clock cycle input [WIDTH - 1 : 0] r_data, // Should we write this cycle? input write_en, // The asynchronous outputs for register reads (indexed via the inputs) output [WIDTH - 1 : 0] v1, output [WIDTH - 1 : 0] v2 );
The above figure shows the basic structure of our register file. From this figure, it can be inferred that a write is edge-triggered,
while a read is level-triggered. The implication of this is that if
write_en is asserted before or on a given clock edge, the data at
r_data will not be stored into the register specified by
r1 until the next clock edge. However, if either
r2 is asserted between clock edges, we would expect the output to appear at
v2 within the same clock cycle.
The following waveform demonstrates this concept:
Question 5: Implement reg_file.v noting the information above.
|Due: April 15|