Volume 03 Intermediate 5 sub-modules ~55 min read

Sequential Circuit Design & Edge-Triggered Realities

Combinational logic has no memory and no opinion about time. The moment you add a flip-flop, time becomes a constraint you can violate - and most silicon bugs that survive to the lab live in exactly this territory: reset release, clock division, and the few hundred picoseconds either side of a clock edge.

3.1 The D flip-flop: master-slave silicon physics

A latch is level-sensitive: while its enable is high, the output follows the input. A flip-flop is edge-sensitive: the output changes only at one instant. But there is no magic "edge detector" inside a flop. An edge-triggered D flip-flop is just two latches in series, clocked in opposite phase.

A positive edge triggered D flip-flop built from a master latch and a slave latch clocked in opposite phase POSITIVE EDGE TRIGGERED D FLIP-FLOP D MASTER D-latch open when clk = 0 Qm SLAVE D-latch open when clk = 1 Q clk inverted clk = 0 Master follows D. Slave holds - Q cannot move. clk = 1 Master frozen at its last value. Slave passes it to Q. The two latches are NEVER open at the same time - that is what makes it edge-triggered.
Figure 3.1 - Data can only advance one stage per clock phase. On the rising edge the master snaps shut on whatever D was, and the slave opens to release it. There is no path from D straight through to Q at any instant - which is precisely why a flip-flop, unlike a latch, cannot be transparent.
This explains the shift register from Volume 01 Remember why q1 <= d; q2 <= q1; makes a two-stage pipeline rather than a wire? Because q1's slave and q2's master are never open simultaneously. The non-blocking assignment is not a simulator convention - it is a faithful model of this topology.

The three timing numbers

Because the master latch needs time to physically settle before it closes, the data cannot be changing right at the edge. That gives every flip-flop a forbidden window:

Timing diagram showing the setup and hold window around a rising clock edge and the clock to Q delay DATA MUST NOT CHANGE IN HERE clk D changes safely early Q t_su t_h t_cq edge
Figure 3.2 - t_su before the edge, t_h after it, and t_cq from the edge until Q is valid. Data arriving inside the crimson window leaves the flop in an undefined state - the subject of Volume 05.
Parameter What it means If you violate it How it is fixed
tsu Data stable before the edge Flop may capture the wrong value or go metastable Slow the clock, or shorten the combinational path
th Data stable after the edge New data races through and corrupts the capture Add delay buffers - slowing the clock does nothing
tcq Edge to valid Q Not a violation - it is a cost you budget for Eats into the next stage's timing budget
The asymmetry that gets asked in every interview A setup violation is a speed problem - lower the clock frequency and it goes away. A hold violation is a structural problem: it involves only t_cq, combinational delay and t_h, with the clock period appearing nowhere in the equation. You cannot fix a hold violation by slowing the clock. A chip with hold violations is broken at every frequency, including DC. We derive both equations formally in Volume 07.

Writing flops in Verilog


// ---- Plain positive-edge D flip-flop ------------------------------------
module dff (
  input  wire clk,
  input  wire d,
  output reg  q
);
  always @(posedge clk)
    q <= d;
endmodule


// ---- Negative-edge triggered -------------------------------------------
module dff_neg (
  input  wire clk,
  input  wire d,
  output reg  q
);
  // Legal, but mixing edge polarities in one design complicates timing
  // closure. Production RTL is almost always single-edge.
  always @(negedge clk)
    q <= d;
endmodule


// ---- Flop with a clock ENABLE (not a gated clock!) ---------------------
module dff_en (
  input  wire clk,
  input  wire en,
  input  wire d,
  output reg  q
);
  // The clock runs FREELY. The enable feeds a MUX in front of the D pin,
  // or maps to the flop's dedicated CE pin. This is the correct way to
  // "stop" a register - never by gating the clock in RTL.
  always @(posedge clk)
    if (en) q <= d;
    // No else: holding the old value IS the intended behaviour, and in a
    // clocked block that is a flop, not an inferred latch.
