4. Field Programmable Gate Arrays

It is time to get our hands on some actual hardware 📟 and take our new logic design skills for a spin. Most hardware components that we use on day-to-day basis have a fixed design. This means that the gates and their wiring are hardcoded. In other words, the design and purpose of such a device stays the same throughout its operating life. As an example, assume that you bought a processor. This could be the Broadcom BCM2711 of the Raspberry Pi 4 Model B which we’ll use as our development platform when discussing the Arm architecture. We can use this processor by running our software on it. Software consist of instructions where the processor performs the corresponding actions. However, we can not change the design of the processor itself.

In contrast, Field Programmable Gate Arrays (FGPAs) allow us reconfigure or “program” the logic chip itself. This means that we can decide which logic gates to use and how those should be connected: The same manufactured FPGA could, e.g., one day power a highly tailored microprocessor design or operate as a graphics processing unit another day. In fact, FPGAs are often used as a first platform for protoyping processor designs. For example, the RISC-V Exchange platform provides designs for entire cores, some of which are available under open source licenses.

In this section we’ll use the DE10-Lite board which is build around the Altera MAX 10 FPGA. Additional information outside of the class is available through the FPGAcademy. We use the Quartus Prime Lite Edition Design Software to program the board.

Hint

Compile all your modules with Icarus Verilog using flags -Wall -Winfloop -g2012 before even approaching the FPGAs. Additionally, write testbenches and run simulations before firing up Quartus Prime.

4.1. RTL Modeling

In Section 3 we specified our hardware modules through elementary logic gates. Given an abstract description of a function, we had to manually lower it to logic gates. This process is cumbersome, error-prone and time-consuming, especially when aiming for an optimized circuit where different realizations have to be considered. From now on, we’ll harness more powerful SystemVerilog constructs under the umbrella of Register Transfer Level (RTL) modeling. This allows to write more abstract code and harness a synthesizer for the lowering to gates. Even though RTL modeling greatly simplifies our work, we should keep in mind the prospective synthesized gate-level representations of our modules.

We discuss RTL modeling in class. All details can be found in the IEEE Standard for SystemVerilog. Further, Ch. 4 of the SystemVerilog Guide of Harvard’s CS141 class represents a more gentle introduction.

4.2. 7-Segment Display Decoder

../_images/decoder_0010.jpg

Fig. 4.2.1 Picture of the finished 7-segment display encoder where only switch button SW1 is active. The decoder connects the buttons SW3, SW2, SW1 and SW0 with the display HEX0. Thus, 0010 is the visualized binary number which corresponds to the shown hex number 2.

Let’s start our FPGA journey by designing a decoder which connects some of the board’s switch buttons to a hexadecimal display. For this we’ll use the first four switch buttons SW3, SW2, SW1 and SW0. Each switch button can either be 0 (down) or 1 (up). We may represent a hexadecimal number alternatively through exactly four binary digits. Thus, in our design, the configuration of the four switch buttons will encode a single hexadecimal number.

Our hexadecimal displays consists of seven segments. While the DE10-Lite board has six of these displays, we’ll only use the right-most one, i.e., HEX0, in this task. The segments of the display are active low. This means you have to set the value 1 for a specific segment to deactivate it.

../_images/display.svg

Fig. 4.2.2 Illustration of the hexadecimal digits 0-F when shown on a seven-segment display of the DE10-Lite board. The numbers give the ids of the segments which are active low.

Fig. 4.2.2 illustrates all sixteen outputs our decoder might have. For example, for the hex digit 0 all but segment 6 have to be active. This means that we have to set all but the seventh bit of HEX0 to 0, i.e., set HEX0[6:0] to 7'b100_0000. If we’d like to encode the hex digit 1, we’d set all bits to 1 except the second and third bit, i.e., set HEX0[6:0] to 7'b111_1001. The respective (incomplete) truth table for our decoder is given in Table 4.2.1.

Table 4.2.1 Truth table of the decoder.

Hex Number

SW[3:0]

HEX0[6:0]

0

4'b0000

7'b100_0000

1

4'b0001

7'b111_1001

2

4'b0010

7'b010_0100

3

4'b0011

7'b011_0000

4

4'b0100

7'b???_????

5

4'b0101

7'b???_????

6

4'b0110

7'b???_????

7

4'b0111

7'b???_????

8

4'b1000

7'b???_????

9

4'b1001

7'b???_????

A

4'b1010

7'b???_????

B

4'b1011

7'b???_????

C

4'b1100

7'b???_????

D

4'b1101

7'b???_????

E

4'b1110

7'b???_????

F

4'b1111

7'b???_????

Once again, to kickstart your work, templates for your modules are provided. The template for the decoder module is located in Listing 4.2.1. This template only provides the decoding cases for the two binary numbers \(0000_2\) and \(0001_2\). The remaining 14 numbers shown in Table 4.2.1 have to be added still.

Listing 4.2.1 Template for the module decoder.
 1/**
 2 * Decoder for a seven-segment display.
 3 *
 4 * @param i_binary_number input binary number.
 5 * @param o_display output bits driving the seven-segment display.
 6 **/
 7module decoder( input  logic [3:0] i_binary_number,
 8                output logic [6:0] o_display );
 9  always_comb
10  begin
11    case( i_binary_number )
12      4'b0000: o_display[6:0] = 7'b100_0000; // 0
13      4'b0001: o_display[6:0] = 7'b111_1001; // 1
14      // TODO: add remaining hex digits 2-F
15      default: o_display[6:0] = 7'bxxx_xxxx;
16    endcase
17  end
18endmodule

Listing 4.2.2 provides a template for the top-level module. The top-level module simply wraps our decoder. Most importantly, we name decoder_de10_lite’s input SW and the output HEX0. This naming scheme allows Quartus Prime to recognize that the SW should be the input of the first three switch buttons and HEX0 the first hex display.

Note

Additional signal names are available from the DE10-Lite User Manual. For example, the fifth LED has the name LEDR4. We could also use Quartus Prime’s pin assignment to assign the boards to our modules’ inputs and outputs. However, using top-level modules with respective names is less cumbersome in the development phase.

Listing 4.2.2 Template for the module decoder_de10_lite.
 1/**
 2 * Top-level entry for the seven-segment display decoder.
 3 * The decoder shows the binary number represented
 4 * by SW3-SW0 of a DE10 Lite board on HEX0 as a hex number.
 5 *
 6 * @param SW bits of switch buttons SW3, SW2, SW1 and SW0.
 7 * @param HEX0 output bits which drive the seven-segment display HEX0.
 8 **/
 9module decoder_de10_lite( input  logic [3:0] SW,
10                          output       [6:0] HEX0 );
11  // TODO: use your decoder to connect SW to HEX0
12endmodule

Tasks

  1. Add the missing encodings for the seven-segment display to Table 4.2.1.

  2. Implement the module decoder. Use the template in Listing 4.2.1.

  3. Finish the implementation of the module decoder_de10_lite. Use Listing 4.2.2’s template.

  4. Compile your modules in Quartus Prime and program the FPGA of a DE10-Lite board.

  5. Make sure that the board shows the correct hexadecimal numbers in display HEX0 for all combinations of the switch buttons SW0, SW1, SW2, SW3. Provide three pictures of the programmed FPGA for the hex numbers 7, A and E. One should see the hex display and the switch button configuration in your pictures. An example is given in Fig. 4.2.1.

4.3. Tiny Calculator

