//
// 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_axi_subordinate #(int CosimToRenodeIndex = 0) (
    ref renode_runtime runtime,
    renode_axi_if bus
);
  import renode_axi_pkg::*;

  typedef logic [bus.AddressWidth-1:0] address_t;
  typedef logic [bus.DataWidth-1:0] data_t;
  typedef logic [bus.StrobeWidth-1:0] strobe_t;
  typedef logic [bus.TransactionIdWidth-1:0] transaction_id_t;

  wire clk = bus.aclk;

  always @(runtime.peripherals[CosimToRenodeIndex].reset_assert_request) begin
    bus.rvalid = 0;
    bus.bvalid = 0;
    bus.areset_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
    bus.areset_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

  always @(clk) read_transaction();
  always @(clk) write_transaction();

  task static read_transaction();
    transaction_id_t transaction_id;
    address_t address;
    renode_pkg::data_t data;
    bit is_error;
    burst_size_t burst_size;
    burst_length_t burst_length;
    burst_type_e burst_type;
    address_t address_last;
    renode_pkg::valid_bits_e valid_bits;
    address_t transfer_bytes;

    get_read_address(transaction_id, address, burst_size, burst_length, burst_type);
    valid_bits = bus.burst_size_to_valid_bits(burst_size);
    transfer_bytes = 2**burst_size;
    address_last = address + transfer_bytes * burst_length;
    for (; address <= address_last; address += transfer_bytes) begin
      if(!is_access_valid(address, valid_bits, burst_type)) begin
          set_read_response(transaction_id, 0, SlaveError, address == address_last);
      end
      else begin
          // The conection.read call may cause simulation time to move forward
          runtime.peripherals[CosimToRenodeIndex].read(renode_pkg::address_t'(address), valid_bits, data, is_error);
          if (is_error) begin
            runtime.connection.log(LogWarning, $sformatf("Unable to read data from Renode at address 'h%h, responding with 0.", address));
            data = 0;
          end
          data = data & valid_bits;
          set_read_response(transaction_id, data_t'(data) << ((address % transfer_bytes) * 8), is_error ? SlaveError : Okay, address == address_last);
      end
    end
  endtask

  task static write_transaction();
    transaction_id_t transaction_id;
    address_t address;
    data_t data;
    bit is_error;
    bit last_transfer;
    burst_size_t burst_size;
    burst_length_t burst_length;
    burst_type_e burst_type;
    address_t address_last;
    renode_pkg::valid_bits_e valid_bits;
    address_t transfer_bytes;

    get_write_address(transaction_id, address, burst_size, burst_length, burst_type);
    valid_bits = bus.burst_size_to_valid_bits(burst_size);
    if(!is_access_valid(address, valid_bits, burst_type)) begin
      do @(posedge clk); while (!bus.wvalid);
      bus.wready <= 1;
      @(posedge clk);
      bus.wready <= 0;
      set_write_response(transaction_id, SlaveError);
    end
    else begin
      transfer_bytes = 2**burst_size;
      address_last = address + transfer_bytes * burst_length;


      for (; address <= address_last; address += transfer_bytes) begin
        do @(posedge clk); while (!bus.wvalid);
        bus.wready <= 1;
        data = bus.wdata >> ((address % transfer_bytes) * 8);

        @(posedge clk);
        bus.wready <= 0;

        if (bus.wlast != (address == address_last)) runtime.connection.log(LogWarning, "Unexpected state of the wlast signal.");
        // The conection.write call may cause simulation time to move forward
        runtime.peripherals[CosimToRenodeIndex].write(renode_pkg::address_t'(address), valid_bits, renode_pkg::data_t'(data) & valid_bits, is_error);
        if (is_error) runtime.connection.log(LogWarning, $sformatf("Unable to write data to Renode at address 'h%h", address));
      end

      set_write_response(transaction_id, Okay);
    end
  endtask

  function static is_access_valid(address_t address, renode_pkg::valid_bits_e valid_bits, burst_type_e burst_type);
    if(!renode_pkg::is_access_aligned(renode_pkg::address_t'(address), valid_bits)) begin
      runtime.connection.log(LogWarning, $sformatf("Unaligned access to 0x%08X unsupported by AXI Subordinate. This will result in bus error response.", address));
      return 0;
    end
    if(burst_type != Incrementing) begin
      runtime.connection.fatal_error($sformatf("Unsupported burst type 'b%b", burst_type));
      return 0;
    end
    return 1;
  endfunction

  task static get_read_address(output transaction_id_t transaction_id, output address_t address,
                               output burst_size_t burst_size, output burst_length_t burst_length, output burst_type_e burst_type);
    @(posedge clk);
    bus.arready <= 1;

    do @(posedge clk); while (!bus.arvalid);
    transaction_id = bus.arid;
    address = bus.araddr;
    burst_size = bus.arsize;
    burst_length = bus.arlen;
    burst_type = burst_type_e'(bus.arburst);
    bus.arready <= 0;
  endtask

  task static get_write_address(output transaction_id_t transaction_id, output address_t address,
                                output burst_size_t burst_size, output burst_length_t burst_length, output burst_type_e burst_type);
    @(posedge clk);
    bus.awready <= 1;

    do @(posedge clk); while (!bus.awvalid);
    transaction_id = bus.awid;
    address = bus.awaddr;
    burst_size = bus.awsize;
    burst_length = bus.awlen;
    burst_type = burst_type_e'(bus.awburst);
    bus.awready <= 0;
  endtask

  task static set_read_response(transaction_id_t transaction_id, data_t data, response_e response, bit last);
        @(posedge clk);
        bus.rid <= transaction_id;
        bus.rdata <= data;
        bus.rresp <= response;
        bus.rlast <= last;
        bus.rvalid <= 1;

        // It's required to assert the valid and ready signals only for one clock cycle.
        do @(posedge clk); while (!bus.rready);
        bus.rlast  <= 0;
        bus.rvalid <= 0;
  endtask

  task static set_write_response(transaction_id_t id, response_e response);
    bus.bid   = id;
    bus.bresp = response;

    @(posedge clk);
    bus.bvalid <= 1;

    do @(posedge clk); while (!bus.bready);
    bus.bvalid <= 0;
  endtask
endmodule