endmodule


// ---- SYNCHRONOUS active-low reset --------------------------------------
module dff_sync_rst (
  input  wire clk,
  input  wire rst_n,
  input  wire d,
  output reg  q
);
  // rst_n is NOT in the sensitivity list - it is just data that happens
  // to win. Nothing happens without a clock edge.
  always @(posedge clk)
    if (!rst_n) q <= 1'b0;
    else        q <= d;
endmodule


// ---- ASYNCHRONOUS active-low reset -------------------------------------
module dff_async_rst (
  input  wire clk,
  input  wire rst_n,
  input  wire d,
  output reg  q
);
  // The reset edge is in the sensitivity list, so the flop clears the
  // instant rst_n falls - no clock required.
  always @(posedge clk or negedge rst_n)
    if (!rst_n) q <= 1'b0;
    else        q <= d;
endmodule
The sensitivity list and the test must agree Writing always @(posedge clk or posedge rst_n) and then testing if (!rst_n) is a real, common bug. The polarity of the edge in the list must match the polarity you test: negedge rst_n pairs with if (!rst_n), posedge rst pairs with if (rst). Get it wrong and simulation and synthesis will disagree - the tool infers the reset from the sensitivity list, while the simulator obeys the if.
Interview grilling - "Why can a latch not be used where a flip-flop is required?"

"A latch is transparent for half the clock period. During that window the input feeds straight through to the output, so a signal can propagate through multiple stages of logic in a single cycle - a race. A flip-flop is never transparent, so exactly one stage of logic happens per clock, which is what makes synchronous timing analysis possible at all."

The follow-up: "Then why do latches exist in real chips?"

Time borrowing (a slow path can steal slack from the next stage), roughly half the transistor count of a master-slave flop, and the clock-gating cell itself is built from a latch. See the Volume 01 discussion - the distinction is always deliberate versus accidental.

3.2 Synchronous vs asynchronous reset trade-offs

Every design needs to start from a known state. The argument about how is one of the oldest in the field, and the correct answer is not "pick one" - it is assert asynchronously, release synchronously.

Synchronous reset Asynchronous reset
Sensitivity list @(posedge clk) @(posedge clk or negedge rst_n)
Needs a running clock? Yes - dead clock means no reset No - works before the PLL locks
Glitch immunity High - the clock filters short glitches None - a 200 ps glitch resets the chip
Timing checks Ordinary setup/hold on the data path Recovery and removal checks on the reset pin
Area Adds a MUX in front of every D pin Uses the flop's dedicated reset pin - free
Typical home FPGA fabric (LUT inputs are already there) ASIC (dedicated reset pin, works at power-up)

The dangerous half is the release

Asserting an asynchronous reset is genuinely safe: it does not care about the clock, which is the entire point. Releasing it is the problem. Two new timing checks exist on the reset pin, and they are the exact mirror of setup and hold:

Check Definition Analogous to
Recovery Minimum time reset must be de-asserted before the next active clock edge Setup time
Removal Minimum time reset must stay asserted after a clock edge Hold time

Now consider what happens in a real chip. Reset is a high-fanout net reaching tens of thousands of flops, each through a different amount of wire delay. If the release happens near a clock edge, some flops see reset gone before the edge and some after. Half your design starts on cycle n and half on cycle n+1 - and any flop caught exactly in the window can go metastable.

The reset synchronizer (AASD)

A two flip-flop reset synchronizer that asserts asynchronously and de-asserts synchronously, with the resulting waveform ASYNCHRONOUS ASSERT, SYNCHRONOUS DE-ASSERT 1'b1 D Q FF1 CLR D Q FF2 CLR rst_n to the design clk arst_n raw, unsynchronised clk arst_n rst_n assert: immediate, no clock needed arst_n released here... rst_n rises 2 edges later, on a clock edge
Figure 3.3 - Both flops have their CLR pins tied to the raw asynchronous reset, so assertion is instant. De-assertion has to shift a 1 through two flops, so it lands cleanly on a clock edge - with the second flop absorbing any metastability the first one picks up.

