Passing unpacked array type as parameter - system-verilog

I created a module to soemthing simple like add some pipeline delay. I made the data type a parameter so it could handle complex things like structs. I have something like this:
module pipe
#(parameter type T = int,
parameter DELAY = 0)
(
input clk,
input T data_in,
output T data_out);
T pipe[DELAY];
always_ff #(posedge clk) begin
pipe[0] <= data_in;
for(int i = 1; i<DEPTH; i++) begin
pipe[i] <= pipe[i-1];
end
end
assign data_out = pipe[DELAY-1];
endmodule
This works great but then I found myself wanting to use it with an unpacked array and I couldn't figure out how to instance it. It would look something like this, but I don't think this is right:
logic [7:0] my_data [16];
logic [7:0] my_data_delayed [16];
pipe #(.T(logic [7:0] [16]), .DELAY(2)) i_pipe
(
.clk(clk),
.data_in(my_data),
.data_out(my_data_delayed)
);
Is there a way to get the type of a variable so it can be passed to this parameter?

You can use typedef do define the data type:
typedef logic [7:0] my_type [16];
my_type my_data;
my_type my_data_delayed;
pipe #(.T(my_type), .DELAY(2)) i_pipe
(
.clk(clk),
.data_in(my_data),
.data_out(my_data_delayed)
);
The remaining question is if it will work with your simulator. I tried it out at EDAplayground, worked with VCS but not Riviera-PRO (got a "not supported in this release" message)
I was able to get typedef struct { logic [7:0] data [16]; } my_type; to work. Less clean but it is a workaround.

While typing the question I figured out one answer. The type operator (section 6.23 in IEEE 1800-2012) spec does what I need. So my instance would look like this:
logic [7:0] my_data [16];
logic [7:0] my_data_delayed [16];
pipe #(.T(type(my_data)), .DELAY(2)) i_pipe
(
.clk(clk),
.data_in(my_data),
.data_out(my_data_delayed)
);
Of course this results in a compile error in my tool so it doesn't look to be universally supported.
My work-around is going to be to make an array of instances of the pipe module.

Related

How to initialize unpacked array of unpacked union of unpacked structs?

What is the legal method of initializing this?
module tb();
typedef struct {
logic [3:0] A;
} a_t;
typedef struct {
logic [7:0] B;
} b_t;
typedef union {
a_t a;
b_t b;
} a_b_t;
a_b_t a_b [8];
initial begin
a_b <= '{default: '{default: 0}};
end
endmodule
xrun gives this error:
xmelab: *E,APBLHS (./tmp.sv,14|23): Assignment pattern - LHS must be an array or structure [SystemVerilog].
I've tried a variety of other ways, and I can't quite get this right. I'm working around it right now by initializing each entry separately with 2 lines of initialization.
As the error message state, assignment patterns only work with struct and arrays, not unions. You would need to use a foreach loop to assign each union member.
initial
foreach (a_b[i]) begin
a_b[i].a <= '{default:0};
a_b[i].b <= '{default:0};
end
Note that unless you are using the DPI for C compatibility, unpacked unions have little usefulness in SystemVerilog. Use packed unions instead.

Parameterizing the Bit Widths of fields in a packed struct so that modules can infer bit width if used in port map

