// Testbench for associative memory module.

// For use in LSU EE 4702 Spring 2000.

// Tested modules are in file am.v


`define dsize 8
`define dbits `dsize-1:0
`define ksize 8
`define kbits `ksize-1:0
`define size_lg 5
`define sbits (`size_lg-1):0
`define size (1<<`size_lg)
`define srange `size-1:0

// The number of rounds is the number of times a particular key
// is used in a test. In each round of a test a particular operation
// is applied to a key.  For example, in a particular test the first
// time any key is used it will be "insert"ed into the am, the
// second time (round) any key is used it will be "remove"d.  These
// operations are specified by strings passed to the do_test task.

`define maxrounds 20

// The shadow arrays hold three more elements than the associative array
// can. The idea is to try to insert something when the am is full.
`define size_plus (`size+3)
`define srange_plus (`size_plus-1):0
// Works for size_lg >= 2
`define sbits_plus `size_lg:0

module am_test();

   wire [`dbits] dout;
   wire          found, rdy, full;
   reg [`dbits]  din;
   reg [`kbits]  key;
   reg [2:0]     op;
   reg           clk;
   
   am am1(dout,found,rdy,full,din,key,op,clk);

   // States for the shadow array.
   parameter     st_never_used = 0;
   parameter     st_in = 1;
   parameter     st_removed = 2;

   // Parameters from am duplicated because Leonardo does not preserve
   // them in synthesized modules.
   parameter op_reset = 0;
   parameter op_insert = 1;
   parameter op_remove = 2;
   parameter op_lookup = 3;
   parameter op_nop = 4;

   // Shadow registers keep track of test data.  They do not exactly
   // duplicate the similarly named registers in the module.
   reg [`dbits] shadow_data [`srange_plus], exp_data;
   reg [`kbits] shadow_key [`srange_plus];
   reg [2:0]    shadow_state [`srange_plus];
   integer      shadow_round [`srange_plus];
   integer      shadow_occ;     
   integer      round_op[`maxrounds-1:0];
   // Expected outputs of module.
   reg          exp_full, exp_found;

   integer      test_index [`size_plus*`maxrounds-1:0];
   integer      test_size;
   time         timeout;
   reg          valid;

   integer      i;

   function [31:0] randi;
      input [31:0] limit;
      randi = ( $random >> 1 ) % limit;
   endfunction // randi

   // Perform a test specified by a string. (See comment near maxrounds, above.)
   task do_test;
      input [(`maxrounds+1)*8-1:0] rts_po;
      reg [(`maxrounds+1)*8-1:0] op_str;
      integer rounds, r;

      begin
         $display("Starting test %s",rts_po);

         // Determine number of rounds and reverse string.
         rounds = 0; 
         while( rts_po[7:0] )
           begin
              op_str = op_str << 8;
              op_str[7:0] = rts_po[7:0];
              rts_po = rts_po >> 8;
              rounds = rounds + 1;
           end

         // Initialize round_op array based on characters in string.
         for(r=0; r<rounds; r=r+1) begin
            case( op_str[7:0] )
              "r": round_op[r] = op_remove;
              "i": round_op[r] = op_insert;
              "l": round_op[r] = op_lookup;
              "n": round_op[r] = op_nop;
              default: begin
                 $display("Unexpected op in test string.");
                 $stop;
              end
            endcase // case( op_str[7:0] )
            op_str = op_str >> 8;
         end

         // Prepare the array of key indices.
         init_test(rounds);
         // Perform the actual tests.
         execute_test;
         
      end
      
   endtask // parse_test
   
   
   task init_test;
      input [31:0] rounds;
      integer i, j, r, temp, pos;

      begin
         test_size = `size_plus * rounds;
         // Set array test_index to: 
         //   0 1 2 ... `size_plus-1  0 1 2 ... `size_plus-1  0 1 ...
         j = 0;
         for(r=0; r<rounds; r=r+1) for(i=0; i<`size_plus; i=i+1) begin
            test_index[j] = i;
            j = j + 1;
         end 
         // Randomly rearrange the elements of test_index.
         for(j=0; j<test_size; j=j+1) begin
            pos = randi(test_size);
            temp = test_index[j]; test_index[j] = test_index[pos];
            test_index[pos] = temp;
         end
         // Initialize the array keeping track of the round for a key.
         for(i=0; i<`size_plus; i=i+1) shadow_round[i] = 0;
      end
      
   endtask // init_test

   task execute_test;

      integer i, j, new_data;
      
      begin

         @( negedge clk );

         if( !rdy ) begin $display("Expected module to be ready."); $stop; end

         for(i=0; i<test_size; i=i+1) begin

            // j is the element number in shadow_foo to use.
            j = test_index[i];

            // Command the am to perform the indicated operation, and
            // determine the expected outputs.  Update shadow_foo.
            // Note that the (correct) current outputs are different
            // than the expected outputs so expected outputs use
            // delayed assignments.
            
            case( round_op[shadow_round[j]] )
              op_insert: begin
                 new_data = $random;
                 op = op_insert;
                 key = shadow_key[j];
                 din = new_data;
                 exp_found <= @( negedge rdy ) shadow_state[j] === st_in;
                 exp_data <= @( negedge rdy ) new_data;
                 if( shadow_state[j] !== st_in && !full ) begin
                    shadow_occ <= @( negedge rdy ) shadow_occ + 1;
                    if( shadow_occ === `size - 1) exp_full <= @( negedge rdy ) 1;
                    shadow_state[j] = st_in;
                 end
                 shadow_data[j] = new_data;
              end

              op_lookup: begin
                 op = op_lookup;
                 key = shadow_key[j];
                 din = $random;
                 exp_found <= @( negedge rdy ) shadow_state[j] === st_in;
                 exp_data <= @( negedge rdy ) shadow_data[j];
              end

              op_remove: begin
                 op = op_remove;
                 key = shadow_key[j];
                 din = $random;
                 exp_found <= @( negedge rdy ) shadow_state[j] === st_in;
                 exp_data <= @( negedge rdy ) shadow_data[j];
                 if( shadow_state[j] === st_in ) begin
                    shadow_occ <= @( negedge rdy ) shadow_occ - 1;
                    exp_full <= @( negedge rdy ) 0;
                    shadow_state[j] = st_removed;
                 end
              end // case: op_remove

              op_nop: begin
                 op = op_nop;
                 key = shadow_key[j];
                 din = $random;
                 exp_found <= @( negedge rdy ) 0;
              end

            endcase // case( round_op[shadow_round[j]] )

            shadow_round[j] = shadow_round[j] + 1;

            if( !rdy ) begin
               $display("Should be ready.");
               $stop;
            end

            // A nop does not cause rdy to go low.
            if( op !== op_nop ) @( posedge rdy );

         end // for(i=0;

         op = op_nop;
         
      end
      
   endtask // execute_test

   always #10 clk = !clk;

   // Display an error message if rdy does not go high after a certain
   // amount of time.

   always @( valid or negedge rdy )
      fork:WATCHDOG
         #(timeout) begin
            $display("Module did not respond in time.");
            $stop;
         end
         @( posedge rdy ) disable WATCHDOG;
      join

   // Check the outputs at appropriate times.
   always @( posedge rdy or found or dout or full or valid )
     if( valid && rdy ) #1 begin

      if( found !== exp_found ) begin
         $display("Disagreement on found"); $stop;
      end

      if( found && dout !== exp_data ) begin
         $display("Disagreement on data."); $stop;
      end

      if( full !== exp_full ) begin
         $display("Disagreement on full."); $stop;
      end
      
     end // if ( valid && rdy )

   initial begin

      // Testbench can't test modules with less than four items.
      if( `size_lg < 2 ) begin
         $display("Testbench cannot handle size_lg less than 2."); $stop;
      end

      // Set to 1 when valid output expected.
      valid = 0;
      clk = 0;
      timeout = `size * 20 * 3;

      // Initialize shadow structures.

      for(i=0;i<`size_plus;i=i+1) begin
         shadow_state[i] = st_never_used;
         shadow_data[i] = $random;
         shadow_key[i] = {$random,i[`sbits_plus]};
      end

      shadow_occ = 0;

      exp_full = 0; exp_found = 0; exp_data = 0;

      // Reset module.
      op = op_nop;
      wait( rdy );
      op = op_reset;
      wait( !rdy );
      valid = 1;
      op = op_nop;
      wait( rdy );

      // Do the tests.
      //
      // Test coding:  "i", insert; "r", remove; "l", lookup; "n", nop.
      
      do_test("ir");   // Partially fill and empty.
      do_test("l");    // Make sure it's still empty.
      do_test("i");    // Make sure it's still empty, and fill it...
      do_test("r");    // ... and empty it ...
      do_test("iri");  // ... and fill it once again, though not monotonically.
      do_test("li");   // Make sure it's still full.
      do_test("r");    // Empty again.
      do_test("inrlirlirnirliri"); // Slowly fill.
      do_test("r");    // Quickly empty.

      $display("Completed all tests successfully.");
      $stop;

   end // initial begin
   
endmodule // am_test