Now that we have a way to display four-digit binary numbers, let’s up the game a bit. In this task we’ll implement a tiny calculator which takes two four-bit binary numbers \(A_{[3:0]}\) and \(B_{[3:0]}\) as inputs. We use the input from the first four switch buttons in SW[3:0] for \(A_{[3:0]}\) and those of the next four in SW[7:4] for \(B_{[3:0]}\). Our tiny calculator computes the 5-bit result \(C_{[4:0]} = A_{3:0} + B_{3:0}\). We make the user aware of our inputs by displaying \(A_{[3:0]}\) on display HEX0 and \(B_{[3:0]}\) on display HEX1. The result \(C_{[4:0]}\) has a total of five bits due to the carry out. Thus, we require two hex numbers for \(C_{[4:0]}\): One for the four bits 3-0 which we display on HEX2 and one for the fourth bit which we display on HEX3.

../_images/calculator.jpg

Fig. 4.3.1 Picture of the tiny calculator in action. In the shown configuration only switch buttons SW0, SW1 and SW5 are active. This means that \(A_{[3:0]} = 0011_2\), \(B_{[3:0]} = 0010_2\) and \(C_{[4:0]} = 00101_2\). We see that the corresponding hexadecimal representations are shown on displays HEX0 - HEX3, i.e., \(0011_2 = 3_{16}\) on HEX0, \(0010_2 = 2_{16}\) on HEX1 and \(00101_2 = 05_{16}\) on HEX2 and HEX3.

A picture of an exemplary setting is provided in Fig. 4.3.1. In the shown case SW[3:0] = 4'b0011 and SW[7:4] = 4'b0010. The corresponding hexadecimal numbers \(3_{16}\) and \(2_{16}\) are shown on HEX0 and HEX1. Additionally, displays HEX2 and HEX3 show the result \(05_{16}\).

Table 4.3.1 Inputs and (incomplete) outputs required for testing your tiny_calculator module.

SW[3:0]

SW[7:4]

HEX0[6:0]

HEX1[6:0]

HEX2[6:0]

HEX3[6:0]

0000

0000

100_0000

100_0000

100_0000

100_0000

0011

0010

???_????

???_????

???_????

???_????

0111

0001

???_????

???_????

???_????

???_????

1000

1000

???_????

???_????

???_????

???_????

1111

1111

???_????

???_????

???_????

???_????

Listing 4.3.1 Template for the module tiny_calculator.
 1/**
 2 * Top-level entry of the tiny calculator.
 3 * The calculator adds the 4-bit binary numbers in SW[3:0] and SW[7:4].
 4 * The two inputs are shown on displays HEX0 and HEX1.
 5 * The result of the addition is shown on displays HEX2 and HEX3.
 6 *
 7 * @param SW bits of switch buttons SW7, SW6, SW5, SW4, SW3, SW2, SW1 and SW0.
 8 * @param HEX0 output bits which drive the seven-segment display HEX0.
 9 * @param HEX1 output bits which drive the seven-segment display HEX1.
10 * @param HEX2 output bits which drive the seven-segment display HEX2.
11 * @param HEX3 output bits which drive the seven-segment display HEX3.
12 **/
13module tiny_calculator( input  logic [7:0] SW,
14                        output logic [6:0] HEX0,
15                        output logic [6:0] HEX1,
16                        output logic [6:0] HEX2,
17                        output logic [6:0] HEX3 );
18  // TODO: implement the tiny calculator
19endmodule

Tasks

  1. Implement the module tiny_calculator. Use the template in Listing 4.3.1 as a basis for your module. Use your four-bit ripple carry adder of Section 3.3 for the addition.

  2. Add the testbench tiny_calculator_tb for your tiny_calculator module. Check that your calculator produces the correct outputs, i.e., the bits HEX0[6:0], HEX1[6:0], HEX2[6:0], HEX3[6:0] for all inputs in Table 4.3.1.

  3. Compile your module in Quartus Prime and program the FPGA of the DE10-Lite board.

  4. Make sure that the board’s displays show the correct hexadecimal numbers for the configurations in Table 4.3.1. Provide a picture for each of the five configurations.