Verification & Toolchain

Rodolfo Azevedo

Institute of Computing, University of Campinas (UNICAMP), Brazil

rodolfo.azevedo@unicamp.br

http://www.ic.unicamp.br/~rodolfo/mo801

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Goal of this class

Module 1, Class 6: testbenches with Verilator, waveforms with GTKWave, and the full synthesis-to-bitstream flow.

You can describe a beautiful circuit in SystemVerilog and still ship a bug. Simulation is the fastest, cheapest debugging loop. This class sets up the workflow you will use for every project: write SV → simulate with Verilator → inspect waveforms in GTKWave → synthesize with Yosys → place & route with nextpnr → load with openFPGALoader.

At the end of this class, you should be able to:

  • Write a SystemVerilog testbench that drives a DUT and checks its outputs, and run it with Verilator.
  • Interpret waveforms in GTKWave to debug both combinational and sequential circuit behaviour.
  • Execute the full synthesis-to-bitstream flow: Yosys → nextpnr → openFPGALoader.
  • Apply a pin constraint file (.cst) to map logic signals to Tang Nano 9K physical pins.
  • Explain why simulation should catch bugs before FPGA synthesis, not after.
MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Why simulate before synthesizing?

Method Time to feedback Can inspect internals? Cost of error
Simulation (Verilator) Seconds Yes — all signals Zero
Synthesis + P&R Minutes No — only ports Time
Load to FPGA Minutes + above Limited (JTAG) Time + frustration

The rule: find bugs in simulation, not on the board. A good simulation catches 90% of bugs in seconds. An undetected bug surfaces 10 minutes later on the FPGA with no internal visibility.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Testbench structure

A testbench is a SV module with no ports — it drives the design under test (DUT) and checks its outputs:

module adder_tb;
    // DUT signals
    logic [7:0] a, b;
    logic [7:0] sum;
    logic       cout;

    // Instantiate the DUT
    adder #(.WIDTH(8)) dut (.a(a), .b(b), .sum(sum), .cout(cout));

    initial begin
        a = 8'd5;  b = 8'd3;  #10;   // apply inputs, wait 10 time units
        $display("5+3 = %0d (expected 8)", sum);

        a = 8'hFF; b = 8'h01; #10;
        $display("255+1 = %0d, cout=%b (expected 0, 1)", sum, cout);

        $finish;
    end
endmodule

#10 is a simulation delay — 10 time units. In a testbench without a clock, delays drive the simulation timeline.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

SystemVerilog preprocessor: include, define, guards

// riscv_defs.vh — shared constants (header file pattern)
`ifndef RISCV_DEFS_VH
`define RISCV_DEFS_VH

`define OP_LOAD   7'b000_0011
`define OP_STORE  7'b010_0011
`define OP_BRANCH 7'b110_0011
// ... all opcodes, funct3, funct7 codes

`endif

Usage in any module:

`include "riscv_defs.vh"
// now `OP_LOAD etc. are available
  • The include guard (`ifndef / `define / `endif) prevents double-inclusion when multiple files include the same header.
  • Alternative: SystemVerilog package — but not supported by Yosys's read_verilog -sv frontend (see next slide).
MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Yosys synthesis limitations: what to avoid

Things that look like valid SystemVerilog but fail with Yosys + nextpnr-himbaechel:

Construct Status Workaround
package + import pkg::*; in port list ❌ Rejected Use `include + `define (see previous slide)
parameter string INIT_FILE = "" ❌ Rejected Use parameter INIT_FILE = "" (no type)
class, dynamic arrays, rand/constraint ❌ Not synthesizable Simulation/testbench only
$display, $monitor, $finish ❌ Not synthesizable Guard with `ifndef SYNTHESIS
always_latch ⚠️ Supported but avoid Latches are usually design errors
struct packed ✅ Supported OK to use
typedef enum ✅ Supported OK to use

Tip: compile with yosys -p "read_verilog -sv file.sv; hierarchy -check" to catch these early.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Clock generation and sequential testbenches

module counter_tb;
    logic clk = 0, rst;
    logic [7:0] count;

    counter #(.WIDTH(8)) dut (.clk(clk), .rst(rst), .count(count));

    // Clock generator: toggles every 5 ns → 10 ns period → 100 MHz
    always #5 clk = ~clk;

    initial begin
        rst = 1;
        @(posedge clk); @(posedge clk);   // wait two cycles
        rst = 0;

        repeat (10) @(posedge clk);        // run 10 cycles
        $display("count after 10 cycles: %0d (expected 10)", count);
        $finish;
    end
endmodule
  • always #5 clk = ~clk — an always block with no sensitivity list runs continuously, toggling clk every 5 ns.
  • @(posedge clk) — wait for the next rising edge. The idiomatic way to advance one cycle.
  • repeat (N) @(posedge clk) — advance N cycles cleanly.
MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

$display, $dumpfile, $dumpvars

initial begin
    // Waveform dump for GTKWave
    $dumpfile("sim.vcd");
    $dumpvars(0, counter_tb);   // dump all signals in this scope (depth 0 = all)

    rst = 1; #20; rst = 0;
    repeat (100) @(posedge clk);

    $display("Simulation complete. Final count = %0d", dut.count);
    $finish;
end
  • $dumpfile("sim.vcd") — create a VCD (Value Change Dump) file.
  • $dumpvars(0, scope) — record every signal transition in the given scope to the VCD file. Open sim.vcd in GTKWave to see waveforms.
  • $display — prints to stdout during simulation. Use %0d (decimal), %h (hex), %b (binary).
  • $finish — ends the simulation. Without it, a simulation with an always clock will run forever.
MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Tasks and functions — reusable test sequences

// A task: can have delays and side effects
task apply_and_check;
    input  logic [7:0] in_a, in_b;
    input  logic [7:0] expected;
    begin
        a = in_a; b = in_b;
        #10;
        assert (sum == expected)
            else $error("FAIL: %0d + %0d = %0d, expected %0d",
                         in_a, in_b, sum, expected);
    end
endtask

// Use it:
initial begin
    apply_and_check(5,   3,   8);
    apply_and_check(100, 56, 156);
    apply_and_check(255,  1,   0);   // overflow
    $finish;
end
  • Tasks can contain timing controls (#, @) — use for multi-cycle stimulus sequences.
  • Functions cannot — use for pure combinational computations (e.g., a reference model).
MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Immediate assertions

// Check a condition right now (not at a clock edge)
assert (sum == expected)
    else $error("sum mismatch at time %0t: got %0h, expected %0h",
                 $time, sum, expected);

// Fatal version — stops simulation immediately
assert (sum == expected)
    else $fatal(1, "unrecoverable mismatch");

Immediate assertions are the minimum bar for any testbench:

  1. Apply a known input.
  2. Wait for combinational logic to settle (or for a clock edge).
  3. Assert the expected output.

For a module with test cases, automate the check loop — do not read output values by eye.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Verilator — compiling SV to C++ simulation

Verilator is a cycle-accurate SystemVerilog simulator that compiles your design to C++, then runs it natively — far faster than event-driven simulators for large designs.

# Compile DUT + SV testbench into an executable
verilator --binary --trace -Wall \
    --top-module adder_tb \
    adder.sv adder_tb.sv \
    -o sim_adder

# Run simulation
./obj_dir/sim_adder

# Open waveform
gtkwave sim.vcd
  • --binary: generate Verilator's own main(), compile and link an executable. It implies --timing, which is what makes #10 delays and always-block clocks work.
  • Without --binary you get C++ sources only — and nothing to run.
MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Two testbench styles — and when to use each

SystemVerilog (tb_foo.sv) C++ harness (tb_foo.cpp)
What it is a module with no ports an ordinary C++ main()
Time #10 delays, always clock you toggle the clock and call eval()
Parameters set at DUT instantiation -GNAME=value on the command line
Verilator --binary --cc --exe --build
Good at self-contained checks file I/O, reference models, thousands of vectors

Reach for SystemVerilog first. One language, less ceremony, and enough for anything self-contained — a decoder, an FSM, a counter.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

The C++ harness

Verilator turns your module into a C++ class: every port becomes a public member.

#include "Vdecoder2to4.h"
#include "verilated.h"

int main(int argc, char **argv) {
    Verilated::commandArgs(argc, argv);
    Vdecoder2to4 *dut = new Vdecoder2to4;

    for (int s = 0; s < 4; s++) {
        dut->sel = s;      // drive an input
        dut->eval();       // recompute outputs — nothing happens without this
        printf("%s sel=%d y=%d\n",
               dut->y == (1 << s) ? "[PASS]" : "[FAIL]", s, (int)dut->y);
    }
    delete dut;
    return 0;
}
  • eval() is the whole model: change inputs, call it, read outputs.
  • For a sequential design there is no clock unless you make one — set clk low, eval(), set it high, eval(). That loop is time.
MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Why keep both

The KWS kernel and the accelerator are where the C++ style pays off:

  • load a real int8 test vector from a file,
  • run the same input through a reference implementation written in C,
  • compare thousands of output elements, element by element.

None of that is comfortable in a SystemVerilog initial block.

Rule of thumb: if the testbench only needs to poke signals and check them, write SV. If it needs to compute what the right answer is, write C++.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Simulation-only code: ifndef SYNTHESIS and ifdef SIMULATION

Two standard patterns for code that must NOT be synthesized:

Pattern 1: `ifndef SYNTHESIS — RTL assertions embedded in the module

// Inside a regular synthesizable module:
`ifndef SYNTHESIS
  always_ff @(posedge clk)
    assert (wptr < DEPTH) else $fatal(1, "FIFO write pointer overflow");
`endif

Yosys defines SYNTHESIS automatically during synthesis. Verilator does not define it by default.

Pattern 2: `ifdef SIMULATION — debug ports that only exist in testbench builds

module riscv_core (
  input  logic        clk, rst_n,
  // ... normal ports ...
`ifdef SIMULATION
  output logic [31:0] dbg_pc,       // expose PC for testbench inspection
  output logic [31:0] dbg_reg_data
`endif
);

Pass -DSIMULATION to Verilator (--compiler gcc -DSIMULATION) to enable.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Verilator lint: suppressing justified warnings

Verilator is stricter than Yosys. Common warnings and how to handle them:

Warning Meaning Suppress when...
UNUSEDSIGNAL Signal declared but never read Debug signals only needed in sim
WIDTHEXPAND Expression auto-widened Intentional zero-extension
WIDTHTRUNC Expression auto-truncated Taking only the low bits deliberately
SYNCASYNCNET Net crosses sync/async boundary After a known synchronizer

Inline suppression (document the reason):

/* verilator lint_off UNUSEDSIGNAL */
logic [31:0] dbg_pc;  // exposed via SIMULATION port only
/* verilator lint_on  UNUSEDSIGNAL */

Rule: suppress only when the warning is a known false positive, and always add a comment explaining why.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

C++ testbench with Verilator: bit-banging a protocol

Verilator compiles synthesizable SV to a C++ class. A C++ testbench can drive any signal cycle by cycle:

#include "Vuart_tx.h"   // generated by Verilator
#include "verilated.h"

int main() {
    auto dut = std::make_unique<Vuart_tx>();
    // Send byte 0xA5 at 115200 baud (27 MHz → 234 clocks/bit)
    dut->txd_in = 0xA5;
    dut->send   = 1;
    tick(dut);  // rising edge: latch
    dut->send   = 0;

    // Sample the TX line bit by bit
    for (int bit = 0; bit < 10; bit++) {    // 1 start + 8 data + 1 stop
        for (int i = 0; i < 234; i++) tick(dut);
        std::cout << "bit " << bit << " = " << (int)dut->tx << "\n";
    }
}

This bit-banging approach lets you test a UART peripheral without a real serial port — just count clock cycles and check the output signal.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

GTKWave — reading VCD waveforms

After running simulation with $dumpvars:

  1. Open gtkwave sim.vcd.
  2. In the Signals panel (left), expand the hierarchy and drag signals to the Waveform panel.
  3. Use the zoom controls to see individual clock cycles.
  4. Markers: click to place a cursor; Ctrl+click for a second cursor to measure intervals.

What to look for:

  • Does rst deassert cleanly after two cycles?
  • Does count start at 0 and increment by 1 every cycle?
  • Is there a cycle where the output is x (red/unknown)? That means an undriven signal.

GTKWave is how you debug Project 1 when a simulation assertion fires — zoom in to the failing cycle and trace back through state, PC, IR, and control signals.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

The OSS CAD Suite flow: source to bitstream

Each step produces human-readable output — read it:

  • Yosys: reports LUT count, FF count, wire count after synthesis.
  • nextpnr: reports whether timing is met and the worst negative slack. Look for WNS: ... — negative means a timing violation.
  • openFPGALoader: confirms the bitstream was loaded successfully.
MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Multi-file projects and the Makefile

Real designs span multiple .sv files. A minimal Makefile:

TOP    = top
SRC    = $(wildcard src/*.sv)
DEVICE = GW1NR-9C

all: bitstream.fs

netlist.json: $(SRC)
	yosys -p "read_verilog -sv $(SRC); synth_gowin -top $(TOP) -json $@"

pnr.json: netlist.json pins.cst
	nextpnr-himbaechel --device $(DEVICE) --json $< --cst pins.cst --write $@ --freq 27

bitstream.fs: pnr.json
	gowin_pack $< -o $@

load: bitstream.fs
	openFPGALoader -b tangnano9k $<

sim: $(SRC) tb/$(TOP)_tb.sv
	verilator --cc --exe --build -j4 --top-module $(TOP)_tb $(SRC) tb/$(TOP)_tb.sv -o sim_$(TOP)
	./obj_dir/sim_$(TOP)

make sim runs simulation. make load synthesizes, P&Rs, packs, and loads in one step.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Gowin GW1NR-9C: FPGA primitives and resources

The Tang Nano 9K uses the Gowin GW1NR-9C — a small FPGA with these on-chip resources:

Resource Count Description
LUT4 8,640 4-input lookup tables (combinational logic)
Flip-flop (DFF) 6,480 D flip-flops (registered state)
Shadow registers 17,280 Additional registers (config/scan use)
BSRAM 26 blocks 18 Kbit each → 468 Kbit total on-chip SRAM
DSP (MULT18X18) 20 blocks 18×18 signed multipliers, used for Zmmul/accelerator
PSRAM 64 Mbit External pseudo-SRAM (off-chip, via dedicated pins)
PLL (rPLL) 2 Phase-locked loops for clock synthesis
ADC 1 8-channel 12-bit (not used in this course)

Yosys maps your RTL to these primitives automatically. The synthesis report (stat command) shows how many of each are used — always check before submitting a project stage.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

FPGA pin constraints: logical → physical mapping

Every signal in your top-level module must be mapped to a physical pin of the FPGA package. This is done via a .cst (constraint) file:

// hw/constraints/tangnano9k.cst
IO_LOC  "clk"    52;      // logical name → pin number
IO_LOC  "rst_n"  4;
IO_LOC  "led[0]" 10;
IO_LOC  "uart_tx" 17;
IO_PORT "clk"    PULL_MODE=UP DRIVE=4;

Key fields:

  • IO_LOC: maps a port name to the package pin number (from the board schematic)
  • IO_TYPE: electrical standard — LVCMOS33 (3.3V logic, default), LVCMOS18, LVDS, etc.
  • PULL_MODE: UP / DOWN / NONE / KEEPER — internal resistor to avoid floating inputs
  • DRIVE: output current strength in mA (4, 8, 16, 24) — higher drive for long traces or LEDs

Example: Tang Nano 9K's button is connected to a pin that floats when not pressed → must use PULL_MODE=UP (active-low button reads 1 when released, 0 when pressed).

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

PLL: clock synthesis

The GW1NR-9C includes 2 rPLL (reconfigurable PLL) blocks. A PLL multiplies and divides a reference clock to generate a precise output frequency:

Why we don't use it in this course

at 27 MHz, the multicycle RV32I core has a timing slack of several nanoseconds — we are well below the Fmax. Using a PLL to run at, say, 50 MHz would require re-analyzing all timing paths and potentially adding pipeline stages.

When you would use it

  • Running the CPU above 27 MHz
  • Generating a precise UART baud reference independent of the oscillator frequency
  • DDR interfaces that require phase-shifted clocks

The instantiation template is in the Gowin IP catalog; the Makefile would add --add-file pll.v to the nextpnr command.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Implementing the PLL: the rPLL primitive

Yosys can't infer a PLL from behavioral RTL — it's dedicated hard-IP silicon, not LUTs/FFs. You instantiate the primitive directly; synth_gowin recognizes rPLL from its Gowin cell library and hands it untouched to nextpnr-himbaechel for placement.

// pll.v — plain Verilog (not SV), hand-written or generator-produced
module Gowin_rPLL (clkout, clkin);
    output clkout;
    input  clkin;

    rPLL rpll_inst (
        .CLKOUT(clkout), .CLKOUTP(), .CLKOUTD(), .CLKOUTD3(), .LOCK(),
        .CLKIN(clkin), .CLKFB(1'b0), .RESET(1'b0), .RESET_P(1'b0),
        .FBDSEL(6'b0), .IDSEL(6'b0), .ODSEL(6'b0),
        .PSDA(4'b0), .DUTYDA(4'b0), .FDLY(4'b0)
    );

    defparam rpll_inst.FCLKIN    = "27";
    defparam rpll_inst.IDIV_SEL  = 2;  // input divider:    ÷(IDIV_SEL+1)  = ÷3
    defparam rpll_inst.FBDIV_SEL = 3;  // feedback divider: ×(FBDIV_SEL+1) = ×4
    defparam rpll_inst.ODIV_SEL  = 8;  // from {2,4,8,16,...} — keeps the VCO in range
endmodule

CLKOUT = CLKIN × (FBDIV_SEL+1) / (IDIV_SEL+1) → 27 × 4 / 3 = 36 MHz, exact.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Using the generated PLL in your design

ODIV_SEL only takes values from a restricted set, chosen so the internal VCO lands in its valid frequency band — get it wrong and the PLL never locks. Don't hand-pick these values. Apicula ships a generator that resolves them for you:

python3 -m apycula.gowin_pll -d GW1NR-9C -if 27 -of 36   # flags vary by version — check --help
# → writes Gowin_rPLL.v with IDIV_SEL / FBDIV_SEL / ODIV_SEL already resolved

Wiring it into your design

wire clk_36, pll_lock;
Gowin_rPLL pll_inst (.clkin(clk_27), .clkout(clk_36));

assign rst_n = pll_lock;   // hold the design in reset until the PLL locks
  • Add the generated file to $(SRC) in the Makefile — extend the wildcard to src/*.sv src/*.v, since pll.v is plain Verilog.
  • Gate your reset on LOCK. A flip-flop sampling CLKOUT before it stabilizes starts in an undefined state — the same failure mode as the glitch attacks from the security corner.
MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

SRAM vs. flash programming

When you openFPGALoader -b tangnano9k bitstream.fs:

  • Default: loads into the FPGA's SRAM configuration — instant, but lost on power-off.
  • Flash: the board can also hold the bitstream in its on-board flash, surviving power cycles.
    • We never do this in this course. The flash takes a limited number of write cycles, and these boards are reused across semesters.

For development: SRAM (default) — fast iteration, and no wear.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Lab 4 due / Lab 2 due

Lab 4 (traffic light FSM, M01A04) is due this class. The testbench for Lab 4 should:

  1. Simulate at least 3 full light cycles (RED→GREEN→YELLOW→RED).
  2. Assert the correct output {red, yellow, green} for each state.
  3. Verify the emergency button overrides to RED within one cycle.

Lab 2 (toolchain setup + Tang Nano 9K bring-up) is also due this class. Your submission should include the make load output showing successful bitstream loading and a photo/video of the LED blink exercise from M01A03.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Security corner: hardware trojans

A hardware trojan is malicious logic inserted into a design — by a rogue designer, a compromised IP core, or even a tampered synthesis tool. The trojan remains dormant until a rare trigger condition activates it.

  • A 2016 study at the University of Michigan demonstrated a trojan in an open-source processor that granted privilege escalation when a specific sequence of otherwise-unused instructions was executed.
  • In your project: the entire HDL is yours — but what if you used a third-party ALU module downloaded from GitHub?

How trojans hide

Technique Example
Rare trigger Activates only after 2³² clock cycles
Cheat code Specific input sequence on an unused port
Analog Modifies transistor sizing — invisible in RTL
MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Defenses at the RTL level

  • Code review and reproducible builds: if you can rebuild Yosys from source and get the same bitstream, you can trust the toolchain.
  • Formal verification: assert that outputs depend only on specified inputs — a trojan with a hidden trigger violates this property.
  • Unused-signal analysis: Yosys reports signals it optimized away — a trojan that references unused ports will either be pruned (good) or keep an otherwise-dead signal alive (suspicious).
MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Security corner: glitch attacks on FSMs

A glitch attack injects a voltage or clock perturbation to make a flip-flop sample the wrong value — causing the FSM to skip a state.

  • Real-world impact: attackers have glitched smartcard FSMs to bypass PIN verification (skip the "check PIN" state and jump straight to "access granted").
  • In your design: a multicycle processor that skips the DECODE state and jumps to WRITEBACK could write garbage to the register file.

Mitigations

  • Illegal-state detection: add a default case in the FSM's always_comb that asserts an error signal or forces a reset.
  • Redundant encoding: instead of binary-encoded states, use Hamming-distance-2 encoding — a single-bit flip cannot reach another valid state.
  • Voltage/clock monitoring: detect out-of-spec conditions and hold the processor in reset.

In Project 1, your FSM's default case is not just good practice — it is your first line of defense against fault injection.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Energy corner: where does power go in a digital circuit?

Two sources of power dissipation in CMOS:

Dynamic power — every time a gate output toggles (0→1 or 1→0), it charges or discharges a capacitor:

  • = activity factor (fraction of gates toggling per cycle)
  • = total capacitance of switching nodes
  • = supply voltage (squared! — halving voltage cuts dynamic power by 4×)
  • = clock frequency

Static power — leakage current flows even when nothing toggles. In modern processes (≤28 nm) this can be 30-50% of total power. In the Tang Nano 9K's 55 nm process, it is smaller but not negligible.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Energy corner: practical implications for your designs

Design choice Energy effect
Unused outputs left floating Oscillate → waste switching energy
Clock running to idle modules Gates toggle on every edge even if output is unused
One-hot vs binary state encoding One-hot: more flip-flops, but fewer toggle per transition
default assignments in always_comb Prevent glitches from incomplete sensitivity — fewer spurious toggles

What Yosys reports

After synthesis, Yosys reports the number of LUTs, flip-flops, and estimated net count. More nets ≈ more capacitance ≈ more dynamic power. Watch this number grow across Projects 1→2→3.

In Lab 3 and Lab 4, you can feel the energy cost directly: the Tang Nano 9K draws more current (warmer USB connector) when your design has high switching activity.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Reproducibility note: pinning your toolchain

Your synthesis results depend on the exact version of every tool. A Yosys update can change LUT count by 10-15%.

  • Pin the OSS CAD Suite release in your project README: oss-cad-suite-2026-08-01.
  • Commit your Makefile — it encodes the exact synthesis, P&R, and programming commands.
  • Record tool versions in your report: yosys --version, nextpnr-himbaechel --version, verilator --version.
  • Use .gitignore for build artifacts (*.fs, *.json, *.asc) — only source HDL and constraints go into version control.

Two students with the same source and the same toolchain version must get the same bitstream. If they don't, something is wrong.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Next module

Module 2 — RISC-V Architecture: the ISA your datapath must implement, the multicycle execution model, and the control FSM that ties everything together. Project 1 begins.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0