**6.111 Lab 2, 2019** This lab is split into two main parts. Start early on them since Lab 3 (the next one) can take a bit of time. In the first part you'll build a system that allows you to selectively display the current state of three different 16-bit numerical generators which are: * a static number specified by switches * an auto-incrementing counter * a counter tallying button presses The goal for Part 1 is to build some relatively simple sequential logic modules and get practice linking a bunch of modules and elements together into one functioning system. In the second part of the lab, you'll develop a small module to send data from the Nexys 4 DDR board to a laptop using a serial-to-USB adapter. This could potentially be useful for transfering smaller images, sound, or other data between the labkit and laptop, for example later on in the course or during projects. # Multiple Digits (First Part Due Thur/Fri Sept 19th or 20th, 2019) For this portion of the lab we want a system that will display multi-digit numbers on the seven-segment displays based on the following rules: * If `btnc` **is not** pressed, the hex value displayed on the lower four displays of the seven-segment display is based on the values of all 16 `sw` values (the toggle switches). * If `btnc` **is** pressed, the hex value displayed on the lower four digits of the seven-segment display is based on the current count value of an automatic counter incrementing at 10 Hz (that increments from `16'h0000` to `16'hFFFF`) * If `btnl` **is** pressed (regardless of what `btnc` is currently), the hex value displayed on the lower four displays of the seven-segment display is based on the number of button presses made to `btnu` (using another counter that increments from `16'h0000` to `16'hFFFF`. The video below shows all three behaviors for reference:
## Reference: The 7-Segment Displays Let's refresh our memory quickly about the seven-segment LED display. Each digit is comprised of seven LEDs connected such that their anodes are all tied together ("common anode), and each LEDs cathode is independently controllable. So at a very basic level, knowing nothing else about their control circuit, we can expect to light up segments by turning the common anode high, and pulling low cathode pins that correspond to the LED segments we want to light up (and pulling high those that we don't by default). ![](./resources/seven_seg_pattern.png) There are eight digits of seven-segment display present. Each digit has a independently controllable anode pin. Each anode is driven on the low side of a PNP transistor, the base of which is attached directly to one of eight anode pins on the FPGA, labeled `an[7:0]`. In order to activate an anode (allowing current to flow), we'd therefore need to drive the corresponding anode `an` pin **low**. By default pins from the output of an FPGA are low, and this is why in Lab 1, all the seven-segment digits were activated when we didn't even have to think about them. ![](./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 scheme going on. Operation then becomes based on the following rules: * If you want to drive a particular seven-segment digit, turn the anode of the LED high by pulling the base of the pnp BJT low (by setting the corresponding `an` 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) The question that should be jumping out to you is how do we make the different segments say different things at the same time if they're all tied together like this? The answer is we don't. Or we sorta do, but it is complicated. What we need to do is bring in the ability to have our digital logic change over time. We call this type __sequential__ logic. What we can do with our LEDs is sequentially light up each of the eight digits with the appropriate pattern for its digit, while keeping the others off. If we do this fast enough (keeping each digit lit for something like 1 ms) and repeatedly for each digit, we can make it look like all eight digits are illuminated to our inferior human eyes. !!! Tip Controlling the display by "strobing" each segment drastically cuts down on the number of pins. If we did it the brute-force way, you'd have eight pins per seven-segment display (one common anode, and seven cathodes), times eight segments, that'd be 64 pins needed. No thank you. Doing it this way requires only 15 pins. Pins equate to circuit board and chip real estate and this usually equates to money. As Method Man tells us, Cash Rules Everything Around Me(Us), so strobing LEDs is ultimately beneficial. ## Getting Started Create a new Vivado project, name it something appropriate, and set up things so the following inputs and outputs are made available for your top level module (by modifying/uncommenting the common `.xdc` file for the Nexys 4 board): * `sw[15:0]`: All sixteen slide switches * `clk_100mhz`: Uncomment both related lines to this at the top of the xdc * `ca`,`cb`,`cc`,`cd`,`ce`,`cf`,`cg`: The cathodes common to all eight seven-segment digits * `an[7:0]`: The selection anodes for each seven-segment * `btnc` and `btnl`: These will be used to select what is being displayed * `btnu`: A button whose pushes one of your counters will be counting * `btnd`: We'll use this button as a global reset Inside your project create a `top_level.sv` file and module that interfaces with these inputs and outputs appropriately just like we've done previously in lab 1. ## Just the Display As a first step let's build a module called `seven_seg_controller` which will be responsible for controlling all eight digits of the seven-segment displays. To not get overwhelmed with all the deliverables, we'll first build and implement it with it just being directly controlled by `sw[15:0]` switches. To make this module as functional as possible (for later usage) it will be responsible for displaying hex on all 8 digits, meaning it can represent 32 bits of information (4 bits per hex symbol) even though we're only using it for 16 here. Consequently, this module should have three inputs: * `val_in[31:0]`: a 32 bit input which will get rendered on the display * `clk_in`: An external system clock for driving the synchronous logic in the module * `rst_in`: a reset control in case you'd like to make your module resettable. Additionally, the module will have two sets of outputs: * `cat_out[6:0]`: Pins intended to **directly** drive the cathode pins in our system (this implies something about where you need to invert them) * `an_out[7:0]`: Pins for selecting/deselecting the individual segments via their anodes. Diagramtically for the first build, `seven_seg_controller` should fit into the `top_level.sv` file like shown: ********************************************************************************************************************* * * * .--------------+-------------------------------------------------------------------. * * | top_level.sv | | * * +---------------' | * * | | * * | | * * | | * * | | * * | .-----------------------+-------. .-------------+----> ca * * | | seven_seg_controller.sv| | +--------------+----> cb * * | +------------------------' | +--------------+----> cc * * sw[15:0] ---->+-------------->+ val_in[31:0] cat_out[6:0] +-------------------+--------------+----> cd * * | | | +--------------+----> ce * * btnd --------->+-------------->+ rst_in | +--------------+----> cf * * | | an_out[7:0] +-----------. '-------------+----> cg * * clk_100mhz --->+-------------->+ clk_in | | | * * | | | | | * * | '-------------------------------' '----------------------+----> an[7:0]* * | | * * | | * * | | * * | | * * '----------------------------------------------------------------------------------' * * * ********************************************************************************************************************* !!! Tip We only have 16 switches, meaning 16-user controllable bits going into a 32 bit input. As a result, the upper 16 bits of `val_in` will be default 0 and you'll only be able to control the lower four hex digits. We're going to provide a working version of `seven_seg_controller` below. Your job for this first part of the lab is to integrate it as described above. This module is a very basic form of state machine, which we'll be covering in upcoming lectures/labs! Note that it uses an instance of `binary_to_seven_seg` from lab 1 so you should bring a copy of that into your project as well (you can either put it in a separate file or include it below int eh same file)! Other than wiring everything together, this module should just work! ~~~~~~~~~~~~~~~~~~~~~~~ verilog linenumbers module seven_seg_controller(input clk_in, input rst_in, input [31:0] val_in, output logic[7:0] cat_out, output logic[7:0] an_out ); logic[7:0] segment_state; logic[31:0] segment_counter; logic [3:0] routed_vals; logic [6:0] led_out; binary_to_seven_seg my_converter ( .val_in(routed_vals), .led_out(led_out)); assign cat_out = ~led_out; assign an_out = ~segment_state; always_comb begin case(segment_state) 8'b0000_0001: routed_vals = val_in[3:0]; 8'b0000_0010: routed_vals = val_in[7:4]; 8'b0000_0100: routed_vals = val_in[11:8]; 8'b0000_1000: routed_vals = val_in[15:12]; 8'b0001_0000: routed_vals = val_in[19:16]; 8'b0010_0000: routed_vals = val_in[23:20]; 8'b0100_0000: routed_vals = val_in[27:24]; 8'b1000_0000: routed_vals = val_in[31:28]; default: routed_vals = val_in[3:0]; endcase end always_ff @(posedge clk_in)begin if (rst_in)begin segment_state <= 8'b0000_0001; segment_counter <= 32'b0; end else begin if (segment_counter == 32'd100_000)begin segment_counter <= 32'd0; segment_state <= {segment_state[6:0],segment_state[7]}; end else begin segment_counter <= segment_counter +1; end end end endmodule //seven_seg_controller //feel free to either include binary_to_seven_seg module here or in its own file! module binary_to_seven_seg ( input [3:0] val_in, output logic [6:0] led_out); // // your previously written function // endmodule // binary_to_seven ~~~~~~~~~~~~~~ !!! Tip Notice the reset signal in this module. It is almost always a good idea to give your sequential circuits a reset signal both for regular operation but also for debugging since it can give you the ability to get your circuit back to a known starting state. !!! WARNING Checkoff 1 When you have assembled all these pieces, show to a staff member for the first first lab checkoff. Note, if you move onto the next section, you can get still get checkoff 1 by default since it will include the functionality shown here. It is up to you if you want to get this one now or merge them in the next section. ## Everything Else Now that you can effectively render in hex any 32 bit number we're going to expand and modify our design. Instead of routing `sw[15:0]` directly into `seven_seg_controller` we'll now selectively route one of three numbers: * The value from `sw[15:0]` like the previous section * The value from a counter auto-incrementing at 10 Hz * The value from a counter that is monitoring button pushes on button `btnu` What we display will be based on the following sets of rules: * If `btnc` **is not** pressed, the hex value displayed on the lower four displays of the seven-segment display is based on the values of all 16 `sw` values. * If `btnc` **is** pressed, the hex value displayed on the lower four displays of the seven-segment display is based on the current count value of an automatic counter incrementing at 10 Hz * If `btnl` **is** pressed (regardless of what `btnc` is currently), the hex value displayed on the lower four displays of the seven-segment display is based on the number of button presses made to `btnu`. In order to do this we'll need to **expand** the `top_level.sv` module you already have by building two more modules, `simple_counter` and `debounce`, as well as implementing some more intermediate logic and of course link everything together. Schematically the entire system will look like the following (and be routed into the input of your already working `seven_seg_controller`). ***************************************************************************************************************************** * * * * * .------------------+-----. * * +------------------------------------------------------------+ | simple_counter.sv | | * * | +------------------------------------------------------+ | +-------------------' | * * | | | | | | * * | | .------------------+-------. | +-->+ clk_in | * * | | | debounce.sv | | | | | * * | | +-------------------' | .-------. +----->+ rst_in count_out[15:0] +---+ * * | | | | | Edge | | | | * * *--)----->+ clk_in clean_out +----->+ Detect +------->+ evt_in | | * * | | | | | | | | | * * | *----->+ rst_in | '-------' '------------------------' | * * | | | | | * * btnu --------->---)--)----->+ bouncey_in | | * * | | | | +--------------------------------+ * * | | '--------------------------' | .---------. * * | | +---->+ | * * | | | 16-wide | [15:0] * * sw[15:0] ----->---)--)---------------------------------------------------------------->+ 3-to-1 +--/---> to .val_in * * | | | Mux | input on the * * | | .------------------+-----. +---->+ | seven_seg_controller * * *--)-----------------------+ | simple_counter.sv | | | | | instance * * | | | +-------------------' | | '--+---+--' * * | *--------------------+ | | | | ^ ^ * * | | | +->+ clk_in | | | | * * | | .--------. | | | | | | * * | | | | +---->+ rst_in count_out[15:0] +-----+ | | * * clk_100mhz --->---*--)----->+ 10 Hz | | | | | * * | | Pulse +------->+ evt_in | | | * * | | | | | | | * * btnd --------->------*----->+ | '------------------------' | | * * '--------' | | * * | | * * | | * * btnc --------->----------------------------------------------------------------------------+ | * * | * * btnl --------->--------------------------------------------------------------------------------+ * * * * * ***************************************************************************************************************************** Your job for this part of the lab will be to: * Complete the implementation of `simple_counter` * Complete the implementation of `debounce` * Add an edge-detector * Add a 10 Hz pulse generator * Add a 16-bit 3-to-1 multiplexer * Integrate everything together ### Adding a Counter A very common thing you'll need to build in designs is something that tallies an the occurence of an event. There are a number of ways to do this. For this lab, first create a module called `simple_counter` that does just that and has three inputs: * `clk_in`: The system clock * `rst_in`: A reset for the system * `evt_in`: The event to be counted. When `evt_in` is HIGH on a rising edge of `clk_in`, the counter should increment * `count_out[15:0]`: The output of the counter module. Note this value is 16 bits so it should be able to count from `16'b0000_0000_0000_0000` to `16'b1111_1111_1111_1111` We'll provide a starting skeleton below. Your job is to finish it: ~~~~~~~~~~~~~~~~~~~~~~~~ verilog linenumbers module simple_counter( input clk_in, input rst_in, input evt_in, output logic[15:0] count_out ); always_ff @(posedge clk_in)begin if(rst_in)begin count_out <= 16'b0; //reset signal end else begin //your code here end end endmodule ~~~~~~~~~~~~~~~~~~~~~~~~~~ Since there's a lot of moving parts going into this lab's checkoff 2 deliverable it would be a good idea to test and verify the behavior of this module prior to integration. We provide a testbench for this module below: ~~~~~~~~~~~~~ verilog linenumbers `timescale 1ns / 1ps module simple_counter_tb; //make logics for inputs and outputs! logic clk; logic rst; logic evt; logic[15:0] count; //size appropriately! //make an instance of the counter...call it uut or my_counter or something else simple_counter my_counter( .clk_in(clk), .rst_in(rst), .evt_in(evt), .count_out(count)); //An always block in simulation **always** runs in the background //this is your standard way of making a clock below: //it says: every 5 ns, make clk be !clk //still need to initialize clk in an initial block always begin #5; //every 5 ns switch...so period of clock is 10 ns...100 MHz clock clk = !clk; end //initial block...this is our test simulation initial begin $display("Starting Sim"); //print nice message clk = 0; //initialize clk (super important) rst = 0; //initialize rst (super important) evt = 0; //initialize evt (super important) #20 //wait a little bit of time at beginning rst = 1; //reset system #20; //hold high for a few clock cycles rst=0; //pull low #20; //wait a little bit evt = 1; //make an evt #10 //wait a clock cycle evt = 0; //pull low #50 //wait 50 ns evt = 1; //pull high #200 //just let it run for a bit evt = 0; //pull low, and be done $finish; end endmodule //counter_tb ~~~~~~~~~~~~~~~~~~~ This is the first module we've written where it has time-dependent behavior we need to scrutinize. We could do a bunch of `$display` calls that show up in the TCL console, but a much more useful tool is the **Waveform Window** which will pop up on running a simulation (the thing we ignored in lab 1) in Vivado. Running the test bench above with a working `simple_counter` module results in the following generated waveform and we can see how it works! The count increments on the rising clock edge every time `evt_in` is High! Exciting. Get your `simple_counter` module working so it matches this behavior! ![](./resources/waveform.png) The Waveform Viewer provides a smorgasbord of really useful things for the user. You can zoom in, hover, expand, analyze signals, and so on. ! Tip In simulation, unless things get explicitely set to a value, they will stay undefined! These undefined values will propagate through the simulation. Notice how in the image of the simulation at the start, `count` variable is `XXXX` (and red) at the beginning. This means that the variable has yet to be set! If you started using our `simple_counter` skeleton, you'll notice that only on a `rst_in` call being high and a clock cycle rising edge occurring, does `count` become 0. In reality when synthesized and placed on a device, even if variables are **not** initialized, they will generally start at 0, but remember not to get messed up by these false undefined demons in simulation. ### Debounce Module In our overall system, one counter is going to tally a periodic 10 Hz signal. The other counter is going to tally button pushes. We'll deal with the 10Hz signal later, but let's worry about the button now. We have to be careful counting button pushes, because switches bounce, which is the phenomenon where a switch may literally bounce on the contacts when pushed/unpushed resulting in high-frequency ON-OFF-ON-OFF signaling. A human may not notice this, but our 100 MHz digital systems will. The required solution is to "debounce" the signal so we'll add a module which handles this for us. Switch bounce typically disappears after 10-ish ms so one approach to building a debouncer is to make a module that only "passes" the button's value after it has been constant for 10 ms (you DSP people can think of this as a form of low-pass filtering). !!! Tip For our debouncer, a signal is considered to be clean if the value remains the same for 10msec. With a 100 MHz clock, that's 1,000,000 clock cycles. At each clock pulse, store the new input in a `logic` variable called `old`. If the new input is the same as the previous input, increment a counter. If not it must be switch bounce so set the counter to zero. When the counter reaches 1,000,000-1, you have a debounced input and update your output appropriately! Again, we'll give you a starting skeleton for the `debounce` below. Fill in the rest: ~~~~~~~~~~~~~~~~~~~~~~~~ verilog linenumbers module debounce( input clk_in, //clock in input rst_in, //reset in input bouncey_in,//raw input to the system output logic clean_out //debounced output ); logic [19:0] count; // is 20 bits enough? logic old; always_ff @(posedge clk_in) begin //Your Verilog here! end endmodule ~~~~~~~~~~~~~~~~~~~~~~~~~~ For building any module we need to test, test, test, and test. Here's a simple testbench we wrote to look at our `debounce` module. Make a testbench file called `debounce_tb.sb` and bring in the code below to it: ~~~~~~~~~~~~~~~~~~~~~~~~ verilog linenumbers `timescale 1ns / 1ps module debounce_tb; //make logics for inputs and outputs! logic clk; logic rst; logic dirty; logic clean; //make an instance of the counter...call it uut or my_counter or something else debounce uut( .clk_in(clk), .rst_in(rst), .bouncey_in(dirty), .clean_out(clean)); //An always block in simulation **always** runs in the background //this is your standard way of making a clock below: //it says: every 5 ns, make clk be !clk //still need to initialize clk in an initial block always begin #5; //every 5 ns switch...so period of clock is 10 ns...100 MHz clock clk = !clk; end //initial block...this is our test simulation initial begin $display("Starting Sim"); //print nice message clk = 0; //initialize clk (super important) rst = 0; //initialize rst (super important) dirty = 0; //initialize evt (super important) #20 //wait a little bit of time at beginning rst = 1; //reset system #20; //hold high for a few clock cycles rst=0; //pull low #20; //wait a little bit dirty = 1; //push the button #15000000; //wait a bit... dirty = 0; //release the button end endmodule //debounce_tb ~~~~~~~~~~~~~~~~~~~~~~~~~~ When this testbench is run (make sure you specify that it is the top level simulation file), you'll get a waveform that looks like the image below. Because our debounce module acts over the span of milliseconds, but our simulation works on the step size of nanoseconds, we need to simulate quite a while (millions of simulation steps). By default the simulations in Vivado won't run more than like 10 microseconds or so no matter how much of a large `#` wait you add. This can be fixed, however by running the simulation for a longer duration manually. Towards the top of your window, change the settings so that you will run the simulation for 50 ms and then re-run: ![](./resources/db_tb_1.png) After the simulation completes (and it might take a few seconds...this is a lot of timesteps), if you auto-scale the view (using the expand button) you should see the entire 50 ms history of the simulation: ![](./resources/db_tb_2.png) Notice how the "clean" output lags the value of "bouncey" input by 10 ms? Great! It is working. You need to make sure your's does this too. Let's say (either hypothetically or actually) your `debounce` module doesn't work upon initial testing. There's only so much you can deduce about the module by analyzing __only__ its input and output signals. Thankfully you can investigate the internal workings of a module in the Waveform viewer. To do this, click on Scope tab on the left side, and expand out the `debounce_tb` field. You'll find `uut` under there (or whatever you named your instance of `debounce`. Drag it over into the Name column in your waveform view and you'll suddenly see **all** internal signals from your `debounce` module. The only problem is, these signals weren't up when we simulated earlier, so Vivado didn't log them. ![](./resources/db_tb_3.png) In order to see those signals, we need Vivado to go ahead and rerun the simulation. To do this: * Click the "Relaunch Simulation button" (the circle arrow button) * Then click the "Run for 50 ms" button * After you auto-scale the view (with the expand button) you should have something like this: ![](./resources/db_tb_4.png) As you can see the internal workings of hte module are now visible for you, and this can really really help in your debugging. ## Other Modules While integrating the modules you just wrote into the entire `top_level.sv` file, there is some other logic (some combinational, some sequential) that we need to generate and condition signals. These other pieces can be broken down into three parts, labeled in the diagram: an Edge Detector, a 10 Hz event pulse, and a 3-to-1 multiplexer for our different counts. The reason and funcitonality for each piece is described below: ### Edge Detector Debouncing the switch "cleans" up the switch signal so upon push or release, it only does one transition. However with the way our `simple_counter` module is written, if we feed the debounced input directly into the counter, one push may result in literally hundreds of thousands of "events" the counter module is running at 100 MHz. We need to instead count only the event of a push __just as it happens__. In other words we need to create a rising-edge signal that lasts for only one clock cycle. A simple way of generating an edge signal is to remember the previous value of a signal and look for a rising pattern like shown below: ~~~~~~~~~~~~~~~~~~~~~~~~ verilog linenumbers //inside top_level: logic old_clean; logic rising_clean; //clean&!old_clean is a signal that indicates a "rising edge": assign rising_clean = clean & !old_clean; always_ff @(posedge clk_100mhz)begin if (rst_in)begin old_clean <= 1'b0; end else begin old_clean <= clean; end end ~~~~~~~~~~~~~~~~~~~~~~~~~~ ### 10 Hz Event Our second instance of `simple_counter` should be auto-incrementing at 10 Hz. This means you need to generate a signal that will drive it appropriately. Remember the signal needs to work with how your `simple_counter` works. In order to implement this do the following: * Create an appropriately sized variable that can count up on `clk_100mhz` * Have a signal that is logical high __only__ when the counter is equal to the period duration - 1 An example of how to do this is below: ~~~~~~~~~~~~~~~~~~~~~~~~ verilog linenumbers //inside top_level: parameter PERIOD = 32'd10_000_000; logic[31:0] periodic_counter; //32 bits is more than sufficient! logic pulse; assign pulse = (periodic_counter == PERIOD-1); always_ff @(posedge clk_100mhz)begin if (rst_in) begin periodic_counter <= 32'b0; end else begin if (periodic_counter == PERIOD-1)begin periodic_counter <= 32'b0; end else begin periodic_counter <= periodic_counter + 32'b1; end end end ~~~~~~~~~~~~~~~~~~~~~~~~~~ ### 3-to-1 Multiplexer When all is built successfully we'll have three different 16 bit numbers to display. Using buttons `btnc` and `btnl` as selection inputs based on the rulesets described earlier in the lab, you need to selectively route these signals to the input of the `seven_seg_controller`. This can be done using only combinational logic! ## Put It All Together You should have everything you need now to build the overall block diagram shown earlier. When it is all together and working, it should perform similar to the starting video on this page. !!! WARNING Checkoff 2 Merge everything together to get your system working! Make sure you've verified behavior on `debounce` and `simple_counter` with the testbenches prior to integration to help in debugging! The staff will push on this design and ask questions so make sure you are prepared to answer! ** For Part 1, you must also upload all the Verilog Files from your working system to the website for grading! # Serial Reporter (Second Part Due Tue/Wed Sept 24th or 25th, 2019) For the second part of the lab you're going to interface the FPGA to your laptop (or a friend's laptop) by sending up ascii over serial. Or More specifically UART from the FPGA to a interface chip and then USB from the interface chip up to the computer which will be listening via a Python program as shown in the video below:
## Serial Communications One of the earliest communications protocol is Universal Asynchronous Receive Transmit serial protocol commonly referred to as UART. Data is sent over a single wire one bit at a time at an agreed-upon fixed data rate known as the **baud**. This form of serial is the same standard used with FTDI chips on Arduino boards or many others. In contrast, SPI and I2C are synchronous protocols which include a clock line. UART and associated protocol data rates can range from 300 bits per second (bps) to megabits/second. (The first modem was 300 bps. When 1200 bps modems came out that was considered high speed - 4 x 300 bps!) The UART protocol specifies the number of start bits, data bits (typically 8), parity bits (if any) and stop bits. In our lab, we have one start bit, one stop bit, 8 data bits, and no parity bit, so the full packet is 10 bits. The wire used for data transmission is normally high and the bits of a packet are sent in the following order: 1. The start bit which is always logical low. 2. The data bits sent least-significant-bit first sequentially 3. The stop bit which is always a logical high A snapshot of an 8bit UART transmission is shown below: ![](./resources/uart.jpg) ## Our Serial Implementation: Your job is to build a module called `serial_tx` that takes in an 8 bit value from 8 `sw` switches in order to specify an ascii character (from the here) and transmits it over `ja[0]` using 115.2 kilobaud serial . This module will fit within a larger system shown below. (We'll also give you a fully-functioning `top_level` Verilog). ********************************************************************************************************************* * * * .--------------+-------------------------------------------------------------------. * * | top_level.sv | | * * +---------------' | * * | | * * | | * * | | * * | | * * | .-------------+-----------------. | * * | | serial_tx.sv | | | * * | +--------------' | | * * | | | | * * sw[15:0] ---->+------------------------------------------->+ val_in[7:0] | | * * | | | | * * | +-->+ trigger_in | | * * | | | data_out +-----+---> ja[0] * * | +-------------------------------------)-->+ rst_in | | * * | | | | | | * * | | +-----------------------------------)-->+ clk_in | | * * | | | | | | | * * | | | | '-------------------------------' | * * | | | .------------+-. | | * * | | | | debounce.sv | | | | * * | | | +-------------' | | | * * | | | | | .------. | | * * btnd --------->+--*-)-->+ rst_in | | | | | * * | | | | | Rising | | | * * | | | clean_out +-->+ Edge +-+ | * * | | | | | Detect | | * * clk_100mhz --->+----*-->+ clk_in | | | | * * | | | '------' | * * btnc --------->+------->+ bouncey_in | | * * | '--------------' | * * | | * * | | * * | | * * '----------------------------------------------------------------------------------' * * * ********************************************************************************************************************* The corresponding `top_level` module: ~~~~~~~~~~~~~~~~~~~~~~~~ verilog linenumbers module top_level( input clk_100mhz, input [15:0] sw, input btnc, input btnd, output logic [15:0] led, output logic [1:0] ja ); logic clean; logic old_clean; assign led = sw; assign ja[1] = 0; //just assign this to be 0 always_ff @(posedge clk_100mhz)begin old_clean <= clean; //for rising edge detection end debounce my_deb(.clk_in(clk_100mhz), .rst_in(btnd), .bouncey_in(btnc), .clean_out(clean)); serial_tx my_tx(.clk_in(clk_100mhz), .rst_in(btnd), .trigger_in(clean&~old_clean), .val_in(sw[7:0]), .data_out(ja[0])); endmodule//top_level ~~~~~~~~~~~~~~~~~~~~~~~~ For the system, we're going to send data at 115.2 Kbps. If the sytem clock is 100 MHz and we want our data output to be approximately 115.2 Kbps (115.2KHz), how many clock cycles do we need to count up to between each bit sending? What is the period of a 115.2 kHz clock? $$ T_{115.2kbps} = \frac{1}{115200 \text{ s}^{-1}} = 8.6805\times 10^{-6} \text{ s } = 8.68 \mu\text{s} $$ If the period of our 100 MHz master clock is 10 ns or $0.01 \mu\text{s}$ that means __roughly__ you need about 868 cycles from your 100 MHz clock every time you want an event to occur at 115.2 KHz. Beginning with the `serial_tx` module below, complete the implementation: ~~~~~~~~~~~ verilog linenumbers module serial_tx( input clk_in, input rst_in, input trigger_in, input [7:0] val_in, output logic data_out); parameter DIVISOR = 868; //treat this like a constant!! logic [9:0] shift_buffer; //10 bits...interesting logic [31:0] count; always @(posedge clk_in)begin end endmodule ~~~~~~~~~~~~~~~ ## Debugging with a Testbench We've provided a testbench for you below to develop your `serial_tx` module. Note that because the Serial BAUD is rather slow compared to the system clock, you'll need to run these simulations a somewhat longer time (not as long as the `debounce` module needed, but longer than the counter module for sure. 100 microseconds will probably work.) ~~~~~~~~~~~~~ verilog linenumbers `timescale 1ns / 1ps module serial_tx_tb; logic clk; logic rst; logic trigger; logic[7:0] val; logic data; serial_tx my_serial( .clk_in(clk), .rst_in(rst), .trigger_in(trigger), .val_in(val), .data_out(data)); always begin #5; clk = !clk; end initial begin clk = 0; rst = 0; trigger = 0; val = 8'b0; #10000; rst = 1; #10; rst = 0; #10; val = 8'b1010_1010; #40; trigger = 1; #10; trigger = 0; #500; //as you run it...should see 10101010 show up ont eh data out line end endmodule ~~~~~~~~~~~~~~ The result of running our module through the testbench above for 100 $\mu$s is shown below for reference. You can see sending `8'b1010_1010` over serial results ins 0->0->1->0->1->0->1->0->1->1 in order! That is what to expect. ![](./resources/uart_tb.png) ### Really Doing It Finally in order to fully test your module we need to wire everythign up and set up some software. Grab an FT2232H UART adapter from up front (two types shown below. Either should be fine, though one uses counterfeit FTDI chips) This is a surprisingly common chip so you may already be able to use it as is, but depending on your computer's history you may need to install a driver for it. The drivers can be found here. We need to wires to interface the FT2232H chip to the FPGA. A ground wire (black in photos below) and a data wire (white in photos below) are used to connect to the PMOD port `JA` on the Nexys board (its ground and `ja[0]` pins, respectively). ![](./resources/uart_hookup.png) The pinout of the PMOD port is shown below. For Port `JA`, data pin 0 corresponds to `ja[0]`, for example. ![](./resources/pmod.png) Once the wiring is done, we can plug in the USB cable to the FTDI chip to our computer. In order to chit-chat with the chip, we: * Need Python3 installed on your computer (most of you already have this) * Need to install pyserial to interface. In a terminal on your laptop run `pip install pyserial` or `pip3 install pyserial` or if you're a conda person probably `conda install pyserial` should be good too. Then when that's all done, you can run the following code should find the serial device and start listening for data to be sent up (again use the video for reference). Upon pressing buttons, the ascii value you're sending up should appear on the screen!: If you'd like to double-check that your entire USB/wiring pipeline is working you can upload this bit file on your Nexys board which will implement a simple module that repeatedly sends up "MIT " *ad nauseam*. If this is working and your code isn't working, you've got a problem with your code then obviously. If this bit file isn't working it is an issue with drivers, wiring or something else. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Python '''Automatically find USB Serial Port (jodalyst 8/2019) ''' import serial.tools.list_ports def get_usb_port(): usb_port = list(serial.tools.list_ports.grep("USB")) if len(usb_port) == 1: print("Automatically found USB-Serial Controller: {}".format(usb_port[0].description)) return usb_port[0].device else: ports = list(serial.tools.list_ports.comports()) port_dict = {i:[ports[i],ports[i].vid] for i in range(len(ports))} usb_id=None for p in port_dict: #print("{}: {} (Vendor ID: {})".format(p,port_dict[p][0],port_dict[p][1])) #print(port_dict[p][0],"UART") print("UART" in str(port_dict[p][0])) if port_dict[p][1]==1027 and "UART" in str(port_dict[p][0]): #for generic USB Devices usb_id = p if usb_id== None: return False else: print("Found it") print("USB-Serial Controller: Device {}".format(p)) return port_dict[usb_id][0].device s = get_usb_port() #grab a port print("USB Port: "+str(s)) #print it if you got if s: ser = serial.Serial(port = s, baudrate=115200, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, timeout=0.01) #auto-connects already I guess? print("Serial Connected!") if ser.isOpen(): print(ser.name + ' is open...') else: print("No Serial Device :/ Check USB cable connections/device!") exit() try: print("Reading...") while True: data = ser.read(1) #read the buffer (99/100 timeout will hit) if data != b'': #if not nothing there. if data[0]<=127: #if going to be valid ascii... print("ASCII: {}, Value: {}".format(data.decode('ascii'),data[0])) else: print("Invalid ASCII, Value: {}".format(data[0])) except Exception as e: print(e) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When it is all running it should look like the video below:
!!! WARNING Checkoff 3 With everything is working, demo serial sending up to a staff member for checkoff 3. Be prepared to explain how your `serial_tx` module works.