Sending txns in specific order b/w multiple interfaces

Hi Forum,
I am modeling a UVM Tb to drive txns across 3 input interfaces to a DUT ( router ).
Since there are 3 independent interfaces I have 3 driver-sequencer pair ( one for each interface )
I am trying to generate 2 scenarios

(1) Transactions are driven in parallel across the 3 interfaces. For this I could simply use ::

// Within main_phase() of test
  txn_seq seq1 , seq2 , seq3;
  phase.raise_objection(this);

  // Create the 3 sequences here
    ............
  // Then start them in parallel across the 3 interfaces
    fork
         seq1.start(env.agent_h.seqr_h1);

         seq2.start(env.agent_h.seqr_h2);

         seq3.start(env.agent_h.seqr_h3);
    join  
  phase.drop_objection(this);

(2) Transactions are driven one at a time.
Eg: 1st txn occurs on intf2 then 2nd txn is driven on intf3 and then 3rd txn is driven on intf1

// Within main_phase() of test
  txn_seq seq1 , seq2 , seq3;
  bit[1:0]   order[3];
  phase.raise_objection(this);

  // Create the 3 sequences here
     .......................
 void'(std::randomize( order ) with { foreach(order[i]) order[i] inside {[1:3]} ; unique{ order } ; } )
   
   foreach( order[i] ) begin
    case( order[i] )
     1:  seq1.start(env.agent_h.seqr_h1);
     2:  seq2.start(env.agent_h.seqr_h2);
     3:  seq3.start(env.agent_h.seqr_h3);
    endcase
  end

  phase.drop_objection(this);

Is there an alternative / better approach to achieve (2) ?

What assures in your 2nd approach that the transactions will actually “be driven” by the BFM according to this order?

In case there is some pre_delay to wait in the BFM prior to driving of the transactions on the interfaces, the actual order of transaction being driven on the buses might be different (and in some cases even parallel).

You should recognize that the actual timing is defined by ypur drivers and not by your sequence execution.

I didn’t quite get your comment.
My understanding is that since the code is procedural, each of the start task will unblock only when the txns have been driven ( i.e finish_item unblocks )
Assuming that the driver uses get_next_item & item_done approach, finish_item will unblock only when driver has driven the txns on the interface

When you add the assumption on the driver get_next_item & item_done it adds more clarity…

Few additional thoughts I could write down:

  1. There are 3 interface agents in your example, each one of them has its own sequencer and driver.
  2. You wrote: seq1.start(env.agent_h.seqr_h1);
    Is each txn_seq is a extended from a uvm_sequence_item or uvm_sequence?
    Could the sequence has a stream of uvm_sequence_item(s) or txns?

My apologies I should have been clear about it.

Yes. I would have a driver-sequencer pair for each interface

Within body() task of each of the 3 sequences, it would call start_item & finish_item. The sequences are extended from uvm_sequence#(seq_item_txn)

Since the intention for (2) was to send 1 txn on each of the interface ( one at a time ), each of the 3 sequence would send 1 txn to the respective driver.
The procedural code would ensure that start() for 2nd sequence is called only when body() task of the 1st sequence ends. ( item_done() in each of the driver ensures that the txn is driven on the interface before the body() task for 2nd sequence starts )

Frankly, I am confused…
So does the 2nd approach actually work as you expected or not?
If according to you it is “correct by construction” what is the purpose of the thread?
Is there any issue you observed in your 2nd approach?
Could you add a screenshot of the waves if there is a problem?

Please provide complete code of the:
a. txn_seq
b. driver (BFM)

My intention was on how to achieve a synchronization between the different drivers so that only 1 of them sends a txn at the respective interface.
I was curious on whether (2) was the best approach or is there a better solution
I haven’t started coding the Tb and am still in the middle of deciding the Tb architecture , Test Plans etc

I understand now…

There are few ways to achieve it… but first check what the code you wrote is doing.

Once you have constructed all your basis components and sequences, the code your wrote for the 2nd approach will be very easy to simulate.
A higher level sequence may have an ENUM of traffic scenarios or your cfg object may have this property, and accordingly it would generate and simulate the traffic into the DUT.

TIP: you could define an SVA assertion, which will fail for the 2nd approach in case a valid transaction is monitored on more than a single interface.

Goodluck