6.1. Parameter and Localparam in Verilog: Building Flexible, Reusable Designs
When designing digital circuits in Verilog, creating reusable and configurable modules is essential for efficient development. Two key constructs that enable this flexibility are parameter and localparam. Understanding when and how to use each can significantly improve your RTL design quality.
6.1.1. What are Parameters?
Parameters in Verilog act as constants that can be customized when a module is instantiated. They allow you to create generic, scalable designs without rewriting code for different configurations.
Naming Conventions
​
-
Use UPPERCASE for parameters to distinguish them from regular signals
-
Use descriptive, self-documenting names
-
Include units in the name when relevant (e.g., TIMEOUT_CYCLES, CLOCK_FREQ_MHZ)
Verilog
// Good examples
parameter DATA_WIDTH = 32;
parameter FIFO_DEPTH = 256;
parameter TIMEOUT_CYCLES = 1000;
parameter CLOCK_FREQ_MHZ = 100;
// Poor examples
parameter w = 32; // Too cryptic
parameter data = 32; // Unclear what it represents
parameter timeout = 1000; // No units specified
Parameter Declaration Syntax
​
There are two primary ways to declare parameters in Verilog:
1. Traditional Parameter Declaration:
Verilog
module memory_controller (
input clk,
input rst_n,
// other ports
);
parameter ADDR_WIDTH = 32;
parameter DATA_WIDTH = 64;
parameter BURST_LENGTH = 8;
parameter TIMING_MODE = "FAST";
// Module implementation
endmodule
2. ANSI-Style Parameter Declaration (Recommended):
Verilog
module memory_controller #(
parameter ADDR_WIDTH = 32,
parameter DATA_WIDTH = 64,
parameter BURST_LENGTH = 8,
parameter TIMING_MODE = "FAST"
)(
input clk,
input rst_n,
input [ADDR_WIDTH-1:0] address,
input [DATA_WIDTH-1:0] write_data,
output reg [DATA_WIDTH-1:0] read_data
);
// Module implementation
endmodule
The ANSI-style declaration is preferred in modern Verilog coding because it clearly separates parameters from the port list and improves code readability.
​
Key Characteristics:
​
-
Declared at module level using the parameter keyword
-
Can be overridden during module instantiation
-
Visible to parent modules
-
Ideal for making modules configurable and reusable
​
Use Parameters when:
​
-
You want users to configure the module during instantiation
-
Designing reusable IP blocks with variable widths, depths, or timing
-
Creating parameterized designs for different applications
6.1.2. Parameter Overriding Methods
Parameters can be overridden during module instantiation using three different methods:
6.1.2.1. Method 1: Defparam Statement (Not Recommended)
Verilog
module top;
memory_controller mem_ctrl (
.clk(clk),
.rst_n(rst_n)
);
defparam mem_ctrl.ADDR_WIDTH = 24;
defparam mem_ctrl.DATA_WIDTH = 32;
endmodule
Why not recommended? The defparam statement can override parameters from anywhere in the hierarchy, making code harder to trace and maintain.
6.1.2.2. Method 2: Ordered Parameter Override
Verilog
module top;
memory_controller #(
24, // ADDR_WIDTH
32, // DATA_WIDTH
16, // BURST_LENGTH
"SLOW" // TIMING_MODE
) mem_ctrl (
.clk(clk),
.rst_n(rst_n),
.address(addr),
.write_data(wdata),
.read_data(rdata)
);
endmodule
Drawback: You must maintain the exact order of parameters and specify all parameters even if you only want to change one.
6.1.2.3. Method 3: Named Parameter Override (Recommended)
Verilog
module top;
memory_controller #(
.ADDR_WIDTH(24),
.DATA_WIDTH(32),
.TIMING_MODE("SLOW")
// BURST_LENGTH uses default value of 8
) mem_ctrl (
.clk(clk),
.rst_n(rst_n),
.address(addr),
.write_data(wdata),
.read_data(rdata)
);
endmodule
Advantages: Clear, maintainable, order-independent, and allows selective overriding.
6.1.3. Parameter Data Types
​
Parameters can hold various types of values:
​
6.1.3.1. Integer Parameters:
​
parameter DATA_WIDTH = 32;
parameter FIFO_DEPTH = 256;
parameter PIPELINE_STAGES = 4;
​
6.1.3.2. Real Parameter:
​
parameter CLOCK_PERIOD = 10.5; // For timing calculations
parameter DUTY_CYCLE = 0.5;
​
6.1.3.3. String Parameters:
​
parameter MODE = "SYNCHRONOUS";
parameter FILE_NAME = "memory_init.hex";
​
6.1.3.4. Bit Vector Parameters:
​
parameter RESET_VALUE = 8'hAA;
parameter INIT_STATE = 4'b1010;
Example: Parameterized FIFO Design
Verilog
module sync_fifo #(
parameter DATA_WIDTH = 8,
parameter DEPTH = 16,
parameter ALMOST_FULL_THRESHOLD = 14,
parameter ALMOST_EMPTY_THRESHOLD = 2
)(
input wire clk,
input wire rst_n,
input wire wr_en,
input wire rd_en,
input wire [DATA_WIDTH-1:0] wr_data,
output reg [DATA_WIDTH-1:0] rd_data,
output wire full,
output wire empty,
output wire almost_full,
output wire almost_empty
);
// Localparams for derived and internal constants
localparam ADDR_WIDTH = $clog2(DEPTH);
localparam PTR_WIDTH = ADDR_WIDTH + 1; // Extra bit for full/empty detection
// Memory array
reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];
// Pointers
reg [PTR_WIDTH-1:0] wr_ptr;
reg [PTR_WIDTH-1:0] rd_ptr;
// Count of elements
wire [PTR_WIDTH-1:0] count;
assign count = wr_ptr - rd_ptr;
// Status signals
assign full = (count == DEPTH);
assign empty = (count == 0);
assign almost_full = (count >= ALMOST_FULL_THRESHOLD);
assign almost_empty = (count <= ALMOST_EMPTY_THRESHOLD);
// Write operation
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr_ptr <= 0;
end else if (wr_en && !full) begin
mem[wr_ptr[ADDR_WIDTH-1:0]] <= wr_data;
wr_ptr <= wr_ptr + 1;
end
end
// Read operation
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rd_ptr <= 0;
rd_data <= 0;
end else if (rd_en && !empty) begin
rd_data <= mem[rd_ptr[ADDR_WIDTH-1:0]];
rd_ptr <= rd_ptr + 1;
end
end
endmodule
Instantiation Example:
Verilog
Instantiation Example:
// 32-bit wide, 64-deep FIFO
sync_fifo #(
.DATA_WIDTH(32),
.DEPTH(64),
.ALMOST_FULL_THRESHOLD(60),
.ALMOST_EMPTY_THRESHOLD(4)
) data_fifo (
.clk(clk),
.rst_n(rst_n),
.wr_en(fifo_wr),
.rd_en(fifo_rd),
.wr_data(data_in),
.rd_data(data_out),
.full(fifo_full),
.empty(fifo_empty),
.almost_full(fifo_afull),
.almost_empty(fifo_aempty)
);
Verilog
module tb_sync_fifo;
// Parameters
parameter DATA_WIDTH = 8;
parameter DEPTH = 16;
parameter ALMOST_FULL_THRESHOLD = 14;
parameter ALMOST_EMPTY_THRESHOLD = 2;
parameter CLK_PERIOD = 10;
// Signals
reg clk;
reg rst_n;
reg wr_en;
reg rd_en;
reg [DATA_WIDTH-1:0] wr_data;
wire [DATA_WIDTH-1:0] rd_data;
wire full;
wire empty;
wire almost_full;
wire almost_empty;
// Clock generation
always #(CLK_PERIOD/2) clk = ~clk;
// Instantiate DUT
sync_fifo #(
.DATA_WIDTH(DATA_WIDTH),
.DEPTH(DEPTH),
.ALMOST_FULL_THRESHOLD(ALMOST_FULL_THRESHOLD),
.ALMOST_EMPTY_THRESHOLD(ALMOST_EMPTY_THRESHOLD)
) dut (
.clk(clk),
.rst_n(rst_n),
.wr_en(wr_en),
.rd_en(rd_en),
.wr_data(wr_data),
.rd_data(rd_data),
.full(full),
.empty(empty),
.almost_full(almost_full),
.almost_empty(almost_empty)
);
// Test variables
integer i, j;
integer error_count;
integer test_count;
reg [DATA_WIDTH-1:0] expected_data;
reg [DATA_WIDTH-1:0] written_data [0:DEPTH-1];
// Main test sequence
initial begin
// Initialize
initialize();
$display("=== Synchronous FIFO Testbench ===");
$display("FIFO Depth: %0d, Data Width: %0d bits", DEPTH, DATA_WIDTH);
$display("Almost Full Threshold: %0d", ALMOST_FULL_THRESHOLD);
$display("Almost Empty Threshold: %0d", ALMOST_EMPTY_THRESHOLD);
$display("==================================");
error_count = 0;
test_count = 0;
// Test 1: Reset Test
test_reset();
// Test 2: Basic Write-Read Test
test_basic_write_read();
// Test 3: Simultaneous Write-Read Test
test_simultaneous_write_read();
// Test 4: Full FIFO Test
test_full_fifo();
// Test 5: Empty FIFO Test
test_empty_fifo();
// Test 6: Almost Full/Empty Test
test_almost_flags();
// Test 7: Overflow/Underflow Test
test_overflow_underflow();
// Test 8: Random Operations Test
test_random_operations();
// Test 9: Back-to-Back Operations
test_back_to_back();
// Summary
$display("\n=== Test Summary ===");
$display("Total Tests: %0d", test_count);
$display("Errors: %0d", error_count);
if (error_count == 0) begin
$display("STATUS: ALL TESTS PASSED!");
end else begin
$display("STATUS: %0d TESTS FAILED!", error_count);
end
#100;
$finish;
end
// Initialize signals
task initialize;
begin
clk = 0;
rst_n = 1;
wr_en = 0;
rd_en = 0;
wr_data = 0;
#100;
end
endtask
// Test 1: Reset Test
task test_reset;
begin
$display("\n--- Test 1: Reset Test ---");
test_count = test_count + 1;
// Apply reset
rst_n = 0;
#(CLK_PERIOD * 2);
// Check all outputs after reset
if (empty !== 1'b1) begin
$display("ERROR: empty should be 1 after reset, got %b", empty);
error_count = error_count + 1;
end
if (full !== 1'b0) begin
$display("ERROR: full should be 0 after reset, got %b", full);
error_count = error_count + 1;
end
if (almost_empty !== 1'b1) begin
$display("ERROR: almost_empty should be 1 after reset, got %b", almost_empty);
error_count = error_count + 1;
end
if (almost_full !== 1'b0) begin
$display("ERROR: almost_full should be 0 after reset, got %b", almost_full);
error_count = error_count + 1;
end
if (rd_data !== 0) begin
$display("ERROR: rd_data should be 0 after reset, got %h", rd_data);
error_count = error_count + 1;
end
// Release reset
rst_n = 1;
#(CLK_PERIOD);
$display("Reset Test: %s", (error_count == 0) ? "PASS" : "FAIL");
end
endtask
// Test 2: Basic Write-Read Test
task test_basic_write_read;
begin
$display("\n--- Test 2: Basic Write-Read Test ---");
test_count = test_count + 1;
// Write data
for (i = 0; i < 8; i = i + 1) begin
@(posedge clk);
wr_en = 1;
wr_data = i + 8'h10;
written_data[i] = i + 8'h10;
end
@(posedge clk);
wr_en = 0;
// Read data and verify
for (i = 0; i < 8; i = i + 1) begin
@(posedge clk);
rd_en = 1;
#1; // Wait for data to appear
if (rd_data !== written_data[i]) begin
$display("ERROR: Read data mismatch at read %0d: expected %h, got %h",
i, written_data[i], rd_data);
error_count = error_count + 1;
end
end
@(posedge clk);
rd_en = 0;
if (empty !== 1'b1) begin
$display("ERROR: FIFO should be empty after reading all data");
error_count = error_count + 1;
end
$display("Basic Write-Read Test: %s", (error_count == 0) ? "PASS" : "FAIL");
end
endtask
// Test 3: Simultaneous Write-Read Test
task test_simultaneous_write_read;
begin
$display("\n--- Test 3: Simultaneous Write-Read Test ---");
test_count = test_count + 1;
// Fill FIFO partially first
for (i = 0; i < 4; i = i + 1) begin
@(posedge clk);
wr_en = 1;
wr_data = i + 8'h20;
written_data[i] = i + 8'h20;
rd_en = 0;
end
// Simultaneous write and read
for (i = 4; i < 12; i = i + 1) begin
@(posedge clk);
wr_en = 1;
rd_en = 1;
wr_data = i + 8'h20;
written_data[i] = i + 8'h20;
#1;
// Verify read data matches previously written data
if (rd_data !== written_data[i-4]) begin
$display("ERROR: Simultaneous op mismatch at cycle %0d: expected %h, got %h",
i-4, written_data[i-4], rd_data);
error_count = error_count + 1;
end
end
@(posedge clk);
wr_en = 0;
rd_en = 0;
$display("Simultaneous Write-Read Test: %s", (error_count == 0) ? "PASS" : "FAIL");
end
endtask
// Test 4: Full FIFO Test
task test_full_fifo;
begin
$display("\n--- Test 4: Full FIFO Test ---");
test_count = test_count + 1;
// Fill FIFO to full
for (i = 0; i < DEPTH; i = i + 1) begin
@(posedge clk);
wr_en = 1;
wr_data = i + 8'h30;
written_data[i] = i + 8'h30;
rd_en = 0;
end
@(posedge clk);
wr_en = 0;
// Check full flag
if (full !== 1'b1) begin
$display("ERROR: FIFO should be full");
error_count = error_count + 1;
end
// Try to write when full (should be ignored)
@(posedge clk);
wr_en = 1;
wr_data = 8'hFF;
rd_en = 0;
#1;
if (full !== 1'b1) begin
$display("ERROR: FIFO should remain full when writing to full FIFO");
error_count = error_count + 1;
end
@(posedge clk);
wr_en = 0;
$display("Full FIFO Test: %s", (error_count == 0) ? "PASS" : "FAIL");
end
endtask
// Test 5: Empty FIFO Test
task test_empty_fifo;
begin
$display("\n--- Test 5: Empty FIFO Test ---");
test_count = test_count + 1;
// Empty the FIFO
for (i = 0; i < DEPTH; i = i + 1) begin
@(posedge clk);
rd_en = 1;
wr_en = 0;
end
@(posedge clk);
rd_en = 0;
// Check empty flag
if (empty !== 1'b1) begin
$display("ERROR: FIFO should be empty");
error_count = error_count + 1;
end
// Try to read when empty (should be ignored)
@(posedge clk);
rd_en = 1;
wr_en = 0;
#1;
if (empty !== 1'b1) begin
$display("ERROR: FIFO should remain empty when reading from empty FIFO");
error_count = error_count + 1;
end
@(posedge clk);
rd_en = 0;
$display("Empty FIFO Test: %s", (error_count == 0) ? "PASS" : "FAIL");
end
endtask
// Test 6: Almost Full/Empty Test
task test_almost_flags;
begin
$display("\n--- Test 6: Almost Full/Empty Test ---");
test_count = test_count + 1;
// Reset and check almost_empty
rst_n = 0;
@(posedge clk);
rst_n = 1;
@(posedge clk);
if (almost_empty !== 1'b1) begin
$display("ERROR: almost_empty should be 1 after reset");
error_count = error_count + 1;
end
// Write until almost_full threshold
for (i = 0; i < ALMOST_FULL_THRESHOLD; i = i + 1) begin
@(posedge clk);
wr_en = 1;
wr_data = i + 8'h40;
rd_en = 0;
end
@(posedge clk);
wr_en = 0;
if (almost_full !== 1'b1) begin
$display("ERROR: almost_full should be 1 when count >= %0d", ALMOST_FULL_THRESHOLD);
error_count = error_count + 1;
end
// Read until almost_empty threshold
for (i = 0; i < (ALMOST_FULL_THRESHOLD - ALMOST_EMPTY_THRESHOLD); i = i + 1) begin
@(posedge clk);
rd_en = 1;
wr_en = 0;
end
@(posedge clk);
rd_en = 0;
if (almost_empty !== 1'b1) begin
$display("ERROR: almost_empty should be 1 when count <= %0d", ALMOST_EMPTY_THRESHOLD);
error_count = error_count + 1;
end
$display("Almost Flags Test: %s", (error_count == 0) ? "PASS" : "FAIL");
end
endtask
// Test 7: Overflow/Underflow Test
task test_overflow_underflow;
begin
$display("\n--- Test 7: Overflow/Underflow Test ---");
test_count = test_count + 1;
// Fill to full
for (i = 0; i < DEPTH; i = i + 1) begin
@(posedge clk);
wr_en = 1;
wr_data = i + 8'h50;
written_data[i] = i + 8'h50;
rd_en = 0;
end
@(posedge clk);
wr_en = 0;
// Overflow test: write when full
@(posedge clk);
wr_en = 1;
wr_data = 8'hFF; // This should be ignored
#1;
// Verify FIFO still contains original data
rd_en = 1;
@(posedge clk);
#1;
if (rd_data !== written_data[0]) begin
$display("ERROR: Overflow corrupted FIFO data");
error_count = error_count + 1;
end
rd_en = 0;
// Empty FIFO
for (i = 0; i < DEPTH; i = i + 1) begin
@(posedge clk);
rd_en = 1;
wr_en = 0;
end
@(posedge clk);
rd_en = 0;
// Underflow test: read when empty
@(posedge clk);
rd_en = 1;
#1;
if (rd_data !== written_data[DEPTH-1]) begin
$display("ERROR: Underflow should not change rd_data");
error_count = error_count + 1;
end
$display("Overflow/Underflow Test: %s", (error_count == 0) ? "PASS" : "FAIL");
end
endtask
// Test 8: Random Operations Test
task test_random_operations;
integer rand_wr, rand_rd;
begin
$display("\n--- Test 8: Random Operations Test ---");
test_count = test_count + 1;
// Reset
rst_n = 0;
@(posedge clk);
rst_n = 1;
// Random operations for 100 cycles
for (i = 0; i < 100; i = i + 1) begin
@(posedge clk);
rand_wr = $random & 1;
rand_rd = $random & 1;
wr_en = rand_wr;
rd_en = rand_rd;
wr_data = $random;
// Check flag consistency
#1;
if (full && !empty) begin
// OK
end else if (empty && !full) begin
// OK
end else if (full && empty) begin
$display("ERROR: FIFO cannot be both full and empty");
error_count = error_count + 1;
end
if (almost_full && !full) begin
// OK
end else if (almost_full && full) begin
// OK
end
if (almost_empty && !empty) begin
// OK
end else if (almost_empty && empty) begin
// OK
end
end
@(posedge clk);
wr_en = 0;
rd_en = 0;
$display("Random Operations Test: %s", (error_count == 0) ? "PASS" : "FAIL");
end
endtask
// Test 9: Back-to-Back Operations
task test_back_to_back;
begin
$display("\n--- Test 9: Back-to-Back Operations Test ---");
test_count = test_count + 1;
// Reset
rst_n = 0;
@(posedge clk);
rst_n = 1;
// Back-to-back writes
for (i = 0; i < 8; i = i + 1) begin
@(posedge clk);
wr_en = 1;
wr_data = i + 8'h60;
written_data[i] = i + 8'h60;
end
@(posedge clk);
wr_en = 0;
// Back-to-back reads
for (i = 0; i < 8; i = i + 1) begin
@(posedge clk);
rd_en = 1;
#1;
if (rd_data !== written_data[i]) begin
$display("ERROR: Back-to-back read mismatch at %0d: expected %h, got %h",
i, written_data[i], rd_data);
error_count = error_count + 1;
end
end
@(posedge clk);
rd_en = 0;
$display("Back-to-Back Operations Test: %s", (error_count == 0) ? "PASS" : "FAIL");
end
endtask
// Monitor to track FIFO status
initial begin
$display("\nTime\tWR_EN\tRD_EN\tWR_DATA\tRD_DATA\tFULL\tEMPTY\tA_FULL\tA_EMPTY");
$display("-------------------------------------------------------------------");
forever begin
@(posedge clk);
#1;
$display("%0t\t%b\t%b\t%h\t%h\t%b\t%b\t%b\t%b",
$time, wr_en, rd_en, wr_data, rd_data, full, empty, almost_full, almost_empty);
end
end
// VCD dump for waveform viewing
initial begin
$dumpfile("sync_fifo.vcd");
$dumpvars(0, tb_sync_fifo);
end
endmodule
6.1.4. Advanced Parameter Techniques
​
6.1.4.1. Using generate block with Parameters
​
Parameters become extremely powerful when combined with generate statements for creating scalable, parameterized hardware:
Verilog
// Adder unit module (pipelined adder)
module adder_unit #(
parameter WIDTH = 8
)(
input wire clk,
input wire [WIDTH-1:0] a,
input wire [WIDTH-1:0] b,
output reg [WIDTH-1:0] sum
);
always @(posedge clk) begin
sum <= a + b;
end
endmodule
Verilog
module parallel_adder #(
parameter NUM_STAGES = 4,
parameter DATA_WIDTH = 8
)(
input wire clk,
input wire [NUM_STAGES*DATA_WIDTH-1:0] a,
input wire [NUM_STAGES*DATA_WIDTH-1:0] b,
output wire [NUM_STAGES*DATA_WIDTH-1:0] sum
);
localparam TOTAL_WIDTH = NUM_STAGES * DATA_WIDTH;
genvar i;
generate
for (i = 0; i < NUM_STAGES; i = i + 1) begin : adder_stages
wire [DATA_WIDTH-1:0] a_slice;
wire [DATA_WIDTH-1:0] b_slice;
wire [DATA_WIDTH-1:0] sum_slice;
assign a_slice = a[i*DATA_WIDTH +: DATA_WIDTH];
assign b_slice = b[i*DATA_WIDTH +: DATA_WIDTH];
assign sum[i*DATA_WIDTH +: DATA_WIDTH] = sum_slice;
adder_unit #(
.WIDTH(DATA_WIDTH)
) adder_inst (
.clk(clk),
.a(a_slice),
.b(b_slice),
.sum(sum_slice)
);
end
endgenerate
endmodule
Verilog
​​
module tb_parallel_adder;
// Parameters
parameter NUM_STAGES = 4;
parameter DATA_WIDTH = 8;
parameter CLK_PERIOD = 10;
// Signals
reg clk;
reg [NUM_STAGES*DATA_WIDTH-1:0] a;
reg [NUM_STAGES*DATA_WIDTH-1:0] b;
wire [NUM_STAGES*DATA_WIDTH-1:0] sum;
// Clock generation
always #(CLK_PERIOD/2) clk = ~clk;
// Instantiate DUT
parallel_adder #(
.NUM_STAGES(NUM_STAGES),
.DATA_WIDTH(DATA_WIDTH)
) dut (
.clk(clk),
.a(a),
.b(b),
.sum(sum)
);
// Test cases
initial begin
// Initialize
clk = 0;
a = 0;
b = 0;
// Wait for reset
#100;
$display("=== Parallel Adder Testbench ===");
$display("Time\t Test Case\t Expected Sum\t Actual Sum\t Status");
$display("----------------------------------------------------------------");
// Test Case 1: Simple addition
a = {8'd10, 8'd20, 8'd30, 8'd40}; // [40, 30, 20, 10]
b = {8'd5, 8'd15, 8'd25, 8'd35}; // [35, 25, 15, 5]
#CLK_PERIOD;
check_result("Test 1", {8'd75, 8'd55, 8'd35, 8'd15});
// Test Case 2: Maximum values
a = {8'd255, 8'd255, 8'd255, 8'd255};
b = {8'd1, 8'd1, 8'd1, 8'd1};
#CLK_PERIOD;
check_result("Test 2", {8'd0, 8'd0, 8'd0, 8'd0}); // Overflow expected
// Test Case 3: Mixed values
a = {8'd100, 8'd50, 8'd200, 8'd25};
b = {8'd50, 8'd100, 8'd55, 8'd75};
#CLK_PERIOD;
check_result("Test 3", {8'd150, 8'd150, 8'd255, 8'd100});
// Test Case 4: Zero values
a = {8'd0, 8'd0, 8'd0, 8'd0};
b = {8'd0, 8'd0, 8'd0, 8'd0};
#CLK_PERIOD;
check_result("Test 4", {8'd0, 8'd0, 8'd0, 8'd0});
// Test Case 5: Random values
a = {8'd123, 8'd45, 8'd67, 8'd89};
b = {8'd77, 8'd155, 8'd33, 8'd166};
#CLK_PERIOD;
check_result("Test 5", {8'd200, 8'd200, 8'd100, 8'd255});
// Finish simulation
#100;
$display("=== Simulation Complete ===");
$finish;
end
// Task to check results
task check_result;
input string test_name;
input [NUM_STAGES*DATA_WIDTH-1:0] expected;
reg [DATA_WIDTH-1:0] exp_slice, act_slice;
integer i;
reg all_pass;
begin
all_pass = 1;
$write("%0t\t %s", $time, test_name);
// Check each stage
for (i = 0; i < NUM_STAGES; i = i + 1) begin
exp_slice = expected[i*DATA_WIDTH +: DATA_WIDTH];
act_slice = sum[i*DATA_WIDTH +: DATA_WIDTH];
if (exp_slice !== act_slice) begin
all_pass = 0;
end
end
// Display results
$write("\t ");
for (i = NUM_STAGES-1; i >= 0; i = i - 1) begin
$write("%0d ", expected[i*DATA_WIDTH +: DATA_WIDTH]);
end
$write("\t ");
for (i = NUM_STAGES-1; i >= 0; i = i - 1) begin
$write("%0d ", sum[i*DATA_WIDTH +: DATA_WIDTH]);
end
if (all_pass) begin
$display("\t PASS");
end else begin
$display("\t FAIL");
$display("ERROR: Mismatch detected!");
for (i = 0; i < NUM_STAGES; i = i + 1) begin
exp_slice = expected[i*DATA_WIDTH +: DATA_WIDTH];
act_slice = sum[i*DATA_WIDTH +: DATA_WIDTH];
if (exp_slice !== act_slice) begin
$display(" Stage %0d: Expected %0d, Got %0d",
i, exp_slice, act_slice);
end
end
end
end
endtask
// Monitor to track signals
initial begin
$monitor("Time=%0t: a=[%0d %0d %0d %0d] b=[%0d %0d %0d %0d] sum=[%0d %0d %0d %0d]",
$time,
a[31:24], a[23:16], a[15:8], a[7:0],
b[31:24], b[23:16], b[15:8], b[7:0],
sum[31:24], sum[23:16], sum[15:8], sum[7:0]);
end
// VCD dump for waveform viewing
initial begin
$dumpfile("parallel_adder.vcd");
$dumpvars(0, tb_parallel_adder);
end
endmodule
6.1.4.2. Conditional compilation based on Parameters
Verilog
module configurable_processor #(
parameter ENABLE_MULTIPLIER = 1,
parameter ENABLE_DIVIDER = 0,
parameter ENABLE_CACHE = 1,
parameter CACHE_SIZE = 1024
)(
input wire clk,
input wire rst_n,
// processor interface
);
// Conditional hardware instantiation
generate
if (ENABLE_MULTIPLIER) begin : gen_multiplier
localparam MUL_LATENCY = 3;
// Multiplier logic
reg [31:0] mul_result;
// ...
end
if (ENABLE_DIVIDER) begin : gen_divider
localparam DIV_LATENCY = 32;
// Divider logic
reg [31:0] div_result;
// ...
end
if (ENABLE_CACHE) begin : gen_cache
localparam CACHE_LINE_SIZE = 64;
localparam NUM_CACHE_LINES = CACHE_SIZE / CACHE_LINE_SIZE;
localparam INDEX_BITS = $clog2(NUM_CACHE_LINES);
// Cache implementation
reg [CACHE_LINE_SIZE*8-1:0] cache_memory [0:NUM_CACHE_LINES-1];
// ...
end else begin : gen_no_cache
// Direct memory access without cache
end
endgenerate
endmodule
6.1.4.3. Parameter Validation and Error Checking
Verilog
module validated_fifo #(
parameter DATA_WIDTH = 8,
parameter DEPTH = 16,
parameter ALMOST_FULL = 14
)(
// ports
);
// Compile-time parameter validation
localparam PARAM_CHECK_1 = (DATA_WIDTH > 0) && (DATA_WIDTH <= 128);
localparam PARAM_CHECK_2 = (DEPTH >= 2) && (DEPTH <= 4096);
localparam PARAM_CHECK_3 = (ALMOST_FULL < DEPTH);
localparam PARAM_CHECK_4 = (DEPTH & (DEPTH - 1)) == 0; // Power of 2
// Generate compile error if parameters are invalid
initial begin
if (!PARAM_CHECK_1)
$error("DATA_WIDTH must be between 1 and 128");
if (!PARAM_CHECK_2)
$error("DEPTH must be between 2 and 4096");
if (!PARAM_CHECK_3)
$error("ALMOST_FULL must be less than DEPTH");
if (!PARAM_CHECK_4)
$error("DEPTH must be a power of 2");
end
localparam ADDR_WIDTH = $clog2(DEPTH);
// FIFO implementation
endmodule
6.1.4.4. Common Use Cases and Design Patterns
​
Pattern 1: Protocol-Agnostic Bus Interface
Verilog
module bus_interface #(
parameter ADDR_WIDTH = 32,
parameter DATA_WIDTH = 32,
parameter PROTOCOL = "AXI" // "AXI", "AHB", "APB"
)(
input wire clk,
input wire rst_n,
// Generic interface ports
);
localparam IS_AXI = (PROTOCOL == "AXI");
localparam IS_AHB = (PROTOCOL == "AHB");
localparam IS_APB = (PROTOCOL == "APB");
generate
if (IS_AXI) begin : axi_protocol
// AXI-specific implementation
localparam ID_WIDTH = 4;
localparam BURST_WIDTH = 2;
// ...
end else if (IS_AHB) begin : ahb_protocol
// AHB-specific implementation
localparam TRANS_WIDTH = 2;
localparam BURST_WIDTH = 3;
// ...
end else if (IS_APB) begin : apb_protocol
// APB-specific implementation
// ...
end
endgenerate
endmodule
Pattern 2: Configurable Pipeline Stages
Verilog
module pipeline_multiplier #(
parameter DATA_WIDTH = 32,
parameter PIPELINE_STAGES = 3 // Can be 0 (combinational) to 4
)(
input wire clk,
input wire rst_n,
input wire [DATA_WIDTH-1:0] a,
input wire [DATA_WIDTH-1:0] b,
input wire valid_in,
output wire [2*DATA_WIDTH-1:0] product,
output wire valid_out
);
localparam PRODUCT_WIDTH = 2 * DATA_WIDTH;
localparam HAS_PIPELINE = (PIPELINE_STAGES > 0);
// Partial product calculation
wire [PRODUCT_WIDTH-1:0] partial_product;
assign partial_product = a * b;
generate
if (PIPELINE_STAGES == 0) begin : no_pipeline
// Purely combinational
assign product = partial_product;
assign valid_out = valid_in;
end
else if (PIPELINE_STAGES == 1) begin : single_stage
reg [PRODUCT_WIDTH-1:0] product_reg;
reg valid_reg;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
product_reg <= 0;
valid_reg <= 0;
end else begin
product_reg <= partial_product;
valid_reg <= valid_in;
end
end
assign product = product_reg;
assign valid_out = valid_reg;
end
else begin : multi_stage
// Multi-stage pipeline with intermediate registers
reg [PRODUCT_WIDTH-1:0] pipe_data [0:PIPELINE_STAGES-1];
reg [PIPELINE_STAGES-1:0] pipe_valid;
integer i;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
for (i = 0; i < PIPELINE_STAGES; i = i + 1) begin
pipe_data[i] <= 0;
end
pipe_valid <= 0;
end else begin
// First stage
pipe_data[0] <= partial_product;
pipe_valid[0] <= valid_in;
// Subsequent stages
for (i = 1; i < PIPELINE_STAGES; i = i + 1) begin
pipe_data[i] <= pipe_data[i-1];
pipe_valid[i] <= pipe_valid[i-1];
end
end
end
assign product = pipe_data[PIPELINE_STAGES-1];
assign valid_out = pipe_valid[PIPELINE_STAGES-1];
end
endgenerate
endmodule
