Avoid race condition between two monitors generating dependent transactions

Hi everyone!

I need advice on the following problem:
Assume I’ve got a monitor class as follows


class if_monitor;
  mailbox #(transaction_t) monitor_mbx;
  // ...
  task monitor_transaction(output transaction_t t);
    @(cb iff cb.valid === 1'b1 && cb.ready === 1'b1);
    // assign transaction data
  endtask
  
  task run();
    forever begin
      transaction_t t;
      monitor_transaction(t);
      monitor_mbx.put(t);
    end
  endtask
endclass

Then, I’ve got a DUT with two monitored interfaces, if_a and if_b.
Both issue transactions changing the state of the DUT.
My scoreboard models the state of the DUT and observes the two interfaces and is connected as follows:


// TB
initial begin
  fork 
    forever begin
      transaction_t trans_a;
      monitor_a.monitor_mbx.get(trans_a);
      scoreboard.put_a(trans_a);
    end
    forever begin
      transaction_t trans_b;
      monitor_b.monitor_mbx.get(trans_b);
      scoreboard.put_b(trans_b);
    end
  join
end

The problem is now, that in terms of the state update of the scoreboard, it matters in which order put_a and put_b methods are called.
I.e. if in one clock cycle I receive both a trans_a and a trans_b, I need to call put_a before I call put_b.
Using the above connection, there’s no way of guaranteeing that.

I have found the following “solution”:
It works, but just as long as there are no other #0s in the monitoring methods.
This fact has just thaught me why we want to avoid #0 at all cost.


// TB
initial begin
  forever begin
    transaction_t trans_a;
    transaction_t trans_b;
    fork : wait_for_txn
      monitor_a.monitor_mbx.peek(trans_a);
      monitor_b.monitor_mbx.peek(trans_b);
    join_any
    // wait for both mailboxes to finish peeking
    #0;
    disable wait_for_txn;
    if (monitor_a.monitor_mbx.try_get(trans_a)) begin
      scoreboard.put_a(trans_a);
    end
    if (monitor_b.monitor_mbx.try_get(trans_b)) begin
      scoreboard.put_b(trans_b);
    end
  end
end

Question: Is there a better way to do this?

Any ideas are welcome!

Cheers, No

In reply to No:

There are a number of ways to approach this. But I think it might help if we knew the reasoning behind your requirement that put_a has to happen before put_b.

One possible solution is always doing a put() on both monitors every clock cycle and using null to represent there was no transaction. Then the scoreboard would do a fork/join to wait for both mailboxes get()s. This might not be the best solution ff there are many clock cycles between having any “real” transaction creating extra simulation overhead.

In reply to dave_59:

Hi Dave,

Thanks very much for your response.

So, essentially it boils down to one of the monitors observing a stimuli interface, and the other monitor observing responses.
Some of the responses are produced by the DUT combinationally, so in order for the scoreboard to process the stimuli before the responses I need to do this zero-delay transaction ordering.

I guess the usual way to achieve this kind of thing is to have a loop running inside the scoreboard, which would check a response mailbox and once there is a response, check the stimuli mailboxes and model at that point.

What I wanted to achieve in my architecture was to have a scoreboard to work without an internal run loop.
Essentially, each transaction sampled at the interfaces would call a scoreboard function (put_x) which would instantaneously execute and check responses, or change the internal state of the scoreboard’s model of the DUT. I think that would be kind of neat. But it seems that sort of thing is harder to achieve than I thought.

Your fork ... join suggestion makes sense.
But it will require the scoreboard to have that run loop anyways, and also some quite annoying changes to the monitors.
So I’ll probably not go that route…

Does this explanation make any sense to you?
If it were not for that combinational output I believe it could work quite well for moderately complex designs…

In general, what do you think about this alternative variant of implementing a scoreboard?
Is it a somewhat valid approach, or would you recommend to always go the route with the scoreboard’s internal run loop anyways?

In reply to No:

It’s hard to know what “combinationally” means for the timing of your responses without more detail that is probably feasible in this forum. Your alternative would certainly work if the ordering of responses was not important, or if you could wait until the end of the test to do all the checking.

In reply to dave_59:

Ok. Thanks, Dave for your advice.