//
// Copyright (c) 2010-2024 Antmicro
//
// This file is licensed under the MIT License.
// Full license text is available in 'licenses/MIT.txt'.
//

`timescale 1ns / 1ps

package renode_pkg;
  typedef longint address_t;
  typedef longint data_t;

  typedef enum int {
    // The included file contains enumerators of the action type used by the Renode protocol.
    // The values must be in sync with ActionType from Renode (defined in C#).
    `include "renode_action_enumerators.svh"
  } action_e;

  const int no_peripheral_index = -1;

  typedef struct {
    action_e action;
    address_t address;
    data_t data;
    int peripheral_index;
  } message_t;

  typedef enum data_t {
    QuadWord = {64{1'b1}},
    DoubleWord = {32'b0, {32{1'b1}}},
    Word = {48'b0, {16{1'b1}}},
    Byte = {56'b0, {8{1'b1}}}
  } valid_bits_e;

  typedef enum int {
    LogNoisy = -1,
    LogDebug = 0,
    LogInfo = 1,
    LogWarning = 2,
    LogError = 3
  } log_level_e;

  import "DPI-C" function void renodeDPIConnect(
    int receiverPort,
    int senderPort,
    string address
  );

  import "DPI-C" function void renodeDPIDisconnect();

  import "DPI-C" function bit renodeDPIIsConnected();

  import "DPI-C" function bit renodeDPILog(
    int logLevel,
    string data
  );

  import "DPI-C" function bit renodeDPIReceive(
    output action_e action,
    output address_t address,
    output data_t data,
    output int peripheralIndex 
  );

  import "DPI-C" function bit renodeDPISend(
    action_e action,
    address_t address,
    data_t data,
    int peripheralIndex
  );

  import "DPI-C" function bit renodeDPISendToAsync(
    action_e action,
    address_t address,
    data_t data,
    int peripheralIndex
  );

  function static bit is_access_aligned(address_t address, valid_bits_e valid_bits);
    case(valid_bits)
      Byte: return 1;
      Word: return address % 2 == 0;
      DoubleWord: return address % 4 == 0;
      QuadWord: return address % 8 == 0;
      default: return 0;
    endcase
  endfunction

  function static integer valid_bits_to_transaction_width(valid_bits_e valid_bits);
    case (valid_bits)
      QuadWord: return 64;
      DoubleWord: return 32;
      Word: return 16;
      Byte: return 8;
      default: begin
          $error($sformatf("Cannot determine transaction width for valid_bits %d", valid_bits));
          return 0;
      end
    endcase
  endfunction

  class renode_connection;
    semaphore exclusive_receive = new(1);

    function new();
      $timeformat(0, 9, "s", 0);
    endfunction

    function void connect(int receiver_port, int sender_port, string address);
      renodeDPIConnect(receiver_port, sender_port, address);
      if(is_connected())
        $display("Renode at %t: Connected using the socket based interface", $realtime);
      else
        $error("Renode at %t: Connection error", $realtime);
    endfunction

    function bit is_connected();
      return renodeDPIIsConnected();
    endfunction

    function void fatal_error(string message);
      string error_msg;
      error_msg = $sformatf("Renode at %t: Error! %s", $realtime, message);
      log(LogError, error_msg);
      disconnect();
      $error(error_msg);
      $finish;
    endfunction

    function void handle_message(message_t message, output bit is_handled);
      is_handled = 1;
      case (message.action)
        renode_pkg::invalidAction: ;  // Intentionally left blank
        renode_pkg::disconnect: handle_disconnect();
        default: is_handled = 0;
      endcase
    endfunction

    function void log(log_level_e log_level, string message);
`ifdef RENODE_DEBUG
      $display("Renode at %t logs: %s", $realtime, message);
`endif
      if(!renodeDPILog(log_level, message)) begin
        $display("Renode at %t: Unable to send the log: %s", $realtime, message);
      end
    endfunction

    function void receive(output message_t message);
      bit is_received = try_receive(message);
      if (!is_received) fatal_error("Unable to receive a message.");
    endfunction

    function bit try_receive(output message_t message);
      bit is_received = renodeDPIReceive(message.action, message.address, message.data, message.peripheral_index);
      if(is_received) begin
`ifdef RENODE_DEBUG
      log(LogDebug, $sformatf("Renode at %t: Received action %0s, address = 'h%h, data = 'h%h, peripheral index: %d", $realtime, message.action.name(), message.address, message.data, message.peripheral_index));
`endif
      end
      return is_received;
    endfunction

    function void send(message_t message);
`ifdef RENODE_DEBUG
      log(LogDebug, $sformatf("Renode at %t: Sent action %0s, address = 'h%h, data = 'h%h, peripheral index: %d", $realtime, message.action.name(), message.address, message.data, message.peripheral_index));
`endif
      if (!renodeDPISend(message.action, message.address, message.data, message.peripheral_index)) fatal_error("Unexpected channel disconnection");
    endfunction

    function void send_to_async_receiver(message_t message);
`ifdef RENODE_DEBUG
      log(LogDebug, $sformatf("Renode at %t: Sent async action %0s, address = 'h%h, data = 'h%h, peripheral index: %d", $realtime, message.action.name(), message.address, message.data, message.peripheral_index));
`endif
      if (!renodeDPISendToAsync(message.action, message.address, message.data, message.peripheral_index)) fatal_error("Unexpected channel disconnection");
    endfunction

    local function void disconnect();
      renodeDPIDisconnect();
    endfunction

    local function void handle_disconnect();
      send(message_t'{ok, 0, 0, renode_pkg::no_peripheral_index});
      disconnect();
`ifdef RENODE_DEBUG
      $display("Renode at %t: disconnected", $realtime);
`endif
    endfunction
  endclass

  class bus_connection;
    event reset_assert_request;
    event reset_assert_response;
    event reset_deassert_request;
    event reset_deassert_response;

    event read_transaction_request;
    event read_transaction_response;
    address_t read_transaction_address;
    data_t read_transaction_data;
    valid_bits_e read_transaction_data_bits;
    bit read_transaction_is_error;

    event write_transaction_request;
    event write_transaction_response;
    address_t write_transaction_address;
    data_t write_transaction_data;
    valid_bits_e write_transaction_data_bits;
    bit write_transaction_is_error;

    // Passing a class by a reference isn't supported by Verilator.
    // Events are indirectly triggered by tasks.
    task reset_assert();
      ->reset_assert_request;
      @(reset_assert_response);
    endtask

    task reset_assert_respond();
      ->reset_assert_response;
    endtask

    task reset_deassert();
      ->reset_deassert_request;
      @(reset_deassert_response);
    endtask

    task reset_deassert_respond();
      ->reset_deassert_response;
    endtask

    task read(address_t address, valid_bits_e data_bits, output data_t data, output bit is_error);
      read_transaction_address = address;
      read_transaction_data_bits = data_bits;
      ->read_transaction_request;
      @(read_transaction_response) begin
        data = read_transaction_data;
        is_error = read_transaction_is_error;
      end
    endtask

    task read_respond(data_t data, bit is_error);
      read_transaction_data = data;
      read_transaction_is_error = is_error;
      ->read_transaction_response;
    endtask

    task write(address_t address, valid_bits_e data_bits, data_t data, output bit is_error);
      write_transaction_address = address;
      write_transaction_data_bits = data_bits;
      write_transaction_data = data;
      ->write_transaction_request;
      @(write_transaction_response) begin
        is_error = write_transaction_is_error;
      end
    endtask

    task write_respond(bit is_error);
      write_transaction_is_error = is_error;
      ->write_transaction_response;
    endtask
  endclass

  // It's required to pass the whole instance to modules.
  // Passing single property triggers a null pointer dereference in Verilator.
  class renode_runtime;
    const string ReceiverPortArgName = "RENODE_RECEIVER_PORT";
    const string SenderPortArgName = "RENODE_SENDER_PORT";
    const string AddressArgName = "RENODE_ADDRESS";

    renode_connection connection = new();

    // Initialized by the Renode module
    bus_connection controllers[];
    bus_connection peripherals[];

    function void connect_plus_args();
      int receiver_port, sender_port;
      string address;
      if(!$value$plusargs({ReceiverPortArgName, "=%d"}, receiver_port)
        || !$value$plusargs({SenderPortArgName, "=%d"}, sender_port)
        || !$value$plusargs({AddressArgName, "=%s"}, address))
      begin
          $error("Please specify the +%s, +%s and +%s arguments in the command that invokes the simulation", ReceiverPortArgName, SenderPortArgName, AddressArgName);
      end
      else begin
        connection.connect(receiver_port, sender_port, address);
      end
    endfunction

    function bit is_connected();
      return connection.is_connected();
    endfunction
  endclass
endpackage
