blob: 4b29f1fca7fd88d1f87350d3ccf5fa9cb262aeac [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "mem_area.h"
#include <algorithm>
#include <cassert>
#include <cstring>
#include <sstream>
#include "sv_scoped.h"
// DPI exports, defined in prim_util_memload.svh
extern "C" {
void simutil_memload(const char *file);
int simutil_set_mem(int index, const svBitVecVal *val);
int simutil_get_mem(int index, svBitVecVal *val);
}
MemArea::MemArea(const std::string &scope, uint32_t num_words,
uint32_t width_byte)
: scope_(scope), num_words_(num_words), width_byte_(width_byte) {
assert(0 < num_words);
assert(width_byte <= SV_MEM_WIDTH_BYTES);
}
void MemArea::Write(uint32_t word_offset,
const std::vector<uint8_t> &data) const {
// This "mini buffer" is used to transfer each write to SystemVerilog.
// `simutil_set_mem` takes a fixed SV_MEM_WIDTH_BITS-bit vector but it will
// only use the bits required for the RAM width. As an example, for a 32-bit
// wide RAM only elements 3:0 of `minibuf` will be written to memory. Since
// the simulator may still read bits from minibuf it does not use, we must
// use a fixed allocation of the full bit vector size to avoid an out of
// bounds access.
uint8_t minibuf[SV_MEM_WIDTH_BYTES];
memset(minibuf, 0, sizeof minibuf);
assert(width_byte_ <= sizeof minibuf);
uint32_t data_words = (data.size() + width_byte_ - 1) / width_byte_;
assert(word_offset + data_words <= num_words_);
for (uint32_t i = 0; i < data_words; ++i) {
uint32_t dst_word = word_offset + i;
uint32_t phys_addr = ToPhysAddr(dst_word);
WriteBuffer(minibuf, data, i * width_byte_, dst_word);
WriteFromMinibuf(phys_addr, minibuf, dst_word);
}
}
std::vector<uint8_t> MemArea::Read(uint32_t word_offset,
uint32_t num_words) const {
assert(word_offset + num_words <= num_words_);
uint32_t num_bytes = width_byte_ * num_words;
assert(num_words <= num_bytes);
// See Write for an explanation for this buffer.
uint8_t minibuf[SV_MEM_WIDTH_BYTES];
memset(minibuf, 0, sizeof minibuf);
assert(width_byte_ <= sizeof minibuf);
std::vector<uint8_t> ret;
ret.reserve(num_bytes);
for (uint32_t i = 0; i < num_words; ++i) {
uint32_t src_word = word_offset + i;
uint32_t phys_addr = ToPhysAddr(src_word);
ReadToMinibuf(minibuf, phys_addr);
ReadBuffer(ret, minibuf, src_word);
}
return ret;
}
void MemArea::LoadVmem(const std::string &path) const {
SVScoped scoped(scope_.c_str());
// TODO: Add error handling.
simutil_memload(path.c_str());
}
void MemArea::WriteBuffer(uint8_t buf[SV_MEM_WIDTH_BYTES],
const std::vector<uint8_t> &data, size_t start_idx,
uint32_t dst_word) const {
size_t words_left = data.size() - start_idx;
size_t to_copy = std::min(words_left, (size_t)width_byte_);
if (to_copy < width_byte_) {
memset(buf, 0, SV_MEM_WIDTH_BYTES);
}
memcpy(buf, &data[start_idx], to_copy);
}
void MemArea::ReadBuffer(std::vector<uint8_t> &data,
const uint8_t buf[SV_MEM_WIDTH_BYTES],
uint32_t src_word) const {
// Append the first width_byte_ bytes of buf to data.
std::copy_n(reinterpret_cast<const char *>(buf), width_byte_,
std::back_inserter(data));
}
void MemArea::ReadToMinibuf(uint8_t *minibuf, uint32_t phys_addr) const {
SVScoped scoped(scope_);
if (!simutil_get_mem(phys_addr, (svBitVecVal *)minibuf)) {
std::ostringstream oss;
oss << "Could not read memory word at physical index 0x" << std::hex
<< phys_addr << ".";
throw std::runtime_error(oss.str());
}
}
void MemArea::WriteFromMinibuf(uint32_t phys_addr, const uint8_t *minibuf,
uint32_t dst_word) const {
SVScoped scoped(scope_);
if (!simutil_set_mem(phys_addr, (const svBitVecVal *)minibuf)) {
std::ostringstream oss;
oss << "Could not set memory at byte offset 0x" << std::hex
<< dst_word * width_byte_ << ".";
throw std::runtime_error(oss.str());
}
}