///  LSU EE 4755 -- Fall 2023 -- Digital Design / HDL
///
///  Rudiments of Procedural and Behavioral Code

/// Contents

 // Behavioral Code and Structured Procedures
 // The initial Structured Procedure
 // Basics of Delay (#) and Event (@) Controls
 // The always Structured Procedure
 // The $display system task, and a testbench example.


/// 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.


////////////////////////////////////////////////////////////////////////////////
/// Procedural and Behavioral Code

//  In a procedural language (such as C, Python, Fortran, and other
//  conventional languages) statements execute one after another,
//  sometimes jumping forward and backward as directed by conditional
//  ("if"), looping ("for"), procedure call, and other constructs. (An
//  example of a non-procedural language is Prolog.)
//
//  Verilog descriptions (please don't call them programs) can consist
//  of both procedural and non-procedural code. In non-procedural code
//  the order of execution does not follow a sequential path through
//  lines of the description, but is determined by other rules. An
//  example of non-procedural code would be the add4_structural module
//  below and the pie and two_pie modules from l005-review.v. All (or
//  almost all) descriptions in this file are procedural.
//
//  Verilog procedural code is called /behavioral code/ if the intent
//  is to describe hardware. An example is the add4_behavioral module
//  below. Behavioral code is used to specify what hardware does
//  rather than describe it as an interconnection of simpler
//  components (as does structural code). Procedural code looks like a
//  program in a conventional programming language but there are major
//  differences. One difference is in when a piece of code is supposed
//  to start running. In C, execution starts with the "main" routine
//  and other procedures start execution when they are called from
//  code in the main routine. In Verilog, procedural code is invoked
//  in a variety of ways, some times in a way similar to a procedure
//  call, other times in response to a change in a signal. See the
//  material in l007-simulation-basic.v for more on when procedural
//  code can execute.

//  Details on procedural code will be presented later. Here the
//  rudiments are presented here so that procedural code can be used
//  Verilog examples.

 /// :Def: Procedural Code
//   Statement(s) within a structured procedure, which is started with
//   the following keywords:
//     :Keywords: initial,always,always_comb,always_ff,always_latch,final
//   The order of execution is similar to procedural languages like C.
//
 /// :Def: Behavioral Code
//   Procedural code that is part of a module which is intended to
//   describe hardware. This usually means that the code computes
//   module outputs.
//

//
 /// :Example: Procedural Behavioral Adder.
//
module add4_behavioral
  #( int w = 10 )
   ( output logic [w-1:0] sum, input uwire [w-1:0] a[4] );

   // This code is procedural because of the always_comb block.
   // It is behavioral because it is intended to describe hardware.

   always_comb begin
      sum = a[0];
      for ( int i=1; i<4; i++ ) sum += a[i];
   end

   // The way to determine for sure whether the intent is to describe
   // hardware is to ask the person that wrote it, or to look for
   // comments, documentation, etc, that explain that the intent is to
   // model hardware. Lacking those, one can look at the code and
   // guess.

endmodule

 /// :Example: Structural (Not Procedural) Adder
//
module add4_structural
  #( int w = 10 )
   ( output logic [w-1:0] sum, input uwire [w-1:0] a[4] );

   // This code is not procedural because it lacks procedural blocks.

   uwire [w-1:0] p0, p1;
   my_adder #(w) add01( p0, a[0], a[1] );
   my_adder #(w) add23( p1, a[2], a[3] );
   my_adder #(w) add0123( sum, p0, p1 );

endmodule


 /// :Example: Procedural But Not Behavioral Adder
//
module add4_not_behavioral_by_any_means
  #( int w = 10 )
   ( input uwire [w-1:0] a[4] );

   // This is procedural because of the procedural block (starting with
   // always_comb).
   //
   // It is not behavioral because the module does not have outputs,
   // it just writes to the console.

   logic [w-1:0] sum;
   always_comb begin
      sum = 0;
      for ( int i=0; i<4; i++ ) sum += a[i];
      $write("The sum of %0d + %0d + %0d + %0d = %0d\n",
             a[0], a[1], a[2], a[3], sum);
   end

endmodule




//////////////////////////////////////////////////////////////////////////////
/// Specifying Procedural Code
//
//  Procedural code is placed in a /structured procedure/.
//  When and how many times the procedural code executes depends
//    on the particular structured procedure.

// :SV09: Section 9.2
// :BV3: Section A.11.1


 /// Structured Procedures
//
// :Keyword: initial STATEMENT
//           Scheduling:
//              Fixed: Scheduled into inactive region at simulation start.
//           Informal Description:
//              Start statement at t=0.
//           Typical Use:
//              Main routine for testbench code.
//
// :Keyword: always_comb STATEMENT
 ///               Intent is to describe combinational logic.
