`ifndef CALC_MONITOR_SV
 `define CALC_MONITOR_SV

//-----------------------------------------------------------------------------
// Calc1 monitor.
//
// Passive component.  One collector thread per port reconstructs a transaction
// from the pin activity:
//   * a request starts when reqX_cmd_in != 0  -> capture cmd and operand1,
//   * operand2 is the data line on the following cycle,
//   * the matching response is the first cycle in which out_respX != 0.
//
// The completed transaction (stimulus + observed response/result) is broadcast
// over the analysis port to the scoreboard, and functional coverage is sampled.
//-----------------------------------------------------------------------------
class calc_monitor extends uvm_monitor;

   // control fields
   bit checks_enable   = 1;
   bit coverage_enable = 1;

   uvm_analysis_port #(calc_seq_item) item_collected_port;

   `uvm_component_utils_begin(calc_monitor)
      `uvm_field_int(checks_enable,   UVM_DEFAULT)
      `uvm_field_int(coverage_enable, UVM_DEFAULT)
   `uvm_component_utils_end

   // The virtual interface used to view HDL signals.
   virtual interface calc_if vif;

   // number of transactions collected (per port and total)
   int unsigned num_collected;

   //--------------------------------------------------------------------------
   // Functional coverage model (Vezba 11).
   //--------------------------------------------------------------------------
   bit [CMD_WIDTH-1:0]  cov_cmd;
   bit [1:0]            cov_port;
   bit [RESP_WIDTH-1:0] cov_resp;
   bit [DATA_WIDTH-1:0] cov_op1;
   bit [DATA_WIDTH-1:0] cov_op2;

   covergroup calc_cg;
      option.per_instance = 1;
      option.name         = "calc_functional_coverage";

      cp_cmd : coverpoint cov_cmd {
         bins add     = {CMD_ADD};
         bins sub     = {CMD_SUB};
         bins shl     = {CMD_SHL};
         bins shr     = {CMD_SHR};
         bins invalid = {[4'h3:4'h4], 4'h7, [4'h8:4'hF]};
      }
      cp_port : coverpoint cov_port {
         bins port1 = {2'd0};
         bins port2 = {2'd1};
         bins port3 = {2'd2};
         bins port4 = {2'd3};
      }
      // the monitor only emits an item once a real response is seen, so only
      // SUCCESS and ERROR are ever sampled here
      cp_resp : coverpoint cov_resp {
         bins success = {RESP_SUCCESS};
         bins error   = {RESP_ERROR};
      }
      // interesting operand corners
      cp_op1 : coverpoint cov_op1 {
         bins zero    = {32'h0000_0000};
         bins one     = {32'h0000_0001};
         bins max     = {32'hFFFF_FFFF};
         bins msb     = {32'h8000_0000};
         bins others  = default;
      }
      cp_op2 : coverpoint cov_op2 {
         bins zero    = {32'h0000_0000};
         bins one     = {32'h0000_0001};
         bins max     = {32'hFFFF_FFFF};
         bins shamt   = {[32'h2:32'h1F]};   // small shift amounts
         bins others  = default;
      }
      // every legal command must be exercised on every port
      cx_cmd_port : cross cp_cmd, cp_port;
      // every command must be seen producing both success and error responses
      cx_cmd_resp : cross cp_cmd, cp_resp;
   endgroup

   function new(string name = "calc_monitor", uvm_component parent = null);
      super.new(name,parent);
      item_collected_port = new("item_collected_port", this);
      calc_cg = new();
   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:",get_full_name(),".vif"})
   endfunction : connect_phase

   task main_phase(uvm_phase phase);
      wait_reset_done();
      // launch one independent collector per port
      for (int p = 0; p < NUM_PORTS; p++) begin
         automatic int port = p;
         fork
            collect_port(port[1:0]);
         join_none
      end
   endtask : main_phase

   // Collect every request/response pair seen on a single port.
   task collect_port(bit [1:0] port);
      calc_seq_item it;
      int unsigned  wait_cnt;
      forever begin
         // wait for the start of a request on this port
         do @(posedge vif.clk); while (get_cmd(port) === CMD_NOP || vif.rst !== '0);

         it      = calc_seq_item::type_id::create($sformatf("it_p%0d", port));
         it.port = port;
         it.cmd  = get_cmd(port);
         it.op1  = get_data(port);

         // operand2 is presented on the next cycle
         @(posedge vif.clk);
         it.op2 = get_data(port);

         // wait for the response on this port (bounded - a non-responding DUT
         // is reported, not waited on forever); resp stays NONE on timeout so
         // the scoreboard flags the missing response
         wait_cnt = 0;
         do begin
            @(posedge vif.clk);
            wait_cnt++;
         end while (get_resp(port) === RESP_NONE && wait_cnt < RSP_TIMEOUT);
         it.resp   = get_resp(port);
         it.result = get_out_data(port);

         num_collected++;
         `uvm_info(get_type_name(),
                   $sformatf("Collected: %s", it.convert2string()), UVM_MEDIUM)

         if (coverage_enable) sample_coverage(it);
         item_collected_port.write(it);
      end
   endtask : collect_port

   function void sample_coverage(calc_seq_item it);
      cov_cmd  = it.cmd;
      cov_port = it.port;
      cov_resp = it.resp;
      cov_op1  = it.op1;
      cov_op2  = it.op2;
      calc_cg.sample();
   endfunction

   //--- per-port signal accessors -------------------------------------------
   function bit [CMD_WIDTH-1:0] get_cmd(bit [1:0] port);
      case (port)
        2'd0 : return vif.req1_cmd_in;
        2'd1 : return vif.req2_cmd_in;
        2'd2 : return vif.req3_cmd_in;
        2'd3 : return vif.req4_cmd_in;
      endcase
   endfunction

   function bit [DATA_WIDTH-1:0] get_data(bit [1:0] port);
      case (port)
        2'd0 : return vif.req1_data_in;
        2'd1 : return vif.req2_data_in;
        2'd2 : return vif.req3_data_in;
        2'd3 : return vif.req4_data_in;
      endcase
   endfunction

   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

   function bit [DATA_WIDTH-1:0] get_out_data(bit [1:0] port);
      case (port)
        2'd0 : return vif.out_data1;
        2'd1 : return vif.out_data2;
        2'd2 : return vif.out_data3;
        2'd3 : return vif.out_data4;
      endcase
   endfunction

   task wait_reset_done();
      while (vif.rst !== '0) @(posedge vif.clk);
   endtask : wait_reset_done

   function void report_phase(uvm_phase phase);
      `uvm_info(get_type_name(),
                $sformatf("Monitor collected %0d transactions, functional coverage = %0.2f%%",
                          num_collected, calc_cg.get_coverage()), UVM_LOW)
   endfunction : report_phase

endclass : calc_monitor

`endif
