«Бог не меняет того, что (происходит) с людьми, пока они сами не изменят своих помыслов.» Коран, Сура 12:13

UVM/UVM Cookbook/Driver/Bidirectional

Материал из Wiki
< UVM
Перейти к: навигация, поиск
Проект Методология UVM

UVM Cookbook


Вебинары
Литература

* OVM *
500px-Bidirectional_sequence_driver_use_model.gif Bidirectional sequence driver use model.gif

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
500px-Coupled_bus_timing.gif 500px

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)