Also discussed at:
https://verificationacademy.com/forums/systemverilog/parameterizing-bit-widths-fields-packed-struct-so-modules-can-infer-bit-width-if-used-port-map-virtual-interface-interface-compile-time-configured-struct-bit-width
https://forums.xilinx.com/t5/Synthesis/Parameterizing-the-Bit-Widths-of-fields-in-a-packed-struct-so/td-p/1191678
I am having trouble accomplishing my intent in SystemVerilog trying to use the latest language features to make my code more elegant and less verbose. For synthesis**
I would like to accomplish the following:
be able to parameterize the bit widths of fields in a packed struct that I want to define ... I have attempted to accomplish this using a parameterized interface construct
I would like for modules with that parameterized interface as an INPUT to the module to be able to infer the bit width of a field inside that packed struct defined inside the interface
I have been mostly successful in past experiments but I have run into an issue.
Please see the following simple interface definition:
interface MyInterface #(int DATA_W, ADDR_W) () ;
typedef struct packed
{ logic valid
; logic [ADDR_W-1:0] addr
; logic [DATA_W-1:0] data
; } SimpleStruct;
SimpleStruct bus;
logic ready;
modport SNK (input bus, output ready);
modport SRC (output bus, input ready);
endinterface
It is easy enough to instantiate an interface and use it at the input of a simple module in my Top module for this example:
module TopTest
( input wire Clock
, input wire Reset
, input wire [31:0] In
, output wire dummyOut
) ;
MyInterface # ( 32, 3 ) my_interface ();
assign my_interface.bus.data = In ;
assign my_interface.bus.addr = 3'h3 ;
InnerTest inst_mod_inner_test
( .Clock( Clock )
, .Reset( Reset )
, .Sink( my_interface )
) ;
assign dummyOut = my_interface.ready ;
endmodule
The problem that I am running into is that I do not want to parameterize the actual module with field bit widths, because I believe that at compile time the bit widths of the fields should be already established and accessible. This seems to not be the case, and I am wondering if there is anything I can do to accomplish inferring the bit width of the packed struct in the interface (remember that is the case because I want it parameterized, I know it is easy to get $bits of a field of a struct that is not defined in an interface but instead defined in a package or module)
module InnerTest
( input wire Clock
, input wire Reset
, MyInterface.SNK Sink
) ;
localparam BIT_WIDTH_SINK_DATA = $bits( Sink.bus.data ) // this line errors out b/c sink is 'virtual'
RAM # ( .DATA_WIDTH( BIT_WIDTH_SINK_DATA ) ) ram ( ... // etc
... other code to drive output ready of interface ...
endmodule
There are many reasons why a designer would want to make a module "parameterizable" and I have taken that approach in the past, but I am very interested in not duplicating information. If I were to take the easy approach, I would simply parameterize my inner test module so that I provided it DATA_WIDTH, but I would then have two numbers to update and a lot of parameters that I feel I do not need. I think it would be most elegant if I could simply infer characteristics of the parameterized struct somehow. The information I am looking for is truly known at compile time in my opinion. I just can't seem to access it, or this is another shortfall of SystemVerilog.
Follow up Q, in Simulation
The workaround mentioned by Dave was very useful when using QuestaSim, but now running into different issue in QuestaSim:
parameter reference "sink.bus.data" through interface port "sink" is not valid when the actual interface in the instance is an arrayed instance element or below a generate construct
What is the workaround for this, I don't understand why simply being in a generate statement would impact things way downstream. In this case i use a generate statement to choose between different FIFO implementations, a few layers above the line of code where the error happens.
typedef sink.bus.data questasim_workaround;
localparam width = $bits(questasim_workaround);
Follow Up Experiment
I have experimented with passing in type instead of restricting myself to passing in DATA_W.
interface MyInterface #(int ADDR_W, type DATA_TYPE) () ;
typedef struct packed
{ logic valid
; logic [ADDR_W-1:0] addr
; DATA_TYPE data
; } SimpleStruct;
SimpleStruct bus;
logic ready;
modport SNK (input bus, output ready);
modport SRC (output bus, input ready);
endinterface
This allows for more flexibility. I have observed that Vivado Simulator and Synthesis tools can handle an example like this without issue.
module SomeModule
( MyInterface myInt
blah...
);
localparam SOMETHING = $bits(myInt.DATA_TYPE);
// or equivalently
localparam SOMETHING_ELSE = $bits(myInt.data);
// or even this, for needs of a internal signal for pipelined processing steps
MyInterface # ($bits(myInt.addr), myInt.DATA_TYPE) internal_0 () ;
In place of this in QUestaSim we have had to implement Dave's work around:
module SomeModule
( MyInterface myInt
blah...
);
// this gets less elegant :/
typedef myInt.data questasim_workaround_data;
typedef myInt.addr questasim_workaround_addr;
localparam SOMETHING = $bits(questasim_workaround_data);
// or equivalently
localparam SOMETHING_ELSE = $bits(questasim_workaround_data);
// or even this, for needs of a internal signal for pipelined processing steps
MyInterface # ($bits(questasim_workaround_addr), questasim_workaround_data) internal_0 () ;
The current SystemVerilog BNF does not allow any dotted "." names in a parameter initialization. But you can get around this by using a typedef instead
interface MyInterface #(int DATA_W=0, ADDR_W=0) () ;
typedef logic [DATA_W-1:0] data_t;
...
endinterface
module InnerTest
( input wire Clock
, input wire Reset
, MyInterface.SNK Sink
) ;
typedef Sink.data_t data_t;
localparam BIT_WIDTH_SINK_DATA = $bits( data_t );
...
endmodule
Whenever you see an error that is unexpected in Vivado around "[Synth 8-27] scoped/hierarchical type name not supported" ... check to see if the instantiation port map matches all the names of the actual module definitions portmap. That was the issue in Vivado only with this code. The spelling didn't match and instead of a "[Synth 8-448] named port connection 'clkkkk' does not exist" I got a "[Synth 8-27] scoped/hierarchical type name not supported" error
As explained in: https://forums.xilinx.com/t5/Synthesis/Parameterizing-the-Bit-Widths-of-fields-in-a-packed-struct-so/td-p/1191678

