Digital Electronics

Chapter 9
Hardware Description Languages and RTL Design

Post Graduate Program in Electrical Engineering
Digital Electronics   •   Post Graduate

9. Hardware Description Languages and RTL Design

Introduction and Context

Digital systems are no longer designed primarily by drawing gates and wiring them manually. Modern processors, communication interfaces, controllers, accelerators, and embedded systems contain millions or billions of transistors. Their behaviour is therefore specified using hardware description languages (HDLs), simulated to verify functional correctness, and synthesized into gates, registers, memories, and interconnections. Hardware description languages provide the abstraction needed to describe both what a circuit does and, when required, how its constituent hardware is organized.

The most widely used HDLs in contemporary digital design are Verilog, SystemVerilog, and VHDL. SystemVerilog extends Verilog with stronger typing, interfaces, packages, assertions, object-oriented verification features, and constructs specifically useful for RTL design. VHDL provides strong typing, explicit design units, packages, and a highly structured syntax. Although their syntax differs, both languages support behavioural, dataflow, and structural descriptions and can be used to create synthesizable register-transfer-level (RTL) designs.

RTL is the abstraction level at which a synchronous digital system is described in terms of registers, combinational logic, and the transfer of data between registers during clock events. RTL design is central to field-programmable gate array (FPGA) development and application-specific integrated circuit (ASIC) design. A successful RTL description must be functionally correct, synthesizable, timing-aware, parameterised where appropriate, reusable, and sufficiently clear for review and maintenance.

Learning Objectives

After studying this chapter, the reader should be able to:

1. Explain the role of HDLs and distinguish behavioural, dataflow, and structural modelling.

2. Describe the relationship among RTL, simulation, synthesis, and implementation.

3. Write synthesizable SystemVerilog RTL for combinational and sequential circuits.

4. Use parameters, generate constructs, packages, and interfaces to create reusable designs.

5. Identify common coding errors involving incomplete assignments, blocking and non-blocking assignments, reset design, and unintended latches.

6. Apply linting, assertions, version control, and review practices to RTL development.

7. Evaluate design trade-offs involving area, timing, power, portability, and maintainability.

From Hardware Intent to Implemented Circuit

An HDL source file is not itself a circuit. It is an executable specification that may be interpreted in two different ways. A simulator executes the description according to the language's event-driven semantics. A synthesis tool analyzes the same description and attempts to construct an equivalent hardware network from a synthesizable subset of the language. The circuit inferred by synthesis depends on the statements used, their context, and the assumptions made about clocks, resets, and incomplete conditions.

The design flow commonly proceeds through specification, microarchitecture, RTL coding, simulation, linting, synthesis, static timing analysis, implementation, and hardware verification. The RTL designer must consider not only logical function but also clock frequency, latency, resource use, reset behaviour, parameter limits, and interfaces with other blocks.

RTL describes transfers between storage elements. For example, the statement

```systemverilog

always_ff @(posedge clk)

q <= d;

```

represents a bank of flip-flops. The value of `d` is sampled at the active clock edge and appears at `q` after that event. In contrast, the expression

```systemverilog

assign y = a & b;

```

describes combinational logic whose output responds continuously to its inputs. This distinction between time-independent combinational relationships and edge-triggered state updates is the foundation of RTL reasoning.

Three Ways of Describing Hardware

Behavioural modelling

Behavioural modelling expresses the operation of a circuit using procedural statements. It is particularly useful for finite-state machines, counters, controllers, and algorithms. The description emphasizes the required behaviour rather than explicitly naming every gate.

```systemverilog

always_ff @(posedge clk or negedge rst_n) begin

if (!rst_n)

count <= '0;

else if (enable)

count <= count + 1'b1;

end

```

This code describes a register that resets asynchronously and increments when enabled. The synthesis tool infers flip-flops, reset circuitry, and an adder. Behavioural RTL is usually the most productive style, but it must be written within a synthesizable subset and with a clear understanding of what hardware each construct implies.

