Lab 01: Combinational Logic

Fall 2022

The questions below are due on Thursday September 15, 2022; 10:00:00 PM.
 
You are not logged in.

Please Log In for full access to the web site.
Note that this link will take you to an external site (https://shimmer.mit.edu) to authenticate, and then you will be redirected back to this page.
A Python Error Occurred:

Error on line 5 of Python tag (line 6 of source):
    kerberos = cs_user_info['username']

KeyError: 'username'

Goals: For most of 6.205 (with some exceptions here and there depending on project choices), you'll be using the Nexys4 DDR (or Arty A7) FPGA development board made by Digilent. This board is built around a Artix 7 Series FPGA, which is from the lower-budget category of FPGAs by Xilinx. Don't be mistaken, however - the A7 is a very capable FPGA. In order to program and work with it, we'll be using the SystemVerilog language and the Vivado toolchain. That's the main goal of this lab...build something on the FPGA.

If you haven't done the first pset, do it. Or at least do the last question since you'll use that module in this lab.

Make sure you follow the Vivado setup documentation. You should also submit your SSH Key so that you can use the lab-bc build tool now or in the future. SSH Key Submissions may take up to 24 hours to take effect, so do this early!

You need to do two Lab Safety Things for 6.111:

  1. Please go to this link and take the EHS00509w Electrical Safety Awareness course. To do this, search for "509" under the Course Catalog Page, which should find the "Electrical Safety Awareness" course. Do this course. It isn't too bad.
  2. Please go to this link and read and sign the undergraduate lab safety acknowledgement form
Overview

We're going to build two simple FPGA designs today. These should be relatively quick to synthesize since they are comprised of combinational-only logic. We will go through the whole build process and discuss how to set up your file tree for a design. Be aware: We are doing this in a different way than we have in the past, and in a way that is not often discussed online. However, doing it this way will:

  • Make the build quicker
  • Work in a manner closer to how you would in industry
  • Avoid using Vivado's limited GUI infrastructure
  • Allow you to use whatever code editor you're most comfortable with
  • Let you keep track of files in a version-control-friendly way (if you so desire)

The Project Folder

So let's get started! On your computer make a new directory and call it lab01. In that folder, make the following subdirectories called:

  • src: This will contain .sv and .v files intended for synthesis. This is the stuff that's actually going onto your Nexys board, to program it and make it do interesting things.
  • sim: This will contain .sv and .v files intended for simulation. You'll learn more about these, how they differ from proper source files, and how we use them in a bit.
  • xdc: This will contain the constraints file for sythesis. We'll explain what this is later, but there is usually only one of these, and it has a .xdc extension.
  • obj: This will contain output products and information from Vivado. This includes the bit file, which is eventually written to the FPGA.

SV Files

These are your (System) Verilog files. They have an ".sv" extension on them to inform Vivado (and other software) that they use the SystemVerilog standard. Occasionally we'll also use regular ".v" files for particular purposes. The purpose of these files can be divided into two categories:

  • Synthesis Files: These files describe hardware designs. These eventually get turned into actual circuits onboard the FPGA. As mentioned previously, we place them in the src directory.
  • Simulation Files: These files, often called "test benches" are not meant to be synthesized. Instead, they are used to test other Verilog files. These files will live in the sim directory. It is best to keep them separate so that Vivado doesn't try to synthesize non-synthesizable logic.

Why simulation? Since Verilog takes a long time to build for FPGAs, using test benches to simulate modules off-FPGA dramatically reduces development time. When writing software (ie, your 6.101/6.009 lab) your code executes quickly, and writing tests might feel like a waste of time. Since we're writing code to describe hardware, not software, our code takes way longer to build, making simulations significantly more valuable.

XDC Files

An FPGA is a chip. That chip has a bunch of metal pins on it that are named with a letter-number grid scheme like "H17" or "K15". We use an XDC file to apply settings to these pins, as well as to give them more meaningful names. For example, pin H17 is hardwired to one of the LEDs on this board, so we call it led[0] in the XDC file.

A default XDC file top_level.xdc is provided for you here. By default ALL lines are commented out. At the start of any project, uncomment the lines used as inputs/outputs in your top_level module, which serves as the entry point for your entire design. Go ahead and put a copy of this file into your xdc directory. We'll come back to it in a moment.

Output Files

When you build a design with Vivado it outputs a .bit file that configures the FPGA's internals to match your design. It also generates a number of reports about resources used, timing issues, and other things. Much of the time the pot of gold at the end of the rainbow is the .bit file, though the reports can be really helpful too. For now this directory is empty since we haven't run Vivado yet.

Bto7S

For a first project, we're going to use the bto7s module you wrote in PSet 01 to control the seven-segment display on the Nexys board. It won't control all the digits independently (we'll need sequential logic for that) so each digit on the display will show the same thing. We'll use two .sv files for this: one to contain the bto7s module, and one to connect the module to the physical pins on the chip.

Make both files, and copy in your working bto7s module from PSet 01 into bto7s.sv. The contents of top_level.sv are below:

// prevents system from inferring an undeclared logic (good practice)
`default_nettype none

module top_level(
        input wire [15:0]     sw,
        input wire            btnl,
        input wire            btnu,
        input wire            btnr,

        output logic[15:0]    led,
        output logic          led17_r,
        output logic          led16_b,
        output logic [7:0]    an,
        output logic          ca,cb,cc,cd,ce,cf,cg);

  logic [6:0] cat_segs;

  // instantiate a bto7s module called 'converter'
  bto7s converter(.x_in(sw[3:0]), .s_out(cat_segs));

  // a typo...keep this here for moment
  assign {cg,cf,ce,cd,cc,cb,ca} = ~cat_segss;
  assign an = 8'b0;

  /* we'll use the LEDs later...for now, just link them to the switches
   * and force some lights on
   */

  assign led = sw;
  assign led17_r = 1'b1;
  assign led16_b = 1'b1;

endmodule // top_level
/* I usually add a comment to associate my endmodule line with the module name
 * this helps when if you have multiple module definitions in a file
 */

// reset the default net type to wire, sometimes other code expects this.
`default_nettype wire

This file is the top level file and its inputs and outputs are actually going to be the pins on the FPGA, which are connected to the peripherals on the development board. Specifically, the module above is dropping the bto7s module into the following sort of schematic:

An "approximate" schematic of what we're building first. Note this is not totally accurate, since there are some control electronics between the FPGA and the seven segment LED.

The missing "link" right now between our top_level module and the actual FPGA. We've declared an output called led that is 16 bits wide, but the FPGA doesn't know what that maps to on the actual chip. The XDC file provides this mapping. We only want to include mappings for connections we actually use, so we'll only uncomment the lines needed to setup the top_level module with its inputs and outputs. XDC files use "#" comments so we just remove the leading # on the necessary lines. Specifially:

For example to activate sw[0] you'd simply start with this:

##Switches
#set_property -dict { PACKAGE_PIN J15   IOSTANDARD LVCMOS33 } [get_ports { sw[0] }]; #IO_L24N_T3_RS0_15 Sch=sw[0]
#set_property -dict { PACKAGE_PIN L16   IOSTANDARD LVCMOS33 } [get_ports { sw[1] }]; #IO_L3N_T0_DQS_EMCCLK_14 Sch=sw[1]
#set_property -dict { PACKAGE_PIN M13   IOSTANDARD LVCMOS33 } [get_ports { sw[2] }]; #IO_L6N_T0_D08_VREF_14 Sch=sw[2]

and then go to this:

##Switches
set_property -dict { PACKAGE_PIN J15   IOSTANDARD LVCMOS33 } [get_ports { sw[0] }]; #IO_L24N_T3_RS0_15 Sch=sw[0]
#set_property -dict { PACKAGE_PIN L16   IOSTANDARD LVCMOS33 } [get_ports { sw[1] }]; #IO_L3N_T0_DQS_EMCCLK_14 Sch=sw[1]
#set_property -dict { PACKAGE_PIN M13   IOSTANDARD LVCMOS33 } [get_ports { sw[2] }]; #IO_L6N_T0_D08_VREF_14 Sch=sw[2]

For our design, we want to use the following connections on the chip:

  • All 16 sw pins.
  • The btnl, btnu, and btnr buttons.
  • All 16 led pins.
  • All 8 an anode pins of the seven-segment display.
  • The ca, cb, cc, cd, ce, cf, and cg cathode pins of the seven-segment display.
  • The red and blue channels of LEDs seventeen and sixteen, which Digilent engineers decided to bequeath upon the titles ofled17_r and led16_b. We thank them for their deeds. 1

Build

If you're using lab-bc.py and you notice a BrokenPipeError (even if you were using lab-bc.py without issues before), or something similar (e.g. an OSError or EINVAL or something like that), go into lab-bc.py and change port = 12345 to port = 80, then try again. We initially planned to use port 12345 to send data to our build machines, but a firewall present at the boundary of the MIT SECURE network blocks this...

Using your preferred Vivado setup, generate a bitstream for this design:

build.tcl
  • If you installed Vivado locally, do vivado -mode batch -source build.tcl, and watch the messages scroll by. -mode batch forces Vivado to build on the command line, and -source build.tcl forces Vivado to use your build script rather than defaulting to a 'project-mode' setup. Here is a copy of a pretty generic build.tcl that you should put in the root of your lab folder. Both of these save you a ton of time - get used to using them! If all goes well, this should succeed, and you'll wind up with an output file in obj.
  • If you are running Vivado on a lab machine, do the same as above.
  • If you're using lab-bc, drop lab-bc.py into your project directory and run python3 lab-bc.py -o obj and wait a while. The same thing should happen, and you'll also wind up with a build log in obj.

You'll notice something broke, no matter which build method you used. Read the lines on your screen if you used option 1 or 2, and you'll notice an error message in obj/build.log if you used lab-bc. It turns out there's a typo in your Verilog code (attentive readers may have noticed we commented it above...). Fix this typo and rebuild -- now you know what an error message looks like.

Once this is done, using openFPGALoader, flash the output bitfile (obj/out.bit) to your FPGA. Verify that the lower four bits can set all sixteen appropriate digits on the displays like shown in the video below.

Checkoff 1:
For checkoff 1, show your seven segment display working with the lower four switches.

ALU

ok for the second part of this lab we'll implement a ALU (Arithmetic Logic Unit).

An "Approximate" Schematic of what we're building first. Note this is not totally accurate since there are some control electronics between the FPGA and the seven segment display.

The ALU will take in two 8 bit numbers:

  • Number 0 (n0) is taken from the lower eight bits of the switch array (sw[7:0])
  • Number 1 (n1) is taken from the upper eight bits of the switch array (sw[15:8])

The buttons btnr, btnu, and btnl will be used together to select from one of eight operations:

The buttons {btnl, btnu, btnr} will be used to select from one of eight operations:

  • (000): Addition: n1 + n0
  • (001): Subtraction: n1 - n0
  • (010): Multiplication: n1 * n0
  • (011): Division: n1 / n0 (integer division)
  • (100): Remainder: n1 % n0 (modulo)
  • (101): Bitwise AND: n1 & n0
  • (110): Bitwise OR: n1 | n0
  • (111): Bitwise XOR: n1 ^ n0

In addition two other checks will always be performed:

  • Equality: n1 == n0 with its result presented on led16_b
  • Greater than: n1 > n0 with its result presented on led17_r

Test Benching

Below we've provided a basic testbench for you to locally develop and simulate this ALU. You might say, "I don't need to do a testbench or simulate, I'll just code this up and iMmEdIaTeLy run a Vivado build." These are famous last words. Many an all-nighter has begun with similar thoughts. Test benching/simulating your code sucks because you have to write more code, but it sucks way less than the making miniscule changes and then waiting hours for a hardware build to happen.

"Ten hours of hardware debugging saves you ten minutes of simulation." -A bad engineer

A testbench file is still Verilog, but it is not synthesizable Verilog. It is meant for simulation. Consequentely a testbench file should be thought of more as a regular program file that "runs". Starts at the top and goes in order as you go down. There are still rules and things, of course, but it should feel more natural to you coming from a Python/C existence in terms of its ordering/causality.

One of the great difficulties in Verilog is that it was originally meant to be a simulation language and was then bent into the Hardware Description Language role. It isn't the end of the world, but always try to keep track of the two types of files:

  • Synthesizable Verilog (files that we use to describe hardware). You will never see any sort of "time" in synthesizable Verilog
  • Simulation Verilog (testbenches). This Verilog will have a concept of time, and is meant to run and test in simulation synthesizable Verilog files.
// set the timestep on the internal simulation clock
`timescale 1ns / 1ps
`default_nettype none

//The timescale specifies the timestep size (1ns) and time resolution of rounding (1ps)
//we'll usually use 1ns/1ps in our class

module alu_tb();

  //make inputs and outputs of appropriate size for the module testing:
  logic [7:0] d0_in;
  logic [7:0] d1_in;
  logic [2:0] sel_in;
  logic [15:0] res_out;
  logic gt_out;
  logic eq_out;

  //create an instance of the module. UUT= unit under test, but call it whatever:
  //always use named port convention when declaring (it is much easier to protect from bugs)
  alu uut(.d0_in(d0_in), .d1_in(d1_in), .sel_in(sel_in),
      .res_out(res_out), .gt_out(gt_out), .eq_out(eq_out));
  //All simulations start with the the "initial block's top
  // They then run forward in order like regular code.
  //lines that are one after the other happen "instaneously together"
  //Time passes using the # notation. (#10 is 10 nanoseconds)
  // set the initial values of the module inputs
  initial begin
    d0_in = 0; //set d0_in to 0
    d1_in = 0; //same for d1_in
    sel_in = 0; //same for sel_in

    // Extremely Important!
    // Even though the system is combinatorial-only, make sure some simulation time runs before analyzing outputs
    #10; //wait 10 ns
    //now print something:
    $display("\n---------\nStarting Simulation!");
    d0_in = 12; //change values!
    d1_in = 45;

    // run through all operations and monitor outputs
    $display("d1_in      d0_in     sel_in  res_out           eq_out  gt_out");
    for(integer i = 0; i < 8; i = i + 1) begin
        sel_in = i; //set sel_in
        #10; //wait for a bit of time (10 ns)
        //then evaluate outputs:
        $display("%8b   %8b  %3b     %15b  %b       %b", d1_in, d0_in, sel_in, res_out, eq_out, gt_out);
    end

    $display("\n---------\nFinishing Simulation!");
    $finish; //finish simulation.
  end
endmodule // alu_tb

If you place the testbench file in your sim folder, from the root of your project folder, you can do:

iverilog -g2012 -o sim/alu.out sim/alu_tb.sv src/alu.sv

and then

vvp sim/alu.out

and outputs should appear. Use and expand this starting test case above to make sure your module is doing all the right operations when needed. Try a variety of input numbers! During Checkoff 2 you will be required to show your test case is testing your system working with at least:

  • d0=0 and d1=0
  • d0=100 and d1=10
  • d0=10 and d1=100
  • d0=42 and d1=42
  • d0=7 and d1=42

When you feel confident that your module is working, run it in the checker below. Do not just write your code in the checker without testing locally.

Putting it on the Hardware

If you've done a rigorous job testing your design in simulation, then we should be ready to deploy it onto hardware.

Make an instance of your alu module inside your current top_level module. Comment out the following three lines that are currently there, since you'll now be controlling those three sets of LEDs from your ALU. directly. :

  assign led = sw;
  assign led17_r = 1'b1;
  assign led16_b = 1'b1;

In addition, so you don't get distracted by the seven segment LEDs for this stage, change the values of an to all be 1! This will turn off all the seven segment LEDs. Build, upload and make sure it works like the video below shows.

Checkoff 2:
Show your testbench testing everything. Demonstrate your final stystem working to a staff member.