UVM/UVM Cookbook/Driver/Bidirectional
- Sequences
- Virtual
- VirtualSequencer
- Slave [1]
- Driver
- Unidirectional
- Bidirectional
- Pipelined
- Analysis Components & Techniques
- UVM Tutorial for Candy Lovers
One of the most common form of sequence driver use models is the scenario where the sequencer sends request sequence_items to the driver which executes the request phase of the pin level protocol, and then the driver responds to the response phase of the pin-level transaction returning the response back to the sequence. In this use model the flow of data is bidirectional and a new request phase cannot be started until the response phase has completed. An example of this kind of protocol would be a simple peripheral bus such as the AMBA APB.
To illustrate how this use model would be implemented, a DUT containing a GPIO and a bus interface will be used. The bus protocol used is shown in the timing diagram. The request phase of the transaction is initiated by the valid signal becoming active, with the address and direction signal (RNW) indicating which type of bus transfer is taking place. The response phase of the transaction is completed when the ready signal becomes active.
The driver that manages this protocol will collect a request sequence_item from the sequencer and then drive the bus request phase. The driver waits until the interface ready line becomes active and then returns the response information, which would consist of the error bit and the read data if a read has just taken place.
The recommended way of implementing the driver is to use get_next_item() followed by item_done() as per the following example:
class bidirect_bus_driver extends uvm_driver #(bus_seq_item); `uvm_component_utils(bidirect_bus_driver) bus_seq_item req; virtual bus_if BUS; function new(string name = "bidirect_bus_driver", uvm_component parent = null); super.new(name, parent); endfunction task run_phase( uvm_phase phase ); // Default conditions: BUS.valid <= 0; BUS.rnw <= 1; // Wait for reset to end @(posedge BUS.resetn); forever begin seq_item_port.get_next_item(req); // Start processing req item repeat(req.delay) begin @(posedge BUS.clk); end BUS.valid <= 1; BUS.addr <= req.addr; BUS.rnw <= req.read_not_write; if(req.read_not_write == 0) begin BUS.write_data <= req.write_data; end while(BUS.ready != 1) begin @(posedge BUS.clk); end // At end of the pin level bus transaction // Copy response data into the req fields: if(req.read_not_write == 1) begin req.read_data = BUS.read_data; // If read - copy returned read data end req.error = BUS.error; // Copy bus error status BUS.valid <= 0; // End the pin level bus transaction seq_item_port.item_done(); // End of req item end endtask: run_phase endclass: bidirect_bus_driver |
Note that the driver is sending back the response to the sequence by updating the fields within the req sequence_item before making the item_done() call. At the sequence end of the transaction, the sequence is blocked in the finish_item() call until the item_done() occurs, when it is unblocked, its req handle is still pointing to the req object which has had its reponse fields updated by the driver. This means that the sequence can reference the response contents of the req sequence_item.
class bus_seq extends uvm_sequence #(bus_seq_item); `uvm_object_utils(bus_seq) bus_seq_item req; rand int limit = 40; // Controls the number of iterations function new(string name = "bus_seq"); super.new(name); endfunction task body(); req = bus_seq_item::type_id::create("req"); repeat(limit) begin start_item(req); // The address is constrained to be within the address of the GPIO function // within the DUT, The result will be a request item for a read or a write assert(req.randomize() with {addr inside {[32'h0100_0000:32'h0100_001C]};}); finish_item(req); // The req handle points to the object that the driver has updated with response data uvm_report_info("seq_body", req.convert2string()); end endtask: body endclass: bus_seq |
Download-tarball.png | Download a complete working example: (tarball: use_models_bidir_item_done_uvm.tgz) |
Alternative Implementation Option
Although this option is discussed below, the recommended way to implement this sequence driver use model is as in the preceeding code.
Driver put, Sequence get_response
Alternatively, the code in the driver could use a get() method to collect the request sequence_item, this get() call would unblock the finish_item() call in the sequence execution. However, the driver should use the put() method to signal back to the sequence that it has fully completed the bus transfer cycle, and the sequence should use a blocking call to the get_response() method to wait for the driver to complete the transfer. Any response information from the pin level bus transaction can be sent from the driver to the sequence via the argument to the put() method.
// Alternative version of the driver run method task run_phase( uvm_phase phase ); bus_seq_item req; bus_seq_item rsp; // Default conditions: BUS.valid <= 0; BUS.rnw <= 1; // Wait for reset to end @(posedge BUS.resetn); forever begin seq_item_port.get(req); // Start processing req item repeat(req.delay) begin @(posedge BUS.clk); end BUS.valid <= 1; BUS.addr <= req.addr; BUS.rnw <= req.read_not_write; if(req.read_not_write == 0) begin BUS.write_data <= req.write_data; end while(BUS.ready != 1) begin @(posedge BUS.clk); end // At end of the pin level bus transaction // Copy response data into the rsp fields: $cast(rsp, req.clone()); // Clone the req rsp.set_id_info(req); // Set the rsp id = req id if(rsp.read_not_write == 1) begin rsp.read_data = BUS.read_data; // If read - copy returned read data end rsp.error = BUS.error; // Copy bus error status BUS.valid <= 0; // End the pin level bus transaction seq_item_port.put(rsp); // put returns the response end endtask: run_phase // Corresponding version of the sequence body method: task body(); bus_seq_item req; bus_seq_item rsp; req = bus_seq_item::type_id::create("req"); repeat(limit) begin start_item(req); // The address is constrained to be within the address of the GPIO function // within the DUT, The result will be a request item for a read or a write assert(req.randomize() with {addr inside {[32'h0100_0000:32'h0100_001C]};}); finish_item(req); get_response(rsp); // The rsp handle points to the object that the driver has updated with response data uvm_report_info("seq_body", rsp.convert2string()); end endtask: body |
For more information on this implementation approach, especially how to initialise the response item see the section on the get, put use model in the Driver/Sequence API article.
Download-tarball.png | Download a complete working example: (tarball: use_models_bidir_get_put_uvm.tgz) |