Design Archives - Verilog Pro https://www.verilogpro.com/category/design/ Verilog and Systemverilog Resources for Design and Verification Thu, 29 Sep 2022 08:01:22 +0000 en-US hourly 1 https://wordpress.org/?v=6.4.4 98068679 Verilog Module for Design and Testbench https://www.verilogpro.com/verilog-module-for-design-and-testbench/ https://www.verilogpro.com/verilog-module-for-design-and-testbench/#comments Sun, 19 Jun 2022 07:48:07 +0000 https://www.verilogpro.com/?p=784 A Verilog module is a building block that defines a design or testbench component, by defining the building block’s ports and internal behaviour. Higher-level modules can embed lower-level modules to create hierarchical designs. Different Verilog modules communicate with each other through Verilog port. Together, the many Verilog modules communicate and model dataflow of a larger, ... Read more

The post Verilog Module for Design and Testbench appeared first on Verilog Pro.

]]>
A Verilog module is a building block that defines a design or testbench component, by defining the building block’s ports and internal behaviour. Higher-level modules can embed lower-level modules to create hierarchical designs. Different Verilog modules communicate with each other through Verilog port. Together, the many Verilog modules communicate and model dataflow of a larger, hierarchical design.

Verilog has a simple organization. All data, functions, and tasks are in modules, except for system tasks and functions, which are global. Any uninstantiated module is at the top level. A model must contain at least one top-level module.

Defining a Verilog Module

A Verilog module is enclosed between the keywords module and endmodule. It has the following components:

  • Keyword module to begin the definition
  • Identifier that is the name of the module
  • Optional list of parameters
  • Optional list of ports (to be addressed more deeply in a future article)
  • Module item
  • Keyword endmodule to end the definition

Let’s address each of these components one by one.

Verilog Parameter

Verilog parameters were introduced in Verilog-2001 (not present in the original Verilog-1995). They allow a single piece of Verilog module code to be more extensible and reusable. Each instantiation of a Verilog module can supply different values to the parameters, creating different variations of the same base Verilog module. For example, a FIFO Verilog module may have a Verilog parameter to adjust its data width (or even data type, in SystemVerilog). They are not strictly necessary for a design, so I will defer discussing the topic further to a future article.

Verilog Port

A Verilog module only optionally needs to have a list of Verilog port (a port list). For example, a top level testbench may not have any Verilog ports at all. Verilog ports allow different modules of a design to communicate with each other. There are other (more backdoor) ways that Verilog modules can communicate. But for a design that intends to be synthesized, Verilog ports is the standard method. There are many ways to code port connections. This will be discussed in more detail in another future article.

Module Item

Module item is essentially the “code that is inside the module” (after the port declaration). It defines what constitutes the module, and can include many different types of declarations and definitions (net and variable declarations, always blocks and initial blocks, etc.)

Putting it Together

Here is a very simple example of Verilog module definition, that puts together all the pieces above.

module my_module
#(
    parameter WIDTH = 1
) (
    input wire              clk,
    input wire              rst_n,
    input wire [WIDTH-1:0]  in_a, in_b,
    output reg [WIDTH-1:0]  out_c
);

always @(posedge clk or negedge rst_n)
    out_c <= in_a & in_b;

endmodule

Instantiating a Verilog Module

A Verilog module can instantiate other Verilog modules, creating a hierarchy of modules to form the full design and testbench. Any uninstantiated module is at the top level.

Instantiation Statement

The Verilog module instantiation statement creates one or more named instances of a defined module. Multiple instances (identical copies of the Verilog module) can be created on the same line of code. This type of coding style is obviously easier with simple modules that have few (or none) ports. Multiple instantiations can even contain a range specification. This allows an array of instances to be created.

Connecting the Ports

For a Verilog module that does have ports, Verilog defines two styles to connect the ports:

  • By position – specify the connection to each port in the same order as the ports were listed in the module declaration
  • By name – specify each port by the name used in the (sub) module declaration, followed by the name used in the instantiating module

When connecting by name, an unconnected port can be indicated by either omitting it from the port list, or by providing no expression in the parentheses ( .name () ). The two types of port connections shall not be mixed (in Verilog) in a single declaration.

For a Verilog module that does not have any port, you still need to write the parentheses when instantiating it.

