Waiting for Responses for all Outstanding transactions

Hi All,
I am writing a sequence for testing outstanding AXI transactions
“Read + write <= 12. All combinations possible”
i.e There could be 12 in-flight writes & 0 in-flight reads
There could be 12 in-flight reads & 0 in-flight writes
There could be x in-flight reads & y in-flight writes such that x + y <= 12
The valid values of AxID are 0 & 1.
Here is my attempt ::

  //  Within class 'outstanding_reg_seq' 
    int  max_outstanding_txn = 12;     //  Can be re-assigned from test

  bit [4:0] outstanding_txn[bit[1:0]]; // Per ID (0-1) we can have 12 txns. Index would be ID.Elemet would be No. of Oustanding txns !!
  bit       axid[$];                   // Each AxID (0-1) can have 12 Outstanding txns.

  typedef enum bit [1:0] { ALL_ZEROES , ALL_ONES , MIX }  axid_t;
  axid_t    axid_type ;

  virtual task body();
      bit  local_ind; 

      use_response_handler(1);

      if( !std::randomize(axid_type) )
        `uvm_fatal(get_name(),"Randomization failed!")
     else   
        `uvm_info(get_name(),$sformatf("axid_type chosen as %0s",axid_type.name()),UVM_LOW)

     if( axid_type == ALL_ZEROES ) begin
        if( ! std::randomize(axid) with { axid.size() == max_outstanding_txn ;
                                          axid.sum() with ( int'( item == 0 ) ) == max_outstanding_txn; } )
           `uvm_fatal(get_name(),"Randomization failed!")
     end      
     else if( axid_type == ALL_ONES ) begin
     
        if( ! std::randomize(axid) with { axid.size() == max_outstanding_txn ;
                                          axid.sum() with ( int'( item == 1 ) ) == max_outstanding_txn; } )
           `uvm_fatal(get_name(),"Randomization failed!")
     end      
     else begin
       `uvm_info(get_type_name(),$sformatf("Sending Mix of AXIDs"),UVM_LOW) // AxID coud be 0/1
     
        if( ! std::randomize(axid) with { axid.size() == max_outstanding_txn ;
                                          axid.sum() with ( int'( item inside {0,1} ) ) == max_outstanding_txn; } )
           `uvm_fatal(get_name(),"Randomization failed!")
     end      

     axid.shuffle();
     foreach(axid[i]) `uvm_info("AxID",$sformatf("Axid['d%0d]: 'd%0d",i,axid[i]),UVM_LOW)

     foreach(reg_addresses[i]) begin 
     `uvm_info(get_name(),$sformatf("Outstanding txns: 'd%0d",outstanding_txn.sum() with( int'(item) ) ),UVM_LOW)

      wait( axid.size() > 0 ); // Else it will pop default value of 0 !!
      local_ind = axid.pop_back();

     `uvm_create(transfer)
       if(!transfer.randomize() with {addr[11:0] == reg_addresses[i]; len == 0; burst == AXI_BURST_TYPE_INCR; 
                                     cache == 0; id == local_ind ; prot == 0; qos == 0; region == 0; axuser == 0; size == AXI_SIZE_4BYTE;})
        `uvm_fatal(get_name(),"Randomization failed!")

         // Call other APIs

       outstanding_txn[local_ind]++; // Per ID (0-1) IP can have 12 Outstanding txns.      
      `uvm_info(get_name(),$sformatf("Outstanding txns: 'd%0d",outstanding_txn.sum() with( int'(item) ) ),UVM_LOW)
      `uvm_send(transfer)

    end:foreach


    // Expectation is that below wait statement ensures that ALL Responses corresponding to transmitted AxID's have been Received
    wait( outstanding_txn.sum() with( int'(item) ) == 0 ); // Element for respective ID would be 0 when ALL Responses related to ID have been received

   `uvm_info(get_name(), "Completed....",UVM_LOW)

  endtask : body


 virtual function void response_handler(uvm_sequence_item response);
    axi4_transaction  resp;     
    
    void'($cast(resp,response.clone()));
    `uvm_info("Response_handler",$sformatf("Response: %0s",resp.sprint()), UVM_LOW)
   
    // Once response is received for particular ID, it's no longer outstanding for respective ID
    outstanding_txn[resp.id]--; // Each Index would be AxID and Each Element would be Outstanding txns for the respective ID
    ..............
     axid.push_back(resp.id);   // Available for re-use
  endfunction: response_handler

My question is regarding the wait statement at the end of body() task
Would wait( outstanding_txn.sum() with( int’(item) ) == 0 ) ensure that if IP were to send more responses with RID/BID than expected, the wait statement would remain blocked ?

I’m not sure if your approach is acceptable. It does not make any sense doing any number of RD without prior WR to the same address…

The intention of the sequence is to test that the IP reaches 12 outstanding txns
We backpressure the RReady & BReady signals to ensure that there outstanding txns ( via UVC APIs ).
Along with testing the no. of outstanding txns, we also check the RDATA during reads.

The above sequence is created twice in the test.
One sequence is specifically used for reads and the other for writes.
The queue ‘reg_addresses’ is populated from the test and has different set of registers assigned to the 2 sequences
If read is being performed to a register which wasn’t written prior, the RDATA is compared with default value. If write has been done to the register prior to the read, then the RDATA is compared with expected data (calculated based on random write data and the register field policy)
This RDATA comparison logic is part of function response_handler

A few questions and suggestions:

  1. Which EDA vendor’s third-party AXI VIP are you using?

  2. If your VIP provider offers Agent’s monitor callback hooks for various phases of the AXI protocol (e.g., read address request, read response, write address, write data, and write response), I recommend utilizing these callbacks to accurately track the number of outstanding AXI read/write transactions, this method is generally more reliable and maintainable than implementing the tracking mechanism within sequence logic.

  3. I suggest wrapping the following wait statement:
    wait( outstanding_txn.sum() with( int'(item) ) == 0 );
    in a fork…join_any construct, where the secondary thread includes a timeout. This approach acts as a watchdog mechanism to prevent the simulation from hanging indefinitely (or until the UVM_DEFAULT_TIMEOUT is triggered).

  4. I would implement checks to ensure that the outstanding_txn counter does not overflow(>12) or underflow(<0) during transaction tracking:
    outstanding_txn[local_ind]++;
    outstanding_txn[resp.id]--;

1 Like

@MichaelP please find my inline comments ::
(1) We have an inhouse AXI UVC which acts as VIP
(2) We use signals from Write & Read Driver within UVC to track the number of outstanding writes & reads respectively. So if the value of 12 isn’t reached we flag an error at the end of body() task
(3) If Response were not be received for a write / read transfer the wait statement would never unblock. As a result the body() task wouldn’t end and the objection from test which started the sequence wouldn’t be dropped. We would run into UVM_FATAL timeout issue in this case
(4) Since we constraint axid.size() == max_outstanding_txn i.e 12 there would be no overflow.
Incase of underflow i.e IP sending more responses than expected, ideally the UVC would catch this via an assertion. Even in absence of one the wait statement wouldn’t unblock leading to UVM_FATAL timeout eventually