Lab 7 - Memory and Control

1 Objectives

Upon completion of this lab you should be able to create and test a design using verilog. You will also see a working memory module and be able to connect and test combined components in verilog.

2 General

This lab will guide you through building and testing instruction memory and a small control unit. We will follow the "Top-down design; bottom-up implementation" approach.

This lab is completed by your project group. You only need to have 1 or 2 people work on this lab those people will become your team's "memory experts". They are expected to read the FULL "resources" page on the course website, it will make this lab (and the project) much easier.

The earlier you do this lab the better, delaying it until the due date leads to many debugging issues later with your design.

You will use verilog to create a testbench that simulates the PC, supplying addresses to a memory unit. A memory unit representing instruction memory will responds to input addresses, outputting instructions. Finally, a control unit receives the instruction and sets control bits appropriately. In this exercise, only the opcode bits of the instruction are needed; the remaining bits can be 0.

Verilog
testbench
>Address>
Memory
unit
>Instruction>
Control
unit
>Control bits>
Simulation

3 Building Block Memory in Verilog

In this section, you will build a block memory module.

  1. Start Quartus
  2. Set up a new project, create a new folder for the working directory called "Lab 7", name the project "Lab7". Finish the project set up like you did in Lab 4.
  3. Go to New > File > Verilog HDL File and select "OK"
  4. A blank file should open, go to Edit > Insert Template > Verilog HDL > Full designs > RAM > Single port RAM then select "Insert", then "Close".
  5. The file should have been populated with some verilog to make a generic memory. Save the file as "memory.v"
  6. Familiarize yourself with the code:
    1. The line starting with "#(parameter..." specifies how big your words are and how big the addresses are.
    2. Line 14 defines the "ram" variable, this is defining an array of "reg" variables of the correct size. This is the array that is memory. if we access index 0002 in this array, we get the data stored at address number 0002.
    3. The always block starting at line 19 defines the behavior of the memory module, this one reads and writes both on the positive edge.
    4. The assign statement on line 31 sets the output for memory, this memory always outputs the data based on the input address. No "MemRead" signal is used here.
  7. We need to change some parameters to match our needs for this lab, change the following values:
    1. rename the module from 'single_port_ram' to 'memory'.
    2. DATA_WIDTH = 16
    3. ADDR_WIDTH = 10
    4. In the definition for ram change it to ram[0:2**ADDR_WIDTH-1] -- note we just moved the "0:" to the front of the definition and removed it from the end.
  8. Now we need to add in some code to read in the initial data in memory, before the generated always block, online 18 add the following code:
    initial begin
        $readmemh("memory.txt", ram);
    end
  9. We need to create the "memory.txt" file that we just used in that code. Go to: File > New > Text File then press OK. Add in this content to the text file, then save it as "memory.txt":
    1234
    1337
    dead
    beef

At this point your memory is complete, now we just need to test it.

4 Testing Your Memory

Create a newverilog file to act as a test bench.

  1. File > New > Verilog HDL then press OK. Save the file as "tb_memory.v"

  2. You should set up the test bench for this file just like you did in Lab 4.

    1. Look at the inputs to the module you generated for memory to set this up correctly. You will need to initialize a 'memory' module.

    2. You should set up one always block to run the clock.

    3. You should set up one initial block to run your tests. The test code will be very simple, for now you can just use something like this code:

           initial begin
               clk = 0;
               addr = 'h000;
               din = 'h1111;
      
               #PERIOD;
               addr = 'h001;
           end

      If you have trouble getting the syntax right you can look at this testbench here. I strongly suggest you try to set it up yourself first before you click this link though.

  3. Run your test:

    1. Set the memory.v file as the Top-Level Entity in Quartus, then Compile the design.
    2. Open up the ModelSim Tools (Tools > Run Simulation Tool > RTL Simulation)
    3. Set up a new project and create a waveform that shows all the inputs and outputs to your memory module. Check Lab 4 for specifics if you need them. Recall, that if you cant see the testbench file in the things to simulate you may need to close the ModelSim project and reopen it (the file ends in ".mpf").
    4. Make sure you add the "memory.txt" file to the project. You may also need to copy the .txt file into the "./simulation/modelsim" directory, any time you update the "memory.txt" file you will need to manually re-copy it to this location. (Yes, this sucks, if you find a better solution let me know!)
    5. Run your simulation, you should see the input address change, and the dout value change on the next rising edge.
    6. Note the "Memory List" tab in the left pane, undernead the file list. Go ot this tab, you should see one item, double click it to open. You will see the memory you generates, showing data in the first 4 words in memory, and X's for all the uninitialized words. You can use this for debugging purposes later.

5 Control

Download BlockMemoryControl.v and add it to your project. Modify BlockMemoryControl.v file so that it sets the correct controls (RegDst, RegWrite, MemRead, MemWrite, Branch, ALUSrc, MemToReg) for four main instruction types in RISC-V (R-types, lw, sw, beq). For example, if the opcode is 0x23 the output should be ALUSrc=1, MemtoReg=X (any value), RegWrite=0, MemRead=0, MemWrite=1, Branch=0, ALUOp=00.

See figure 4.26 in your textbook for what signals to set for various opcodes. Note: the sample code is for MIPS and you may want to change the outputs or inputs to the control unit to match RISC-V.

Similar to your memory unit, you will need to unit test for the control unit. Write a testbench to verify your control unit works properly.

6 Connecting the Two Components

  1. Create a new verilog file called "connected_control_memory.v", this will be a combined module with these inputs:
    1. CLK
    2. PC
    3. datain

And one output for each control signal.

  1. Set up the module to instantiate a control and a memory and then hook them up to each other. (You can use the Lab 4 counters as an example for this.)

  2. Edit the memory.txt file to have one instruction for each instruction type (R-type, lw, sw, beq).

  3. Write a test bench for this new module that tests that the correct control signals are output for each instruction.

7 Turning It In

Demonstrate your working memory and control module to your instructor. There is no submission to gradescope for this lab.

Only one lab should be submitted for each project team. Make sure all team member's names are included on your submission.