//           Scheduling:
//              Fixed: Scheduled into inactive region at simulation start.
//              Sensitivity List: Construct sensitivity list based on
//                objects used in STATEMENT.
//           Informal Description:
//              Execute statement at t=0 and whenever live-in objects change.
//              On each execution ..
//              .. every object that can be written, will be written.
//           Typical Use:
//              Model combinational logic.
//
//           There are two additional always_FOO statements,
//              always_latch and always_ff. They will be covered later.
//
// :Keyword: always_ff @( posedge OBJECT ) STATEMENT
// :Keyword: always_ff @( EDGE OBJECT ) STATEMENT
 ///               Intent is to describe edge-triggered registers and flip-flips.
//           Informal Description:
//              Execute statement when OBJECT changes from 0 to 1 or
//              changes as specified by EDGE.
//           Typical Use:
//              Model edge-triggered flip-flops and registers.
//
// :Keyword: always_latch STATEMENT
 ///               Intent is to describe level-sensitive registers and flip-flips.
//           Informal Description:
//              Execute statement at t=0 and whenever live-in objects change.
//              Objects may or may not be written
//              Every object that can be written, will be written.
//              Objects may or may not be written on a particular execution.
//           Typical Use:
//              Model level-triggered flip-flops.
//           Will not be used in this course.
//
// :Keyword: always STATEMENT
//           Can be used for anything.
//           Scheduling:
//              Fixed: Scheduled into inactive region at simulation start.
//              Scheduled into inactive region each time STATEMENT completes.
//           Informal Description:
//              Execute statement repeatedly.
//              STATEMENT must have a timing or event control (unless
//                your goal is to hang the simulator).
//           Typical Use:
//              In testbench code to generate clock signals.
//              In testbench code to monitor changes in other signals.
//              In older versions of Verilog, used to model all kinds
//                of hardware.
//
// :Keyword: final STATEMENT
//           Scheduling:
//              Scheduled into inactive region when all regions are empty.
//              Just once, of course.
//           Informal Description:
//              Execute statement before simulator exits.
//
// STATEMENT is usually a block, e.g.,  begin a=1; etc.... ; end


 /// Simple and Compound Statements
//
module explain_statement( input int f );
   int a,aa,b,c,d,e,g;

   // Simple statement.
   initial a = 1;
   //
   // A verbose way of coding the line above.
   //
   initial
     begin
        aa = 1;
     end

   // Simple statement.
   initial if ( f < 5 ) g = f - 1; else g = f + 3;

   // Compound statement, which is bracketed by begin/end.
   initial begin b = 1;  c = 3;  end

   // Compound statement taking up more space.
   initial begin
      d = 4;
      e = 5;
   end

   `ifdef PLEASE_DONT_DEFINE_ME
   // Illegal: Can't place procedural code here.
   h = 7;
   `endif

endmodule


 /// Quick Examples of Structured Procedures
//
module demo_blocks
  ( output logic [15:0]  b, e, e1, q, q1, q2,
    input uwire [15:0] f, d );

   logic [15:0] c, c1, a;
   logic clk;

   initial begin
      clk = 0;
      a = 1;
   end
   //
   // t =  0; clk -> 0
   //
   always #5 clk = !clk;
   //
   // t =  5:  clk -> 1
   // t = 10:  clk -> 0
   // t = 15:  clk -> 1
   // t = 20:  clk -> 0

   logic clock;
   initial clock = 0;

   /// Some examples below depend on event and delay control material
   /// which is covered further down.

   // Generate a clock signal using a less compact coding style.
   //
   always begin
      #5;
      clock = !clock;
   end

   // This is an infinite loop that will freeze the simulator time at zero.
   `ifdef FREEZE_WITH_AN_INFINITE_LOOP
   always i++;
   `endif

   always @( posedge clk ) $write("Tic\n");
   always @( negedge clk )
     begin
        $write("Toc\n");
     end

   always_ff @( posedge clk ) b = f + 1;

   // Executes at t=0 and whenever b or d changes.
   //
   always_comb q = b + d;
   //
   // Equivalent in old-school Verilog
   //
   always @* q1 = b + d;
   //
   // Equivalent in very old-school Verilog.
   //
   always @( b or d ) q2 = b + d; // WARNING: Points deducted.
   //
   // The style above is risky because it's easy to accidentally omit
   // a variable from the event list.

   always_comb begin
      c = b + d;
      if ( f == 4755 ) e = 0;
      if ( c < f ) e++; else e--;
   end

   always_latch begin
      c1 = b + d;
      if ( f == 4755 ) e1 = c1;
   end


   final $write("This is module demo_blocks saying goodbye.\n");

