Continue to Site

Welcome to EDAboard.com

Welcome to our site! EDAboard.com is an international Electronics Discussion Forum focused on EDA software, circuits, schematics, books, theory, papers, asic, pld, 8051, DSP, Network, RF, Analog Design, PCB, Service Manuals... and a whole lot more! To participate you need to register. Registration is free. Click here to register now.

System Verilog : Passing parameterized structs through ports

Status
Not open for further replies.

Verilog_Guy

Newbie level 3
Joined
Nov 18, 2014
Messages
4
Helped
0
Reputation
0
Reaction score
0
Trophy points
1
Activity points
62
Trying to write reusable System Verilog code using structures (and unions) using parameters. The code needs to be synthesizable. I've having trouble passing parameterized structures through ports. Here's what I'd like to do:


module my_top_module

parameter FOO = 8;

typedef struct packed {
logic [FOO-1:0] bar;​
} my_struct_t;

my_struct_t my_struct;
assign my_struct.bar = 8'h01;

my_core_module #(
.FOO(FOO)​
) my_core_module (
.my_struct(my_struct)​
);

endmodule

// ----------------------------

module my_core_module #(
parameter FOO = 4​
) (
input my_struct_t my_struct​
);

typedef struct packed {
logic [FOO-1:0] bar;​
} my_struct_t;

However, this code gives me the following error:


Error-[SV-UIOT] Undefined interface or type
../rtl/my_top_module.sv, 27
my_struct_t, "my_struct"
The definition for the forward-referenced interface 'my_struct_t' is missing
or 'my_struct_t' is the name of an undefined user type.
Check to see if the interface definition file/work library is missing or the
definition of the user type is missing.

After doing extensive experiments, I've found that the only way I've been able to make this work is if I switch back to using the old non-ANSI port declarations, as shown below. However, it seems wrong to me that I should have to do this.

module my_core_module #(
) (
my_struct​
);

parameter FOO = 4;

typedef struct packed {
logic [FOO-1:0] bar;​
} my_struct_t;

input my_struct_t my_struct;​

What am I missing? Is this a shortcoming of the languange? If so, it seems that structures are broken (for all practical purposes) in System Verilog as they exist today....

Thanks!
 

There's a few things wrong with what you did, mostly related to scoping.

Try something like this instead:


Code Verilog - [expand]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
localparam FOO = 8;
 
typedef struct packed {
    logic [FOO-1:0] bar;
} my_struct_t;
 
// ----------------------------
 
module my_top_module(
    input        clk,
    input        rst_n,
    output [7:0] led
    );
 
    my_struct_t my_struct;
    assign my_struct.bar = 8'h01;
 
    my_core_module my_core_module_inst (
        .my_struct (my_struct),
        .whatever  (led)
    );
endmodule // my_top_module
   
// ----------------------------
 
module my_core_module(
    input        my_struct_t my_struct,
    output [7:0] whatever
    );
    
    assign whatever = ~ my_struct.bar[7:0]; // big fat assumption that FOO >= 8 , but this is only an example.
endmodule // my_core_module



That's not perfect either from an architecture point of view, but at least it works and hopefully you see the difference with what you did.

The key point is that you have to make sure the struct typedef is visible to all scopes within which it is used. I anticipate a couple of butbutbut remarks, so lets see what those are. ;-)
 

mrflibble, thanks for your reply....

I guess I should have mentioned that we like to create a lot of reusable IP. So having to create global parameters and structures doesn't really work from a re-usability standpoint. However, for non-reusable code this could be a (less than ideal) option. I also realize that interfaces are an option, though for one-off block-to-block connections parameterized structures seem more suitable than interfaces (interfaces seem more suitable for more formal, standardized interfaces). Also packages seem to be a non-starter, since you can't pass parameters in to them.

I can't help but wonder if I'm missing something fundamental though. One of the selling points of System Verilog is that it is supposed to be all about code reusability. And I *can* pass parameterized structures through ports using non-ANSI port declarations, so I find it hard to believe that such a basic feature as doing this with ANSI port declarations is missing from the language....but maybe it is?
 

