• Home   /  
  • Archive by category "1"

Systemverilog Struct Assignment Submission

SystemVerilog struct (ure) and union are very similar to their C programming counterparts, so you may already have a good idea of how they work. But have you tried using them in your RTL design? When used effectively, they can simplify your code and save a lot of typing. Recently, I tried incorporating SystemVerilog struct and union in new ways that I had not done before with surprisingly (or not surprisingly?) good effect. In this post I would like to share with you some tips on how you can also use them in your RTL design.

What is a SystemVerilog Struct (ure)?

A SystemVerilog struct is a way to group several data types. The entire group can be referenced as a whole, or the individual data type can be referenced by name. It is handy in RTL coding when you have a collection of signals you need to pass around the design together, but want to retain the readability and accessibility of each separate signal.

When used in RTL code, a packed SystemVerilog struct is the most useful. A packed struct is treated as a single vector, and each data type in the structure is represented as a bit field. The entire structure is then packed together in memory without gaps. Only packed data types and integer data types are allowed in a packed struct. Because it is defined as a vector, the entire structure can also be used as a whole with arithmetic and logical operators.

An unpacked SystemVerilog struct, on the other hand, does not define a packing of the data types. It is tool-dependent how the structure is packed in memory. Unpacked struct probably will not synthesize by your synthesis tool, so I would avoid it in RTL code. It is, however, the default mode of a structure if the packed keyword is not used when defining the structure.

SystemVerilog struct is often defined with the typedef keyword to give the structure type a name so it can be more easily reused across multiple files. Here is an example:

What is a SystemVerilog Union?

A SystemVerilog union allows a single piece of storage to be represented different ways using different named member types. Because there is only a single storage, only one of the data types can be used at a time. Unions can also be packed and unpacked similarly to structures. Only packed data types and integer data types can be used in packed union. All members of a packed (and untagged, which I’ll get to later) union must be the same size. Like packed structures, packed union can be used as a whole with arithmetic and logical operators, and bit fields can be extracted like from a packed array.

A tagged union is a type-checked union. That means you can no longer write to the union using one member type, and read it back using another. Tagged union enforces type checking by inserting additional bits into the union to store how the union was initially accessed. Due to the added bits, and inability to freely refer to the same storage using different union members, I think this makes it less useful in RTL coding.

Take a look at the following example, where I expand the earlier SystemVerilog struct into a union to provide a different way to access that same piece of data.

Ways to Use SystemVerilog Struct in a Design

There are many ways to incorporate SystemVerilog struct into your RTL code. Here are some common usages.

Encapsulate Fields of a Complex Type

One of the simplest uses of a structure is to encapsulate signals that are commonly used together into a single unit that can be passed around the design more easily, like the opcode structure example above. It both simplifies the RTL code and makes it more readable. Simulators like Synopsys VCS will display the fields of a structure separately on a waveform, making the structure easily readable.

If you need to use the same structure in multiple modules, a tip is to put the definition of the structure (defined using typedef) into a SystemVerilog package, then import the package into each RTL module that requires the definition. This way you will only need to define the structure once.

SystemVerilog Struct as a Module Port

A module port can have a SystemVerilog struct type, which makes it easy to pass the same bundle of signals into and out of multiple modules, and keep the same encapsulation throughout a design. For example a wide command bus between two modules with multiple fields can be grouped into a structure to simplify the RTL code, and to avoid having to manually decode the bits of the command bus when viewing it on a waveform (a major frustration!).

Using SystemVerilog Struct with Parameterized Data Type

A structure can be used effectively with modules that support parameterized data type. For example if a FIFO module supports parameterized data type, the entire structure can be passed into the FIFO with no further modification to the FIFO code.

Ways to Use SystemVerilog Union in a Design

Until very recently, I had not found a useful way to use a SystemVerilog union in RTL code. But I finally did in my last project! The best way to think about a SystemVerilog union is that it can give you alternative views of a common data structure. The packed union opcode example above has a “fields view” and a “dword view”, which can be referred to in different parts of a design depending on which is more convenient. For example, if the opcode needs to be buffered in a 64-bit buffer comprised of two 32-bit wide memories, then you can assign one dword from the “dword view” as the input to each memory, like this:

In my last project, I used a union this way to store a wide SystemVerilog struct into multiple 39-bit memories in parallel (32-bit data plus 7-bit SECDED encoding). The memories were divided this way such that each 32-bit dword can be individually protected by SECDED encoding so it is individually accessible by a CPU. I used a “dword view” of the union in a generate loop to feed the data into the SECDED encoders and memories. It eliminated alot of copying and pasting, and made the code much more concise!

Conclusion

SystemVerilog struct and union are handy constructs that can encapsulate data types and simplify your RTL code. They are most effective when the structure or union types can be used throughout a design, including as module ports, and with modules that support parameterized data types.

Do you have another novel way of using SystemVerilog struct and union? Leave a comment below!

References

Download Article Companion Source Code

Get the FREE, EXECUTABLE test bench and source code for this article, notification of new articles, and more!

.

Related

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

33

34

35

36

37

38

typedefenumlogic[15:0]

{

  ADD=16'h0000,

  SUB = 16'h0001

}my_opcode_t;

 

typedefenumlogic[15:0]

{

  REG=16'h0000,

  MEM = 16'h0001

}my_dest_t;

 

typedefstructpacked

