Parameterised structures/unions used as ports

Hi,

This is my first post so be gentle.

I have an RTL design that has some FF storage but depending on a particular mode it can be accessed 1/2 width, full depth or full width half depth.

// mode == 0
reg   [511:0] flags[63:0]; // 64 * 512-bit flags

// mode == 1
reg  [1024:0] flags[31:0]; // 32 * 1024-bit flags

I was looking for a nice syntax for accessing these 32Kbit arrays in either organisation. And I considered a synthesisable SV union.

package my_pkg;
    typedef union packed
    {
        reg  [511:0] mode0[63:0];
        reg [1023:0] mode1[31:0];
    } flags_union_t;
endpackage

module flags
  import my_pkg::flags_union_t;
(
    input  wire              clk,
    input  wire              clk_en,
    input  wire              reset_n,
    input  wire              write,
    input  wire        [5:0] flag_idx,
    input  wire     [1023:0] new_flags,
    input  wire              mode,
    output     flags_union_t flags
);

always @(posedge clk or negedge reset_n)
begin
    if (reset_n == 1'b0)
    begin
        for (int unsigned i=0; i<32; i++)
            flags.mode1 <= {1024{1'b0}};
    end
    else if (clk_en)
    begin
        if (slot_write)
        begin
            if (mode == 1'b0)
                flags.mode0[flag_idx[5:0]] <= new_flags[511:0];
            else
                flags.mode1[flag_idx[4:0]] <= new_flags[1023:0];
        end
    end
end

endmodule

Seemed to work ok, but I was asked to parameterise the module so the number of flags was a parameter. The idea being that the code could be re-used for two instances with different number of flags.

But I could find no syntax to get the parameter from the top-level module into the package or any alternatives to a package that allowed you to have a structure/union as a type on a port.

Any suggestions?

The SV LRM 1800-2017 section 6.25 has an example using a parameterised class but as an RTL designer who has only just started using SV for synthesis the idea of using classes is still a bit daunting. (And I don’t 100% understand the example)

Is there no other method for parameterising a structure/union for use as a module port other than a pkg? Or perhaps an easier alternative to using a union in the first place that I didn’t think of?

In reply to alexholland:

An interface - which can be parameterized - would work, instead of a structure:

interface #( parameter WIDTH0 = 64, DEPTH0 = 512 /*other paramters*/) flags_if;
  logic [ DEPTH0 - 1 : 0 ] mode0 [ WIDTH0 - 1 : 0 ];
  //other mode flags here..
  modport driver( output mode0 /* other flags */ );
endinterface

module top;
  flags_if #( .DEPTH0( DEPTH0 ), .WIDTH0( WIDTH0 ) this_if;
  flags_submodule submod( .my_flags( this_if ) );
endmodule

module flags_submodule
(
  flags_if.driver my_flags
);

assign my_flags.mode0[ foo ] = bar;
endmodule


Classes can be parameterized too, however I’m not aware of any synthesis tool which support this for RTL code.

In reply to Mark Curry:

Thanks for the reply.

Excuse my stupidity (having not used SV interfaces before) but how do I get a union into a SV interface?

Am I not stuck with the same problem as I had when using just ports? I cant get a struct/union typedef into an SV interface without a package and you can’t parameterise a package?

I think the solution will have to be to use a union inside the module that writes the data into the storage and have two different looking ports on the module for the two views.

No package hence can be parameterised in the normal way.

Not as elegant but I think this should work.

module flags
(
    input  wire              clk,
    input  wire              clk_en,
    input  wire              reset_n,
    input  wire              write,
    input  wire        [5:0] flag_idx,
    input  wire     [1023:0] new_flags,
    input  wire              mode,
    output wire      [511:0] flags_mode0[63:0]
    output wire     [1023:0] flags_mode1[31:0],
);
    typedef union packed
    {
        reg  [511:0] mode0[63:0];
        reg [1023:0] mode1[31:0];
    } flags_union_t;

    flags_union_t flags;
 
    always @(posedge clk or negedge reset_n)
    begin
        if (reset_n == 1'b0)
        begin
            for (int unsigned i=0; i<32; i++)
                flags.mode1 <= {1024{1'b0}};
        end
        else if (clk_en)
        begin
            if (slot_write)
            begin
                if (mode == 1'b0)
                    flags.mode0[flag_idx[5:0]] <= new_flags[511:0];
                else
                    flags.mode1[flag_idx[4:0]] <= new_flags[1023:0];
            end
        end
    end

    assign flags_mode0 = flags.mode0;
    assign flags_mode1 = flags.mode1;

endmodule
 

In reply to alexholland:

My suggestion would be to avoid using struct/union as your “higher level” object, and instead just use an interface. Combining interfaces and structs/union while possible, usually adds too much clutter.

–Mark

In reply to Mark Curry:
Thanks for the reply. Sorry I still don’t understand. Please bear with me. I will get it eventually.

The output of this module “flags” is to be connected to multiple blocks, the idea was by using a union each connected module could view the underlaying storage of “flags” using either of the two representations (1024x32 or 512x64).

How does a SV interface allow me to export the two representations of the storage behind “flags” like a union would have?

In reply to alexholland:

I’m not exactly clear on your requirements. One other option is to create the (perhaps parameterized) “flags” with a single definition for the “bus”. Then, as needed reshuffle the flags within the instanced module where appropriate.

For instance, I have a reusable “matrix_transpose” module, that as the name implies, transposes a matrix in SystemVerilog:

module matrix_transpose
#(
  parameter ROWS = 2,  // Min 1, no max
  parameter COLS = 3,  // Min 1, no max
  parameter ELEMENT_SIZE = 8  // Min 1, no max
)
(
  input  wire [ ROWS - 1 : 0 ] [ COLS - 1 : 0 ] [ ELEMENT_SIZE - 1 : 0 ] a_i,
  output wire [ COLS - 1 : 0 ] [ ROWS - 1 : 0 ] [ ELEMENT_SIZE - 1 : 0 ] y_o
);

You can do similar to re-order the indexing / aspect ratio of your “flags” module as needed.
Pick one representation that the bus carries, then reshuffle as necessary.

A reshuffle represents NO logic in a synthesized designs - you’re just changing the way, you the designer select individual wires.

It may have some simulation performance impact to reshuffle those large groups of bits. But that’s up to you on whether or not that’s a problem.