Practical 4: Verilog Modules
Objectives
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:
- Implement hardware modules in Verilog.
- Test hardware modules using verilog test benches (functional testing) in ModelSim.
- Use a basic unit test framework when writing tests.
- Read and interpret simulated waveforms.
- Debug and modify hardware modules using waveforms and other debugging tools.
Guidelines
-
During this lab, ModelSim will create lots of temmporary files. Do not commit every file created to git. Only commit source and project files.
-
At a few points during this lab, we will directly instruct you to add, commit, and push files to git. You are encouraged to do that more often than we tell you to show "iteration" on your worksheet. (See "Turning it In" section at the bottom of this page for details.)
-
As you write code, especially test bench code, use comments to explain the details of any complicated operations or reasons for the code you wrote. Documenting your code is important so others can understand it!
Your Tasks
0 Update your GIT repository
This practical will be completed individually using the B-solo
git repository that you used for Practical3.
-
In Git Bash (or your favorite git client), navigate to your git repository and do a
git pull
to sync your repo with github. -
Open the
Practical4
folder in your repository and make sure there are NUMBER HERE verilog (.v
) files.
1 Open Practical4
in Visual Studio Code
- If you don't already have VS Code installed, install it using the software center.
- 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
Practical4
folder that you identified in the previous step. - Select that folder to open.
- Navigate to your git repo, and into the
- Open a few of the Verlog files that appear in the file navigator:
Register.v
tb_Register.v
- Right now editing verilog is annoying in VS Code. Install the "Verilog HDL" extension to make it nicer:
- Click the "Extensions" button in the Activity bar (far left). It looks like this:
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.
- Click the "Extensions" button in the Activity bar (far left). It looks like this:
- Navigate back to the verilog files you opened and make sure syntax coloring works.
2 Set up a ModelSim project
-
Configure ModelSim for better debugging.
- In the folder
C:\intelFPGA_lite\18.1\modelsim_ase\
, renamemodelsim.ini
tomodelsim.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.
- In the folder
-
Set up the ModelSim project.
- Start ModelSim.
- Create a new project for this lab
- Choose
New > New Project
from theFile
menu. - Make sure the project location is inside the
Practical4
folder in your git repo. - Name the project
Practical4
.
- Choose
- When prompted, choose
Add Existing Files to Project
- Add all of the .v files from your individual repo (Practical4 folder) to the project.
- Once ModelSim has your project open and you've added all the verilog files, make sure you can compile them. Choose
Compile All
from theCompile
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.
- Keep ModelSim open, we'll use it again soon.
- Start ModelSim.
-
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 thevunit
module, then execute various tasks within that instance.-
Open
tb_Register.v
in VS Code.- Inside the
tb_Register
module near the top, there are a lot of instances declared. Many are thereg
drivers for the inputs (we'll put data into these to drive the hardware we want to test). There's also onewire
instancedata_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 theRegister
module.
These are the main components "on" our test bench. The rest of the code in
tb_Register
is the testing procedure. - Inside the
-
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.
-
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.
-
-
Running
tb_Register
.Try running
tb_Register
. You can do this from inside your ModelSim project.- First, make sure everything is compiled.
- Next, click the "Library" Tab in the project view:
- Then expand the
work
library and double-clicktb_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 Practical4 project file again
- NOTE: if
- Once the simulation view is open, run the entire test bench using
run -all
from theSimulate > Run
menu. When the simulation completes, you should see a bunch of output in the transcript (bottom of the screen).
-
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.
-
Make sure
tb_Register
is selected in thesim
view, then select all of the signals in theObjects
panel and drag them over to the waveform window to add them: -
Restart the simulation (
Restart
from theSimulate
menu) -
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: -
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.
-
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. -
Fix the broken assert so that it tests the right thing.
-
Restart the simulation (
Restart
from theSimulate
menu) and executerun -all
again. -
Verify that the red triangles and "ERROR" messages in the transcript are gone before moving on.
-
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
wave.do
totb_Register.do
- Click "Save", then "OK".
-
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.
tb_Register.v
(The edited test)tb_Register.do
(Your waveform config)Practical4.mpf
(Your ModelSim project file)
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.
- If your simulation of
tb_Register
is still running, end that (Simulate > End Simulation
menu item). - Implement one type of instruction at a time in the
ImmGen
. - 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:
- Create a "task" for each type of instruction and put your instructions in there, much like the other test benches in this lab. Then, call the task from the
initial
block at the bottom of the test module. - We've created a list of
parameter
definitions that allow you to use variable names instead of binary opcode values in your case statement. Use those. - If it makes it easier for you, create some wire busses and assign them to various bits of the instruction as shorthand for subfields of an instruction. You must do this outside of any
initial
,task
, oralways
blocks. For example:wire [6:0] opcode; assign opcode = instruction[6:0]; // now you can use "opcode" instead of "instruction[6:0]" to refer to the same bits.
- To concatenate values in verilog, put them in braces separated by commas. For example:
{12'hF00, 12'h123} // evaluates to the 24-bit value, 0xF00123
- You can make copies of bits in verilog (to sign extend, for example) using the replication operator:
{24{opcode[1]}} // evaluates to 24 copies of bit 1 of the opcode bus
Add, commit, and push the following files to your git repository. Be sure to include a message that indicates your current progress.
tb_ImmGen.v
(The edited test bench)tb_ImmGen.do
(if you created a waveform, add and commit it)ImmGen.v
(The edited ImmGen module)Practical4.mpf
(Your updated ModelSim project file)
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.
-
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).
-
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.
- This version has two
-
Now, open up
tb_memory.v
. This is a test bench for both memories.- 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 calleddp_ram
. - Skim through the first task,
test_singleport_memory()
to get familiar with what it is testing. - Your next job is to run the test in ModelSim. Compile and start the simulation like you did for
tb_Register
, but this time starttb_memory
.- When you
run -all
, you should see in the transcript that tests completed and none failed.
- When you
- The structure of this test bench is similar to the one for the register, but there are two different modules instantiated. One is called
-
Next you should configure a waveform to help you visualize both memory instances.
- Start by adding the signal
CLK
to your waveform. - Next, select all the signals in the
tb_memory
instance that start withsp_
(shift-click or control-click to select many). Then drag them to the wave view or type Control-W to add them. - 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". - Repeat for the dual-port connections (they start with
dp_
). - 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.
- Save your waveform as
tb_memory.do
- 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
anddp_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).
- Start by adding the signal
-
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!- Uncomment the two lines so that
test_dualport_bothports
task runs after the$info
task. - Restart and run the
tb_memory
tests and note that there will be some failures. This is because the tests are incomplete! - 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. - Keep in mind that the
SP_memory
tests are running before the ones forDP_memory
, make sure you're looking at the right parts of the waveform as you debug!
- Uncomment the two lines so that
Add, commit, and push the following files to your git repository. Be sure to include a message that indicates your current progress.
tb_memory.v
(The edited test)tb_memory.do
(Your waveform config)Practical4.mpf
(Your updated ModelSim project file)
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.
-
First, create a
RegFile.v
file and declare aRegFile
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 intowrite_regnum
write_enable
- a one-bit value that indicates whether or not to write data towrite_regnum
reset
- a one-bit value that indicates whether or not to reset the register file's contentsCLK
- a one-bit clock signal
- It will also have two outputs
regdata_a
- a 32-bit value that was read fromregnum_a
regdata_b
- a 32-bit value that was read fromregnum_a
- On the rising edge (posedge) of the clock, the register file will write from
data_in
into the register specified bywrite_regnum
, but only ifwrite_enable
is set to 1.- except if
write_regnum
is 0; register 0 should never change.
- except if
- 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 to0xfffffff0
. regdata_a
andregdata_b
continuously update and read the registers specified byregnum_a
andregnum_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 theRegister
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.
- If you choose to use the
-
Write tests to sufficiently test all functionality of your register file.
-
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 yourRegFile.v
andtb_RegFile.v
to the project. - After adding them you can compile and run the test bench the same way you have the others.
- In the
-
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.
tb_RegFile.v
(Your new test bench)tb_RegFile.do
(if you created a waveform, add and commit it)RegFile.v
(Your new RegFile module)Practical4.mpf
(Your updated ModelSim project file)
Turn It In
Grading Rubric
General Requirements for all Practicals:
- fits the need
- discuss performance
- tests for correctness
- iteration and documentation
Fill out the Practical Worksheet
In the worksheet, explain how you satisfy each of these "grading rubric" items 1-4. Some guidelines:
-
None of the answers should be more than 100 words.
-
You can complete this electronically (on ipad, pdf editor, or MS Word) or print the worksheet and complete it with pen or pencil.
Practical 4 Rubric items Possible Points Practical Worksheet 25 Tests for DP_Memory 15 RegFile Implementation 15 RegFile Tests 15 ImmGen Implementation 15 ImmGen Tests 15 Extra points 5 Total out of 100 For extra points, you could:
- improve the
vunit
test framework to have a few additional tasks that make your tests easier to write - heavily document (with comments in your code) the
ImmGen
andRegFile
components so your design and implementation are very clear to the graders - create additional tests for memory that identify any strange timing behaviors
- ... etc
- improve the
-
Submit your completed Practical Worksheet to gradescope.
-
Practical 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.- Open a terminal window (like git bash) and navigate to your
B
repository folder. - type
git status
to see files that have changed since your last commit. - If any verilog or .do files have changed, git
add
them, then commit them withgit commit -m "message here"
(replace "message here" with information about what you're committing and why). - Send any changes to the server with
git push
- Open a terminal window (like git bash) and navigate to your
-
Verify that your changes were pushed correctly:
- View your
B
repo on the github classroom website. You should see the files - 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.
- View your