////////////////////////////////////////////////////////////////////////////////
//
/// LSU EE 4755 Fall 2025 Homework 4 -- SOLUTION
//

 /// Assignment  https://www.ece.lsu.edu/koppel/v/2025/hw04.pdf

`default_nettype none

typedef enum logic [1:0]
  { tr_both = 3, tr_incr = 2, tr_decr = 1, tr_equal = 0 } Trend;

//////////////////////////////////////////////////////////////////////////////
///  Problem 1
//
  /// Complete trend2
  //  
//
//     [✓] Complete trend so that it computes the correct outputs.
//     [✓] It CAN NOT use expressions such as samp < samp_prev,
//     [✓] .. instead it must use the outputs of ist_compare instantiations.
//     [✓] Instantiate as many ist_compare modules as needed.
//     [✓] The module can use procedural code, but not for comparisons.
//
//     [✓] Make sure that the testbench does not report errors.
//     [✓] Do not assume particular parameter values.
//     [✓] The module must be synthesizable.
//
//     [✓] Code must be written clearly.
//     [✓] Pay attention to cost and performance.

module ist_compare
  #( int w = 15 )
   ( output uwire [1:0] gl, input uwire [w-1:0] a, b );
   assign gl = { a < b, a > b };
endmodule

module trend2
  #( int wd = 20, wi = 10  )
   ( output logic [1:0] prev_trend,
     output logic [wi-1:0] prev_length,
     output logic [1:0] curr_trend, last_2_trend,
     output logic [wi-1:0] curr_idx_start,
     input uwire [wd-1:0] samp,
     input uwire reset, clk );

   uwire [wi-1:0] debug_val;

   /// SOLUTION

   // Remember the most recent sample.
   //
   logic [wd-1:0] samp_prev;
   always_ff @( posedge clk ) samp_prev <= samp;

   // Compare the current sample to the most-recent sample, compute
   // their trend, and set the output port to that trend.
   //
   uwire [1:0] tr_last_2_next;
   ist_compare #(wd) c( tr_last_2_next, samp_prev, samp );
   always_ff @ ( posedge clk )
     last_2_trend <= reset ? tr_equal : tr_last_2_next;

   // Keep track of the sample index.
   //
   logic [wi-1:0] idx;
   uwire [wi-1:0] next_idx = reset ? 0 : idx + 1;
   always_ff @( posedge clk ) idx <= next_idx;

   // Keep track of the index of the last sample that was not equal to
   // the one before it. (The index is not updated if the current and
   // most-recent samples are the same.)
   //
   logic [wi-1:0] last_change_idx;
   always_ff @( posedge clk )
     if ( reset || tr_last_2_next ) last_change_idx <= next_idx;
   assign debug_val = last_change_idx;

   // Bitwise-OR the trend of the last two samples with the trend of
   // the current sequence. Set trend_new to true if both bits of
   // trend_mix are 1 (tr_both is 2'b11). 
   //
   uwire [1:0] trend_mix = curr_trend | tr_last_2_next;
   uwire trend_new = trend_mix == tr_both
         || !curr_trend && tr_last_2_next != tr_equal;
   //
   // Note that if both bits of trend_mix are 1 then either the last
   // two samples are increasing (tr_incr) and the current sequence
   // was decreasing, or the last two samples are decreasing and the
   // current sequence was increasing. In either case that means the
   // trend has changed.

   always_ff @( posedge clk )
     if ( reset ) begin

        curr_idx_start <= 0;
        curr_trend <= tr_equal;

        prev_length <= 0;
        prev_trend <= tr_equal;

     end else if ( trend_new ) begin

        // A new trend has been detected, set outputs appropriately.

        // Set the trend.
        //
        curr_trend <= tr_last_2_next;
        //
        // Set current sequence start ..
        //
        curr_idx_start <= last_change_idx;
        //
        // .. using last_change_idx because there may have been
        // several consecutive equal elements, which are all part of
        // this new sequence.

        // Set the previous-trend outputs.
        //
        prev_trend <= curr_trend;
        prev_length <= last_change_idx - curr_idx_start;

     end

endmodule



//////////////////////////////////////////////////////////////////////////////
/// Testbench Code
//
// It is okay to modify the testbench code to facilitate the coding
// and debugging of your modules. Keep in mind that your submission
// will be tested using a different testbench, so no one will be
// accused of dishonesty for modifying the testbench below. If you do
// modify the testbench be sure to make sure all of the original tests
// are performed to make sure that your code passes the original
// testbench.


// cadence translate_off

program reactivate
   (output uwire clk_reactive, output int cycle_reactive,
    input uwire clk, input var int cycle);
   assign clk_reactive = clk;
   assign cycle_reactive = cycle;
endprogram

string trend_str[int] =
       '{ tr_both: "Bth", tr_incr: "Inc", tr_decr: "Dec", tr_equal: "Eql" };

function string trend_str_get( logic [1:0] trend );
      trend_str_get = $isunknown(trend) ? "Unk" : trend_str[trend];
endfunction

function logic[1:0] func_compare( int unsigned a, b );
   func_compare = { a < b, a > b };
endfunction

function string eob( bit is_error, string err_msg, string debug_msg );
      localparam bit debug = 0;
      eob = is_error ? err_msg : ( debug ? debug_msg : " " );
endfunction

function int min( int a, int b );
      min = a <= b ? a : b;
endfunction

module testbench;

   // Minimum number of trace lines to show.
   //
   localparam int trace_lines_n_wanted = 10;
   //
   // Additional lines may be shown if there are errors.

   localparam int n_errors_show = 20;


   logic done;

   localparam int wd = 10;
   localparam int wi = 12;
   localparam int max_idx = ( 1 << wi ) - 1;
   localparam int limit_val = ( 1 << wd ) - 1;

   localparam int n_tests = 20000;

   localparam int cyc_max = n_tests + 100;

   int seed;
   initial seed = 475501;

   function automatic bit rand_bern( int period );
      rand_bern = $dist_uniform(seed,1,period) == 1;
   endfunction

   function automatic int rand_n( int n );
      rand_n = $dist_uniform(seed,0,n-1);
   endfunction

   bit clk;
   int cycle, cycle_limit;
   logic clk_reactive;
   int cycle_reactive;
   reactivate ra(clk_reactive,cycle_reactive,clk,cycle);
   string event_trace;
   string ev_trace[$];

   initial begin
      clk = 0;
      cycle = 0;
      event_trace = "";

      done = 0;
      cycle_limit = cyc_max;
      //  wait( tstart );

      fork
         while ( !done ) #1 cycle += clk++;
         wait( cycle >= cycle_limit )
           $write("Exit from clock loop at cycle %0d, limit %0d.  %s\n %s\n",
                  cycle, cycle_limit, "** CYCLE LIMIT EXCEEDED **",
                  event_trace);
      join_any;

      done = 1;
   end

   logic [wd-1:0] din;
   logic reset;
   uwire [wi-1:0] curr_idx_start, prev_length;
   uwire [1:0] curr_trend, prev_trend, last_2_trend;

   trend2 #(wd,wi) mut_trend2
     ( prev_trend, prev_length, curr_trend, last_2_trend,
       curr_idx_start, din, reset, clk );

   initial begin

      automatic int n_err_trend = 0, n_err_last_2_trend = 0;
      automatic int n_err_idx_start = 0, n_err_prev_length = 0;
      automatic int n_err_prev_trend = 0;
      automatic int n_resets = 0, n_changes = 0;
      automatic int heading_shown_cyc = -1000;
      automatic Trend shadow_curr_trend = tr_equal;
      string tr_entry, err_text, heading;
      int shadow_idx, idx_last_change;
      int shadow_prev_length, shadow_idx_start;
      Trend shadow_prev_trend;

      reset = 1;
      din = 0;
      @( negedge clk );
      @( negedge clk );


      for ( int i=0; i<n_tests; i++ ) begin

         automatic logic [wd-1:0] din_prev = din;
         automatic Trend shadow_last_2_trend;
         bit err_idx_start, err_prev_length, err_prev_trend;
         bit err_trend, err_last_2_trend, show_errs, have_errs;

         if ( i == 0 || shadow_idx >= max_idx || rand_bern( n_tests/5 ) ) begin

            // Reset
            reset = 1;
            n_resets++;
            din = rand_n( 1 << wd );
            din_prev = din;
            shadow_prev_trend = tr_equal;
            shadow_prev_length = 0;
            shadow_curr_trend = tr_equal;
            shadow_idx = 0;
            shadow_idx_start = shadow_idx;
            idx_last_change = 0;

         end else begin
            shadow_idx++;
         end

         if ( reset ) begin ; end else if ( !shadow_curr_trend ) begin

            // Pick a direction.
            automatic int dir = rand_bern( 2 );
            automatic int abs_delta = 1 + $abs($dist_normal(seed,4,10));
            automatic int delta = dir ? abs_delta : -abs_delta;
            din += delta;
            if ( din > din_prev ) shadow_curr_trend = tr_incr;
            if ( din < din_prev ) shadow_curr_trend = tr_decr;

            shadow_idx_start = idx_last_change;

         end else if ( rand_bern( 3 ) ) begin
            // Switch trend.
            automatic int abs_delta = 1 + $abs($dist_normal(seed,4,10));
            n_changes++;
            if ( shadow_prev_trend === shadow_curr_trend )
              $fatal(1, "Benchmark Error: should have switched trends %h %h.",
                     shadow_prev_trend, shadow_curr_trend);
            shadow_prev_trend = shadow_curr_trend;
            shadow_prev_length = idx_last_change - shadow_idx_start;
            shadow_idx_start = idx_last_change;
            if ( shadow_curr_trend == tr_incr ) begin
               din = din - min(din, abs_delta);
               shadow_curr_trend = tr_decr;
            end else begin
               din = min(din + abs_delta,(1<<wd)-1);
               shadow_curr_trend = tr_incr;
            end
         end else if ( rand_bern( 3 ) == 0 ) begin
            //   Maintain trend.
            automatic int delta = 1 + $abs($dist_normal(seed,4,10));
            automatic int d_try =
              shadow_curr_trend == tr_incr ? din + delta : din - delta;
            din = d_try < 0 ? 0 : d_try > limit_val ? limit_val : d_try;
         end else begin
            //   Equal.
         end

         if ( din != din_prev ) idx_last_change = shadow_idx;

         shadow_last_2_trend =
           din > din_prev ? tr_incr : din < din_prev ? tr_decr : tr_equal;

         @( negedge clk );

         err_prev_trend = shadow_prev_trend !== prev_trend;
         err_prev_length = shadow_prev_length !== prev_length;

         err_trend = shadow_curr_trend !== curr_trend;
         err_last_2_trend = shadow_last_2_trend !== last_2_trend;
         err_idx_start = shadow_idx_start !== curr_idx_start;

         heading = $sformatf
           ("   %5s c %4s  %4s  TrLst2  TrCurr %5s   TrPrev %4s %12s %5s",
               "Cyc", "Idx","Samp",              "IdxSt", "LenP", " ", "Debug");

         tr_entry =
           $sformatf
             ({"Tr %5d %s %4d  %4d  %2b-%3s  ",
               "%2b-%3s %5d   %2b-%3s %4d <- Correct  %5d "},
              cycle, reset ? "R" : " ",
              shadow_idx,

              din,

              shadow_last_2_trend,
              trend_str[shadow_last_2_trend],

              shadow_curr_trend,
              trend_str[shadow_curr_trend],
              shadow_idx_start,

              shadow_prev_trend,
              trend_str[shadow_prev_trend],
              shadow_prev_length,

              mut_trend2.debug_val
              );

         err_text =
           $sformatf("Er %5s %13s %6s  %6s %5s   %6s %4s <- Mod Errors",
                     " ", " ",

                     eob( err_last_2_trend,
                          $sformatf("%2b-%s",last_2_trend,
                                    trend_str_get(last_2_trend)), "2t2t2t" ),

                     eob( err_trend,
                          $sformatf("%2b-%s",curr_trend,
                                    trend_str_get(curr_trend)), "ctctct"),

                     eob( err_idx_start,
                          $sformatf("%0d",curr_idx_start), "ist" ),

                     eob( err_prev_trend,
                          $sformatf("%2b-%s",prev_trend,
                                    trend_str_get(prev_trend)), "------"),
                     eob( err_prev_length,
                          $sformatf("%0d",prev_length), "len" )
                     );

         ev_trace.push_back(tr_entry);
         while ( ev_trace.size() > 10 ) tr_entry = ev_trace.pop_front();

         show_errs = 0;

         if ( err_prev_trend && n_err_prev_trend++ < n_errors_show )
           show_errs = 1;
         if ( err_prev_length && n_err_prev_length++ < n_errors_show )
           show_errs = 1;
         if ( err_idx_start && n_err_idx_start++ < n_errors_show )
           show_errs = 1;

         if ( err_trend && n_err_trend++ < n_errors_show ) show_errs = 1;
         if ( err_last_2_trend && n_err_last_2_trend++ < n_errors_show )
           show_errs = 1;

         have_errs = err_prev_trend || err_prev_length ||
           err_idx_start || err_trend || err_last_2_trend;

         if ( show_errs || cycle < trace_lines_n_wanted ) begin
            automatic bit show_heading = cycle - heading_shown_cyc > 12;
            if ( show_heading ) begin
               $write("\n%s\n",heading);
               heading_shown_cyc = cycle;
            end
            while ( ev_trace.size() ) $write("%s\n",ev_trace.pop_front());
            if ( have_errs )
              $write("%s\n",err_text);
         end

         reset = 0;

      end

      $write("\nErrors last-2-trend: %0d\n", n_err_last_2_trend);
      $write("Errors current trend, idx start: %0d, %0d\n",
             n_err_trend, n_err_idx_start);
      $write("Errors previ trend, length: %0d, %0d\n",
             n_err_prev_trend, n_err_prev_length);
      $write("%s",
             $sformatf({ "Done. Summary: %0d resets, %0d changes, errs: ",
               "(pt,pl,ct,is,lt) %0d,%0d,%0d,%0d,%0d\n"},
             n_resets, n_changes,
             n_err_prev_trend, n_err_prev_length,
             n_err_trend, n_err_idx_start,
             n_err_last_2_trend));

      done = 1;

   end

endmodule

// cadence translate_on