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

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


//////////////////////////////////////////////////////////////////////////////
///  Problem 0
//
 /// Look over the modules and enum below.
//
//

// Ensure that an omitted type results in an error message.
`default_nettype none

// Module names. (Used by the testbench.)
//
typedef enum { M_proc, M_iter, M_tree, M_iter_sol, M_tree_sol } M_Type;

// Hide the fact that we've memorized ASCII codes.
//
typedef enum
  { Char_0 = 48, Char_9 = 57,
    Char_A = 65, Char_Z = 90, Char_a = 97, Char_z = 122 }
  Chars_Special;


// A function version of atoi1 -- Convert ASCII character to a value.
//
function int atoi1_func( input logic [7:0] char, input int r );
      automatic int char_uc =
        char >= Char_a && char <= Char_z ? char - Char_a + Char_A : char;
      automatic int val_09 = char - Char_0;
      automatic int val_az = 10 + char_uc - Char_A;
      automatic bit is_09 = char>=Char_0 && char <= Char_9 && char < Char_0 + r;
      automatic bit is_az = char_uc >= Char_A && char_uc < Char_A + r - 10;
      atoi1_func = is_09 ? val_09 : is_az ? val_az : -1;
endfunction

module atoi1
  #( int r = 32, w = 10 )
   ( output logic [w-1:0] val,
     output logic is_digit,
     input uwire [7:0] char );
   always_comb begin
      automatic int valr = atoi1_func(char,r);
      is_digit = valr >= 0;
      val = is_digit ? valr : 0;
   end
endmodule

module mux2
  #( int w = 3 )
   ( output uwire [w-1:0] x,
     input uwire s,
     input uwire [w-1:0] a0, a1 );
   assign x = s ? a1 : a0;
endmodule

module mult_by_c
  #( int w_in = 8, int c = 16, int w_out = w_in+$clog2(c) )
   ( output uwire [w_out-1:0] prod, input uwire [w_in-1:0] a );
   assign prod = a * c;
endmodule

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


//////////////////////////////////////////////////////////////////////////////
///  Problem 1
//
 ///   Complete atoi_it so that it computes the string value as follows.
 ///
//
//     [✔] atoi_it must instantiate and use n atoi modules.
//     [✔] atoi_it must instantiate and use mult_by_c modules.
//     [✔] atoi_it must instantiate and use add modules.
//     [✔] Procedural code can be used, but not in place of atoi and mult_by_c.
//
//     [✔] DO NOT use atoi1_func in your solution.
//
//     [✔] 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 atoi_it
  #( int r = 11, n = 5, wv = $clog2( r**n ), wd = $clog2(n+1) )
   ( output logic [wv-1:0] val,
     output logic [wd-1:0] nd,
     input uwire [7:0] str [n-1:0] );

   /// SOLUTION

   uwire [wv-1:0] vali[n-1:-1];
   uwire is_valid[n-1:-1];
   uwire [wd-1:0] ndi[n-1:-1];
   assign is_valid[-1] = 1;
   assign ndi[-1] = 0;
   assign vali[-1] = 0;
   assign nd = ndi[n-1];
   assign val = vali[n-1];

   localparam int wcv = $clog2(r);

   for ( genvar i=0; i<n; i++ ) begin

      // Find Value of Digit i
      //
      uwire [wcv-1:0] valdr;
      uwire is_digit;
      atoi1 #(r,wcv) a( valdr, is_digit, str[i] );

      // Determine if this digit continues a sequence of valid digits
      // starting at str[0].
      //
      assign is_valid[i] = is_digit && is_valid[i-1];

      // Replace value with zero if str[i] is not a digit, or if the
      // string of valid digits has already ended.
      //
      uwire [wcv-1:0] vald = is_valid[i] ? valdr : 0;

      // Multiply (scale) the digit value based on its position in the number.
      //
      uwire [wv-1:0] vals;
      mult_by_c #( .w_in(wcv), .c(r**i), .w_out(wv) ) mc( vals, vald );

      // Add the scaled digit to the value accumulated so far.
      //
      add #(wv) a1( vali[i], vali[i-1], vals );

      // Update the number of digits so far.
      //
      assign ndi[i] = is_valid[i] ? i+1 : ndi[i-1];

   end

endmodule


module atoi_pr
  #( int r = 11, n = 5, wv = $clog2( r**n ), wd = $clog2(n+1) )
   ( output logic [wv-1:0] val,
     output logic [wd-1:0] nd,
     input uwire [7:0] str [n-1:0] );

   /// DO NOT Modify the module. Use it for reference.

   always_comb begin
      val = 0; nd = 0;
      for ( int i=0; i<n; i++ ) begin
         // Get val of current char. If val is < 0 then char is not a digit.
         automatic int dval = atoi1_func(str[i],r);
         if ( dval < 0 ) break;
         val += dval * r**i;
         nd++;
      end
   end

endmodule


//////////////////////////////////////////////////////////////////////////////
///  Problem 2
//
 ///   Complete atoi_tr so that it computes the string value as follows.
 ///
//
//     [✔] atoi_tr must recurisvely instantiate two instances of itself.
//     [✔] atoi_tr must instantiate and use atoi modules.
//     [✔] atoi_tr must instantiate and use mult_by_c modules.
//     [✔] Procedural code can be used, but not in place of atoi and mult_by_c.
//
//     [✔] DO NOT use atoi1_func in your solution.
//
//     [✔] 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 atoi_tr
  #( int r = 11, n = 5, wv = $clog2( r**n ), wd = $clog2(n+1) )
   ( output uwire [wv-1:0] val,
     output var logic [wd-1:0] nd,
     input uwire [7:0] str [n-1:0] );

   /// SOLUTION

   if ( n == 1 ) begin

      uwire is_dd;
      uwire [wv-1:0] valr;
      atoi1 #(r,wv) a( valr, is_dd, str[0] );
      assign val = is_dd ? valr : 0;
      assign nd = is_dd; // Note: nd may be more than one bit.

   end else begin

      // Prepare to split the input string into two halves. Note that
      // the hi half may be larger, and so we use nhi to compute the
      // number of bits needed in the value output (vwh) and the
      // number of digits output (dwh).
      //
      localparam int nlo = n/2;
      localparam int nhi = n - nlo;
      localparam int vwh = $clog2( r**nhi );
      localparam int dwh = $clog2( nhi+1 );
      //
      uwire [vwh-1:0] vallo, valhi;
      uwire [dwh-1:0] ndlo, ndhi;

      // Split input string between two recursive instantiations
      //
      atoi_tr #(r,nlo,vwh,dwh) alo( vallo, ndlo, str[nlo-1:0] );
      atoi_tr #(r,nhi,vwh,dwh) ahi( valhi, ndhi, str[n-1:nlo] );

      // Determine whether the hi half of the string may be part
      // of the number.
      //
      uwire hitoo = ndlo == nlo;
      uwire [vwh-1:0] valhid = hitoo ? valhi : 0;

      // Scale the upper half.
      //
      uwire [wv-1:0] valhis;  // VALue HIgh Scaled
      mult_by_c #(vwh,r**nlo,wv) mc( valhis, valhid );

      assign val = vallo + valhis;
      assign nd = hitoo ? nlo + ndhi : ndlo;

   end

endmodule


//////////////////////////////////////////////////////////////////////////////
/// Testbench Code


// cadence translate_off

module testbench;

   localparam int nnsets = 7;
   localparam int nset[nnsets] = '{ 1, 2, 3, 4,  7, 8, 9 };

   localparam int npsets = 2;
   localparam int pset[npsets] = '{ 10, 16 };

   localparam int nmsets = 2;
   localparam M_Type mset[nmsets] = '{ M_tree, M_iter };

   string mtype_str[M_Type] =
          '{ M_proc:"atoi_pr", M_tree:"atoi_tr", M_iter:"atoi_it",
             M_tree_sol:"a_tr_sol", M_iter_sol:"a_it_sol" };

   int t_errs_len_mod[M_Type];
   int t_errs_val_mod[M_Type];
   int t_errs_len_r[int];
   int t_errs_val_r[int];
   int t_errs_len_n[int];
   int t_errs_val_n[int];

   int t_errs;     // Total number of errors.
   initial t_errs = 0;
   final begin
      for ( int i=0; i<npsets; i++ )
        $write("Total errors for radix %2d: %5d len, %5d val\n",
               pset[i], t_errs_len_r[pset[i]],
               t_errs_val_r[pset[i]]);
      for ( int i=0; i<nnsets; i++ )
        $write("Total errors for string length %2d: %5d len, %5d val\n",
               nset[i], t_errs_len_n[nset[i]],
               t_errs_val_n[nset[i]]);
      for ( int i=0; i<nmsets; i++ )
        $write("Total errors for mod %4s: %5d len, %5d val\n",
               mtype_str[mset[i]], t_errs_len_mod[mset[i]],
               t_errs_val_mod[mset[i]]);
      $write("Total number of errors: %0d\n",t_errs);
   end

   localparam int nsets = nnsets * npsets * nmsets;

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

   // Instantiate a testbench at each size.
   //
   for ( genvar m=0; m<nmsets; m++ )
     for ( genvar n=0; n<nnsets; n++ )
       for ( genvar i=0; i<npsets; i++ )
         begin
            localparam int idx = m * npsets * nnsets + n * npsets + i;
            testbench_r #(pset[i],nset[n],mset[m])
            t2( .done(d[idx]), .tstart(d[idx-1]) );
       end

endmodule


module testbench_r
  #( int r = 16, n = 3,
     M_Type mtype = M_proc )
   ( output logic done, input uwire tstart );

   localparam int w = $clog2(r**n);
   localparam int ntests = 500;
   localparam int wd = $clog2(n+1);

   uwire [wd-1:0] nd;
   uwire [w-1:0] val;
   logic [7:0] str[n-1:0];

   string mtype_str[M_Type] =
          '{ M_proc:"atoi_pr", M_tree:"atoi_tr", M_iter:"atoi_it",
                M_tree_sol:"a_tr_sol", M_iter_sol:"a_it_sol" };

   case ( mtype )
     M_proc: atoi_pr #(r,n,w) a8( val, nd, str );
     M_tree: atoi_tr #(r,n,w) a8( val, nd, str );
     M_iter: atoi_it #(r,n,w) a8( val, nd, str );
     M_tree_sol: atoi_tr_sol #(r,n,w) a8( val, nd, str );
     M_iter_sol: atoi_it_sol #(r,n,w) a8( val, nd, str );
   endcase

   logic [7:0] non_digit[256];

   function string to_string(input logic [w-1:0] val);

      automatic string result = "";
      if ( val == 0 ) result = "0";
      while ( val ) begin

         automatic int d = val % r;
         automatic int v = d < 10 ? d + Char_0 : d - 10 + Char_A;
         val = val / r;
         result = { string'(v), result };
      end
      to_string = result;
   endfunction

   initial begin
      automatic int nd_size = 0;
      automatic int rm10 = r > 10 ? 10 : r;
      automatic bit err_silent = testbench.t_errs > 10;
      for ( int i=32; i<128; i++ ) begin
         if ( i >= Char_0 && i < Char_0 + rm10 ) continue;
         if ( i >= Char_A && i < Char_A + r - 10 ) continue;
         if ( i >= Char_a && i < Char_a + r - 10 ) continue;
         non_digit[nd_size++] = i;
      end

      wait( tstart );

      for ( int tt=0; tt<3; tt++ ) begin

         automatic bit single_char = tt == 0;
         automatic bit space_pad = single_char || tt == 1;
         automatic int nttests = single_char ? r : ntests;
         automatic int n_err = 0, n_lerr = 0;
         automatic string ttype =
           single_char ? "Single_Char (SC)"
             : space_pad ? "Space_Pad (SP)" : "General (GE)";
         automatic string abbrev =
           single_char ? "SC" : space_pad ? "SP" : "GE";

         //  $write("Radix %2d, starting %s tests.\n", r, ttype);

      for ( int i=0; i<nttests; i++ ) begin
         automatic int len = single_char ? 1 : 1 + {$random} % n;
         automatic logic [w-1:0] sval = 0;

         for ( int j=0; j<len; j++ ) begin
            automatic int d = {$random} % r;
            automatic int char_a = {$random} % 1 ? Char_A : Char_a;
            if ( d == 0 && j == len - 1 ) d = 1;
            if ( single_char ) d = i;
            str[j] = d < 10 ? Char_0 + d : char_a + d - 10;
            sval += d * r ** j;
         end

         str[len] = space_pad ? " " : non_digit[{$random}%nd_size];

         for ( int j=len+1; j<n; j++ )
           str[j] = space_pad ? 32 : 32 + {$random}%(128-32);

         #1;

         if ( sval !== val ) begin
            n_err++;
            if ( !err_silent && n_err < 5 )
              $write("Mod-%s R-%2d n-%2d Ty-%s  Error val %s != %s (correct) for string \"%s\"\n",
                     mtype_str[mtype], r, n, abbrev, to_string(val), to_string(sval), string'(str));
         end
         if ( !single_char && len !== nd ) begin
            n_lerr++;
            if ( !err_silent && n_lerr < 10 )
              $write("Mod-%s R-%2d n-%2d Ty-%s  Error len %0d != %0d (correct) for string \"%s\"\n",
                     mtype_str[mtype],
                     r, n, abbrev, nd, len, string'(str) );
         end

         #1;

      end

         $write("Mod-%s Radix-%2d n-%2d Ty-%s, done with %0d tests, %0d val errors, %0d len errors.\n",
                mtype_str[mtype],
             r, n, abbrev, nttests, n_err, n_lerr);

            testbench.t_errs += n_err + n_lerr;
         testbench.t_errs_len_mod[mtype] += n_lerr;
         testbench.t_errs_val_mod[mtype] += n_err;
         testbench.t_errs_len_r[r] += n_lerr;
         testbench.t_errs_val_r[r] += n_err;
         testbench.t_errs_len_n[n] += n_lerr;
         testbench.t_errs_val_n[n] += n_err;
         if ( n_err + n_lerr ) err_silent = 1;
      end
      done = 1;
   end


endmodule

// cadence translate_on