Circuit simulation with hardware description languages

Test Run

© Lead Image © donatas1205, 123RF.com

© Lead Image © donatas1205, 123RF.com

Article from Issue 272/2023
Author(s):

Designing field-programmable gate arrays is only half the job: The hardest part is the simulation, but Linux is the best place to tackle certain challenges.

In the previous issue of Linux Magazine, I introduced the digital integrated circuits called field-programmable gate arrays (FPGAs) [1], explained why they are important, and described the bare minimum needed to set up and try a state-of-the-art FPGA design environment on Linux. This article, which is divided into three parts, concludes that introduction.

First I'll describe the main types of elementary digital circuits that can be built, combining the logic gates covered in the first tutorial and the basic features of hardware description languages (HDLs) that allow their implementation and simulation.

The central part combines some HDL code that's freely available online [2] [3] into one simple HDL module and the code necessary to simulate it. The final part shows a run of that simulation and concludes by explaining why Linux is the perfect platform for working with FPGAs.

Representing Digital Circuits with HDL

FPGAs (and every other digital integrated circuit) comprise millions of logic gates, with boolean input and output signals – that is, having one of two states, conventionally represented as 1 or 0.

Sequential components are combinations of logic gates that sample all their input ports, and (with constant, predefined delays) update their outputs on the positive or negative fronts of one or more signals called clocks (i.e., square waves of a fixed frequency that constitute common timing references for the chip).

The simplest sequential block is a memory element, also called a flip-flop [4], that can load and preserve binary values: Its output changes only on the fronts of a control signal, almost always a clock, copying and then preserving whatever value the input pin has in those instants.

You can think of combinational logic circuits as the exact opposite of sequential circuits, because they use no clock signal and have no memory. Their output never depends on a previous value but changes immediately every time one of the inputs changes. In practice, there will always be some delay, which will depend on the complexity of the wiring.

Synchronous circuits are what you get by attaching the outputs of a combinational circuit to the inputs of a memory element. The inputs of the combinational circuit are generic signals combined in a feedback loop with the outputs of that same memory element. The simplest synchronous circuit possible is probably the toggle [4] (Figure 1). The triangle represents a combinational inverter, whose output is always the opposite of its input: When Q is 1, D is 0, and vice versa. Q, however, is only updated when the clock (clk) signal has a positive transition and thus has the behavior shown in Figure 2.

Figure 1: The simplest combination of memory and combinational logic: a toggle that inverts its value at every clock cycle.
Figure 2: The feedback loop of Figure 1 generates an output that is synchronous with the clock, at half the frequency.

Of course, synchronous circuits can be much more complicated than a toggle. Theoretically, a synchronous circuit can have any number of memory elements and input signals in addition to the clock. Consequently, the output of a synchronous circuit will be multibit binary buses, not single wires, and the combinational part could be a very complex boolean function of all the inputs and memory elements together.

The presence of a clock signal that makes the outputs change only at predefined instances makes synchronous circuits very reliable, but with a significant constraint: They behave as designed only if the inputs of all their memory elements are always stable when a clock transition comes to sample them. Because those signals come out of the combinational part of the circuit, that part cannot have so many levels of logic gates that signal propagation through it would take more than one clock cycle.

Aside from memory banks, the most common type of synchronous circuit is the finite-state machine (FSM), which is an "abstract machine that can be in exactly one of a finite number of states at any given time" [5]. In practice, the state of an FSM at any clock cycle can depend on the whole sequence of transitions it experienced in the previous N clock cycles, where N depends on how many states and input signals the FSM has and how complex its combinational part is. Because that "state" could represent pretty much anything, from a plain counter to the phases of a real-time cryptographic algorithm, FSMs are the most common component of many HDL designs.

Making It in HDL

To facilitate design and reuse, digital circuits are partitioned in hardware modules that can contain submodules; however, as far as the rest of the circuit is concerned, it is basically a black box.

Digital designers create models of their module with HDL languages such as VHDL or Verilog. I focus on Verilog in this article, but almost everything you learn here applies to VHDL as well.

HDLs add two classes of features to those present in software programming languages. One class comprises all the operators and constructs needed to describe the physical characteristics of the logic gates, such as the capability to change only on clock transitions. The other class includes everything needed to simulate and analyze the real-time behavior of a circuit model.

HDLs let designers describe blocks that will always operate and be simulated in parallel with all the other blocks of the design. If you follow the rules, the behavior of every block will always be the same regardless of the order in which all the other blocks are instantiated. The other main features are the data types and syntax constructs that support modeling the circuit types.

Reg vs. Wires

The most important of HDL language data types are wires and regs (registers). Both wires and regs can store multiple bits.

Wires correspond to logical "nets" (i.e., connections between circuit elements). Although wires can represent actual direct connections between two or more modules, in those cases, you cannot assign values to a wire (except by forcing it, for debug purposes), because it's only driven by modules. You may, however, define a local wire whose value is an instantaneous Boolean function of some other signal. In all cases, when the moment comes to create the circuit, wires always translate to combinational logic.

Unlike wires, regs are a Verilog data type that can store values. The values must be explicitly calculated in the HDL code. Depending on how you write that code, regs can become either combinational or sequential logic.