As to what to connect to the port, from Verilog, it can be a register or net identifier, an expression, or a blank (to indicate no connection that that port). An unconnected port may also be simply omitted from the port list, but only when connecting by name.

Here are some examples to illustrate the concepts above.

wire clk, rst_n;
wire a, b, c1, c2, c3, c4, d;

// Instantiating a module and connecting ports by position
my_module mod_b (clk, rst_n, a, b, c1);

// Instantiating a module and connecting ports by name
my_module mod_a
(
    .clk   (clk),
    .rst_n (rst_n),
    .in_a  (a),
    .in_b  (b),
    .out_c (c2)
);

// Instantiating a module, but leaving a port unconnected (a bug in this case!)
my_module mod_c
(
    .clk   (), // A bug! But this will compile
    .rst_n (rst_n),
    .in_a  (a),
    .in_b  (b),
    .out_c (c3)
);

// Instantiating a module with no ports
my_module_with_no_ports mod_d();

// Connecting an expression to a port
my_module mod_e
(
    .clk   (clk),
    .rst_n (rst_n),
    .in_a  (a & d), // an expression as a port connection
    .in_b  (b),
    .out_c (c4)
);

Verilog Module Hierarchy

When instantiating and connecting Verilog modules and ports, a hierarchical design is created. Every identifier (for example every module) has a unique hierarchical path name. This is useful generally in testbench coding, where you sometimes need to reference a particular signal, in a somewhat backdoor way, in a different module within your testbench. This is generally not used in design coding, where you always want to more formally use Verilog ports to make explicit connections and model dataflow (with exception possibly with Verilog defparam—topic for a future article).

The complete hierarchy of names can be viewed as a tree structure, with the root being the top level module. Each module, generate block instance, task, function, named begin-end or fork-join block defines a new hierarchical level (also called a scope), in a particular branch of the tree. A design description contains one or more top-level modules. Each such module forms the root of a name hierarchy. The following figure shows an example Verilog module hierarchy.

Verilog module hierarchical name example
Verilog module hierarchical name example

Verilog generate block, that do not have a name label, creates a hierarchy that is only visible within the block, and within the sub-tree formed by this block—and nowhere else. Therefore it’s good practice to always name Verilog generate blocks so all identifiers can be referenced throughout your environment. See my article Verilog Generate Configurable RTL Designs.

Now that we have defined a hierarchy, we can reference any named Verilog object or hierarchical name reference, by concatenating the names of the modules, module instance names, generate blocks, tasks, functions, or named blocks that contain it. Each of the names in the hierarchy is separated by a period.

You can reference the complete path name, starting from the top-level (root) module, or you can reference “downwards”, starting from the level where the path is being used. You can also reference a particular instance of an array (or a generate loop) by a constant expression within square brackets. The expression shall evaluate to one of the legal index values of the array.

The following code matches the example design hierarchy above. We’ll use it to illustrate some examples of referencing.

// A trivial AND module
module my_and
(
  input wire in1,
  input wire in2,
  output wire out
);
  assign out = in1 & in2;

endmodule

// A design that instantiates the AND module
module dut
(
  input wire clk,
  input wire rst_n,
  input wire [3:0] a, b,
  output reg [3:0] c
);
  my_and i_my_and_3 (a[3], b[3], c[3]);

  generate
    genvar gi;
    for (gi=0; gi<3; gi=gi+1) begin : gen_and
      my_and i_my_and (a[gi], b[gi], c[gi]);
    end // gen_and
  endgenerate
endmodule

// Testbench that instantiates the design under test (DUT)
module testbench;
  wire clk, rst_n;
  wire [3:0] a, b, c;

  dut i_dut
  (
    .clk   (clk),
    .rst_n (rst_n),
    .a     (a),
    .b     (b),
    .c     (c)
  );

  // Some examples of hierarchical references
  initial begin
    $monitor(a); // reference to testbench hierarchy
    $monitor(i_dut.a); // reference to dut hierarchy
    $monitor(i_dut.i_my_and_3.out); // reference to dut sub-module hierarchy
    $monitor(i_dut.gen_and[0].i_my_and.out); // reference to generated sub-module
  end
endmodule

