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.