Back to Learn

Learn / Concept

Macro & wrapper contract checking

A hard macro — a sky130/gf180 SRAM, a PLL, an IO pad, an analog block, or any vendor-hardened cell — is integrated into an SoC by instantiating it from an RTL wrapper and wiring the wrapper’s ports to the macro’s pins. The macro’s real contract lives in its physical views: the Liberty (.lib) timing/power model and the LEF abstract, not in the RTL. When the wrapper’s port list, directions, widths, or power/ground (PG) pins diverge from those views, the build fails late at LVS, silently mis-connects power, or ties an output where an input was expected. This guide explains the contract and how a structural checker cross-checks it.

Where the real contract lives

A hard macro is a fixed physical object. You cannot change its pins; you can only connect to them correctly. The authoritative description of those pins is split across two files. The Liberty view lists each pin in a pin or bus group with a direction, and the power/ground rails as pg_pin groups. The LEF abstract lists the same pins as PIN entries under the MACRO, with a DIRECTION and a USE POWER|GROUND marker for the rails. The RTL wrapper is just how you reference the macro — a hand-written black-box declaration plus a named instantiation — and it is exactly the kind of artifact that drifts as a design is refactored.

The bugs that hide in a wrapper

These mismatches survive functional simulation completely, because simulation runs the RTL view of the macro — it never consults the Liberty or LEF, and it does not model power and ground at all. The most damaging is a missing PG pin: the wrapper omits the macro’s power or ground connection, so the rail is unconnected and the failure shows up only at LVS or, worse, in the lab. Other common bugs are an output pin wired as an input, a bus declared at the wrong width, and a wrapper that simply omits a pin the macro has.

// RTL wrapper that instantiates a hard macro (e.g. a sky130 SRAM).
// The macro's REAL contract is its Liberty/LEF view, NOT this code.
// Two integration bugs hide here that a functional sim never sees:
//   1. the PG pins (VPWR/VGND) are absent from the wrapper
//   2. one data pin is wired with the wrong direction
module sram_wrap (
  input  logic        clk,
  input  logic        we,
  input  logic [9:0]  addr,
  input  logic [31:0] wdata,
  output logic [31:0] rdata
  // BUG: no VPWR / VGND ports -> PG pins from the LEF are unconnected
);
  sky130_sram_1kx32 u_sram (
    .clk0  (clk),
    .csb0  (1'b0),
    .web0  (we),
    .addr0 (addr),
    .din0  (wdata),
    .dout0 (rdata)
    // BUG: .vccd1 / .vssd1 (power/ground) not connected here
  );
endmodule

What a contract checker cross-checks

A macro contract checker parses both sides and compares only what it can compare soundly. From the views it builds, per macro, a map of each pin to its direction, bus width (where derivable), and whether it is a power/ground pin. From the RTL it reads the black-box module declaration ports and the instance’s named .PIN(net) connections. Then it emits findings:

  • missing pin — a pin in the view absent from the RTL declaration or connection; and, separately, a missing PG pin present in the view but absent from the wrapper (a real integration bug).
  • direction mismatch — a view output declared or used as an RTL input, reported only when both sides are explicit.
  • width mismatch — a derivable bus width that differs, reported only when both widths are derivable.
  • view-internal mismatch — the Liberty and LEF disagree on a pin’s direction.
  • unchecked — a pin or aspect that could not be compared soundly, surfaced rather than silently assumed equal.

What is deliberately out of scope

The checker does not guess what it cannot read. Signal polarity (active-low vs active-high) is not checked, because neither Liberty nor LEF carries an explicit polarity attribute and inferring it from pin names would be unsound. A Liberty bus with no resolvable type or range is left unchecked per pin (LEF bracket-indexed bits are counted). Positional, ordered instance connections are not checked — only named .PIN(net) hookups. This is structural connectivity evidence against the views you provide; it is not electrical timing, not LVS, and not a signoff.

How ChipVerify AI helps

ChipVerify AI (a pre-signoff RTL evidence tool, currently in closed beta, not a foundry signoff tool) cross-checks an RTL macro wrapper against the macro’s Liberty and LEF views and names every missing pin, missing PG connection, direction flip, and width mismatch by file and line — integration evidence no open tool checks as a unit, surfaced before the build reaches LVS.

Related reading

Try the public scanner at /tinytapeout or request access.

FAQ

Why isn't the RTL the source of truth for a hard macro's pins?

A hard macro — an SRAM, PLL, IO pad, or analog block — is a fixed physical object. Its real contract is in its physical views: the Liberty (.lib) timing/power model and the LEF abstract, which list the actual pins, their directions, and the power/ground connections. The RTL wrapper is only how you instantiate and wire it; it is a hand-written declaration that can drift from the views. When the wrapper disagrees with the views, the build fails late at LVS, silently mis-connects power, or ties an output where an input was expected.

What does a macro contract checker actually compare?

It parses the macro's pin contract from the Liberty (pin/bus/pg_pin groups and direction) and/or the LEF (MACRO/PIN/DIRECTION/USE POWER|GROUND), building a per-macro map of each pin's direction, bus width where derivable, and whether it is a power/ground pin. It parses the RTL side — the black-box module declaration ports and the named instance connections — and cross-checks them: a pin in the view missing from the RTL, a power/ground pin absent from the wrapper, a direction declared opposite to the view, a bus width that differs, or a Liberty/LEF internal disagreement. Anything it cannot compare soundly is reported as unchecked, never assumed equal.

What is out of scope for this check?

Signal polarity (active-low vs active-high) is not checked, because Liberty and LEF carry no explicit polarity attribute and the checker will not guess from pin names. Liberty bus widths with no resolvable type/range are left unchecked per pin. Positional (ordered) instance connections are not checked — only named .PIN(net) hookups. It is structural connectivity evidence against the provided views, not electrical timing, not signoff, and not LVS itself.

Cross-check your macro wrappers against their views

Sign in, upload your RTL wrapper and the macro's Liberty/LEF, and ChipVerify AI names missing pins, missing power/ground connections, direction flips, and width mismatches by file and line — structural integration evidence to review, not a foundry signoff or LVS.