Some final notes on Verilog module hierarchy:

  • Each node in the hierarchical name tree creates a new scope for identifiers. Within each scope, the same identifier (e.g. net name, variable name) can be declared only once. Conversely, it is permitted to use the same identifier in different scopes, or different Verilog modules.
  • Objects declared in automatic tasks and functions are one exception that cannot be referenced by hierarchical names.
  • It is also possible, from Verilog LRM perspective, to reference “upwards” in the hierarchy. For example, it is possible for a lower level module to reference a variable in the module that instantiates the lower level module. However, the usage is tricky, and application is dubious (certainly not recommended for design code), so I will not go into it here.

Verilog Module Summary

Verilog module is one of the fundamental hierarchical constructs in Verilog. It encapsulates code and functionality, allowing a larger design to be built from lower level components, enhancing modularity and reuse. This article described the basic syntax of a Verilog module, how to define a module, how to connect multiple modules together, and how the interconnection creates a design hierarchy.

The next article in this fundamentals series will dive deeper into Verilog ports, exploring how the syntax evolved from the original Verilog to the latest SystemVerilog language manuals. Stay tuned!

References

The post Verilog Module for Design and Testbench appeared first on Verilog Pro.

]]>
https://www.verilogpro.com/verilog-module-for-design-and-testbench/feed/ 2 784
Verilog Always Block for RTL Modeling https://www.verilogpro.com/verilog-always-block/ https://www.verilogpro.com/verilog-always-block/#respond Wed, 13 Apr 2022 15:00:00 +0000 https://www.verilogpro.com/?p=723 Verilog always block is a procedural statement that starts an activity flow. It is essentially an infinite loop. However, when combined with a Verilog event expression, it can be used to model combinational and sequential logic.

The post Verilog Always Block for RTL Modeling appeared first on Verilog Pro.

]]>
This article is going to introduce the Verilog always block—one of the most basic constructs that has existed since the very beginning of Verilog (IEEE standard 1364-1995)—relate it to some other introductory constructs, and use them to write some simple hardware logic.

After a long hiatus, I’m picking up the proverbial pen again and writing some Verilog articles! I have a new goal to create a series of articles to help new engineers transition from “textbook knowledge” to real world knowledge needed to become a digital design engineer. You’ll see some new articles on basic concepts, as well as intermediate level concepts similar to my previous articles. Hope you find these new articles useful for your career! Let’s get started!

Verilog Always Block In a Nutshell

Verilog behaviour models (RTL design/model is a class of behavioural models) contain procedural statements that control the simulation, and manipulate variables to model hardware circuitry and data flow. The Verilog always block is a procedural statement that starts an activity flow. Each Verilog always block starts a separate activity flow. All of the activity flows are concurrent to model the inherent concurrence of hardware. Each Verilog always block repeats continuously throughout the duration of the simulation, executing the statements defined in its procedure. Its activity ceases only when the simulation is terminated.

An Infinite Loop?

The Verilog always block essentially describes an infinite loop. Without some form of timing control to avoid a zero-delay infinite loop, and allow simulation time to advance, a simulation deadlock condition can be created (read: a simulation hang). The following code, for example, creates such a zero-delay infinite loop.

always areg = ~areg;

If there is a Verilog always block with no timing control, your simulation will look as though it has hung, and will not advance in time. So let’s add a timing control to make the code more useful:

always #half_period clk = ~clk;

Now this becomes a potentially useful statement. It causes the clk signal to toggle its polarity at a delay of half_period. If you haven’t noticed already, it can be used to create a continuously toggling clock stimulus in a simulation.

The “#” is formally called a delay control. It causes the simulation to insert the specified delay (to that procedural block) where the “#” is written.

Timing Control with Event Expression (Sensitivity List)

Another way to provide a timing control is in the form of a Verilog event expression. The syntax of Verilog event expression is “@(event_expression)“. For the procedural block that contains the Verilog event expression, it causes the simulator to wait until the event_expression has occurred before continuing execution of that procedural block. When used together with a Verilog always block, it adds the necessary timing control to make the Verilog always block useful to model hardware. The SystemVerilog standards (IEEE 1800-2005 onwards) also describe the event expression, when used to trigger a Verilog always block, as a “sensitivity list”.

The event expression (or sensitivity list) can contain multiple signals. Each signal is separated by the keyword “or”. The following Verilog always block describes a simple OR gate with inputs A and B, and output C. This code tells the simulator to re-evaluate the value of output C, whenever the signal A or B changes. It is not entirely straightforward, but you can see how this essentially describes the behaviour of an OR gate in simulation.

