Uvm_scoreboard run_phase wrt queue logic

HI All,

Consider the following basic uvm_scoreboard with two implementation ports.

Question_1:


`uvm_analysis_imp_decl(_port1)  //for input transaction
`uvm_analysis_imp_decl(_port2)  //for output transaction

class mem_scoreboard extends uvm_scoreboard;
  
  `uvm_component_utils(mem_scoreboard)
  
  uvm_analysis_imp_port1#(mem_seq_item,mem_scoreboard) input_port;
  uvm_analysis_imp_port2#(mem_seq_item,mem_scoreboard) output_port;

  //---------------------------------------
  // Virtual Interface
  //---------------------------------------
  virtual mem_if vif;
  
  //---------------------------------------
  // declaring pkt_qu to store the pkt's recived from monitor
  //---------------------------------------
  mem_seq_item input_pkt_queue[*];  // Associative Array
  mem_seq_item output_pkt_queue[*]; // Associative Array
  
  //---------------------------------------
  // new - constructor
  //---------------------------------------
  function new (string name, uvm_component parent);
    super.new(name, parent);
  endfunction : new
  
  //---------------------------------------
  // build_phase - create port and initialize local memory
  //---------------------------------------
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    input_port = new("input port", this);
    output_port = new("output port", this);
  endfunction: build_phase
  
  //---------------------------------------
  // write task - recives the pkt from monitor and pushes into queue
  //---------------------------------------
  virtual function void write_port1(mem_seq_item input_pkt);
    input_pkt_queue[input_pkt.axi_id] = input_pkt;
  endfunction 
  
  virtual function void write_port2(mem_seq_item output_pkt);
    output_pkt_queue[output_pkt.axi_id] = output_pkt;    
  endfunction : write

  //---------------------------------------
  // run_phase - compare's the read data with the expected data(stored in local memory)
  // local memory will be updated on the write operation.
  //---------------------------------------
  virtual task run_phase(uvm_phase phase);
    mem_seq_item mem_pkt1;
    mem_seq_item mem_pkt2;
    
    forever begin
      wait(input_pkt_queue.num() > 0 && output_pkt_queue.num() > 0); //this line
      if(input_pkt_queue.axi_id.exists(output_pkt_queue.axi_id))
        begin
          mem_pkt1 = input_pkt_queue[axi_id];
          mem_pkt2 = output_pkt_queue[axi_id];
          if(mem_pkt1.compare(mem_pkt2))
            $display("packet matches");
          else
            $display("packet mis-matches");
        end
    end
  endtask : run_phase

IN the run_phase, I have used wait statement for both input and output pkt queue to have at least one element. 
But what in case only one packet arrived and another packet has not yet come, in this case, the entire scoreboard is blocked or its blocked until we receive both packets. 

so my question is "This approach of run_phase is blocking the entire scoreboard operation if either packet did not come.  How can I modify the run_phase of the above scoreboard so that scoreboard won't be blocked but still the check should keep happening"

Kindly share your thoughts on this. 

-----------------------------------------------------------

Quesstion_2 : 
=============

class my_scoreboard extends uvm_scoreboard;
  ....
  
  //associative array for out of order matching
  
  my_transaction exp_trans[string];
  my_transaction act_trans[string];
  
  function void write_expected(my_transaction txn);
    exp_trans[txn.id] = txn;
    check_and_match(txn.id);
  endfunction
  
  function void write_actual(my_transaction txn);
    act_trans[txn.id] = txn;
    check_and_match(txn.id);
  endfunction
  
  function void check_and_compare(string id);
    if (exp_trans.exists(id) &&
        act_trans.exists(id) )begin //{
      
      if (exp_trans[id].compare(act_trans[id])
        `uvm_info(PASSED, match found for ID)
          else
         `uvm_error(FAILED, match found for ID)
              
           exp_trans.delete(id);
           act_trans.delete(id);
                    end //}
  endfunction
 
endclass

In this approach, I could see scoreboard is not blocking. But the data integrity check is happening at "check_phase". Also, it is not with "forever begin",  consider a case;
pkt1 with ID1 arrived
pkt2 with ID2 arrived
since ID mismatch, so this check will not happen, it will be in associative array only. 

Again this check should happen when the exact packet arrives with same ID. 
Will this happens with this approach?
Means, my question is "since we do not have forever, will the check happens for all the packets even with packets coming sooner or later" or anything is missing in this approach for data integrity. 

Kindly suggest for both questions

Thank you,

Here are my assumptions ::
(1) input_pkt_queue is populated (via monitor1) when UVC drives the AXI write transactions to the DUT and output_pkt_queue is populated (via monitor2) when DUT sends AXI responses ( RDATA ) back to UVC

(2) For Burst transfers the write function is called via respective monitor’s Analysis port during the last beat

The comparison should occur only when DUT is driving RDATA back to UVC

I would declare output_pkt_queue & input_pkt_queue as 2D array ( with the 2nd dimension being Queue type ) for cases where there are more than 1 AXI Writes with same AwID
As per AXI protocol in such cases the DUT would send RDATA in-order

  // Declared both Queues as 2D Unpacked array
  mem_seq_item   input_pkt_queue[int][$] , output_pkt_queue[int][$] ; 

  virtual function void write_port1(mem_seq_item input_pkt);
    input_pkt_queue[input_pkt.axi_id].push_back( input_pkt );
  endfunction: write_port1 
  
  virtual function void write_port2(mem_seq_item output_pkt);
    output_pkt_queue[output_pkt.axi_id].push_back( output_pkt );    
  endfunction : write_port2

virtual task run_phase(uvm_phase phase);
    mem_seq_item mem_pkt1;
    mem_seq_item mem_pkt2;
    
    forever begin
      wait(output_pkt_queue.num() != 0); 

      foreach( output_pkt_queue[pkt] ) begin

        if( input_pkt_queue.exists(pkt) begin  

          mem_pkt1 =  input_pkt_queue[pkt].pop_front();
          mem_pkt2 = output_pkt_queue[pkt].pop_front();

          if(mem_pkt1.compare(mem_pkt2))
            $display("packet matches");
          else
            $error("packet mis-matches");
        end:if

        // Additional check that DUT drives response related to Input
        else begin // No element from input_pkt_queue/output_pkt_queue is popped out
           $error("No input txn with axi_id: 0x%0h found ",pkt);
        end                          
      end:foreach

    // EDIT :: In case of $error, delete the element from output_pkt_queue. 
    //         An input_pkt_queue entry is deleted ( via pop_front() ) only when a matching entry is found in output_pkt_queue
       output_pkt_queue.delete();
         
   end:forever
 endtask : run_phase

For your Question2 I believe function ‘check_and_compare’ should be called only from ‘write_actual’
There should be an addition check ( similar to $error("No input txn with axi_id: 0x%0h found ",pkt); above ) for cases where DUT drives a transaction unrelated to any input

Also note that you have a few typos in Question2
Eg1: You call ‘check_and_match’ whereas you have defined ‘check_and_compare’

Eg2: Input argument to ‘check_and_compare’ should be int / integer / logic / reg type as the actual argument is ‘id’ i.e non-string type)

Post discussion on this with a colleague ::
(1) In Question1 the output_pkt_queue isn’t required at all.
The check in run_phase() could be performed directly within the write function.

virtual function void write_port2(mem_seq_item  output_pkt);
    mem_seq_item  mem_pkt1;

    if( input_pkt_queue.exists(output_pkt.axi_id) )  begin
          // pop_front() deletes the entry from input_pkt_queue.
          mem_pkt1 = input_pkt_queue[output_pkt.axi_id].pop_front();          

          if( mem_pkt1.compare(output_pkt) )
            $display("Packet matches");
          else
            $error("Packet mis-matches");
     end
     // Additional check that DUT drives response related to Input
     else begin
        $error("No input txn with axi_id: 0x%0h found ",output_pkt.axi_id);
     end                          
      
  endfunction : write_port2

This eliminates the need of 1 Queue per output ( driven by DUT ) write function

(2) Consider a case that UVC does a AXI Read at an address without a prior Write to the same address.
Using above code, the $error would execute due to no matching AXI Write i.e no matching entry in input_pkt_queue
Scoreboard would need to handle this scenario

@AGS91 : Thank you so much for the additional information, its helpful.
Let me understand and ask here if further queries :slight_smile: