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

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



`default_nettype none

//////////////////////////////////////////////////////////////////////////////
///  Problem 1
//
 /// Modify
 ///
//
//     [✔] nn4x4b must instantiate exactly four nn1x4b modules.
//     [✔] nn1x4b must instantiate exactly two nn1x2 modules.
//
//     [✔] 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.
//     [✔] Pay attention to port widths. Do not make them larger than needed.
//
//     [✔] Code must be written clearly.

module nn4x4
  #( int wa = 10, ww = 5 )
   ( output uwire [wa-1:0] ao[4],
     input uwire [wa-1:0] ai[4],
     input uwire [ww-1:0] wht[4][4] );

   /// DO NOT MODIFY THIS ROUTINE.

   assign ao[0] = ai[0] * wht[0][0] + ai[1] * wht[0][1]
          + ai[2] * wht[0][2] + ai[3] * wht[0][3];

   assign ao[1] = ai[0] * wht[1][0] + ai[1] * wht[1][1]
          + ai[2] * wht[1][2] + ai[3] * wht[1][3];

   assign ao[2] = ai[0] * wht[2][0] + ai[1] * wht[2][1]
          + ai[2] * wht[2][2] + ai[3] * wht[2][3];

   assign ao[3] = ai[0] * wht[3][0] + ai[1] * wht[3][1]
          + ai[2] * wht[3][2] + ai[3] * wht[3][3];

endmodule

module nn4x4b
  #( int wa = 10, ww = 5 )
   ( output uwire [wa-1:0] ao[4],
     input uwire [wa-1:0] ai[4],
     input uwire [ww-1:0] wht[4][4] );

   /// SOLUTION

   nn1x4b #(wa,ww) n0( ao[0], ai, wht[0] );
   nn1x4b #(wa,ww) n1( ao[1], ai, wht[1] );
   nn1x4b #(wa,ww) n2( ao[2], ai, wht[2] );
   nn1x4b #(wa,ww) n3( ao[3], ai, wht[3] );

endmodule

module nn1x4b
  #( int wa = 10, ww = 5 )
   ( output uwire [wa-1:0] ao,
     input uwire [wa-1:0] ai[4],
     input uwire [ww-1:0] wht[4] );

   /// SOLUTION

   uwire [wa-1:0] aoa, aob;
   nn1x2b #(wa,ww) n0(aoa, ai[0:1], wht[0:1] );
   nn1x2b #(wa,ww) n1(aob, ai[2:3], wht[2:3] );
   assign ao = aoa + aob;

endmodule

module nn1x2b
  #( int wa = 10, ww = 5 )
   ( output uwire [wa-1:0] ao,
     input uwire [wa-1:0] ai[2],
     input uwire [ww-1:0] wht[2] );

   /// SOLUTION

   assign ao = ai[0] * wht[0] + ai[1] * wht[1];

endmodule



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

module nnOxI
  #( int no = 4, ni = 4, wa = 10, ww = 5 )
   ( output logic [wa-1:0] ao[no],
     input uwire [wa-1:0] ai[ni],
     input uwire [ww-1:0] wht[no][ni] );

      /// DO NOT MODIFY THIS ROUTINE.

     always_comb
       for ( int o = 0;  o < no;  o++ ) begin
          ao[o] = 0;
          for ( int i=0; i<ni; i++ ) ao[o] += ai[i] * wht[o][i];
       end

endmodule

// cadence translate_off

module testbench;

   localparam int wa = 16;
   localparam int ww = 8;
   localparam int ni = 4;  // Number of input neurons.
   localparam int no = 4;  // Number of output neurons.
   localparam int nmut = 3;

   localparam int ntests = 10;

   logic [wa-1:0] ai[ni];
   uwire [wa-1:0] ao[nmut][no];
   logic [ww-1:0] wht[no][ni];

   string mname[] = { "Behav", "Flat", "Sol" };

   typedef struct { string name; int no, ni; } Test_Set;
   Test_Set ts[] = '{ '{ "n12", 1, 2 }, '{ "n14", 1, 4 }, '{ "n44", 4, 4 } };

   nnOxI #(no,ni,wa,ww) nn1i(ao[0],ai,wht);
   nn4x4 #(wa,ww) nn2i(ao[1],ai,wht);
   nn4x4b #(wa,ww) nn3i(ao[2],ai,wht);

   initial begin

      automatic int mut = 2;
      automatic string test_summary = "";

      $write("Testing module %s\n", mname[mut]);

      foreach ( ts[ti] ) begin

         automatic Test_Set tinfo = ts[ti];
         automatic int n_err = 0;

         $write("\n** Starting test set %s  (%0d outputs, %0d inputs) **\n",
                tinfo.name, tinfo.no, tinfo.ni );

         for ( int tnum=0; tnum < ntests;  tnum++ ) begin

            for ( int io=0; io<no; io++ )
              for ( int ii=0; ii<ni; ii++ )
                wht[io][ii] = io < tinfo.no && ii < tinfo.ni ? {$random} : 0;

            for ( int ii=0; ii<ni; ii++ )
              ai[ii] = ii < tinfo.ni ? {$random} : 0;

            #1;

            for ( int io=0; io<tinfo.no; io++ ) begin

               if ( ao[0][io] !== ao[mut][io] ) begin
                  n_err++;
                  if ( n_err < 4 )
                    $write
                      ("Error test # %0d, output %0d: %0d != %0d (correct)\n",
                       tnum, io, ao[mut][io], ao[0][io] );
               end
            end
         end

         begin
            automatic string msg =
              $sformatf("%0d %s tests on %s: %0d errors found.",
                        ntests, tinfo.name, mname[mut], n_err);
            $write("Done with %s\n",msg);
            test_summary = { test_summary, $sformatf("Results of %s\n", msg) };
         end

      end

      $write("\n** Summary of Results **\n%s", test_summary);

   end


endmodule

// cadence translate_on