Why Master Sequential Logic? Sequential logic--circuits with memory and state--is the backbone of every computing system on the planet. From the billions of flip-flops in Apple's M3 processor to the state machines controlling Tesla's autopilot systems, sequential logic is what transforms static gates into intelligent machines. Engineers at Intel design CPU pipelines with hundreds of sequential stages; at Samsung, they optimize memory controller state machines for nanosecond-level timing. Understanding how to implement sequential logic in Verilog isn't just a classroom exercise--it's the foundation for designing CPUs, memory systems, communication interfaces, and every digital system that needs to remember information or sequence operations over time.

18 min read | Beginner to Intermediate

What You'll Learn

  • How to implement sequential logic circuits like flip-flops, counters, and shift registers using always blocks
  • The critical difference between combinational and sequential always blocks and when to use each
  • How to properly handle clock signals, reset signals, and asynchronous vs. synchronous reset strategies
  • How to write synthesizable sequential logic that correctly maps to hardware flip-flops and meets timing requirements

A previous article showed different examples of using an always block to implement combinational logic. An always block is also mainly used to implement sequential logic which has memory elements like flip flops that can hold values.

Real-World Application

Intel's CPU Pipeline Registers: In Intel's latest processors (Raptor Lake, Meteor Lake), the instruction execution pipeline has 15-20 stages, and between every stage are banks of flip-flops (pipeline registers) that hold intermediate results. Each clock cycle, data flows from one stage to the next through these sequential elements. These pipeline registers are implemented using the exact sequential logic patterns you'll learn here--always blocks sensitive to clock edges, synchronous resets, and carefully controlled data flow. A single Core i9 processor contains over 1 billion flip-flops, all working in concert at 5+ GHz. Understanding sequential logic is understanding how modern CPUs work at their most fundamental level.

Sequential vs. Combinational Logic

Key Differences:

  • Combinational Logic: Output depends only on current inputs (no memory). Examples: adders, multiplexers, decoders. Implemented with always @(*) or assign.
  • Sequential Logic: Output depends on current inputs AND past history (has memory). Examples: flip-flops, counters, shift registers, state machines. Implemented with always @(posedge clk).

Synthesis Insight: Sequential logic always blocks synthesize to flip-flops and registers, while combinational always blocks synthesize to gates (AND, OR, NOT, MUX). When you write always @(posedge clk), you're telling the synthesis tool "create flip-flops here." This is why understanding the difference is critical--it directly affects your hardware implementation and timing behavior.

JK Flip Flop

A JK flip flop is one of the many types of flops used to store values and has two data inputs j and k along with one for reset rstn and another for clock clk. The truth table for a JK flop is shown below and is typically implemented using NAND gates.

rstnjkqComments
0000When reset is asserted, output is always zero
100Hold valueWhen both j and k are 0, output remains the same as before
1010When j=0, k=1 (Reset): output is cleared to 0
1101When j=1, k=0 (Set): output is set to 1
111Toggle valueWhen j=1, k=1: output toggles the current value

The behavioral Verilog code for a JK flip-flop can be written as shown below:


// JK Flip-Flop with asynchronous active-low reset
module jk_ff (
 	input j, 			// Input J: when high with K low, sets Q to 1
 	input k,  			// Input K: when high with J low, resets Q to 0
 	input rstn, 		// Active-low async reset: immediately clears Q
 	input clk,  		// Clock: triggers state changes on rising edge
 	output reg q  		// Output Q: stored state value
);

 	// Sequential logic: sensitive to clock edge and reset edge
 	always @ (posedge clk or negedge rstn) begin
 		if (!rstn) begin
 			// Asynchronous reset: Q becomes 0 immediately when rstn goes low
 			// This happens independent of clock edge
 			q <= 0;
 		end else begin
			// JK flip-flop logic (evaluated only on rising clock edge):
			// j=0,k=0: hold current value (q unchanged)
			// j=0,k=1: reset Q to 0
			// j=1,k=0: set Q to 1
			// j=1,k=1: toggle Q
			// Boolean expression: Q_next = (J & ~Q) | (~K & Q)
			q <= (j & ~q) | (~k & q);
 		end
 	end
endmodule

Critical Sequential Logic Concepts:

  • Sensitivity List: @(posedge clk or negedge rstn) means "trigger this block on rising clock edge OR falling reset edge"
  • Asynchronous Reset: Reset takes effect immediately, not waiting for clock edge (important for initialization)
  • Non-Blocking Assignment: <= is mandatory for sequential logic to avoid race conditions
  • Output Type: output reg q is required because q is assigned inside a procedural block

Common Error:

