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.