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

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • RTL Foundations36
  • Combinational Design25
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
    • Reset Architecture
      • Reset Is a System Contract
      • Async Assert, Sync Deassert
      • Reset Tree Planning
      • Reset Sequencing and POR Controller
      • What a Reset Costs
    • Metastability and Synchronisation
    • Asynchronous FIFO
    • CDC Protocols
    • Reset Domain Crossing
    • CDC Verification
  • Memory & Buses57
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

←Reset Architecture
  1. Topics
  2. RTL Design
  3. Reset & CDC
  4. Reset Architecture
  5. Reset Sequencing and POR Controller
On this page

    Part 6 of 9, lesson 4 of 34Part 6 of 9/Reset & CDC/Lesson 4 of 34Intermediate

    Reset Sequencing and POR Controller

    A sequencer that releases domain resets in order after PLL lock, with hold and gap times.

    Requesting resets in the order A, B, C does not release them in that order. With destination periods of 70, 14 and 22 ns, B's local reset releases at 77 ns, before A's at 105 ns, and traffic aimed at B can be lost.

    After this lesson you can: describe the power-up sequence, set HOLD and GAP, and gate each block's release on a readiness acknowledgement from the block it depends on.

    Order and timing at power-up

    When power comes up, the clock source takes time to stabilise. The design stays in reset until then.

    Blocks come out in dependency order: first the clock status block, then the memory controller, then the CPU that uses it.

    Power-up timeline of the sequencer below with HOLD = 8 and GAP = 4: release requests for A, B and C at lock + 9, +13 and +17 reference cycles

    Read the lanes from left to right; the time scale before lock is not drawn to scale, because the PLL can take any time to lock.

    • Nothing is requested until pll_lock rises: the sequencer counts only qualified lock.

    • Request A rises at lock + 9: HOLD = 8 cycles of counting, plus one edge for the registered output.

    • B and C follow GAP = 4 cycles apart. These are requests, not local releases; the next sections show why order of completion needs an acknowledgement.

    Release requests are separated by GAP reference cycles after the lock hold; destination synchronisers and readiness follow separately.

    Sequencer operation

    The sequencer's own reset must already be release-synchronised to the sequencer clock, using a reset synchroniser. The three outputs are registered release requests, not local reset completion. With HOLD = 8 and GAP = 4 they rise at lock + 9, +13 and +17 reference edges.

    Each request then passes through its destination reset synchroniser. In ideal RTL local reset rises on the second subsequent destination edge; stopped clocks or physical late resolution can delay completion. Request order does not guarantee completion order. For a true A-before-B dependency, A must assert a readiness level after local reset and initialisation, synchronise that level back to the controller, and gate B's request on that acknowledgement. Repeat for B-before-C. Clear readiness and acknowledgement history on coordinated reset; mask traffic until both peers are ready. The fixed-gap sequencer below does not implement these acknowledgements.

    Controller contract: clk is an always-running reference clock, independent of the PLL being monitored. pll_lock is already synchronised to this reference; the sequencer counts qualified high samples. A separate, technology-approved clock-fault/reset controller asserts arst_n asynchronously on clock loss and releases it synchronously to the reference. A short loss must be latched there until acknowledged; simply sampling raw lock can miss it. This sequencer does not implement that detector. Destination clocks must be clean before release, and downstream readiness, not GAP alone, determines when traffic may start.

    Make HOLD and GAP parameters and write them in the spec. The right numbers depend on the slowest block's requirement (for example “memory controller needs 16 clocks of reset after the PLL is stable”). The counter is sized from them with $clog2, so enlarging either one is safe.

    Common mistakes

    A synchronous !pll_lock branch responds only when the reference clock ticks. It is a secondary restart path for synchronised status, not immediate clock-loss containment. Route qualified clock faults through the asynchronous reset controller; never clock this sequencer from the PLL it monitors.

    Walk through a power-up: supply ramps, POR asserts reset, oscillator and PLL start, lock detect, hold time, ordered release, software boot. Mention the watchdog and lock-loss paths as additional reset sources.

    Check yourself With HOLD = 8 and GAP = 4, what happens at lock + 17 reference cycles?

    The controller requests C's release. Local C reset still needs two subsequent C edges in ideal RTL, and C may need further initialisation before acknowledging readiness. A dependency must wait for that acknowledgement, not just a reference-clock gap.

    Testbench

    reset_seq.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    `timescale 1ns/1ps
    
    // Reset sequencer: releases three domain resets in a fixed order, after the PLL is locked.
    module reset_seq #(parameter int HOLD = 8, parameter int GAP = 4) (
      input  logic clk,              // always-running reference, not the monitored PLL output
      input  logic arst_n,           // qualified faults assert asynchronously; release synchronised to clk
      input  logic pll_lock,         // already synchronised to clk by the clock controller
      output logic rst_a_n, rst_b_n, rst_c_n
    );
      logic [$clog2(HOLD + 2 * GAP + 2)-1:0] cnt;                         // sized from the parameters: it can always reach HOLD + 2 * GAP
      always_ff @(posedge clk or negedge arst_n)
        if (!arst_n) begin
          cnt <= '0;  rst_a_n <= 1'b0;  rst_b_n <= 1'b0;  rst_c_n <= 1'b0;   // everything held in reset
        end else if (!pll_lock) begin
          cnt <= '0;  rst_a_n <= 1'b0;  rst_b_n <= 1'b0;  rst_c_n <= 1'b0;   // a lost lock restarts the sequence
        end else begin
          if (cnt != '1) cnt <= cnt + 1'b1;                                      // saturate at all-ones, above every threshold
          if (cnt >= HOLD)           rst_a_n <= 1'b1;                         // 1st: A
          if (cnt >= HOLD + GAP)     rst_b_n <= 1'b1;                         // 2nd: B, GAP cycles later
          if (cnt >= HOLD + 2 * GAP) rst_c_n <= 1'b1;                         // 3rd: C, GAP cycles after B
        end
    endmodule
    

    Requirement

    Source check

    Nothing released before lock

    Checked after 10 cycles with pll_lock = 0

    Requests held at least HOLD cycles after lock

    Source checks the registered request timing; expected first request at +9

    Request order A, B, C, exactly GAP apart

    Request cycles compared; no local completion claim

    Lock loss re-asserts everything

    All three resets low on the edge after pll_lock falls

    External fault/reset asserts without a clock

    Directed check stops the reference, asserts arst_n, and requires all outputs low immediately and throughout the stop

    Destination-edge trace and readiness acknowledgement

    Phase 1 requests A/B/C at 10/50/90 ns with 70/14/22 ns destination periods: local releases come at 105/77/121 ns, so B overtakes A. Phase 2 gates each dependent request on a two-register readiness acknowledgement. Readiness here means only one local edge after reset release; replace it with the block's actual initialisation completion. Trace lines report destination edge numbers.

    design.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
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    `timescale 1ns/1ps
    module tb_reset_completion;
      logic ref_clk = 0, ca = 0, cb = 0, cc = 0;
      logic ra = 0, rb = 0, rc = 0;
      logic [1:0] sa = 0, sb = 0, sc = 0;
      logic ready_a = 0, ready_b = 0;
      logic [1:0] ack_a = 0, ack_b = 0;
      int ea = 0, eb = 0, ec = 0;
      realtime ta = 0, tb = 0, tc = 0;
      always #5 ref_clk = ~ref_clk;
      always #35 ca = ~ca;
      always #7 cb = ~cb;
      always #11 cc = ~cc;
      always @(posedge ca or negedge ra)
        if (!ra) begin sa <= 0; ready_a <= 0; end
        else begin sa <= {sa[0], 1'b1}; ready_a <= sa[1]; end
      always @(posedge cb or negedge rb)
        if (!rb) begin sb <= 0; ready_b <= 0; end
        else begin sb <= {sb[0], 1'b1}; ready_b <= sb[1]; end
      always @(posedge cc or negedge rc)
        if (!rc) sc <= 0;
        else sc <= {sc[0], 1'b1};
      // Coordinated restart clears acknowledgement history before another sequence.
      always @(posedge ref_clk or negedge ra)
        if (!ra) begin ack_a <= 0; ack_b <= 0; end
        else begin
          ack_a <= {ack_a[0], ready_a};
          ack_b <= {ack_b[0], ready_b};
        end
      always @(posedge ca) begin
        ea++; #1;
        if (sa[1] && ta == 0) begin ta = $realtime - 1; $display("A release: edge %0d at %0t", ea, ta); end
      end
      always @(posedge cb) begin
        eb++; #1;
        if (sb[1] && tb == 0) begin tb = $realtime - 1; $display("B release: edge %0d at %0t", eb, tb); end
      end
      always @(posedge cc) begin
        ec++; #1;
        if (sc[1] && tc == 0) begin tc = $realtime - 1; $display("C release: edge %0d at %0t", ec, tc); end
      end
      initial begin
        $timeformat(-9, 0, " ns", 0);   // print %t values in ns, not ps
        #10 ra = 1;
        #40 rb = 1;
        #40 rc = 1;
        #60;
        if (ta != 105 || tb != 77 || tc != 121)
          $fatal(1, "unexpected ideal destination-edge trace: %0t %0t %0t", ta, tb, tc);
        // Restart away from all rising edges, then use completion acknowledgements.
        #2; ra = 0; rb = 0; rc = 0;
        #2;
        if (sa !== 0 || sb !== 0 || sc !== 0 || ack_a !== 0 || ack_b !== 0)
          $fatal(1, "restart did not clear reset/ack history");
        ta = 0; tb = 0; tc = 0;
        @(negedge ref_clk); ra = 1;
        for (int n = 0; n < 100 && ack_a[1] !== 1'b1; n++) begin
          @(posedge ref_clk); #1;
        end
        if (ack_a[1] !== 1'b1 || ta == 0) $fatal(1, "A readiness timeout");
        @(negedge ref_clk); rb = 1;
        for (int n = 0; n < 100 && ack_b[1] !== 1'b1; n++) begin
          @(posedge ref_clk); #1;
        end
        if (ack_b[1] !== 1'b1 || tb == 0) $fatal(1, "B readiness timeout");
        @(negedge ref_clk); rc = 1;
        repeat (3) begin @(posedge cc); #2; end
        if (!(ta < tb && tb < tc) || sc[1] !== 1'b1)
          $fatal(1, "acknowledged completion order failed");
        $display("PASS: request order can differ from completion; ready acknowledgements enforce dependencies");
        $finish;
      end
      initial begin #5000; $fatal(1, "completion watchdog"); end
    endmodule
    • The sequencer outputs are registered release requests, not local reset completion. With HOLD = 8 and GAP = 4 they rise at lock + 9, +13 and +17.

    • For a true A-before-B dependency, A raises a readiness level after reset and initialisation. It is synchronised back, and B's request waits for it.

    • A synchronous !pll_lock branch reacts only when the reference clock ticks. Route clock faults through the asynchronous reset path.

    Rule of thumb: an ordered request is not an ordered release; wait for the acknowledgement.

    How this was checked

    Source checker, run with Icarus Verilog 13.0 (2026-10-16): prints PASS, with requests at +9, +13 and +17. It covers registered request order, spacing, hold time, lock loss and asynchronous assertion. Destination completion is checked separately.

    Self-contained ideal-RTL checker, run with Icarus Verilog 13.0 (2026-10-16): prints the phase-1 trace at 105/77/121 ns, then PASS. These finite digital checks do not establish physical reset timing or CDC signoff.

    • Interview: run a mock interview on this topic

    Related topics

    • Next: Metastability and synchronisation

    Previous

    158 / 350

    Next: What a Reset Costs

    Up next

    Up nextWhat a Reset Costs

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Reset Is a System Contract
    • Async Assert, Sync Deassert