//
// 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

import renode_pkg::renode_runtime, renode_pkg::LogWarning;

module renode_apb3_completer #(
    parameter int unsigned OutputLatency = 0,
    int CosimToRenodeIndex = 0
) (
    ref renode_runtime runtime,
    renode_apb3_if bus
);

  typedef logic [bus.AddressWidth-1:0] address_t;
  typedef logic [bus.DataWidth-1:0] data_t;

  // Renaming the bus is a style preference
  wire clk;
  assign clk = bus.pclk;
  logic rst_n;
  assign bus.presetn = rst_n;

  address_t paddr;
  logic     pselx;
  logic     penable;
  logic     pwrite;
  data_t    pwdata;
  logic     pready;
  data_t    prdata;
  logic     pslverr;

  assign paddr = bus.paddr;
  assign pselx = bus.pselx;
  assign penable = bus.penable;
  assign pwrite = bus.pwrite;
  assign pwdata = bus.pwdata;

  assign bus.pready = pready;
  assign bus.prdata = prdata;
  assign bus.pslverr = pslverr;

  // Connection initiated reset
  always @(runtime.peripherals[CosimToRenodeIndex].reset_assert_request) begin
    rst_n = 0;
    // The reset takes 2 cycles to prevent a race condition without usage of a non-blocking assigment.
    repeat (2) @(posedge clk);
    runtime.peripherals[CosimToRenodeIndex].reset_assert_respond();
  end

  always @(runtime.peripherals[CosimToRenodeIndex].reset_deassert_request) begin
    rst_n = 1;
    // There is one more wait for the clock edges to be sure that all modules aren't in a reset state.
    repeat (2) @(posedge clk);
    runtime.peripherals[CosimToRenodeIndex].reset_deassert_respond();
  end

  renode_pkg::valid_bits_e valid_bits;
  assign valid_bits = renode_pkg::valid_bits_e'((1 << bus.DataWidth) - 1);

  //
  // APB3 Completer
  //

  bit is_error;
  renode_pkg::data_t prdata_int;

  // Internal state
  typedef enum {
    STATE_IDLE,
    STATE_ACCESS
  } state_t;
  state_t peripheral_state;
  state_t peripheral_state_next;

  // Next state logic
  always_comb begin : proc_fsm_next_state
    case (peripheral_state)
      STATE_IDLE: begin
        if (pselx && !penable) begin
          peripheral_state_next = STATE_ACCESS;
        end
      end
      STATE_ACCESS: begin
        peripheral_state_next = STATE_IDLE;
      end
      default: begin
        peripheral_state_next = STATE_IDLE;
      end
    endcase
  end

  // FSM logic
  always_ff @(posedge clk or negedge rst_n) begin : proc_fsm
    if (rst_n == 1'b0) begin
      peripheral_state <= STATE_IDLE;
    end else begin
      peripheral_state <= peripheral_state_next;

      // Write Enable logic: write data to memory in Renode
      if (pselx && penable && pwrite) begin
        // Workaround::Bug::Verilator::Task call inside of always block requires using fork...join
        fork
          begin
            runtime.peripherals[CosimToRenodeIndex].write(renode_pkg::address_t'(paddr), valid_bits, renode_pkg::data_t'(pwdata),
                             is_error);
            if (is_error) begin
              runtime.connection.log(LogWarning, "Renode connection write transfer was unable to complete");
            end
          end
        join
      end

      // Read Enable logic: read data from memory in Renode
      if (pselx && !penable && !pwrite) begin
        // Workaround::Bug::Verilator::Task call inside of always block requires using fork...join
        fork
          begin
            // The runtime.peripherals[CosimToRenodeIndex].read call may cause elapse of a simulation time.
            runtime.peripherals[CosimToRenodeIndex].read(renode_pkg::address_t'(paddr), valid_bits, prdata_int, is_error);
            if (is_error) begin
              runtime.connection.log(LogWarning, "Renode connection read transfer was unable to complete");
            end
          end
        join
      end else begin
        prdata_int <= '0;
      end
    end
  end

  //
  // Generate artificial delay to the {PRDATA,PREADY} signals
  // Useful to validate wait states, by default is turned off.
  //
  genvar i;
  generate
    if (OutputLatency == 0) begin : gen_latency_0
      assign prdata = data_t'(prdata_int);
      assign pready = (peripheral_state == STATE_ACCESS);
    end else begin : gen_latency_gt_0
      data_t prdata_reg[OutputLatency];
      data_t pready_reg[OutputLatency];
      for (i = 0; i < OutputLatency; i++) begin : gen_output_registers
        if (i == 0) begin : gen_i_eq_0
          always_ff @(posedge clk or negedge rst_n) begin : proc_first_reg
            prdata_reg[i] <= prdata_int;
            pready_reg[i] <= (peripheral_state == STATE_ACCESS);
          end
        end else begin: gen_i_neq_0
          always_ff @(posedge clk or negedge rst_n) begin : proc_latency_ith
            prdata_reg[i] <= prdata_reg[i-1];
            pready_reg[i] <= pready_reg[i-1];
          end
        end
      end
      assign prdata = prdata_reg[OutputLatency-1];
      assign pready = pready_reg[OutputLatency-1];
    end
  endgenerate

endmodule