endmodule



////////////////////////////////////////////////////////////////////////////////
/// Initial Blocks

// :SV09: Section 9.2.1
// :BV3: Section A.11.1
//
 /// Placement
//
//   Appear in modules and programs (programs not yet covered).
//   There can be any number of initial blocks in each module and program.
//
 /// Execution Timing
//
//   All initial blocks start at t=0.
//
//
 /// Typical Uses
//
//   Initialization.
//
//   Testbench.
//
//
 /// NOT USED FOR
//
//   Describing synthesizable hardware.


// :Example:
//
//  Simple example.

module mymod1(input uwire myport, output uwire alsomyport);
   int  a, b;

   // Starts running at t=0.
   initial begin
      a = 1;
      b = myport;
      $write("Welcome to mymod1! Today we will set, a=%0d and b=%0h\n", a, b);
   end

endmodule


////////////////////////////////////////////////////////////////////////////////
/// Delays and Event Controls

// :SV09: Section 9.4 up to and including 9.4.2.1

 /// Description
//
// Delay and event controls are used to pause the execution of
// procedural code. With a delay, execution of the piece of procedural
// code is paused for the specified amount of time. With an event
// control, execution of the piece of procedural code is paused until
// something happens. The pause only affects the procedural code in
// which the delay or event control appears. During the pause the
// simulator will do something else.

// Here only a basic description is given. Later, more advanced
// treatment will be given, including non-blocking delays and
// the simulator event queue.


 ///              Delay Control
//
// :Keyword: # AMT STATEMENT
//           Scheduling: 
//              Suspend process and schedule it into inactive region ..
//              .. with timestamp t + AMT.
//           Informal Description:
//              Wait AMT time units, then execute STATEMENT.
//              In many cases STATEMENT is blank.

 ///              Event Control
//
// :Keyword: @* STATEMENT
//           Scheduling:
//              Suspend process and set up sensitivity list based on
//                objects used in statement. Processes will be scheduled
//                into inactive region when an object in the sensitivity
//                list changes.
//           Informal Description:
//              Wait until any source (live-in) object in STATEMENT changes.
//              The "*" is called an implicit event expression list.

// :Keyword: @ ( posedge A ) STATEMENT
// :Keyword: @ ( A or B or ... ) STATEMENT
//           Scheduling:
//              Suspend process and set up sensitivity list based on
//                objects used in the expression. Processes will be scheduled
//                into inactive region when an object in the sensitivity
//                list changes.
//           Informal Description:
//              Wait for A or B to change, then execute statement.
//              Sensitivity List: the "A or B or ..." part.
//

 ///             Level-Sensitive Event Control (Wait Statement )
//
// :Keyword: wait ( COND ) STATEMENT
//           Scheduling:
//              If COND is false suspend process and set up
//                sensitivity list based on objects used in the
//                expression. Processes will be scheduled into
//                inactive region when an object in the sensitivity
//                list changes. On resumption re-execute wait.
//           Informal Description:
//              Wait (if necesary) for COND to be true, then execute STATEMENT.


// :Example:
//
// Simple delay and event control examples.
//
module delay_and_event_demo(input uwire [15:0] b, c, xkcd);

   logic [15:0] a;

   initial begin

      a = 1;      // Assignment occurs at t = 0;

      // Below, a changes at t = 5.
      //
      #5 a = 2;  // AMT -> 5,  STATEMENT -> "a = 2;"

      // At this point t = 5;

      #7;  // AMT -> 7,  STATEMENT -> "" (Statement is empty.)

      // a is assigned at t = 12.

      a = 3;

      @ ( b );  // Wait until b changes.
      @ ( a or b );  // Wait until either a or b or both change.
      @ ( posedge b );  // Wait until b changes from 0 to 1.
      @ ( negedge b );  // Wait until b changes from 1 to 0.
      @ ( edge b );  // Wait until b changes from 1 to 0 or 0 to 1.2

      // Assume that b next changes at t=20.

      a = 4;   // a assigned at t = 20.

      // Wait until b changes. (b is used as a source in STATEMENT.)
      //
      @*  a = b + c;  // STATEMENT -> "a = b + c;"

      // If condition is not true wait until it is.
      //
      wait ( xkcd == a + b );
      //
      // At this point xkcd == a + b.
      // If it was true just before reaching the wait ..
      // .. then the wait time was zero.

      // Wait for condition to change.  There will always be a wait here.
      //
      @ ( xkcd or c );


   end

