Loading header...

Exercise: Find and Fix a Timing Failure

Timing closure is not guesswork. When an FPGA build reports negative slack, the useful response is to identify the failing clock path, understand which logic and routing dominate the delay, and change the hardware architecture while preserving behavior. In this exercise you will create a failing arithmetic path, pipeline it, and update the testbench so the added latency is verified instead of hidden.

Learning Objectives

You will be able to:

  • read setup timing terms such as clock period, data arrival time, required time, and slack;
  • recognize a long combinational datapath between two registers;
  • split arithmetic across pipeline stages without changing the mathematical result;
  • update a self-checking testbench for fixed pipeline latency;
  • explain the tradeoff between latency, throughput, registers, DSP blocks, and maximum clock frequency.

Prerequisites

  • Verilog sequential logic and nonblocking assignments.
  • Basic testbench writing with a clock, reset, and expected-value comparison.
  • Static timing analysis concepts: setup time, hold time, clock period, critical path, and slack.
  • An FPGA or open-source flow that can synthesize and report timing, such as Yosys with nextpnr, or a vendor tool.

Task

Start with an intentionally heavy one-cycle datapath. Constrain it to a clock that is deliberately aggressive for your device, such as 100 MHz, 150 MHz, or faster depending on the target FPGA.

module slow_mix (
    input  wire        clk,
    input  wire [15:0] a,
    input  wire [15:0] b,
    input  wire [15:0] c,
    input  wire [15:0] d,
    output reg  [33:0] y
);
    always @(posedge clk) begin
        y <= ((a * b) + (c * d)) ^ ((a + c) * (b + d));
    end
endmodule

The expression is legal RTL, but it asks the FPGA to complete several multiplications, additions, XOR logic, and routing delay in one clock period. On a real implementation this can easily create negative setup slack.

Timing Terms Used In This Exercise

Term Meaning Practical check
Clock period Time available for one cycle Tclk = 1 / fclk; 100 MHz gives 10 ns
Data path delay Register clock-to-Q plus logic plus routing plus setup Dominant blocks often appear in the critical path report
Required time Latest time data may arrive before the capture edge Tightens as requested frequency increases
Slack Required time minus arrival time Negative setup slack means the design may fail
Latency Number of cycles before a sample's result appears Must be modeled in the testbench
Throughput Results accepted or produced per unit time A filled pipeline can still produce one result per clock

Datapath Before And After Pipelining

flowchart LR subgraph SLOW["one cycle path"] A["input registers"] --> M1["multiply a*b"] M1 --> ADD["add products"] A --> M2["multiply c*d"] A --> S["add a+c and b+d"] S --> M3["multiply sums"] ADD --> XOR["xor"] M3 --> XOR XOR --> Y["output register"] end subgraph PIPE["pipelined path"] P0["stage 0 inputs"] --> P1["stage 1 products"] P1 --> P2["stage 2 combine"] P2 --> P3["stage 3 output"] end

The pipeline does not make multiplication free. It shortens the longest register-to-register path by putting registers between groups of operations.

Buildable Pipelined Version

The following version uses three visible stages. It also carries a valid signal so the testbench knows when the output corresponds to a real input sample.