Never use blocking assignments (=) in sequential always blocks! Using q = (j & ~q) | (~k & q); instead of q <= creates race conditions where the order of execution within the same clock cycle becomes unpredictable. This works in simulation by chance but fails in real hardware timing analysis. Always use non-blocking (<=) for sequential logic to ensure proper flip-flop inference and avoid timing violations.

Testbench

First declare all variables used in the testbench and start a clock using a simple always block that can be driven to the design. Then instantiate the design and connect its ports with corresponding testbench variables. Note that q is of type wire because it is connected to an output of the design which will be actively driving it. All other inputs to the design are of type reg so that they can be driven within a procedural block such as initial.

The stimulus first initializes all inputs to the design to zero and then de-asserts reset after some time. A for loop is used to drive different values to j and k which are driven at random times. Once the loop is done, wait for some more time and finish the simulation.


module tb;
	// Declare testbench variables
	reg j, k, rstn, clk;
	wire q; 				// Output from DUT (must be wire)
	integer i;
	reg [2:0] dly;

	// Generate clock: 50% duty cycle, 20ns period (50 MHz)
	always #10 clk = ~clk;

	// Instantiate the JK flip-flop design under test
	jk_ff u0 (
		.j(j),
		.k(k),
		.clk(clk),
		.rstn(rstn),
		.q(q)
	);

	// Stimulus generation
	initial begin
		// Initialize all inputs to zero at time 0
		{j, k, rstn, clk} <= 0;

		// Release reset after 10ns to start operation
		#10 rstn <= 1;

		// Apply random stimulus: 10 iterations
		for (i = 0; i < 10; i = i+1) begin
			dly = $random; // Random delay (0-7 ns)
			#(dly) j <= $random; // Random J input
			#(dly) k <= $random; // Random K input
		end

		// Let simulation run a bit longer, then finish
		#20 $finish;
	end

	// Monitor output changes
	initial begin
		$monitor("Time=%0t | rstn=%b j=%b k=%b q=%b",
		$time, rstn, j, k, q);
	end

	// Waveform dumping for analysis
	initial begin
		$dumpfile("jk_ff.vcd");
		$dumpvars(0, tb);
	end
endmodule

Note from the simulation wave that at the posedge of clock, output q changes value based on the state of inputs j and k as given in the truth table.

Modulo-10 Counter

Modulus(MOD) counters simply count up to a certain number before rolling back to zero. A MOD-N counter will count from 0 to N-1 and then roll back to zero and start counting again. Such counters typically require ceil(log2N) flops to hold the count value. Shown below is the Verilog code for a MOD-10 counter that keeps counting up at every clock clk as long as reset rstn is deasserted.

Verilog parameters can be used to make a MOD-N counter which is more scalable.


// Modulo-10 Counter: Counts from 0 to 9, then rolls over
module mod10_counter (
	input clk, // Clock input
	input rstn, // Synchronous active-low reset
	output reg [3:0] out // 4-bit output (0-9, needs 4 bits)
);

	// Sequential logic: triggered on rising clock edge only
	always @ (posedge clk) begin
		if (!rstn) begin
			// Synchronous reset: counter resets to 0 on next clock edge
			// (Unlike asynchronous reset, this waits for clock edge)
			out <= 0;
		end else begin
			// Counter logic
			if (out == 9) // Check if reached maximum count
				out <= 0; // Roll over to 0
			else
				out <= out + 1; // Increment counter
		end
	end
endmodule

Synchronous vs. Asynchronous Reset:

  • Synchronous Reset: Sensitivity list is @(posedge clk) only. Reset takes effect on next clock edge. Easier to meet timing, better for most designs.
  • Asynchronous Reset: Sensitivity list is @(posedge clk or negedge rstn). Reset takes effect immediately. Used when instant reset is critical (e.g., safety systems).

Real-World Application

AMD Zen CPU Instruction Retirement Counter: AMD's Zen processor architecture uses performance counters--essentially sophisticated modulo counters--to track instruction execution statistics. These counters track retired instructions, cache misses, branch mispredictions, and hundreds of other performance metrics. The counters can be 48-bits wide, counting into the trillions before wrapping. Hardware profilers and performance analysis tools (AMD µProf, Intel VTune) read these counters to optimize code. The same modulo counter pattern you're learning here is scaled up to create these essential performance monitoring units in every modern CPU.

Testbench

The testbench first declares some variables that can be assigned some values and driven to the design inputs. The counter module is then instantiated and connected with the testbench signals which are later driven with some values in the stimulus. Since the counter also requires a clock, the testbench clock is modeled with an always block. The stimulus simply sets default values at time 0ns, then deasserts reset after 10ns and the design is allowed to run for some time.


