Synchronization problems in uvm_driver

Hi we’re facing to what it seems a synchronization problem when driving the signals on the driver. To put you in context, we have an interface with a clocking block defined like this:


clocking cb @(posedge clk);
    default input #1step output #(1.2ns);
    output sb_id, nxt_sen, kill;
endclockin

the run_phase of the driver looks like this:


task run_phase(uvm_phase phase);
    forever begin
        seq_item_port.get_next_item(trans);
        vif.cb.sb_id   <= trans.sb_id;
        vif.cb.nxt_sen <= trans.nxt_sen;
        vif.cb.kill    <= trans.kill;
        @(vif.cb);
        vif.cb.nxt_sen <= 1'b0;
        vif.cb.kill    <= 1'b0;
        seq_item_port.item_done()  
    end
endtask

As you can see in the run_phase of the driver it has a synchronization after driving the the transaction, and then we set the nxt_sen and kill signals to '0, as it is the default state of the interface . The problem with this is that both nxt_sen and kill signals always stays at 0.

As you can see, transactions keep coming since sb_id signal is changing, but nxt_sen stays at 0, although in the transaction it is set to 1.
The way I thought this forever loop worked is:

cycle 0: If there is a transaction get it
cycle 0: Drive the signals and wait for clock posedge (nxt_sen should be to 1 at least for 1 cycle)
cycle 1: Set nxt_sen and kill singals to 0
cycle 1: Wait for a next transaction, if there’s one get it so we would drive this new transaction in cycle 1. If there isn’t new transactions step into next cycles until there’s one.

For what I’ve been able to investigate, trying different approaches, is that the synchronization is not done properly but I couldn’t find a resource that explains how timing in RTL simulations work and the behavior of SystemVerilog. Also when in the documentation of the get_next_item function says:

If no sequence is available, wait for a requesting unlocked relevant sequence, then re-arbitrate

How it does the wait ?

Thanks for the help.
Salut!

In reply to Sustrak:

My general rule for using clocking blocks is have your thread only use the clocking block event to block the process and nothing else.

task run_phase(uvm_phase phase);
    forever begin
        do begin
          @(vif.cb);
          seq_item_port.try_next_item(trans);
        end while (trans == null);
        vif.cb.sb_id   <= trans.sb_id;
        vif.cb.nxt_sen <= trans.nxt_sen;
        vif.cb.kill    <= trans.kill;
        @(vif.cb);
        vif.cb.nxt_sen <= 1'b0;
        vif.cb.kill    <= 1'b0;
        seq_item_port.item_done()  
    end
endtask

In reply to dave_59:

Thanks for the idea, but if I would need that consecutive transactions are driven in consecutive clocks, would your solution works since there’s 2 clock synchronizations ?

Would it better for that to drive an “empty” transaction ?

Thanks,
Salut!

In reply to Sustrak:

Not sure how your protocol defines an empty transaction, but don’t invent it if not needed.

Okay, so what it worked for me is to put the: seq_item_port.item_done() before synchronizing with the clock so the code would look like this.


task run_phase(uvm_phase phase);
    forever begin
        seq_item_port.try_next_item(trans);

        if (trans == null) begin
            vif.cb.nxt_sen <= 1'b0;
            vif.cb.kill    <= 1'b0;
            @(vif.cb);
        end
        else begin
            `uvm_info(get_type_name(), trans.sprint(), UVM_DEBUG)
            vif.cb.sb_id   <= trans.sb_id;
            vif.cb.nxt_sen <= trans.nxt_sen;
            vif.cb.kill    <= trans.kill;
            seq_item_port.item_done();
            @(vif.cb);
        end
    end
endtask

Thanks for the help Dave