cva5/test_benches/verilator/AXIMem.cc
mohammadshahidzade 4efa1e2d03
Os fixes6 (#29)
* Support for atomic extension A
* Support instruction fence extension Zifencei
* Update CSRs to Version 20240411 and include compliant support for Zihpm, Sstc, and Smstateen extensions
* Support address translation
* Fixes interrupts and exception handling
* Adds interrupt controllers
* Support coherent multicore systems through a new data cache and arbiter
* Multiple bugfixes
* Adds new scripts for example systems in Vivado and LiteX
* Removes legacy, unused, and broken scripts, examples, and files

---------

Co-authored-by: Chris Keilbart <keilbartchris@gmail.com>
Co-authored-by: msa417 <msa417@ensc-rcl-14.engineering.sfu.ca>
Co-authored-by: Rajnesh Joshi <rajnesh.joshi28@gmail.com>
Co-authored-by: Rajnesh Joshi <rajneshj@sfu.ca>
2025-03-11 16:06:16 -07:00

413 lines
No EOL
12 KiB
C++

/*
* Copyright © 2024 Chris Keilbart
*
* 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.
*
* Initial code developed under the supervision of Dr. Lesley Shannon,
* Reconfigurable Computing Lab, Simon Fraser University.
*
* Author(s):
* Chris Keilbart <ckeilbar@sfu.ca>
*/
#include <fstream>
#include <algorithm>
#include <cstring>
#include "verilated.h"
#include "Vcva5_sim.h"
#include "AXIMem.h"
static_assert(MEM_WORDS >= 1 && MEM_WORDS <= 16, "Bus width must be 32-512");
//No other assertions, this unit can handle any compliant master (but does not check for compliance)
//TODO: ~0 is used to mark invalid data, but 1 for invalid IDs because Verilator cannot handle set MSBs
unsigned AXIMem::log2(unsigned x) {
unsigned y = 0;
while (x) {
x >>= 1;
y++;
}
return y - 1;
}
AXIMem::AXIMem(Vcva5_sim* tb){
this->tb = tb;
//Static casting for safety is an alternative
rdata_pointer = (uint32_t*) &tb->ddr_axi_rdata;
wdata_pointer = (uint32_t*) &tb->ddr_axi_wdata;
//Create the delay distributions
generator = default_random_engine(seed);
rvalid_geo = geometric_distribution<unsigned>(rvalid_distribution_p);
bvalid_geo = geometric_distribution<unsigned>(bvalid_distribution_p);
arready_bern = bernoulli_distribution(arready_p);
awready_bern = bernoulli_distribution(awready_p);
wready_bern = bernoulli_distribution(wready_p);
rvalid_bern = bernoulli_distribution(rvalid_p);
rst();
}
AXIMem::AXIMem(ifstream (&memFiles)[NUM_CORES], Vcva5_sim* tb) : AXIMem(tb) {
uint32_t line_bin;
string line_hex;
uint32_t addr;
const uint32_t addr_mask = (MEM_WORDS-1) << 2;
//First iterate over cores
for (int i = 0; i < NUM_CORES; i++) {
addr = (STARTING_ADDR + i*PROGRAM_SPACING);
//Hex files have one 32 bit hexadecimal number per line, we check the first line to determine file type
getline(memFiles[i], line_hex);
bool fileIsHex = line_hex.size() == 8;
try {
stoul(line_hex, 0, 16);
}
catch (invalid_argument& ex) {
fileIsHex = false;
}
memFiles[i].clear();
memFiles[i].seekg(0, ios::beg);
//Then iterate over pages
while (!memFiles[i].eof()) {
if (fileIsHex) {
getline(memFiles[i], line_hex);
line_bin = stoul(line_hex, 0, 16);
}
else
memFiles[i].read((char*) &line_bin, 4);
uint32_t masked_addr = addr & addr_mask;
uint32_t* rdata;
try {
rdata = mem.at(addr >> ADDR_SHIFT_AMT);
} catch (out_of_range ex) {
rdata = new uint32_t[MEM_WORDS];
memset(rdata, 0, 4*MEM_WORDS);
mem[addr >> ADDR_SHIFT_AMT] = rdata;
}
rdata[masked_addr>>2] = line_bin;
addr += 4;
}
}
}
inline void AXIMem::pre_ar() {
tb->ddr_axi_arready = 0;
//Randomly deny a request
if (arready_bern(generator))
return;
for (unsigned i = 0; i < max_read_requests; i++)
if (!inflight_r[i].valid) {
tb->ddr_axi_arready = 1;
return;
}
}
inline void AXIMem::post_ar() {
if (!tb->ddr_axi_arvalid || !tb->ddr_axi_arready)
return;
unsigned free_index;
unsigned max_id_latency = 0;
for (int i = max_read_requests-1; i >= 0; i--) {
if (!inflight_r[i].valid) //Searching backwards means insertions are likely at the beginning
free_index = i;
else if (inflight_r[i].id == tb->ddr_axi_arid)
max_id_latency = max(max_id_latency, inflight_r[i].remaining);
}
inflight_r[free_index].address = tb->ddr_axi_araddr;
inflight_r[free_index].id = tb->ddr_axi_arid;
inflight_r[free_index].burst = tb->ddr_axi_arburst;
inflight_r[free_index].size = tb->ddr_axi_arsize;
inflight_r[free_index].length = tb->ddr_axi_arlen;
//Need to set the latency above the latency of the maximum outstanding request with the same ID (for ordering)
inflight_r[free_index].remaining = max(rvalid_min_latency + rvalid_geo(generator), max_id_latency+1);
inflight_r[free_index].valid = true;
}
inline void AXIMem::rst_ar() {
tb->ddr_axi_arready = 0;
arready_bern.reset();
rvalid_geo.reset();
for (unsigned i = 0; i < max_read_requests; i++)
inflight_r[i].valid = false;
}
inline void AXIMem::pre_aw() {
tb->ddr_axi_awready = 0;
//Randomly deny a request
if (awready_bern(generator))
return;
for (unsigned i = 0; i < max_write_requests; i++) {
if (!inflight_w[i].valid) {
tb->ddr_axi_awready = 1;
return;
}
}
}
inline void AXIMem::post_aw() {
if (!tb->ddr_axi_awvalid || !tb->ddr_axi_awready)
return;
unsigned free_index;
unsigned max_id_latency;
for (int i = max_write_requests-1; i >= 0; i--) {
if (!inflight_w[i].valid) //Searching backwards means insertions are likely at the beginning
free_index = i;
else if (inflight_w[i].id == tb->ddr_axi_awid)
max_id_latency = max(max_id_latency, inflight_w[i].remaining);
}
w_queue.push(free_index);
inflight_w[free_index].address = tb->ddr_axi_awaddr;
inflight_w[free_index].id = tb->ddr_axi_awid;
inflight_w[free_index].burst = tb->ddr_axi_awburst;
inflight_w[free_index].size = tb->ddr_axi_awsize;
inflight_w[free_index].length = tb->ddr_axi_awlen;
inflight_w[free_index].remaining = 0;
inflight_w[free_index].valid = true;
}
inline void AXIMem::rst_aw() {
tb->ddr_axi_awready = 0;
awready_bern.reset();
for (unsigned i = 0; i < max_write_requests; i++)
inflight_w[i].valid = false;
}
inline void AXIMem::pre_w() {
tb->ddr_axi_wready = !wready_bern(generator) && !w_queue.empty();
}
inline void AXIMem::post_w() {
//Place the wdata in a queue for future commitment
if (tb->ddr_axi_wvalid && tb->ddr_axi_wready) {
const unsigned current_id = w_queue.front();
wdata_entry new_entry;
memcpy(new_entry.data, wdata_pointer, 4*MEM_WORDS);
new_entry.strb = tb->ddr_axi_wstrb;
inflight_wdata[current_id].push(new_entry);
if (tb->ddr_axi_wlast) {
unsigned max_id_latency = 0;
for (unsigned i = 0; i < max_write_requests; i++) {
if (inflight_w[i].valid && inflight_w[i].id == current_id)
max_id_latency = max(max_id_latency, inflight_w[i].remaining);
}
inflight_w[current_id].remaining = max(bvalid_min_latency + bvalid_geo(generator), max_id_latency+1);
w_queue.pop();
}
}
//Decrement outstanding write latencies and commit finished outstanding writes
for (int i = max_write_requests-1; i >= 0; i--) { //Go backwards because that makes it more likely to reorder requests
//0 remaining indicates a request that is waiting for wdata
if (!inflight_w[i].valid || inflight_w[i].remaining == 0)
continue;
if (--inflight_w[i].remaining != 0)
continue;
//Commit
b_queue.push(i);
uint32_t raw_addr = inflight_w[i].address;
const unsigned burst = inflight_w[i].burst;
const unsigned size_bytes = 1 << inflight_w[i].size;
const uint32_t line_bytes = size_bytes*(inflight_w[i].length+1);
const uint32_t mask = ~0 << log2(line_bytes);
while (!inflight_wdata[i].empty()) {
//Do the write
uint8_t* rdata;
try {
rdata = (uint8_t*) mem.at(raw_addr >> ADDR_SHIFT_AMT);
} catch (out_of_range ex) {
rdata = (uint8_t*) new uint32_t[MEM_WORDS];
memset(rdata, 0, 4*MEM_WORDS);
mem[raw_addr >> ADDR_SHIFT_AMT] = (uint32_t*) rdata;
}
uint8_t* wdata = (uint8_t*) inflight_wdata[i].front().data;
uint64_t strb = inflight_wdata[i].front().strb;
for (unsigned j = 0; j < 4*MEM_WORDS; j++) {
if (strb & 1)
*rdata = *wdata;
wdata++;
rdata++;
strb >>= 1;
}
inflight_wdata[i].pop();
//Set up the address for the next go
if (burst == 1) //INCR
raw_addr += size_bytes;
else if (burst == 2) //WRAP
raw_addr = (mask & raw_addr) | (~mask & raw_addr+size_bytes);
}
}
}
void AXIMem::rst_w() {
tb->ddr_axi_wready = 0;
bvalid_geo.reset();
wready_bern.reset();
while (!w_queue.empty())
w_queue.pop();
for (unsigned i = 0; i < max_write_requests; i++)
while (!inflight_wdata[i].empty())
inflight_wdata[i].pop();
}
void AXIMem::pre_r() {
if (rvalid_bern(generator) || r_queue.empty()) {
tb->ddr_axi_rvalid = 0;
tb->ddr_axi_rlast = 0;
tb->ddr_axi_rid = 1; //Cannot use ~0
memset(rdata_pointer, ~0, 4*MEM_WORDS);
return;
}
const unsigned current_id = r_queue.front();
tb->ddr_axi_rvalid = 1;
tb->ddr_axi_rid = inflight_r[current_id].id;
tb->ddr_axi_rlast = read_burst_count == inflight_r[current_id].length;
uint32_t start_addr = inflight_r[current_id].address;
const unsigned burst = inflight_r[current_id].burst;
const unsigned size_bytes = 1 << inflight_r[current_id].size;
if (burst == 1) //INCR
start_addr += read_burst_count*size_bytes;
else if (burst == 2) { //WRAP
const uint32_t line_bytes = size_bytes*(inflight_r[current_id].length+1);
const uint32_t mask = ~0 << log2(line_bytes);
start_addr = (mask & start_addr) | (~mask & start_addr+read_burst_count*size_bytes);
}
uint32_t* rdata;
try {
rdata = mem.at(start_addr >> ADDR_SHIFT_AMT);
} catch (out_of_range ex) {
rdata = new uint32_t[MEM_WORDS];
memset(rdata, 0, 4*MEM_WORDS);
mem[start_addr >> ADDR_SHIFT_AMT] = rdata;
}
memcpy(rdata_pointer, rdata, 4*MEM_WORDS);
}
void AXIMem::post_r() {
//Handle the last read
if (tb->ddr_axi_rready && tb->ddr_axi_rvalid) {
if (tb->ddr_axi_rlast) {
read_burst_count = 0;
inflight_r[r_queue.front()].valid = false;
r_queue.pop();
}
else
read_burst_count++;
}
//Commit a read
for (int i = max_read_requests-1; i >= 0; i--) { //Go backwards because that makes it more likely to reorder requests
if (!inflight_r[i].valid || !inflight_r[i].remaining)
continue;
if (!--inflight_r[i].remaining)
r_queue.push(i);
}
}
void AXIMem::rst_r() {
read_burst_count = 0;
tb->ddr_axi_rvalid = 0;
tb->ddr_axi_rresp = 0; //Always good
tb->ddr_axi_rlast = 0;
tb->ddr_axi_rid = 1; //Cannot use ~0
memset(rdata_pointer, ~0, 4*MEM_WORDS);
while (!r_queue.empty())
r_queue.pop();
}
void AXIMem::pre_b() {
bool valid = !b_queue.empty();
tb->ddr_axi_bvalid = valid;
tb->ddr_axi_bid = valid ? inflight_w[b_queue.front()].id : 1; //Cannot use ~0
}
void AXIMem::post_b() {
if (tb->ddr_axi_bvalid && tb->ddr_axi_bready) {
inflight_w[b_queue.front()].valid = false;
b_queue.pop();
}
}
void AXIMem::rst_b() {
tb->ddr_axi_bid = 0;
tb->ddr_axi_bresp = 0; //Always good
tb->ddr_axi_bvalid = 0;
while (!b_queue.empty())
b_queue.pop();
}
void AXIMem::rst() {
//Order does not matter
rst_ar();
rst_aw();
rst_r();
rst_w();
rst_b();
}
void AXIMem::pre() {
if (tb->rst)
rst();
else {
pre_ar();
pre_aw();
pre_w();
pre_r();
pre_b();
}
}
void AXIMem::post() {
post_ar();
post_r();
post_aw();
post_w();
post_b();
}
AXIMem::~AXIMem() {
for (auto it = mem.begin(); it != mem.end(); it++)
delete[] it->second;
}