Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Course
  • Reading Path
  • CDC/RDC Glossary
  • CDC/RDC Metrics Reference
  • CDC/RDC Interview Prep
  • CDC/RDC Signoff Cheatsheet
  • Clock Domains and Failure Modes8
  • Metastability and Synchronizers16
  • CDC Protocols and Handshakes16
  • Reconvergence and Gray Coding16
  • Reset Domain Crossing16
  • Architecture and Design Rules16
  • CDC Signoff Methodology24
  • RDC Signoff16
  • Debug and Case Studies11
  • CDC and RDC Interview Prep30
  • Capstone: Sign Off a Multi-clock Peripheral4
    • Capstone Brief
    • Classify the Report
    • Fix and Verify
    • Waive and Sign Off
Loading lesson

‹Capstone: Sign Off a Multi-clock Peripheral
  1. Topics
  2. CDC & RDC
  3. Capstone: Sign Off a Multi-clock Peripheral
  4. Capstone Brief

Part 11 · Capstone Signoff Lab · Advanced

Capstone Brief

The peripheral specification, block diagram, crossing inventory, and the first version of the design.

On this page

    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 specification

    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.

    Block diagram showing the configuration domain, the engine domain, and the crossings between and into them.
    Block diagram of the capstone peripheral: threshold crosses into the engine, event and status cross back, reset enters both domains.

    The crossing inventory

    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

    eng_event (clk_eng)

    clk_cfg

    Single-bit pulse

    Two-flop synchroniser (ev_a, ev_b)

    Engine owner

    C2

    eng_count[3:0] (clk_eng)

    clk_cfg

    4-bit counter value

    One two-flop chain per bit (st_a, st_b)

    Engine owner

    C3

    cfg_thresh_q[3:0] (clk_cfg)

    clk_eng

    4-bit level

    Direct capture; held stable by software discipline

    Integration owner

    C4

    rst_n_async

    clk_cfg flops

    Reset release

    Asynchronous input used directly

    Reset owner

    C5

    rst_n_async

    clk_eng flops

    Reset release

    Reset synchroniser u_rst_eng

    Reset owner

    The first version

    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.

    rst_sync.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    // 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;
    endmodule

    What the checks look for

    A 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.

    How to work this capstone

    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.

    Common pitfall

    This version passes a functional simulation: the testbench pulse happens to overlap a destination edge, the status word usually settles before it is read, and reset releases cleanly at the simulated phase. None of that transfers to silicon, where phases drift and edges misalign. A green simulation on unsynchronised crossings is the trap this capstone teaches you to walk past.

    Check yourself The inventory lists five crossings. Without calling any of them wrong, which two rows would you examine first, and why?

    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.

    Check yourself Why does the inventory record an owner per crossing?

    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.

    Exercise

    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.

    Related topics

    • Next: Classify the Report

    • Crossing Inventory

    • Violation Classes

    • RTL: Metastability and Synchronisation

    Previous

    163 / 179

    Next: Classify the Report

    Up next

    Up nextClassify the Report

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Fix and Verify
    • Waive and Sign Off