Combinatorial assignment to "composite" wire in always block

This is not a blocker that I'm dealing with, just looking for possibly a more elegant way of doing it.
module Ram(RamClk, CKE, CAS, RAS, WE, DQM, BA, A, DQ,
OpEnable, Addr, RdData, WrData, Ready);
input RamClk;
output CKE;
output logic CAS, RAS, WE;
output [1:0] DQM;
output logic [1:0] BA;
output logic [11:0] A;
inout [15:0] DQ;
input OpEnable;
output [23:0] Addr; //24 bits = 16 Mb
output [15:0] RdData;
input [15:0] WrData;
output Ready;
// code omitted here
wire CMD = {CAS,RAS,WE};
// code omitted here
//multiplexers
always #*
begin
if (InitDone == 0)
begin
CMD = InitCmd;
A = InitA;
BA = InitBA;
end
else
begin
//todo: assign the output of the main CMD, A and BA multiplexers
CMD = CMD_NOP;
A = 0;
BA = 0;
end
end
//rest of the module
I have 3 outputs of the module RAS, CAS, WE that I'm combining into a single wire for convenience so that I can assign command values to them as a whole.
Now I need to connect a multiplexer to CMD and I'm doing it in an always block for convenience.
I could do it in separate assign statements in this case of only 2 inputs to the multiplexer using the ternary expression, but let's imagine the multiplexer has more inputs, for more inputs it looks more convenient to use multiple if/else or a case statement which I cannot use with assign as far as I understand.
Now I have an issue that I have to have a reg on the LHS in the always block although it is combinatorial logic in this case and it will not infer a register in this case.
But if I change CMD to reg then I cannot use the {} notation to combine CAS, RAS and WE.
I could replace the CMD with a task or combine/split CAS, RAS and WE outside the module and have them as a single CMD module parameter.
Any more elegant way of expressing this?
When you have have multiple variables that you would like to assign at once, you can use the concatenation operator on the LHS of an expression:
output logic CAS, RAS, WE;
...
logic [2:0] CMD;
assign {CAS, RAS, WE} = CMD;
...
CMD = 3'b011; // Will assign CAS = 0, RAS = 1, WE = 1
...
You can also just not use CMD at all and just use {CAS, RAS, WE} in its place; depending on your style and what makes sense for your project. A side note, ideally, you should be using always_comb over always #(*) and logic over wire and reg (mostly) if you are in a SystemVerilog environment as you seem to be.

Concatenating elements of unpacked array together

