View on GitHub

FPGA tutorial

Verilog

We’ll be working in the verilog directory.

There are some solutions in verilog/spoilers, but as the name suggests, you shouldn’t look there before trying to solve the exercises yourself.

Prerequisites

First, initialize the Git submodule with libraries:

git submodule update --init

You will need the following:

Makefile targets

To run adder_tb.v, use:

make run V=adder_tb.v

To run adder.v and view the results in GTKWave, use:

make sim V=adder_tb.v

Adders

On paper

We’ll try to add some binary numbers (101010 + 111). How does it work? Draw a single bit adder circuit, and connect 4 of them to have a 4-bit adder.

Now we move on to hexadecimal addition. Try adding 7FFA + 3048. How does a single 4-bit adder look like, and how do we connect more of them?

Let’s look closer at a 4-bit adder. The adder will have the following ports:

Trace what it will do for some inputs (for instance, 10 + 8). How would we write the adder in pseudocode?

Now, let’s try to design the same for decimal digits and BCD (binary-coded decimals). What kind of inputs and outputs we’ll have? Again, let’s write pseudocode.

In Verilog

In adder.v, there is a 4-bit and 8-bit adder implemented. Run the testbench (make run V=adder_tb.v) and see the values. View the waveform (make sim V=adder_tb.v).

Now, try to implement a BCD adder. Expand it to build a 4-digit (16-bit) BCD adder. Test it using the provided testbench.

Latch and flip-flop

Let’s go back to NAND game and examine latch and data flip-flop.

There is a simple data flip-flop (DFF) implemented in dff.v. Let’s read it, and draw a wave diagram.

Modify the testbench (dff_tb.v) to check if it works as it should.

Now, create a data flip-flop with an en (enable) input. The value should change only if en is set to 1. Test it using the provided testbench.

module dff_en(input wire clk,
              input wire en,
              input wire data,
              output wire out);

Counter

Implement a counter:

module counter(input wire clk,
               input wire en,
               input wire rst,
               output reg [3:0] count);

You can use the provided counter.v and counter_tb.v.

The counter should increase on a positive clock edge whenever en (enable) is set, and reset to 0 whenever rst (reset) is set:

Clock divider

Given a clock signal, output a clock signal that is 4 times slower.

module clock_divider(input wire clk_in,
                     output wire clk_out);

In other words, we should get:

Can you do the same, but 1024 times slower? (1024 = 2 to the 10th power, or 1 << 10).

Traffic light controller

module traffic(input wire clk,
               input wire go,
               output wire red,
               output wire yellow,
               output wire green);

You can use the provided traffic.v and traffic_tb.v.

Parallel to serial

Write a module that receives an 8-bit value and converts it to single bits.

module serial(input wire clk,
              input wire in,
              input wire [7:0] data,
              output wire ready,
              output wire out);

Memory module

Implement a 256-byte memory module with read and write ports.

module memory(input wire clk,
              input wire ren,
              input wire [7:0] raddr,
              output reg [7:0] rdata,
              input wire wen,
              input wire [7:0] waddr,
              input wire [7:0] wdata);

Write a test bench. What will be the result of reading uninitialized memory? How to initialize the memory to 0?

Hint: You can use a $display statement to print debug messages while the module is working (for instance, "Storing byte XX at address YY").