module tb;
	reg clk, rstn;
	wire [3:0] out; // Counter output (wire, not reg)

	// Instantiate the modulo-10 counter
	mod10_counter u0 (
		.clk(clk),
		.rstn(rstn),
		.out(out)
	);

	// Generate clock: 20ns period
	always #10 clk = ~clk;

	// Stimulus
	initial begin
		// Initialize signals
		{clk, rstn} <= 0;

		// Release reset after 10ns
		#10 rstn <= 1;

		// Let counter run for multiple cycles to observe rollover
		#450 $finish;
	end

	// Display counter value at each change
	initial begin
		$monitor("Time=%0t | rstn=%b out=%0d", $time, rstn, out);
	end

	// Waveform dump
	initial begin
		$dumpfile("mod10_counter.vcd");
		$dumpvars(0, tb);
	end
endmodule

See that the counter module counts from zero to 9, rolls over to zero and starts counting again.

4-bit Left Shift Register

Shown below is a 4-bit left shift register that accepts an input d into LSB and all other bits will be shifted left by 1. For example, if d equals zero and the initial value of the register is 0011, it will become 0110 at the next edge of the clock clk.


// 4-bit Left Shift Register
module lshift_4b_reg (
 input d, // Serial data input (enters at LSB)
 input clk, // Clock signal
 input rstn, // Synchronous active-low reset
 output reg [3:0] out // 4-bit parallel output
);

 // Sequential logic: shift operation on clock edge
 always @ (posedge clk) begin
 if (!rstn) begin
 // Reset: clear all bits
 out <= 0;
 end else begin
 // Left shift operation:
 // - out[3] gets out[2] (MSB shifts out, lost)
 // - out[2] gets out[1]
 // - out[1] gets out[0]
 // - out[0] gets d (new bit enters at LSB)
 // Concatenation syntax: {out[2:0], d}
 out <= {out[2:0], d};
 end
 end
endmodule

Shift Register Applications:

  • Serial-to-Parallel Conversion: UART, SPI, I2C interfaces use shift registers to receive serial data
  • Delay Lines: FIR filters use shift registers to create tapped delay lines for signal processing
  • Pattern Detection: Communication systems shift in bits to detect sync patterns (e.g., Ethernet preamble)
  • CRC Calculation: Linear Feedback Shift Registers (LFSRs) implement CRC error detection

FPGA Optimization: On Xilinx and Intel FPGAs, shift registers are recognized by the synthesizer and mapped to specialized SRL (Shift Register LUT) primitives instead of discrete flip-flops. A 32-bit shift register might use 32 flip-flops normally, but with SRL inference, it uses just 1-2 LUTs--a 90%+ resource savings! Write your shift registers in the standard pattern shown here, and modern synthesis tools will automatically optimize them.

Testbench

The testbench follows a similar template like the one shown before where some variables are declared, design module is instantiated and connected with the testbench signals. Then a clock is started and the stimulus is driven to the design using an initial block. In this testbench example, different values of d has to be exercised and hence a for loop is used to iterate 20 times and apply random values to the design.


module tb;
 reg clk, rstn, d;
 wire [3:0] out;
 integer i;

 // Instantiate the 4-bit left shift register
 lshift_4b_reg u0 (
 .d(d),
 .clk(clk),
 .rstn(rstn),
 .out(out)
 );

 // Generate clock
 always #10 clk = ~clk;

 // Stimulus
 initial begin
 // Initialize all signals
 {clk, rstn, d} <= 0;

 // Release reset
 #10 rstn <= 1;

 // Shift in 20 random bits, synchronized to clock edge
 for (i = 0; i < 20; i=i+1) begin
 @(posedge clk) d <= $random; // Apply new bit at clock edge
 end

 // Wait a bit and finish
 #10 $finish;
 end

 // Monitor shift register value
 initial begin
 $monitor("Time=%0t | d=%b out=%b", $time, d, out);
 end

 // Waveform dump
 initial begin
 $dumpfile("lshift_4b.vcd");
 $dumpvars(0, tb);
 end
endmodule

Note that each bit is shifted to the left by 1 and the new value of d is applied to LSB.

Common Beginner Mistakes

Mistake #1: Using blocking assignments (=) in sequential logic

Why it's problematic: Many beginners write sequential logic using blocking assignments:


always @(posedge clk) begin 
	q = d; 
end

This creates race conditions in simulation and synthesis problems. Blocking assignments execute sequentially within the same time step, which can cause the wrong evaluation order when multiple always blocks interact. For example, if one block updates q and another reads q in the same clock cycle, the result is unpredictable. Worse, the synthesized flip-flops no longer match the simulation when other blocks read the same variable, which causes simulation/synthesis mismatches.