This may come off as a bit of a rant (sorry!), but the whole "reuse model" of System Verilog seems broken to me....

  1. You can't pass parameters into packages (thus no reuse).
  2. You can't pass parametrized structures and unions through ports, unless:
    • You revert to using non-ansi style port lists, which have pretty spotty tool support these days.
    • You use packages, which don't allow resuse because of #1 (above).
It seems to me a designer is left with two choices... 1) Use packages / structures / unions and forget about reuse, or 2) don't use packages / structures / unions -- just use good old nets which can be parameterized and overridden through passed in parameters and thus allow for code reuse.

Am I missing anything? Anyone here with insight as to the what the architects of System Verilog were thinking (or better yet have pull to get this fixed in a future version of System Verilog)?
 

I suspect the ANSI versus non-ANSI port declaration is a bit of a red herring. Why? Because the non-ANSI way is the only way in which you are allowed to cram your horrible Do Not Use This workaround of doing the typedef declaration in that module. Isn't the idea behind re-use is that you make something you actually want to re-use? XD To that end you probably want something that is not going to be hard to maintain. And plonking your typedef's in there is a nice example of what not to do IMO. Can you think of a programming language where the equivalent is a good idea or even close to a best practice?

Anyways, I will assume that you arrived at that particular recipe not because you thought it was a great idea, but purely because it was a recipe that roughly did what you wanted and it's the only one that actually got synthesized. If you did this because you thought it was a good idea, I probably am missing something. In that case could you explain why you chose this particular construct? IMO you want to do the typedef (parameterized or not) somewhere else exactly once, and then use it in the various modules.

As for packages not accepting parameters.... Yes, no, sortof? There is nothing stopping you to parameterize the stuff inside your package. Maybe it's a matter of expectations. If you expect to be able to provide a parameter when you do an import my_pkg::*, then think again. Or better, look at other (programming) languages. You don't provide the parameters on the import-like expression in languages like C++/java/python/whatever either. Instead you should be looking at static package members to hold the parameters for you. You could even pull in the default values (or overriding values) for those with external parameters that you give a nice hierarchical name. That way you can specify them on your synthesis/simulation command line a la -G/-g/-defparam/-D or some such.

So packages do not accept parameters on the import or `include statement. Nope. And rightfully so IMO. But you can still parameterize the members inside that package. Package is nothing more than a convenient container and a namespace.

As for interfaces, why only use interfaces for big projects? Interfaces are damn handy and I wish verilog would have had that waaaay earlier.

And structs not being parameterized is indeed bloody stupid, and an oversight of the design committee IMO. If I take for example C++ then classes and structs are very closely related, and you can template both of them.

You could try a paramerized class as a wrapper for your struct to get the job done. But no doubt the synthesis tools will barf despite the fact that everything is static and could have been resolved compile time.

But from a practical point of view none of that has to be a showstopper, since you are dumping everything inside a package. And you can simply have a parameter as package member, and use that. Not as elegant as a properly parameterized struct, but gets the job done.

Am I missing anything? Anyone here with insight as to the what the architects of System Verilog were thinking (or better yet have pull to get this fixed in a future version of System Verilog)?

I have no idea! The lack of parameters for struct while providing it for class is indeed just silly. And even that would not be so bad if you could use class as workaround. But that's only going to work if you want simulation only. Synthesis is probably going to be a no-go. Speaking of synthesis, any particular reason why you are not using a packed struct there?
 

Heh! Amusing implementation in Vivado. So I tried a workaround with a parameterized virtual class as wrapper for a typedef. First try simulation since that tends to have better class support compared to synthesis.

[XSIM 43-3980] File "..../sources_1/new/my_pkg.sv" Line 62 : The "System Verilog Class" is not supported yet for simulation.

Oh well, luckily there is always Modelsim.

Just for the fun of it also I ran synthesis (for which I know classes are not supported), which gives no error but that is no doubt a silent ignore. Besides, if simulation is already a nogo then just forget about synthesis. :p

Short version: Given the rather limited list of supported features in synthesizers I think your best bet is parameterized interfaces. You can even plonk those in a package.
 

Again, thanks for your reply!

I suspect the ANSI versus non-ANSI port declaration is a bit of a red herring. Why? Because the non-ANSI way is the only way in which you are allowed to cram your horrible Do Not Use This workaround of doing the typedef declaration in that module. Isn't the idea behind re-use is that you make something you actually want to re-use? XD To that end you probably want something that is not going to be hard to maintain. And plonking your typedef's in there is a nice example of what not to do IMO. Can you think of a programming language where the equivalent is a good idea or even close to a best practice?

Anyways, I will assume that you arrived at that particular recipe not because you thought it was a great idea, but purely because it was a recipe that roughly did what you wanted and it's the only one that actually got synthesized. If you did this because you thought it was a good idea, I probably am missing something. In that case could you explain why you chose this particular construct? IMO you want to do the typedef (parameterized or not) somewhere else exactly once, and then use it in the various modules.

The typedef you see plonked in the code was to provide a simple example for this forum. In practice, I have all my typedefs pulled in to a single include file (the include file is acting as a package, if you will). So as far as maintainability, I see no issues. And yes, I hate having resort to non-ANSI declarations, but nonetheless it does seem to work.

As for packages not accepting parameters.... Yes, no, sortof? There is nothing stopping you to parameterize the stuff inside your package. Maybe it's a matter of expectations. If you expect to be able to provide a parameter when you do an import my_pkg::*, then think again. Or better, look at other (programming) languages. You don't provide the parameters on the import-like expression in languages like C++/java/python/whatever either. Instead you should be looking at static package members to hold the parameters for you. You could even pull in the default values (or overriding values) for those with external parameters that you give a nice hierarchical name. That way you can specify them on your synthesis/simulation command line a la -G/-g/-defparam/-D or some such.

I've always thought of parameter passing / overriding as one of the best features of Verilog. Something that made it better than C++/java/python/whatever. Maybe the relevant question is this: is Verilog trying to become these other languages? To that end, are parameters going away, and their restricted usage in structs and unions the first step toward this end?

So packages do not accept parameters on the import or `include statement. Nope. And rightfully so IMO. But you can still parameterize the members inside that package. Package is nothing more than a convenient container and a namespace.

