Hello! In our previous lesson, we designed a parameterized N-bit register with a load enable signal. This taught us how to store data and control when to store it, a fundamental skill in digital design.
Today, we'll build directly on that foundation to create another essential component: a synchronous up-counter. As promised, you'll see that a counter is simply a clever variation of the register we just built. Our goal is to implement a counter that is both flexible (N-bits wide, thanks to parameters) and controllable (it only counts when we tell it to, using an enable signal).
1. From Register to Counter: A Small but Powerful Change
Let's recall the register with a load enable. Its logic was: "If enabled, load new data; otherwise, hold the current data."
if (enable) q <= d;
A counter follows a very similar pattern, but with one key difference. Instead of loading external data d, it loads its own current value plus one.
if (enable) count <= count + 1;
That's it! Conceptually, a synchronous counter is just a register whose input is connected to the output of an adder that increments the register's current value. The enable signal controls whether the register loads this new, incremented value or simply holds its existing one.
The schematic below illustrates this exact structure for a 2-bit up-counter. You can see the register (RTL_REG_SYNC) holding the state, an adder (RTL_ADD) calculating the next state (count + 1), and a multiplexer (RTL_MUX) that, controlled by the enable signal, decides whether to load the new value or hold the old one.

2. Parameterizing Your Counter
Just like with our register, hard-coding a specific width (like 4-bit or 8-bit) is inflexible. We want to write one piece of code that can be used to create a counter of any size. We'll use the parameter keyword, which you're already familiar with from the last lesson.
The following resources walk through this process of creating a configurable counter. They are excellent references for seeing how parameters are used in a practical context.
Creating a Configurable Counter Circuit
This article from FPGA Coding clearly demonstrates how to transform a fixed-width counter into a configurable one.
Focus on the section titled "Now add configuration". Notice how the hard-coded [3:0] is replaced with [BITS - 1 : 0] after declaring the BITS parameter. This is the key technique for creating parameterized modules.
Verilog Parameters - Parameter Example
This example from ChipVerify shows a complete, parameterized counter module that includes an enable signal.
Read the section "Parameter Example" and examine the Verilog code for the counter. It uses a parameter N for width and an input en for enable, which is exactly what we need. Note: Both of these examples use an asynchronous reset (e.g., always @(posedge clk, posedge rst) or if (!rstn)). For consistency in our course, we will stick to the synchronous reset style (always @(posedge clk) with if (reset)) that we established in the previous lesson. The core logic for the enable and parameterization, however, is perfectly illustrated in these examples.
3. Implementing the N-bit Synchronous Counter in Verilog
Now, let's combine these concepts—the counter's incrementing logic, the enable signal, synchronous reset, and parameterization—into a single, clean Verilog module.
module sync_up_counter #(
parameter N = 8 // Default width is 8 bits
) (
// Inputs
input wire clk,
input wire reset, // Synchronous, active-high reset
input wire enable, // Count enable
// Output
output reg [N-1:0] count
);
// This block describes the sequential logic for the counter
always @(posedge clk) begin
if (reset) begin
// On reset, force the counter to a known state (zero)
count <= {N{1'b0}};
end else if (enable) begin
// If not reset and enabled, increment the count
count <= count + 1;
end
// If reset is 0 and enable is 0, 'count' implicitly holds its value.
// The synthesis tool infers a register with feedback.
end
endmodule
Let's break down this code:
module sync_up_counter #(parameter N = 8): We define our module, making its widthNconfigurable with a default of 8 bits.output reg [N-1:0] count: The counter's output is an N-bit register, defined using our parameterN.always @(posedge clk): This defines our synchronous logic block. As always for sequential logic, we use non-blocking assignments (<=).if (reset): Our first priority is the synchronous reset. Ifresetis high on a rising clock edge, the counter is forced to 0. The{N{1'b0}}syntax creates an N-bit vector of zeros.else if (enable): If we are not in a reset state, we then check theenablesignal. If it's high, we perform the core counter action.count <= count + 1;: This is the heart of the counter. The+operator instructs the synthesis tool to create an N-bit adder. The result of this addition becomes the value that thecountregister will latch on the next clock edge.- Implicit Hold: Just like in our register design, there is no final
elsestatement. Whenresetis 0 andenableis 0, no assignment is made tocount. In a clockedalwaysblock, this synthesizes to hardware that holds the register's current value—exactly the behavior we want.
4. Practice: Instantiating Your Counter
You've designed a generic N-bit counter. Let's practice using it.
Your task: Imagine you are designing a timer for a system that needs to count for 4096 cycles. Write the Verilog code to instantiate your sync_up_counter to create a counter suitable for this task.
- The instance name should be
timer_counter. - The required inputs are
sys_clk,sys_rst, andtimer_en. - The output wire should be named
timer_val.
Think about the required bit-width N. What is the minimum number of bits needed to represent the value 4095 (which corresponds to 4096 cycles, from 0 to 4095)? (Hint: , ).
Click here for the solution
To count up to 4095, you need 12 bits, since . Therefore, you must override the parameter N to be 12.
// Wires to connect to the counter instance
wire sys_clk;
wire sys_rst;
wire timer_en;
wire [11:0] timer_val; // The output wire must match the counter's width
// Instantiate a 12-bit synchronous up-counter
sync_up_counter #(
.N(12) // Override the default parameter N=8 with 12
) timer_counter (
.clk(sys_clk),
.reset(sys_rst),
.enable(timer_en),
.count(timer_val)
);
Explanation:
By using #(.N(12)), we configure this specific instance of our generic counter to be 12 bits wide, overriding the default value of 8. This is the power of parameterized design in action.
Conclusion
You have successfully taken the register concept from the last lesson and extended it to create a parameterized synchronous counter. This is a significant step, as counters are used everywhere in digital systems—for controlling sequences, generating timing intervals, and managing program flow in processors.
Key Takeaways:
- A synchronous up-counter is fundamentally a register combined with an adder.
- The core logic is
count <= count + 1, which is controlled by anenablesignal. - The
if (reset) ... else if (enable) ...structure creates a clear priority: reset is checked first, then enable. - If neither
resetnorenableis active, the counter implicitly holds its value. - Using a
parameterfor the bit-widthNmakes the design reusable and robust.
In our next lesson, we will explore another powerful variation of the register: the shift register. Instead of incrementing a value, a shift register moves data laterally, which is crucial for tasks like serial communication and data manipulation.