Search code examples
system-verilogvivado

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 () ;

Solution

  • 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