Hello! Welcome to the next lesson in our journey into digital design with Verilog.
In our last session, we established the two golden rules for assignments in Verilog: use non-blocking (<=) for sequential logic and blocking (=) for combinational logic. This distinction is the bedrock of writing correct and predictable hardware descriptions.
Today, we'll put that knowledge into practice by designing one of the most fundamental building blocks in any digital system: a register. Our goal is to implement a flexible, reusable N-bit register that can store data, but only when we tell it to, using a load enable signal. We'll also make it parameterized, so we can easily create an 8-bit, 32-bit, or any-width register from the same piece of code.
1. What is a Register and Why Does it Need an "Enable"?
At its core, a register is simply a collection of D flip-flops that share a common clock. Each flip-flop stores one bit, so an N-bit register uses N flip-flops to store an N-bit value.
But a simple register that just loads its input on every clock cycle has a major drawback. Imagine it's part of a larger system where many components share a common data bus. If our register is always listening, it will capture whatever random data happens to be on the bus at every clock tick. We need a way to tell the register, "Load the data on the bus now," and "Ignore the bus and hold your current value at all other times."
This is the job of the load enable signal.
To understand this problem more deeply, let's watch a couple of short video clips.
Introduction to Registers | What is Shift Register? Types of Shift Registers
The first video, from the ALL ABOUT ELECTRONICS channel, does an excellent job of explaining the practical problem with a register that's always on.
Watch the segment on the limitations of a basic register. Pay close attention to the example of a fast clock and the need to hold data for a longer duration, which illustrates why we can't just load data on every cycle.
30 - Describing Registers in Verilog
This second video reinforces the same concept and sets the stage for the solution.
Just watch the introduction to the load enable signal. The speaker clearly explains the need to hold data and release the bus for other components.
2. How the Load Enable Works: The Multiplexer
So, how do we implement this "hold" functionality? The solution is elegant: we place a 2-to-1 multiplexer before the data input of each flip-flop.
- The
loadsignal acts as the multiplexer's select line. - When
loadis 1, the multiplexer selects the external data input (d). - When
loadis 0, the multiplexer selects the flip-flop's own current output (q).
This creates a feedback loop. By feeding the current output back to the input, the flip-flop simply re-loads the same value it already holds on the next clock edge.
The following video clip shows this exact circuit diagram.
Introduction to Registers | What is Shift Register? Types of Shift Registers
Let's return to the ALL ABOUT ELECTRONICS video to see the circuit-level solution.
Watch the section explaining how the data inputs are controlled. This will give you a clear mental model of the hardware we are about to describe in Verilog. Notice how the load signal effectively creates a multiplexer using AND/OR gates.
So, the logic we need to model is:
if (load == 1) q_next = d;else q_next = q_current;
3. Writing a Parameterized Register in Verilog
Now let's translate this concept into reusable Verilog code. A key goal in modern design is to write code once and use it many times. We achieve this with parameters.
Introducing Verilog parameter
A parameter is a constant within a Verilog module that can be changed (or "overridden") when you instantiate that module. This allows us to, for example, define a parameter N = 32 for data width, and then later create an 8-bit instance or a 64-bit instance from the same source code.
This reading from ChipVerify provides a concise and practical guide to using parameters.
Verilog Parameters - ChipVerify
This article will teach you how to make your designs flexible and reusable.
Start by reading "What are Verilog Parameters?" and "Module Parameters" to understand the basic syntax and purpose. Then, skip to and carefully read "Common Beginner Mistakes". This section is particularly important as it highlights pitfalls like hard-coding bit-widths, which directly relates to our goal of creating a flexible N-bit register.
The Verilog Code
Armed with the concepts of load enable logic and parameters, let's look at a clean, synthesizable implementation of our register. The code below defines a module for an N-bit register with a synchronous reset (reset) and a load enable (enable).
module Nbit_register #(
parameter N = 32 // Default width is 32 bits
) (
// Inputs
input wire clk,
input wire reset,
input wire enable,
input wire [N-1:0] d,
// Output
output reg [N-1:0] q
);
always @(posedge clk) begin
if (reset) begin
q <= {N{1'b0}}; // Reset to all zeros
end else if (enable) begin
q <= d; // Load new data from d
end
// If reset is 0 and enable is 0, q implicitly holds its value
end
endmodule
Let's break this down:
module Nbit_register #(...);: This is the modern ANSI style for declaring a module with parameters.parameter N = 32: We define our width parameterNand give it a default value of 32.[N-1:0] d, [N-1:0] q: Notice how we useNto define the size of our data input and output. This is crucial for making the module parameterizable, as pointed out in the ChipVerify article.always @(posedge clk): This is our sequential logic block. As per last lesson's rule, we will exclusively use non-blocking assignments (<=) inside.if (reset): We include a synchronous reset. It's good practice to have a reset signal to force the register into a known state at the beginning. The syntax{N{1'b0}}is a Verilog replication operator; it means "replicate the bit1'b0N times," which creates a vector of N zeros.else if (enable): This is the core of our load-enable logic. Only ifresetis low andenableis high will the register load the value fromd.- The Implicit "Hold": What happens if both
resetandenableare low? Notice there is no finalelsestatement. In a clockedalwaysblock, if aregis not assigned a value under some condition, it is synthesized to hold its previous value. This is exactly the behavior of a register, and it automatically infers theq <= qfeedback from the multiplexer model we saw earlier. This is the standard, concise way to model this behavior.
This single always block perfectly describes the N-bit register with enable and reset functionality we set out to build. A more verbose style, like the one using q_reg and q_next shown in the "Describing Registers in Verilog" video, is also valid but less common for a simple register. The single-block style is generally preferred for its clarity and conciseness.
4. Practice: Using Your New Module
You've designed a generic N-bit register. Now, let's use it.
Your task: Write the Verilog code to instantiate your Nbit_register module to create a 16-bit register.
- The instance name should be
program_counter. - Its clock input
clkshould connect to a wire namedsys_clk. - Its
resetinput should connect tosys_rst. - Its
enableinput should connect topc_write_en. - Its data input
dshould connect tonext_pc_value. - Its output
qshould connect tocurrent_pc.
Click here for the solution
// Wires that connect to our register instance
wire sys_clk;
wire sys_rst;
wire pc_write_en;
wire [15:0] next_pc_value;
wire [15:0] current_pc;
// Instantiation of the 16-bit register
Nbit_register #(
.N(16) // Overriding the default parameter N=32 with 16
) program_counter (
.clk(sys_clk),
.reset(sys_rst),
.enable(pc_write_en),
.d(next_pc_value),
.q(current_pc)
);
Explanation:
The key is the #(.N(16)) part. This syntax overrides the default parameter N = 32 that was defined inside the module, creating a specialized 16-bit version for this specific instance. This demonstrates the power of parameterized design.
Conclusion
Excellent work! You have now designed and implemented one of the most critical components in digital logic. You've combined your knowledge of sequential logic, assignment operators, and parameterization to create a robust and reusable building block.
Key Takeaways:
- A register is a group of flip-flops used to store a multi-bit value.
- A load enable signal provides crucial control, allowing the register to hold its value instead of loading new data on every clock cycle.
- This control is implemented in hardware using a multiplexer that selects between new data and the current stored value.
- In Verilog, this is modeled concisely using an
if (enable)condition within analways @(posedge clk)block. If the condition is false, the register implicitly holds its value. - The
parameterkeyword is used to create flexible and reusable modules, preventing code duplication and making designs easier to manage.
In our next lesson, we will build directly on what we've learned today. We will modify our register to create another essential component: a synchronous counter. You'll see that a counter is really just a register with some simple arithmetic logic added to its data path.