always @(A or B)
    C = A | B;

Modelling Hardware Logic with Verilog Always Block

Combining these ideas brings us to the more common usage of Verilog always block—together with an event expression.

always @(event_expression)
    single_statement;

always @(event_expression)
begin
    multiple_statements;
end

For hardware modeling, the Verilog event expression is typically specified in one of two forms:

  • Sequential logic: execution triggered based on a clock event (and frequently a reset event)
  • Combinational logic: execution triggered based on the inputs to the logic (i.e. nets and variables on the right hand side of an assignment statement)

Modeling Sequential Logic

A typical event expression for a flip-flop based design is “@(posedge clock_name)”. This tells the simulator to only evaluate the Verilog always block when the clock_name signal makes a positive edge transition (0->1), which is how a flip-flop based design is constructed. Without the “posedge” keyword, the Verilog always block will trigger on any transition of the clock_name signal, both positive and negative.

The following code describes a flip-flop with input d and output q, clocked by positive edge of clk (with no reset):

always @(posedge clk)
    q <= d;

Let’s make it a more complete flip-flop design by also adding a reset, namely an asynchronous reset. An asynchronous reset means the reset will occur even without the presence of a clock. An asynchronous reset is modeled by adding the reset signal also to the sensitivity list. This will cause the simulator to re-evaluate this Verilog always block when the reset signal transitions, irrespective of whether the clk signal transitions (which is what an asynchronous reset means). The Verilog event expression “@(negedge rst_n)” makes the reset active low (trigger an evaluation when rst_n transitions 1->0), and the “if” statement specifies a reset value for the flip-flop. The following code describes the same flip-flop, now with an active-low asynchronous reset (rst_n) that will reset the flip-flop output q to 0 when the reset is asserted (when rst_n transitions 1->0).

always @(posedge clk or negedge rst_n)
    if (!rst_n)
        q <= 1'b0;
    else
        q <= d;

To describe two flip flops, you can write them as two separate Verilog always block. Let’s make it more interesting and put them in series into a two-stage pipeline. First the diagram, then the code.

Two stage pipelined D flip flops
Two stage pipelined D flip flops
always @(posedge clk or negedge rst_n)
    if (!rst_n)
        q1 <= 1'b0;
    else
        q1 <= d1;

always @(posedge clk or negedge rst_n)
    if (!rst_n)
        q2 <= 1'b0;
    else
        q2 <= q1;

Or you can also write it as one Verilog always block.

always @(posedge clk or negedge rst_n)
    if (!rst_n) begin
        q1 <= 1'b0;
        q2 <= 1'b0;
    end
    else begin
        q1 <= d1;
        q2 <= q1;
    end

When there are multiple Verilog always blocks, there is no implied order of execution between them. There is also no limit to the number of always constructs that can be defined in a module.

Modeling Combinational Logic

The Verilog always block can also model combinational logic, but it is a bit less straight forward to understand. A physical implementation of a combinational circuit obviously operates continuously, sampling the inputs and calculating the resulting outputs. A simulator, however, cannot execute a logical statement “continuously”, without causing the zero-delay infinite loop described at the beginning of the article. Therefore to simulate combinational circuit, we have to define specific events at which the simulator should execute procedures, while maintaining correct behaviour.

The simple answer is, to model combinational circuit, the sensitivity list needs to contain all the inputs to the circuit (all the variables on the right hand side of the assignment). The following code describes a combinational AND gate using a Verilog always block.

always @(A or B)
    C = A & B;

More on Event Expression with Verilog Always Block

Here are a few more tips with using Verilog event expression with Verilog always block.

Using Comma in Event Expression

Verilog-2001 standard introduced the use of comma “,” to separate items in the event expression. Prior to that with the original Verilog-1995 standard, the separate items in the event expression must be separated by the keyword “or”. I have used the Verilog-1995 syntax in all the code examples so far. But here is the same flip-flop code example written with the Verilog-2001 syntax.

always @(posedge clk, negedge rst_n)
    if (!rst_n)
        q <= 1'b0;
    else
        q <= d;

Personally I prefer the Verilog-2001 syntax. That is how I write my code.

