Commit b22870d8 authored by Jonas Kaufmann's avatar Jonas Kaufmann Committed by Antoine Kaufmann
Browse files

lib/simbricks/axi: add AXI Lite Manager (Master) adapter

parent 105688c8
/*
* Copyright 2024 Max Planck Institute for Software Systems, and
* National University of Singapore
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef SIMBRICKS_AXI_AXIL_MANAGER_HH_
#define SIMBRICKS_AXI_AXIL_MANAGER_HH_
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <functional>
#include <iostream>
#include <memory>
#include <utility>
#include <variant>
// #define AXIL_R_DEBUG 0
// #define AXIL_W_DEBUG 0
namespace simbricks {
struct AXILOperationR {
AXILOperationR(uint64_t addr, uint64_t req_id) : addr(addr), req_id(req_id) {
}
uint64_t addr;
uint64_t req_id;
uint64_t data = 0;
};
struct AXILOperationW {
AXILOperationW(uint64_t addr, uint64_t req_id, uint64_t data, bool posted)
: addr(addr), req_id(req_id), data(data), posted(posted) {
}
uint64_t addr;
uint64_t req_id;
uint64_t data;
bool posted;
};
template <size_t BytesAddr, size_t BytesData>
class AXILManagerReadPort {
/* address channel */
uint8_t *const ar_addr_;
const uint8_t &ar_ready_;
uint8_t &ar_valid_;
/* data channel */
const uint8_t *const r_data_;
uint8_t &r_ready_;
const uint8_t &r_valid_;
uint8_t &r_resp_;
/* Temp values for outputs. We can't update the outputs directly in `step()`
since that violates the semantics of non-blocking assignments. */
uint8_t ar_valid_tmp_ = 0;
uint64_t ar_addr_tmp_ = 0;
/* other stuff */
uint64_t main_time_ = 0;
AXILOperationR *cur_op_ = nullptr;
std::function<void()> op_done_cb_;
bool handling_op_ = false;
public:
AXILManagerReadPort(uint8_t *ar_addr, const uint8_t &ar_ready,
uint8_t &ar_valid, const uint8_t *r_data,
uint8_t &r_ready, const uint8_t &r_valid, uint8_t &r_resp,
std::function<void()> op_done_cb)
: ar_addr_(ar_addr),
ar_ready_(ar_ready),
ar_valid_(ar_valid),
r_data_(r_data),
r_ready_(r_ready),
r_valid_(r_valid),
r_resp_(r_resp),
op_done_cb_(std::move(op_done_cb)) {
}
void step(uint64_t cur_ts);
void step_apply();
void set_op(AXILOperationR &axi_op) {
if (cur_op_ != nullptr) {
throw "AXILManagerReadPort::set_op() cur_op_ must be completed i.e. NULL "
"before setting new one";
}
cur_op_ = &axi_op;
}
};
template <size_t BytesAddr, size_t BytesData>
class AXILManagerWritePort {
/* address channel */
uint8_t *const aw_addr_;
const uint8_t &aw_ready_;
uint8_t &aw_valid_;
/* data channel */
uint8_t *const w_data_;
const uint8_t &w_ready_;
uint8_t &w_valid_;
uint8_t &w_strb_;
/* response channel */
uint8_t &b_ready_;
const uint8_t &b_valid_;
const uint8_t &b_resp_;
/* Temp values for outputs. We can't update the outputs directly in `step()`
since that violates the semantics of non-blocking assignments. */
uint8_t aw_valid_tmp_ = 0;
uint64_t aw_addr_tmp_ = 0;
uint8_t w_valid_tmp_ = 0;
uint64_t w_data_tmp_ = 0;
/* other stuff */
uint64_t main_time_ = 0;
AXILOperationW *cur_op_ = nullptr;
std::function<void()> op_done_cb_;
bool handling_op_ = false;
public:
AXILManagerWritePort(uint8_t *aw_addr, const uint8_t &aw_ready,
uint8_t &aw_valid, uint8_t *w_data,
const uint8_t &w_ready, uint8_t &w_valid,
uint8_t &w_strb, uint8_t &b_ready,
const uint8_t &b_valid, const uint8_t &b_resp,
std::function<void()> op_done_cb)
: aw_addr_(aw_addr),
aw_ready_(aw_ready),
aw_valid_(aw_valid),
w_data_(w_data),
w_ready_(w_ready),
w_valid_(w_valid),
w_strb_(w_strb),
b_ready_(b_ready),
b_valid_(b_valid),
b_resp_(b_resp),
op_done_cb_(std::move(op_done_cb)) {
}
void step(uint64_t cur_ts);
void step_apply();
void set_op(AXILOperationW &axi_op) {
if (cur_op_ != nullptr) {
throw "AXILManagerReadPort::set_op() cur_op_ must be completed i.e. NULL "
"before setting new one";
}
cur_op_ = &axi_op;
}
};
template <size_t BytesAddr, size_t BytesData>
class AXILManager {
static_assert(BytesData == 4 || BytesData == 8,
"AXI 4 Lite standard dictates 32 or 64 bits data width");
static_assert(BytesAddr <= 8);
using ReadPortT = AXILManagerReadPort<BytesAddr, BytesData>;
using WritePortT = AXILManagerWritePort<BytesAddr, BytesData>;
private:
ReadPortT read_port_;
WritePortT write_port_;
std::deque<std::variant<AXILOperationR, AXILOperationW>> pending_{};
public:
AXILManager(uint8_t *ar_addr, const uint8_t &ar_ready, uint8_t &ar_valid,
const uint8_t *r_data, uint8_t &r_ready, const uint8_t &r_valid,
uint8_t &r_resp, uint8_t *aw_addr, const uint8_t &aw_ready,
uint8_t &aw_valid, uint8_t *w_data, const uint8_t &w_ready,
uint8_t &w_valid, uint8_t &w_strb, uint8_t &b_ready,
const uint8_t &b_valid, const uint8_t &b_resp)
: read_port_(ReadPortT(
ar_addr, ar_ready, ar_valid, r_data, r_ready, r_valid, r_resp,
std::bind(&AXILManager<BytesAddr, BytesData>::op_done, this))),
write_port_(WritePortT(
aw_addr, aw_ready, aw_valid, w_data, w_ready, w_valid, w_strb,
b_ready, b_valid, b_resp,
std::bind(&AXILManager<BytesAddr, BytesData>::op_done, this))) {
}
/*
Updates the output signals based on the inputs.
This function doesn't apply the output changes yet. This is necessary to
properly model the semantics of non-blocking writes, i.e. the changes only
become visible to the connected Subordinate in the next clock cycle. In
Verilator, call this function before `eval()`. To apply the output changes,
call `step_apply()`.
*/
void step(uint64_t cur_ts);
/*
Applies the output changes. In Verilator, call this function after `eval()`.
*/
void step_apply();
/* issue a new request */
void issue_read(uint64_t req_id, uint64_t addr);
void issue_write(uint64_t req_id, uint64_t addr, uint64_t data, bool posted);
protected:
virtual void read_done(AXILOperationR &axi_op) = 0;
virtual void write_done(AXILOperationW &axi_op) = 0;
private:
void op_done();
void ports_set_op();
};
/******************************************************************************/
/* Start of implementation. We need to put this into the header since code for
templated classes is only emitted at instantiation. */
/* AXILManagerReadPort */
/******************************************************************************/
template <size_t BytesAddr, size_t BytesData>
void AXILManagerReadPort<BytesAddr, BytesData>::step(uint64_t cur_ts) {
main_time_ = cur_ts;
/* drive these signals to constants */
r_ready_ = 1;
r_resp_ = 0;
/* addr handshake complete */
if (ar_valid_ && ar_ready_) {
assert(cur_op_ != nullptr);
ar_valid_tmp_ = 0;
ar_addr_tmp_ = 0;
#if AXIL_R_DEBUG
std::cout << main_time_
<< " AXIL R addr handshake done id=" << cur_op_->req_id << "\n";
#endif
}
/* data handshake complete */
if (r_ready_ && r_valid_) {
assert(cur_op_ != nullptr);
#if AXIL_R_DEBUG
std::cout << main_time_
<< " AXIL R read data segment id=" << cur_op_->req_id << "\n";
#endif
std::memcpy(&cur_op_->data, r_data_, BytesData);
cur_op_ = nullptr;
handling_op_ = false;
op_done_cb_();
}
/* issue new read request */
if (!handling_op_ && cur_op_ != nullptr) {
handling_op_ = true;
ar_addr_tmp_ = cur_op_->addr;
ar_valid_tmp_ = 1;
#if AXIL_R_DEBUG
std::cout << main_time_ << " AXIL R issuing new op id=" << cur_op_->req_id
<< " addr=" << cur_op_->addr << "\n";
#endif
}
}
template <size_t BytesAddr, size_t BytesData>
void AXILManagerReadPort<BytesAddr, BytesData>::step_apply() {
ar_valid_ = ar_valid_tmp_;
std::memcpy(ar_addr_, &ar_addr_tmp_, BytesAddr);
}
/******************************************************************************/
/* AXILManagerWritePort */
/******************************************************************************/
template <size_t BytesAddr, size_t BytesData>
void AXILManagerWritePort<BytesAddr, BytesData>::step(uint64_t cur_ts) {
main_time_ = cur_ts;
/* drive these signals to constants */
w_strb_ = 0xff;
b_ready_ = 1;
/* addr handshake complete */
if (aw_valid_ && aw_ready_) {
aw_valid_tmp_ = 0;
aw_addr_tmp_ = 0;
#if AXIL_W_DEBUG
std::cout << main_time_
<< " AXIL W addr handshake done id=" << cur_op_->req_id << "\n";
#endif
}
/* handshake for data complete */
if (w_ready_ && w_valid_) {
#if AXIL_W_DEBUG
std::cout << main_time_
<< " AXIL W data handshake done id=" << cur_op_->req_id << "\n";
#endif
w_valid_tmp_ = 0;
w_data_tmp_ = 0;
}
/* response handshake complete */
if (b_ready_ && b_valid_) {
#if AXIL_W_DEBUG
std::cout << main_time_ << " AXIL W completed id=" << cur_op_->req_id
<< "\n";
#endif
cur_op_ = nullptr;
handling_op_ = false;
op_done_cb_();
}
/* issue new request */
if (!handling_op_ && cur_op_ != nullptr) {
#if AXIL_W_DEBUG
std::cout << main_time_ << " AXIL W issuing new op id=" << cur_op_->req_id
<< " addr=" << cur_op_->addr << "\n";
#endif
handling_op_ = true;
aw_addr_tmp_ = cur_op_->addr;
aw_valid_tmp_ = 1;
w_data_tmp_ = cur_op_->data;
w_valid_tmp_ = 1;
}
}
template <size_t BytesAddr, size_t BytesData>
void AXILManagerWritePort<BytesAddr, BytesData>::step_apply() {
aw_valid_ = aw_valid_tmp_;
std::memcpy(aw_addr_, &aw_addr_tmp_, BytesAddr);
w_valid_ = w_valid_tmp_;
std::memcpy(w_data_, &w_data_tmp_, BytesData);
}
/******************************************************************************/
/* AXILManager */
/******************************************************************************/
template <size_t BytesAddr, size_t BytesData>
void AXILManager<BytesAddr, BytesData>::step(uint64_t cur_ts) {
if (pending_.empty()) {
return;
}
auto &axi_op = pending_.front();
if (std::holds_alternative<AXILOperationR>(axi_op)) {
read_port_.step(cur_ts);
} else if (std::holds_alternative<AXILOperationW>(axi_op)) {
write_port_.step(cur_ts);
}
}
template <size_t BytesAddr, size_t BytesData>
void AXILManager<BytesAddr, BytesData>::step_apply() {
if (pending_.empty()) {
return;
}
auto &axi_op = pending_.front();
if (std::holds_alternative<AXILOperationR>(axi_op)) {
read_port_.step_apply();
} else if (std::holds_alternative<AXILOperationW>(axi_op)) {
write_port_.step_apply();
}
}
template <size_t BytesAddr, size_t BytesData>
void AXILManager<BytesAddr, BytesData>::issue_read(uint64_t req_id,
uint64_t addr) {
if (addr % BytesData != 0) {
throw "AXILManager::issue_read() addr has to be aligned to BytesData";
}
bool was_empty = pending_.empty();
pending_.emplace_back(AXILOperationR{addr, req_id});
if (was_empty) {
ports_set_op();
}
}
template <size_t BytesAddr, size_t BytesData>
void AXILManager<BytesAddr, BytesData>::issue_write(uint64_t req_id,
uint64_t addr,
uint64_t data,
bool posted) {
if (addr % BytesData != 0) {
throw "AXILManager::issue_write() addr has to be aligned to BytesData";
}
bool was_empty = pending_.empty();
pending_.emplace_back(AXILOperationW{addr, req_id, data, posted});
if (was_empty) {
ports_set_op();
}
}
template <size_t BytesAddr, size_t BytesData>
void AXILManager<BytesAddr, BytesData>::op_done() {
auto &axi_op = pending_.front();
if (std::holds_alternative<AXILOperationR>(axi_op)) {
read_done(std::get<AXILOperationR>(axi_op));
} else if (std::holds_alternative<AXILOperationW>(axi_op)) {
write_done(std::get<AXILOperationW>(axi_op));
}
pending_.pop_front();
ports_set_op();
}
template <size_t BytesAddr, size_t BytesData>
void AXILManager<BytesAddr, BytesData>::ports_set_op() {
if (pending_.empty()) {
return;
}
auto &axi_op = pending_.front();
if (std::holds_alternative<AXILOperationR>(axi_op)) {
read_port_.set_op(std::get<AXILOperationR>(axi_op));
} else if (std::holds_alternative<AXILOperationW>(axi_op)) {
write_port_.set_op(std::get<AXILOperationW>(axi_op));
}
}
} // namespace simbricks
#endif // SIMBRICKS_AXI_AXIL_MANAGER_HH_
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment