Can multidimensional arrays with different dimensions be used?
Short answer, NO.
Verilog does not support unique sized multidimensional arrays. SystemVerilog does support dynamic arrays however these cannot be connected to module ports and cannot be synthesized.
Embedded code (such as Perl's EP3, Ruby's eRuby/ruby_it, Python's prepro, etc.) can generate custom denominational arrays and code iterations, but the parameters must be hard coded before compile. The final value of any parameter of a given instance is discoverer during compile time, well after the embedded script is ran. The parameter must be treated as a global constant, therefore Multiplier#(4,4)
and Multiplier#(8,8)
cannot exist in the same project unless to teach the script how to extract the full hierarchy and parameters of the project. (Good luck coding and maintaining that).
How can the unused bits be safely eliminated?
If the synthesizer is not advance enough to exclude unused bits on its own, then the bits can be optimized by flattening the multidimensional array into a one-dimensional array with intelligent part-select. The trick is finding the equation which can be achieved by following these steps:
- Find the pattern of the
lsb
index for each part part select:
- Assume
M
is 4, the lsb
for each part-select are 0, 5, 11, 18, 26, 35, ...
. Plug this pattern into WolframAlpha to find the equation a(n) = (n-1)*(n+8)/2
.
- Repeat with
M
equal to 3 for the pattern 0, 4, 9, 15, ...
to get equation a(n)=(n-1)*(n+6)/2
- Repeat again with
M
equal to 5 for the pattern 0, 6, 13, 21, 30, ...
to get equation a(n)=(n-1)*(n+10)/2
.
- Since the relation of
M
and N
is linear (i.e. multiple; no exponential, logarithmic, etc.), only two equations are needed to create a variable parameter M
equation. For non-linear equations more data-point equations are recommended. In this case note that for M=3,4,5
the pattern (n+6),(n+8),(n+10)
, therefore the generic equation can be derived to: lsb(n)=(n-1)*(n+2*M)/2
- Fine the pattern of the
msb
index for each part select:
- Use the same process of as finding the
lsb
(ends up being msb(n)=(n**2+(M*2+1)*n-2)/2
). Or define the msb
in terms of lsb
: msb(n)=lsb(n+1)-1
IEEE std 1364-2001 (Verilog 2001) introduced macros with arguments and indexed part-select; see § 19.3.1 '`define' and § 4.2.1 'Vector bit-select and part-select addressing' respectively. Or see IEEE std 1800-2012 § 22.5.1 '`define' and § 11.5.1 'Vector bit-select and part-select addressing' respectively. This answer assumes that these features are supported by the SO's simulator and synthesizer since the generate
keyword was also introduced in IEEE std 1364-2001, see § 12.1.3 'Generated instantiation' (and IEEE std 1800-2012 § 27. 'Generate constructs'). For tools that are not fully support IEEE std 1364-2001, see `ifdef
examples provided here.
Since the functions to calculate the part-select ranges are frequently used, use `define
macros with arguments. This will help prevent copy/paste bugs. The extra sets of ()
in the macro definitions are to insure proper order of operations. It is also a good idea to `undef
the macros at the end of the module definition, preventing the global space from getting polluted. With the flattened array it may become challenging to debug. By defining pass-through connections within the generate block's for-loop the signal can become readable and can be probed in waveform.
module Multiplier #(parameter M = 4, parameter N = 4)(
input [M-1:0] A, //Input A, size M
input [N-1:0] B, //Input B, size N
output [M+N-1:0] P ); //Output P (product), size M+N
// global space macros
`define calc_pp_lsb(n) (((n)-1)*((n)+2*M)/2)
`define calc_pp_msb(n) (`calc_pp_lsb(n+1)-1)
`define calc_pp_range(n) `calc_pp_lsb(n) +: (M+n)
wire [`calc_pp_msb(N):0] PP; // Partial Product
assign PP[`calc_pp_range(1)] = { 1'b0 , { A & {M{B[0]}} } };
assign P = PP[`calc_pp_range(N)]; // Product
genvar i;
generate
for (i=1; i < N; i=i+1)
begin: addPartialProduct
wire [M+i-1:0] gA,gB,gS; wire Cout;
assign gA = PP[`calc_pp_range(i)];
assign gB = { A & {M{B[i]}} , {i{1'b0}} };
assign PP[`calc_pp_range(i+1)] = {Cout,gS};
RippleCarryAdder#(M+i) adder( .A(gA), .B(gB), .S(gS), .Cin (1'b0), .* );
end
endgenerate
// Cleanup global space
`undef calc_pp_range
`undef calc_pp_msb
`undef calc_pp_lsb
endmodule
Working example with side-by-side and test bench: http://www.edaplayground.com/s/6/591
Will the end code be readable and debug-able?
Yes, for anyone who has already learned how to properly use the generate construct. The generate block's for-loop defines local wires which are confined to scope of the loop index. gA
form loop-0 and gA
from loop-1 are unique signals and cannot interact with each other. The local signals can be probed in waveform which is great for debugging.