As for interfaces, why only use interfaces for big projects? Interfaces are damn handy and I wish verilog would have had that waaaay earlier.

And structs not being parameterized is indeed bloody stupid, and an oversight of the design committee IMO. If I take for example C++ then classes and structs are very closely related, and you can template both of them.

You could try a paramerized class as a wrapper for your struct to get the job done. But no doubt the synthesis tools will barf despite the fact that everything is static and could have been resolved compile time.

But from a practical point of view none of that has to be a showstopper, since you are dumping everything inside a package. And you can simply have a parameter as package member, and use that. Not as elegant as a properly parameterized struct, but gets the job done.

Sounds good. I was really trying understand if structs / unions were crippled (purposefully or not) in System Verilog, or if I was missing something. Sounds like they are crippled. So it is what it is and I have to search for other ways of doing what I want to do. We've got to work with the language we are given, not the one we want. :smile: And as you say, maybe packages are a better alternative than resorting to non-ansi. Your points are noted.

I have no idea! The lack of parameters for struct while providing it for class is indeed just silly. And even that would not be so bad if you could use class as workaround. But that's only going to work if you want simulation only. Synthesis is probably going to be a no-go. Speaking of synthesis, any particular reason why you are not using a packed struct there?

I do show a packed struct in my example. Perhaps you missed it?

Thanks again!
 

The typedef you see plonked in the code was to provide a simple example for this forum. In practice, I have all my typedefs pulled in to a single include file (the include file is acting as a package, if you will). So as far as maintainability, I see no issues. And yes, I hate having resort to non-ANSI declarations, but nonetheless it does seem to work.
Having to use verilog-1995 port declarations as a workaround for shortcomings in SystemVerilog ... That's just depressing. :p

I've always thought of parameter passing / overriding as one of the best features of Verilog. Something that made it better than C++/java/python/whatever. Maybe the relevant question is this: is Verilog trying to become these other languages? To that end, are parameters going away, and their restricted usage in structs and unions the first step toward this end?
I wouldn't exactly call verilog or SV better than say C++ or java. I would actually call it worse. :p On the whole I find HDL tooling to be yeeeears behind that in the software world. But what the hell, it's what's available so here we are.

Sounds good. I was really trying understand if structs / unions were crippled (purposefully or not) in System Verilog, or if I was missing something. Sounds like they are crippled. So it is what it is and I have to search for other ways of doing what I want to do. We've got to work with the language we are given, not the one we want. :smile: And as you say, maybe packages are a better alternative than resorting to non-ansi. Your points are noted.

