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

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

typedef enum
  { Char_escape = 128, Char_escape_stop = 200, Char_EOS = 255,
    Char_A = 65, Char_Z = 90, Char_a = 97, Char_z = 122,
    Char_0 = 48, Char_9 = 57,
    Char_space = 32, Char_underscore = 95, Char_cr = 13
    } Chars_Special;

`default_nettype none

//////////////////////////////////////////////////////////////////////////////
///  Problem 1
//
 ///   Complete word_count as described in the handout.
 ///
//
//     [✔] Do not use more adders than are necessary, especially for len_avg.
//
//     [✔] Make sure that the testbench does not report errors.
//     [✔] Module must be synthesizable. Use command: genus -files syn.tcl
//
//     [✔] Don't assume any particular parameter value.
//
//     [✔] Code must be written clearly.
//     [✔] Pay attention to cost and performance.



module word_count
  #( int wl = 5, wn = 6, n_avg_of = 10 )
   ( output logic word_start, word_part, word_ended,
     output logic [wl-1:0] len_word,
     output logic [wn-1:0] num_words,
     output logic [wl-1:0] len_avg,
     input uwire [7:0] char,
     input uwire reset, clk );

   uwire char_az = char >= Char_a && char <= Char_z
         || char >= Char_A && char <= Char_Z;
   uwire char_09 = char >= Char_0 && char <= Char_9;
   uwire char_wd_start = char_az;
   uwire char_wd_part = char_wd_start || char_09 || char == Char_underscore;

   /// SOLUTION

   logic prev_char_wd_part;

   uwire next_word_start = char_wd_start && !prev_char_wd_part;
   uwire next_word_part = word_part && char_wd_part || next_word_start;
   uwire next_word_ended = word_part && !char_wd_part;

   always_ff @( posedge clk ) begin
      prev_char_wd_part <= reset ? 0 : char_wd_part;
      word_start <= reset ? 0 : next_word_start;
      word_part <= reset ? 0 : next_word_part;
      word_ended <= reset ? 0 : next_word_ended;
   end

   logic [wl-1:0] len_recent[n_avg_of];
   logic [wl+$clog2(n_avg_of):0] len_sum;
   assign len_avg = len_recent[n_avg_of-1] ? len_sum / n_avg_of : 0;

   always_ff @ ( posedge clk ) if ( reset ) begin

      num_words <= 0;
      len_word <= 0;

      for ( int i=0; i<n_avg_of; i++ ) len_recent[i] = 0;
      len_sum = 0;

   end else begin

      len_word <= next_word_start ? 1 : next_word_part ? len_word+1 : len_word;
      num_words <= next_word_ended ? num_words + 1 : num_words;

      if ( next_word_ended ) begin
         len_sum -= len_recent[n_avg_of-1];
         len_sum += len_word;
         for ( int i=n_avg_of-1; i>0; i-- ) len_recent[i] = len_recent[i-1];
         len_recent[0] = len_word;
      end

   end

endmodule

module word_count_blank
  #( int wl = 5, wn = 6, n_avg_of = 10 )
   ( output logic word_start, word_part, word_ended,
     output logic [wl-1:0] len_word,
     output logic [wn-1:0] num_words,
     output logic [wl-1:0] len_avg,
     input uwire [7:0] char,
     input uwire reset, clk );

   uwire char_az = char >= Char_a && char <= Char_z
         || char >= Char_A && char <= Char_Z;
   uwire char_09 = char >= Char_0 && char <= Char_9;
   uwire char_wd_start = char_az;
   uwire char_wd_part = char_wd_start || char_09 || char == Char_underscore;

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 on the one hand no
// one will be accused of dishonesty for modifying the testbench
// below. However be sure to restore any changes 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

module testbench;

   localparam int npsets = 3;
   localparam int pset[npsets][2] =
              '{ { 2, 5 }, { 1, 6}, {9, 7 } };


   int n_err_shown;  // Number of times error info printed to console.
   int n_err_sh_nc, n_err_sh_nw, n_err_sh_avg, n_err_sh_state;
   initial begin
      n_err_sh_nc = 0;
      n_err_sh_nw = 0;
      n_err_sh_avg = 0;
      n_err_sh_state = 0;
   end
   int t_errs;       // Total number of errors.
   initial begin t_errs = 0; n_err_shown = 0; end
   final $write("Total number of errors: %0d\n",t_errs);

   uwire d[npsets:-1];    // Start / Done signals.
   assign d[-1] = 1;  // Initialize first at true.

   // Instantiate a testbench at each size.
   //
   for ( genvar i=0; i<npsets; i++ )
     testbench_n #(pset[i][0],pset[i][1]) t2( .done(d[i]), .tstart(d[i-1]) );

endmodule

module testbench_n
  #( int win_sz = 10, wd_len_max = 5 )
   ( output logic done, input uwire tstart );

   localparam int wl = $clog2(wd_len_max+1);
   localparam int wn = $clog2(win_sz) + 5;
   localparam int n_tests = 10000;
   localparam int cyc_max = n_tests * 2;

   // Number of starting trace lines shown.
   localparam int tr_initial_lines = 12;
   // Number of trace lines to show when there is an error.
   localparam int tr_err_context = 5;

   int seed;
   initial seed = 4755;

   function string sample( input string str );
      sample = str[ $dist_uniform( seed, 0, str.len()-1 ) ];
   endfunction

   function string fbit( input logic b, input string s );
      fbit = b === 1 ? s : b === 0 ? "_" : b === 1'bx ? "x" : "z";
   endfunction

   bit clk;
   int cycle, cycle_limit;
   logic clk_reactive;
   int cycle_reactive;
   reactivate ra(clk_reactive,cycle_reactive,clk,cycle);
   string event_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

   uwire [wl-1:0] len, lavg;
   uwire [wn-1:0] nw;
   uwire w_start, w_part, w_ended;
   logic [7:0] char;
   logic reset;

   string test_one = "I II III 2not o_wd four cinco a b c d ";
   //  string test_one = "A or bee       ";

   word_count #(wl,wn,win_sz) wd_cnt
     (w_start, w_part, w_ended, len, nw, lavg, char,reset,clk);

   bit char_wd_start[256];
   bit char_wd_part[256];
   string str_wd_start, str_wd_part, str_wd_notstart;
   localparam string str_wd_not = " ,!.-";
   int lens[$];

   initial begin

      automatic logic [wl-1:0] shadow_nc = 0;
      automatic logic [wn-1:0] shadow_nw = 0;
      automatic logic [wl-1:0] shadow_avg = 0;
      automatic int len_sum = 0;
      automatic int n_err_nc = 0, n_err_w_st=0, n_err_w_pa=0, n_err_w_en=0;
      automatic logic shadow_w_st, shadow_w_pa, shadow_w_en;
      automatic int n_err = 0, n_err_lavg = 0, n_err_nw = 0;
      automatic int str_idx = 0;
      automatic string str_win = {10{" "}};
      automatic string test_str_buffer;
      automatic int n_err_pre;
      automatic logic pw_start, pw_part, pw_ended; // State before + edge.
      automatic string tr_recent[$];
      automatic bit need_reset = 0;
      bit in_word, was_in_word, was_word_char;

      for ( int i=0; i<256; i++ )
        begin char_wd_start[i] = 0; char_wd_part[i] = 0; end
      for ( int i=Char_a; i<=Char_z; i++ )
        begin
           char_wd_start[i] = 1; char_wd_part[i] = 1;
        end
      for ( int i=Char_A; i<=Char_Z; i++ )
        begin
           char_wd_start[i] = 1; char_wd_part[i] = 1;
        end
      for ( int i=Char_0; i<=Char_9; i++ ) char_wd_part[i] = 1;
      char_wd_part[Char_underscore] = 1;

      for ( int i=0; i<256; i++ ) begin
         if ( !char_wd_start[i] && char_wd_part[i] )
           str_wd_notstart = { str_wd_notstart, string'(byte'(i)) };
         if ( char_wd_start[i] )
           str_wd_start = { str_wd_start, string'(byte'(i)) };
         if ( char_wd_part[i] )
           str_wd_part = { str_wd_part, string'(byte'(i)) };
      end

      test_str_buffer = { test_one, test_one };
      str_idx = 0;

      in_word = 0;
      was_in_word = 0;
      was_word_char = 0;
      char = Char_A;
      reset = 1;
      @( posedge clk_reactive ); @( posedge clk_reactive );
      reset = 0;

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

         automatic int round = i / test_one.len();
         automatic bit do_reset =
           round == 1 && $dist_uniform(seed,1,7) == 1
             || round > 1 && $dist_uniform(seed,1,(wd_len_max+4)/2*win_sz*2)==1;
         automatic bit show_err = 0;

         @( negedge clk );

         if ( str_idx >= test_str_buffer.len() ) begin
            automatic int wd_sz = $dist_uniform(seed,1,wd_len_max);
            automatic int wd_ws = $dist_uniform(seed,1,4);
            automatic bit fake_word = $dist_uniform(seed,1,10) == 1;

            test_str_buffer = "";
            str_idx = 0;

            if ( fake_word )
              test_str_buffer = { test_str_buffer, sample( str_wd_notstart ) };
            else
              test_str_buffer = { test_str_buffer, sample( str_wd_start ) };
            for ( int j=1; j<wd_sz; j++ )
              test_str_buffer = { test_str_buffer, sample( str_wd_part ) };
            for ( int j=0; j<wd_ws; j++ )
              test_str_buffer = { test_str_buffer, sample( str_wd_not ) };

         end

         reset = do_reset;
         char = test_str_buffer[str_idx++];

         if ( round < 1 ) begin

         end else begin

         end
         str_win = { str_win.substr(1,9), char };

         pw_start = w_start;
         pw_part = w_part;
         pw_ended = w_ended;

         @( posedge clk_reactive );

         if ( do_reset ) begin

            was_in_word = 0;
            was_word_char = 0;
            in_word = 0;
            shadow_w_en = 0;
            shadow_w_pa = 0;
            shadow_w_st = 0;

         end else begin

            was_in_word = in_word;
            shadow_w_st = !was_word_char && char_wd_start[char];
            in_word =
              was_in_word && char_wd_part[char] || shadow_w_st;
            shadow_w_pa = in_word;
            shadow_w_en = was_in_word && !in_word;
            was_word_char = char_wd_part[char];

         end
         if ( do_reset ) begin

            shadow_nc = 0;
            shadow_nw = 0;
            shadow_avg = 0;
            lens.delete();
            len_sum = 0;

         end else if ( was_in_word && in_word ) begin

            shadow_nc++;

         end else if ( shadow_w_en ) begin

            shadow_nw++;
            len_sum += shadow_nc;
            lens.push_front(shadow_nc);
            if ( lens.size() > win_sz )
              len_sum -= lens.pop_back();
            if ( lens.size() == win_sz )
              shadow_avg = len_sum / win_sz;

         end else if ( shadow_w_st ) begin
            shadow_nc = 1;
         end

         n_err_pre = n_err;
         if ( w_start !== shadow_w_st ) begin
            n_err_w_st++;
            n_err++;
         end
         if ( w_part !== shadow_w_pa ) begin
            n_err_w_pa++;
            n_err++;
         end
         if ( w_ended !== shadow_w_en ) begin
            n_err_w_en++;
            n_err++;
         end
         if ( n_err_pre != n_err ) begin
            if ( testbench.n_err_sh_state++ < 4 ) show_err = 1;
         end

         if ( shadow_nw !== nw )
           begin
              n_err_nw++; n_err++; need_reset = 1;
              if ( testbench.n_err_sh_nw++ < 4 ) show_err = 1;
           end
         if ( shadow_avg !== lavg )
           begin
              n_err_lavg++; n_err++; need_reset = 1;
              if ( testbench.n_err_sh_avg++ < 4 ) show_err = 1;
           end
         if ( shadow_nc !== len ) begin
            n_err_nc++; n_err++;
            if ( testbench.n_err_sh_nc++ < 4 ) show_err = 1;
         end

         begin
            automatic string hd =
              "       W-M    I  Text---->|         SPE L  N A {D}";
            automatic string item =
              $sformatf
                ("Trace %2d-%1d %4d \"%10s\"  %s %s%s%s %s%s%s %1d %2d %1d {%1d}",
                 win_sz, wd_len_max, i, str_win,
                 do_reset ? "R" : " ",
                 fbit(pw_start,"s"), fbit(pw_part,"p"), fbit(pw_ended,"e"),
                 fbit(w_start,"S"), fbit(w_part,"P"), fbit(w_ended,"E"),
                 len, nw, lavg,
                 wd_cnt.char_az
                 );

            if ( n_err != n_err_pre )
              item =
                { item,
                  $sformatf(" <-Error Correct-> %s%s%s %1d %2d %1d",
                            shadow_w_st ? "S" : "_", shadow_w_pa ? "P" : "_",
                            shadow_w_en ? "E" : "_",
                            shadow_nc, shadow_nw, shadow_avg) };
            if ( i == 0 ) $write("%s\n",hd);
            if ( i < tr_initial_lines )
              $write("%s\n",item);
            else begin
               if ( tr_recent.size() > tr_err_context ) tr_recent.delete(0);
               tr_recent.push_back(item);
            end
         end

         if ( n_err != n_err_pre && show_err ) begin
            while ( tr_recent.size() > 0 )
              $write("%s\n",tr_recent.pop_front());
         end

      end

      $write
        ("Done with n_avg_of=%0d, max wd len=%0d. Errors: st %0d, pa %0d, en %0d, nc %0d, nw %0d, av %0d\n",
         win_sz, wd_len_max,
         n_err_w_st, n_err_w_pa, n_err_w_en,
         n_err_nc, n_err_nw, n_err_lavg);

      testbench.t_errs += n_err;
      done = 1;


   end

endmodule

// cadence translate_on