// 
// -------------------------------------------------------------
//    Copyright 2004-2008 Synopsys, Inc.
//    Copyright 2010 Mentor Graphics Corporation
//    All Rights Reserved Worldwide
// 
//    Licensed under the Apache License, Version 2.0 (the
//    "License"); you may not use this file except in
//    compliance with the License.  You may obtain a copy of
//    the License at
// 
//        http://www.apache.org/licenses/LICENSE-2.0
// 
//    Unless required by applicable law or agreed to in
//    writing, software distributed under the License is
//    distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
//    CONDITIONS OF ANY KIND, either express or implied.  See
//    the License for the specific language governing
//    permissions and limitations under the License.
// -------------------------------------------------------------
// 

//------------------------------------------------------------------------------
// Title: Shared Register and Memory Access Test Sequences
//------------------------------------------------------------------------------
// This section defines sequences for testing registers and memories that are
// shared between two or more physical interfaces, i.e. are associated with
// more than one <uvm_reg_map> instance.
//------------------------------------------------------------------------------


//------------------------------------------------------------------------------
// Class: uvm_reg_shared_access_seq
//
// Verify the accessibility of a shared register
// by writing through each address map
// then reading it via every other address maps
// in which the register is readable and the backdoor,
// making sure that the resulting value matches the mirrored value.
//
// If bit-type resource named
// "NO_REG_TESTS" or "NO_REG_SHARED_ACCESS_TEST"
// in the "REG::" namespace
// matches the full name of the register,
// the register is not tested.
//
//| uvm_resource_db#(bit)::set({"REG::",regmodel.blk.r0.get_full_name()},
//|                            "NO_REG_TESTS", 1, this);
//
// Registers that contain fields with unknown access policies
// cannot be tested.
//
// The DUT should be idle and not modify any register during this test.
//
//------------------------------------------------------------------------------

