////////////////////////////////////////////////////////////////////////////////
//
/// LSU EE 4755 Fall 2021 Homework 1
/// SOLUTION

 /// Assignment  https://www.ece.lsu.edu/koppel/v/2021/hw01.pdf
 /// Solution Discussion  https://www.ece.lsu.edu/koppel/v/2021/hw01_sol.pdf

`default_nettype none

//////////////////////////////////////////////////////////////////////////////
///  Problem 1
//
 ///   Complete insert_at so that output o is set to ia with ib inserted at pos. 
 ///
//
//     [✔] Make sure that the testbench does not report errors.
//     [✔] Module must be synthesizable. Use command: genus -files syn.tcl
//
//     [✔] Do not use procedural code.
//     [✔] Do not use the << or >> operators (or anything similar).
//     [✔] Use the shift and mask modules to provide shifted values
//         and bitmasks.
//
//     [✔] Don't assume any particular parameter value.
//
//     [✔] Code must be written clearly.
//     [✔] Pay attention to cost and performance.


module insert_at
  #( int wa = 20, wb = 10, wo = wa+wb, walg = $clog2(wa+1) )
   ( output logic [wo-1:0] o,
     input uwire [wa-1:0] ia,
     input uwire [wb-1:0] ib,
     input uwire [walg-1:0] pos );

   /// SOLUTION

   /// :Example: Input Values:
   //
   //  ia =              aaaaaaaa   (Each a is a bit of ia, it can be 0 or 1.)
   //  ib =                   bbb   (Each b is a bit of ib, it can be 0 or 1.)
   //  pos = 2
   //
   /// Desired Output Value
   //
   //  o      =       aaaaaabbbaa   (Notice that ib is inserted at pos 2.)

   uwire [wa-1:0] mask_low;
   mask_lsb #(wa) ml(mask_low, pos);

   uwire [wa-1:0] ia_low = ia & mask_low;
   uwire [wa-1:0] ia_high_low = ia & ~mask_low;

   //  ia =              aaaaaaaa
   //  mask_low =        00000011  (Two low bits are 1 because pos=2.)
   //  ia_low =          000000aa  (ia_low has the bits to the right of pos.)
   //  ia_high_low =     aaaaaa00  (ia_high_low: the bits to the left of pos.)

   localparam int wblg = $clog2(wb);
   uwire [wo-1:0] ia_high;
   shift_left #(wa,wo,wblg) slc( ia_high, ia_high_low, wblg'(wb) );

   //  ia_high_low =     aaaaaa00  
   //  ia_high =      aaaaaa00000 (Shift wb bits to make room for ib.)

   uwire [wo-1:0] ib_at_pos;
   shift_left #(wb,wo,walg) slb( ib_at_pos, ib, pos );

   //  ib =                   bbb   
   //  ib_at_pos =    000000bbb00 (Shifted pos bits, and widened to wo bits.)

   assign o = ia_high | ib_at_pos | ia_low;

   //  ia_high =      aaaaaa00000
   //  ib_at_pos =    000000bbb00
   //  ia_low =          000000aa
   //  o      =       aaaaaabbbaa

endmodule


module shift_left
  #( int wi = 4, wo = wi, wolg = $clog2(wo) )
   ( output uwire [wo-1:0] o,
     input uwire [wi-1:0] i,
     input uwire [wolg-1:0] amt );
   assign o = i << amt;
endmodule

module shift_right
  #( int wi = 4, wo = wi, wolg = $clog2(wo) )
   ( output uwire [wo-1:0] o,
     input uwire [wi-1:0] i,
     input uwire [wolg-1:0] amt );
   assign o = i >> amt;
endmodule

module mask_lsb
  #( int wo = 6, wp = $clog2(wo+1) )
   ( output logic [wo-1:0] o, input uwire [wp-1:0] n1 );
   always_comb for ( int i=0; i<wo; i++ ) o[i] = i < n1;
endmodule

module mask_msb
  #( int wo = 6, wp = $clog2(wo+1) )
   ( output logic [wo-1:0] o, input uwire [wp-1:0] n1 );
   always_comb for ( int i=0; i<wo; i++ ) o[wo-i-1] = i < n1;
endmodule



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

// cadence translate_off

module testbench;

   logic done [1:-1];
   initial done[-1] = 1;

   testbench_size #(8,3, "Set 1") tb1(done[0],done[-1]);
   testbench_size #(4,5, "Set 2") tb2(done[1],done[0]);

endmodule

module testbench_size
  #( int wa = 8, int wb = 3, string label = "set me" )
   ( output logic done_me,
     input uwire logic done_pred );

   localparam int wo = wa+wb;
   localparam int walg = $clog2(wa+1);

   localparam int n_tests = (wa+1) * 3;


   logic [wa-1:0] ia;
   logic [wb-1:0] ib;
   uwire [wo-1:0] o;
   logic [walg-1:0] pos;

   insert_at #(wa,wb) iat(o, ia, ib, pos);

   initial begin

      automatic int n_err = 0;

      wait ( done_pred === 1 );

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

         automatic int rnd = tn / (wa+1);

         logic [wo-1:0] o_shadow;

         pos = tn % (wa+1);
         case ( rnd )
            0: begin ia = -1; ib = 0; end
            1: begin ia = 0;  ib = -1; end
            default: {ia,ib} = {$random};
         endcase

         #1;

         for ( int i=0; i<pos; i++ ) o_shadow[i] = ia[i];
         for ( int i=0; i<wb; i++ ) o_shadow[i+pos] = ib[i];
         for ( int i=pos; i<wa; i++ ) o_shadow[i+wb] = ia[i];

         if ( o_shadow !== o ) begin
            n_err++;
            if ( n_err < 6 )
              $write("Error %s for ia=%b  ib=%b  pos=%d  %b != %b (correct)\n",
                     label,
                     ia, ib, pos, o, o_shadow);
         end

      end

      $write("For %s, done with %0d tests, %0d errors found.\n",
             label, n_tests, n_err);

      done_me = 1;

   end

endmodule

// cadence translate_on