blob: dc2ea1879bf83ab236656084fec0959b92b33140 [file] [log] [blame]
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef HW_SIM_HW_PRIMITIVES_H_
#define HW_SIM_HW_PRIMITIVES_H_
#include <verilated.h>
#include <algorithm>
#include <map>
#include <memory>
#include <queue>
#include <vector>
#include "absl/types/span.h"
// A class that wraps and controls a verilator clock signal. Also provides an
// observer mechanism
class Clock {
public:
// A class that observes changes to the clock signal. The constructor and
// destructor will automatically subscribe/unsubscribe from the clock.
class Observer {
public:
explicit Observer(Clock* clock);
virtual ~Observer();
virtual void OnRisingEdge() {}
virtual void OnFallingEdge() {}
protected:
Clock& clock() { return *clock_; }
private:
Clock* const clock_;
};
template <typename Model>
Clock(VerilatedContext* context, uint8_t* clock, Model* model)
: context_(context),
clock_(clock),
eval_function_([model]() { model->eval(); }) {}
~Clock() = default;
// Advance the clock on cycle (one positive edge, one negative edge).
void Step();
// Update the simulation. If observers change input signals to the design,
// they should call this function to ensure internal signals get updated.
void Eval();
private:
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
VerilatedContext* const context_;
uint8_t* const clock_;
std::function<void()> eval_function_;
std::vector<Observer*> observers_;
};
// Struct representing the data transferred in an AXI4 read/write addr channel.
struct AxiAddr {
uint32_t addr_bits_addr;
uint8_t addr_bits_prot;
uint8_t addr_bits_id;
uint8_t addr_bits_len;
uint8_t addr_bits_size;
uint8_t addr_bits_burst;
uint8_t addr_bits_lock;
uint8_t addr_bits_cache;
uint8_t addr_bits_qos;
uint8_t addr_bits_region;
// Create an AxiAddr from a transfer id, starting address and transaction
// length.
static AxiAddr FromIdAddrSize(int id, uint32_t addr, uint32_t byte_length);
};
// Struct representing the data transferred in an AXI4 write data channel.
struct AxiWData {
VlWide<4> write_data_bits_data;
uint16_t write_data_bits_strb;
uint8_t write_data_bits_last;
};
// A driver to control interactions of the write channels in an AXI4 slave.
class AxiSlaveWriteDriver : Clock::Observer {
public:
AxiSlaveWriteDriver(
Clock* clock, uint8_t* write_addr_valid, uint32_t* write_addr_bits_addr,
uint8_t* write_addr_bits_prot, uint8_t* write_addr_bits_id,
uint8_t* write_addr_bits_len, uint8_t* write_addr_bits_size,
uint8_t* write_addr_bits_burst, uint8_t* write_addr_bits_lock,
uint8_t* write_addr_bits_cache, uint8_t* write_addr_bits_qos,
uint8_t* write_addr_bits_region, const uint8_t* write_addr_ready,
uint8_t* write_data_valid, VlWide<4>* write_data_bits_data,
uint16_t* write_data_bits_strb, uint8_t* write_data_bits_last,
const uint8_t* write_data_ready, const uint8_t* write_resp_valid,
const uint8_t* write_resp_bits_id, const uint8_t* write_resp_bits_resp,
uint8_t* write_resp_ready)
: Clock::Observer(clock),
write_addr_valid_(write_addr_valid),
write_addr_bits_addr_(write_addr_bits_addr),
write_addr_bits_prot_(write_addr_bits_prot),
write_addr_bits_id_(write_addr_bits_id),
write_addr_bits_len_(write_addr_bits_len),
write_addr_bits_size_(write_addr_bits_size),
write_addr_bits_burst_(write_addr_bits_burst),
write_addr_bits_lock_(write_addr_bits_lock),
write_addr_bits_cache_(write_addr_bits_cache),
write_addr_bits_qos_(write_addr_bits_qos),
write_addr_bits_region_(write_addr_bits_region),
write_addr_ready_(write_addr_ready),
write_data_valid_(write_data_valid),
write_data_bits_data_(write_data_bits_data),
write_data_bits_strb_(write_data_bits_strb),
write_data_bits_last_(write_data_bits_last),
write_data_ready_(write_data_ready),
write_resp_valid_(write_resp_valid),
write_resp_bits_id_(write_resp_bits_id),
write_resp_bits_resp_(write_resp_bits_resp),
write_resp_ready_(write_resp_ready) {
// Always ready to accept response
*write_resp_ready_ = 1;
}
~AxiSlaveWriteDriver() final = default;
std::shared_ptr<bool> WriteTransaction(int id, uint32_t addr,
absl::Span<const uint8_t> data) {
// Enqueue addr
AxiAddr axi_addr = AxiAddr::FromIdAddrSize(id, addr, data.size());
EnqueueAddr(axi_addr);
// Enqueue data
while (data.size() > 0) {
uint32_t base_addr = (addr / 16) * 16;
uint32_t sub_addr = addr - base_addr;
uint32_t bytes_to_write = 16 - sub_addr;
bytes_to_write =
std::min(static_cast<uint32_t>(data.size()), bytes_to_write);
absl::Span<const uint8_t> local_data = data.subspan(0, bytes_to_write);
AxiWData axi_data;
uint8_t* data_ptr =
reinterpret_cast<uint8_t*>(&(axi_data.write_data_bits_data[0])) +
sub_addr;
memcpy(data_ptr, local_data.data(), bytes_to_write);
axi_data.write_data_bits_strb = 0;
for (uint32_t i = sub_addr; i < sub_addr + bytes_to_write; i++) {
axi_data.write_data_bits_strb |= (1 << i);
}
axi_data.write_data_bits_last = (bytes_to_write == data.size());
EnqueueData(axi_data);
data.remove_prefix(bytes_to_write);
addr += bytes_to_write;
}
assert(outstanding_transactions_.find(id) ==
outstanding_transactions_.end());
const auto [it, success] =
outstanding_transactions_.insert({id, std::make_shared<bool>(false)});
return it->second;
}
private:
void EnqueueAddr(const AxiAddr& addr) { addr_queue_.push(addr); }
void EnqueueData(const AxiWData& data) { data_queue_.push(data); }
void OnFallingEdge() final {
// Send Addr
*write_addr_valid_ = !addr_queue_.empty();
clock().Eval();
if (!addr_queue_.empty()) {
*write_addr_bits_addr_ = addr_queue_.front().addr_bits_addr;
*write_addr_bits_prot_ = addr_queue_.front().addr_bits_prot;
*write_addr_bits_id_ = addr_queue_.front().addr_bits_id;
*write_addr_bits_len_ = addr_queue_.front().addr_bits_len;
*write_addr_bits_size_ = addr_queue_.front().addr_bits_size;
*write_addr_bits_burst_ = addr_queue_.front().addr_bits_burst;
*write_addr_bits_lock_ = addr_queue_.front().addr_bits_lock;
*write_addr_bits_cache_ = addr_queue_.front().addr_bits_cache;
*write_addr_bits_qos_ = addr_queue_.front().addr_bits_qos;
*write_addr_bits_region_ = addr_queue_.front().addr_bits_region;
if (*write_addr_ready_) {
addr_queue_.pop();
}
clock().Eval();
}
// Send Data
*write_data_valid_ = !data_queue_.empty();
clock().Eval();
if (!data_queue_.empty()) {
*write_data_bits_data_ = data_queue_.front().write_data_bits_data;
*write_data_bits_strb_ = data_queue_.front().write_data_bits_strb;
*write_data_bits_last_ = data_queue_.front().write_data_bits_last;
if (*write_data_ready_) {
data_queue_.pop();
}
clock().Eval();
}
// Receive Response
if (*write_resp_valid_) {
assert(*write_resp_bits_resp_ == 0);
auto it = outstanding_transactions_.find(*write_resp_bits_id_);
if (it != outstanding_transactions_.end()) {
*(it->second) = true;
outstanding_transactions_.erase(it);
}
}
}
// Signals
// WAddr
uint8_t* const write_addr_valid_;
uint32_t* const write_addr_bits_addr_;
uint8_t* const write_addr_bits_prot_;
uint8_t* const write_addr_bits_id_;
uint8_t* const write_addr_bits_len_;
uint8_t* const write_addr_bits_size_;
uint8_t* const write_addr_bits_burst_;
uint8_t* const write_addr_bits_lock_;
uint8_t* const write_addr_bits_cache_;
uint8_t* const write_addr_bits_qos_;
uint8_t* const write_addr_bits_region_;
const uint8_t* const write_addr_ready_;
// WData
uint8_t* const write_data_valid_;
VlWide<4>* const write_data_bits_data_;
uint16_t* const write_data_bits_strb_;
uint8_t* const write_data_bits_last_;
const uint8_t* const write_data_ready_;
// WResp
const uint8_t* const write_resp_valid_;
const uint8_t* const write_resp_bits_id_;
const uint8_t* const write_resp_bits_resp_;
uint8_t* const write_resp_ready_;
std::queue<AxiAddr> addr_queue_;
std::queue<AxiWData> data_queue_;
std::map<uint8_t /*id*/, std::shared_ptr<bool>> outstanding_transactions_;
};
// A driver to control interactions of the read channels in an AXI4 slave.
class AxiSlaveReadDriver : Clock::Observer {
public:
struct Transaction {
bool finished;
uint32_t start_addr;
uint32_t end_addr;
std::vector<uint8_t> data;
};
AxiSlaveReadDriver(
Clock* clock, uint8_t* read_addr_valid, uint32_t* read_addr_bits_addr,
uint8_t* read_addr_bits_prot, uint8_t* read_addr_bits_id,
uint8_t* read_addr_bits_len, uint8_t* read_addr_bits_size,
uint8_t* read_addr_bits_burst, uint8_t* read_addr_bits_lock,
uint8_t* read_addr_bits_cache, uint8_t* read_addr_bits_qos,
uint8_t* read_addr_bits_region, const uint8_t* read_addr_ready,
const uint8_t* read_data_valid, const VlWide<4>* read_data_bits_data,
const uint8_t* read_data_bits_id, const uint8_t* read_data_bits_resp,
const uint8_t* read_data_bits_last, uint8_t* read_data_ready)
: Clock::Observer(clock),
read_addr_valid_(read_addr_valid),
read_addr_bits_addr_(read_addr_bits_addr),
read_addr_bits_prot_(read_addr_bits_prot),
read_addr_bits_id_(read_addr_bits_id),
read_addr_bits_len_(read_addr_bits_len),
read_addr_bits_size_(read_addr_bits_size),
read_addr_bits_burst_(read_addr_bits_burst),
read_addr_bits_lock_(read_addr_bits_lock),
read_addr_bits_cache_(read_addr_bits_cache),
read_addr_bits_qos_(read_addr_bits_qos),
read_addr_bits_region_(read_addr_bits_region),
read_addr_ready_(read_addr_ready),
read_data_valid_(read_data_valid),
read_data_bits_data(read_data_bits_data),
read_data_bits_id_(read_data_bits_id),
read_data_bits_resp_(read_data_bits_resp),
read_data_bits_last_(read_data_bits_last),
read_data_ready_(read_data_ready) {
(*read_data_ready_) = 1;
}
std::shared_ptr<Transaction> ReadTransaction(int id, uint32_t addr,
uint32_t byte_length) {
// Enqueue addr
AxiAddr axi_addr = AxiAddr::FromIdAddrSize(id, addr, byte_length);
addr_queue_.push(axi_addr);
// Enqueue data
assert(outstanding_transactions_.find(id) ==
outstanding_transactions_.end());
const auto [it, success] =
outstanding_transactions_.insert({id, std::make_shared<Transaction>()});
it->second->finished = false;
it->second->start_addr = addr;
it->second->end_addr = addr + byte_length;
it->second->data.reserve(byte_length);
return it->second;
}
private:
void OnFallingEdge() final {
// Send Addr
*read_addr_valid_ = !addr_queue_.empty();
clock().Eval();
if (!addr_queue_.empty()) {
*read_addr_bits_addr_ = addr_queue_.front().addr_bits_addr;
*read_addr_bits_prot_ = addr_queue_.front().addr_bits_prot;
*read_addr_bits_id_ = addr_queue_.front().addr_bits_id;
*read_addr_bits_len_ = addr_queue_.front().addr_bits_len;
*read_addr_bits_size_ = addr_queue_.front().addr_bits_size;
*read_addr_bits_burst_ = addr_queue_.front().addr_bits_burst;
*read_addr_bits_lock_ = addr_queue_.front().addr_bits_lock;
*read_addr_bits_cache_ = addr_queue_.front().addr_bits_cache;
*read_addr_bits_qos_ = addr_queue_.front().addr_bits_qos;
*read_addr_bits_region_ = addr_queue_.front().addr_bits_region;
if (*read_addr_ready_) {
addr_queue_.pop();
}
clock().Eval();
}
// Received data
if (*read_data_valid_) {
assert(*read_data_bits_resp_ == 0);
auto it = outstanding_transactions_.find(*read_data_bits_id_);
if (it == outstanding_transactions_.end()) {
return;
}
// TODO(derekjchow): Should probably handle non-INCR mode.
uint32_t sub_addr = it->second->start_addr % 16;
uint32_t bytes_to_read = 16 - sub_addr;
bytes_to_read = std::min(bytes_to_read,
it->second->end_addr - it->second->start_addr);
const uint8_t* read_data =
reinterpret_cast<const uint8_t*>(&(*read_data_bits_data)[0]);
for (uint32_t i = 0; i < bytes_to_read; i++) {
it->second->data.push_back(read_data[i + sub_addr]);
}
it->second->start_addr += bytes_to_read;
if (*read_data_bits_last_) {
it->second->finished = true;
outstanding_transactions_.erase(it);
}
}
}
// Signals
// RAddr
uint8_t* const read_addr_valid_;
uint32_t* const read_addr_bits_addr_;
uint8_t* const read_addr_bits_prot_;
uint8_t* const read_addr_bits_id_;
uint8_t* const read_addr_bits_len_;
uint8_t* const read_addr_bits_size_;
uint8_t* const read_addr_bits_burst_;
uint8_t* const read_addr_bits_lock_;
uint8_t* const read_addr_bits_cache_;
uint8_t* const read_addr_bits_qos_;
uint8_t* const read_addr_bits_region_;
const uint8_t* const read_addr_ready_;
// RData
const uint8_t* const read_data_valid_;
const VlWide<4>* const read_data_bits_data;
const uint8_t* const read_data_bits_id_;
const uint8_t* const read_data_bits_resp_;
const uint8_t* const read_data_bits_last_;
uint8_t* const read_data_ready_;
std::queue<AxiAddr> addr_queue_;
std::map<uint8_t /*id*/, std::shared_ptr<Transaction>>
outstanding_transactions_;
};
// Struct representing the data transferred in an AXI4 read data channel.
struct AxiRData {
VlWide<4> read_data_bits_data;
uint8_t read_data_bits_id;
uint8_t read_data_bits_resp;
uint8_t read_data_bits_last;
};
// A driver to control interactions of the read channels in an AXI4 master.
class AxiMasterReadDriver : Clock::Observer {
public:
AxiMasterReadDriver(
Clock* clock, const uint8_t* read_addr_valid,
const uint32_t* read_addr_bits_addr, const uint8_t* read_addr_bits_prot,
const uint8_t* read_addr_bits_id, const uint8_t* read_addr_bits_len,
const uint8_t* read_addr_bits_size, const uint8_t* read_addr_bits_burst,
const uint8_t* read_addr_bits_lock, const uint8_t* read_addr_bits_cache,
const uint8_t* read_addr_bits_qos, const uint8_t* read_addr_bits_region,
uint8_t* read_addr_ready, uint8_t* read_data_valid,
VlWide<4>* read_data_bits_data, uint8_t* read_data_bits_id,
uint8_t* read_data_bits_resp, uint8_t* read_data_bits_last,
const uint8_t* read_data_ready)
: Clock::Observer(clock),
read_addr_valid_(read_addr_valid),
read_addr_bits_addr_(read_addr_bits_addr),
read_addr_bits_prot_(read_addr_bits_prot),
read_addr_bits_id_(read_addr_bits_id),
read_addr_bits_len_(read_addr_bits_len),
read_addr_bits_size_(read_addr_bits_size),
read_addr_bits_burst_(read_addr_bits_burst),
read_addr_bits_lock_(read_addr_bits_lock),
read_addr_bits_cache_(read_addr_bits_cache),
read_addr_bits_qos_(read_addr_bits_qos),
read_addr_bits_region_(read_addr_bits_region),
read_addr_ready_(read_addr_ready),
read_data_valid_(read_data_valid),
read_data_bits_data_(read_data_bits_data),
read_data_bits_id_(read_data_bits_id),
read_data_bits_resp_(read_data_bits_resp),
read_data_bits_last_(read_data_bits_last),
read_data_ready_(read_data_ready) {
(*read_addr_ready_) = 1;
}
void RegisterReadCallback(
std::function<AxiRData(const AxiAddr& addr)> read_cb) {
read_cb_ = read_cb;
}
private:
void OnFallingEdge() final {
// Send Data
*read_data_valid_ = !data_queue_.empty();
clock().Eval();
if (!data_queue_.empty()) {
*read_data_bits_data_ = data_queue_.front().read_data_bits_data;
*read_data_bits_id_ = data_queue_.front().read_data_bits_id;
*read_data_bits_resp_ = data_queue_.front().read_data_bits_resp;
*read_data_bits_last_ = data_queue_.front().read_data_bits_last;
if (*read_data_ready_) {
data_queue_.pop();
}
clock().Eval();
}
// Receive Address
if (*read_addr_valid_) {
axi_addr_.addr_bits_addr = *read_addr_bits_addr_;
axi_addr_.addr_bits_prot = *read_addr_bits_prot_;
axi_addr_.addr_bits_id = *read_addr_bits_id_;
axi_addr_.addr_bits_len = *read_addr_bits_len_;
axi_addr_.addr_bits_size = *read_addr_bits_size_;
axi_addr_.addr_bits_burst = *read_addr_bits_burst_;
axi_addr_.addr_bits_lock = *read_addr_bits_lock_;
axi_addr_.addr_bits_cache = *read_addr_bits_cache_;
axi_addr_.addr_bits_qos = *read_addr_bits_qos_;
axi_addr_.addr_bits_region = *read_addr_bits_region_;
if (read_cb_) {
AxiRData read_result = read_cb_(axi_addr_);
data_queue_.push(read_result);
} else {
assert(false && "Read callback is empty!");
}
}
}
// Signals
// RAddr
const uint8_t* const read_addr_valid_;
const uint32_t* const read_addr_bits_addr_;
const uint8_t* const read_addr_bits_prot_;
const uint8_t* const read_addr_bits_id_;
const uint8_t* const read_addr_bits_len_;
const uint8_t* const read_addr_bits_size_;
const uint8_t* const read_addr_bits_burst_;
const uint8_t* const read_addr_bits_lock_;
const uint8_t* const read_addr_bits_cache_;
const uint8_t* const read_addr_bits_qos_;
const uint8_t* const read_addr_bits_region_;
uint8_t* const read_addr_ready_;
// RData
uint8_t* const read_data_valid_;
VlWide<4>* const read_data_bits_data_;
uint8_t* const read_data_bits_id_;
uint8_t* const read_data_bits_resp_;
uint8_t* const read_data_bits_last_;
const uint8_t* const read_data_ready_;
std::queue<AxiRData> data_queue_;
AxiAddr axi_addr_;
std::function<AxiRData(const AxiAddr&)> read_cb_;
};
// Struct representing the data transferred in an AXI4 read data channel.
struct AxiWResp {
uint8_t write_resp_bits_id;
uint8_t write_resp_bits_resp;
};
// A driver to control interactions of the write channels in an AXI4 slave.
class AxiMasterWriteDriver : Clock::Observer {
public:
AxiMasterWriteDriver(
Clock* clock, const uint8_t* write_addr_valid,
const uint32_t* write_addr_bits_addr, const uint8_t* write_addr_bits_prot,
const uint8_t* write_addr_bits_id, const uint8_t* write_addr_bits_len,
const uint8_t* write_addr_bits_size, const uint8_t* write_addr_bits_burst,
const uint8_t* write_addr_bits_lock, const uint8_t* write_addr_bits_cache,
const uint8_t* write_addr_bits_qos, const uint8_t* write_addr_bits_region,
uint8_t* write_addr_ready, const uint8_t* write_data_valid,
const VlWide<4>* write_data_bits_data,
const uint16_t* write_data_bits_strb, const uint8_t* write_data_bits_last,
uint8_t* write_data_ready, uint8_t* write_resp_valid,
uint8_t* write_resp_bits_id, uint8_t* write_resp_bits_resp,
const uint8_t* write_resp_ready)
: Clock::Observer(clock),
write_addr_valid_(write_addr_valid),
write_addr_bits_addr_(write_addr_bits_addr),
write_addr_bits_prot_(write_addr_bits_prot),
write_addr_bits_id_(write_addr_bits_id),
write_addr_bits_len_(write_addr_bits_len),
write_addr_bits_size_(write_addr_bits_size),
write_addr_bits_burst_(write_addr_bits_burst),
write_addr_bits_lock_(write_addr_bits_lock),
write_addr_bits_cache_(write_addr_bits_cache),
write_addr_bits_qos_(write_addr_bits_qos),
write_addr_bits_region_(write_addr_bits_region),
write_addr_ready_(write_addr_ready),
write_data_valid_(write_data_valid),
write_data_bits_data_(write_data_bits_data),
write_data_bits_strb_(write_data_bits_strb),
write_data_bits_last_(write_data_bits_last),
write_data_ready_(write_data_ready),
write_resp_valid_(write_resp_valid),
write_resp_bits_id_(write_resp_bits_id),
write_resp_bits_resp_(write_resp_bits_resp),
write_resp_ready_(write_resp_ready) {
*write_addr_ready_ = 0;
*write_data_ready_ = 0;
}
~AxiMasterWriteDriver() final = default;
void RegisterWriteCallback(
std::function<AxiWResp(const AxiAddr& addr, const AxiWData& data)>
write_cb) {
write_cb_ = write_cb;
}
private:
void OnFallingEdge() final {
// Send Response
*write_resp_valid_ = !resp_queue_.empty();
clock().Eval();
if (!resp_queue_.empty()) {
*write_resp_bits_id_ = resp_queue_.front().write_resp_bits_id;
*write_resp_bits_resp_ = resp_queue_.front().write_resp_bits_resp;
if (*write_resp_ready_) {
resp_queue_.pop();
}
clock().Eval();
}
// Receive Addr
if (*write_addr_valid_) {
axi_addr_.addr_bits_addr = *write_addr_bits_addr_;
axi_addr_.addr_bits_prot = *write_addr_bits_prot_;
axi_addr_.addr_bits_id = *write_addr_bits_id_;
axi_addr_.addr_bits_len = *write_addr_bits_len_;
axi_addr_.addr_bits_size = *write_addr_bits_size_;
axi_addr_.addr_bits_burst = *write_addr_bits_burst_;
axi_addr_.addr_bits_lock = *write_addr_bits_lock_;
axi_addr_.addr_bits_cache = *write_addr_bits_cache_;
axi_addr_.addr_bits_qos = *write_addr_bits_qos_;
axi_addr_.addr_bits_region = *write_addr_bits_region_;
}
// Receive Data
if (*write_data_valid_) {
axi_data_.write_data_bits_data = *write_data_bits_data_;
axi_data_.write_data_bits_strb = *write_data_bits_strb_;
axi_data_.write_data_bits_last = *write_data_bits_last_;
}
if (*write_addr_valid_ && *write_data_valid_) {
*write_addr_ready_ = 1;
*write_data_ready_ = 1;
if (write_cb_) {
AxiWResp resp_result = write_cb_(axi_addr_, axi_data_);
resp_queue_.push(resp_result);
} else {
assert(false && "Write callback is empty!");
}
} else {
*write_addr_ready_ = 0;
*write_data_ready_ = 0;
}
}
// Signals
// WAddr
const uint8_t* const write_addr_valid_;
const uint32_t* const write_addr_bits_addr_;
const uint8_t* const write_addr_bits_prot_;
const uint8_t* const write_addr_bits_id_;
const uint8_t* const write_addr_bits_len_;
const uint8_t* const write_addr_bits_size_;
const uint8_t* const write_addr_bits_burst_;
const uint8_t* const write_addr_bits_lock_;
const uint8_t* const write_addr_bits_cache_;
const uint8_t* const write_addr_bits_qos_;
const uint8_t* const write_addr_bits_region_;
uint8_t* const write_addr_ready_;
// WData
const uint8_t* const write_data_valid_;
const VlWide<4>* const write_data_bits_data_;
const uint16_t* const write_data_bits_strb_;
const uint8_t* const write_data_bits_last_;
uint8_t* const write_data_ready_;
// WResp
uint8_t* const write_resp_valid_;
uint8_t* const write_resp_bits_id_;
uint8_t* const write_resp_bits_resp_;
const uint8_t* const write_resp_ready_;
std::queue<AxiWResp> resp_queue_;
AxiAddr axi_addr_;
AxiWData axi_data_;
std::function<AxiWResp(const AxiAddr&, const AxiWData&)> write_cb_;
};
#endif // HW_SIM_HW_PRIMITIVES_H_