HDL code that explicitly declares every reg variable that must exist in the target silicon and explicitly calculates its values is called Register Transfer Level (RTL) code. Well-written RTL code might take more time to write than higher level code, but you will have a much higher probability of generating a physical circuit that behaves exactly like its HDL model without having to declare and connect every single logic gate manually to all the others.

The deliberately dumb code of Listing 1 shows both kinds of data types and the Verilog syntax to handle them at the RTL level.

Listing 1

Sample Verilog Code

01 module sample_counter;
02   input CLK;                      // Clock signal
03   input RST;                      // Reset signal, active low
04   output reg[23:0] COUNTER;       // 24-bit counter;
05   output wire SOME_OTHER_SIGNAL;  // single bit output
06   wire  EIGHT_PULSE;
07
08   always @(posedge CLK)
09   begin
10   if (RST == 1;b0)
11     COUNTER <=  24'd0;     // set all the bits of COUNTER to 0
12   else
13     COUNTER <= COUNTER + 24'd1;   // 24 bit adder
14   end
15
16   assign EIGHT_PULSE = (COUNTER[2:0] == 3'd0) ? 1'b1: 1'b0;
17
18   controller ctl (EIGHT_PULSE, SOME_OTHER_SIGNAL);
19
20 endmodule

In Verilog, modules are enclosed by the module and endmodule keywords. Verilog modules always have a unique name and can instantiate other modules. Listing 1 describes a simple module called sample_counter that contains an instance called ctl of another module called controller.

The first five lines after the module declaration (lines 2-6) list all the input and output ports of the module and their nature: two single-bit inputs, CLK and RST; two outputs, COUNTER and SOME_OTHER_SIGNAL; and a local single-bit wire, EIGHT_PULSE. COUNTER is a 24-bit-wide reg, and SOME_OTHER_SIGNAL is a single-bit wire. Comments have the same syntax as in C. I recommend you use comments extensively to document what each signal is and how the module works.

Between the declaration of the variables and the instantiation of the ctl submodule, sample_counter contains two blocks, or processes, that work concurrently. The always block starting on line 8 declares that the value of COUNTER must be updated at every positive edge of the CLK signal with a non-blocking assignment. As long as RST is low, COUNTER will be zero; otherwise, its value will be incremented by 1. It is important to note the syntax of the non-blocking assignment operator (<=) and that you should always declare the width of numeric constants. If line 11 said 5'd0, for example, the five least significant bits of COUNTER would be set to zeroes, but all the other bits might end up with undefined values.

See the "Blocking" box for more on what blocking and non-blocking means in HDL. For now, just take note that because it uses the non-blocking assignment and works on clock edges, the always block will be synthesized as a synchronous 24-bit counter.

Blocking

Blocking assignments are just like "normal" software assignments and always translate to combinational logic. The name describes the fact that they "block" the simulator, which must execute them serially, one at a time, in the order they appear. The obvious consequence is that, all inputs being equal, the complete output of a series of blocking assignment will depend on their order. In other words, blocking assignments are one of the most common ways for inattentive novice HDL designers to hurt themselves, producing code that mysteriously does not work as they wanted.

Non-blocking assignments are almost the opposite. For example, assume that a simulation has a time resolution of 1ns; that is, it must calculate all the values of all the wires and registers it contains every nanosecond. Non-blocking means that, at the beginning of each nanosecond, the simulator:

  • samples the values in that instant of all the variables that appear on the right side of all the non-blocking statements it contains, at every level of the hierarchy, and
  • then uses those sampled values (some of which might be feedback loops!) to calculate the next values of all the variables on the left side of all the non-blocking statements.

The ability of HDL simulators not to block themselves when calculating any next value before they have collected all the inputs for all the assignments made with the <= operator is what emulates the real behavior of real hardware registers. Working in that way, the result of each statement does not depend on the order in which all the non-blocking statements are written.

Line 13 shows the beauty and power of HDL languages: You don't need to know, or write down in detail, how 24-bit binary addition actually works. Just use +, and the Verilog compiler, synthesizer, and simulator take care of all the implementation and simulation details.

The same considerations apply to the last piece of the sample_counter: The assign keyword that controls EIGHT_PULSE is a continuous blocking assignment, because it uses = instead of <=, which runs every time any of the three least significant bits of COUNTER change (that is what COUNTER[2:0] means). When all those bits are zero, that is when COUNTER is   or 8, 16, …; then, EIGHT_PULSE becomes immediately high; otherwise, it is low. Because it uses the assign keyword and the blocking assignment, this part of the module will be synthesized as combinational logic.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Qiskit

    Qiskit is an open source framework that aims to make quantum computing technology both understandable and ready for production.

  • Introduction to FPGA Design

    Learn what FPGAs are, how they work, and how to design FPGA integrated circuits on Linux.

  • Logisim

    Throw out the cumbersome pencil and paper; you can design, draw, and test digital circuits with Logisim.

  • Pico Sleep Mode

    The Raspberry Pi Pico's high-performance chip is trimmed for I/O and does not try to save power. However, a few tricks in battery mode can keep it running longer.

  • New Tech Retrofit

    An electronic project at a local science center was showing its age, calling for a refresh: in this case, rebuilding it almost from scratch with an Arduino instead of relays.

comments powered by Disqus