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