Dataflow modelling

Dataflow modelling describes relationships among signals using continuous assignments or concurrent expressions. It is natural for combinational logic, arithmetic equations, multiplexers, and decoders.

```systemverilog

assign y = select ? b : a;

assign parity = ^data;

```

The first expression infers a two-input multiplexer, while the second computes reduction XOR. Dataflow code is concise and directly exposes combinational dependencies. Excessively complex expressions, however, can obscure critical paths and make timing analysis or review more difficult.

Structural modelling

Structural modelling instantiates components and connects them explicitly. It resembles a schematic represented as text and is useful for hierarchy, technology-independent composition, repeated structures, and integration of separately verified modules.

```systemverilog

alu_slice u0 (.a(a[0]), .b(b[0]), .cin(cin), .sum(sum[0]), .cout(c1));

alu_slice u1 (.a(a[1]), .b(b[1]), .cin(c1), .sum(sum[1]), .cout(cout));

```

Structural descriptions provide precise control over composition but can become verbose and error-prone for regular large structures. Generate loops and parameterised modules reduce this problem.

These modelling styles are not mutually exclusive. A practical design may use behavioural RTL for a controller, dataflow expressions for local combinational logic, and structural instantiation for memories, arithmetic units, or vendor-specific primitives.

Synthesizable RTL Semantics

Combinational logic

A combinational process must assign every output for every possible input condition. In SystemVerilog, `always_comb` communicates this intent and allows tools to detect certain errors.

```systemverilog

always_comb begin

y = '0;

case (sel)

2'b00: y = a;

2'b01: y = b;

2'b10: y = c;

default: y = d;

endcase

end

```

The default assignment prevents an inferred latch. A latch appears when the output must retain its previous value because some execution path does not assign it. Latches may be intentional, but accidental latches are a frequent source of timing, verification, and portability problems.

Sequential logic

Sequential logic stores state. The preferred SystemVerilog construct is `always_ff`, normally with non-blocking assignments.

```systemverilog

always_ff @(posedge clk) begin

if (reset)

q <= '0;

else

q <= next_q;

end

```

Non-blocking assignment schedules the update without allowing the newly assigned value to affect subsequent statements in the same clocked process. This models the simultaneous behaviour of physical flip-flops. Blocking assignments are generally used for intermediate calculations in combinational processes, although an individual organization may impose stricter rules.

A common and readable architecture separates next-state logic from state registers:

```systemverilog

always_comb begin

next_state = state;

if (start)

next_state = BUSY;

if (done)

next_state = IDLE;

end

always_ff @(posedge clk or negedge rst_n) begin

if (!rst_n)

state <= IDLE;

else

state <= next_state;

end

```

The designer must define the priority when multiple conditions can occur. Sequential statements in a procedural block do not imply sequential hardware operations; the clocked registers update together, while priority is inferred from conditional ordering in next-state logic.

Blocking and non-blocking assignments

Using blocking assignments in clocked logic can cause simulation ordering to differ from the intended flip-flop behaviour, especially when several registers are updated in one process. Conversely, careless use of non-blocking assignments in combinational code can create delta-cycle effects and obscure combinational intent. The essential rule is to choose the assignment style that matches the hardware abstraction and to apply a consistent project coding standard.

Four-state values and type discipline

Simulation values include 0, 1, unknown (`X`), and high impedance (`Z`). Unknown propagation can expose uninitialized registers, incomplete assignments, and multiple drivers. It can also produce differences between simulation and synthesized hardware because synthesis may optimize away logic whose simulation value is unknown.

Strongly typed declarations and explicit widths reduce errors. Unsized constants, signed arithmetic, implicit casting, and mixed-width expressions can produce unexpected extension or truncation. Designers should declare signedness deliberately and use local parameters for widths and limits.

Parameterisation and Reuse

