Fork..join for multiple analysis_port.write() in monitor

Hi,

Before start asking question, here is a little background of my work.
I’m doing memory testing using recommended way in OVM.
For read request and read data cycle is happen one clock cycle difference. For example, at first clock cycle, agent initiates a read request to the memory, the read data returning from memory at second clock cycle, then the monitor gets the transaction on the interface at second clock cycle and do analysis_port.write(), follow by scoreboard gets the data on second clock cycle.

The read data cycle is possible to happen at the same time as write cycle, read cycle and idle cycle (happen on the same clock cycle), in this case, i use fork join method for write cycle function, read cycle function, read data cycle function and idle cycle function. Each function has its own analysis_port.write(). For example,

fork
	    rdat_cyc(txn);
            rd_cyc(txn);
	    wr_cyc(txn);
	    idl_cyc(txn);
        join

The question is, by doing this, since all four functions are running in parallel, would the simulator executing the second function call before finish executing the first function call?

I try to remove the fork join to make it sequential function call, however I see my data that sent through analysis port get overwritten by second function call and getting unexpected result in scoreboard.

May I know how usually people code the monitor for memory DUT to handle read cycle and read data cycle? Since it can be overlapped.

Thanks!

The execution order of the threads in a fork-join block is not deterministic, in other words you shouldn’t rely on the order.

The usual strategy in this type of monitor component is to fork a number of tasks in the run_phase() which are dedicated to each particular type of cycle. Each task sits in a forever loop and sends an analysis transaction using an analysis_port write() each time it sees the specific cycle complete.

task run_phase(uvm_phase phase);

fork
rdat_cyc();
rd_cyc();
wr_cyc();
idl_cyc();
join

endtask: run_phase

task rdat_cyc;
forever begin
// Look for RDAT
// Assemble TXN
// Clone TXN
ap.write(TXN_CLONE);
end
endtask

Note that if you have a common transaction and a common analysis port, then you should clone() the transaction before you send it, otherwise the transaction handle you see in the analysis component is always the same - i.e. it will be overwritten.

In reply to mperyer:

Thanks for the suggestion.

Currently what I did was, instead of having a forever loop in each tasks, I did it this way

task run_phase();
     forever 
     begin
       @(posedge intf.clk);

       fork
          rdat_cyc(txn);
          wr_cyc(txn);
          rd_cyc(txn);
          idl_cyc(txn);
       join
     end
endtask: run_phase

The code showing above looks fine right?

Another question, when both rdat_cyc and wr_cyc tasks are being called, (no matter how it still execute sequentially, let say rdat_cyc execute first) only the code in rdat_cyc will be executed, until it finish executing all codes in the rdat_cyc task, then the code in wr_cyc task will only start executing, is this correct? I ask this question because I want to make sure there is no random execution between two tasks.

Thanks for your help!

In reply to ericew:

No, that is not correct. The tasks will all start at the same simulation time in an indeterminate order, but there is also no guarantee that they finish in the same order. That is the behavior of a fork/join block. Also, you are passing a handle (txn) to the same object in all tasks.

In reply to dave_59:

Thanks Dave for the reply.

Just want to know the alternative way of handle this type of monitor, is it possible not to use fork…join for this type of monitor component? Or is it a MUST to use fork…join for this type of monitor?

Just wondering how can I make this work without using fork…join method?

Thanks!

Hi,
To do it without the fork join, you can try the following.

a) Create 4 txn for each pkt type say rdat_cyc_txn, rd_cyc_txn, wr_cyc_txn, idl_cyc_txn.
Have a field in the txn to say whether, the transaction is complete or not, say pkt_compl.

b) At each clk cycle evaluate each of the cycle(rdat_cy, rd_cy, etc) is valid or not.
if any or all the cycle(rdat_cy, rd_cy etc) is valid, fill the each of the rdat_cyc_txn,
rd_cyc_txn, wr_cyc_txn, idl_cyc_txn with captured details and set the pkt_compl = 1. This
functionality can be achieved by making the rdat_cyc, wr_cyc as non-time consuming
methods.

c) call a function which will check if the txn are complete, in a defined order say
rdat_cyn_txn, rd_cyc_txn, wr_cyc_txn, idl_cyc_txn. if a txn is complete call the
corresponding ports write method(or single write method). after that new() that txn.

This is simplified statement, change it to suit your requirement. Only disadvantage is one
has to evaluate at each clk cycle.