class [docs]uvm_reg_shared_access_seq extends uvm_reg_sequence #(uvm_sequence #(uvm_reg_item));

   // Variable: rg
   // The register to be tested
   uvm_reg rg;

   `uvm_object_utils(uvm_reg_shared_access_seq)

   function [docs]new(string name="uvm_reg_shared_access_seq");
     super.new(name);
   endfunction


   virtual task [docs]body();
      uvm_reg_data_t  other_mask;
      uvm_reg_data_t  wo_mask[$];
      uvm_reg_field fields[$];
      uvm_reg_map maps[$];

      if (rg == null) begin
         `uvm_error("uvm_reg_shared_access_seq", "No register specified to run sequence on");
         return;
      end

      // Registers with some attributes are not to be tested
      if (uvm_resource_db#(bit)::get_by_name({"REG::",rg.get_full_name()},
                                             "NO_REG_TESTS", 0) != null ||
          uvm_resource_db#(bit)::get_by_name({"REG::",rg.get_full_name()},
                                             "NO_REG_SHARED_ACCESS_TEST", 0) != null )
        return;

      // Only look at shared registers
      if (rg.get_n_maps() < 2) return;
      rg.get_maps(maps);

      // Let's see what kind of bits we have...
      rg.get_fields(fields);

      // Identify unpredictable bits and the ones we shouldn't change
      other_mask = 0;
      foreach (fields[k]) begin
         int lsb, w;
         
         lsb = fields[k].get_lsb_pos();
         w   = fields[k].get_n_bits();
         
         if (!fields[k].is_known_access(maps[0])) begin
            repeat (w) begin
               other_mask[lsb++] = 1'b1;
            end
         end
      end
      
      // WO bits will always readback as 0's but the mirror
      // with return what is supposed to have been written
      // so we cannot use the mirror-check function
      foreach (maps[j]) begin
         uvm_reg_data_t  wo;
         wo = 0;
         foreach (fields[k]) begin
            int lsb, w;
            
            lsb = fields[k].get_lsb_pos();
            w   = fields[k].get_n_bits();
            
            if (fields[k].get_access(maps[j]) == "WO") begin
               repeat (w) begin
                  wo[lsb++] = 1'b1;
               end
            end
         end
         wo_mask[j] = wo;
      end
      
      // Try to write through each map
      foreach (maps[j]) begin
         uvm_status_e status;
         uvm_reg_data_t  prev, v;
         
         // The mirror should contain the initial value
         prev = rg.get();
         
         // Write a random value, except in those "don't touch" fields
         v = ({$random, $random} & ~other_mask) | (prev & other_mask);
         
         `uvm_info("uvm_reg_shared_access_seq", $sformatf("Writing register %s via map \"%s\"...",
                                    rg.get_full_name(), maps[j].get_full_name), UVM_LOW);
         
         `uvm_info("uvm_reg_shared_access_seq", $sformatf("Writing 'h%h over 'h%h", v, prev),UVM_DEBUG);
         
         rg.write(status, v, UVM_FRONTDOOR, maps[j], this);
         if (status != UVM_IS_OK) begin
            `uvm_error("uvm_reg_shared_access_seq", $sformatf("Status was %s when writing register \"%s\" through map \"%s\".",
                                        status.name(), rg.get_full_name(), maps[j].get_full_name()));
         end
         
         foreach (maps[k]) begin
            uvm_reg_data_t  actual, exp;
            
            `uvm_info("uvm_reg_shared_access_seq", $sformatf("Reading register %s via map \"%s\"...",
                                       rg.get_full_name(), maps[k].get_full_name()), UVM_LOW);
            
            // Was it what we expected?
            exp = rg.get() & ~wo_mask[k];
            
            rg.read(status, actual, UVM_FRONTDOOR, maps[k], this);
            if (status != UVM_IS_OK) begin
               `uvm_error("uvm_reg_shared_access_seq", $sformatf("Status was %s when reading register \"%s\" through map \"%s\".",
                                           status.name(), rg.get_full_name(), maps[k].get_full_name()));
            end
            
            `uvm_info("uvm_reg_shared_access_seq", $sformatf("Read 'h%h, expecting 'h%h",
                                        actual, exp),UVM_DEBUG);
            
            if (actual !== exp) begin
               `uvm_error("uvm_reg_shared_access_seq", $sformatf("Register \"%s\" through map \"%s\" is 'h%h instead of 'h%h after writing 'h%h via map \"%s\" over 'h%h.",
                                           rg.get_full_name(), maps[k].get_full_name(),
                                           actual, exp, v, maps[j].get_full_name(), prev));
            end
         end
      end
   endtask: body
endclass: uvm_reg_shared_access_seq


//------------------------------------------------------------------------------
// Class: uvm_mem_shared_access_seq
//------------------------------------------------------------------------------
//
// Verify the accessibility of a shared memory
// by writing through each address map
// then reading it via every other address maps
// in which the memory is readable and the backdoor,
// making sure that the resulting value matches the written value.
//
// If bit-type resource named
// "NO_REG_TESTS", "NO_MEM_TESTS",
// "NO_REG_SHARED_ACCESS_TEST" or "NO_MEM_SHARED_ACCESS_TEST"
// in the "REG::" namespace
// matches the full name of the memory,
// the memory is not tested.
//
//| uvm_resource_db#(bit)::set({"REG::",regmodel.blk.mem0.get_full_name()},
//|                            "NO_MEM_TESTS", 1, this);
//
// The DUT should be idle and not modify the memory during this test.
//
//------------------------------------------------------------------------------

class [docs]uvm_mem_shared_access_seq extends uvm_reg_sequence #(uvm_sequence #(uvm_reg_item));

   // variable: mem
   // The memory to be tested
   uvm_mem mem;

   `uvm_object_utils(uvm_mem_shared_access_seq)

   function [docs]new(string name="uvm_mem_shared_access_seq");
     super.new(name);
   endfunction

   virtual task [docs]body();
      int read_from;
      uvm_reg_map maps[$];

      if (mem == null) begin
         `uvm_error("uvm_mem_shared_access_seq", "No memory specified to run sequence on");
         return;
      end

      // Memories with some attributes are not to be tested
      if (uvm_resource_db#(bit)::get_by_name({"REG::",mem.get_full_name()},
                                             "NO_REG_TESTS", 0) != null ||
          uvm_resource_db#(bit)::get_by_name({"REG::",mem.get_full_name()},
                                             "NO_MEM_TESTS", 0) != null ||
          uvm_resource_db#(bit)::get_by_name({"REG::",mem.get_full_name()},
                                             "NO_REG_SHARED_ACCESS_TEST", 0) != null ||
          uvm_resource_db#(bit)::get_by_name({"REG::",mem.get_full_name()},
                                             "NO_MEM_SHARED_ACCESS_TEST", 0) != null )
            return;

      // Only look at shared memories
      if (mem.get_n_maps() < 2) return;
      mem.get_maps(maps);

      // We need at least a backdoor or a map that can read
      // the shared memory
      read_from = -1;
      if (mem.get_backdoor() == null) begin
         foreach (maps[j]) begin
            string right;
            right = mem.get_access(maps[j]);
            if (right == "RW" ||
                right == "RO") begin
               read_from = j;
               break;
            end
         end
         if (read_from < 0) begin
            `uvm_warning("uvm_mem_shared_access_seq", $sformatf("Memory \"%s\" cannot be read from any maps or backdoor. Shared access not verified.", mem.get_full_name()));
            return;
         end
      end
      
      // Try to write through each map
      foreach (maps[j]) begin
         
         `uvm_info("uvm_mem_shared_access_seq", $sformatf("Writing shared memory \"%s\" via map \"%s\".",
                                    mem.get_full_name(), maps[j].get_full_name()), UVM_LOW);
         
         // All addresses
         for (int offset = 0; offset < mem.get_size(); offset++) begin
            uvm_status_e status;
            uvm_reg_data_t  prev, v;
            
            // Read the initial value
            if (mem.get_backdoor() != null) begin
               mem.peek(status, offset, prev);
               if (status != UVM_IS_OK) begin
                  `uvm_error("uvm_mem_shared_access_seq", $sformatf("Status was %s when reading initial value of \"%s\"[%0d] through backdoor.",
                                              status.name(), mem.get_full_name(), offset));
               end
            end
            else begin
               mem.read(status, offset, prev, UVM_FRONTDOOR, maps[read_from], this);
               if (status != UVM_IS_OK) begin
                  `uvm_error("uvm_mem_shared_access_seq", $sformatf("Status was %s when reading initial value of \"%s\"[%0d] through map \"%s\".",
                                              status.name(), mem.get_full_name(),
                                              offset, maps[read_from].get_full_name()));
               end
            end
            
            
            // Write a random value,
            v = {$random, $random};
            
            mem.write(status, offset, v, UVM_FRONTDOOR, maps[j], this);
            if (status != UVM_IS_OK) begin
               `uvm_error("uvm_mem_shared_access_seq", $sformatf("Status was %s when writing \"%s\"[%0d] through map \"%s\".",
                                           status.name(), mem.get_full_name(), offset, maps[j].get_full_name()));
            end
            
            // Read back from all other maps
            foreach (maps[k]) begin
               uvm_reg_data_t  actual, exp;
               
               mem.read(status, offset, actual, UVM_FRONTDOOR, maps[k], this);
               if (status != UVM_IS_OK) begin
                  `uvm_error("uvm_mem_shared_access_seq", $sformatf("Status was %s when reading %s[%0d] through map \"%s\".",
                                              status.name(), mem.get_full_name(), offset, maps[k].get_full_name()));
               end
               
               // Was it what we expected?
               exp = v;
               if (mem.get_access(maps[j]) == "RO") begin
                  exp = prev;
               end
               if (mem.get_access(maps[k]) == "WO") begin
                  exp = 0;
               end
               // Trim to number of bits
               exp &= (1 << mem.get_n_bits()) - 1;
               if (actual !== exp) begin
                  `uvm_error("uvm_mem_shared_access_seq", $sformatf("%s[%0d] through map \"%s\" is 'h%h instead of 'h%h after writing 'h%h via map \"%s\" over 'h%h.",
                                              mem.get_full_name(), offset, maps[k].get_full_name(),
                                              actual, exp, v, maps[j].get_full_name(), prev));
               end
            end
         end
      end
   endtask: body
