6.2. What are Localparams?
Localparams are similar to parameters but with one critical difference: they cannot be modified during instantiation. They're used for internal constants derived from parameters or for values that should remain fixed within the module.
Naming Convention
​
-
Use UPPERCASE for consistency with parameters
-
Prefix with module-specific identifier if needed for clarity
-
Group related localparams together
Verilog
// FSM states
localparam IDLE = 3'b000;
localparam ACTIVE = 3'b001;
localparam DONE = 3'b010;
// Derived calculations
localparam ADDR_WIDTH = $clog2(DEPTH);
localparam MAX_COUNT = (1 << COUNTER_WIDTH) - 1;
// Protocol-specific constants
localparam AXI_BURST_INCR = 2'b01;
localparam AXI_BURST_WRAP = 2'b10;
Localparam Declaration
Verilog
module fifo_controller #(
parameter DATA_WIDTH = 8,
parameter DEPTH = 16
)(
input clk,
input rst_n,
input push,
input pop,
output full,
output empty
);
// Derived localparam - cannot be overridden
localparam ADDR_WIDTH = $clog2(DEPTH);
localparam MAX_COUNT = DEPTH;
// FSM state encoding - should never be changed externally
localparam IDLE = 3'b001;
localparam WRITE = 3'b010;
localparam READ = 3'b100;
// Internal implementation
reg [ADDR_WIDTH-1:0] write_ptr;
reg [ADDR_WIDTH-1:0] read_ptr;
reg [2:0] state;
endmodule
Key Characteristics:
​
-
Cannot be overridden during instantiation
-
Perfect for derived values and internal constants
-
Protects critical design parameters from accidental modification
-
Improves code maintainability and safety
​
Use Localparams when:
​
-
Defining constants derived from parameters
-
Declaring FSM states or control values
-
Protecting internal constants from external modification
-
Improving code readability with named constants
6.2.1. Why Localparams Are Essential
6.2.1.1. Design Integrity:
Localparams protect critical internal constants from accidental modification. For example, FSM state encodings should never be overridden externally as it would break the entire state machine logic.
6.2.1.2. Derived Values:
When constants are mathematically derived from parameters, using localparam ensures consistency:
Verilog
module cache_memory #(
parameter CACHE_SIZE = 1024, // in bytes
parameter LINE_SIZE = 64 // in bytes
)(
// ports
);
localparam NUM_LINES = CACHE_SIZE / LINE_SIZE; // Always consistent
localparam INDEX_BITS = $clog2(NUM_LINES);
localparam OFFSET_BITS = $clog2(LINE_SIZE);
// If NUM_LINES was a parameter, someone could set it inconsistently
endmodule
6.2.1.3. Code Readability:
Named constants improve code clarity compared to magic numbers:
Verilog
// Poor practice
if (status == 3'b101) begin
// What does 3'b101 mean?
end
// Good practice
localparam ERROR_STATE = 3'b101;
if (status == ERROR_STATE) begin
// Clear intent
end
Example: Parameterized AXI Interface Width Converter
Verilog
module axi_width_converter #(
parameter S_DATA_WIDTH = 32, // Slave interface width
parameter M_DATA_WIDTH = 64, // Master interface width
parameter ADDR_WIDTH = 32,
parameter ID_WIDTH = 4
)(
input wire aclk,
input wire aresetn,
// Slave interface
input wire [ID_WIDTH-1:0] s_awid,
input wire [ADDR_WIDTH-1:0] s_awaddr,
input wire [7:0] s_awlen,
input wire [2:0] s_awsize,
input wire s_awvalid,
output wire s_awready,
input wire [S_DATA_WIDTH-1:0] s_wdata,
input wire [S_DATA_WIDTH/8-1:0] s_wstrb,
input wire s_wlast,
input wire s_wvalid,
output wire s_wready,
// Master interface
output wire [ID_WIDTH-1:0] m_awid,
output wire [ADDR_WIDTH-1:0] m_awaddr,
output wire [7:0] m_awlen,
output wire [2:0] m_awsize,
output wire m_awvalid,
input wire m_awready,
output wire [M_DATA_WIDTH-1:0] m_wdata,
output wire [M_DATA_WIDTH/8-1:0] m_wstrb,
output wire m_wlast,
output wire m_wvalid,
input wire m_wready
);
// Localparams for derived values and internal constants
localparam S_STRB_WIDTH = S_DATA_WIDTH / 8;
localparam M_STRB_WIDTH = M_DATA_WIDTH / 8;
localparam RATIO = M_DATA_WIDTH / S_DATA_WIDTH; // Upsizing ratio
localparam RATIO_WIDTH = $clog2(RATIO);
// Verify configuration validity at compile time
localparam CONFIG_VALID = (M_DATA_WIDTH >= S_DATA_WIDTH) &&
(M_DATA_WIDTH % S_DATA_WIDTH == 0) &&
(RATIO <= 16);
// State machine states for write data accumulation
localparam IDLE = 2'b00;
localparam ACCUMULATE = 2'b01;
localparam TRANSFER = 2'b10;
// Internal registers
reg [1:0] state;
reg [M_DATA_WIDTH-1:0] data_accumulator;
reg [M_STRB_WIDTH-1:0] strb_accumulator;
reg [RATIO_WIDTH-1:0] beat_counter;
// Implementation would handle data width conversion
endmodule
Example: Memory Controller with Timing Parameters
Verilog
// Convert timing parameters from nanoseconds to clock cycles
// These should be localparams as they're derived from parameters
localparam tRCD_CYCLES = $rtoi($ceil(tRCD * 1000 / CLOCK_PERIOD_PS));
localparam tRP_CYCLES = $rtoi($ceil(tRP * 1000 / CLOCK_PERIOD_PS));
localparam tRAS_CYCLES = $rtoi($ceil(tRAS * 1000 / CLOCK_PERIOD_PS));
localparam tRC_CYCLES = $rtoi($ceil(tRC * 1000 / CLOCK_PERIOD_PS));
localparam tRFC_CYCLES = $rtoi($ceil(tRFC * 1000 / CLOCK_PERIOD_PS));
localparam tWR_CYCLES = $rtoi($ceil(tWR * 1000 / CLOCK_PERIOD_PS));
localparam tRTP_CYCLES = $rtoi($ceil(tRTP * 1000 / CLOCK_PERIOD_PS));
// Maximum counter width needed
localparam COUNTER_WIDTH = $clog2(tRFC_CYCLES + 1);
// Command encoding
localparam CMD_NOP = 3'b111;
localparam CMD_ACTIVE = 3'b011;
localparam CMD_READ = 3'b101;
localparam CMD_WRITE = 3'b100;
localparam CMD_PRECHARGE = 3'b010;
localparam CMD_REFRESH = 3'b001;
// Controller states
localparam ST_IDLE = 4'h0;
localparam ST_ACTIVATE = 4'h1;
localparam ST_READ = 4'h2;
localparam ST_WRITE = 4'h3;
localparam ST_PRECHARGE = 4'h4;
localparam ST_REFRESH = 4'h5;