module reset_sync #(
  parameter STAGES = 2            // 2 is standard; 3 for very high frequency
) (
  input  wire clk,                // destination clock domain
  input  wire arst_n,             // raw asynchronous reset, active low
  output wire rst_n               // clean reset for this clock domain
);

  reg [STAGES-1:0] sync;

  always @(posedge clk or negedge arst_n) begin
    if (!arst_n)
      sync <= {STAGES{1'b0}};     // ASSERT: asynchronous, immediate
    else
      sync <= {sync[STAGES-2:0], 1'b1};   // DE-ASSERT: shift a 1 through
  end

  assign rst_n = sync[STAGES-1];

endmodule
Why this is the industry standard answer Assertion needs no clock, so the chip resets correctly at power-up before the PLL has locked. De-assertion is registered, so every flop in the domain leaves reset on the same clock edge - no split-brain start-up. And the second flop gives any metastability a full clock period to resolve. One tiny module, both problems solved.
One synchronizer per clock domain A reset synchronized to a 100 MHz clock is not valid for a 33 MHz domain. Each clock domain needs its own reset_sync instance driven by its own clock. This is one of the most common review findings in real designs, and it connects directly to reset domain crossing in Volume 05.

3.3 Counters & modulo-N design

A counter is an adder feeding a register feeding the adder. Everything interesting is in the control: when to count, when to wrap, and how to tell the next stage you wrapped.


module counter_mod_n #(
  parameter N = 10,               // counts 0 .. N-1
  parameter W = $clog2(N)
) (
  input  wire         clk,
  input  wire         rst_n,
  input  wire         en,         // count enable
  input  wire         clr,        // synchronous clear
  input  wire         load,       // synchronous parallel load
  input  wire [W-1:0] load_val,
  output reg  [W-1:0] count,
  output wire         tc          // terminal count
);

  // TC asserts on the LAST value, while still enabled - so it can drive
  // the enable of the next counter in a cascade without losing a count.
  assign tc = en && (count == N-1);

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n)      count <= {W{1'b0}};
    else if (clr)    count <= {W{1'b0}};    // clear beats load
    else if (load)   count <= load_val;     // load beats count
    else if (en) begin
      // Explicit wrap. Do NOT rely on natural rollover unless N is a
      // power of two - for N = 10 the counter would run to 15.
      if (count == N-1) count <= {W{1'b0}};
      else              count <= count + 1'b1;
    end
  end

endmodule
The priority order is a design decision, so state it The if / else if chain above encodes reset > clear > load > count. That ordering is real hardware - a priority MUX in front of the register - and it is exactly what an interviewer means when they ask "what happens if clr and load assert together?" There is no universally correct answer, but there is a wrong one: not knowing which your code implements.

Gray code counters

A binary counter can change several bits at once - 0111 → 1000 flips four. If another clock domain samples that value mid-transition it can read a value that was never actually there. A Gray code counter changes exactly one bit per step, so a mistimed sample can only ever be off by one count.

Binary to Gray - one XOR per bit, no carry chain gray = bin ⊕ (bin >> 1)

module gray_counter #(
  parameter W = 4
) (
  input  wire         clk,
  input  wire         rst_n,
  input  wire         en,
  output reg  [W-1:0] gray,
  output reg  [W-1:0] bin
);

  wire [W-1:0] bin_next  = bin + 1'b1;
  wire [W-1:0] gray_next = bin_next ^ (bin_next >> 1);

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      bin  <= {W{1'b0}};
      gray <= {W{1'b0}};
    end else if (en) begin
      bin  <= bin_next;
      gray <= gray_next;   // registered, so it is glitch-free at the output
    end
  end

endmodule
Where this is going Gray counters are the beating heart of the asynchronous FIFO in Volume 06. Keep the binary version alongside the Gray version - you need binary for arithmetic (comparing pointers, computing occupancy) and Gray only for crossing the clock boundary. Note also that Gray code only works this way when N is a power of two; a Gray sequence that wraps at a non-power-of-two changes multiple bits at the wrap.

3.4 Shift registers & linear feedback shift registers

A shift register is a chain of flops. Trivial as hardware, and enormously useful: serial interfaces, delay lines, synchronizers, pattern detectors, and - with one XOR gate added - pseudo-random number generation.


module shift_reg_universal #(
  parameter W = 8
) (
  input  wire         clk,
  input  wire         rst_n,
  input  wire [1:0]   mode,      // 00 hold, 01 shift right, 10 shift left, 11 load
  input  wire         sin_r,     // serial input for a right shift (enters MSB)
  input  wire         sin_l,     // serial input for a left shift  (enters LSB)
  input  wire [W-1:0] pin,       // parallel input
  output reg  [W-1:0] q
);

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) q <= {W{1'b0}};
    else begin
      case (mode)
        2'b00: q <= q;                    // hold
        2'b01: q <= {sin_r, q[W-1:1]};    // shift right
        2'b10: q <= {q[W-2:0], sin_l};    // shift left
        2'b11: q <= pin;                  // parallel load
      endcase
    end
  end

endmodule

The LFSR

Feed a few bits of a shift register through an XOR and back into the input, and the register stops shifting in data and starts walking a long, deterministic, statistically random-looking cycle. With the right tap positions an n-bit LFSR visits every state except one before repeating.

Maximal-length sequence period = 2n - 1    states
An eight bit Fibonacci linear feedback shift register with taps at bits eight six five and four 8-BIT FIBONACCI LFSR - x⁸ + x⁶ + x⁵ + x⁴ + 1 b7 b6 b5 b4 b3 b2 b1 b0 out feedback Taps at bits 8, 6, 5, 4 give a maximal-length sequence: 255 states before repeating. All-zeros is a dead state - XOR of zeros is zero, so it never leaves.
Figure 3.4 - Fibonacci form: several taps XOR together into a single feedback bit. Simple to read, but the XOR chain sits in the critical path.

// NOTE: tap positions are specific to the register WIDTH - they come from
// a table of primitive polynomials (see below). So these modules are fixed
// at 8 bits on purpose. Parameterising the width without also swapping the
// taps would silently destroy the maximal-length property.

module lfsr8_fibonacci (
  input  wire       clk,
  input  wire       rst_n,
  input  wire       en,
  output reg  [7:0] lfsr
);

  wire feedback;

  // Polynomial x^8 + x^6 + x^5 + x^4 + 1.
  // Bit 8 of the polynomial is lfsr[7], bit 6 is lfsr[5], and so on.
  assign feedback = lfsr[7] ^ lfsr[5] ^ lfsr[4] ^ lfsr[3];

  always @(posedge clk or negedge rst_n) begin
    // The seed MUST be non-zero. Any non-zero value works and gives the
    // same cycle, just entered at a different point.
    if (!rst_n)  lfsr <= 8'hFF;
    else if (en) lfsr <= {lfsr[6:0], feedback};
  end

endmodule


// ---- Self-correcting variant: 256 states, including all-zeros ----------
module lfsr8_de_bruijn (
  input  wire       clk,
  input  wire       rst_n,
  input  wire       en,
  output reg  [7:0] lfsr
);

  // Inverting the feedback when the low 7 bits are all zero splices the
  // all-zero state into the cycle, giving a full 2^8-length sequence.
  // Now a zero seed is harmless - useful on FPGAs, where registers
  // power up to zero.
  wire feedback = lfsr[7] ^ lfsr[5] ^ lfsr[4] ^ lfsr[3] ^ (lfsr[6:0] == 7'b0);

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n)  lfsr <= 8'h00;
    else if (en) lfsr <= {lfsr[6:0], feedback};
  end

endmodule
Width Polynomial Tap bits Sequence length
4x⁴ + x³ + 14, 315
8x⁸ + x⁶ + x⁵ + x⁴ + 18, 6, 5, 4255
16x¹⁶ + x¹⁵ + x¹³ + x⁴ + 116, 15, 13, 465 535
32x³² + x²² + x² + x + 132, 22, 2, 14 294 967 295
Interview grilling - "Why does an LFSR lock up, and how many states does it really have?"

The lock-up. With XOR feedback, the all-zeros state is a fixed point: the XOR of any number of zeros is zero, so the register shifts in a zero forever. That is why the sequence is 2ⁿ - 1 and not 2ⁿ - the all-zero state is excluded, forming its own one-state cycle.

The variant they will probe: "What if I use XNOR feedback instead?" Then the dead state is all ones, for exactly the same reason. XNOR is often preferred in FPGAs because registers power up to zero, so an XNOR LFSR self-starts without needing an explicit non-zero seed.

The other classic follow-up: "Fibonacci or Galois?"

FibonacciGalois
StructureMany taps → one XOR chain → inputOne bit fans out to XORs spread through the chain
Critical pathChain of XORs - grows with tap countOne XOR - constant
ReadabilityMatches the polynomial directlyHarder to eyeball
Use whenClarity mattersSpeed matters

Both produce maximal-length sequences of the same period from the same polynomial - just in a different order. Galois is what you want at high frequency.

Where LFSRs actually get used: built-in self-test pattern generation (BIST), CRC computation, scramblers on serial links, and cheap counters where you only need "N distinct states" rather than counting in order - an LFSR counter is smaller and faster than a binary counter because it has no carry chain.

3.5 Clock dividers: even, odd & the 50% duty problem

Read this before you write a clock divider A clock produced by fabric logic is a derived clock. It arrives skewed relative to the source, it is not driven by the clock tree, and it creates a new clock domain that every downstream path must be constrained against. In real ASIC and FPGA work you should use a PLL/MMCM for frequency synthesis, or a clock enable to run slower logic on the original clock. Divide in logic only when you genuinely need a clock output pin. Interviewers ask this question anyway - and the best answer starts by saying exactly this.

The clock enable alternative


// What you should almost always do instead: ONE clock domain, and a
// periodic enable pulse that makes downstream logic advance every Nth cycle.
module tick_gen #(
  parameter N = 3
) (
  input  wire clk,
  input  wire rst_n,
  output wire tick        // one-cycle pulse, once every N clocks
);

  localparam W = $clog2(N);
  reg [W-1:0] cnt;

  assign tick = (cnt == N-1);

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n)   cnt <= {W{1'b0}};
    else if (tick) cnt <= {W{1'b0}};
    else           cnt <= cnt + 1'b1;
  end

endmodule

// Downstream logic then reads:
//     always @(posedge clk) if (tick) ... ;
// Same clock everywhere. No new clock domain. No skew. Timing analysis
// stays trivial.

Even division is easy


// Divide by 2: a single toggle flop. Duty cycle is exactly 50% by
// construction, because the output flips once per input rising edge.
module clk_div2 (
  input  wire clk,
  input  wire rst_n,
  output reg  clk_out
);
  always @(posedge clk or negedge rst_n)
    if (!rst_n) clk_out <= 1'b0;
    else        clk_out <= ~clk_out;
endmodule


// Divide by any EVEN N: count to N/2 - 1, then toggle.
module clk_div_even #(
  parameter N = 6                 // must be even
) (
  input  wire clk,
  input  wire rst_n,
  output reg  clk_out
);
  localparam HALF = N/2;
  localparam W    = $clog2(HALF);
  reg [W-1:0] cnt;

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      cnt     <= {W{1'b0}};
      clk_out <= 1'b0;
    end else if (cnt == HALF-1) begin
      cnt     <= {W{1'b0}};
      clk_out <= ~clk_out;        // toggle every N/2 input clocks
    end else begin
      cnt     <= cnt + 1'b1;
    end
  end
endmodule

Odd division at 50% duty: the classic question

Divide by 3 and you need the output high for 1.5 input clock periods. You cannot get half a period from rising edges alone - so use the falling edge too. Build the same divide-by-3 waveform twice, once on each edge, and AND them. The negative-edge copy is offset by exactly half a period, and the AND trims exactly that half period off the high time.

Waveform showing how ANDing a posedge derived and negedge derived divide by three waveform produces an exact fifty percent duty cycle clk pos_wave posedge neg_wave negedge clk_out start-up transient 1.5 T high 1.5 T low period = 3T, duty = 50% DIVIDE BY 3 WITH EXACT 50% DUTY - clk_out = pos_wave AND neg_wave
Figure 3.5 - Each half is high for 2 of every 3 periods. Because neg_wave is shifted by half a period, the AND is high for 2T - 0.5T = 1.5T out of 3T. The first pulse is a start-up artefact; the waveform is periodic from the second cycle on.

module clk_div_odd #(
  parameter N = 3                 // must be ODD and >= 3
) (
  input  wire clk,
  input  wire rst_n,
  output wire clk_out
);

  localparam W = $clog2(N);

  reg [W-1:0] pos_cnt, neg_cnt;

  // ---- counter clocked on the RISING edge -------------------------------
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n)              pos_cnt <= {W{1'b0}};
    else if (pos_cnt == N-1) pos_cnt <= {W{1'b0}};
    else                     pos_cnt <= pos_cnt + 1'b1;
  end

  // ---- identical counter clocked on the FALLING edge --------------------
  // This is the only place in the whole course where a negedge block is
  // the right answer - it is what buys us the half-period offset.
  always @(negedge clk or negedge rst_n) begin
    if (!rst_n)              neg_cnt <= {W{1'b0}};
    else if (neg_cnt == N-1) neg_cnt <= {W{1'b0}};
    else                     neg_cnt <= neg_cnt + 1'b1;
  end

  // Each wave is high for (N-1) of N states - for N = 3 that is 2 of 3.
  wire pos_wave = (pos_cnt != N-1);
  wire neg_wave = (neg_cnt != N-1);

  // The AND trims exactly half a period off the high time:
  //   (N-1)T - 0.5T  =  N/2 * T   ->  exactly 50% duty.
  assign clk_out = pos_wave & neg_wave;

endmodule
That final AND is a combinational gate on a clock Which means it can glitch, and a glitch on a clock is a phantom edge that clocks your entire design. In an ASIC flow this AND must be a proper clock-gating cell from the standard cell library, not an ordinary AND2. Say this unprompted in an interview and you have demonstrated you have actually built one.
Requirement Correct approach
Slower logic, same domainClock enable - no new clock at all
Divide by 2, 4, 8…Toggle flop chain, or a counter bit
Divide by any even NCounter to N/2, toggle
Divide by odd N, 50% dutyDual-edge counters ANDed, as above
Non-integer ratio (e.g. ×2.5)PLL / MMCM. Logic cannot do this cleanly
A real clock output pinClock output buffer, constrained in the SDC

Volume 03 recap

Concept The one thing to remember
Master-slave DFF Two latches, opposite phase, never open together. That is the "edge".
Setup vs hold Setup is fixed by slowing the clock. Hold is not - period is absent from its equation.
Sensitivity list negedge rst_n must pair with if (!rst_n).
Reset Assert asynchronously, de-assert synchronously. One synchronizer per clock domain.
Recovery / removal Setup and hold, but for the reset pin on its release.
Modulo-N counter Wrap explicitly unless N is a power of two. State your priority order.
Gray code One bit changes per step, so a mistimed sample is only ever off by one.
LFSR 2ⁿ-1 states; all-zeros is dead with XOR, all-ones with XNOR. Galois for speed.
Clock division Prefer a clock enable. For odd N at 50%, AND a posedge and negedge waveform.