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

 /// Assignment  http://www.ece.lsu.edu/koppel/v/2016/hw02.pdf


`default_nettype none

//////////////////////////////////////////////////////////////////////////////
///  Problem 1
//
 /// Modify aa_digit_val so that it works for any radix, not just 10.
//
//     [x] The code must be synthesizable.
//     [x] Make sure that the testbench does not report errors.
//     [x] Can use behavioral or implicit structural code.

module aa_decimal_digit_val
  ( output wire [3:0] val,
    output wire is_dig,
    input wire [7:0] char );

   // Do not edit this module.

   assign       is_dig = char >= "0" && char <= "9";
   assign       val = is_dig ? char - "0" : 0;

endmodule

module aa_digit_val
  #( int radix = 10 )
   ( output wire [3:0] val,
     output wire is_dig,
     input wire [7:0] char );

   /// SOLUTION

   // Check whether char is in range 0-9 or a-f, regardless of radix.
   //
   wire  is_dig_09 = char >= "0" && char <= "9";
   wire  is_dig_af = char >= "a" && char <= "f";

   // Convert char to binary, assuming that it is hexadecimal.
   //
   wire [3:0] val_raw = is_dig_09 ? char - "0" : char - "a" + 10;

   // Determine whether char is a valid digit in radix radix.
   //
   assign     is_dig = ( is_dig_09 || is_dig_af ) && val_raw < radix;

   assign     val = is_dig ? val_raw : 0;

endmodule


//////////////////////////////////////////////////////////////////////////////
///  Problem 2
//
 /// Modify aa_full_adder so that it adds two radix-RADIX ASCII-encoded digits.
//
//     [x] The code must be synthesizable.
//     [x] Make sure that the testbench does not report errors.
//     [x] Can use behavioral or implicit structural code.


module aa_full_adder
  #( int radix = 10 )
   ( output wire [7:0] sum,
     output wire carry_out,
     output wire is_dig_out,
     input wire [7:0] a, b,
     input wire carry_in,
     input wire is_dig_in);

   /// SOLUTION

   // Instantiate two aa_digit_val modules, connecting one to each
   // input digit. These will determine whether each character input
   // is a valid digit and if so provide the binary value of the
   // digit.
   //
   wire [3:0] val_a, val_b;
   wire       is_dig_a, is_dig_b;
   aa_digit_val #(radix) dva(val_a, is_dig_a, a);
   aa_digit_val #(radix) dvb(val_b, is_dig_b, b);

   // Compute the sum of carry_in and the binary versions of a and
   // b. Note that the sum may contain a carry, and so it can not be
   // assigned to the module output.
   //
   wire [4:0] sum_val = carry_in + val_a + val_b;

   // Determine whether there is a carry out.
   //
   assign     carry_out = sum_val >= radix;

   // Determine the sum, in binary, with the carry removed.
   //
   wire [3:0] sum_dig_val = carry_out ? sum_val - radix : sum_val;

   // Convert the sum to ASCII or to a blank if we don't have a valid digit.
   //
   assign sum = !is_dig_out ? " " :
          sum_dig_val < 10 ? "0" + sum_dig_val : "a" + sum_dig_val - 10;


   // If the value of is_dig_out, below, is true then output sum will
   // be set to a digit of the sum. Otherwise sum should be set to a
   // blank. The value of is_dig_out will be false when we are past
   // the last digit of both a and b, and we don't have a carry out
   // from the previous digit.
   //
   assign is_dig_out = is_dig_in && ( carry_in || is_dig_a || is_dig_b );


endmodule


module aa_width2
  #( int radix = 10 )
   ( output wire [1:0][7:0] sum,
     output wire c_out,
     output wire is_dig_out,
     input wire [1:0][7:0] a, b,
     input wire c_in,
     input wire is_dig_in);

   wire   co0, id_0;

   aa_full_adder #(radix) fa1(sum[0],co0,id_0,a[0],b[0],c_in,is_dig_in);
   aa_full_adder #(radix) fa2(sum[1],c_out,is_dig_out,a[1],b[1],co0,id_0);

endmodule


module reference_adder
  #( int radix = 10,
     int digits = 2,
     int width = $clog2( radix ** digits ) )
   ( output logic [width-1:0] sum,
     output logic carry_out,
     input wire [width-1:0] a, b,
     input wire carry_in );

   always_comb { carry_out, sum } = 0 + carry_in + a + b;

endmodule


//////////////////////////////////////////////////////////////////////////////
/// Testbench Code
//
//  The code below instantiates some of the modules above,
//  provides test inputs, and verifies the outputs.
//
//  The testbench may be modified to facilitate your solution. Of
//  course, the removal of tests which your module fails is not a
//  method of fixing a broken module. (One might modify the testbench
//  so that the first tests it performs are thoe which make it easier
//  to determine what the problem is, for example, test inputs that
//  are all 0's or all 1's.)


// cadence translate_off


// Convert integer A into a radix RADIX ASCII representation.
function automatic string radtos(int unsigned a, int radix);
      begin
         radtos = "";
         while ( 1 )
           begin
              automatic int dig = a % radix;
              if ( radtos.len() > 0 && a == 0 ) break;
              a = a / radix;
              radtos = { dig < 10 ? "0" + dig : "a" + dig - 10, radtos };
           end
      end
endfunction

module aa_test;

   logic start_done[32];

   for ( genvar radix = 2; radix <= 16; radix++ )
     aa_test_digit_val #(radix) aa(start_done[radix],start_done[radix-1]);

   for ( genvar radix = 2; radix <= 16; radix++ )
     aa_test_width2 #(radix) aa(start_done[15+radix],start_done[15+radix-1]);

   initial begin

      start_done[1] = 1;

   end

endmodule

module aa_test_digit_val #( int radix = 10 )
   ( output logic done, input wire start );

   localparam int err_limit = 10;

   logic [7:0] a;
   wire [3:0]  dval;
   wire        is_d;

   aa_digit_val #(radix) dv1(dval,is_d,a);

   int digit_vals[256];

   int num_errs;

   initial begin
      num_errs = 0;

      wait ( start == 1 );

      digit_vals = { 256 { -1 } };
      for ( int i=0; i<radix; i++ )
        digit_vals[ i < 10 ? "0" + i : "a" + i - 10 ] = i;

      for ( int i=0; i<256; i++ )
        begin
           automatic bit is_d_shadow = digit_vals[i] >= 0 ;
           #1;
           a = i;
           #1;
           if ( is_d != is_d_shadow ) begin
              $write
                ("Error in aa_digit_val for char %c (%0d) radix %0d, is_d %0d != %0d (correct)\n",
                 i, i, radix, is_d, is_d_shadow );
              num_errs++;
              if ( num_errs > err_limit ) $finish(2);
              continue;
           end
           if ( is_d_shadow && dval != digit_vals[i] ) begin
              $write
                ("Error in aa_digit_val for char %c (%0d) radix %0d: val %0d != %0d (correct)\n",
                 i, i, radix, dval, digit_vals[i]);
              num_errs++;
              if ( num_errs > err_limit ) $finish(2);
              continue;
           end

        end

      done = 1;

   end

endmodule

module aa_test_width2 #( int radix = 10 )
   ( output logic done, input wire start );

   localparam int err_limit = 10;
   localparam int max_digits = 2;
   localparam int max_dno = max_digits - 1;
   localparam int num_tests = 100;

   wire [max_dno:0][7:0] sum;
   logic [max_dno:0][7:0] a, b, shadow_sum;
   wire        co;
   logic       fo, ci, fi;

   aa_width2 #(radix) fa1(sum[1:0],co,fo,a[1:0],b[1:0],ci,fi);

   int unsigned aval, bval, ssum_val;

   int num_errs;

   initial begin
      num_errs = 0;

      wait ( start == 1 );

      for ( int i=0; i<num_tests; i++ )
        begin
           automatic int width = max_digits;
           aval = {$random()} >> 1;
           bval = {$random()} >> 1;
           ssum_val = aval + bval;
           a = radtos(aval,radix);
           b = radtos(bval,radix);
           shadow_sum = radtos(ssum_val,radix);
           ci = 0;
           fi = 1;
           #1;

           if ( sum !== shadow_sum ) begin

              $write("Error %s + %s != %s (%s correct).\n",
                     a,b,sum,shadow_sum);
              num_errs++;
              if ( num_errs > err_limit ) $finish(2);

           end

           #1;

        end

      done = 1;

   end

endmodule


// cadence translate_on