endclass: uvm_mem_shared_access_seq



//------------------------------------------------------------------------------
// Class: uvm_reg_mem_shared_access_seq
//------------------------------------------------------------------------------
//
// Verify the accessibility of all shared registers
// and memories in a block
// by executing the <uvm_reg_shared_access_seq>
// and <uvm_mem_shared_access_seq>
// sequence respectively on every register and memory within it.
//
// If bit-type resource named
// "NO_REG_TESTS", "NO_MEM_TESTS",
// "NO_REG_SHARED_ACCESS_TEST" or "NO_MEM_SHARED_ACCESS_TEST"
// in the "REG::" namespace
// matches the full name of the block,
// the block is not tested.
//
//| uvm_resource_db#(bit)::set({"REG::",regmodel.blk.get_full_name(),".*"},
//|                            "NO_REG_TESTS", 1, this);
//
//------------------------------------------------------------------------------

class [docs]uvm_reg_mem_shared_access_seq extends uvm_reg_sequence #(uvm_sequence #(uvm_reg_item));

   // Variable: model
   //
   // The block to be tested
   //
   //| uvm_reg_block model; 


   // Variable: reg_seq
   //
   // The sequence used to test one register
   //
   protected uvm_reg_shared_access_seq reg_seq;
   

   // Variable: mem_seq
   //
   // The sequence used to test one memory
   //
   protected uvm_mem_shared_access_seq mem_seq;
   
   `uvm_object_utils(uvm_reg_mem_shared_access_seq)

   function [docs]new(string name="uvm_reg_mem_shared_access_seq");
     super.new(name);
   endfunction


   // Task: body
   //
   // Executes the Shared Register and Memory sequence
   //
   virtual task [docs]body();

      if (model == null) begin
         `uvm_error("uvm_reg_mem_shared_access_seq", "No register model specified to run sequence on");
         return;
      end
      
      uvm_report_info("STARTING_SEQ",{"\n\nStarting ",get_name()," sequence...\n"},UVM_LOW);

      reg_seq = uvm_reg_shared_access_seq::type_id::create("reg_shared_access_seq");
      mem_seq = uvm_mem_shared_access_seq::type_id::create("reg_shared_access_seq");

      this.reset_blk(model);
      model.reset();

      do_block(model);
   endtask: body


   // Task: do_block
   //
   // Test all of the registers and memories in a block
   //
   protected virtual task do_block(uvm_reg_block blk);
      uvm_reg regs[$];
      uvm_mem mems[$];
      
      if (uvm_resource_db#(bit)::get_by_name({"REG::",blk.get_full_name()},
                                             "NO_REG_TESTS", 0) != null ||
          uvm_resource_db#(bit)::get_by_name({"REG::",blk.get_full_name()},
                                             "NO_MEM_TESTS", 0) != null ||
          uvm_resource_db#(bit)::get_by_name({"REG::",blk.get_full_name()},
                                             "NO_REG_SHARED_ACCESS_TEST", 0) != null ||
          uvm_resource_db#(bit)::get_by_name({"REG::",blk.get_full_name()},
                                             "NO_MEM_SHARED_ACCESS_TEST", 0) != null )
        return;

      this.reset_blk(model);
      model.reset();

      // Iterate over all registers, checking accesses
      blk.get_registers(regs, UVM_NO_HIER);
      foreach (regs[i]) begin
         // Registers with some attributes are not to be tested
         if (uvm_resource_db#(bit)::get_by_name({"REG::",regs[i].get_full_name()},
                                                "NO_REG_TESTS", 0) != null ||
             uvm_resource_db#(bit)::get_by_name({"REG::",regs[i].get_full_name()},
                                                "NO_REG_SHARED_ACCESS_TEST", 0) != null )
           continue;
         reg_seq.rg = regs[i];
         reg_seq.start(this.get_sequencer(), this);
      end

      // Iterate over all memories, checking accesses
      blk.get_memories(mems, UVM_NO_HIER);
      foreach (mems[i]) begin
         // Registers with some attributes are not to be tested
         if (uvm_resource_db#(bit)::get_by_name({"REG::",mems[i].get_full_name()},
                                                "NO_REG_TESTS", 0) != null ||
             uvm_resource_db#(bit)::get_by_name({"REG::",mems[i].get_full_name()},
                                                "NO_MEM_TESTS", 0) != null ||
             uvm_resource_db#(bit)::get_by_name({"REG::",mems[i].get_full_name()},
                                                "NO_REG_SHARED_ACCESS_TEST", 0) != null ||
             uvm_resource_db#(bit)::get_by_name({"REG::",mems[i].get_full_name()},
                                                "NO_MEM_SHARED_ACCESS_TEST", 0) != null )
            continue;
         mem_seq.mem = mems[i];
         mem_seq.start(this.get_sequencer(), this);
      end

      begin
         uvm_reg_block blks[$];
         
         blk.get_blocks(blks);
         foreach (blks[i]) begin
            do_block(blks[i]);
         end
      end
   endtask: do_block


   //
   // task: reset_blk
   // Reset the DUT that corresponds to the specified block abstraction class.
   //
   // Currently empty.
   // Will rollback the environment's phase to the ~reset~
   // phase once the new phasing is available.
   //
   // In the meantime, the DUT should be reset before executing this
   // test sequence or this method should be implemented
   // in an extension to reset the DUT.
   //
   virtual task [docs]reset_blk(uvm_reg_block blk);
   endtask


endclass: uvm_reg_mem_shared_access_seq