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
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.
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?
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.