Chapter 03: Sequential Logic & Flip-Flops

Adding MEMORY to your hardware - the game changer

The Problem with Combinational Logic

Everything we've built so far has been memoryless. Change input, output changes. But how do you build a counter? A state machine? A CPU? You need MEMORY.

Sequential Logic = Combinational Logic + Memory. Output depends on current inputs AND previous state.

The D Flip-Flop: Your First Memory Element

A D flip-flop captures the value on its D input when the clock ticks, and holds it until the next tick.

// Simple D flip-flop
// Captures input on rising clock edge

module dff (
    input  clk,      // Clock signal
    input  d,        // Data input
    output reg q   // Data output (registered)
);

    // On rising edge of clock, capture D into Q
    always @(posedge clk) begin
        q <= d;
    end

endmodule
Key Concepts:
always @(posedge clk) = "Do this on rising clock edge"
<= = Non-blocking assignment (for sequential logic)
reg type = Can hold a value between clock edges

Adding Reset: The Standard Pattern

Every real flip-flop needs a reset to put it in a known state at power-up:

// D flip-flop with synchronous reset

module dff_reset (
    input  clk,
    input  rst,      // Active-high reset
    input  d,
    output reg q
);

    always @(posedge clk) begin
        if (rst)
            q <= 1'b0;      // Reset to 0
        else
            q <= d;        // Normal operation
    end

endmodule

Building a Counter: Sequential Logic in Action

Let's build an 8-bit counter that increments every clock cycle:

// 8-bit counter with reset
// Counts from 0 to 255, then wraps to 0

module counter_8bit (
    input  clk,
    input  rst,
    output reg [7:0] count
);

    always @(posedge clk) begin
        if (rst)
            count <= 8'd0;
        else
            count <= count + 1;  // Increment
    end

endmodule
What's happening: On each clock edge, the counter reads its current value, adds 1, and stores the result. The + operator synthesizes to an 8-bit adder with feedback!

Shift Register: Serial to Parallel Conversion

// 8-bit shift register
// Shifts data in from 'serial_in', shifts out to 'serial_out'

module shift_reg_8bit (
    input  clk,
    input  rst,
    input  serial_in,
    output serial_out,
    output [7:0] parallel_out
);

    reg [7:0] shift_reg;

    always @(posedge clk) begin
        if (rst)
            shift_reg <= 8'd0;
        else
            shift_reg <= {shift_reg[6:0], serial_in};  // Shift left
    end

    assign serial_out = shift_reg[7];      // MSB out
    assign parallel_out = shift_reg;    // All bits out

endmodule
Concatenation: {shift_reg[6:0], serial_in} takes bits 6-0 and appends serial_in as bit 0. This creates the shift operation!

State Machine: The Power Pattern

State machines are the backbone of control logic. Here's a traffic light controller:

// Traffic light state machine
// RED → GREEN → YELLOW → RED

module traffic_light (
    input  clk,
    input  rst,
    output reg red,
    output reg yellow,
    output reg green
);

    // State encoding
    localparam RED    = 2'b00;
    localparam GREEN  = 2'b01;
    localparam YELLOW = 2'b10;

    reg [1:0] state, next_state;
    reg [25:0] timer;

    // State register
    always @(posedge clk) begin
        if (rst)
            state <= RED;
        else if (timer == 0)
            state <= next_state;
    end

    // Next state logic
    always @(*) begin
        case(state)
            RED:    next_state = GREEN;
            GREEN:  next_state = YELLOW;
            YELLOW: next_state = RED;
            default: next_state = RED;
        endcase
    end

    // Output logic
    always @(*) begin
        {red, yellow, green} = 3'b000;
        case(state)
            RED:    red    = 1'b1;
            YELLOW: yellow = 1'b1;
            GREEN:  green  = 1'b1;
        endcase
    end

    // Timer (counts clock cycles)
    always @(posedge clk) begin
        if (rst || timer == 0)
            timer <= 27_000_000;  // 1 second @ 27MHz
        else
            timer <= timer - 1;
    end

endmodule

Critical Concepts

CRITICAL: In always @(posedge clk) blocks, ALWAYS use non-blocking assignments (<=). Mixing = and <= causes race conditions and simulation/synthesis mismatches!