Module 5: Behavioral Verilog for Synthesis ยท 5.5 Modeling Flip-Flops and Registers

Register Patterns

Open any RTL and the same few register shapes appear over and over. All six here are the enable-and-reset structure of the last article with a different expression for the next value, and the counters and shift registers are the ones from sections 3.4 and 3.5 written down. Once you have built each, you recognise it on sight in someone else’s code.

Up counter

module up_counter #(
    parameter N = 8
) (
    input  wire         clk, rst_n, en,
    output reg  [N-1:0] count
);
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)  count <= {N{1'b0}};
        else if (en) count <= count + 1'b1;
    end
endmodule

count == {N{1'b1}} is the cycle before it wraps, which is the terminal count of section 3.5.

Up/down counter

always @(posedge clk or negedge rst_n) begin
    if (!rst_n)   count <= {N{1'b0}};
    else if (en) begin
        if (up)   count <= count + 1'b1;
        else      count <= count - 1'b1;
    end
end

A mux between plus one and minus one.

Modulo-N counter

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

With M a parameter this is a decade counter, a seconds counter, or anything that wraps somewhere other than a power of two. It is the clear-on-N-1 counter of section 3.5, and M - 1, not M, for the reason given there.

Serial in, parallel out

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) shift_reg <= {N{1'b0}};
    else        shift_reg <= {shift_reg[N-2:0], serial_in};
end

The concatenation drops the top bit, moves the rest up one, and puts the new bit at the bottom. After $N$ edges the word is in the register with every bit available. This is the receiver of every UART and SPI port.

Parallel in, serial out

always @(posedge clk) begin
    if (load)
        shift_reg <= data_in;
    else if (shift_en)
        shift_reg <= {shift_reg[N-2:0], 1'b0};
end

assign serial_out = shift_reg[N-1];

Load a word at once, then push it out a bit per edge from the top. The transmitter.

Saturating accumulator

An ordinary accumulator wraps on overflow, and in audio or fixed-point work a wrap turns a loud signal into a click. A saturating one sticks at the maximum. Compute the sum one bit wider and clamp on the carry:

wire [N:0] sum = {1'b0, acc} + {1'b0, data_in};

always @(posedge clk) begin
    if (add_en)
        acc <= sum[N] ? {N{1'b1}} : sum[N-1:0];
end

The extra bit is the carry out of the addition, as in the saturating adder of section 4.3; if it is set the true sum was too big and the register takes all ones.

A testbench for the shift register

`timescale 1ns / 1ps
`default_nettype none

module sipo_shift_reg #(
    parameter N = 8
) (
    input  wire         clk, rst_n, serial_in,
    output reg  [N-1:0] shift_reg
);
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) shift_reg <= {N{1'b0}};
        else        shift_reg <= {shift_reg[N-2:0], serial_in};
    end
endmodule

module tb_sipo_shift_reg;
    parameter N = 8;

    reg          clk = 0, rst_n, serial_in;
    wire [N-1:0] shift_reg;

    sipo_shift_reg #(.N(N)) dut (.clk(clk), .rst_n(rst_n), .serial_in(serial_in), .shift_reg(shift_reg));

    always #5 clk = ~clk;

    reg [N-1:0] test_byte = 8'b1011_0010;
    integer     i;

    initial begin
        $dumpfile("tb.vcd");
        $dumpvars(0, tb_sipo_shift_reg);

        rst_n = 0; serial_in = 0;
        @(negedge clk); rst_n = 1;

        for (i = N - 1; i >= 0; i = i - 1) begin     // most significant bit first
            serial_in = test_byte[i];
            @(negedge clk);
        end

        if (shift_reg !== test_byte)
            $display("FAIL: shift_reg=%b (expected %b)", shift_reg, test_byte);
        else
            $display("PASS: shift_reg=%b after %0d cycles", shift_reg, N);

        $finish;
    end
endmodule

Feed the byte in most-significant-bit first over eight edges, and the register should hold the byte. That is the standard check for any shift register before it goes into something bigger.

Six patterns and one idea: the enable-and-reset register, with the next value being an increment, a decrement, a wrap, a shifted-in bit or a clamped sum. They cover most of the sequential logic in most designs. The next article arranges the same flip-flops to solve a timing problem rather than a storage one: pipelining.

โ† Enables and Loads Pipeline Registers โ†’