{

  my_opcode_t  opcode;// 16-bit opcode, enumerated type

  my_dest_t    dest;// 16-bit destination, enumerated type

  logic[15:0]opA;

  logic[15:0]opB;

}my_opcode_struct_t;

 

my_opcode_struct_t cmd1;

 

initial begin

  // Access fields by name

  cmd1.opcode<=ADD;

  cmd1.dest<=REG;

  cmd1.opA<=16'h0001;

  cmd1.opB <= 16'h0002;

 

  // Access fields by bit position

  cmd1[63:48]<=16'h0000

  cmd1[47:32] <= 16'h0000;

  cmd1[31:16]<=16'h0003;

  cmd1[15: 0] <= 16'h0004;

 

  // Assign fields at once

  cmd1<='{SUB, REG, 16'h0005,16'h0006};

end

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

typedefunionpacked

{

  my_opcode_struct_t opcode_s;"fields view"tothe struct

  logic[1:0][31:0]dword;// "dword view" to the struct

}my_opcode_union_t;

 

my_opcode_union_t cmd2;

 

initial begin

  // Access opcode_s struct fields within the union

  cmd2.opcode_s.opcode=ADD;

  cmd2.opcode_s.dest=REG;

  cmd2.opcode_s.opA=16'h0001;

  cmd2.opcode_s.opB = 16'h0002;

 

  // Access dwords struct fields within the union

  cmd2.dword[1]=32'h0001_0001; // opcode=SUB, dest=MEM

  cmd2.dword[0] = 32'h0007_0008;// opA=7, opB=8

end

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

module simple_fifo

(

  parameter type DTYPE=logic[7:0],

  parameter      DEPTH=4

)

(

  input  logic                      clk,

  input  logic                      rst_n,

  input  logic                      push,

  input  logic                      pop,

  input  DTYPE                      data_in,

  output logic[$clog2(DEPTH+1)-1:0]count,

  output DTYPE                      data_out

);

  // rest of FIFO design

endmodule

 

module testbench;

  parameter MY_DEPTH=4;

 

  logic clk,rst_n,push,pop,full,empty;

  logic[$clog2(MY_DEPTH+1)-1:0]count;

  my_opcode_struct_t data_in,data_out;

 

  simple_fifo

  #(

    .DTYPE(my_opcode_struct_t),

    .DEPTH(MY_DEPTH)

  )

  my_simple_fifo(.*);

endmodule

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

my_opcode_union_t my_opcode_in,my_opcode_out;

 

// Toy code to assign some values into the union

always_comb begin

  my_opcode_in.opcode_s.opcode=ADD;

  my_opcode_in.opcode_s.dest   =REG;

  my_opcode_in.opcode_s.opA    =16'h0001;

  my_opcode_in.opcode_s.opB    = 16'h0002;

end

 

// Use the "dword view" of the union in a generate loop

generate

  genvar gi;

  for(gi=0;gi<2;gi=gi+1)begin:gen_mem

    // instantiate a 32-bit memory

    mem_32 u_mem

    (

      .D(my_opcode_in.dword[gi]),

      .Q(my_opcode_out.dword[gi]),

      .*

    );

  end// gen_mem

endgenerate

Jason Yu

SoC Design Engineer at Intel Corporation

Jason has 10 years' experience in the semiconductor industry, designing and verifying Solid State Drive controller SoC. His areas of workinclude microarchitecture and RTL design, dynamic and formal verification using UVM and Cadence JasperGold, and full-chip low power verification with UPF. Thoughts and opinions expressed in articles are personal and do not reflect that of Intel Corporation in any way.

Latest posts by Jason Yu (see all)

Categories Design, SystemVerilogTags FIFO, struct, union

I am working with a complex design using abstract data types and it is going well, for the most part.  I am using valid SV code for pattern assignment is resulting in an error with Vivado 2017.4.

 

[Synth 8-359] invalid assignment pattern ["E:/dev/projects/chiplib/cores/dport_tx/verilog/dptx_framing.sv":661]

 

If I define a struct as:

 

typedefstruct { logic a; logic [7:0] b; } tSTRUCT_TYPE;

 

And define an input and signals as:

input tSTRUCT_TYPE struct_in [3:0]
tSTRUCT_TYPE struct_use [3:0]
tSTRUCT_TYPE temp_0, temp_1, temp_2, temp_3

Then attempt a pattern assignment:

struct_use <= '{ struct_in[3], struct_in[2], struct_in[1], struct_in[0] };

Which results in an error.  The odd part is that if I remove the SV list assignment and make it a Verilog concatenation (remove the leading tick, that is), the code passes through okay.  A concatenation of unpacked structs doesn't seem right and I'm not confident that will get synthesized properly.  Please don't suggest a direct assignment, the actual code is more complicated than this example.

 

By using an intermediate assignment of the struct to a temp, this now passes through okay.

// workaround for Vivado temp_0 = struct_in[0]; temp_1 = struct_in[1];
temp_2 = struct_in[2];
temp_3 = struct_in[3];

struct_use <= '{ temp_3, temp_2, temp_1, temp_0 };

The above solution resolves the error but is not really feasible in this design given the number of places and how this is used.  We would need hundreds of new signals in the design.  In addition to the fact that hacking code is annoying and makes it less maintainable and more error prone.

 

We've tried most of the obvious modifications.  Any help would be appreciated.

 

One thought on “Systemverilog Struct Assignment Submission

Leave a comment

L'indirizzo email non verrĂ  pubblicato. I campi obbligatori sono contrassegnati *