Turns out I am a silly person. You cannot put interfaces inside of packages. :( Because who would ever want to do that, right? Maybe you could do something with interface classes, but again that would preclude the synthesis step so no real option unless you are interested in simulation only.

But yeah, as far as I can tell structs are crippled with respect to parameters. And I truly cannot understand why they chose to do it this way. Custom data types seem a reasonably logical place to want to parameterize bit-width etc.

I do show a packed struct in my example. Perhaps you missed it?
I swear, one of these days I am going to need either glasses, sleep or more coffee ._.
 

The proper way to do this is described in section 6.25 Parameterized data types of the 1800-2012 LRM. This was meant to be synthesizable, but not all synthesis tools support it yet. The same issues comes up in section 13.8 Parameterized tasks and functions. I do know of at least one synthesis tool that supports it.

Since you are using packed structs, it might be better to pass simple vectors through the ports and assign them locally to struct variables inside your modules. When you used packed types, you loose all the type checking that make sure the structures are consistently formed. That's why it would be much better to put unpacked structs in a package and reference them in your port connections.
 
Indeed. I already tested the parameterized virtual class way of doing things as listed in 6.25 & 13.8, but at least in vivado that is a nogo. So for simulation no problem, but doesn't work for synthesis. And when I say "simulation no problem" I mean "simulation no problem when using a decent simulator like modelsim, etc". Anything class flavored doesn't work in vivado as far as I can tell.

I do know of at least one synthesis tool that supports it.
Which one would that be?

As for packed versus unpacked structs ... is there any difference in using packed/unpacked for synthesis?

Since you are using packed structs, it might be better to pass simple vectors through the ports and assign them locally to struct variables inside your modules.
Isn't that just adding an extra layer of potential mistakes? Or put differently, what is the advantage of doing so?
 

DC. See https://www.sutherland-hdl.com/papers/2013-SNUG-SV_Synthesizable-SystemVerilog_paper.pdf

Regarding unpacked structs, their key benefit is strong type safety when writing your code. As you should know in Verilog, you can make assignment from any size packed bit vector to any other size packed bit vector and the tool will happily truncate or pad the vector to make it fit. Maybe if you're lucky the tool will give you a size mismatch warning when making a port connection.

But take the case of two packed structs with the same overall size, but completely different field layouts. SystemVerilog will gladly let you assign one to the other without any warning. The only advantage of packed structs is that it gives you symbolic names for bit-vector slices.
 

On page 14 it does indeed say that "Parameterized data types are synthesizable". That would seem to imply that at least DC and synplify both support it, since he's comparing these two later on.

And while going over that DC versus Synplify-Pro list, not supporting "Package import before module port list" seems a bit odd. At least vivado does support that. Keeps the `include statements to a minimum, which is nice IMO.

Sometimes I really really wish all those vendors would actually implement the same thing. Cramming the LRM in your head is enough of a challenge already. No need to compound it further with exception this, lemma that.

But take the case of two packed structs with the same overall size, but completely different field layouts. SystemVerilog will gladly let you assign one to the other without any warning. The only advantage of packed structs is that it gives you symbolic names for bit-vector slices.

Good point. Well then the question almost becomes "why ever use packed structs?". Faster simulation? Well, and aggregate comparison operator as mentioned in your paper. There is something to be said for being able to do that. But this combination is actually a bit annoying. Strong typing ... I am all in favor! So then you'd chose unpacked structs for both simulation and synthesis. But then for structs with a sufficiently large member count the aggregate comparison is real handy. That way you can compare two instances of a struct with 1 simple compare line instead of checking every member.

You could of could take the two unpacked structs of same type, cast them to packed and then compare those. But that's a bit ... well it's a bit something. I'm not sure yet what it is. That would not be too bad either if it was a class, because then you'd just override the default comparison operator or maybe declare a new one with an obvious name. But since classes for synthesis support is rather flaky that's no real option either.

Guess it's time for more playing around with vivado to see what language features actually work.
 

Status
Not open for further replies.

Part and Inventory Search

Welcome to EDABoard.com

Sponsor

Back
Top