Showing results for 
Show  only  | Search instead for 
Did you mean: 
Registered: ‎06-23-2014

SystemVerilog typedef struct confusion

I'm using Vivado 2017.1.


I have over 40 years programming experience, including over 25 years of object oriented programming experience.  Also, the parallel programming of FPGA's comes easy to me.


But I'm not understanding something here.  I'm getting strange behavior.  Just now, my strange behavior began to give me a clue.  I think it has to do with what's a register, what's not, and perhaps even a little misbehavior on how the SIMULATOR does things.


So perhaps you can clarify for me.




typedef struct packed {
    logic control_flag; // should be 1
    logic [6:0] control_data;
} t_control_word; 

typedef struct packed {
    logic control_flag; // should be 0
logic [6:0] data;
} t_data_word;

typedef union packed {
t_control_word control_word;
t_data_word data_word;
} t_fifo_word;

interface i_fifo_din;
t_fifo_word data;
logic wr_en;
logic full;
end interface;

interface i_fifo_dout;
t_fifo_word data;
logic rd_en;
logic empty;
end interface;

// The above appears in a separate .sv, like a header file, and is used in multiple .sv code files.
// The interfaces are passed as parameters between modules
// Reducing that complexity out, below is an example of my issue

i_fifo_din fifo_din(); // Instance the interface.
i_fifo_dout fifo_dout(); // Instance the interface

// I'm leaving out the actual fifo instantiation from this example

t_data_word temporary_dw;

always @(posedge clk) begin
fifo_din.wr_en <= 1; // This works in sim as expected.
if (first case) begin <= 1; // This does NOT work as expected
end else if (second case) begin <= 12; // This works as expected, and waveform shows the decimal 12 spread across the original struct bits
end else if (third case) begin
temporary_dw.control_flag <= 1; // This WORKS as expected <= dw; // This WORKS as expected (which, by the way, is one clock cycle delayed from value given dw just above)

My problem is that the "first case" doesn't work, ever, not a clock cycle delayed or anything.  The second case proves I can assign to the reduced level hierarchical name.  The third case proves I can assign to a temporary word with the final level of the structure hiearchy, and then I can later assign that to the reduced level hierarchical name (a clock cycle later, of course).


I'm wondering if my problem is a lack of understanding of "logic" versus values that get stored in some register somewhere upon the next rising clock edge.  I would think that the "Instance the Interface" lines of code had the same EFFECT as would "reg myvariable".  That is, creating a thing that can save a value across clocks, as opposed to simply a wire or net.  But "logic" is flexible, I believe, in that where that effective "reg" is can depend on how the logic variables are used.  I'm wondering if I'm getting some wierd situation where there is NOT any effective "reg", just "wires/nets to nowhere" in some cases.


Note I believe need to use "logic" because I use the same interface in both the caller (instantiator) and callee (module instantiated).  I can't use "reg" or anything else.  And I don't think I'm supposed to add "reg" to any of the structure typedef names upon instantiation, as one might do "output reg mybit".


I must be missing something in my understanding, and if I knew it, life would make a lot more sense...


Sorry I can't write this post more concisely or more clearly!


Thanks in advance for your help.




0 Kudos
4 Replies
Registered: ‎01-23-2009

Since you don't tell us what "first case" and "second case" are, we can't figure out if there is a flow issue in your code...


We also aren't clear what your code after the "// Im leaving out the actual FIFO..." - Is this supposed to be RTL code for synthesis, or is this part of a testbench for testing your FIFO?


But, this looks like the classic "flip-flop" vs. reg/logic misconception...


Verilog (and SystemVerilog) are Hardware Description Languages - yes, they are used primarily to describe hardware, but they are still nothing more than (and nothing less than) programming languages. The syntax of wire/logic/reg are constructs of the language.


The common confusion is that there is a one-to-one relationship between one or more of these language constructs and things in hardware - specifically what becomes a net in the hardware and what becomes a flip-flop. The determination of these, though, is not (directly) related to the language construct used, but how the language construct is used.


