Lab 4: Verilog Modules


This is not a list of tasks for you to do. It is a list of skills you will have or things you will know after you complete the lab.

Following completion of this lab you should be able to:


Your Tasks

0 Update your GIT repository

This lab will be completed individually using the B-solo git repository that you used for Lab3.

  1. In Git Bash (or your favorite git client), navigate to your git repository and do a git pull to sync your repo with github.

  2. Open the Lab4 folder in your repository and make sure there are NUMBER HERE verilog (.v) files.

1 Open Lab4 in Visual Studio Code

  1. If you don't already have VS Code installed, install it using the software center.
  2. Launch VS Code, and when it's open, choose "Open Folder" either from the main window or from the File menu.
    • Navigate to your git repo, and into the Lab4 folder that you identified in the previous step.
    • Select that folder to open.
  3. Open a few of the Verlog files that appear in the file navigator:
    • Register.v
    • tb_Register.v
  4. Right now editing verilog is annoying in VS Code. Install the "Verilog HDL" extension to make it nicer:
    1. Click the "Extensions" button in the Activity bar (far left). It looks like this:
    2. Search for "Verilog HDL" and install one of the first couple of extensions. One is called "Verilog HDL" (green blob shape), and another is called "Verilog-HDL/SystemVerilog/Bluespec System" (FPGA microchip logo). We've had good luck with the first one (green blob, "Verilog HDL") and recommend that.
  5. Navigate back to the verilog files you opened and make sure syntax coloring works.

