Skip to section
Foundationsfor rotation-free search
Section 45 of 5287% of course
Contents
Chapter 7 Section 7.7 80 min

Part V · Hardware foundations

Read a small SystemVerilog module slowly

Translate ports, continuous assignments, and clocked blocks into behavior.

Okay, let's finally look at some code

SystemVerilog is a Hardware Description Language (HDL). The snippet below describes a single, buffered multiplication stage. It's deliberately simple so we can focus on the plumbing, and you can actually execute its testbench using Icarus Verilog.

module stream_multiply #(
    parameter int WIDTH = 16
) (
    input  logic                   clk,
    input  logic                   reset_n,
    input  logic                   input_valid,
    output logic                   input_ready,
    input  logic [WIDTH-1:0]       left,
    input  logic [WIDTH-1:0]       right,
    output logic                   output_valid,
    input  logic                   output_ready,
    output logic [(2*WIDTH)-1:0]   product
);
    assign input_ready = !output_valid || output_ready;

    always_ff @(posedge clk) begin
        if (!reset_n) begin
            output_valid <= 1'b0;
            product      <= '0;
        end else if (input_ready) begin
            output_valid <= input_valid;
            if (input_valid)
                product <= left * right;
        end
    end
endmodule

Start from the outside in

Look at parameter int WIDTH=16. This just makes the module configurable so we can stamp out different sizes. A line like logic [WIDTH-1:0] left is declaring a physical bundle of wires, numbered from top to bottom. Notice how the product gets 2 * WIDTH bits, to hold the full unsigned result.

source formbehavioral readingquestion to ask
input / outputmodule boundarywho is driving this signal from the outside?
logic [W−1:0]W physical bitsis this signed, unsigned, or something weird?
assigncontinuous combinational logicis this path too long to settle in one cycle?
always_ff @(posedge clk)state update on the clock tickwhat controls whether this actually updates?
<=nonblocking register assignmentwhich old state value is being sampled here?

Keep the two behaviors separated in your head

Look for the assign statement. This is continuous logic—it's calculating input_ready on the fly based on whether the output register is empty or about to clear out. Now look at the always_ff block. This only runs when the clock ticks. It handles the reset flag, and if the stage is ready to advance, it reaches out and grabs the new product.

Translating backpressure into a Boolean equation

This module only has room to store one output item. So, when is it allowed to accept a new input? Either when it's completely empty (!output_valid), OR when the current item is guaranteed to leave on this exact clock tick (because the downstream guy said output_ready). So, !output_valid || output_ready is just a mathematical proof of capacity compressed into a single line.

Worked example

Tracing 7 × 9

Imagine we present input_valid=1, left=7, and right=9 while the module is saying input_ready=1. When the clock ticks, the module swallows the data, calculates product=63, and flips its own output_valid=1. If the next module in line drops output_ready=0, our module just holds the 63 steady. Once the blockage clears, the 63 transfers out.

Wait, why doesn't the 63 get overwritten by garbage while we're stalled?

Because during the stall, output_valid is 1 and output_ready is 0. That forces our input_ready to drop to 0. Since the always_ff block says else if (input_ready), the whole block gets skipped! The registers don't get assigned a new value, so they just hold onto the 63 until the traffic clears.

Check your understanding

Why does the module say input_ready is true when output_valid is false?

Section summary

  • Ports define the contract your module makes with the outside world.
  • assign handles the instant combinational logic.
  • always_ff handles the synchronized, edge-triggered register updates.
  • This module proves how to safely buffer one piece of data under valid/ready backpressure.

Repository layer · second pass

How do we read SystemVerilog as a circuit contract?

Ports define the module boundary. Continuous assignments describe combinational wiring. always_comb describes a complete combinational procedure. always_ff describes state sampled on a declared edge. Parameters elaborate structure before runtime; they are not mutable variables.

Read in passes: interface and widths, state elements, combinational datapath, handshake conditions, then reset and corner cases. Finally translate the module into a cycle-level table and compare it with the testbench.

Reasoning chain

  1. 1

    Annotate every port direction and width.

  2. 2

    Mark registers written in always_ff.

  3. 3

    Mark pure signals driven combinationally.

  4. 4

    Find the exact transfer condition.

  5. 5

    Trace reset and stall behavior.

  6. 6

    Derive observable latency.

Worked trace

Interpret output_valid

  1. An output register stores product and valid.
  2. When output is empty, input_ready is high.
  3. On input transfer, the product is captured.
  4. If downstream stalls, valid and payload remain.

Result. The few RTL lines implement a one-entry elastic stage.

Executable lens · Python

Make the hidden state visible

# Cycle model for the RTL stage
if reset:
    output_valid = False
elif input_ready:
    output_valid = input_valid
    if input_valid:
        output_data = a * b

Retype this example, predict each intermediate value, and then change one input that touches a boundary.

Misconception clinic

Tempting mistakes

  • Assuming textual line order defines separate cycles.
  • Ignoring implicit width of unsized literals and expressions.

Retrieval and transfer

Close the book first

  1. Annotate stream_multiply.sv line by line.
  2. Predict behavior when valid and ready are both low.
  3. Add an assertion for stable stalled output.