Specifically, to generate a flip-flop

  - you must be in an always @(posedge clk) procedural block, or always_ff @(posedge clk) procedural block

  - you must make a procedural assignment to an HDL construct that can take a procedural assignment

     - in Verilog this is the "reg", in SystemVerilog this is the "reg" or the "logic"

  - the reg/logic that was given the value must be used either

     - anywhere in the design if the assignment is a non-blocking assignment

        - for flip-flop inference you should always use non-blocking assignments

     - OR anywhere outside of the same branch of the same procedural block when using a blocking assignment

        - (I am just putting this here to recognize that there is a legal syntax for a "temporary variable" that uses blocking assignments that don't infer flip-flops


Conversely, the a reg/logic may end up inferring combinatorial logic and nets (i.e. no flip-flops) when the above are not true:

  - assigned in a continuous assign (legal for a logic, not a reg)

  - assigned in a procedural assignment in an always @(*)  (or always_comb)

  - the above mentioned temporary variable blocking assignment in an always @(posedge clk)


So, it is not the interface, nor the reg/logic declaration that creates flip-flops in your design, but the fact that you are performing non-blocking assignments to them in your always @(posedge clk) statement.



0 Kudos
Registered: ‎06-23-2014

I may have just figured out an answer,

but I'm still GREATLY INTERESTED in the insight of others on this problem.


In essence, instancing the interface did NOT instantiate a structure of storage areas that could save the values like a reg across clocks.  To do that, I needed to instantiate the structure and assign the structure inside the interface to it.  That is, I added code like this:

t_fifoword fifo_din_DATA; // This actually creates the "reg" equivalent
assign = fifo_din_data; // And this connects the interface net up to the "reg" above.
fifo_din_DATA.control_word.control_flag <= 1; // Note I'm assigning the "reg" equivalent, not the net in the interface.

To try and express in a different way, simply instantiating the interface does create a network, but there are no sources on the network.  I want there to be a memory storage element (aka "reg") that is a source.  By declaring an actual instantiation of the structure, and then connecting to this structure my prior free-floating net, my net now has a source.


In general, I've known I always need to "instantiate the interface" with something like "i_fifo_din fifo_din();"  In the MONTHS I've been working on this project with simulations, I must have almost always *also* instantiated a reg/source.  But this time I didn't. Perhaps I need to add to my coding style to always do something like this:

i_myinterface myinterface();
t_mystruct mystruct;
assign myinterface.mystruct = mystruct;

Of course, this is where myinterface is defined to have mystruct as a member.  I'll soon enough find out if this causes trouble in those other cases where I *do* tend to create a reg/source somewhere.


0 Kudos
Registered: ‎06-23-2014

Avrum, our posts crossed each other in time.  I'm reading yours more carefully right now...

0 Kudos
Registered: ‎06-23-2014



OK, so let's see if I understand you...


First of all, there wasn't a flow issue in my code, I'm certain.  Also, the fifo instantiation was a module instantiation for a fifo created by the FIFO generator.  This shouldn't have been germane, either.  So the germane stuff should have been provided.  Now I'll try to parse out the meaning of what you wrote...


Your bottom line, "it is not the interface, nor the reg/logic declaration that creates flip-flops in your design", seems to be the most important.  That's not how I was viewing it.  You continue with "but the fact that you are performing non-blocking assignments to them in your always @(posedge clk) statement."  That *IS* consistent with how I was viewing things.


I end up re-reading your post from the bottom up.  So I think I understand your last paragraph.  But, I don't fully follow your "Conversely" paragraph.  I'm confused by your writing "above is not true:" and then following it with a list.  I think you mean that the list entries are cases where the early "Specifically" paragraph wasn't the case.


So I'll jump up to your "Specifically" paragraph.

- inside "always @(posedge clk)".  Yep.  I already knew that.

- in my case make a procedural assignment to a "logic".  Yep.  I already knew that.

- the "logic" needs to be anywhere in the design for a non-blocking assignment.  ****This statement seems to have no real meaning, so I must not be understanding the statement****


Well, perhaps you could help by clarifying why the method I used in my SECOND post on this thread worked.  


I greatly appreciate it...

0 Kudos