Every signoff starts the same way: a specification, a picture of the clock domains, and an inventory of what crosses between them. This lesson gives you all three for the capstone peripheral, plus the first version of its RTL. The code below is complete and honest about what it is — a starting point with problems still in it, the way real block RTL arrives at the signoff desk. Your job across the next three lessons is to do what the methodology sections describe: classify its report, fix what is broken, waive what is safe, and close the gate.
The peripheral has two clock domains. The configuration domain (clk_cfg, about 37 MHz, 27 ns period) owns a simple register interface: a write strobe cfg_wr with 4-bit write data cfg_wdata programs a threshold register. The engine domain (clk_eng, 100 MHz, 10 ns period) runs a free-running 4-bit counter and raises a one-cycle event pulse eng_event whenever the counter equals the programmed threshold. Two results travel back to the configuration domain: the 4-bit counter value as a status word, and the event as an interrupt-style pulse. A single asynchronous reset input rst_n_async (active-low) must release cleanly in both domains.
Before any report is read, the inventory lists every crossing the design intends: source, destination, what kind of signal it is, what structure carries it, and who owns it. This table is the map the later lessons work against. Read it as a statement of what is there, not as a verdict on whether it is right — deciding that is the next lesson's exercise.
# | Source | Destination | Type | Protocol | Owner |
|---|---|---|---|---|---|
C1 |
|
| Single-bit pulse | Two-flop synchroniser ( | Engine owner |
C2 |
|
| 4-bit counter value | One two-flop chain per bit ( | Engine owner |
C3 |
|
| 4-bit level | Direct capture; held stable by software discipline | Integration owner |
C4 |
|
| Reset release | Asynchronous input used directly | Reset owner |
C5 |
|
| Reset release | Reset synchroniser | Reset owner |
The full design is below, 55 lines including the reset synchroniser. The module names peri_top and rst_sync stay the same in every later lesson, so findings and fixes always point at blocks you can already name. It is marked as the first version: it compiles and it simulates, and — as the report will show — that proves less than it seems.
// bad-example: capstone peripheral, first version (signoff pending).
// It compiles and simulates cleanly: the three planted defects are
// structural, so a passing simulation says nothing about CDC safety.
module rst_sync (
input wire clk, input wire rst_n_async, output wire rst_n_sync
);
reg [1:0] sh;
always @(posedge clk or negedge rst_n_async) begin
if (!rst_n_async) sh <= 2'b00;
else sh <= {sh[0], 1'b1};
end
assign rst_n_sync = sh[1];
endmodule
module peri_top (
input wire clk_cfg, input wire clk_eng, input wire rst_n_async,
input wire cfg_wr, input wire [3:0] cfg_wdata,
output wire [3:0] status_cfg, output wire event_cfg
);
wire rst_n_eng;
rst_sync u_rst_eng (.clk(clk_eng), .rst_n_async(rst_n_async), .rst_n_sync(rst_n_eng));
reg [3:0] cfg_thresh_q;
reg [3:0] eng_count, eng_thresh;
reg eng_event;
reg ev_a, ev_b;
reg [3:0] st_a, st_b;
always @(posedge clk_eng or negedge rst_n_eng) begin
if (!rst_n_eng) begin
eng_count <= 4'h0; eng_thresh <= 4'h0; eng_event <= 1'b0;
end else begin
eng_count <= (eng_count == 4'hF) ? 4'h0 : eng_count + 4'h1;
eng_thresh <= cfg_thresh_q;
eng_event <= (eng_count == eng_thresh);
end
end
always @(posedge clk_cfg or negedge rst_n_async) begin
if (!rst_n_async) cfg_thresh_q <= 4'h0;
else if (cfg_wr) cfg_thresh_q <= cfg_wdata;
end
always @(posedge clk_cfg or negedge rst_n_async) begin
if (!rst_n_async) begin ev_a <= 1'b0; ev_b <= 1'b0; end
else begin ev_a <= eng_event; ev_b <= ev_a; end
end
assign event_cfg = ev_b;
always @(posedge clk_cfg or negedge rst_n_async) begin
if (!rst_n_async) begin st_a <= 4'h0; st_b <= 4'h0; end
else begin st_a <= eng_count; st_b <= st_a; end
end
assign status_cfg = st_b;
endmoduleA structural analysis of this design asks one question per inventory row: does the carrying structure match the signal type? A pulse needs a pulse qualifier, a multi-bit value needs a protocol, a reset release needs a synchroniser, and a direct capture needs a written stability argument. The inventory gives the reviewer the rows; the report in the next lesson gives the findings; the classification is yours. The violation classes lesson lists the categories those findings will fall into.
Follow the signoff loop in lesson order. First classify each finding in the report lesson as fix, waive, or accept — without peeking at the fixes. Then repair the defects in the fix lesson and check each repair three ways. Finally write the single waiver and close the gate in the signoff lesson. Each lesson's output is the next lesson's input, exactly like a real signoff thread.
C1 and C2: both carry engine-domain information into a slower configuration domain, and both rows describe bare synchroniser structures with no stated qualifier or protocol. Type-versus-structure mismatch is where signoff findings concentrate.
Because every finding later needs someone to fix it or defend it. A crossing with no owner becomes a waiver with no author — the exact failure the waiver policy exists to prevent.
Extend it: draw the same inventory for a peripheral you know (a UART, a timer, a sensor front end) with real clock names, and mark for each row which structure carries it today. Bring that table to the report lesson and classify its rows the same way you classify C1 to C5.