There are a lot of missing details about the timing. Let’s say you have a total of N Agents connect to your scoreboard. Will all N agents send a one transaction for every check your scoreboard needs to verify? If that’s the case, you can set up a counter that each write function increments. The write function that reaches N can execute your check and reset the counter.
Otherwise, I can’t see how you can do this without using an anaylsis_fifo with the details given.
The race conditions you are facing might be caused by a bad architecture.
You say you have a lot of agents with the categories you mention above. Your agents should not be organized with respect to inputs and outputs. They should be specified wrt to functional interfaces. The key question for your UVM testbench is: how many functional interfaces do you have. Each agent should be related to these interfaces. Then you might avoid your races.
My RTL has multiple input and output buses, some using proper communication protocols and some just being wired signal buses (interrupts, status registers etc). Each bus has its own agent; communication bus monitors report transactions to the scoreboard on the usual valid/ready combination, wire monitors report transactions on a change in the wires.
All of that logic feeds into some non-deterministic round robin type request handler, so in order to make the scoreboard handle all this I have agents at a key stage in the middle of the design telling the scoreboard exactly what is being processed. I wish I could blackbox this properly but I can’t.
The scoreboard observes inputs and processes and creates expected output transactions, and then compares those when the outputs occur. The issue I am facing is that if the scoreboard sees certain inputs BEFORE it sees the processes then it predicts the outputs incorrectly.
I first turned all of the imp_decl write functions into tasks (I got lots of compilation warnings from the simulator) and added in minor delays to stagger the triggers to go inputs > processes > outputs, but this caused other issues to appear in the scoreboard. I backed out of this fix (though in retrospect I believe that I had the timescale set incorrectly so I shall try this again in future)
The current solution I have is to connect the agents to slightly delayed clocks. Active agents operate on the same clock as the RTL, Passive agents operate on the delayed clock. This ensures that my inputs are seen before my processes. This is working so far.