endmodule


////////////////////////////////////////////////////////////////////////////////
/// Always Blocks

// :SV09: Section 9.2.2
// :BV3: Section A.11.1

 /// Description
//
 // :Keyword: always STATEMENT
//
//   - STATEMENT starts at t=0;
//   - The amount of time STATEMENT takes depends on its contents.
//     Assume that STATEMENT always takes 5 units.
//     - Then STATEMENT will finish at t=5 ...
//     - ... and then will start again at t=5 (hence always) ...
//     - ... and will finish at t=10 ...
//     - ... and start again at t=10 ...
//
//  Note: There will be an infinite loop when STATEMENT
//  does not contain some kind of delay or event control.
//
 // :Keyword: always_comb STATEMENT
//
//  Restriction: STATEMENT cannot have timing (e.g, #3;) 
//               nor event controls (@(a or b);).
//
//   - STATEMENT executes at t=0.
//   - STATEMENT executes whenever an object used in STATEMENT changes.

 /// Common Use of Always -- Describe Combinational Logic
//
//   :Sample: always @* STATEMENT
//   :Sample: always_comb STATEMENT


module always_example
  ( output logic [15:0] sum,
    input uwire [15:0] a, b,  input uwire clk);

   always_comb begin

      sum = a + b;
      if ( sum < a + b + 0 ) sum = 16'hffff;

   end

endmodule

// :Example:
//
// Use of always to generate a clock signal.

module myclockgen(output logic clock);

   initial clock = 0;

   always #5 clock = !clock;

endmodule


// :Example:
//
// Good use of always, once upon a time.
 /// Now it's bad!
//
module mymod2good(output logic x,y, input uwire a,b,c);

   // Runs each time a or b or c changes.
   //
   always @( a or b or c ) begin  /// FOOLISH!  Use always @* instead.
      x = a + 1;
      y = b + c;
   end
endmodule

// :Example:
//
// Unusual use of always. Probably an error.
//
module mymod2bad(output logic x,y,z, input uwire a,b,c);

   // Runs each time a or b changes. Won't run if only c changes.
   //
   always @( a or b ) begin  /// Note: c intentionally omitted.
      x = a + 1;
      y = b + c;
   end

endmodule


// :Example:
//
// Use of implicit event expression. always_comb
//
module mymod21(x,y,z,a,b,c);
   input uwire a, b, c;
   output logic x, y, z;

   // Runs at t=0 and each time any right-hand-side object changes.
   //
   always_comb begin
      x = a + 1;
      y = b + c;
   end

endmodule


 /// Multiple initial And always Blocks

// A module can have any number of initial and always constructs.
// Timing and other details will be discussed later.

module mymod3(output logic x,y,z, input uwire a,b,c);

   // Runs at initialization.
   initial begin
      x = 1;
   end

   // Runs at initialization ..
   // .. either before or after the block above.
   initial begin
      y = 1;
   end

   // Runs each time x changes.
   //
   always @( * ) begin
      y = 2 + x;
   end

endmodule


///////////////////////////////////////////////////////////////////////////////
/// Procedural Assignments v. Continuous Assignments
///

 /// :Def: Procedural Assignment
 //        An assignment statement in procedural code.
//
module an_adderc(output logic [9:0] s, input uwire [9:0] a,b);
   logic [9:0] s2;
   always_comb begin
      s2 = a + b;
      s = s2 + 5;
   end
endmodule

 /// :Def: Continuous Assignment
 //        An assignment at module (not procedural) scope
 //        that is re-executed whenever objects 
 //        on the right-hand change.
 //
module an_adder(output uwire [9:0] s, input uwire [9:0] a,b);
   uwire [9:0] s2 = a + b;
   assign s = s2 + 5;
endmodule
 // 
 //  :Keyword: assign
 //  The assign keyword SHOULD NOT be used in procedural code.
 //  In this class it SHALL NOT be used in procedural code.
 //
 //  :Syntax: assign NET_OBJ = EXPR;
 //  :Sample: assign s = b + c;
 //
 //  EXPR is executed whenever an object in EXPR changes.

 /// Procedural Assignment versus Continuous Assignment
 //
 //  Procedural:
 //    Statements execute in order.
 //    Statement must be in a structured procedure.
 //  Continuous:
 //    Statements execute whenever objects in expression change.
 //    Statements must be at module scope (in this class).


module add3_behavioral_a
  #( int w = 10 )
   ( output logic [w-1:0] sum, input uwire [w-1:0] a[3] );

   always_comb begin
      sum = a[0] + a[1] + a[2];
   end

endmodule