2 Set up a ModelSim project

  1. Configure ModelSim for better debugging.

    • In the folder C:\intelFPGA_lite\18.1\modelsim_ase\, rename modelsim.ini to modelsim.ini.bak.
    • Download and save this copy of modelsim.ini to your C:\intelFPGA_lite\18.1\modelsim_ase folder. This enables a couple of options that will help mark failed tests on the waveform.
  2. Set up the ModelSim project.

    1. Start ModelSim.
    2. Create a new project for this lab
      • Choose New > New Project from the File menu.
      • Make sure the project location is inside the Lab4 folder in your git repo.
      • Name the project Lab4.
    3. When prompted, choose Add Existing Files to Project
      • Add all of the .v files from your individual repo (Lab4 folder) to the project.
    4. Once ModelSim has your project open and you've added all the verilog files, make sure you can compile them. Choose Compile All from the Compile menu or click the "Compile All" button in the toolbar().
      • Note: there will be a compile error! See if you can find and fix the error. While you can edit files in ModelSim, we strongly recommend you use VS Code instead because it is more powerful.
    5. Keep ModelSim open, we'll use it again soon.
  3. Exploring the tb_Register.v test bench.

    Your instructors have created a very basic unit test framework for you to use in this course called vunit. In order to use it in a verilog test bench, you make an instance of the vunit module, then execute various tasks within that instance.

    1. Open tb_Register.v in VS Code.

      • Inside the tb_Register module near the top, there are a lot of instances declared. Many are the reg drivers for the inputs (we'll put data into these to drive the hardware we want to test). There's also one wire instance data_out, which is a probe we will connect to the output of our Register unit under test so we can read its output.
      • There's also, vunit VU();. This creates an instance of the test framework so we can use vunit tasks.
      • Finally, you'll see the declaration of UUT, which is an instance of the Register module.

      These are the main components "on" our test bench. The rest of the code in tb_Register is the testing procedure.

    2. Scroll past the three tasks declared until you get to the initial block at the bottom. It has a comment at the top that says "Run the tests". This is the main driver for our test bench.

      • This has to be down at the bottom so the tasks it refers to can be found. It's annoying, sorry.
      • There are three tests that run: one to test that reset works, one to test that write-enable works, and then finally a third test that re-checks reset again.
      • But notice the second test is commented out! Remove the comment so it will run.
    3. If you scroll up to the tasks you passed before, you can see each one (when it runs) will execute a specific test. Creating tasks for each test is a nice way to break up your tests so you can quickly turn them on or off by commenting them out where they get called.

  4. Running tb_Register.

    Try running tb_Register. You can do this from inside your ModelSim project.

    1. First, make sure everything is compiled.
    2. Next, click the "Library" Tab in the project view:
    3. Then expand the work library and double-click tb_Register to start the simulation.
      • NOTE: if tb_Register isn't in the work library, click back to the "Project" tab, then try closing the project (in the File menu) and then open your Lab4 project file again
    4. Once the simulation view is open, run the entire test bench using run -all from the Simulate > Run menu. When the simulation completes, you should see a bunch of output in the transcript (bottom of the screen).
  5. Debugging tb_Register.

    You'll quickly notice that some tests are failing. The transcript output alone is not always useful. We want to monitor signals on our test bench as they change, so we need to set up a waveform.

    1. Make sure tb_Register is selected in the sim view, then select all of the signals in the Objects panel and drag them over to the waveform window to add them:

    2. Restart the simulation (Restart from the Simulate menu)

    3. Execute run -all again. If you like buttons instead of menus, look for this button: . (The restart button is just to the left of the text box in that image. Hover over the buttons to see what they all do.)

      Now you should see waves in the Wave panel:

    4. At the top of the waves, you'll see some inverted triangles. These indicate the times when messages were posted to the Transcript. The green arrow are for "info" (the one on the left in the image below) and the red one (on the right in the image below) corresponds to an error. Double-click a red triangle to see what happens.

    5. Use the signals on the waveform and read through the transcript output and try to identify which asserts in tb_Register.v failed. HINT: only one assert fails, but it gets executed twice by the test bench.

    6. Fix the broken assert so that it tests the right thing.

    7. Restart the simulation (Restart from the Simulate menu) and execute run -all again.

    8. Verify that the red triangles and "ERROR" messages in the transcript are gone before moving on.

    9. Save your waveform config so you can quickly reuse it later if you want:

      • Click (left-click, not right-click) in the middle of the waves.
      • Choose "Save Format" or type Control-S
      • Click the "Browse..." button.
      • In the dialog that pops up, change the name of the file from to
      • Click "Save", then "OK".
    10. You can later reload this by going to "File > Load > Macro File..." and selecting your .do file while a simulation is running.

Once you've fixed the errors in tb_Register.v, you should save your changes. Add, commit, and push the following files to your git repository. Be sure to include a message that indicates your current progress.

DO NOT add all the temporary files made by ModelSim. It'll clutter your git repo and may cause problems down the road. git commit -a will do this, so don't use -a. In VS Code, you must choose which files to stage or it will add them all by default. Do the extra step to pick which files to commit: it will save you time in the future.

3 Create and Test ImmGen

In class we talked about the Immediate Generator that looks at the bits of an instruction to figure out how to construct an immediate.

We've provided you with an ImmGen.v file that has the start of the immediate generator, but you must finish implementing it. We're also providing you with a test bench tb_ImmGen.v that has a few tests, but you need to write more tests.

  1. If your simulation of tb_Register is still running, end that (Simulate > End Simulation menu item).
  2. Implement one type of instruction at a time in the ImmGen.
  3. For each instruction type, be sure there are at least two tests in tb_ImmGen.v to test that type of instruction. We've given you a couple for the first type of instruction (I-Types).

Suggestions and hints:

Add, commit, and push the following files to your git repository. Be sure to include a message that indicates your current progress.

4 Inspect and test Memory

Next, you will see how RAM is constructed and pre-populated with values (such as your machine code) in ModelSim.

  1. In VS code, open up SP_Memory.v. This is a basic RAM module that was autogenerated by Quartus. It is word-addressed (not byte-addressed), and each address is 10 bits long (not 32). This physical module is much smaller than a 32-bit processor supports, so we will have to engineer around that in the future.

    • There is very little actual code in this file, it is mostly comments.
    • The comments explain how to create an intialization file. You can read them now or later.
    • the ram variable is a big array of words.
    • Near the bottom of the file is an always @(posedge clk) block: this is where memory is written.
    • Below that always block is an assign; this is a continuous connection that sets the output port value to whatever is at the currently provided address. This means that the output should change very quickly, and asynchronously (not with the clock).
  2. Open DP_Memory.v in VS Code next.

    • This is a version of memory that has two sets of inputs, two sets of outputs, and two clock inputs.
    • Effectively it is two memories that share contents.
    • Notice that it looks very similar to SP_Memory except:
      • This version has two always @ blocks that look at different clocks.
      • This version also employs synchronous reads (only on the clock edge). This means the output values will only change on the clock edge, regardless of the other inputs.
  3. Now, open up tb_memory.v. This is a test bench for both memories.

    1. The structure of this test bench is similar to the one for the register, but there are two different modules instantiated. One is called sp_ram and one is called dp_ram.
    2. Skim through the first task, test_singleport_memory() to get familiar with what it is testing.
    3. Your next job is to run the test in ModelSim. Compile and start the simulation like you did for tb_Register, but this time start tb_memory.
      • When you run -all, you should see in the transcript that tests completed and none failed.
  4. Next you should configure a waveform to help you visualize both memory instances.

    1. Start by adding the signal CLK to your waveform.
    2. Next, select all the signals in the tb_memory instance that start with sp_ (shift-click or control-click to select many). Then drag them to the wave view or type Control-W to add them.
    3. When they appear in the wave panel, they will all be selected. With all of them selected, right click one of them and choose Group.... Name the group "Single Port RAM".
    4. Repeat for the dual-port connections (they start with dp_).
    5. Restart and run your simulation again. Now you should have pretty waves!
      • At this point, you may want to change some of the values on the waveform to use hexadecimal radix so they're a little easier to read. Right click them and select from the "Radix" menu to change how they are interpreted.
    6. Save your waveform as
    7. In the lab worksheet, there is a waveform that is incomplete. Draw what you expect to come out given the inputs provided (you are adding the missing parts of the dp_q_a and dp_q_b signals). Remember that dual port memory has two clocks, and port B is activated on the falling edge of the other clock. Draw the missing signals to show what you expect the outputs to change (and write in their new values).
  5. Currently the test_dualport_bothports task is commented out in the intial block at the bottom of the test bench. This means it doesn't run!

    1. Uncomment the two lines so that test_dualport_bothports task runs after the $info task.
    2. Restart and run the tb_memory tests and note that there will be some failures. This is because the tests are incomplete!
    3. Edit the test_dualport_bothports task to complete part 2, verifying that your waveform is correct. You can use the waveform you completed in the lab worksheet as a guide.
    4. Keep in mind that the SP_memory tests are running before the ones for DP_memory, make sure you're looking at the right parts of the waveform as you debug!

Add, commit, and push the following files to your git repository. Be sure to include a message that indicates your current progress.

5 Create RegFile and tests

Okay, you're mostly on your own for this one! You must create a register file and test it. We'll give you the specifications, and you can choose how you want to implement and test it, but you MUST test all features of the register file.

  1. First, create a RegFile.v file and declare a RegFile module in that file.

    • Your register file should contain 32 general purpose registers, with one exception: the zero register should always be equal to zero.
    • It will have the following inputs:
      • regnum_a - a five-bit register specifier to read, usually this will be "rs1"
      • regnum_b - a five-bit register specifier to read, usually this will be "rs2"
      • write_regnum - a five-bit register specifier of which register to write ("rd", for example)
      • data_in - a 32-bit value to write into write_regnum
      • write_enable - a one-bit value that indicates whether or not to write data to write_regnum
      • reset - a one-bit value that indicates whether or not to reset the register file's contents
      • CLK - a one-bit clock signal
    • It will also have two outputs
      • regdata_a - a 32-bit value that was read from regnum_a
      • regdata_b - a 32-bit value that was read from regnum_a
    • On the rising edge (posedge) of the clock, the register file will write from data_in into the register specified by write_regnum, but only if write_enable is set to 1.
      • except if write_regnum is 0; register 0 should never change.
    • If reset is set to 1 at the rising edge, all registers should be set to zero except for register 2, which should be set to 0xfffffff0.
    • regdata_a and regdata_b continuously update and read the registers specified by regnum_a and regnum_b respectively. (This is an async read).

    Here's a picture of what you're building:

    Here are some suggestions and hints:

    • You can create the registers inside your regfile either as native reg data types, or using the Register module from this lab.
      • If you choose to use the Register module, consider using a generate loop or an ["array of instances"]() approach.
      • if you choose to use the reg primitive type, see the Resources section of this web site for help.
  2. Write tests to sufficiently test all functionality of your register file.

  3. To run your testbench in ModelSim you'll need to add the new files to your ModelSim project.

    • In the Project tab (where the files are listed and the green checkmarks for compiling are visible) right click and select "Add to Project > Existing File..." then select your RegFile.v and tb_RegFile.v to the project.
    • After adding them you can compile and run the test bench the same way you have the others.
  4. On the lab worksheet, explain how you chose which tests to write and how you know the component is fully tested. Do this below in the "Turn It In" section for general rubric item 3 ("tests for correctness").

Add, commit, and push the following files to your git repository. Be sure to include a message that indicates your current progress.

Turn It In

Grading Rubric

General Requirements for all Labs:

  1. fits the need
  2. discuss performance
  3. tests for correctness
  4. iteration and documentation

Fill out the Lab Worksheet

In the worksheet, explain how you satisfy each of these "grading rubric" items 1-4. Some guidelines:

  1. Submit your completed Lab Worksheet to gradescope.

  2. Lab code will be submitted to your B git repository as new files and committed modifications to the repo we provided you. You must include your name in a comment at the top of all files you submit.

    1. Open a terminal window (like git bash) and navigate to your B repository folder.
    2. type git status to see files that have changed since your last commit.
    3. If any verilog or .do files have changed, git add them, then commit them with git commit -m "message here" (replace "message here" with information about what you're committing and why).
    4. Send any changes to the server with git push
  3. Verify that your changes were pushed correctly:

    1. View your B repo on the github classroom website. You should see the files
    2. If the files on your github web page do not match the files you think you completed, ask an instructor or TA for help figuring out why your files did not get sumbitted.