Implicit Event Expression @* or @(*)

Sensitivity list is a frequent source of bugs in a Verilog design/model. A deeper discussion of common pitfalls will be the subject of a future article. The standard writers wanted to simplify the usage of the the sensitivity list somewhat in Verilog-2001, so they added the “implicit event_expression list” syntax to simplify using Verilog always block, at least to describe combinational logic. Using an implicit event expression list, the AND gate combinational logic can be rewritten as follows.

always @*
    C = A & B;

always @(*)
    C = A & B;

More precisely, the “@*” and “@(*)” syntax will add all nets and variables that appear in the (right hand side of a) statement to the event expression, with some exceptions.

Verilog Always Block, Evolution to SystemVerilog always_comb and always_ff

SystemVerilog adds several new syntax in addition to the Verilog always block, primarily to address the exceptions noted above. You can read more about these constructs in my article SystemVerilog always_comb, always_ff.

Conclusion

Verilog always block is one of the four procedural statements in the original Verilog language. It can be used to model testbench stimulus as well as hardware design. The Verilog always block is essentially an infinite loop. However, when combined with a Verilog event expression, it can be used to model combinational and sequential logic. SystemVerilog adds several new versions of “always”, in addition to Verilog always block, to address some limitations and pitfalls of the original Verilog syntax.

References

The post Verilog Always Block for RTL Modeling appeared first on Verilog Pro.

]]>
https://www.verilogpro.com/verilog-always-block/feed/ 0 723
Verilog Generate Configurable RTL Designs https://www.verilogpro.com/verilog-generate-configurable-rtl/ https://www.verilogpro.com/verilog-generate-configurable-rtl/#comments Thu, 04 Jan 2018 18:00:09 +0000 http://www.verilogpro.com/?p=641 Verilog generate statement is a powerful construct for writing configurable, synthesizable RTL. It can be used to create multiple instantiations of modules and code, or conditionally instantiate blocks of code. However, many Verilog programmers often have questions about how to use Verilog generate effectively. In this article, I will review the usage of three forms ... Read more

The post Verilog Generate Configurable RTL Designs appeared first on Verilog Pro.

]]>
Verilog generate statement is a powerful construct for writing configurable, synthesizable RTL. It can be used to create multiple instantiations of modules and code, or conditionally instantiate blocks of code. However, many Verilog programmers often have questions about how to use Verilog generate effectively. In this article, I will review the usage of three forms of Verilog generate—generate loop, if-generate, and case-generate.

Types of Verilog Generate Constructs

There are two kinds of Verilog generate constructs. Generate loop constructs allow a block of code to be instantiated multiple times, controlled by a variable index. Conditional generate constructs select at most one block of code between multiple blocks. Conditional generate constructs include if-generate and case-generate forms.

Verilog generate constructs are evaluated at elaboration, which occurs after parsing the HDL (and preprocessor), but before simulation begins. Therefore all expressions within generate constructs must be constant expressions, deterministic at elaboration time. For example, generate constructs can be affected by values from parameters, but not by dynamic variables.

A Verilog generate block creates a new scope and a new level of hierarchy, almost like instantiating a module. This sometimes causes confusion when trying to write a hierarchical reference to signals or modules within a generate block, so it is something to keep in mind.

Use of the keywords generate and endgenerate (and begin/end) is actually optional. If they are used, then they define a generate region. Generate regions can only occur directly within a module, and they cannot nest. For readability, I like to use the generate and endgenerate keywords.

Verilog Generate Loop

The syntax for a generate loop is similar to that of a for loop statement. The loop index variable must first be declared in a genvar declaration before it can be used. The genvar is used as an integer to evaluate the generate loop during elaboration. The genvar declaration can be inside or outside the generate region, and the same loop index variable can be used in multiple generate loops, as long as the loops don’t nest.

Within each instance of the “unrolled” generate loop, an implicit localparam is created with the same name and type as the loop index variable. Its value is the “index” of the particular instance of the “unrolled” loop. This localparam can be referenced from RTL to control the generated code, and even referenced by a hierarchical reference.

Generate block in a Verilog generate loop can be named or unnamed. If it is named, then an array of generate block instances is created. Some tools warn you about unnamed generate loops, so it is good practice to always name them.

The following example shows a gray to binary code converter written using a Verilog generate loop.

