Your simulation passes. Lint is clean. The synthesis check is green. Are you done? Not yet. Another team must turn your RTL into silicon, and they cannot ask your testbench questions. Anything you leave unsaid becomes a guess, and a guess in the back-end becomes wrong hardware.
What does the next team need from your RTL before they can do their job?
Your register-transfer level (RTL) code is finished when the next team can build from it without asking you anything. That team spans several groups, and each one reads your package for a different answer. The package below is the full set: eight items, each with one owner waiting for it.
RTL source and a file list in compile order. The code plus the order to read it in, so the build never depends on which file the tool happens to see first.
The top module name. One name that says where the design starts. Everything else hangs below it.
Every clock, with its intended frequency. Each clock's name and the speed it must run at. An unnamed clock is a guessed clock.
Every reset, fully described. Polarity, active level, whether it acts with or without the clock, and how it is released. Resets that differ between blocks must say so.
Hierarchy and ownership. Which blocks exist and who owns each one, so questions reach the right person.
Timing constraints. What speed each path must meet. The next lesson reads this file line by line.
Known issues and waivers. Every open problem plus every waived check. A waiver is written permission to ignore a flagged check, with a reason, an owner, and a revisit date. An unwritten waiver does not exist.
Tool versions used. The exact releases that produced the green run, recorded as Pinning Tool Versions describes, so the back-end reproduces your results before trusting them.
Three items on the list deserve one line each, because the back-end raises them on every project. A clock-domain crossing is a signal passing between two clocks, and each one needs handling the back-end must know about. Scan readiness means your flip-flops can join the test chains manufacturing uses to find broken chips, so nothing in your RTL may block that insertion. Power intent names which blocks switch off or retain state, so the power team plans for it from the start.
The figure below draws the package as one box on the left and its five readers on the right. Follow each arrow: the same eight items answer different questions for synthesis, physical design, timing sign-off, manufacturing test, and power.
Before the package leaves, the RTL must pass a hygiene list: the habits that keep the back-end from tripping on your code. The project's counter is shown below, and each check after it names what the back-end would flag:
`timescale 1ns/1ps
// Counts accepted events, 4 bits, wraps 15 -> 0.
// Priority: reset > clear > count > hold. Reset is synchronous, active low.
module mini_controller (
input logic clk,
input logic rst_n,
input logic event_valid,
input logic clear_count,
output logic [3:0] count
);
always_ff @(posedge clk) begin
if (!rst_n)
count <= 4'd0;
else if (clear_count)
count <= 4'd0;
else if (event_valid)
count <= count + 4'd1;
end
endmoduleThis listing is the design the checks below judge: one clock, one reset, and a priority chain the tools can build directly.
One clock. The design has a single clock input, clk. The constraints file gives its intended period as 10 ns, an illustrative value chosen for teaching, and the package must state it so nobody guesses.
One reset, fully stated. The reset rst_n is synchronous and active low: the if (!rst_n) branch sits inside the clocked block, so deassertion takes effect on a rising edge like any other synchronous input.
No inferred latches. The priority chain covers reset, then clear, then count, then hold inside a clocked block, so every path assigns the count or keeps it by design. Nothing holds a value by accident.
No delays or initial blocks in the RTL. The source contains no timing controls and no initial blocks: everything in it describes hardware, and nothing in it runs only in simulation.
Lint is clean. The Verilator lint step passes with no warnings on this file, measured on macOS with Verilator 5.048:
Synthesis is clean. The Yosys check reports zero problems on this file, measured on macOS with Yosys 0.69+post. You have already read both clean outputs in full: lint in Lint with Verilator and the synthesis check in First Synthesis Check. The hand-off only needs the verdict, so repeat both runs and tick them off the checklist:
Check | Command | Hand-off passes when |
|---|---|---|
Lint |
| no warnings and exit status 0 |
Synthesis check |
| the check reports 0 problems and exit status 0 |
Simulation |
| exactly one |
Everything |
| all three above, in one command |
One clock, clk. It is written in three places that must agree: the port list in the RTL, the constraints file with its intended period, and the hand-off package. If any one of the three is missing, the back-end guesses, and a guessed clock builds the wrong hardware.
Change a 4-bit increment to add an 8-bit constant instead, and lint fails the build at once with two width warnings and a nonzero exit, measured on macOS with Verilator 5.048:
# captured terminal output
$ verilator --lint-only -Wall --top-module w widthdemo/w.sv
%Warning-WIDTHEXPAND: widthdemo/w.sv:2:45: Operator ADD expects 8 bits on the LHS, but LHS's VARREF 'count' generates 4 bits.
: ... note: In instance 'w'
2 | always_ff @(posedge clk) count <= count + 8'd1;
| ^
... For warning description see https://verilator.org/warn/WIDTHEXPAND?v=5.048
... Use "/* verilator lint_off WIDTHEXPAND */" and lint_on around source to disable this message.
%Warning-WIDTHTRUNC: widthdemo/w.sv:2:36: Operator ASSIGNDLY expects 4 bits on the Assign RHS, but Assign RHS's ADD generates 8 bits.
: ... note: In instance 'w'
2 | always_ff @(posedge clk) count <= count + 8'd1;
| ^~
... For warning description see https://verilator.org/warn/WIDTHTRUNC?v=5.048
... Use "/* verilator lint_off WIDTHTRUNC */" and lint_on around source to disable this message.
%Error: Exiting due to 2 warning(s)
[exit status 1]Read the warning literally: the addition expects 8 bits on one side while the target holds 4, and the assignment then truncates back to 4. The project avoids this by sizing every constant to its target, as the 4'd1 in the counter shows. The fix is the same everywhere: size the constant to the signal, or waive the warning in writing with a reason instead of ignoring it.
Handing over RTL with unstated clocks. A clock named only in conversation arrives at the back-end as a guess about frequency, and every timing answer built on it is wrong. Name each clock and its intended frequency in the package.
Resets that differ between blocks without saying so. One block's synchronous active-low reset is another block's asynchronous active-high reset until the package says otherwise. State every reset's polarity, timing, and release, or unify them first.
Leaving waivers unwritten. A waived warning with no note rots: the next tool version flags it again, the next engineer re-litigates it, and nobody remembers why it was safe. Write the reason, the owner, and the revisit date, or fix the code.
Rule: hand over everything the next team needs to build without asking you a question.
The short module below compiles. Read it as the back-end would: two hygiene problems hide in fourteen lines, and both would sail through simulation. Name them before reading the answer:
// A small enabled register. Read it before trusting it.
module needs_review (
input logic clk,
input logic en,
input logic d,
output logic q
);
initial q = 1'b0;
always_comb begin
if (en)
q = d;
end
endmoduleFirst, the combinational block assigns q only when en is high, so the tools infer a latch: q holds its value by accident instead of by design. Second, the initial block sets q once at time zero in simulation and synthesizes to nothing, so the design has no real reset. The fix gives the module a synchronous active-low reset input, moves the state into a clocked block with a reset branch, and gives the combinational logic a default assignment so no path holds by accident.
One clock, clk, with its intended period from the constraints file, which is an illustrative 10 ns. One reset, rst_n, synchronous and active low, released synchronously on a rising edge. Both appear in the RTL, the constraints, and the package, all three agreeing.
Waive it in writing or fix the code, never ignore it silently. A written waiver names the check, the reason it is safe, the owner, and the revisit date. If the fix is cheaper than the paperwork, fix the code and keep the lint clean.
Done means the next team can build without asking you.
Write down every clock, every reset, and every waiver.
Check hygiene first: one clock, stated resets, no latches, lint and synthesis clean.