///  LSU EE 4755 -- Fall 2026 -- 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, the 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, we want the top-level module to be the testbench.
 //  We can invoke the Verilog simulator with an option that specifies
 //  which module to use as the top-level module. For Cadence Xcellium
 //  we would use the command:
 //
 //    xrun my_file.v -top my_chosen_top_level_module
 //
 //  If we omit "-top my_chosen_top_level_module" then the simulator
 //  will use *all* modules that are not instantiated by any other as
 //  a top-level module. This usually works, but it can be confusing
 //  if you have two testebenches in a file, because both will run and
 //  generate output.
 //
 //  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 non-blocking assignment update. (Covered later in the semester.)
 //  A process is associated with an instance, so there is a set of
 //  processes for each instance. (For an example search for
 //  try_mad.m1.p and try_mad.m2.p.)

 //  :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 some 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.");
 //
 //    - Two processes: continuous assignment expressions.
         assign s = a ^ b ^ cin;
         uwire [7:0] x = d + e;
 //
 //    - A primitive instantiation.
         and a1(aab,a,b);  // The process computes a && b.

 //    - Non-blocking assignment update.
         always_ff @( posedge clk )
           x <= a + b;
         // The sum a+b is computed when the line above executes.
         // The update event writes the computed sum to x.

 // :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
  //
  //  There are four main ways a process can be scheduled: they can
  //  be initially scheduled, time-delay scheduled, sensitivity
  //  list scheduled, non-blocking-assignment scheduled.
  //
  //  :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.
  //  These scheduling events are placed in the inactive region of time
  //  slot t=0.
  //
  //  :Def: Time-Delay Scheduled Process
  //  A process that executes a time delay (#). The scheduling event
  //  is placed in the inactive region of the appropriate time slot.
  //  For example, if in time-slot t=10 a process reaches statement
  //  "#5;" that process will stop and be time-delay scheduled to
  //  resume execution by placing an event in the inactive region of
  //  time slot t=15.
  //
  //  :Def: Sensitivity List Scheduled
  //  A process that is scheduled to resume execution because an
  //  object in its sensitivity list (see below) changed. The
  //  scheduling event will be placed in the inactive region of the
  //  current time slot.
  //
  //  :Def: Non-Blocking Assignment Scheduled
  //  An update event that is scheduled due to the execution of a
  //  non-blocking assignment in procedural code. (Non-blocking
  //  assignments will be covered later in the semester.) The update
  //  event is placed in the NBA region usually of the current time
  //  slot.

  //  :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 ) ).  // Sens. list: a, b
  //    In a wait expression ( wait( x < y ) ).      // Sens. list: x,y
  //
  //  Implicitly Specified Sensitivity Objects
  //    Objects used in an always @* (live-in objects).
  //    Objects used in an always_comb (live-in objects).
  //    Objects used in an assign right-hand side.
  //    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.
   //
   // Objects s and co are not live-in, so they are not in the
   // sensitivity list. They are not live-in because their values when
   // the always_comb starts are not used. For example, since s is
   // written when the always_comb starts its previous value is not
   // seen and so it is not live in.

   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 @.
   // Note: Should not use this form when describing hardware.

   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