Example of parameterized gray to binary code converter

module gray2bin
#(parameter SIZE = 8)
(
  input [SIZE-1:0] gray,
  output [SIZE-1:0] bin
)

Genvar gi;
// generate and endgenerate is optional
// generate (optional)
  for (gi=0; gi&lt;SIZE; gi=gi+1) begin : genbit
    assign bin[gi] = ^gray[SIZE-1:gi]; // Thanks Dhruvkumar!
  end
// endgenerate (optional)
endmodule

Another example from the Verilog-2005 LRM illustrates how each iteration of the Verilog generate loop creates a new scope. Notice wire t1, t2, t3 are declared within the generate loop. Each loop iteration creates a new t1, t2, t3 that do not conflict, and they are used to wire one generated instance of the adder to the next. Also note the naming of the hierarchical reference to reference an instance within the generate loop.

module addergen1
#(parameter SIZE = 4)
(
  input  logic [SIZE-1:0] a, b,
  input  logic            ci,
  output logic            co,
  output logic [SIZE-1:0] sum
);

wire [SIZE :0] c;
genvar i;

assign c[0] = ci;

// Hierarchical gate instance names are:
// xor gates: bitnum[0].g1 bitnum[1].g1 bitnum[2].g1 bitnum[3].g1
// bitnum[0].g2 bitnum[1].g2 bitnum[2].g2 bitnum[3].g2
// and gates: bitnum[0].g3 bitnum[1].g3 bitnum[2].g3 bitnum[3].g3
// bitnum[0].g4 bitnum[1].g4 bitnum[2].g4 bitnum[3].g4
// or gates: bitnum[0].g5 bitnum[1].g5 bitnum[2].g5 bitnum[3].g5
// Gate instances are connected with nets named:
// bitnum[0].t1 bitnum[1].t1 bitnum[2].t1 bitnum[3].t1
// bitnum[0].t2 bitnum[1].t2 bitnum[2].t2 bitnum[3].t2
// bitnum[0].t3 bitnum[1].t3 bitnum[2].t3 bitnum[3].t3

for(i=0; i&lt;SIZE; i=i+1) begin:bitnum
  wire t1, t2, t3;
  xor g1 ( t1, a[i], b[i]);
  xor g2 ( sum[i], t1, c[i]);
  and g3 ( t2, a[i], b[i]);
  and g4 ( t3, t1, c[i]);
  or g5 ( c[i+1], t2, t3);
end

assign co = c[SIZE];

endmodule

Generate loops can also nest. Only a single generate/endgenerate is needed (or none, since it’s optional) to encompass the nested generate loops. Remember each generate loop creates a new scope. Therefore the hierarchical reference to the inner loop needs to include the label of the outer loop.

Conditional If-Generate

Conditional if-generate selects at most one generate block from a set of alternative generate blocks. Note I say at most, because it may also select none of the blocks. The condition must again be a constant expression during elaboration.

Conditional if-generate may be named or unnamed, and may or may not have begin/end. Either way, it can contain only one item. It also creates a separate scope and level of hierarchy, like a generate loop. Since conditional generate selects at most one block of code, it is legal to name the alternative blocks of code within the single if-generate with the same name. That helps to keep hierarchical reference to the code common regardless of which block of code is selected. Different generate constructs, however, must have different names.

Conditional Case-Generate

Similar to if-generate, case-generate can also be used to conditionally select one block of code from several blocks. Its usage is similar to the basic case statement, and all rules from if-generate also apply to case-generate.

Direct Nesting of Conditional Generate

There is a special case where nested conditional generate blocks that are not surrounded by begin/end can consolidate into a single scope/hierarchy. This avoids creating unnecessary scope/hierarchy within the module to complicate the hierarchical reference. This special case does not apply at all to loop generate.

The example below shows how this special rule can be used to construct complex if-else if conditional generate statements that belong to the same hierarchy.