A reusable module should expose genuine design choices without allowing invalid configurations. Parameters can control data width, depth, number of channels, or implementation options.

```systemverilog

module fifo #(

parameter int WIDTH = 32,

parameter int DEPTH = 16

) (

input logic clk,

input logic rst_n,

input logic wr_en,

input logic rd_en,

input logic [WIDTH-1:0] din,

output logic [WIDTH-1:0] dout,

output logic full,

output logic empty

);

```

Parameterisation affects more than vector widths. A FIFO depth that is not a power of two may require special pointer comparisons; a depth of one may require different logic; and zero or negative values must be rejected. Elaborative checks, such as assertions or generate-time conditions, should make invalid configurations fail early.

The `$clog2` system function is useful for calculating address widths, but boundary cases require care. For example, `$clog2(1)` is zero, which may create a zero-width vector if used without protection. A safe width expression often ensures a minimum width of one.

Generate constructs create repeated or conditional hardware at elaboration time:

```systemverilog

genvar i;

generate

for (i = 0; i < LANES; i++) begin : g_lane

processing_lane #(.WIDTH(WIDTH)) u_lane (...);

end

endgenerate

```

Packages centralize types, constants, functions, and enumerations. Interfaces group related signals and can include modports that specify directions for different users. These mechanisms improve consistency across large projects, but excessive abstraction can make tool support and debugging more difficult. Reuse is valuable only when the module's assumptions, timing, reset, and parameter constraints are documented.

Finite-State Machines and RTL Architecture

A finite-state machine (FSM) consists of a state register, next-state logic, and output logic. Moore outputs depend on state, whereas Mealy outputs depend on state and inputs. Moore machines often provide simpler timing and fewer glitches; Mealy machines may reduce latency but require careful treatment of input changes.

An enumerated type makes state intent explicit:

```systemverilog

typedef enum logic [1:0] {IDLE, READ, WRITE, ERROR} state_t;

state_t state, next_state;

```

The designer should define reset state, illegal-state behaviour, output defaults, and transitions for all relevant conditions. A `default` branch can recover from an illegal encoded state, although formal verification and reset analysis are still needed. State encoding—binary, one-hot, or another scheme—may be selected by synthesis or constrained by the designer. The choice affects area, timing, power, and susceptibility to certain faults.

Verification, Linting, and Design Quality

Simulation demonstrates behaviour for selected test cases; it does not prove correctness for all possible inputs. A professional RTL flow combines several forms of analysis.

Linting examines syntax, style, width mismatches, inferred latches, multiple drivers, unreachable code, incomplete case statements, unsafe clock-domain crossings, and other patterns associated with defects. Lint rules should be agreed upon early and treated as quality gates rather than optional cosmetic checks.

Assertions express properties that must hold. For example, a request may be required to remain asserted until acknowledgement, or a FIFO may never report both empty and full. SystemVerilog Assertions can be evaluated during simulation and, in suitable tools, formal verification. Assertions turn design assumptions into executable documentation.

Functional coverage measures whether important states, transitions, input combinations, and corner cases have been exercised. Code coverage is useful but insufficient: executing every line does not establish that the correct behaviours have been checked. Formal methods can prove properties over all legal input sequences, particularly for control logic and protocol rules.

Clock-domain crossing (CDC) deserves special attention. A signal crossing between unrelated clocks can become metastable. Single-bit controls generally require synchronizers; pulses may require pulse-stretching or handshake protocols; multi-bit data commonly requires an asynchronous FIFO or a carefully designed transfer protocol. RTL simulation alone cannot model all physical metastability effects.

A Disciplined RTL Development Workflow

A practical workflow begins with an unambiguous specification. The designer identifies clock domains, reset polarity and synchrony, interface timing, legal and illegal inputs, latency, throughput, and parameter constraints. A block diagram and timing diagrams are often more useful at this stage than immediate coding.

