blob: 8d6e7a126c74a2bc3550308ed531ce78c4d47e99 [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 "otbn_memutil.h"
#include <cassert>
#include <cstring>
#include <gelf.h>
#include <iostream>
#include <libelf.h>
#include <limits>
#include <regex>
#include <sstream>
#include <stdexcept>
#include "scrambled_ecc32_mem_area.h"
#include "sv_scoped.h"
#include "sv_utils.h"
OtbnMemUtil::OtbnMemUtil(const std::string &top_scope)
: imem_(SVScoped::join_sv_scopes(top_scope, "u_imem"), 4096 / 4, 4 / 4),
dmem_(SVScoped::join_sv_scopes(top_scope, "u_dmem"), 4096 / 32, 32 / 4),
expected_end_addr_(-1) {
RegisterMemoryArea("imem", 0x4000, &imem_);
RegisterMemoryArea("dmem", 0x8000, &dmem_);
}
void OtbnMemUtil::LoadElf(const std::string &elf_path) {
LoadElfToMemories(false, elf_path);
}
const StagedMem::SegMap &OtbnMemUtil::GetSegs(bool is_imem) const {
return GetMemoryData(is_imem ? "imem" : "dmem").GetSegs();
}
uint32_t OtbnMemUtil::GetLoopWarp(uint32_t addr, uint32_t from_cnt) const {
auto key = std::make_pair(addr, from_cnt);
auto it = loop_warp_.find(key);
return (it == loop_warp_.end()) ? from_cnt : it->second;
}
void OtbnMemUtil::OnElfLoaded(Elf *elf_file) {
assert(elf_file);
expected_end_addr_ = -1;
loop_warp_.clear();
// Look through the symbol table of elf_file for an expected end
// address and any loop warping symbols.
Elf_Scn *scn = nullptr;
while ((scn = elf_nextscn(elf_file, scn))) {
Elf32_Shdr *shdr = elf32_getshdr(scn);
assert(shdr);
if (shdr->sh_type != SHT_SYMTAB)
continue;
Elf_Data *sec_data = elf_getdata(scn, nullptr);
assert(sec_data);
int num_syms = shdr->sh_size / shdr->sh_entsize;
for (int i = 0; i < num_syms; ++i) {
GElf_Sym sym;
gelf_getsym(sec_data, i, &sym);
const char *sym_name = elf_strptr(elf_file, shdr->sh_link, sym.st_name);
if (!sym_name)
continue;
OnSymbol(sym_name, sym.st_value);
}
break;
}
}
void OtbnMemUtil::OnSymbol(const std::string &name, uint32_t value) {
// Expected end address
if (name == "_expected_end_addr") {
expected_end_addr_ = value;
}
// Loop warping. These symbols are of the form "_loop_warp_FROM_TO_*" where
// FROM and TO are decimal loop counts and the value of the symbol is the
// address where it should apply. Trailing junk is allowed (to ensure
// uniqueness).
std::regex lw_re("_loop_warp_([0-9]+)_([0-9]+).*");
std::smatch lw_match;
if (std::regex_match(name, lw_match, lw_re)) {
assert(lw_match.size() == 3);
// Parse the "from" count. We know that the captured group is one or
// more decimal digits, so the only question of correctness is whether
// it's in range or not. For the "from" count, we know that the loop
// counter in the design is only 32 bits so if the value is out of range
// then we just ignore it: it can never match anyway.
errno = 0;
unsigned long from_cnt = strtoul(lw_match[1].str().c_str(), nullptr, 10);
bool good_from_cnt =
(errno == 0) && (from_cnt <= std::numeric_limits<uint32_t>::max());
// Parse the "to" count. Again, the only question is whether it's in range
// or not. Saturate to the maximum value of a uint32: since the design has a
// 32-bit loop counter, this will always jump to the last iteration.
unsigned long to_cnt = strtoul(lw_match[2].str().c_str(), nullptr, 10);
uint32_t to_cnt32 =
std::min(to_cnt, (unsigned long)std::numeric_limits<uint32_t>::max());
if (good_from_cnt) {
AddLoopWarp(value, static_cast<uint32_t>(from_cnt), to_cnt32);
}
}
}
void OtbnMemUtil::AddLoopWarp(uint32_t addr, uint32_t from_cnt,
uint32_t to_cnt) {
auto key = std::make_pair(addr, from_cnt);
auto pr = loop_warp_.insert(std::make_pair(key, to_cnt));
if (!pr.second) {
// We didn't actually insert anything, which means that there was already a
// value stored for this address/from_cnt pair. Since the ordering of ELF
// symbols isn't something that the user can control, we don't really have a
// sensible behaviour at this point: throw an error.
std::ostringstream oss;
oss << "Duplicate loop warp symbols for address 0x" << std::hex << addr
<< " and initial count " << std::dec << from_cnt << ".";
throw std::runtime_error(oss.str());
}
}
extern "C" OtbnMemUtil *OtbnMemUtilMake(const char *top_scope) {
try {
return new OtbnMemUtil(top_scope);
} catch (const std::exception &err) {
std::cerr << "Failed to create OtbnMemUtil: " << err.what() << "\n";
return nullptr;
}
}
extern "C" void OtbnMemUtilFree(OtbnMemUtil *mem_util) { delete mem_util; }
extern "C" svBit OtbnMemUtilLoadElf(OtbnMemUtil *mem_util,
const char *elf_path) {
assert(mem_util);
assert(elf_path);
try {
mem_util->LoadElf(elf_path);
return sv_1;
} catch (const std::exception &err) {
std::cerr << "Failed to load ELF file from `" << elf_path
<< "': " << err.what() << "\n";
return sv_0;
}
}
extern "C" svBit OtbnMemUtilStageElf(OtbnMemUtil *mem_util,
const char *elf_path) {
assert(mem_util);
assert(elf_path);
try {
mem_util->StageElf(false, elf_path);
return sv_1;
} catch (const std::exception &err) {
std::cerr << "Failed to load ELF file from `" << elf_path
<< "': " << err.what() << "\n";
return sv_0;
}
}
extern "C" int OtbnMemUtilGetSegCount(OtbnMemUtil *mem_util, svBit is_imem) {
assert(mem_util);
const StagedMem::SegMap &segs = mem_util->GetSegs(is_imem);
size_t num_segs = segs.size();
// Since the segments are disjoint and 32-bit aligned, there are at most 2^30
// of them (this, admittedly, would mean an ELF file with a billion segments,
// but it's theoretically possible). Fortunately, that number is still
// representable with a signed 32-bit integer, so this assertion shouldn't
// ever fire.
assert(num_segs < (unsigned)std::numeric_limits<int>::max());
return num_segs;
}
extern "C" svBit OtbnMemUtilGetSegInfo(OtbnMemUtil *mem_util, svBit is_imem,
int seg_idx, svBitVecVal *seg_off,
svBitVecVal *seg_size) {
assert(mem_util);
assert(seg_off);
assert(seg_size);
const StagedMem::SegMap &segs = mem_util->GetSegs(is_imem);
// Make sure there is such an index.
if ((seg_idx < 0) || ((unsigned)seg_idx > segs.size())) {
std::cerr << "Invalid segment index: " << seg_idx << ". "
<< (is_imem ? 'I' : 'D') << "MEM has " << segs.size()
<< " segments.\n";
return sv_0;
}
// Walk to the desired segment (which we know is safe because we just checked
// the index was valid).
auto it = std::next(segs.begin(), seg_idx);
uint32_t seg_addr = it->first.lo;
// We know that seg_addr must be 32 bit aligned because DpiMemUtil checks its
// segments are aligned to the memory word size (which is 32 or 256 bits for
// imem/dmem, respectively).
assert(seg_addr % 4 == 0);
uint32_t word_seg_addr = seg_addr / 4;
size_t size_bytes = it->second.size();
// We know the size can't be too enormous, because the segment fits in a
// 32-bit address space.
assert(size_bytes < std::numeric_limits<uint32_t>::max());
// Divide by 4 to get the number of 32 bit words. Round up: we'll pad out the
// data with zeros if there's a ragged edge. (We know this is valid because
// any next range is also 32 bit aligned).
uint32_t size_words = ((uint64_t)size_bytes + 3) / 4;
memcpy(seg_off, &word_seg_addr, sizeof(uint32_t));
memcpy(seg_size, &size_words, sizeof(uint32_t));
return sv_1;
}
extern "C" svBit OtbnMemUtilGetSegData(
OtbnMemUtil *mem_util, svBit is_imem, int word_off,
/* output bit[31:0] */ svBitVecVal *data_value) {
assert(mem_util);
assert(data_value);
if ((word_off < 0) ||
((unsigned)word_off > std::numeric_limits<uint32_t>::max() / 4)) {
std::cerr << "Invalid word offset: " << word_off << ".\n";
return sv_0;
}
uint32_t byte_addr = (unsigned)word_off * 4;
const StagedMem::SegMap &segs = mem_util->GetSegs(is_imem);
auto it = segs.find(byte_addr);
if (it == segs.end()) {
return sv_0;
}
uint32_t seg_addr = it->first.lo;
assert(seg_addr <= byte_addr);
// The offset within the segment
uint32_t seg_off = byte_addr - seg_addr;
assert(seg_off < it->second.size());
// How many bytes are available in the segment, starting at seg_off? We know
// this will be positive (because RangeMap::find finds us a range that
// includes seg_addr and then DpiMemUtil makes sure that the value at that
// range is the right length).
size_t avail = it->second.size() - seg_off;
size_t to_copy = std::min(avail, (size_t)4);
// Copy data from the segment into a uint32_t. Zero-initialize it, in case
// to_copy < 4.
uint32_t data = 0;
memcpy(&data, &it->second[seg_off], to_copy);
// Now copy that uint32_t into data_value and return success.
memcpy(data_value, &data, 4);
return sv_1;
}
int OtbnMemUtilGetExpEndAddr(OtbnMemUtil *mem_util) {
assert(mem_util);
return mem_util->GetExpEndAddr();
}
svBit OtbnMemUtilGetLoopWarp(OtbnMemUtil *mem_util,
/* bit [31:0] */ const svBitVecVal *addr,
/* bit [31:0] */ const svBitVecVal *from_cnt,
/* output bit [31:0] */ svBitVecVal *to_cnt) {
assert(mem_util);
uint32_t addr32 = get_sv_u32(addr);
uint32_t from32 = get_sv_u32(from_cnt);
uint32_t to32 = mem_util->GetLoopWarp(addr32, from32);
set_sv_u32(to_cnt, to32);
return to32 != from32;
}
int OtbnMemUtilGetNumLoopWarps(OtbnMemUtil *mem_util) {
assert(mem_util);
size_t sz = mem_util->GetLoopWarps().size();
assert(sz < (unsigned)std::numeric_limits<int>::max());
return sz;
}
void OtbnMemUtilGetLoopWarpByIndex(
OtbnMemUtil *mem_util, int idx,
/* output bit [31:0] */ svBitVecVal *addr,
/* output bit [31:0] */ svBitVecVal *from_cnt,
/* output bit [31:0] */ svBitVecVal *to_cnt) {
assert(mem_util);
assert(0 <= idx);
auto &warps = mem_util->GetLoopWarps();
assert((unsigned)idx < warps.size());
auto it = std::next(warps.begin(), idx);
uint32_t addr32 = it->first.first;
uint32_t from32 = it->first.second;
uint32_t to32 = it->second;
set_sv_u32(addr, addr32);
set_sv_u32(from_cnt, from32);
set_sv_u32(to_cnt, to32);
}