module pipelined_mix (
    input  wire        clk,
    input  wire        rst,
    input  wire        valid_in,
    input  wire [15:0] a,
    input  wire [15:0] b,
    input  wire [15:0] c,
    input  wire [15:0] d,
    output reg         valid_out,
    output reg  [33:0] y
);
    reg        v1, v2;
    reg [31:0] ab_s1, cd_s1;
    reg [16:0] ac_s1, bd_s1;
    reg [32:0] sum_s2;
    reg [33:0] prod_s2;

    always @(posedge clk) begin
        if (rst) begin
            v1 <= 1'b0;
            v2 <= 1'b0;
            valid_out <= 1'b0;
            y <= 34'd0;
        end else begin
            v1 <= valid_in;
            ab_s1 <= a * b;
            cd_s1 <= c * d;
            ac_s1 <= a + c;
            bd_s1 <= b + d;

            v2 <= v1;
            sum_s2 <= ab_s1 + cd_s1;
            prod_s2 <= ac_s1 * bd_s1;

            valid_out <= v2;
            y <= {1'b0, sum_s2} ^ prod_s2;
        end
    end
endmodule

The reset clears validity and output state. The arithmetic registers do not require explicit reset for functional correctness because they are ignored until the corresponding valid bit arrives.

Minimal Self-Checking Testbench Idea

module tb_pipelined_mix;
    reg clk = 0, rst = 1, valid_in = 0;
    reg [15:0] a, b, c, d;
    wire valid_out;
    wire [33:0] y;

    reg [33:0] expect_pipe [0:2];
    integer i;

    pipelined_mix dut (
        .clk(clk), .rst(rst), .valid_in(valid_in),
        .a(a), .b(b), .c(c), .d(d),
        .valid_out(valid_out), .y(y)
    );

    always #5 clk = ~clk;

    function [33:0] model;
        input [15:0] aa, bb, cc, dd;
        begin
            model = ((aa * bb) + (cc * dd)) ^ ((aa + cc) * (bb + dd));
        end
    endfunction

    always @(posedge clk) begin
        if (rst) begin
            expect_pipe[0] <= 0;
            expect_pipe[1] <= 0;
            expect_pipe[2] <= 0;
        end else begin
            expect_pipe[0] <= model(a, b, c, d);
            expect_pipe[1] <= expect_pipe[0];
            expect_pipe[2] <= expect_pipe[1];
            if (valid_out && y !== expect_pipe[2]) begin
                $display("Mismatch: got %h expected %h", y, expect_pipe[2]);
                $finish;
            end
        end
    end

    initial begin
        a = 0; b = 0; c = 0; d = 0;
        repeat (3) @(posedge clk);
        rst = 0;
        valid_in = 1;
        for (i = 0; i < 50; i = i + 1) begin
            @(posedge clk);
            a = i;
            b = i + 3;
            c = 16'h0100 + i;
            d = 16'h0020 + (i * 2);
        end
        valid_in = 0;
        repeat (5) @(posedge clk);
        $display("Pipeline timing exercise passed");
        $finish;
    end
endmodule

Expected Behavior

  • The original expression and the pipelined module compute the same value for each input sample.
  • The pipelined output appears three clock cycles after the sample is accepted.
  • valid_out is low during reset and during the pipeline fill period.
  • Implementation timing should improve because each stage has less logic depth.
title "Illustrative pipeline latency"
time start=0 end=8 unit=cycles divisions=8

CLK: square label="clk" low=0 high=1 duty=50 cycles=4 unit=logic color=#2563eb
VIN: pulse label="valid in" low=0 high=1 at=1 width=1 unit=logic color=#dc2626
S1: pulse label="stage 1" low=0 high=1 at=2 width=1 unit=logic color=#7c3aed
S2: pulse label="stage 2" low=0 high=1 at=3 width=1 unit=logic color=#0891b2
VOUT: pulse label="valid out" low=0 high=1 at=4 width=1 unit=logic color=#16a34a

marker SAMPLE at=1 label="sample"
marker RESULT at=4 label="result"

The waveform is explanatory; it is not a simulated trace from a specific FPGA.

Verification Steps

  1. Simulate slow_mix and pipelined_mix with the same input stream.
  2. Confirm the pipelined output matches the delayed golden model.
  3. Synthesize the original version with a chosen clock constraint.
  4. Record worst setup slack, the critical path endpoints, and the dominant logic.
  5. Synthesize the pipelined version with the same clock constraint.
  6. Confirm setup slack improves or becomes positive.
  7. Check whether multipliers inferred DSP blocks or LUT logic.
  8. Confirm there are no unconstrained clocks or ignored timing reports.

Common Failure Symptoms

Symptom Likely cause Debugging move
Output is numerically wrong Stage alignment or bit-width bug Compare stage registers against the model
Output is one cycle early or late Expected-value queue length is wrong Trace valid_in, v1, v2, and valid_out
Timing still fails Multiplier stage still too large, DSP not inferred, or clock too fast Inspect the new critical path
Resource use increases Added pipeline registers and possible DSP mapping change Compare utilization reports
Simulation passes but hardware fails Missing clock constraint or invalid timing exception Review constraints before changing RTL

Debugging Guidance

  • Name pipeline stage registers with a stage suffix such as _s1 and _s2.
  • Carry valid, start, tag, or transaction ID signals through the same number of stages as the data.
  • Use unsigned widths deliberately; a 16 x 16 multiply produces up to 32 result bits.
  • Treat negative hold slack separately from setup slack; adding pipeline stages is mainly a setup fix.
  • Avoid adding random registers at module boundaries without checking the functional latency contract.

Extension Challenge

Add a ready/valid handshake with backpressure. Then update the pipeline so it either stalls all stages together or uses skid buffering. Prove in the testbench that no samples are dropped when ready_out is deasserted for several cycles.

Explained Solution

Timing is checked between adjacent registers. The original slow_mix design asks a single clock period to cover multiply, add, multiply, XOR, and routing delay. The pipelined design inserts registers after intermediate products and sums. That reduces the maximum combinational delay per stage, so the requested clock frequency becomes more realistic. The cost is fixed latency and extra registers, but once the pipeline is full the throughput can still be one result per clock.

Summary

A timing failure is evidence, not a mystery. Read the critical path, locate the dominant logic, split the datapath at meaningful boundaries, and verify the new latency with a self-checking testbench. The design is not fixed until both simulation and timing reports agree.

Next: Exercise: Design an Asynchronous FIFO.

Further Reading

  • AMD Vivado Design Suite User Guide: Design Analysis and Closure Techniques
  • Intel Quartus Prime Timing Analyzer Cookbook
  • Project F: FPGA pipelining and DSP examples
  • ZipCPU articles on timing, pipelines, and formal checks

Mind Map

mindmap root((Timing Repair)) Core idea Negative slack Long logic path Pipeline stages Same math later Key formulas Tclk equals 1 over fclk Slack equals required minus arrival Latency in cycles Throughput one per clk Design rules Register stage boundaries Carry valid with data Check bit widths Read critical path Practical checks Sim delayed model Compare timing reports Check DSP inference No unconstrained clocks Common mistakes Ignoring latency Misaligned valid Signed width surprise Random registers