The RTL is then organized into small modules with one clear purpose. Signals should have meaningful names, widths should be explicit, and interfaces should be documented. Combinational and sequential logic should be visibly separated. After each functional increment, the design should be compiled, linted, simulated, and reviewed.

Version control is an engineering requirement rather than an administrative convenience. HDL files, packages, testbenches, scripts, constraints, documentation, and tool-version information should be managed in a repository. Commits should represent coherent changes, and branches or pull requests should support peer review. Generated files and machine-specific outputs should normally be excluded. Reproducible builds require controlled tool versions, explicit file lists, and documented configuration.

Applied Case: Parameterised Streaming Packet Buffer

Background

Consider an illustrative design for an FPGA-based measurement instrument. Samples arrive on a streaming input at a fixed clock rate. Groups of samples must be buffered and transmitted to a processing engine. The buffer must support configurable sample width and depth and must provide backpressure when it cannot accept more data.

Problem

A first implementation used a fixed-width array and separate counters for writing and reading. Simulation passed ordinary traffic, but parameter changes caused pointer overflow, and simultaneous read and write operations produced inconsistent full and empty flags. A combinational process also omitted assignments under one condition, creating an unintended latch.

Analysis

The design was recast as a synchronous FIFO. Its state variables were write pointer, read pointer, occupancy count, and memory contents. The interface was defined using `valid` and `ready`: a transfer occurs only when both are high. Full and empty conditions were derived from occupancy rather than from ambiguous pointer equality. The design team documented whether the memory permits simultaneous read and write at the same location and specified the expected read latency.

Approach

The FIFO was parameterised by `DATA_WIDTH` and `DEPTH`. Pointer widths were calculated with a protected minimum width. The occupancy counter used enough bits to represent every value from zero through `DEPTH`. Sequential state updates were placed in `always_ff`; combinational flag logic used defaults and `always_comb`. Assertions checked that occupancy remained within bounds, that a read was not accepted when empty, and that a write was not accepted when full.

The verification environment tested depth one, non-power-of-two depths, simultaneous operations, reset during inactivity, maximum-rate traffic, and prolonged backpressure. Linting detected an unsigned comparison and a width mismatch in an intermediate expression. Version control preserved the failing test, correction, and review record.

Outcome

The resulting module was reused in three instrument configurations. The parameter checks prevented unsupported configurations from reaching synthesis, and the assertions exposed a protocol error in the upstream producer. The design became easier to integrate because its timing, reset behaviour, and handshake rules were explicit.

Learning Points

The case illustrates that parameterisation is not merely replacing constants with parameters. Boundary conditions, arithmetic widths, memory semantics, verification assumptions, and interface contracts must all be reconsidered. It also demonstrates the value of combining RTL discipline, linting, assertions, and version control.

Common Failure Modes and Professional Responsibilities

Frequent RTL mistakes include incomplete combinational assignments, accidental multiple drivers, incorrect reset sensitivity, use of the wrong assignment operator, implicit truncation, unsynchronized clock-domain crossings, and dependence on simulation initialization. Another error is coding an algorithm without estimating its hardware cost. A division, variable shift, large comparison, or deeply nested conditional may infer substantial area or create a critical timing path.

Reset design requires system-level judgment. Asynchronous assertion with synchronous deassertion is common in some systems, but the appropriate choice depends on clock availability, technology, reliability requirements, and integration methodology. Resetting every data register may increase routing and power cost; not resetting control state may compromise safe startup.

Professional designers must also consider intellectual property licensing, security, safety, and traceability. Hardware can implement access-control failures, information leaks, unsafe defaults, or exploitable fault conditions. Safety-critical designs require documented requirements, review evidence, verification traceability, and compliance with applicable organizational or industry standards. HDL source should be treated as an engineering artifact subject to controlled change.

Current Developments

Modern RTL practice increasingly combines simulation with formal property checking, automated linting, CDC analysis, and continuous integration. High-level synthesis can generate RTL from C, C++, or domain-specific languages, but the resulting implementation still requires timing, interface, reset, resource, and verification analysis. SystemVerilog interfaces, assertions, and reusable packages support larger design teams, while open-source simulators and synthesis tools improve accessibility.

