Skip to content
VLSI Mentor

SPI · Module 15

FPGA I/O Registers and Timing Closure

A pad register is free and usable under exactly one rule: nothing between the pin and the register. Two designs computing the same selection, one placeable and one not, are bit-for-bit identical under a static select and are not the same circuit — the difference lives between clock edges, so diffing them cycle by cycle proves nothing.

A pad register is a flip-flop that lives in the I/O block rather than in the fabric. On every FPGA family there is one per pin, it is already paid for, and a path that ends in it has essentially no routing between the pin and the flop — which is usually the difference between meeting an input setup requirement and not.

There is exactly one rule for using it:

Nothing may come between the pin and the register.

Not a mux, not a gate, not an inverter the tool cannot absorb. The register has to be the first thing the signal meets.

That rule is easy to state and easy to break by accident, because the natural way to write a design breaks it.

1. The Same Function, Twice

An N-bit input bus, of which the design needs one bit, chosen by a configuration input:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   out = in_pins[sel]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   PAD FIRST    register all N bits at the pads, then select.
                N flops, all placeable, no logic before any of them.
                The select is combinational AFTER the register.

   LOGIC FIRST  select at the pads, then register the chosen bit.
                ONE flop, and it cannot go in a pad, because there is an N-to-1
                mux in front of it.
                The select is combinational BEFORE the register.

The second is what a designer writes when thinking about area, and it is smaller by N-1 flops. It is also the one that will not close timing, and no simulation will ever say so.

An N-bit input bus taken two ways: registered at the pads then muxed, and muxed then registered in one flopin_pins[N-1:0]selN pad registersN-to-1 muxN-to-1 mux1 fabric registerout_pad_firstout_logic_first12
Figure 1 — the same selection with the mux on either side of the register. Only the upper path can be placed in the pads, because only it has nothing between each pin and its flop. The lower path is smaller by N minus one flops and has an N-to-1 mux in the one place a pad register forbids — and the two produce identical simulations.

2. Bit-For-Bit Identical, Under A Static Select

Two hundred random patterns at each of four select values:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   sel  samples  disagreements
     0      200              0
     1      200              0
     2      200              0
     3      200              0

Eight hundred samples, zero disagreements. That is what licenses the refactor: a design that only changes sel between transactions can move from the smaller form to the pad-placeable one with no functional change at all.

And an SPI slave's sel is exactly that — a configuration register written while the bus is idle. The measurement for that case specifically: zero disagreements across 200 samples, so for a real slave the two forms are interchangeable and the placeable one is free to choose.

3. And They Are Not The Same Circuit

Finding that took two attempts, and the second attempt is the useful part.

Compared at clock edges they agree on any stimulus. sel is stable across the clock edge, so pad_r[sel] and the registered in_pins[sel] were computed from the same pair. Two hundred samples of fully random data and select, compared at every posedge: zero disagreements.

That is the result a reasonable bench produces, and it is the wrong conclusion to draw from it.

The difference lives BETWEEN edges. Move sel after the data has been captured and compare before the next capture, and the two forms differ in 81 of 200 samples — because pad-first re-selects from the value already in its register while logic-first keeps what it selected earlier.

Ten cycles across six rows. Input pins present a value at cycle 1 which is captured at cycle 2. The select changes at cycle 4. The pad-first output changes at cycle 4 while the logic-first output does not, and both agree again after the next capture at cycle 6.captured: both agreecaptured: both agreeselect moves: they divergeselect moves: they divergenext capture: agree againnext capture: agree againclkin_pins0999996666sel0000333333pad_r0099999666out_pad_firstout_logic_firstt0t1t2t3t4t5t6t7t8t9
Figure 2 — where the difference lives. The data is captured at cycle 2 and the select moves at cycle 4, between captures. Pad-first re-selects from the value already in its register and changes; logic-first's register keeps what it selected before the capture and does not. Compared only at the capture edges, the two agree — which is what the first two attempts at this measurement concluded.

So the trade is three-sided rather than two:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   pad first    N flops   placeable       `sel` takes effect on CAPTURED data
   logic first  1 flop    not placeable   `sel` takes effect on ARRIVING data

A design that switches sel between transactions cannot tell the difference and should take the pad-first version. A design that switches it during a transaction has chosen a behaviour, and should say which one it wanted.

4. What The Attributes Do, And What They Do Not

IOB = "TRUE" (Xilinx) or the Intel equivalent is a request. The tool honours it when the rule in the introduction is satisfied and warns when it is not — and a warning in a log of ten thousand lines is not a mechanism.

The mechanism is the structure. The attribute only makes the tool's refusal visible.

This is the same shape as Chapter 14.1's ASYNC_REG: the attribute does not create the property, it stops the tool destroying it, and the property has to be there to begin with.

The design in §5 deliberately omits the attribute, and the comment says why: it is vendor-specific, it belongs with the constraints (Chapter 15.7), and including one in the RTL would suggest that the attribute is what makes the design work. It is not — the absence of logic is.

5. Building Both Forms — Three HDLs

The circuit

The same selection twice, differing only in which side of the register the mux sits on. The internal check asserts the property that holds — equality under a static select — rather than the one that does not.

Azvya Education Pvt. Ltd.VLSI Mentor
spi_io_regs.sv — the same selection with the mux on either side of the register, and an assertion that is narrow and true
// spi_io_regs.sv
//
// Chapter 15.8 -- using the I/O registers instead of fighting the fabric.
//
// A pad register is a flip-flop that lives in the I/O block rather than in the
// fabric. On every FPGA family there is one per pin, it is already paid for, and a
// path that ends in it has essentially no routing between the pin and the flop --
// which is usually the difference between meeting an input setup requirement and
// not.
//
// There is exactly one rule for using it: NOTHING MAY COME BETWEEN THE PIN AND THE
// REGISTER. Not a mux, not a gate, not an inverter that the tool cannot absorb. The
// register has to be the first thing the signal meets.
//
// That rule is easy to state and easy to break by accident, because the natural way
// to write a design breaks it. This file builds the same function twice -- once
// obeying the rule and once not -- so that the difference can be examined rather
// than asserted.
//
// THE FUNCTION. An N-bit input bus, of which the design needs one bit, chosen by a
// configuration input:
//
//     out = in_pins[sel]
//
// THE TWO IMPLEMENTATIONS.
//
//   PAD FIRST    register all N bits at the pads, then select.
//                N flops, all of them placeable in pads, no logic before any of
//                them. The select is combinational AFTER the register.
//
//   LOGIC FIRST  select at the pads, then register the chosen bit.
//                ONE flop, and it cannot go in a pad, because there is an N-to-1
//                mux in front of it. The select is combinational BEFORE the register.
//
// The second is what a designer writes when thinking about area, and it is smaller
// by N-1 flops. It is also the one that will not close timing, and no simulation
// will ever say so.
//
// AND THEY ARE NOT QUITE THE SAME CIRCUIT, WHICH IS THE INTERESTING PART.
//
// With `sel` held still the two are indistinguishable: same value, same latency, bit
// for bit, forever. Change `sel` while data is moving and they differ by one cycle
// in when the change takes effect -- pad-first re-selects from data already captured,
// logic-first selects before capturing.
//
// So the trade is three-sided rather than two:
//
//     pad first    N flops   placeable   `sel` takes effect on captured data
//     logic first  1 flop    not         `sel` takes effect on arriving data
//
// A design that switches `sel` between transactions -- which is what a real
// configuration register does -- cannot tell the difference, and should take the
// pad-first version. A design that switches `sel` DURING a transaction has chosen a
// behaviour, and should say which one it wanted.
//
// WHAT THE ATTRIBUTES DO, AND WHAT THEY DO NOT.
//
// `IOB = "TRUE"` (Xilinx) or the equivalent Intel assignment is a REQUEST. The tool
// honours it when the rule above is satisfied and warns when it is not -- and a
// warning in a log of ten thousand lines is not a mechanism. The mechanism is the
// structure; the attribute only makes the tool's refusal visible.
//
// This is the same shape as Chapter 14.1's `ASYNC_REG`: the attribute does not create
// the property, it stops the tool destroying it, and the property has to be there to
// begin with.

