///  LSU EE 4755 -- Fall 2021 -- Digital Design / HDL
///
///  Basic Verilog Simulation

/// Contents

 // Module Instantiation Hierarchy
 // Simulation Terminology
 // Basic Simulation


/// References

// :SV12: IEEE 1800-2017 -- The SystemVerilog Standard
//        https://ieeexplore.ieee.org/document/8299595/
//        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
//
//  :SV17: Section 23.3  (Instantiation Tree)
//  :SV17: Section 23.6  (Hierarchical Names)
//
// To understand simulation, one needs to clearly understand the
// difference between a module and a module instance and which module
// contains all 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 );

   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
//
//  :SV17: 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, a continuous assignment, or a 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 some other region.


`ifdef XX
 /// Examples of Processes
 //
 //    - The code in an initial, always, or 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.
  //  
  //
  //  Examples:            
`ifdef XXX

   always_comb begin s = a ^ b ^ cin;  c = a & b | a & c | b & c;  end;
   // Sensitivity list: a, b, cin.

   assign x = y + z;
   // Sensitivity list: y, z.

   and a1(aab,a,b);
   // Sensitivity list: a, b.

   always @( posedge clk ) x = y + z;
   // Sensitivity list: clk

   always @( y ) x = y + z;  /// Note: z omitted. Not a good idea.
   // Sensitivity list: y

   // 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);
   uwire [31:0] p = a * b;  // Sensitivity list a, b.
   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.
   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.
   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-2017 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
 //
 //  (The active region should be 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).
 //
 //
 ///  Some Objects
 //
 //   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
 //  i2 = m1.b
 //  i3 = m1.c
 //  i4 = m2.a
 //  i5 = m2.b
 //  ...
 //
 /// 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)

 /// 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.
 // Active: {}.          Inactive:  {}
 //
 // -> Schedule:
 // 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