module test;
  parameter p = 0, q = 0;
  wire a, b, c;

  //---------------------------------------------------------
  // Code to either generate a u1.g1 instance or no instance.
  // The u1.g1 instance of one of the following gates:
  // (and, or, xor, xnor) is generated if
  // {p,q} == {1,0}, {1,2}, {2,0}, {2,1}, {2,2}, {2, default}
  //---------------------------------------------------------

  if (p == 1)
    if (q == 0) begin : u1 // If p==1 and q==0, then instantiate
      and g1(a, b, c); // AND with hierarchical name test.u1.g1
    end
    else if (q == 2) begin : u1 // If p==1 and q==2, then instantiate
      or g1(a, b, c); // OR with hierarchical name test.u1.g1
    end
    // "else" added to end "if (q == 2)" statement
    else ; // If p==1 and q!=0 or 2, then no instantiation
  else if (p == 2)
    case (q)
      0, 1, 2:
        begin : u1 // If p==2 and q==0,1, or 2, then instantiate
          xor g1(a, b, c); // XOR with hierarchical name test.u1.g1
        end
      default:
        begin : u1 // If p==2 and q!=0,1, or 2, then instantiate
          xnor g1(a, b, c); // XNOR with hierarchical name test.u1.g1
        end
    endcase

endmodule

This generate construct will select at most one of the generate blocks named u1. The hierarchical name of the gate instantiation in that block would be test.u1.g1. When nesting if-generate constructs, the else always belongs to the nearest if construct. Note the careful placement of begin/end within the code Any additional begin/end will violate the direct nesting requirements, and cause an additional hierarchy to be created.

Named vs Unnamed Generate Blocks

It is recommended to always name generate blocks to simplify hierarchical reference. Moreover, various tools often complain about anonymous generate blocks. However, if a generate block is unnamed, the LRM does describe a fixed rule for how tools shall name an anonymous generate block based on the text of the RTL code.

First, each generate construct in a scope is assigned a number, starting from 1 for the generate construct that appears first in the RTL code within that scope, and increases by 1 for each subsequent generate construct in that scope. The number is assigned to both named and unnamed generate constructs. All unnamed generate blocks will then be given the name genblk[n] where [n] is the number assigned to its enclosing generate construct.

It is apparent from the rule that RTL code changes will cause the unnamed generate construct name to change. That in turn makes it difficult to maintain hierarchical references in RTL and scripts. Therefore, it is recommended to always name generate blocks.

Conclusion

Verilog generate constructs are powerful ways to create configurable RTL that can have different behaviours depending on parameterization. Generate loop allows code to be instantiated multiple times, controlled by an index. Conditional generate, if-generate and case-generate, can conditionally instantiate code. The most important recommendation regarding generate constructs is to always name them, which helps simplify hierarchical references and code maintenance.

References

The post Verilog Generate Configurable RTL Designs appeared first on Verilog Pro.

]]>
https://www.verilogpro.com/verilog-generate-configurable-rtl/feed/ 17 641
SystemVerilog Struct and Union – for Designers too https://www.verilogpro.com/systemverilog-structures-unions-design/ https://www.verilogpro.com/systemverilog-structures-unions-design/#comments Tue, 17 Jan 2017 18:00:28 +0000 http://www.verilogpro.com/?p=455 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 ... Read more

The post SystemVerilog Struct and Union – for Designers too appeared first on Verilog Pro.

]]>
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:

typedef enum logic[15:0]
{
  ADD = 16'h0000,
  SUB = 16'h0001
} my_opcode_t;

typedef enum logic[15:0]
{
  REG = 16'h0000,
  MEM = 16'h0001
} my_dest_t;

typedef struct packed
{
  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

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.

typedef union packed
{
  my_opcode_struct_t opcode_s; // "fields view" to the 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

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.

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

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:

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

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

[lab_subscriber_download_form download_id=6].

The post SystemVerilog Struct and Union – for Designers too appeared first on Verilog Pro.

]]>
https://www.verilogpro.com/systemverilog-structures-unions-design/feed/ 4 455
Clock Domain Crossing Design – Part 3 https://www.verilogpro.com/clock-domain-crossing-design-part-3/ https://www.verilogpro.com/clock-domain-crossing-design-part-3/#comments Tue, 17 May 2016 17:00:20 +0000 http://www.verilogpro.com/?p=328 In Clock Domain Crossing (CDC) Design – Part 2, I discussed potential problems with passing multiple signals across a clock domain, and one effective and safe way to do so. That circuit, however, does hot handle the case when the destination side logic cannot accept data and needs to back-pressure the source side. The two ... Read more

The post Clock Domain Crossing Design – Part 3 appeared first on Verilog Pro.

]]>