Race condition between write() in UVM scoreboard

Assume there is a memory DUT whose interface signals are like:
Rd interface: rden, rdaddr
Wr interface: wren, wraddr, wdata

If the DUT receives both wren and rden in the same clock cycle, it should service write first and then the read.

In TB, assuming that I have 2 agents/monitors, how do I ensure that the write() method for the write analysis port is executed first before the write() of the read analysis port?

One solution could be to make all the signals part of the same interface/monitor/agent. But let’s say that the interfaces/monitors need to be separate for other reasons.
I’m looking for a solution where the scoreboard could synchronize between 2 write() implementations. Is there a way?

In reply to vignesh.kannan:

The key question is: Does your DUT have really 1 interface for read and another one for write.
And does your DUT allow read and write at the same time.
The scoreboard can never have race condition, because it does not know anything about clock cycles etc. It knows only the order of your transactions.

In reply to chr_sue:

Hi Chr_sue,

Yes my DUT has 2 interfaces. 1 for read and 1 for write.
Yes, the DUT allows read and write at the same time. I’ve mentioned the DUT’s behavior above when that happens.

In the case where both read and write happens at the same time/clk, my write_monitor and read_monitor will be calling their ap.write() concurrently. This COULD potentially cause the write_wr() and write_rd() implementations in the scoreboard to get executed in the wrong order. How do we prevent this? I’m open to any changes in my monitor(s) or scoreboard.

In reply to vignesh.kannan:

What you are mentioning is not related to the monitor. It is related to the driver. If the write port is active befor the read port you get different results to the vice-versa order. You have to control your drivers. Then anything is OK.

In reply to chr_sue:

Sorry… Your solution is not clear to me. How do i control my drivers here?

Lets say I have 2 drivers/monitors and they are named as: rd_drv, wr_drv, rd_mon, wr_mon

rd_mon:

task run_phase();
  ...
  if(vif.rd_en) begin
    item.rd_addr = vif.rd_addr;
    ap.write(item);                //Writing sampled rd item to the scoreboard
  end
endtask

wr_mon:

task run_phase();
  ...
  if(vif.wr_en) begin
    item.wr_addr = vif.wr_addr;
    item.wr_data = vif.wr_data;
    ap.write(item);              //Writing sampled wr item to the scoreboard
  end
endtask

wr_drv:

task run_phase();
  ...
  seq_item_port.get_next_item(item);
  drive_if(item);
  seq_item_port.done();
endtask

task drive_if(wr_item item);
  @vif.cb;
  vif.wr_en <= 1'b1;
  vif.wr_data <= item.wr_data;
  vif.wr_addr <= item.wr_addr;
endtask

rd_drv:

task run_phase();
  ...
  seq_item_port.get_next_item(item);
  drive_if(item);
  seq_item_port.done();
endtask

task drive_if(rd_item item);
  @vif.cb;
  vif.rd_en <= 1'b1;
  vif.rd_addr <= item.rd_addr;
endtask

Lets say there is a virtual sequence which runs read sequences and writes sequences in parallel. When both drivers drive their corresponding pkts at the same time, my monitors sample them and send it to a single scoreboard which has a memory model.
The connection from monitors to scoreboard is like below:
rd_mon → ap1 → scbd
wr_mon → ap2 → scbd
My scoreboard implements 2 different write() implementations and acts accordingly.
How do i ensure correct order here? ie. When both write/read happens at the same time, the write operation should happen first and then the read operation.

Feel free to alter the driver/monitor code. And let me know if you need more code if my question is unclear.

In reply to vignesh.kannan:

When you are running a virtual sequence with a fork/join of rd and wr sequence then it is randomly what is selected first, wr or rd. If you get the rd seq_item first you’ll read first and wr later.
You can control this by setting a higher prio to the wr sequence. Then the wr is selected first anf the wr happens first. I believe this is what you want to do.

In reply to chr_sue:

Actually, no. I think we both are not on the same page here. Let’s say the virtual sequence sends reads, writes with random delays in between such that for a given clock, you can hit any of the 4 scenarios:

  1. only read
  2. only write
  3. write and read together.
  4. neither read nor write

NOTE that i have 2 interfaces, not 1.

I want to use this virtual sequence because it’s capable of doing ALL OF THE ABOVE.
Case 3) is a valid scenario, and I’m able to drive that.

My question here is, how do I change the monitor/driver/scoreboard for case 3) knowing that my DUT handles write → read when both are presented during the same cycle. If I just use the code above, there is no guarantee that the ap.write() of rd_mon gets called after the ap.write() of wr_mon.

If ap.write() of rd_mon gets called before the ap.write() of wr_mon, then my scoreboard will return the data prior to the write, and DUT would return the data after the write. Do you see the problem I’m facing?

In reply to vignesh.kannan:
It’s useless to have delays in generating seq_items. Any timing will be done in the drivers. Your seq_items my have a data member defining the delays between the execution of read or writes.
I believe it is useless to do reads without doing writes prior to the reads. And do you share addresses between the read and the write port?

Back to your last statment: when you are executing reads and writes with the same clock cycle. This will end up in a bad situation. Inserting an additional clock cycle in the read access will delay this and you have a clear situation.

In reply to chr_sue:

Forget about the stimulus for a second. I’m driving a valid stimulus to the DUT where a read and a write CAN happen to my DUT at the same time. It’s part of the spec for my DUT. If both happen at the same time, then my DUT is expected to handle both such that it writes the data into the address first and then reads out the memory. There is no issue/question with my stimulus.

Both of your previous suggestions are to drive write and read separately! That’s not what I want. I want to test my DUT when both happen together.

Yes, they can share the addresses between the read port and write port.

I don’t agree to your statement “when you are executing reads and writes with the same clock cycle. This will end up in a bad situation.”

My DUT is intelligent enough to handle reads/writes together. You don’t have to worry about that. Let’s focus on the testbench!

What should I do in my monitor/scoreboard to ensure that my TB also handles these writes/reads together!

In reply to vignesh.kannan:

OK, I try to summarize what I understood:
(1) you can read/write on the same clock cycle independently
(2) this is valid also when using the same addr for read and write
(3) when you write to a specific addr and read from the same addr you see on the read the data from the previous write and modifiying this content through the write
(4) if you read from an addr which was not written before you see 'x or any initial value
(5) When reading form and writing to the same addr you have an offset of 1 value in the transactions coing from the monitors.

Is my understanding correct?