Machine-learning-assisted coding and verification tools can suggest RTL, identify patterns, or generate test stimuli. They do not remove the need for specification ownership, review, security analysis, or independent verification. Generated code may contain incorrect assumptions, poor parameter handling, or constructs unsupported by the target toolchain. The responsible use of automation therefore includes constrained prompts or templates, linting, formal checks, simulation, and human approval.

Best Practices

A robust RTL practice can be summarized as follows:

  • Define clock, reset, latency, handshake, and parameter assumptions before coding.
  • Use `always_comb`, `always_ff`, explicit widths, meaningful names, and consistent signedness.
  • Assign every combinational output on every path and use non-blocking assignments for registers.
  • Separate state, next-state, and output logic where this improves reviewability.
  • Make parameter limits explicit and test boundary configurations.
  • Treat lint, CDC analysis, assertions, and code review as part of design, not as final cleanup.
  • Keep RTL, tests, constraints, scripts, and documentation under version control.
  • Verify behaviour at module boundaries and consider synthesis and timing consequences of every abstraction.

Chapter Summary

Hardware description languages allow digital systems to be specified at several abstraction levels. Behavioural descriptions express operations procedurally, dataflow descriptions express signal relationships, and structural descriptions compose explicit modules. RTL focuses on transfers among registers through combinational logic and is the principal design representation for FPGA and ASIC implementation.

Synthesizable RTL requires disciplined treatment of combinational completeness, clocked state, assignment semantics, widths, signedness, reset, and clock-domain crossings. Parameterisation, generate constructs, packages, and interfaces promote reuse, but only when configuration limits and behavioural assumptions are made explicit. Quality depends on a flow combining simulation, linting, assertions, coverage, formal analysis, CDC checking, synthesis, timing analysis, peer review, and version control. The professional RTL designer therefore manages not only syntax and function but also implementation cost, verification evidence, maintainability, and system-level risk.

Key Terms

HDL: A language used to describe the behaviour or structure of digital hardware.

SystemVerilog: An HDL extending Verilog with RTL, verification, typing, assertion, and design-organization features.

VHDL: A strongly typed HDL used for modelling, simulating, and synthesizing digital systems.

RTL: Register-transfer level; an abstraction describing registers and the logic transferring data among them.

Synthesis: The conversion of synthesizable HDL into an implementation technology's logic representation.

Simulation: Execution of an HDL model using event-driven semantics to observe behaviour.

Behavioural modelling: Describing intended operations using procedural constructs.

Dataflow modelling: Describing continuous relationships among signals and expressions.

Structural modelling: Describing hardware through explicit component instantiation and connections.

Combinational logic: Logic whose outputs depend only on current inputs.

Sequential logic: Logic containing state whose values depend on clocked or other storage events.

Latch: Level-sensitive storage inferred when a combinational output is not assigned on all paths.

Parameterisation: Making module characteristics configurable at elaboration time.

Generate construct: An elaboration-time mechanism for conditional or repeated hardware creation.

FSM: Finite-state machine consisting of state storage, transition logic, and outputs.

Linting: Static analysis that identifies suspicious, unsafe, or non-compliant RTL patterns.

Assertion: An executable statement specifying a property that should hold during operation.

CDC: Clock-domain crossing; transfer of signals between different or unrelated clock domains.

Non-blocking assignment: A procedural assignment whose update is scheduled, commonly used for clocked RTL.

Version control: A system for tracking, reviewing, and reproducing changes to design artifacts.

Review Questions

Short Answer Questions

1. What distinguishes RTL from a purely behavioural software description?

2. Why can incomplete assignments in `always_comb` infer latches?

3. State the usual assignment convention for clocked and combinational RTL.

4. What is the purpose of a parameterised module?

