**6.111 Lab 1, 2019**
Default XDC File: [nexys4_ddr_default.xdc]("./resources/nexys4_ddr_default.xdc")
# Getting Started For most of 6.111 (with some exceptions here and there depending on project choices), you'll be using the Nexys4 DDR FPGA development board by Digilent. This board is built around a Artix 7 Series FPGA which is the modern lower-budget of FPGAs by Xilinx. Don't be mistaken, however, these are very capable FPGAs. In order to program and work with it, we'll be using Vivado and we'll use SystemVerilog/Verilog to do so. ## Using Vivado To open and set up Vivado on the lab computer, check out the [Vivado Quickstart Guide found here]("https://web.mit.edu/6.111/volume2/www/f2019/handouts/labs/vivado_quickstart/quickstart.html"). This shows you how to create a project with files that are already created. Often times (including in this lab), that won't be the case or it'll be a mix of already-made files and new ones. A FPGA project within the Vivado framework gets very complicated very quickly. A project can consist of hundreds of files beyond the ones that we directly write and which are all critical to the structure of your project. In general Vivado does a pretty reliable job in handling them, so whenever possible, make sure you let Vivado manage the files for you. What this basically means is when Vivado asks things like, "Do you want to include a copy in the Project directory?" or "Do you want Vivado to automatically manage this file?" just err on the side of caution and say yes. ## File Types There's four main types of files you'll create/use in Vivado, with some overlap between them. Together they are what comprise designs that we build and supporting code that let's us test those designs. They are: * **`.sv` files for Design:** These are SystemVerilog (or Verilog (`.v`)) files that describe the actual design and interconnection of different modules that will get implemented in hardware. At a minimum you always need at least one file for your top level. * **`.xdc` files for Hardware Interfacing:** You usually have one file per project and we'll reuse the same one for most of the semester with modifications based on what peripheral devices we're using. It specifies the mapping of the FPGAs pins to other parts on the board and gives them names and some context. * **`.sv` files for Simulation:** These are SystemVerilog (or Verilog (`.v`)) files that are meant to simulate other verilog files. We'll usually as a matter of convention append a `_tb` onto the names of these files to indicate they are "test benches", which means it is meant for testing a verilog file that will actually get used in construction of designs. There is a lot of additional syntax and things that you use in test benches, but which are not "synthesizable" into the hardware so it is good for Vivado to keep simulation files apart from design files since they are both SystemVerilog. Yes this is a bit confusing, but it will make sense as you use it more and more. * **Block Diagram designs/IP:** IP or "Intellectual Property" which is usually proprietary methods that allow you to generate commonly used modules one might need in a digital design. This might be best explained by an example: Using Vivado's CORDIC IP synthesizer you can create a trigonometric function module that can then be used in your design. You don't need to write it from scratch, though you can certainly write one up in SystemVerilog if you'd like. The benefit of IP, is that often times IP-based modules are able to be even more synthesized than what you could make via an RTL (SystemVerilog) language. Depending on use case if you're deploying your designs in industry, you may have to pay a royalty for use of certain IP packages if they're used in your design, and in fact some companies have made money and/or been bought by Xilinx because they came up with effective ways to implement particular design topologies in an FPGA fabric and their algorithms form the basis of some of the IP you see in the Xilinx IP Catalog. ## Building The goal at the end of the day is to have the FPGA be assembled into a real, live system. You start by writing Verilog and integrating IP and the result is a bit file which gets loaded onto the FPGA and configures all of its internal interconnects to implement your design. Starting with correct code, this entire process can take anywhere from a few minutes to hours depending on the complexity of the design. All "builds" are broken into three main steps: ### Synthesis Generally pretty quick (except for initial first-time). In this step the System Verilog and other content (including block diagram IP, interconnects, etc) are interpretted and built into a synthesizable system that can be simulated (if desired). Synthesis is where syntax issues in your SystemVerilog are caught as well as blatant connection conflicts or things like combinatorial loops (cases where a logic gate drives itself with no flip-flops/latches in between). Synthesis does not look into timing restrictions or how a particular line will get turned into an actual constructed device using resources on the chip. That is usually determined in the next step, **Implementation.** ### Implementation This is where the bulk of a "build" cycle will be spent. In this step, the successfully-synthesized project (previous step) will be laid out using the on-chip resources of the FPGA. It is an iterative process which tries to optimize many variables in order to fit it on the chip. Small designs can be implented quickly, but larger designs will take longer. The larger a design is relative to the resources on a chip, the longer the implementation step will take since it has less breathing room. Implementation will provide a resource-level view of your entire design (down to the individual Lookup tables (LUTs) in the design as well as show where everythign is laid out on the chip). Timing information will also be provided and timing violations (violation of setup and hold requirements) will be indicated here. ### Generate Bitstream Usually this is very quick after the slog of Implementation. Here the implementation results from the previous step are converted into a binary file that can be loaded into the FPGA and used to set up its internal circuitry correctly. ### Program the Device The quickest step by far. This will take a few seconds and pipes the binary file of the previous step onto the FPGA as well as triggers a few restart/configuration pins to make it automatically load. These few seconds are the best seconds of your life. You've successfully gotten a design through all the steps, but it hasn't yet let you down by not working. Savor them. It doesn't get any better than this. ## The First Build of a Project Depending if you're on a lab computer or something else, the first build will take a while, whereas subsequent ones will generally be quicker because of caching and other things. ## Design Sources This is where the bulk of your files will exist. The two types of files you'll place in here are going to be SystemVerilog files (.sv) and IP/block diagram files. You will assemble them into a hierarchy with the highest level file being a `sv` file of the entire device with inputs and outputs that are mapped using the constraints file (discussed below). ## Constraints The constrains file (`.xdc`) is used to map the Ball-Grid-Array pins on the actual FPGA (which have bingo-like names such as "C17" and "B4") to more human-readable names based on their hard-wired purpose, affiliate them into arrays if desired, as well as configure those pins (as inputs, outputs, tri-state lines). In addition, off-chip sources such as clocks can be configured to a certain extent here. In general when working with an FPGA, you'll generate an xdc file for each FPGA/board design because the PCB (printed circuit board) will have hard-wired certain pins to certain things (some LEDs, other chips, input ports, etc). Generating an XDC file is a time-consuming process and usually done at the PCB-layout stage. Since we'll always be using the Nexys 4 DDR board this semester and Digilent has provided us with an XDC for the board, this avoids the hassle of this step. However the default XDC file they provide is annoying in its syntax (using one indexing for some things...ikr?) so we're provide a cleaned up one HERE. It comes fully commented (`#` is the comment symbol in XDC files), meaning no pins are linked yet. In order to link them you'll change a chunk of the XDC like follows: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Python ## Clock signal #set_property -dict { PACKAGE_PIN E3 IOSTANDARD LVCMOS33 } [get_ports { clk_100mhz }]; #IO_L12P_T1_MRCC_35 Sch=clk100mhz #create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports {clk_100mhz}]; ##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] ~~~ into this (which activates and notifies Vivado that a 100 MHz clock crystal is attached to pin E3, and indicates that three switches are attached to pins J15, L16, and M13). ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Python ## Clock signal set_property -dict { PACKAGE_PIN E3 IOSTANDARD LVCMOS33 } [get_ports { clk_100mhz }]; #IO_L12P_T1_MRCC_35 Sch=clk100mhz create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports {clk_100mhz}]; ##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] ~~~ You'll generally not modify the XDC file in our class other than commenting or uncommenting lines as needed. It is good practice to only bring in pins that are currently being used in a given project, but nobody will yell at you if you don't. ## Simulation Sources This where Verilog/SystemVerilog that is meant for testing will live. We'll usually call these files "testbenches". You'll notice that when a testbench file calls an instance of a design module for testing that it will then appear underneath the test bench file in this region of the side panel. !!! Tip We'd really strongly recommend that you use one Verilog file per module that you create. One really large benefit of modularizing your design is it lets Vivado cache things designs that have already been synthesized. This way when you're building your next design it won't take as long. # First Assignment (Simple Combinatorial: Checkoff 1) We could talk all day, but it gets boring. Let's start to make something. First up we're going to make some combinational logic on our system. Create a new project for the Nexys4DDR board using the Default XDC. You can either choose to include the xdc file while you're creating the project initially (like in the Vivado quickstart), or you can add it after the project has been created by: * Under Project Manager click on **Add Sources** * Then click on **Add or Create Constraints** * Click on **Add File** and bring in a copy of the default (fully commented out) [nexys4_ddr_default.xdc]("./resources/nexys4_ddr_default.xdc") * Make sure "Copy Constraints File Into Project" is checked * Click **Finish**, and skip the next window that pops up where you define the module by just clicking **OK** and reassuring Vivado **Yes** Regardless of how you created the the xdc file, activate (uncomment out) the following inputs in it (note that `btnl`, `btnc`, and `btnr` are located a little bit further down the file): * `sw[3:0]`: The first four toggle (non-momentary) switches * `btnl`: The left momentary push button * `btnc`: The center momentary push button * `btnr`: The right momentary push button Also activate the following outputs (again be sure to scan the whole file for these names): * `led[4:0]`: The first five green LEDs located above each slider/toggle switch * `led16_b`: The blue channel of the first RGB (Red Green Blue) LED * `led16_g`: The green channel of the first RGB LED * `led16_r`: The red channel of the first RGB LED Also inactivate (comment out) unused inputs and outputs like `clk_100mhz`, for example. Let's now create a top-level module for our design that expects these inputs and outputs. * Under Project Manager click on **Add Sources** * Then click on **Add or Create Design Sources** * Click on **Create File** and make a **SystemVerilog** file called `top_level.sv` * Click **Finish**, and skip the next window that pops up where you define the module by just clicking **OK** and reassuring Vivado **Yes** !!! ERROR: Seriously You must make sure the files are **SystemVerilog** files (NOT VERILOG!) Seriously In the **Sources** field, if you scroll down to the **Design Sources** you'll find a folder that appeared called `top_level`. It should be bolded since it is the first file we've created. The bolding indicates it is the top-level file. Double click on the `top_level.sv` file so we can edit it. This code should interface with the inputs/outputs you specified in the `.xdc` file. Vivado will give you some boilerplate starter code for your module, but I usually just delete it all and start from scratch. You should get practice with learning how to do this yourself (it is just typing after all), but for teaching we'll give you a starting skeleton for today. Copy/paste the starting `top_level` module into your `top_level.sv` file below and study how the inputs, outputs and everythign are specified ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ verilog module top_level( sw, led, led16_g, led16_b, led16_r, btnl, btnc, btnr ); //Inputs: (can also declare these inline up above) input[3:0] sw; //when declaring input btnl; input btnc; input btnr; //Outputs: output logic[4:0] led; output logic led16_g; output logic led16_b; output logic led16_r; //Your logic here: 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 and they are long. ~~~~~~~~~~~~ For this first part, using combinational SystemVerilog, set things up so that the outputs are driven in the following ways: * `led[0]`: Logical OR of `sw[0]` and `sw[1]` * `led[1]`: Logical Exclusive OR of `sw[0]`, `sw[1]`, and `sw[2]` * `led[2]`: Always on regardless * `led[3]`: Always off regardless * `led[4]`: Logical NAND of `sw[1]` and `sw[2]` * `led16_b`: The value of `btnc` * `led16_g`: On only when both `led[0]` AND `led[4]` are on * `led16_r`: The two's place value resulting from the addition of `sw[1:0]` and `sw[3:2]` ## Some Basic Syntax As you start to mess with System/Verilog a few notes on syntax that we noticed from students working with this lab in the first day: * The `logic` term is a generic catch-all for the major logical data types we'll use. Verilog has `wire`s and `reg`s and understanding what these do and mean is something we'll go into this class, but one thing SystemVerilog came up with is the higher level `logic` type. Upon synthesis the Verilog compiler will figure out what to make a `wire` and what to make a `reg` based on how it is used so it sorta simplifies our code for us. So if you want to declare a variable you'd do something like `logic var;` * For combinational logic (what we're doing in this lab) outside of `always_comb` blocks (which we'll cover in an upcoming lecture), `assign` is the verb used to assign/link. So you can't just do `x=y;` You have to do `assign x = y;` * You **can** assign a `logic` to a number upon declaration, but you **can't** assign it to other `logic`s. If you want to do that you'll first need to declare the variable `logic var;` and then on a lower line do `assign var = a&b;` for example. `logic x = 1;` is fine. * The whole `3'b...` or `1'b...` thing is a way to specify the size of data that you're constructing. The "`b`" means binary and is saying read the following symbols as binary. You could also do "`d`" instead, which would tell it to interpret what you specify as a decimal. Consequently `assign x = 3'b110;` means something different than `assign x = 3'd110;` (the former assigns binary 110 to `x`. The latter will try to assign the binary representation of integer 110 to `x` * If you have a one bit size `logic` that you created doing `logic c;` for example you can just do `assing c=1;` if you want and what will happen on compilation is treat `1` as a 32 bit integer, recognize that the 32 bit representation of an integer in binary is `0000_0000_0000_0000_0000_0000_0000_0001` and assign that to `c`. Because `c` is only one bit long the upper 31 bits will overflow and get dumped and `c` will end up with just `1` which is identical to doing `assign c=1'b1;`, but doing it with the explicit sizing is good practice and will also save you from costly mistakes when working with arrays. Also when specifying a number you can use injected `_` to space things to make it easier for our human brain to keep track of. * 1-dimensional arrays in Verilog (at this point in our careers) are created like so: `logic [5:0] d;`. This creates a **6**-long array with indexing going left to right. If you then did `assign d = 6'b010100;` and wanted to access the 2nd-most-right element you'd do `d[1]`. If you wanted the lowest two elements (two most right) you'd do `d[1:0]`. By convention numbers towards the left are increasing in significance ultimately becoming the most signficant bit (msb) where as the right most number is the least significant bit (lsb). # Testing This is a pretty simple project (compared to what you'll be doing later), so it'll build relatively quickly and it isn't that much of a loss to debug in hardware. With that said, one thing we really, really, **really** want to stress with hardware design is the importance and benefit of simulation prior to hardware builds. You can iterate through simulations much much faster than through the entire build process (seconds vs minutes). Six Hours testing on the hardware will save you fifteen minutes of simulation or so the saying goes. But there's more of an energy barrier to writing simulation tests in hardware than in other languages out there like Python so people always tend to avoid writing the test_benches (which is bad practice). Try not to do this! Let's build a test_bench: * Under Project Manager click on **Add Sources** * Then click on **Add or Create Simulation Sources** * Click on **Create File** and make a SystemVerilog file called `top_tb.sv` * Click **Finish**, and skip the next window that pops up where you define the module by just clicking **OK** and reassuring Vivado **Yes** Now in the **Sources** field, if you scroll down to the **Simulation Sources** you'll find a folder that appeared called `sim_1`. If you open this up you'll see both files that you've created so far. Your `top_level.sv` and your `top_tb.sv`. One of these files should be **bolded**. This indicates that within the scope of the simulation it is the top file, which means when you run simulate it will run. Your top level SystemVerilog file is not a simulation however, so if it happens to be bolded (depends on the state of Vivado at time of test bench creation) we need to switch that. If `top_tb` is not bolded, right-click on the `top_tb` and select **Set as Top**. It will then appear bold. You'll notice that in your **Design Sources** folder higher up in the Sources field, **top_level.sv** is still bolded, __and this is good!__ It tells us that for the upcoming implementation our top level file is what will get used, whereas for simulations, we want to first call our test bench (which will itself call the other files). Now double click on the `top_tb.sv` file so we can edit it. We're going to write a testbench for our top-level module. A starter/example one with lots of comments below is provide. Copy/paste this into your file and use this as a starting point for how to test stuff. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ verilog `timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Testing input-output relationships for part one of lab one // commented excessively for learning // jds ////////////////////////////////////////////////////////////////////////////////// module top_tb; // a test-bench module really doesn't need inputs and outputs. It is the top-level module // that doesn't have to interface with anything else outside since it is a simulation //. If a module has no input or output arguments, you don't need to add a parentheses. // In a testbench, the first step is to create appropriately sized logics for all the inputs and outputs // of the unit that you are testing! This often requires some clicking back and forth to make sure things logic [3:0] sw_in; logic [4:0] led_out; logic green_out; //notice that we can name these whatever we want. logic blue_out; logic red_out; logic left_in; //we're just declaring these things here. We'll assign them values down below logic center_in; logic right_in; // below we declare an instance of our module that we want to test. In the case of this testbench, // we're testing the top_level module from top_level.sv. We often call the module we're testing the // "Unit Under Test" or uut for short, but this is just convention, you can call it lentil_soup, it doesn't matter. // You will forget this, but for reference in declaring a module in SystemVerilog the syntax is: // NAME_OF_MODULE NAME_OF_INSTANCE (ARGUMENTS...); As shown below, our module is top_level, and we're calling its instance // uut in this test bench. // Also notice how we are explicitely specifying the module's inputs and outputs. You don't have to do this and could alternatively do: // top_level uut( sw_in,led_out,green_out,blue_out,red_out,left_in,center_in,right_in); if you'd like! Just be careful since // your input/output accounting can get screwed up in this manner, whereas explicit linkage is a bit safer easier to track. // Also note that I broke down the inputs/outputs to one-per line. This is for readability but is not required. Remember SystemVerilog // is heavily influenced by C in syntax...which means you can make it as ugly or not-ugly as you want. top_level uut( .sw(sw_in), .led(led_out), .led16_g(green_out), .led16_b(blue_out), .led16_r(red_out), .btnl(left_in), .btnc(center_in), .btnr(right_in) ); // so far we've only just set up all the connections...simulations usually take place in time. This one is especially weird // because everything is combinatorial and therefore isn't time-dependent, but we'll still simulate this module with some time // included for the sake of learning initial begin //set the inputs to the module to starting values here: sw_in = 4'b0000; //assign all four switches to be 0 (could have also done = 0;) left_in = 1'b0; //one-bit value...assign it to 0 center_in = 1'b0; right_in = 0; //can also just write it as 0...Verilog is usually figures it out) #10; //Extremely important!!! make sure some time runs prior to analyzing outputs Even in a combinatorial-only system //here, #10 means "10 ns" based on the timescale unit up above at the top $display("\n\n---------\nStarting Simulation!"); $display("sw_in\tleft_in\tcenter_in\tright_in\tled_out\tgreen_out\tblue_out\tred_out"); $display("%4b\t%b\t\t%b\t\t\t%b\t\t\t%5b\t%b\t\t\t%b\t\t\t%b", sw_in,left_in,center_in,right_in,led_out,green_out,blue_out,red_out); left_in = 1'b1; #10; //10 ns pause $display("sw_in\tleft_in\tcenter_in\tright_in\tled_out\tgreen_out\tblue_out\tred_out"); $display("%4b\t%b\t\t%b\t\t\t%b\t\t\t%5b\t%b\t\t\t%b\t\t\t%b", sw_in,left_in,center_in,right_in,led_out,green_out,blue_out,red_out); sw_in = 4'b1010; #10; $display("sw_in\tleft_in\tcenter_in\tright_in\tled_out\tgreen_out\tblue_out\tred_out"); $display("%4b\t%b\t\t%b\t\t\t%b\t\t\t%5b\t%b\t\t\t%b\t\t\t%b", sw_in,left_in,center_in,right_in,led_out,green_out,blue_out,red_out); assert (red_out == 0) else $error("Bad"); $finish; //call this at the end of your test. end endmodule //top_level_tb (i usually try to do this so I know where this endmodule is supposed to be associated ~~~~~~~~~~~~~~~~~~~~ You'll note that this test case is both rather verbose and far from exhaustive. By verbose, I mean there's quite a bit of copy-pasting going on. SystemVerilog (and regular old Verilog) have lots of standard programming tools/abstractions that allow far more efficient expression of design patterns, but we'll deal with those in the future and as they are needed. By "far from exhaustive" you'll notice that I'm really only testing a small, small subset of the entire system's inputs and resulting outputs. What would an exhaustive testbench look like? We have 7 independent inputs, and since each input is digital, meaning it can be a 1 or a 0, this means we have $2^7 = 128$ unique input combinations. If we were using this code to control a life-support system or correctly do some high speed trading with billions of dollars, we would probably invest the time to make sure that the module works correctly for __all__ 128 unique inputs as expected. In industry what often happens is a particular design/functionality might even first be generated up in some software language, perhaps C. This software implementation of our functionality provides what's termed a "bit accurate model" of the system operation. This would then be used as the gold-standard to "verify" our hardware implementation. For this first module since it isn't too crazy or complicated and lives don't depend on it, we probably don't need to do an exhaustive test unless you really wanted to. Nevertheless, the simple way to do some checks with a test bench is in pseudo-code and you should spend a bit of time with this code for debugging your design. 1. Set up inputs 1. Allow a bit of time to pass 1. Check the outputs One way to check is to just manually look at some formatted readouts you generate. Another way is with the `assert` call. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ verilog sw_in = 4'b1010; //should sum to 10+10 = 100 (value of 0 assigned to red_out) assert (red_out == 0) else $error("Problem with addition"); sw_in = 4'b1111; //should sum to 11+11 = 110 (value of 0 assigned to red_out) assert (red_out == 1) else $error("Problem with addition"); //and so on... ~~~~~~~~~~~~ ## Running a Simulation With your Test Bench all set up, you can run a simulation. In order to do this, under the **Simulation** drop-down in the Flow Navigator on the left side, click on **Run Simulation**>**Run Behavioral Simulation**. !!! ERROR: Potential Problem If you get an error about `logic` not being defined it means you didn't listen to us, and you should because we care about you. Vivado is probably thinking your `.sv` file is a Verilog file and not a **SystemVerilog** file. Don't worry you can change this. Right click on the file in the Sources tab, and then select **Set File Type**. Then make sure to choose **SystemVerilog**. No we don't know why Vivado can't figure out it is SystemVerilog from the `.sv` When you do this some prompts may come up. Click OK when they do. Vivado will then run synthesis on all your modules and do syntax checking on all your SystemVerilog/Verilog. Any errors will break the simulation and throw back errors which will be reported in the **TCL console** at the bottom. Use those, along with the Red/Yellow/Orange/Green markers on the far side of Vivado's code editor (near the scroll bar...red means error which will be elaborated on when you hover on it) to guide you in any debugging. If the simulation and modules being tested are syntatically good, the simulation will run. Vivado gives you simulation information in two ways: * The **TCL console** at the bottom of the page view will print out anything you print using `$display` or `$error` calls in your test bench for example. For today's lab, this will most likely be sufficient. * Vivado will also pop up a **Waveform Window** which will show the signals from your simulation over time. It is basically like a simulation oscilloscope. This panel is extremely useful when we start dealing with signals that change in time, but for Lab 1 it may be a little bit overkill since we're only doing combinational logic. With that said feel free to look at the traces which show up. You should be able to see that as time progresses since you're changing the inputs to your system, your system's outputs should be changing. We'll get more experience (and have more of a reason to use) the Waveform Window in Lab 2 and beyond. Make sure all your inputs and outputs do what they're supposed to before moving onto part 2 below! Again you don't need to exhaustively test this in simulation, but it is good to experiment here since you will be required to show a functioning test bench for Checkoff 2. # Second Assignment (More Complicated Combinatorial: Checkoff 2) For the second assignment, you are to design a binary to hex module that renders hexadecimal(`0,1,2,3,4,5,6,7,8,9,A,b,c,d,E,F`) visualizations of 4-bit binary values provided by switches. This can be accomplished using a standard, classic 7-segment LED display, of which we have eight on the Nexys 4 board. An image demonstrating "standard" hex representation on a seven-segment display is shown below: ![](./resources/seven_seg_pattern.png) The seven-segment LED displays are each comprised of seven LEDs (eight technically if you count the decimal point) interfaced through the `ca`,`cb`,`cc`,`cd`,`ce`,`cf`,`cg`,and`an[7:0]` pins, which are the cathodes and anodes of various subsets of LEDs. The LEDs are illuminated in particular patterns to convey the correct human-readable information. Each LED in each display has a name (A through G) as shown below. Each seven-segment display is what we call "Common Anode" meaning all seven LEDs have their anodes tied together and their cathodes are independently controlled. This means in order to turn on a given segment, the anode pin needs to be High and the corresponding cathode pin needs to be pulled low (ground). In this way you can control seven LEDs not with fourteen pins, but instead only eight! ![](./resources/seven_seg_schematic.png) Further complicating the situation, however is how **all** the seven segment displays (there are eight of them) are tied together. If we look at the documentation from Digilent it tells us that __all__ of the "A" LEDs on all eight seven-segment displays are connected together, as are all the "B" pins and so on. So while each individual seven-segment digit is common anode, the displays as a whole have a common cathode thing going on. With this schematic in mind, operation then becomes based on the following rules: * If you want to drive a particular seven-segment digit, set its anode pin **LOW**. This turns on the PNP transistor driving it, allowing current to flow. * If you want to drive a particular seven-segment's segment, set its corresponding cathode pin **LOW** since this lets the current leaving the cathode go to ground. ![](./resources/overall_schematic.png) This is a lot to worry about actually for our first assignment. What we'll do today is ignore the anodes. This defaults them to 0V which means all seven segments are on. Then what we'll do is appropriately control the cathode pins as specified above and in the documentation so that switches `sw[3:0]` have their correct hexadecimal representation appear across the seven-segment displays. We'll worry about how to make all these seven-segment displays work at the "same" time in Lab 2. We need clocks and we're not there yet. ## Do It * Create a new project as you did previously, add a constraints file with pins `ca`,`cb`,`cc`,`cd`,`ce`,`cf`,`cg`, and `sw[3:0]` active, and then create a new design source file called `top_level.sv` that will be our project's top level file. Leave it as default for now. * Next, create a second file for the module you'll be writing based on the skeleton below which takes in a four bit value `bin_in` representing a binary number and generates the correct seven segment LED pattern out for hexadecimal display via the `hex_out` array. Design this module using only __positive__ logic, despite the fact that we need to invert some things in our higher level module. (so an input value of `4'b0001` should yield an output value of `7'b0000110`). ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ verilog module binary_to_seven_seg( bin_in, hex_out ); input [3:0] bin_in; //declaring input explicitely output logic [6:0] hex_out; //declaring output explicitely //your logic here //many ways to do this (syntatically) // assign statements with ternary operators or logical statements/equality checks // always_comb block with some if/else if/else logic inside // switch statement // etc.... this is up to you! endmodule //binary_to_hex ~~~ At a block diagram level, your design can be abstracted as shown: **************************************************************** * * * .-------------------+---------------------. .* * | bin_to_seven_seg.sv| | * * +--------------------' | * * | | * * | YOUR | * * ----->+ bin_in[3:0] VERILOG hex_out[6:0] +----> * * | IN HERE | * * | | * * | | * * | | * * '-----------------------------------------' .* * * **************************************************************** This module will then have an instance of it exist and be linked within a top level file `top_level.sv` like shown: ********************************************************************************************************* * * * .--------------+-------------------------------------------------------. * * | top_level.sv | | * * +---------------' | * * | | * * | .-------------------+-----. .-------------+----> ca * * | | bin_to_seven_seg.sv| | .----. +--------------+----> cb * * | +--------------------' | | | +--------------+----> cc * * sw[3:0] ----->+-------------->+ bin_in[3:0] hex_out[6:0] +-->+INVERT+--+--------------+----> cd * * | | | | | +--------------+----> ce * * | | | '----' +--------------+----> cf * * | '-------------------------' '-------------+----> cg * * | | * * | | * * '----------------------------------------------------------------------' * * * ********************************************************************************************************* We (you) can write our `top_level.sv` file to reflect this design as shown below: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ verilog module top_level( input[3:0] sw, //note you can also specify inputs and outputs in line like this output logic ca, //you'll see both this way (and the previous way), is matter of output logic cb, //legacy and preference output logic cc, output logic cd, output logic ce, output logic cf, output logic cg ); //create a linker array to take the output of our module (will make sense below) logic [6:0] led_out; //create an instance of our binary_to_seven_seg module! Feed in sw to the input //and feed otu the output to the led_out array which we'll do operations on below. //notice the instance syntax is the same as what appeared in the testbench above! //...in otherwords: NAME_OF_MODULE NAME_OF_INSTANCE(inputs,outputs); binary_to_seven_seg my_converter ( .bin_in(sw), .hex_out(led_out)); //the values we want to assign to the cathode pins should be the logical inverse of s //what our module above generates so do a bitwise inversion and assign that to all the pins! assign {cg,cf,ce,cd,cc,cb,ca} = ~led_out; endmodule //top_level ~~~ To aid in your design and good practices, we're also going to require you to generate a testbench for your module that you're generating. You can use the code below to help you get started, but during your checkoff you'll need to show how your `binary_to_seven_seg` module successfully passes an exhaustive test-suite (there are four bits on the input...meaning how many possible input/output combinations?) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ verilog `timescale 1ns / 1ps module binhex_tb; logic [3:0] sw; logic [6:0] leds; binary_to_seven_seg my_display(.bin_in(sw), .hex_out(leds)); initial begin $display("Starting Sim"); sw = 4'b0001; //first input...set it to be "1" #10;//let the sim run a little bit (SystemVerilog needs some time to take place!) $display("Input is: %4b, Output is %7b",sw,leds); //Now check that a binary 1 input results in a hex-display "1": assert(leds==7'b0000110) else $error("Didn't draw 1 right :/"); //you do the rest! $finish; end endmodule ~~~ When everything is running your system should look similar to what's shown in the video below: !!! WARNING For checkoff 2 what you need is/are to show to staff: * A functioning `binary_to_seven_seg.sv` file! * A fully functional, exhaustive input-output testbench for the module! * Demonstration of the whole thing working on the board!