How do I create a pipelined Driver Sequencer REQ and RSP channel?

Hello,

I’m fairly new to UVM and already I find myself using some advanced techniques. In the simplest sense what I desire is to get information from the DUT ctrl_if which tells me how many credits I have available and then use this information in my virtual sequence to configure and send packets through my CTRL and DATA agents back to the DUT.

The virtual sequence uses the number of credits available to generate a particular number of data packets to hand over to each DATA agent. The number of data packets generated and starting agent need to be sent back to the ctrl_if for driving into the DUT.

I have noticed there are a few methods of getting information from the DUT back to the sequence, e.g.

  1. By hooking up the monitor to a TLM analysis port in the sequence - Wrong since sequences are objects not components.
  2. By passing a handle to the ctrl_if to the sequence through the UVM Config DB. - Seems a little odd.
  3. By using REQ/RSP ports between the driver and sequencer, e.g. slave driver. - Seems appropriate.

The 3rd one I have been attempting to achieve as it seems the most appropriate but what I want isn’t a slave driver. I want a master driver capable of getting things back. What I need to do is send and receive control packets on every cycle through a single driver, using the information from the previous cycle to generate a packet for the next cycle.

I’m using polymorphic interfaces and as such I have tasks in my CTRL interface for driving and sampling control cycles, each of which do so at a clock edge (so they block until clock edge), e.g.


//--------------------------------------------------------------------------------------------
// TASK: drive_control
// Performs the signal level wiggling required to drive a ctrl_packet into the DUT.
//
virtual task drive_control_cycle(ctrl_packet packet);
    @(posedge clock iff (reset_n === 1'b1));
    signal_a <= packet.a;
    // Drive other signals.
endtask : drive_control_cycle
 
//--------------------------------------------------------------------------------------------
// TASK: read_control
// Generates a ctrl_packet from the signals sampled on this cycle.
//
virtual task read_control_cycle(ctrl_packet packet);
    @(posedge clock iff (reset_n === 1'b1));
    packet.a = signal_a;
    // Read other signals.
endtask : read_control_cycle

For example I need my driver to call read_control_cycle() with a REQ from the sequence which the sequence then uses to modify and send a RSP to the driver which it drives by calling drive_control_cycle(). Is this possible? If I fork two tasks in the driver to deal with the read and drive tasks, how do I simultaneously send and receive RSP’s and REQ’s from the virtual sequence?

It seems like the seq_item_port is very much half-duplex since finish_item() will block for a cycle. Is it possible to use the rsp_port to send back responses? If this needs any clarification I’d be sure to add more information.

Many thanks,
Tom

The most common option to get information back into the sequence is to use the request itself, kind of like you showed you’re doing in your ctrl_if. Normally it’s kind of overkill to use the response port.

I’m not really sure how your protocol works, but I’ll guess. Let’s say you have the virtual sequence you described:


class some_vsequence extends uvm_sequence;
  task body();
    ctrl_packet cp;
    data_packet dp;
    int unsigned num_dps;

    // Read number of packets
    `uvm_do_on_with(cp, ctrl_sequencer, {
      kind == READ;
    })
    // driver has driven the packet and has has updated it with the information the DUT returned
    num_dps = cp.num_data_packets;
    
    // Warn DUT about number of packets we're going to send it
    `uvm_do_on_with(cp, ctrl_sequencer, {
      kind == WRITE;
      num_data_packets == num_dps;
    })

    repeat(num_dps) begin
      `uvm_do_on(dp, data_sequencer)
    end
  endtask
endclass

The control agent driver should update the request it gets from its sequencer with the response from the DUT:


class ctrl_driver extends uvm_driver #(ctrl_packet);
  task get_and_drive();
    forever begin
      ctrl_packet packet;
      seq_item_port.get_next_item(packet);
      drive(packet);
      seq_item_port.item_done();
    end
  endtask

  task drive(ctrl_packet packet);
    vif.drive_control_cycle(packet);
    if (packet.kind == READ)
      vif.read_control_cycle(packet);
  endtask
endclass

Since both the sequence and the driver see the same object, changes done in the driver are propagated to the sequence.

You mention pipelining in the title, but nowhere in the question anymore. In that case, everything gets a bit more complicated. This is because the packet has to be marked as done before the response comes from the design. Then, you’d need to use the driver → sequencer response path.

Post a reply if all this isn’t enough to get you going further.

In reply to Tudor Timi:

Hi Timi,

This is the original method I had implemented. The problem with this is that calls to the drive_control_cycle() and read_control_cycle() tasks each block for 1 cycle and as such `uvm_do_on_with(ctrl_sequencer) will block for 2 cycles.

I need something more like this for my sequence and the entire sequence (execution of the body) should take one cycle as I need to start sending a REQ again on the next cycle…


virtual task body();
 for (int i = 0; i < max_seq_length; i++)
      begin
         // --- Request to CTRL driver - Tell me what I should do.

         ctrl_req = ctrl_packet::type_id::create("ctrl_req");
         ctrl_rsp = ctrl_packet::type_id::create("ctrl_rsp");

         start_item(ctrl_req, -1, ctrl_seqr); // Needed for calculation of RSP
         finish_item(ctrl_req);

         // --- Calculations based on REQ

         starting_agent    = $urandom_range(num_agents - 1, 0);
         available_credits = BUF_DEPTH - ctrl_req.used_credits;
         num_instructions  = $urandom_range(available_credits, 0);

         if (num_instructions > num_agents) num_instructions = num_agents;

         // --- Response to CTRL driver - Do what you're told to.

         start_item(ctrl_rsp, -1, ctrl_seqr);
         ctrl_rsp.copy(ctrl_req);
         assert(ctrl_rsp.randomize());
         ctrl_rsp.starting_inst = starting_agent;
         ctrl_rsp.number_inst   = num_instructions;
         finish_item(ctrl_rsp);

         // --- Data Sequencer control based off of CTRL request.

         for (int i = starting_agent; i < (starting_agent + num_instructions); i++)
         begin
            int sel = i % num_agents;
            data_packet[sel] = data_packet::type_id::create("data_packet");

            `uvm_info("VSEQUENCE", $sformatf("***** Starting a DATA packet on agent %0d *****", sel), UVM_HIGH)
            fork begin
               start_item(data_packet[sel], -1, data_seqr[sel]);
               assert(data_packet[sel].randomize());
               finish_item(data_packet[sel]);
            end join_none
         end
         wait fork;
      end
endtask : body

The problem here is that finish_item() blocks until the driver calls item_done() so I think I may also need a way for the ctrl_driver to deal with both REQ and RSP’s at the same time and may need (as you mention) a “driver → sequencer response path”.

I am a little confused by this approach but it seems the only option.

Thanks,
Tom

In reply to Emphacy:

Even if you implement a get_response(…) call in there, don’t you have a chicken-egg problem? You want to get some values from your DUT, but in the exact cycle you get your values you want to have driven a command to your DUT based on those values.

Is your protocol like this?



  1.     2.     3.     4. 
   __     __     __     __ 
__|  |___|  |___|  |___|  |__

___  _____ __________________
___/ REQ1 X REQ2 \___________

__________  _____ ___________
__________/ RSP1 X RSP2 \____


If you get your DUT credits and stuff with RSP1 on clock edge 3, you can’t use them to drive REQ2, because REQ2 should have already started by then, already at clock edge 2. The best you can do then is predict what the DUT would give you and use that when driving REQ2. You don’t even need REQ1 if you can do this.