5. Why is linting useful even when simulation tests pass?

Long Answer Questions

1. Compare behavioural, dataflow, and structural modelling, including suitable applications and limitations of each.

2. Explain how a synthesizer interprets combinational and sequential SystemVerilog constructs.

3. Describe a disciplined workflow for developing, verifying, synthesizing, and maintaining an RTL module.

4. Explain the principal design and verification issues in a parameterised FIFO.

5. Discuss the role of assertions, coverage, formal verification, and CDC analysis in professional RTL development.

Analytical / Application Questions

1. An `always_comb` block assigns an output only when `enable` is one. Identify the inferred hardware and propose two RTL corrections, depending on the intended function.

2. A parameterised counter works for widths 8 and 16 but fails at width 1. Analyze the likely causes involving widths, constants, and boundary expressions, and propose a verification strategy.

3. A controller receives a one-cycle pulse from an unrelated clock domain. Select an appropriate transfer method, explain why direct sampling is unsafe, and describe the RTL and verification checks required.

22. Multiple Choice Questions with Answers

Question 1

Which RTL construct most directly represents a bank of edge-triggered flip-flops?

A. assign y = a & b;

B. always_comb

C. always_ff @(posedge clk)

D. generate for

Correct Answer: C. always_ff @(posedge clk)

Question 2

What hardware is commonly inferred when a combinational output is not assigned on every execution path?

A. A latch

B. A synchronizer

C. A decoder with no storage

D. An asynchronous FIFO

Correct Answer: A. A latch

Question 3

Why are non-blocking assignments normally used for clocked RTL?

A. They force all logic to become combinational

B. They model simultaneous register updates at a clock event

C. They prevent synthesis of flip-flops

D. They eliminate all unknown simulation values

Correct Answer: B. They model simultaneous register updates at a clock event

Question 4

Which modelling style explicitly describes component instances and their interconnections?

A. Behavioural

B. Dataflow

C. Structural

D. Algorithmic software modelling

Correct Answer: C. Structural

Question 5

A parameterised module has DEPTH = 1 and uses `$clog2(DEPTH)` directly as a vector width. What problem may result?

A. Automatic insertion of a clock-domain synchronizer

B. A zero-width vector or invalid declaration

C. Guaranteed inference of a latch

D. Conversion of all signals to signed values

Correct Answer: B. A zero-width vector or invalid declaration

Question 6

Which practice best prevents unintended truncation in arithmetic RTL?

A. Relying on unsized constants everywhere

B. Leaving signedness implicit

C. Using explicit widths and deliberate signed declarations

D. Replacing all registers with wires

Correct Answer: C. Using explicit widths and deliberate signed declarations

Question 7

What is the principal danger of directly sampling a pulse from an unrelated clock domain?

A. It always creates a combinational loop

B. It may cause metastability or miss the pulse

C. It necessarily doubles the clock frequency

D. It prevents synthesis of the receiving module

Correct Answer: B. It may cause metastability or miss the pulse

Question 8

Which verification technique expresses a rule such as 'a request remains asserted until acknowledgement'?

A. A synthesis constraint only

B. A SystemVerilog assertion

C. A parameter declaration

D. A structural instance

Correct Answer: B. A SystemVerilog assertion

Question 9

In the packet-buffer case, why were occupancy bounds and simultaneous operations important to verify?

A. They determine the HDL file extension

B. They expose counter, flag, and handshake errors at boundary conditions

C. They remove the need for reset design

D. They guarantee a particular FPGA placement

Correct Answer: B. They expose counter, flag, and handshake errors at boundary conditions

Question 10

Which statement best describes the role of version control in RTL projects?

A. It replaces functional simulation

B. It records and reviews changes to RTL and related design artifacts

C. It automatically proves timing closure

D. It converts behavioural code into structural code

Correct Answer: B. It records and reviews changes to RTL and related design artifacts

Digital Electronics
Chapter 9 of 15