Also discussed at:
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:
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 () ;
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