I am using the following function in my System Verilog code. I wondered if there was an idiomatic way of achieving the same effect that perhaps would not require the width to be hardwired. I tried streaming operators, but could not quite get them to work. I need to use unpacked arrays. Many thanks.
function bit [64:0] cat8 (bit [7:0] a[8]);
return { a[7], a[6], a[5], a[4], a[3], a[2], a[1], a[0] };
endfunction;
since you reversing the array in concat, there is no good way to express it.
you have:
bit [7:0] a[8];
which is equivalent to
bit [7:0] a[0:7];
in your concat you start with a[7] in the most significant bits whether 7 is the least significant index in the array.
This is the reason why the streaming operators did not work in your case.
So, if you really need to reverse the array, than you have what you have, otherwise you can find that these 2 things are equivalent:
{ a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7] }
and
{ >> {a}}
of course you can declare your array as bit [7:0] a[7:0] and keep index ordering in concat as you have. But it will not reverse the array again, as in the above case.
you can define a new datatype through typedef.
typedef bit[7:0] octet;
typedef octet upack[7:0];
function bit [64:0] cat8 (upack a);
// your code
endfunction;
Below should work for you
module top;
function bit [63:0] cat8 (bit [7:0] a[8]);
return { <<8{a}};
endfunction;
bit [7:0] arr[8];
initial begin
arr= '{1,2,3,4,5,6,7,8};
foreach (arr[i])$display("%h", arr[i]);
$display("%h", cat8(arr));
end
endmodule

Pack individual signal into an array

I have a bunch of signals like this:
logic [7:0] in0;
logic [7:0] in1;
logic [7:0] in2;
logic [7:0] in3;
That I want to assign to an array:
logic [7:0] in_array [4];
assign in_array[0] = in0;
assign in_array[1] = in1;
assign in_array[2] = in2;
assign in_array[3] = in3;
Easy enough, but if instead of 4 items I have 128 this gets annoying. I am sure there is a combination of defines and generates that can do this in a loop. Something like:
`define IN(x) inx
genvar i;
generate
for(i = 0; i<4; i++) begin
assign in_array[i] = `IN(i);
end
endgenerate
The above code doesn't work, but I think that I have done something like this before.
Simplifying that code is something that cannot be done in SystemVerilog. You can reduce you typing by creating a macro like below (note the double backticks ``), but you will still need to manually write each index. Macros are are resolved before generate loops and the input variable to the macro is treated as a literal.
// short named macro for reduced typing
// Note: using short named macro is typically a bad practice,
// but will be removed latter with an undef
`define A(idx) assign array_in[idx] = out``idx
//This works
`A(0);
`A(1);
`A(2);
`A(3);
// doesn't work. For example # gidx==0 will eval to 'assign array_in[0] = outgidx;'.
// There is not outgidx
genvar gidx;
generate
for(gidx=0; gidx<4; gidx++) begin
`A(gidx);
end
endgenerate
`undef A // prevent macro from from being used latter on
If it is just a small number of entries, it is best to do it manually. If it is large number of entries, then you need to consider a way to generate the for you, such as embedded coded.
There are also various embedded code (such as Perl's EP3, Ruby's eRuby/ruby_it, Python's prepro, etc.) that can generate the desired code. Pick your preference. You will need to per-process these files before giving to the compiler. Example with EP3 generating 400 assignments:
#perl_begin
foreach my $idx (0..400) {
printf "assign array_in[%0d] = out%0d;", $idx, $idx;
}
#perl_end
Use `` to separate text from argument.
`define IN(x) in``x
But there is another issue with the variable i not being declared at the time when the macro is evaluated. Thus the whole generate loop just connects to ini, because i is just another letter. Because of this macros cannot be assigned by dynamically allocated values.
The environment of your module already has to connect explicitly to each input assign in0 = out0; ... assign in127 = out127. So the simplest solution would be to have in_array as your modules input and let the environment connect to it assign array_in[0] = out0.
Something like this:
module parent_module();
/*some other stuff that has outputs out0, out1 etc.*/
logic [7:0] array_in[4];
assign array_in[0] = out0;
assign array_in[1] = out1;
assign array_in[2] = out2;
assign array_in[3] = out3;
my_module(.array_in(array_in));
endmodule