/// LSU EE 4755 -- Fall 2024 -- Digital Design / HDL /// /// Basic Verilog Simulation /// Contents // Module Instantiation Hierarchy // Simulation Terminology // Basic Simulation /// References // :SV: IEEE 1800-2023 -- The SystemVerilog Standard // https://ieeexplore.ieee.org/document/10458102 // This is for those already familiar with Verilog. // // :BV3: Brown & Vranesic, Fundamentals of Digital Logic with Verilog, 3rd Ed. // The text used in LSU EE 2740. // This is for those who need to review basic Verilog. ////////////////////////////////////////////////////////////////////////////// /// Introduction // // SystemVerilog is primarily a simulation language. A Verilog // description consists of lots of pieces of code, called processes, // that execute in an order determined by the objects that they use // and by delay (eg, #3) and event (eg @(a or b)) constructs. // // These demo-notes describe the basics of how simulation works. // // A major frustration for some beginners is assuming that Verilog // executes like a procedural language (such as C++). In particular, // many wrongly assume that a module instantiation is the equivalent // of a procedure call. Not even close. Don't skip these notes. ////////////////////////////////////////////////////////////////////////////// /// Module Instantiation Tree // // :SV: Section 23.3 (Instantiation Tree) // :SV: Section 23.6 (Hierarchical Names) // // To understand simulation, one needs to clearly understand the // difference between a module and a module instance. One must also // identify the top-level module, one that is not instantiated in any // of the others. Those terms and related concepts are described here. // :Def: Top-Level Module // The module to simulate or synthesize. // The top-level module usually instantiates other modules. // // For simulation, the top-level module is usually the testbench. // Many simulators can guess which module is the top-level module. // (Cadence xrun by default simulates *all* top-level modules it // finds in a file, which can be confusing if you only intended to // simulate one of them.) // // For synthesis, the top-level module is the module that is to be // synthesized. In Cadence Genus it must be identified explicitly, // with the "elaborate" command. // :Def: Instantiation Tree [of a top-level module] // The module instances defined by a top-level module. These // instances form a tree with the top-level module as root. // // An instantiation tree is sometimes called a module hierarchy or // design hierarchy. // :Example: // // A multiply-add (mad) module and its testbench, try_mad. // // Module try_mad is the top-level module. The instantiation tree // consists of try_mad, m1, and m2. Module try_mad is the root of the // tree, it has two children, m1, and m2, which are both instances of // mad. // // A simulator might guess that try_mad is the top-level module // because no other module instantiates try_mad. module mad(output uwire [31:0] s, input uwire [31:0] a, b, c); uwire [31:0] p = a * b; assign s = p + c; endmodule module try_mad; logic [31:0] i1, i2, i3, i4, i5, ii; uwire [31:0] i0; mad m1( ii, i1, i2, i3 ); // mad instance m1. mad m2( i0, i4, i5, ii ); // mad instance m2. initial begin i1 = 1; i2 = 2; i3 = 3; i4 = 4; i5 = 5; #1 $write("try_mad, result of %0d * %0d + %0d + %0d * %0d = %0d\n", i1, i2, i3, i4, i5, i0); end endmodule // :Def: Hierarchical Name // A name that identifies an object or other item in an instantiation // tree. SystemVerilog defines an absolute hierarchical name (starting // at the top-level module) and a relative hierarchical name (starting // at the module in which the relative hierarchical name is used). // // Hierarchical names are typically used in testbench code to peek // inside of a module, perhaps to verify correctness or to help // debug. // // An absolute hierarchical name of some object consists of the names // of its ancestors starting with the module name of the top-level // module (try_mad2 in the example below), and then continuing with // the *instance* names of the remaining ancestors, and ending with // the name of the object. All names are separated by a ".". // // A relative hierarchical name is like an absolute name, but it // omits the name of the module it is used in and its ancestors. // // :Example: // // Relative and absolute hierarchical names are used in the $write // system tasks at the end of the module below. // module try_mad2; logic [31:0] i1, i2, i3, i4, i5, ii; uwire [31:0] i0; mad m1( ii, i1, i2, i3 ); // Hierarchical name: try_mad2.m1 mad m2( i0, i4, i5, ii ); // Hierarchical name: try_mad2.m2 initial begin i1 = 1; i2 = 2; i3 = 3; i4 = 4; i5 = 5; #1 $write("try_mad2, result of %0d * %0d + %0d + %0d * %0d = %0d\n", i1, i2, i3, i4, i5, i0); end always @* $write("try_mad2, new value of m1 p: %0d or m2 p: %0d\n", m1.p, m2.p); // <- Relative always @* $write("try_mad2, new value of m1 s: %0d or m2 s: %0d\n", try_mad2.m1.s, try_mad2.m2.s); // <- Absolute endmodule ////////////////////////////////////////////////////////////////////////////// /// Simulation Terminology // // :SV: Section 4. Complete details, not the basics. // // // In this section simulation terminology is described. Simulation // itself is covered in the following sections. Just the basics, not // complete details. // // :Def: Process // A unit of: // - procedural code, // - continuous assignment, // - primitive. // A process is associated with an instance, so there is a set of // processes for each instance. // :Def: Scheduling [a process] // The placing of a process in the event queue. // :Def: Event // A process that has been scheduled (chosen to run), either now, // soon, or later. (Perhaps "event" is misleading since a scheduled // process is just waiting around in the event queue to be // executed, so why call it an "event"?) // :Def: Event Queue // A list of events. The simulator's to-do list. The event queue is // divided into regions, such as the active and inactive region. // :Def: Region [of an event queue] // A section of an event queue. An event is placed into a // particular region of the event queue. SystemVerilog defines 17 // event queue regions. The regions to be used in this set of notes // are that active and inactive regions. // :Def: Inactive Region // A region of the event queue in which new events are scheduled. // :Def: Active Region // A region of the event queue from which the scheduler removes // events to execute. When the active becomes empty it will be // refilled with the contents of the next non-empty region starting // with the inactive region. Figure 4-1 in :SV: shows the complete // list of regions to check. `ifdef XX /// Examples of Processes // // - Three processes: The code in the initial, always, and final block. initial begin a = 0; b = 1; end always_comb begin s = a ^ b ^ cin; c = a & b | a & c | b & c; end final $write("Simulation completed. Drive home safely."); // // - A continuous assignment expression. assign s = a ^ b ^ cin; // // - A primitive instantiation. and a1(aab,a,b); // :Example: // // Consider the instantiation tree defined by try_mad, above. // Here are the processes: // // try_mad.initial (The initial block in try_mad.) // try_mad.m1.p: The continuous assignment of p in m1. // try_mad.m1.s: The continuous assignment of s in m1. // try_mad.m2.p: The continuous assignment of p in m2. // try_mad.m2.s: The continuous assignment of s in m2. `endif /// Process Scheduling // // :Def: Initially-Scheduled Process // Processes that are scheduled at the start of a simulation. // This includes all initial and always_comb (but not always) statements. // // :Def: Time-Delay Scheduled Process // A process that is scheduled due to its execution of // a time delay (#). // // :Def: Sensitivity List Scheduled // A process that is scheduled due to a change in an object // in its sensitivity list. // // :Def: Sensitivity List [of a process, event control, or wait] // A list of objects (nets and variables) whose change will result // in the process being scheduled. Sensitivity list objects can be // explicitly or implicitly specified. // // Explicitly Specified Sensitivity List Objects: // In an event control ( always @( a or b ) ). // In a wait expression ( wait( x < y ) ). // // Implicitly Specified Sensitivity Objects // Objects used in an always @*. // Objects used in an always_comb. // Objects used in an assign. // Objects used in module connections. // // /// :Example: /// Examples of sensitivity lists. `ifdef XXX always_comb begin s = a ^ b ^ ci; co = a & b | a & ci | b & ci; end; // // Sensitivity list: a, b, ci. // // NOT in list: s, co, because their values are not used inside the process. and a1(aab,a,b); // // Sensitivity list: a, b. assign x = y + z; // // Sensitivity list: y, z. always_comb x = y + z; // // Sensitivity list: y, z. always @(*) x = y + z; // // Sensitivity list: y, z. always @( y ) x = y + z; // Note: z omitted. NOT A GOOD IDEA. DON'T DO THIS! // // Sensitivity list: y. Explicitly provided in argument of @. always @( posedge clk ) x = y + z; // // Sensitivity list: clk // // NOT in list: y, z, because they are not in the event control (@). always_comb begin x = a + b; z = x + y; end // // Sensitivity list: a, b, y. // // NOT in list: x, because its value before execution is not used. // A process that uses multiple event controls and wait statements // will have one sensitivity list for each. // always begin @( a or b ); // Sensitivity list: a, b $write("Hey, a or b changed.\n"); wait( a < c ); // Sensitivity list: a, c $write("Guess what? a < c!\n"); end `endif // :Example: // // Modules mad2 and tm2 are similar to the modules above. // // The instantiation tree defined by top-level module tm2. // module mad2(output uwire [31:0] s, input uwire [31:0] a, b, c); // Process Label /// tm2.m1.p or tm2.m2.p uwire [31:0] p = a * b; // Sensitivity list a, b. // Process Label /// tm2.m1.s or tm2.m2.s assign s = p + c; // Sensitivity list p,c. endmodule module tm2; logic [31:0] i1, i2, i3, i4, i5, ii; uwire [31:0] i0; mad2 m1( ii, i1, i2, i3 ); // Contains two processes. mad2 m2( i0, i4, i5, ii ); // Contains two processes. // A process. Automatically scheduled at simulation start. // Process Label (See notes below for use of label) /// tm2.init initial begin // No sensitivity list. Scheduled at sim start. i1 = 1; i2 = 2; i3 = 3; i4 = 4; i5 = 5; end // A process. Scheduled by sensitivity list. // Process Label (See notes below for use of label) /// tm2.alaw always @( i0 ) // Sensitivity list: i0. $write("TM2: Result of %0d * %0d + %0d + %0d * %0d = %0d\n", i1, i2, i3, i4, i5, i0); endmodule ////////////////////////////////////////////////////////////////////////////// /// Basic Simulation // // :Def: Simulation [of an instantiation tree of a top-level module] // Informal: Computation of signals over time for a top-level module, // and performing of other actions (such as printing messages) // specified in the Verilog code. // // More precise: The execution of processes defined by the top-level // module in the order determined by object state changes, delays, // and other scheduling requirements as defined by the SystemVerilog // standard. // // Simulation for SystemVerilog 1800-2023 is defined by the reference // algorithm in Section 4. In the full version, the event queue is // divided into 17 regions. // // The description below considers just two regions, active and // inactive. When a process is scheduled it is placed into the // inactive region. /// Simulation // // Note: This description ignores many things, especially the // handling of delays. // /// -- Initialization // // Schedule all initial blocks and always_comb blocks (into the // inactive region). // /// -- Main Loop Iteration // // The active region is empty at this time. // Unmark all objects. // Move all events from the inactive region to the active region. // // Foreach event: Remove an event from the active region: // Execute the event. // Mark objects that were changed during the execution. // // Schedule in the inactive region all processes that have a marked // object in their sensitivity list. // // If the inactive region is non-empty go to Main Loop. // :Example: // // Simulation example for tm2 (from an earlier example). // // /// Objects (In instantiation tree of tm2) // // Note that an object appearing in a port connection is known by a // different name in parent (such as tm2) and child (such as // tm2.m1). // // i1, m1.a (Two names for the same object) // i2, m1.b (Ditto) // i4, m2.a (Ditto) // i5, m2.b (Ditto) // ... (Et Cetera) // /// Processes and Sensitivity Lists // // tm2.init: Fixed scheduling. (At start.) // tm2.alaw: Sensitivity List: i0 // tm2.m1.p: Sensitivity List: i1 (m1.a), i2 (m1.b) // tm2.m1.s: Sensitivity List: m1.p, i3 (m1.c) // tm2.m2.p: Sensitivity List: i4 (m2.a), i5 (m2.b) // tm2.m2.s: Sensitivity List: m2.p, ii (m2.c) /// tm2 Simulation Details // /// -- Initialization // // Active: {}. Inactive: { tm2.init } // /// -- Main Loop -- First Iteration // // Active: {}. Inactive: { tm2.init } // -> Copy inactive to active. // Active: {tm2.init}. Inactive: {} // -> Execute: tm2.init // -> Mark: i1, i2, i3, i4, i5. (Marked because they changed.) // Active: {}. Inactive: {} // // Marked Objects: i1, i2, i3, i4, i5. // -> Schedule using marked objects. // Active: {} Inactive: { tm2.m1.p, tm2.m1.s, tm2.m2.p } // /// -- Main Loop -- Second Iteration // // Active: {} Inactive: { tm2.m1.p, tm2.m1.s, tm2.m2.p } // -> Copy inactive to active. // Active: { tm2.m1.p, tm2.m1.s, tm2.m2.p } Inactive: {} // -> Execute: tm2.m1.p // -> Mark: m1.p // Active: { tm2.m1.s, tm2.m2.p } Inactive: {} // -> Execute: tm2.m1.s // -> Mark: ii (m1.s) // Active: { tm2.m2.p } Inactive: {} // -> Execute: tm2.m2.p // -> Mark: m2.p // Active: {} Inactive: {} // // Marked Objects: m1.p, ii (m1.s), m2.p // -> Schedule: // Active: {} Inactive: { tm2.m1.s, tm2.m2.s } // /// -- Main Loop -- Third Iteration // // Active: {} Inactive: { tm2.m1.s, tm2.m2.s } // Active: { tm2.m1.s, tm2.m2.s } Inactive: {} // -> Execute tm2.m1.s // -> Mark: ii (m1.s) // -> Execute tm2.m2.s // -> Mark: i0 (m2.s) // // Marked Objects: ii (m1.s), i0 (m2.s) // -> Schedule: // Active: {} Inactive: { tm2.m2.s, tm2.alaw } // /// -- Main Loop -- Fourth Iteration // // Active: {} Inactive: { tm2.m2.s, tm2.alaw } // Active: { tm2.m2.s, tm2.alaw } Inactive: {} // // -> Execute tm2.m2.s // -> Mark i0 (m2.s) // -> Execute tm2.alaw. (Previous i0 is X, current is 25, so @event true.) // // Marked Objects: i0 (m2.s) // -> Schedule: // Active: {} Inactive: { tm2.alaw } // /// -- Main Loop -- Fifth Iteration // // Active: {} Inactive: { tm2.alaw } // Active: { tm2.alaw } Active: {} // // -> Execute tm2.alaw. (Previous i0 is 25, current is 25, so @event false.) // // Marked Objects: (None.) // -> Schedule: // Active: {} Inactive: {} // /// -- End Simulation /// Discussion of tm2 Example (Above) // // Several processes execute more than once. Except for tm2.alaw, the // results of all but the last execution are not really needed. For // example, tm2.m1.s executes twice: // // The first time it is computing s = p + c, // with p=X (undefined), c=3 -> s=X. // // The second time it computes s = p + c with p=2, c=3, -> s=5. // // Because there are no delays the two executions above occur at the // same time (that is, time in the simulated circuit world). This // multiple execution wastes simulator time (that is, time // experienced by the person running the simulation or cost for the // person paying the electric bill). All we want is the result of the // last execution. // // Though it is wasteful, this multiple execution is something we // live with. // // :Example: // // The Verilog below was used for classroom examples of // simulation. // module arb_exp_1 ( output logic [8:0] x, input uwire [8:0] a, b, c, e, f ); logic [8:0] i1, i2, i3; always_comb begin // Executes when a, b, c, e, or f changes. i1 = b * c; i2 = a + i1; i3 = e + f; x = i2 << i3; end endmodule module arb_exp_two ( output uwire [8:0] x, input uwire [8:0] a, b, c, e, f ); uwire [8:0] i1, i2, i3; // Compute expression x = a + b * c << ( e + f ); // assign i1 = b * c; assign i2 = a + i1; assign i3 = e + f; assign x = i2 << i3; endmodule module arb_exp_2_owt ( output uwire [8:0] x, input uwire [8:0] a, b, c, e, f ); uwire [8:0] i1, i2, i3; // Compute expression x = a + b * c << ( e + f ); // assign x = i2 << i3; assign i2 = a + i1; assign i1 = b * c; assign i3 = e + f; endmodule module my_mult(output uwire [8:0] x, input uwire [8:0] a, b); assign x = a * b; endmodule module my_adder(output uwire [8:0] x, input uwire [8:0] a, b); assign x = a + b; endmodule module my_left_shifter(output uwire [8:0] x, input uwire [8:0] a, b); assign x = a << b; endmodule module arb_exp_3 ( output uwire [8:0] x, input uwire [8:0] a, b, c, e, f ); // Compute expression x = a + b * c << ( e + f ); // uwire [8:0] i1, i2, i3; my_mult m1(i1, b, c); my_adder a1_instance(i2, a, i1); my_adder a2(i3, e, f); my_left_shifter ls1(x, i2, i3); endmodule // Unused code: module cprod(output int pr, pi, input int ar, ai, br, bi); assign pr = ar * br - ai * bi; // Sensitivity list: ar, ai, br, bi. assign pi = ar * bi + ai * br; // Sensitivity list: ar, ai, br, bi. endmodule module cprod3(output int pr, pi, input int ar, ai, br, bi, cr, ci); int ir, ii; cprod p1(ir,ii, ar,ai, br,bi); cprod p2(pr,pi, ir,ii, cr,ci); endmodule