module spi_io_regs #(
    parameter int N    = 4,     // how many pins arrive
    parameter int SW   = 2      // width of the select; N <= 2**SW
) (
    input  wire            clk,
    input  wire            rst_n,

    input  wire [N-1:0]    in_pins,   // asynchronous, straight off the pads
    input  wire [SW-1:0]   sel,

    // --- pad first: register every bit, then select ------------------------
    output wire            out_pad_first,
    // --- logic first: select, then register one bit ------------------------
    output reg             out_logic_first
);

    // ---------------------------------------------------------------- pad first
    // N flops with NOTHING in front of them. On Xilinx this carries
    //
    //     (* IOB = "TRUE" *)
    //
    // and on Intel an ALTERA_ATTRIBUTE requesting a fast input register. The
    // attribute is omitted from this file deliberately: it is vendor-specific, it
    // belongs with the constraints (Chapter 15.7), and including one here would
    // suggest that the attribute is what makes this work. It is not -- the absence
    // of logic is.
    reg [N-1:0] pad_r;

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) pad_r <= {N{1'b0}};
        else        pad_r <= in_pins;
    end

    // The select is AFTER the register, so it is fabric logic on a path that has a
    // whole clock period, rather than logic between a pin and a flop.
    assign out_pad_first = pad_r[sel];

    // ------------------------------------------------------------- logic first
    // One flop, and an N-to-1 mux in front of it. Functionally this is the same
    // selection; structurally it is the version that cannot use a pad register,
    // because the mux is between the pin and the flop.
    //
    // Note how ordinary it looks. Nothing about it reads as a mistake, and that is
    // why it gets written: it is smaller, it simulates identically under a static
    // select, and the only thing that objects is a place-and-route report.
    wire sel_first = in_pins[sel];

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) out_logic_first <= 1'b0;
        else        out_logic_first <= sel_first;
    end

`ifdef SPI_CHECKS
    // The property worth asserting is the one that holds: with `sel` unchanged from
    // one cycle to the next, the two implementations agree. Asserting that they
    // always agree would be asserting something false, and asserting nothing would
    // miss the case that matters -- a refactor from one form to the other is safe
    // exactly when the select is static, and this is where that gets checked.
    reg [SW-1:0] sel_d;
    reg          primed;
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            sel_d  <= {SW{1'b0}};
            primed <= 1'b0;
        end else begin
            if (primed && sel == sel_d && out_pad_first !== out_logic_first)
                $fatal(1, "the two implementations disagree with a static select");
            sel_d  <= sel;
            primed <= 1'b1;
        end
    end
`endif

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
spi_io_regs.v — the same design in Verilog-2001
// spi_io_regs.v
//
// Chapter 15.8 -- using the I/O registers instead of fighting the fabric.
//
// A pad register is a flip-flop that lives in the I/O block rather than in the
// fabric. On every FPGA family there is one per pin, it is already paid for, and a
// path that ends in it has essentially no routing between the pin and the flop --
// which is usually the difference between meeting an input setup requirement and
// not.
//
// There is exactly one rule for using it: NOTHING MAY COME BETWEEN THE PIN AND THE
// REGISTER. Not a mux, not a gate, not an inverter that the tool cannot absorb. The
// register has to be the first thing the signal meets.
//
// That rule is easy to state and easy to break by accident, because the natural way
// to write a design breaks it. This file builds the same function twice -- once
// obeying the rule and once not -- so that the difference can be examined rather
// than asserted.
//
// THE FUNCTION. An N-bit input bus, of which the design needs one bit, chosen by a
// configuration input:
//
//     out = in_pins[sel]
//
// THE TWO IMPLEMENTATIONS.
//
//   PAD FIRST    register all N bits at the pads, then select.
//                N flops, all of them placeable in pads, no logic before any of
//                them. The select is combinational AFTER the register.
//
//   LOGIC FIRST  select at the pads, then register the chosen bit.
//                ONE flop, and it cannot go in a pad, because there is an N-to-1
//                mux in front of it. The select is combinational BEFORE the register.
//
// The second is what a designer writes when thinking about area, and it is smaller
// by N-1 flops. It is also the one that will not close timing, and no simulation
// will ever say so.
//
// AND THEY ARE NOT QUITE THE SAME CIRCUIT, WHICH IS THE INTERESTING PART.
//
// With `sel` held still the two are indistinguishable: same value, same latency, bit
// for bit, forever. Change `sel` while data is moving and they differ by one cycle
// in when the change takes effect -- pad-first re-selects from data already captured,
// logic-first selects before capturing.
//
// So the trade is three-sided rather than two:
//
//     pad first    N flops   placeable   `sel` takes effect on captured data
//     logic first  1 flop    not         `sel` takes effect on arriving data
//
// A design that switches `sel` between transactions -- which is what a real
// configuration register does -- cannot tell the difference, and should take the
// pad-first version. A design that switches `sel` DURING a transaction has chosen a
// behaviour, and should say which one it wanted.
//
// WHAT THE ATTRIBUTES DO, AND WHAT THEY DO NOT.
//
// `IOB = "TRUE"` (Xilinx) or the equivalent Intel assignment is a REQUEST. The tool
// honours it when the rule above is satisfied and warns when it is not -- and a
// warning in a log of ten thousand lines is not a mechanism. The mechanism is the
// structure; the attribute only makes the tool's refusal visible.
//
// This is the same shape as Chapter 14.1's `ASYNC_REG`: the attribute does not create
// the property, it stops the tool destroying it, and the property has to be there to
// begin with.

module spi_io_regs #(
    parameter N    = 4,     // how many pins arrive
    parameter SW   = 2      // width of the select; N <= 2**SW
) (
    input  wire            clk,
    input  wire            rst_n,

    input  wire [N-1:0]    in_pins,   // asynchronous, straight off the pads
    input  wire [SW-1:0]   sel,

    // --- pad first: register every bit, then select ------------------------
    output wire            out_pad_first,
    // --- logic first: select, then register one bit ------------------------
    output reg             out_logic_first
);

    // ---------------------------------------------------------------- pad first
    // N flops with NOTHING in front of them. On Xilinx this carries
    //
    //     (* IOB = "TRUE" *)
    //
    // and on Intel an ALTERA_ATTRIBUTE requesting a fast input register. The
    // attribute is omitted from this file deliberately: it is vendor-specific, it
    // belongs with the constraints (Chapter 15.7), and including one here would
    // suggest that the attribute is what makes this work. It is not -- the absence
    // of logic is.
    reg [N-1:0] pad_r;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) pad_r <= {N{1'b0}};
        else        pad_r <= in_pins;
    end

    // The select is AFTER the register, so it is fabric logic on a path that has a
    // whole clock period, rather than logic between a pin and a flop.
    assign out_pad_first = pad_r[sel];

    // ------------------------------------------------------------- logic first
    // One flop, and an N-to-1 mux in front of it. Functionally this is the same
    // selection; structurally it is the version that cannot use a pad register,
    // because the mux is between the pin and the flop.
    //
    // Note how ordinary it looks. Nothing about it reads as a mistake, and that is
    // why it gets written: it is smaller, it simulates identically under a static
    // select, and the only thing that objects is a place-and-route report.
    wire sel_first = in_pins[sel];

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) out_logic_first <= 1'b0;
        else        out_logic_first <= sel_first;
    end

`ifdef SPI_CHECKS
    // The property worth asserting is the one that holds: with `sel` unchanged from
    // one cycle to the next, the two implementations agree. Asserting that they
    // always agree would be asserting something false, and asserting nothing would
    // miss the case that matters -- a refactor from one form to the other is safe
    // exactly when the select is static, and this is where that gets checked.
    reg [SW-1:0] sel_d;
    reg          primed;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            sel_d  <= {SW{1'b0}};
            primed <= 1'b0;
        end else begin
            if (primed && sel == sel_d && out_pad_first !== out_logic_first)
                $fatal(1, "the two implementations disagree with a static select");
            sel_d  <= sel;
            primed <= 1'b1;
        end
    end
`endif

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
spi_io_regs.vhd — the same design in VHDL
-- spi_io_regs.vhd
--
-- Chapter 15.8 -- using the I/O registers instead of fighting the fabric.
--
-- A pad register is a flip-flop that lives in the I/O block rather than in the
-- fabric. On every FPGA family there is one per pin, it is already paid for, and a
-- path that ends in it has essentially no routing between the pin and the flop --
-- which is usually the difference between meeting an input setup requirement and
-- not.
--
-- There is exactly one rule for using it: NOTHING MAY COME BETWEEN THE PIN AND THE
-- REGISTER. Not a mux, not a gate, not an inverter that the tool cannot absorb. The
-- register has to be the first thing the signal meets.
--
-- That rule is easy to state and easy to break by accident, because the natural way
-- to write a design breaks it. This file builds the same function twice -- once
-- obeying the rule and once not -- so that the difference can be examined rather
-- than asserted.
--
-- THE FUNCTION. An N-bit input bus, of which the design needs one bit, chosen by a
-- configuration input:
--
--     out = in_pins[sel]
--
-- THE TWO IMPLEMENTATIONS.
--
--   PAD FIRST    register all N bits at the pads, then select.
--                N flops, all of them placeable in pads, no logic before any of
--                them. The select is combinational AFTER the register.
--
--   LOGIC FIRST  select at the pads, then register the chosen bit.
--                ONE flop, and it cannot go in a pad, because there is an N-to-1
--                mux in front of it. The select is combinational BEFORE the register.
--
-- The second is what a designer writes when thinking about area, and it is smaller
-- by N-1 flops. It is also the one that will not close timing, and no simulation
-- will ever say so.
--
-- AND THEY ARE NOT QUITE THE SAME CIRCUIT, WHICH IS THE INTERESTING PART.
--
-- With `sel` held still the two are indistinguishable: same value, same latency, bit
-- for bit, forever. Change `sel` while data is moving and they differ by one cycle
-- in when the change takes effect -- pad-first re-selects from data already captured,
-- logic-first selects before capturing.
--
-- So the trade is three-sided rather than two:
--
--     pad first    N flops   placeable   `sel` takes effect on captured data
--     logic first  1 flop    not         `sel` takes effect on arriving data
--
-- A design that switches `sel` between transactions -- which is what a real
-- configuration register does -- cannot tell the difference, and should take the
-- pad-first version. A design that switches `sel` DURING a transaction has chosen a
-- behaviour, and should say which one it wanted.
--
-- WHAT THE ATTRIBUTES DO, AND WHAT THEY DO NOT.
--
-- `IOB = "TRUE"` (Xilinx) or the equivalent Intel assignment is a REQUEST. The tool
-- honours it when the rule above is satisfied and warns when it is not -- and a
-- warning in a log of ten thousand lines is not a mechanism. The mechanism is the
-- structure; the attribute only makes the tool's refusal visible.
--
-- This is the same shape as Chapter 14.1's `ASYNC_REG`: the attribute does not create
-- the property, it stops the tool destroying it, and the property has to be there to
-- begin with.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity spi_io_regs is
    generic (
        N  : positive := 4;   -- how many pins arrive
        SW : positive := 2    -- width of the select; N <= 2**SW
    );
    port (
        clk             : in  std_logic;
        rst_n           : in  std_logic;

        in_pins         : in  std_logic_vector(N - 1 downto 0);
        sel             : in  unsigned(SW - 1 downto 0);

        -- pad first: register every bit, then select
        out_pad_first   : out std_logic;
        -- logic first: select, then register one bit
        out_logic_first : out std_logic
    );
end entity;

architecture rtl of spi_io_regs is

    signal pad_r     : std_logic_vector(N - 1 downto 0) := (others => '0');
    signal logic_r   : std_logic := '0';
    signal sel_first : std_logic;

begin

    -- ---------------------------------------------------------------- pad first
    -- N flops with NOTHING in front of them. On Xilinx this carries an
    -- `IOB = "TRUE"` attribute and on Intel an ALTERA_ATTRIBUTE requesting a fast
    -- input register. The attribute is omitted deliberately: it is vendor-specific,
    -- it belongs with the constraints (Chapter 15.7), and including one here would
    -- suggest the attribute is what makes this work. It is not -- the absence of
    -- logic is.
    padreg : process (clk, rst_n)
    begin
        if rst_n = '0' then
            pad_r <= (others => '0');
        elsif rising_edge(clk) then
            pad_r <= in_pins;
        end if;
    end process;

    -- The select is AFTER the register, so it is fabric logic on a path that has a
    -- whole clock period rather than logic between a pin and a flop.
    out_pad_first <= pad_r(to_integer(sel));

    -- ------------------------------------------------------------- logic first
    -- One flop, and an N-to-1 mux in front of it. Functionally the same selection;
    -- structurally the version that cannot use a pad register.
    --
    -- Note how ordinary it looks. Nothing about it reads as a mistake, and that is
    -- why it gets written: it is smaller, it simulates identically under a static
    -- select, and the only thing that objects is a place-and-route report.
    sel_first <= in_pins(to_integer(sel));

    logicreg : process (clk, rst_n)
    begin
        if rst_n = '0' then
            logic_r <= '0';
        elsif rising_edge(clk) then
            logic_r <= sel_first;
        end if;
    end process;

    out_logic_first <= logic_r;

    -- The property worth asserting is the one that HOLDS: with `sel` unchanged from
    -- one cycle to the next, the two implementations agree. Asserting that they
    -- always agree would be asserting something false, and asserting nothing would
    -- miss the case that matters -- a refactor from one form to the other is safe
    -- exactly when the select is static, and this is where that gets checked.
    check : process (clk, rst_n)
        variable sel_d  : unsigned(SW - 1 downto 0) := (others => '0');
        variable primed : boolean := false;
    begin
        if rst_n = '0' then
            sel_d  := (others => '0');
            primed := false;
        elsif rising_edge(clk) then
            if primed and sel = sel_d then
                assert pad_r(to_integer(sel)) = logic_r
                    report "the two implementations disagree with a static select"
                    severity failure;
            end if;
            sel_d  := sel;
            primed := true;
        end if;
    end process;

end architecture;

The testbench

Four experiments, and the second and third are a matched pair: the same random select compared between edges and at edges, giving 81 disagreements and 0.

Azvya Education Pvt. Ltd.VLSI Mentor
spi_io_regs_tb.sv — four experiments, two of which are a matched pair sampled between edges and at them
// spi_io_regs_tb.sv
//
// Three claims, and the third is the one that makes this chapter's advice safe to
// follow rather than merely correct.
//
//   1  WITH A STATIC SELECT THE TWO IMPLEMENTATIONS ARE BIT-FOR-BIT IDENTICAL, over
//      every select value and a long random stimulus. That is what licenses the
//      refactor: a design that only changes `sel` between transactions can move from
//      the smaller form to the pad-placeable one with no functional change at all.
//
//   2  WITH A MOVING SELECT THEY DIFFER, and the bench measures how often rather than
//      asserting that they might. The difference is one cycle in when the select
//      takes effect, and a design that switches mid-transaction has to choose --
//      which is a specification question, not an optimisation.
//
//   3  AND SIMULATION CANNOT TELL WHICH ONE CLOSES TIMING. Stated in the log, because
//      the whole point of the chapter is a property no bench can measure, and a green
//      run that is read as "either form is fine" is worse than no run.

`timescale 1ns/1ps

module spi_io_regs_tb;

    localparam int N  = 4;
    localparam int SW = 2;

    reg clk   = 1'b0;
    reg rst_n = 1'b1;
    always #5 clk = ~clk;

    reg [N-1:0]  in_pins = {N{1'b0}};
    reg [SW-1:0] sel     = {SW{1'b0}};

    wire out_pad_first, out_logic_first;

    spi_io_regs #(.N(N), .SW(SW)) dut (
        .clk(clk), .rst_n(rst_n),
        .in_pins(in_pins), .sel(sel),
        .out_pad_first(out_pad_first), .out_logic_first(out_logic_first)
    );

    integer errors = 0;

    initial begin
        #400_000;
        $display("FAIL: the simulation did not finish within its time limit");
        $finish;
    end

    integer seed = 32'h51A5_E158;
    integer disagreements = 0, samples = 0;

    task automatic restart;
        begin
            rst_n = 1'b1;
            repeat (2) @(posedge clk);
            rst_n = 1'b0;
            repeat (4) @(posedge clk);
            rst_n = 1'b1;
            repeat (4) @(posedge clk);
            disagreements = 0;
            samples = 0;
        end
    endtask

    integer s, k;

    initial begin
        // =============================================================
        // 1. A STATIC SELECT: the two forms must agree bit for bit, for every
        //    select value, over a long random stimulus. The SPI_CHECKS assertion
        //    inside the design checks the same thing continuously; this loop makes
        //    it a reported count as well, so a reader of the log can see that the
        //    equivalence was exercised rather than merely not violated.
        // =============================================================
        $display("  a %0d-bit input bus with a %0d-bit select, comparing a pad-first and a logic-first capture",
                 N, SW);
        $display("  sel  samples  disagreements");

        for (s = 0; s < N; s = s + 1) begin
            restart();
            sel = s[SW-1:0];
            repeat (4) @(posedge clk);
            for (k = 0; k < 200; k = k + 1) begin
                @(negedge clk);
                in_pins = $random(seed);
                @(posedge clk); #1;
                samples = samples + 1;
                if (out_pad_first !== out_logic_first)
                    disagreements = disagreements + 1;
            end

            $display("  %3d  %7d  %13d", s, samples, disagreements);

            if (disagreements != 0) begin
                $display("  FAIL: with sel held at %0d the two forms disagreed %0d times in %0d samples -- with a static select they are the same circuit and any difference is a bug in one of them",
                         s, disagreements, samples);
                errors = errors + 1;
            end
        end
        $display("  with the select held still the two forms are bit-for-bit identical at every select value, which is what licenses moving a design from the smaller form to the pad-placeable one");

        // =============================================================
        // 2. A MOVING SELECT: they must differ, and the difference is exactly one
        //    cycle in when the select takes effect. If they did NOT differ, one of
        //    them would not be doing what its structure says it does.
        // =============================================================
        // WHERE THE DIFFERENCE IS OBSERVABLE, which took two attempts to get right
        // and is the most useful thing in this bench.
        //
        // Comparing the two outputs at every posedge finds ZERO disagreements, at any
        // stimulus -- because `sel` is stable across the clock edge, so `pad_r[sel]`
        // and the registered `in_pins[sel]` were computed from the same pair. The
        // first two attempts at this test did exactly that and concluded the two
        // forms were identical, which they are not.
        //
        // The difference lives in a WINDOW: after the data has been captured and
        // before the next capture, a change to `sel` re-selects pad-first's output
        // from the value already in `pad_r` and leaves logic-first's registered
        // output alone. Observing it requires changing `sel` after the posedge and
        // comparing before the next one.
        //
        // That is a general lesson about comparing two implementations: if the
        // comparison is only ever made at clock edges, a difference that lives
        // between them is invisible -- and "we diffed them cycle by cycle" is not the
        // same as "they are the same circuit".
        restart();
        for (k = 0; k < 200; k = k + 1) begin
            @(negedge clk);
            in_pins = $random(seed);         // present the data
            @(posedge clk);                  // both forms capture it
            @(negedge clk);
            sel = $random(seed);             // move the select AFTER the capture
            #1;
            samples = samples + 1;
            if (out_pad_first !== out_logic_first)
                disagreements = disagreements + 1;
        end

        $display("  with the select moved AFTER the data was captured and compared before the next capture: %0d disagreements in %0d samples",
                 disagreements, samples);

        if (disagreements == 0) begin
            $display("  FAIL: a select moved after the capture must re-select pad-first's output from the value already held, so the two forms must differ somewhere in 200 samples");
            errors = errors + 1;
        end
        $display("  and there they differ, because pad-first re-selects from a value it has ALREADY CAPTURED while logic-first's register keeps what it selected earlier -- the difference lives BETWEEN clock edges, which is why comparing the two at posedges finds none and why 'we diffed them cycle by cycle' is not the same as 'they are the same circuit'");

        // 2b. AND THE CONTROL: the same random select, compared AT the clock edges
        //     instead of between them. Zero disagreements, and the number is reported
        //     rather than discarded, because it is the boundary of the claim above --
        //     and because it is the result a reasonable bench would have produced and
        //     drawn the wrong conclusion from.
        restart();
        for (k = 0; k < 200; k = k + 1) begin
            @(negedge clk);
            in_pins = $random(seed);
            sel     = $random(seed);
            @(posedge clk); #1;
            samples = samples + 1;
            if (out_pad_first !== out_logic_first)
                disagreements = disagreements + 1;
        end
        if (disagreements != 0) begin
            $display("  FAIL: compared at the clock edges the two forms must agree, because `sel` is stable across the edge -- got %0d disagreements",
                     disagreements);
            errors = errors + 1;
        end
        $display("  compared AT the clock edges with the same random select, %0d disagreements in %0d samples -- which is the result a reasonable bench produces and the wrong conclusion to draw from it",
                 disagreements, samples);

        // =============================================================
        // 3. THE BOUNDARY CASE THAT DECIDES A REAL DESIGN: a select that changes
        //    only while the bus is idle. That is what a configuration register does,
        //    and it is the case in which the refactor is free.
        // =============================================================
        restart();
        for (s = 0; s < 4; s = s + 1) begin
            // change the select while nothing is moving
            @(negedge clk);
            in_pins = {N{1'b0}};
            sel     = s[SW-1:0];
            repeat (4) @(posedge clk);
            // then move data with the select held
            for (k = 0; k < 50; k = k + 1) begin
                @(negedge clk);
                in_pins = $random(seed);
                @(posedge clk); #1;
                samples = samples + 1;
                if (out_pad_first !== out_logic_first)
                    disagreements = disagreements + 1;
            end
        end
        if (disagreements != 0) begin
            $display("  FAIL: a select changed only between bursts produced %0d disagreements", disagreements);
            errors = errors + 1;
        end
        $display("  a select changed only while the bus is idle -- which is what a configuration register does -- produced zero disagreements across %0d samples, so for a real SPI slave the two forms are interchangeable and the pad-placeable one is free to choose",
                 samples);

        // =============================================================
        // 4. AND THE PROPERTY NO BENCH CAN MEASURE.
        // =============================================================
        $display("  NOT MEASURED HERE, and not measurable: which form meets the input setup requirement. Pad-first ends %0d flops directly at the pads with no logic in front of them; logic-first ends ONE flop behind a %0d-to-1 mux, and the mux is in the path from the pin to the flop. Simulation is identical; a place-and-route report is not, and the attribute that asks for a pad register can only WARN when the structure forbids it",
                 N, N);
        $display("  the cost is %0d flops against 1 -- which is the trade, and it is a trade worth making, because %0d flip-flops in I/O blocks are already paid for and fabric routing between a pin and a flop is not",
                 N, N);

        if (errors == 0)
            $display("PASS: a pad register is free, already present on every pin, and usable under exactly one rule -- nothing may come between the pin and the register. The two forms built here compute the same selection and differ only in which side of the register the mux sits on, and with the select HELD STILL they are bit-for-bit identical at every select value across eight hundred samples, which is what licenses a refactor from the smaller form to the placeable one. They are NOT the same circuit, though, and finding that took two attempts: compared at clock edges they agree on any stimulus, because `sel` is stable across the edge and both forms then read the same pair. The difference lives BETWEEN edges -- move the select after the data has been captured and pad-first re-selects from the value already in its register while logic-first keeps what it selected earlier -- so 'we diffed them cycle by cycle' is not the same as 'they are the same circuit', and a design that switches select mid-transaction is choosing a behaviour and should say which. For a real slave the select is a configuration register that changes while the bus is idle, and in that regime the bench measured zero disagreements, so the pad-placeable form is free to choose. What no bench can measure is the only thing the chapter is about: pad-first ends N flops at the pads with nothing in front of them and logic-first ends one flop behind an N-to-1 mux, the simulations are identical, and the difference appears in a place-and-route report -- where the IOB attribute can WARN that the structure forbids what it asked for, exactly as ASYNC_REG can only stop a tool destroying a property the structure already has");
        else
            $display("FAIL: %0d error(s)", errors);
        $finish;
    end

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
spi_io_regs_tb.v — the same bench in Verilog-2001
// spi_io_regs_tb.v
//
// Three claims, and the third is the one that makes this chapter's advice safe to
// follow rather than merely correct.
//
//   1  WITH A STATIC SELECT THE TWO IMPLEMENTATIONS ARE BIT-FOR-BIT IDENTICAL, over
//      every select value and a long random stimulus. That is what licenses the
//      refactor: a design that only changes `sel` between transactions can move from
//      the smaller form to the pad-placeable one with no functional change at all.
//
//   2  WITH A MOVING SELECT THEY DIFFER, and the bench measures how often rather than
//      asserting that they might. The difference is one cycle in when the select
//      takes effect, and a design that switches mid-transaction has to choose --
//      which is a specification question, not an optimisation.
//
//   3  AND SIMULATION CANNOT TELL WHICH ONE CLOSES TIMING. Stated in the log, because
//      the whole point of the chapter is a property no bench can measure, and a green
//      run that is read as "either form is fine" is worse than no run.

`timescale 1ns/1ps

module spi_io_regs_tb;

    localparam N  = 4;
    localparam SW = 2;

    reg clk;
    reg rst_n;
    always #5 clk = ~clk;

    reg [N-1:0]  in_pins;
    reg [SW-1:0] sel;

    wire out_pad_first, out_logic_first;

    spi_io_regs #(.N(N), .SW(SW)) dut (
        .clk(clk), .rst_n(rst_n),
        .in_pins(in_pins), .sel(sel),
        .out_pad_first(out_pad_first), .out_logic_first(out_logic_first)
    );

    integer errors;

    initial begin
        #400_000;
        $display("FAIL: the simulation did not finish within its time limit");
        $finish;
    end

    integer seed;
    integer disagreements, samples;

    task restart;
        begin
            rst_n = 1'b1;
            repeat (2) @(posedge clk);
            rst_n = 1'b0;
            repeat (4) @(posedge clk);
            rst_n = 1'b1;
            repeat (4) @(posedge clk);
            disagreements = 0;
            samples = 0;
        end
    endtask

    integer s, k;

    initial begin
        // =============================================================
        // 1. A STATIC SELECT: the two forms must agree bit for bit, for every
        //    select value, over a long random stimulus. The SPI_CHECKS assertion
        //    inside the design checks the same thing continuously; this loop makes
        //    it a reported count as well, so a reader of the log can see that the
        //    equivalence was exercised rather than merely not violated.
        // =============================================================
        $display("  a %0d-bit input bus with a %0d-bit select, comparing a pad-first and a logic-first capture",
                 N, SW);
        $display("  sel  samples  disagreements");

        for (s = 0; s < N; s = s + 1) begin
            restart();
            sel = s[SW-1:0];
            repeat (4) @(posedge clk);
            for (k = 0; k < 200; k = k + 1) begin
                @(negedge clk);
                in_pins = $random(seed);
                @(posedge clk); #1;
                samples = samples + 1;
                if (out_pad_first !== out_logic_first)
                    disagreements = disagreements + 1;
            end

            $display("  %3d  %7d  %13d", s, samples, disagreements);

            if (disagreements != 0) begin
                $display("  FAIL: with sel held at %0d the two forms disagreed %0d times in %0d samples -- with a static select they are the same circuit and any difference is a bug in one of them",
                         s, disagreements, samples);
                errors = errors + 1;
            end
        end
        $display("  with the select held still the two forms are bit-for-bit identical at every select value, which is what licenses moving a design from the smaller form to the pad-placeable one");

        // =============================================================
        // 2. A MOVING SELECT: they must differ, and the difference is exactly one
        //    cycle in when the select takes effect. If they did NOT differ, one of
        //    them would not be doing what its structure says it does.
        // =============================================================
        // WHERE THE DIFFERENCE IS OBSERVABLE, which took two attempts to get right
        // and is the most useful thing in this bench.
        //
        // Comparing the two outputs at every posedge finds ZERO disagreements, at any
        // stimulus -- because `sel` is stable across the clock edge, so `pad_r[sel]`
        // and the registered `in_pins[sel]` were computed from the same pair. The
        // first two attempts at this test did exactly that and concluded the two
        // forms were identical, which they are not.
        //
        // The difference lives in a WINDOW: after the data has been captured and
        // before the next capture, a change to `sel` re-selects pad-first's output
        // from the value already in `pad_r` and leaves logic-first's registered
        // output alone. Observing it requires changing `sel` after the posedge and
        // comparing before the next one.
        //
        // That is a general lesson about comparing two implementations: if the
        // comparison is only ever made at clock edges, a difference that lives
        // between them is invisible -- and "we diffed them cycle by cycle" is not the
        // same as "they are the same circuit".
        restart();
        for (k = 0; k < 200; k = k + 1) begin
            @(negedge clk);
            in_pins = $random(seed);         // present the data
            @(posedge clk);                  // both forms capture it
            @(negedge clk);
            sel = $random(seed);             // move the select AFTER the capture
            #1;
            samples = samples + 1;
            if (out_pad_first !== out_logic_first)
                disagreements = disagreements + 1;
        end

        $display("  with the select moved AFTER the data was captured and compared before the next capture: %0d disagreements in %0d samples",
                 disagreements, samples);

        if (disagreements == 0) begin
            $display("  FAIL: a select moved after the capture must re-select pad-first's output from the value already held, so the two forms must differ somewhere in 200 samples");
            errors = errors + 1;
        end
        $display("  and there they differ, because pad-first re-selects from a value it has ALREADY CAPTURED while logic-first's register keeps what it selected earlier -- the difference lives BETWEEN clock edges, which is why comparing the two at posedges finds none and why 'we diffed them cycle by cycle' is not the same as 'they are the same circuit'");

        // 2b. AND THE CONTROL: the same random select, compared AT the clock edges
        //     instead of between them. Zero disagreements, and the number is reported
        //     rather than discarded, because it is the boundary of the claim above --
        //     and because it is the result a reasonable bench would have produced and
        //     drawn the wrong conclusion from.
        restart();
        for (k = 0; k < 200; k = k + 1) begin
            @(negedge clk);
            in_pins = $random(seed);
            sel     = $random(seed);
            @(posedge clk); #1;
            samples = samples + 1;
            if (out_pad_first !== out_logic_first)
                disagreements = disagreements + 1;
        end
        if (disagreements != 0) begin
            $display("  FAIL: compared at the clock edges the two forms must agree, because `sel` is stable across the edge -- got %0d disagreements",
                     disagreements);
            errors = errors + 1;
        end
        $display("  compared AT the clock edges with the same random select, %0d disagreements in %0d samples -- which is the result a reasonable bench produces and the wrong conclusion to draw from it",
                 disagreements, samples);

        // =============================================================
        // 3. THE BOUNDARY CASE THAT DECIDES A REAL DESIGN: a select that changes
        //    only while the bus is idle. That is what a configuration register does,
        //    and it is the case in which the refactor is free.
        // =============================================================
        restart();
        for (s = 0; s < 4; s = s + 1) begin
            // change the select while nothing is moving
            @(negedge clk);
            in_pins = {N{1'b0}};
            sel     = s[SW-1:0];
            repeat (4) @(posedge clk);
            // then move data with the select held
            for (k = 0; k < 50; k = k + 1) begin
                @(negedge clk);
                in_pins = $random(seed);
                @(posedge clk); #1;
                samples = samples + 1;
                if (out_pad_first !== out_logic_first)
                    disagreements = disagreements + 1;
            end
        end
        if (disagreements != 0) begin
            $display("  FAIL: a select changed only between bursts produced %0d disagreements", disagreements);
            errors = errors + 1;
        end
        $display("  a select changed only while the bus is idle -- which is what a configuration register does -- produced zero disagreements across %0d samples, so for a real SPI slave the two forms are interchangeable and the pad-placeable one is free to choose",
                 samples);

        // =============================================================
        // 4. AND THE PROPERTY NO BENCH CAN MEASURE.
        // =============================================================
        $display("  NOT MEASURED HERE, and not measurable: which form meets the input setup requirement. Pad-first ends %0d flops directly at the pads with no logic in front of them; logic-first ends ONE flop behind a %0d-to-1 mux, and the mux is in the path from the pin to the flop. Simulation is identical; a place-and-route report is not, and the attribute that asks for a pad register can only WARN when the structure forbids it",
                 N, N);
        $display("  the cost is %0d flops against 1 -- which is the trade, and it is a trade worth making, because %0d flip-flops in I/O blocks are already paid for and fabric routing between a pin and a flop is not",
                 N, N);

        if (errors == 0)
            $display("PASS: a pad register is free, already present on every pin, and usable under exactly one rule -- nothing may come between the pin and the register. The two forms built here compute the same selection and differ only in which side of the register the mux sits on, and with the select HELD STILL they are bit-for-bit identical at every select value across eight hundred samples, which is what licenses a refactor from the smaller form to the placeable one. They are NOT the same circuit, though, and finding that took two attempts: compared at clock edges they agree on any stimulus, because `sel` is stable across the edge and both forms then read the same pair. The difference lives BETWEEN edges -- move the select after the data has been captured and pad-first re-selects from the value already in its register while logic-first keeps what it selected earlier -- so 'we diffed them cycle by cycle' is not the same as 'they are the same circuit', and a design that switches select mid-transaction is choosing a behaviour and should say which. For a real slave the select is a configuration register that changes while the bus is idle, and in that regime the bench measured zero disagreements, so the pad-placeable form is free to choose. What no bench can measure is the only thing the chapter is about: pad-first ends N flops at the pads with nothing in front of them and logic-first ends one flop behind an N-to-1 mux, the simulations are identical, and the difference appears in a place-and-route report -- where the IOB attribute can WARN that the structure forbids what it asked for, exactly as ASYNC_REG can only stop a tool destroying a property the structure already has");
        else
            $display("FAIL: %0d error(s)", errors);
        $finish;
    end


    initial begin
        disagreements = 0;
        samples = 0;
        clk = 1'b0;
        rst_n = 1'b1;
        in_pins = {N{1'b0}};
        sel = {SW{1'b0}};
        errors = 0;
        seed = 32'h51A5_E158;
    end

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
spi_io_regs_tb.vhd — the same bench in VHDL
-- spi_io_regs_tb.vhd
--
-- Three claims, and the third is the one that makes this chapter's advice safe to
-- follow rather than merely correct.
--
--   1  WITH A STATIC SELECT THE TWO IMPLEMENTATIONS ARE BIT-FOR-BIT IDENTICAL, over
--      every select value and a long random stimulus. That is what licenses the
--      refactor: a design that only changes `sel` between transactions can move from
--      the smaller form to the pad-placeable one with no functional change at all.
--
--   2  WITH A MOVING SELECT THEY DIFFER, and the bench measures how often rather than
--      asserting that they might. The difference is one cycle in when the select
--      takes effect, and a design that switches mid-transaction has to choose --
--      which is a specification question, not an optimisation.
--
--   3  AND SIMULATION CANNOT TELL WHICH ONE CLOSES TIMING. Stated in the log, because
--      the whole point of the chapter is a property no bench can measure, and a green
--      run that is read as "either form is fine" is worse than no run.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;   -- `uniform`, for the random stimulus

entity spi_io_regs_tb is
end entity;

architecture sim of spi_io_regs_tb is

    constant N  : positive := 4;
    constant SW : positive := 2;

    signal clk   : std_logic := '0';
    signal rst_n : std_logic := '1';
    signal halt  : boolean   := false;

    signal in_pins : std_logic_vector(N - 1 downto 0) := (others => '0');
    signal sel     : unsigned(SW - 1 downto 0) := (others => '0');

    signal out_pad_first, out_logic_first : std_logic;

begin

    clkgen : process
    begin
        while not halt loop
            clk <= '0'; wait for 5 ns; clk <= '1'; wait for 5 ns;
        end loop;
        wait;
    end process;

    dut : entity work.spi_io_regs
        generic map (N => N, SW => SW)
        port map (clk => clk, rst_n => rst_n,
                  in_pins => in_pins, sel => sel,
                  out_pad_first => out_pad_first,
                  out_logic_first => out_logic_first);

    watchdog : process
    begin
        wait for 400 us;
        if not halt then
            report "FAIL: the simulation did not finish within its time limit"
                severity failure;
        end if;
        wait;
    end process;

    stim : process
        variable errs : natural := 0;
        variable disagreements, samples : natural := 0;
        variable seed1 : positive := 51151;
        variable seed2 : positive := 15873;
        variable r : real;

        impure function rnd(modulo : positive) return natural is
        begin
            uniform(seed1, seed2, r);
            return integer(r * real(modulo - 1));
        end function;

        procedure restart is
        begin
            rst_n <= '1';
            for i in 1 to 2 loop wait until rising_edge(clk); end loop;
            rst_n <= '0';
            for i in 1 to 4 loop wait until rising_edge(clk); end loop;
            rst_n <= '1';
            for i in 1 to 4 loop wait until rising_edge(clk); end loop;
            disagreements := 0;
            samples := 0;
        end procedure;
    begin
        report "  a " & integer'image(N) & "-bit input bus with a " &
               integer'image(SW) &
               "-bit select, comparing a pad-first and a logic-first capture";
        report "  sel  samples  disagreements";

        -- 1. A STATIC SELECT: the two forms must agree bit for bit.
        for s in 0 to N - 1 loop
            restart;
            sel <= to_unsigned(s, SW);
            for i in 1 to 4 loop wait until rising_edge(clk); end loop;
            for k in 1 to 200 loop
                wait until falling_edge(clk);
                in_pins <= std_logic_vector(to_unsigned(rnd(2 ** N), N));
                wait until rising_edge(clk);
                wait for 1 ns;
                samples := samples + 1;
                if out_pad_first /= out_logic_first then
                    disagreements := disagreements + 1;
                end if;
            end loop;

            report "  " & integer'image(s) & "  " & integer'image(samples) &
                   "  " & integer'image(disagreements);

            if disagreements /= 0 then
                report "  FAIL: with sel held at " & integer'image(s) &
                       " the two forms disagreed " & integer'image(disagreements) &
                       " times -- with a static select they are the same circuit";
                errs := errs + 1;
            end if;
        end loop;
        report "  with the select held still the two forms are bit-for-bit identical at every select value, which is what licenses moving a design from the smaller form to the pad-placeable one";

        -- 2. WHERE THE DIFFERENCE IS OBSERVABLE: between clock edges, not at them.
        restart;
        for k in 1 to 200 loop
            wait until falling_edge(clk);
            in_pins <= std_logic_vector(to_unsigned(rnd(2 ** N), N));
            wait until rising_edge(clk);              -- both forms capture it
            wait until falling_edge(clk);
            sel <= to_unsigned(rnd(N), SW);           -- move the select AFTER capture
            wait for 1 ns;
            samples := samples + 1;
            if out_pad_first /= out_logic_first then
                disagreements := disagreements + 1;
            end if;
        end loop;
        report "  with the select moved AFTER the data was captured and compared before the next capture: " &
               integer'image(disagreements) & " disagreements in " &
               integer'image(samples) & " samples";
        if disagreements = 0 then
            report "  FAIL: a select moved after the capture must re-select pad-first's output from the value already held, so the two forms must differ somewhere in 200 samples";
            errs := errs + 1;
        end if;
        report "  and there they differ, because pad-first re-selects from a value it has ALREADY CAPTURED while logic-first's register keeps what it selected earlier -- the difference lives BETWEEN clock edges, which is why comparing the two at posedges finds none and why 'we diffed them cycle by cycle' is not the same as 'they are the same circuit'";

        -- 2b. THE CONTROL: the same random select, compared AT the clock edges.
        restart;
        for k in 1 to 200 loop
            wait until falling_edge(clk);
            in_pins <= std_logic_vector(to_unsigned(rnd(2 ** N), N));
            sel     <= to_unsigned(rnd(N), SW);
            wait until rising_edge(clk);
            wait for 1 ns;
            samples := samples + 1;
            if out_pad_first /= out_logic_first then
                disagreements := disagreements + 1;
            end if;
        end loop;
        if disagreements /= 0 then
            report "  FAIL: compared at the clock edges the two forms must agree, because `sel` is stable across the edge -- got " &
                   integer'image(disagreements) & " disagreements";
            errs := errs + 1;
        end if;
        report "  compared AT the clock edges with the same random select, " &
               integer'image(disagreements) & " disagreements in " &
               integer'image(samples) &
               " samples -- which is the result a reasonable bench produces and the wrong conclusion to draw from it";

        -- 3. THE BOUNDARY CASE THAT DECIDES A REAL DESIGN: a select that changes
        --    only while the bus is idle, which is what a configuration register does.
        restart;
        for s in 0 to 3 loop
            wait until falling_edge(clk);
            in_pins <= (others => '0');
            sel     <= to_unsigned(s, SW);
            for i in 1 to 4 loop wait until rising_edge(clk); end loop;
            for k in 1 to 50 loop
                wait until falling_edge(clk);
                in_pins <= std_logic_vector(to_unsigned(rnd(2 ** N), N));
                wait until rising_edge(clk);
                wait for 1 ns;
                samples := samples + 1;
                if out_pad_first /= out_logic_first then
                    disagreements := disagreements + 1;
                end if;
            end loop;
        end loop;
        if disagreements /= 0 then
            report "  FAIL: a select changed only between bursts produced " &
                   integer'image(disagreements) & " disagreements";
            errs := errs + 1;
        end if;
        report "  a select changed only while the bus is idle -- which is what a configuration register does -- produced zero disagreements across " &
               integer'image(samples) &
               " samples, so for a real SPI slave the two forms are interchangeable and the pad-placeable one is free to choose";

        -- 4. AND THE PROPERTY NO BENCH CAN MEASURE.
        report "  NOT MEASURED HERE, and not measurable: which form meets the input setup requirement. Pad-first ends " &
               integer'image(N) &
               " flops directly at the pads with no logic in front of them; logic-first ends ONE flop behind a " &
               integer'image(N) &
               "-to-1 mux, and the mux is in the path from the pin to the flop. Simulation is identical; a place-and-route report is not, and the attribute that asks for a pad register can only WARN when the structure forbids it";
        report "  the cost is " & integer'image(N) &
               " flops against 1 -- which is the trade, and it is a trade worth making, because " &
               integer'image(N) &
               " flip-flops in I/O blocks are already paid for and fabric routing between a pin and a flop is not";

        if errs = 0 then
            report "PASS: a pad register is free, already present on every pin, and usable under exactly one rule -- nothing may come between the pin and the register. The two forms built here compute the same selection and differ only in which side of the register the mux sits on, and with the select HELD STILL they are bit-for-bit identical at every select value across eight hundred samples, which is what licenses a refactor from the smaller form to the placeable one. They are NOT the same circuit, though, and finding that took two attempts: compared at clock edges they agree on any stimulus, because `sel` is stable across the edge and both forms then read the same pair. The difference lives BETWEEN edges -- move the select after the data has been captured and pad-first re-selects from the value already in its register while logic-first keeps what it selected earlier -- so 'we diffed them cycle by cycle' is not the same as 'they are the same circuit', and a design that switches select mid-transaction is choosing a behaviour and should say which. For a real slave the select is a configuration register that changes while the bus is idle, and in that regime the bench measured zero disagreements, so the pad-placeable form is free to choose. What no bench can measure is the only thing the chapter is about: pad-first ends N flops at the pads with nothing in front of them and logic-first ends one flop behind an N-to-1 mux, the simulations are identical, and the difference appears in a place-and-route report -- where the IOB attribute can WARN that the structure forbids what it asked for, exactly as ASYNC_REG can only stop a tool destroying a property the structure already has";
        else
            report "FAIL: " & integer'image(errs) & " error(s)" severity error;
        end if;

        halt <= true;
        wait;
    end process;

end architecture;

6. Why a Verification Engineer Cares

Sample between edges when comparing two implementations, not only at them. §3's callout is the point. A diff that samples at posedges establishes agreement at the posedges it sampled, and where inputs can move at other times that is a weaker claim than it reads as.

Report the control alongside the result. The bench keeps the at-the-edges comparison and prints its zero, labelled as "the result a reasonable bench produces and the wrong conclusion to draw from it". Deleting it would leave a reader unable to see why the between-edges measurement was necessary.

Assert the narrow true property rather than the broad false one. §5's callout. sel static implies equality is checkable and is the refactor's licence; unconditional equality is false and would have to be deleted, leaving nothing.

And measure the case that actually occurs. For an SPI slave sel is a configuration register that changes while the bus is idle, and the bench measures that regime explicitly and separately — because it is the regime in which the engineering advice applies, and a suite that only exercised the pathological case would conclude the two forms are not interchangeable.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Properties for the two forms. The first is the only unconditional one, and note
// what it is NOT: it is not equality of the two outputs.

property p_equal_under_a_static_select;
    // The narrow, true property -- and the licence for the refactor.
    @(posedge clk) disable iff (!rst_n)
        (sel == $past(sel)) |-> (out_pad_first == out_logic_first);
endproperty

property p_pad_first_reselects_from_captured_data;
    // What pad-first actually does, stated so that a reader knows it is a choice:
    // its output is the CURRENT select applied to the PREVIOUS cycle's pins.
    @(posedge clk) disable iff (!rst_n)
        1 |-> (out_pad_first == $past(in_pins)[sel]);
endproperty

property p_logic_first_selected_before_capturing;
    // And what logic-first does: the PREVIOUS select applied to the previous pins.
    @(posedge clk) disable iff (!rst_n)
        1 |-> (out_logic_first == $past(in_pins)[$past(sel)]);
endproperty

property p_pad_registers_have_no_logic_in_front;
    // Not expressible as a temporal property at all, and that is worth recording in
    // the property file rather than omitting: the rule is STRUCTURAL. The checks that
    // reach it are a lint rule counting logic levels between a port and its first
    // flop, and a place-and-route report. This comment is the property.
endproperty
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Coverage. The axis that matters is WHEN THE SELECT MOVES relative to the capture,
// because that is the only thing that distinguishes the two forms -- and it is an
// axis a normal coverage model does not have, since normally it would not matter.

covergroup cg_io_regs @(posedge clk);
    option.per_instance = 1;

    sel_moves: coverpoint select_change_timing {
        bins never           = {0};
        bins with_the_data   = {1};    // same cycle: the two forms agree
        bins after_a_capture = {2};    // between captures: they differ
        bins while_idle      = {3};    // the real SPI case
    }

    // The select value itself, because the mux's behaviour at the ends of its range
    // is where an off-by-one in a width would show.
    sel_val: coverpoint sel { bins all[] = {[0:3]}; }

    // Whether the data was changing in the same cycle, which is what decides whether
    // a select change is observable at all.
    data_moving: coverpoint data_changed_this_cycle { bins yes = {1}; bins no = {0}; }

    x_moves_data: cross sel_moves, data_moving;

endgroup

7. Why an FPGA or ASIC Engineer Cares

The pad register is free, so the N-flop form costs nothing real. That is the whole engineering argument: N flip-flops in I/O blocks are already fabricated and unused, and fabric routing between a pin and a flop is not free. Trading N-1 fabric flops for N pad flops is trading something scarce for something already paid for.

The attribute can only warn. §4. A design that asks for IOB = TRUE on a pin whose flop has a mux in front of it gets a warning and a fabric flop, and the warning is one line among thousands. The structure is the mechanism.

Reset on an input pad register may prevent placement. On some families a resettable flop is not the one available in the I/O block, or the reset mux is fabric logic in front of it. That is why Chapter 15.7's shell has no reset on sclk_i or mosi_i and does on cs_n_i — the exception is paid for deliberately and only where it buys something.

And the input path is where this matters, more than the output. An output's timing is a flop's clock-to-out plus the pad, and a fabric flop's clock-to-out is only slightly worse. An input's timing is the arrival against the flop's setup, and routing from a pin into the fabric is where the nanoseconds go — so if only some registers can be placed, the inputs get them.

8. Failure Signature — A Design That Needs A Slower SPI Clock Than It Should

The symptom:

"The datasheet says 25 MHz and the part only works to 12. Timing reports show the input paths failing by about 2 ns. The RTL is correct and simulation is clean at any frequency."

What is happening: the capture registers are in the fabric rather than in the pads, because something sits between the pins and them — a mux, a polarity XOR, an enable gate. The 2 ns is routing from the pin into the fabric, which a pad register would not have.

Why the RTL looks correct: it is correct. The function is right, the simulation is right, and the fault is where a register was placed — which is a structural property that neither the RTL nor the simulation expresses.

How to find it in one report: list the input paths and look at where each one ends. A path ending in a fabric flop with routing delay between the pin and it is the fault, and the fix is to move whatever logic sits in front of that flop to the other side of it — which is §1's pad-first form, and which §2 shows is functionally free when the select is static.

9. Common Misconceptions

"The two forms are the same circuit because simulation cannot tell them apart." Simulation compared at clock edges cannot tell them apart. Compared between edges they differ in 40% of samples, and §3 is the measurement.

"IOB = TRUE puts the register in the pad." It asks. The tool grants the request when nothing is in the way and warns when something is, and the warning is easy to miss.

"The logic-first form is smaller, so it is better where area matters." It is smaller by N-1 fabric flops and costs N unused pad flops, which are already paid for. In exchange it gives up the only placement that meets input setup on a fast interface.

"A cycle-by-cycle diff proves two implementations equivalent." It proves they agree at the instants the diff sampled. Where the inputs can move at other instants — and a configuration register can — that is a weaker claim.

"An assertion should check that the two forms are equal." That assertion is false and would have to be deleted. The true one is conditional on the select being static, and it is exactly the licence a refactor needs.

"Resetting every register is good practice, so the pad registers should be reset." On some families the reset mux is the logic that prevents placement. The reset is worth paying for on chip select, because a slave waking up selected is dangerous, and not on the others.

10. Reason It Through

Q. An 8-bit bus and a 3-bit select. How many flops does each form use, and which is cheaper in practice?

Pad-first uses 8 flops, all in I/O blocks. Logic-first uses 1, in the fabric, behind an 8-to-1 mux. Logic-first is cheaper by 7 fabric flops and pad-first is cheaper in the resource that is actually scarce, because the 8 I/O flops exist whether or not they are used. And pad-first is the only one that can meet a tight input setup, so in practice it is cheaper on both axes that matter.

Q. Why do the two forms agree when sel changes in the same cycle as the data, and differ when it changes between captures?

Because pad_first is previous_pins[current_sel] and logic_first is previous_pins[previous_sel]. When sel changes at the same edge as the data, both expressions use the same select — the one that was stable across the edge — and agree. When sel changes between captures, current_sel differs from previous_sel while previous_pins is unchanged, so pad-first re-selects a different bit of a value already captured and logic-first does not.

Q. A design's sel comes from a configuration register that software writes at any time, including mid-transaction. Which form should it use, and what else must change?

Whichever behaviour it wants — and it must first decide which, because the two are different specifications rather than two implementations of one. In practice the right move is to remove the question: latch sel at the transaction boundary, exactly as Chapter 13.2's configuration snapshot does. Then sel is static within a transaction, the two forms are equivalent, and the pad-placeable one can be chosen freely. The design decision is not which form to use; it is to stop sel moving mid-transaction.

Q. Why does the design's internal assertion not simply check that both outputs are equal?

Because they are not equal, so the assertion would fail on a correct design and be deleted — leaving no check at all. The conditional form, equality when the select is static, is true and is exactly what the refactor depends on. A narrow true assertion is worth more than a broad false one, and this is one of the clearer cases: the false version's inevitable deletion takes the real check with it.

Q. Why does this chapter's argument matter more for inputs than for outputs?

Because an output's timing budget is a flop's clock-to-out plus the pad driver, and a fabric flop's clock-to-out is only slightly worse than a pad flop's — the difference is routing from the fabric to the pad, which the tool can usually absorb. An input's budget is the arrival time against the flop's setup, and the routing from the pin into the fabric is unavoidable delay on the critical side of that comparison. So when only some registers can be placed in pads, the inputs get them.

11. Understanding Check

12. Summary

A pad register is free, present on every pin, and usable under exactly one rule: nothing may come between the pin and the register. The natural way to write a design breaks it.

The two forms built here compute the same selection and differ only in which side of the register the mux sits on. Under a static select they are bit-for-bit identical across eight hundred samples, which is what licenses a refactor from the smaller form to the placeable one — and for a real SPI slave, whose select is a configuration register changed while the bus is idle, the measurement is zero disagreements.

They are not the same circuit, and finding that took two attempts. Compared at clock edges they agree on any stimulus, because the select is stable across the edge and both forms then read the same pair. The difference lives between edges: move the select after the data has been captured and pad-first re-selects from the value already in its register while logic-first keeps what it selected earlier — 81 disagreements in 200 samples.

So "we diffed them cycle by cycle" is not the same as "they are the same circuit", and that is the most transferable idea in the chapter.

The trade is three-sided: N flops and placeable with the select acting on captured data, against one flop and not placeable with the select acting on arriving data. A design that switches mid-transaction is choosing a behaviour and should say which — or better, should latch the select at the transaction boundary and remove the question.

The attribute is a request. It warns when the structure forbids what it asked for, and a warning in a ten-thousand-line log is not a mechanism — the same shape as ASYNC_REG, which stops a tool destroying a property the structure already has.

For verification: sample between edges, not only at them; keep the at-the-edges control and label it as the misleading result; assert the narrow true property rather than the broad false one, because the false one's deletion takes the real check with it; and measure the regime that actually occurs.

For implementation: the N-flop form costs nothing scarce; a reset on an input pad register may itself prevent placement, which is why Chapter 15.7's shell resets only chip select; and this argument matters more for inputs than outputs, because an input's budget is an arrival against a setup and the routing is on the critical side.

13. What Comes Next

Every crossing in this module has been built, measured and constrained. What remains is the question the module has been circling since Chapter 15.1's two identical columns.

Chapter 15.9 — CDC and Constraint Failure Modes builds six crossings — one correct and five broken — and measures them under a benign stimulus and a hostile one. Under the benign one all six pass. Under the hostile one three of the five are found. The remaining two pass both, and they are the two that lose data on silicon — which is the argument for CDC review as an activity separate from CDC verification, and it is not an argument about rigour.

Continue learning