module add3_explicit_str_a
  #( int w = 10 )
   ( output uwire [w-1:0] sum, input uwire [w-1:0] a[3] );

   assign sum = a[0] + a[1] + a[2];

endmodule

module add3_behavioral_b
  #( int w = 10 )
   ( output logic [w-1:0] sum, input uwire [w-1:0] a[3] );

   logic [w-1:0] sum12;

   always_comb begin
      sum12 = a[0] + a[1];
      sum = sum12 + a[2];
   end

endmodule

module add3_explicit_str_b
  #( int w = 10 )
   ( output uwire [w-1:0] sum, input uwire [w-1:0] a[3] );

   uwire [w-1:0] sum12;

   assign sum12 = a[0] + a[1];
   assign sum = sum12 + a[2];

endmodule

`ifdef DONT_DEFINE_ME



`endif


////////////////////////////////////////////////////////////////////////////////
/// Simple Testbench, and the $write System Task

// :SV17: Section 21.2 -- Display and Write system tasks.

// The following are useful in procedures.  They are covered briefly
// below and will be covered in detail later.

// :Keyword: $write
// :Keyword: $display
//
// The $display and $write system tasks.
//  Used in procedures to print messages in the transcript.
//  Similar to the C printf library function.
//
//  :Syntax: $write(FORMAT,ARG1,ARG2,...)
//  FORMAT is a string that can contain escape sequences.
//  ARG1, ARG2 are the values to be printed.
//  Format escape sequences start with a % and followed by a format character.
//   Format characters: %d (decimal), %h (hex), %o (octal), %b (binary)
//                      %c (character), %s(string), %t(time), ...
//
//  Examples:

//  $write("The values are: i=%0d or %h (hex) time=%t\n", i, i, $time);


// This is one of many system tasks. (See :SV17: Section 20)


// :Example:
//
// Use of behavioral code to write a testbench for a simple "imply"
// module.  The testbench is in module demo_the_tedious_way.

module imply( output uwire x, input uwire a, b );
   assign   x = ~a | b;
endmodule

module demo_the_tedious_way();

   logic a, b;
   uwire r;

   imply imp1(r, a, b);

   initial begin

      $write("\n --- Output of demo_the_tedious_way ---\n");

      // Here t = 0 (units discussed later).

      a = 0;  b = 0;

      $write("At the very beginning, t = %0t, r = %d\n", $time, r );

      // This delays execution for one unit.
      #1;

      $write("A little later, t = %0d,  r = %d\n",$time,r);

      #1;

      a = 0;  b = 1;

      $write(" t = %0t, r = %d\n",$time,r);

      #1;

      $write(" t = %0t, r = %d\n",$time,r);

      #1;

      a = 1;  b = 0;

      #1;

      $write(" t = %0t, r = %d\n",$time,r);

      #1;

      a = 1;  b = 1;

      #1;

      $write(" t = %0t, r = %d\n",$time,r);

   end

endmodule


// :Example:
//
// Same testbench as code above, but using a for loop to simplify
// things.
//
module demo_the_better_way();

   int i;
   uwire    a = i[0];
   uwire    b = i[1];
   uwire    r;

   imply imp1(r, a, b);

   initial begin

      #100;  // Wait for the other testbench to finish.

      $write("\n --- Output of demo_the_better_way ---\n");

      for ( i=0; i<4; i++ ) begin

         $write("At the beginning of an iteration. t=%0t, i=%2b  r=%d.\n",
                $time, i, r );
         #1;
         $write("In the middle of an iteration.    t=%0t, i=%2b  r=%d.\n",
                $time, i, r );
         #1;
      end

   end

endmodule

// :Example:
//
// Same testbench as code above, but checks whether output is
// correct and reports a tally of errors at the end.
//
module demo_testbench();

   logic [1:0] i;
   uwire    a = i[0];
   uwire    b = i[1];
   uwire    r;

   imply imp1(r, a, b);

   initial begin

      automatic int n_err = 0;

      #200;  // Wait for the other testbenchs to finish.

      $write("\n --- Output of demo_testbench ---\n");

      for ( i=0; i<4; i++ ) begin

         logic shadow_r;

         #1;

         // Compute expected output.
         shadow_r = ~a | b;

         if ( shadow_r !== r ) begin

            n_err++;
            $write("Incorrect result for a=%h, b=%h: %h != %h (correct).\n",
                   a, b, r, shadow_r);

         end

         #1;
      end


      $write("Done with tests, number of errors, %0d\n",n_err);
      $stop;

   end


endmodule


module my_adder #(int w=20)(output uwire [w-1:0] s, input uwire [w-1:0] a,b);
   assign s = a + b;
endmodule