Best Practice: Always use non-blocking assignments (<=) for sequential logic:


always @(posedge clk) begin 
	q <= d; 
end

Non-blocking assignments schedule updates to happen after all right-hand sides are evaluated, ensuring all flip-flops update "simultaneously" like real hardware. This is the industry-standard rule: use <= for sequential (clocked) logic, use = only for combinational logic in always @(*) blocks. Follow this rule religiously and you'll avoid entire categories of subtle timing bugs.

Mistake #2: Incomplete sensitivity lists for sequential logic

Why it's problematic: Students sometimes write always @(posedge clk) for logic that should have asynchronous reset, or forget to include reset in the sensitivity list:

 
always @(posedge clk) 
	if (!rstn) q <= 0; 
	else q <= d;

This creates a synchronous reset instead of the intended asynchronous reset--the reset won't take effect until the next clock edge, which can be problematic during power-up or critical fault conditions. The simulation might work, but the synthesized hardware behaves differently than expected.

Best Practice: Match your sensitivity list to your intent. For asynchronous reset:

 
always @(posedge clk or negedge rstn)

For synchronous reset:


always @(posedge clk)

In general, prefer synchronous reset for most designs--it's easier to meet timing constraints and results in more predictable behavior. Use asynchronous reset only when you specifically need instant reset response, such as in safety-critical systems or when initializing state before the first clock edge.

Mistake #3: Mixing combinational and sequential logic in one always block

Why it's problematic: Beginners sometimes write code like:


always @(posedge clk or a or b) begin 
	q <= a + b; 
end

This tries to combine clock-triggered sequential logic (posedge clk) with combinational logic (sensitive to a and b). The result is confusing behavior: the block triggers both on clock edges AND when a or b change, creating neither pure combinational nor pure sequential logic. Synthesis tools will reject this or infer unexpected hardware (latches, multiple clock domains). The intent is unclear--do you want a registered sum or an unregistered sum?

Best Practice: Separate combinational and sequential logic into different always blocks. For combinational:


always @(*) 
	sum = a + b;

For sequential:


always @(posedge clk) 
	q <= sum;

This clearly shows data flow: combinational logic computes values, sequential logic registers them on clock edges. Modern synthesis tools can optimize this to the same hardware as a single block, but the code is far more readable and maintainable. This separation is critical for large designs and matches industry coding standards (IEEE RTL coding guidelines).

Mistake #4: Incorrect counter rollover conditions

Why it's problematic: Students often write counters with off-by-one errors in the rollover condition for a modulo-10 counter:


if (out == 10) 
	out <= 0;

This actually creates a modulo-11 counter! It counts 0,1,2,3,4,5,6,7,8,9,10, then rolls to 0--that's 11 states, not 10. The counter briefly reaches the value 10 for one clock cycle before resetting, which can cause glitches in downstream logic that expects only values 0-9. This bug is subtle because it "mostly works" but occasionally produces invalid values.

Best Practice: Use if (out == N-1) for a modulo-N counter, not if (out == N). For modulo-10:


if (out == 9) 
	out <= 0; 
else 
	out <= out + 1;

This ensures the counter goes through exactly N states (0 to N-1). Alternatively, use a parameterized design:


parameter N = 10;

// other code

if (out == N-1) 
	out <= 0; 
else 
	out <= out + 1;

Draw a state diagram to verify: for MOD-10, states should be {0,1,2,3,4,5,6,7,8,9}, never reaching 10.

By mastering sequential logic with always blocks, you now understand the fundamental building blocks of every digital system. The flip-flops, counters, and shift registers you've learned are not academic exercises--they're the exact patterns used in CPU cores, memory controllers, and communication interfaces in every modern chip. From Apple's M-series to NVIDIA's GPUs to Intel's processors, billions of flip-flops implement sequential logic using these exact principles.

Summary

In this tutorial, you learned:

  • How sequential logic differs from combinational logic--sequential circuits have memory (flip-flops) and state, triggered by clock edges, while combinational circuits compute outputs instantly from inputs
  • How to implement flip-flops, counters, and shift registers using always @(posedge clk) blocks with proper non-blocking assignments (<=)
  • The critical difference between synchronous reset (in sensitivity list as @(posedge clk) only) and asynchronous reset (@(posedge clk or negedge rstn)) and when to use each
  • Industry best practices: non-blocking assignments for sequential logic, separate always blocks for combinational vs. sequential, correct counter rollover conditions
  • Common mistakes to avoid: blocking assignments in sequential logic, incomplete sensitivity lists, mixing logic types, off-by-one counter errors
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5