`ifndef CALC_DRIVER_SV
 `define CALC_DRIVER_SV

//-----------------------------------------------------------------------------
// Calc1 driver.
//
// Protocol (Vezba 5):
//   * command + operand1 are driven in the same cycle,
//   * operand2 is driven in the next cycle (command line back to 0),
//   * a response appears on out_respX a few (>=3) cycles later.
//
// Only one request may be outstanding per port, so after issuing a request the
// driver waits for that port's response before completing the item.  This keeps
// the very simple "bidirectional, non-pipelined" use model from Vezba 6.
//-----------------------------------------------------------------------------
class calc_driver extends uvm_driver#(calc_seq_item);

   `uvm_component_utils(calc_driver)

   virtual interface calc_if vif;

   function new(string name = "calc_driver", uvm_component parent = null);
      super.new(name,parent);
   endfunction

   function void connect_phase(uvm_phase phase);
      super.connect_phase(phase);
      if (!uvm_config_db#(virtual calc_if)::get(this, "", "calc_if", vif))
        `uvm_fatal("NOVIF",{"virtual interface must be set for: ",get_full_name(),".vif"})
   endfunction : connect_phase

   task main_phase(uvm_phase phase);
      // start from a known idle state and wait until reset is released
      reset_inputs();
      wait_reset_done();

      forever begin
         seq_item_port.get_next_item(req);
         `uvm_info(get_type_name(),
                   $sformatf("Driving: %s", req.convert2string()), UVM_HIGH)
         drive_item(req);
         seq_item_port.item_done();
      end
   endtask : main_phase

   // Drive a single transaction following the two-cycle request protocol and
   // wait for the corresponding response on the same port.
   task drive_item(calc_seq_item it);
      int unsigned wait_cnt;

      // optional idle gap before the request
      repeat (it.delay) @(posedge vif.clk);

      // cycle 1: command + operand1
      drive_req(it.port, it.cmd, it.op1);
      @(posedge vif.clk);

      // cycle 2: operand2, command de-asserted
      drive_req(it.port, CMD_NOP, it.op2);
      @(posedge vif.clk);

      // idle the port again while the pipeline produces the result
      drive_req(it.port, CMD_NOP, '0);

      // wait for this port's response (resp != 0), bounded so a non-responding
      // DUT cannot deadlock the test
      wait_cnt = 0;
      do begin
         @(posedge vif.clk);
         wait_cnt++;
      end while (get_resp(it.port) === RESP_NONE && wait_cnt < RSP_TIMEOUT);

      if (get_resp(it.port) === RESP_NONE)
        `uvm_warning(get_type_name(),
                     $sformatf("No response on port %0d within %0d cycles (cmd=%s) - the scoreboard will flag this",
                               it.port+1, RSP_TIMEOUT, it.cmd2string()))
   endtask : drive_item

   // Drive command/data onto the selected port, leaving the others untouched.
   task drive_req(bit [1:0] port, bit [CMD_WIDTH-1:0] cmd, bit [DATA_WIDTH-1:0] data);
      case (port)
        2'd0 : begin vif.req1_cmd_in <= cmd; vif.req1_data_in <= data; end
        2'd1 : begin vif.req2_cmd_in <= cmd; vif.req2_data_in <= data; end
        2'd2 : begin vif.req3_cmd_in <= cmd; vif.req3_data_in <= data; end
        2'd3 : begin vif.req4_cmd_in <= cmd; vif.req4_data_in <= data; end
      endcase
   endtask : drive_req

   // Combinational read of a port's response line.
   function bit [RESP_WIDTH-1:0] get_resp(bit [1:0] port);
      case (port)
        2'd0 : return vif.out_resp1;
        2'd1 : return vif.out_resp2;
        2'd2 : return vif.out_resp3;
        2'd3 : return vif.out_resp4;
      endcase
   endfunction

   // Drive all request lines to their idle (zero) state.
   task reset_inputs();
      vif.req1_cmd_in <= '0; vif.req1_data_in <= '0;
      vif.req2_cmd_in <= '0; vif.req2_data_in <= '0;
      vif.req3_cmd_in <= '0; vif.req3_data_in <= '0;
      vif.req4_cmd_in <= '0; vif.req4_data_in <= '0;
   endtask : reset_inputs

   task wait_reset_done();
      // reset is active-high (all ones); wait until it is fully released
      while (vif.rst !== '0) @(posedge vif.clk);
      `uvm_info(get_type_name(), "Reset released - starting to drive", UVM_LOW)
   endtask : wait_reset_done

endclass : calc_driver

`endif
