closes #21 : clean design verification [dv] folder

Signed-off-by: Szymon Bieganski <szymon.bieganski@oss.nxp.com>
This commit is contained in:
Szymon Bieganski 2022-11-22 10:31:41 +01:00
parent 520888bedf
commit 3834421ba5
211 changed files with 0 additions and 19120 deletions

View file

@ -1,19 +0,0 @@
CAPI=2:
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
name: "lowrisc:dv:cosim"
description: "Co-simulator framework"
filesets:
files_cpp:
files:
- cosim.h: { is_include_file: true }
- spike_cosim.cc
- spike_cosim.h: { is_include_file: true }
file_type: cppSource
targets:
default:
filesets:
- files_cpp

View file

@ -1,141 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include <string>
#include <vector>
#ifndef COSIM_H_
#define COSIM_H_
// Information about a dside transaction observed on the DUT memory interface
struct DSideAccessInfo {
// set when the access is a store, in which case `data` is the store data from
// the DUT. Otherwise `data` is the load data provided to the DUT.
bool store;
// `addr` is the address and must be 32-bit aligned. `data` and `be` are
// aligned to the address. For example an 8-bit store of 0xff to 0x12 has
// `data` as 0x00ff0000, `addr` as 0x10 and `be` as 0b0100.
uint32_t data;
uint32_t addr;
uint32_t be;
// set if an error response to the transaction is seen.
bool error;
// `misaligned_first` and `misaligned_second` are set when the transaction is
// generated for a misaligned store or load instruction. `misaligned_first`
// is true when the transaction is for the lower half and `misaligned_second`
// is true when the transaction is for the upper half, if it exists.
//
// For example an unaligned 32-bit load to 0x3 produces a transaction with
// `addr` as 0x0 and `misaligned_first` set to true, then a transaction with
// `addr` as 0x4 and `misaligned_second` set to true. An unaligned 16-bit load
// to 0x01 only produces a transaction with `addr` as 0x0 and
// `misaligned_first` set to true, there is no second half.
bool misaligned_first;
bool misaligned_second;
};
class Cosim {
public:
virtual ~Cosim() {}
// Add a memory to the co-simulator environment.
//
// Use `backdoor_write_mem`/`backdoor_read_mem` to access it from the
// simulation environment.
virtual void add_memory(uint32_t base_addr, size_t size) = 0;
// Write bytes to co-simulator memory.
//
// returns false if write fails (e.g. because no memory exists at the bytes
// being written).
virtual bool backdoor_write_mem(uint32_t addr, size_t len,
const uint8_t *data_in) = 0;
// Read bytes from co-simulator memory.
//
// returns false if read fails (e.g. because no memory exists at the bytes
// being read).
virtual bool backdoor_read_mem(uint32_t addr, size_t len,
uint8_t *data_out) = 0;
// Step the co-simulator, checking register write and PC of executed
// instruction match the supplied values. `write_reg` gives the index of the
// written register along with `write_reg_data` which provides the data. A
// `write_reg` of 0 indicates no register write occurred.
//
// `sync_trap` is set to true when the instruction caused a synchronous trap.
// In this case the instruction doesn't retire so no register write occurs (so
// `write_reg` must be 0).
//
// Returns false if there are any errors; use `get_errors` to obtain details
virtual bool step(uint32_t write_reg, uint32_t write_reg_data, uint32_t pc,
bool sync_trap) = 0;
// When more than one of `set_mip`, `set_nmi` or `set_debug_req` is called
// before `step` which one takes effect is chosen by the co-simulator. Which
// should take priority is architecturally defined by the RISC-V
// specification.
// Set the value of MIP.
//
// At the next call of `step`, the MIP value will take effect (i.e. if it's a
// new interrupt that is enabled it will step straight to that handler).
virtual void set_mip(uint32_t mip) = 0;
// Set the state of the NMI (non-maskable interrupt) line.
//
// The NMI signal is a level triggered interrupt. When the NMI is taken the
// CPU ignores the NMI line until an `mret` instruction is executed. If the
// NMI is high following the `mret` (regardless of whether it has been low or
// not whilst the first NMI is being handled) a new NMI is taken.
//
// When an NMI is due to be taken that will occur at the next call of `step`.
virtual void set_nmi(bool nmi) = 0;
// Set the debug request.
//
// When set to true the core will enter debug mode at the next step
virtual void set_debug_req(bool debug_req) = 0;
// Set the value of mcycle.
//
// The co-simulation model doesn't alter the value of mcycle itself (other
// than instructions that do a direct CSR write). mcycle should be set to the
// correct value before any `step` call that may execute an instruction that
// observes the value of mcycle.
//
// A full 64-bit value is provided setting both the mcycle and mcycleh CSRs.
virtual void set_mcycle(uint64_t mcycle) = 0;
// Tell the co-simulation model about observed transactions on the dside
// memory interface of the DUT. Accesses are notified once the response to a
// transaction is seen.
//
// Observed transactions for the DUT are checked against accesses from the
// co-simulation model when a memory access occurs during a `step`. If there
// is a mismatch an error is reported which can be obtained via `get_errors`.
virtual void notify_dside_access(const DSideAccessInfo &access_info) = 0;
// Tell the co-simulation model about an error response to an iside fetch.
//
// `addr` must be 32-bit aligned.
//
// The next step following a call to `set_iside_error` must produce an
// instruction fault at the given address.
virtual void set_iside_error(uint32_t addr) = 0;
// Get a vector of strings describing errors that have occurred during `step`
virtual const std::vector<std::string> &get_errors() = 0;
// Clear internal vector of error descriptions
virtual void clear_errors() = 0;
// Returns a count of instructions executed by co-simulator and DUT without
// failures.
virtual int get_insn_cnt() = 0;
};
#endif // COSIM_H_

View file

@ -1,100 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include <svdpi.h>
#include <cassert>
#include "cosim.h"
#include "cosim_dpi.h"
int riscv_cosim_step(Cosim *cosim, const svBitVecVal *write_reg,
const svBitVecVal *write_reg_data, const svBitVecVal *pc,
svBit sync_trap) {
assert(cosim);
return cosim->step(write_reg[0], write_reg_data[0], pc[0], sync_trap) ? 1 : 0;
}
void riscv_cosim_set_mip(Cosim *cosim, const svBitVecVal *mip) {
assert(cosim);
cosim->set_mip(mip[0]);
}
void riscv_cosim_set_nmi(Cosim *cosim, svBit nmi) {
assert(cosim);
cosim->set_nmi(nmi);
}
void riscv_cosim_set_debug_req(Cosim *cosim, svBit debug_req) {
assert(cosim);
cosim->set_debug_req(debug_req);
}
void riscv_cosim_set_mcycle(Cosim *cosim, svBitVecVal *mcycle) {
assert(cosim);
uint64_t mcycle_full = mcycle[0] | (uint64_t)mcycle[1] << 32;
cosim->set_mcycle(mcycle_full);
}
void riscv_cosim_notify_dside_access(Cosim *cosim, svBit store,
svBitVecVal *addr, svBitVecVal *data,
svBitVecVal *be, svBit error,
svBit misaligned_first,
svBit misaligned_second) {
assert(cosim);
cosim->notify_dside_access(
DSideAccessInfo{.store = store != 0,
.data = data[0],
.addr = addr[0],
.be = be[0],
.error = error != 0,
.misaligned_first = misaligned_first != 0,
.misaligned_second = misaligned_second != 0});
}
void riscv_cosim_set_iside_error(Cosim *cosim, svBitVecVal *addr) {
assert(cosim);
cosim->set_iside_error(addr[0]);
}
int riscv_cosim_get_num_errors(Cosim *cosim) {
assert(cosim);
return cosim->get_errors().size();
}
const char *riscv_cosim_get_error(Cosim *cosim, int index) {
assert(cosim);
if (index >= cosim->get_errors().size()) {
return nullptr;
}
return cosim->get_errors()[index].c_str();
}
void riscv_cosim_clear_errors(Cosim *cosim) {
assert(cosim);
cosim->clear_errors();
}
void riscv_cosim_write_mem_byte(Cosim *cosim, const svBitVecVal *addr,
const svBitVecVal *d) {
assert(cosim);
uint8_t byte = d[0] & 0xff;
cosim->backdoor_write_mem(addr[0], 1, &byte);
}
int riscv_cosim_get_insn_cnt(Cosim *cosim) {
assert(cosim);
return cosim->get_insn_cnt();
}

View file

@ -1,20 +0,0 @@
CAPI=2:
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
name: "lowrisc:dv:cosim_dpi"
description: "DPI wrapper for Co-simulator framework"
filesets:
files_cpp:
depend:
- lowrisc:dv:cosim
files:
- cosim_dpi.cc: { file_type: cppSource }
- cosim_dpi.h: { file_type: cppSource, is_include_file: true }
- cosim_dpi.svh: {file_type: systemVerilogSource }
targets:
default:
filesets:
- files_cpp

View file

@ -1,36 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#ifndef COSIM_DPI_H_
#define COSIM_DPI_H_
#include <stdint.h>
#include <svdpi.h>
// This adapts the C++ interface of the `Cosim` class to be used via DPI. See
// the documentation in cosim.h for further details
extern "C" {
int riscv_cosim_step(Cosim *cosim, const svBitVecVal *write_reg,
const svBitVecVal *write_reg_data, const svBitVecVal *pc,
svBit sync_trap);
void riscv_cosim_set_mip(Cosim *cosim, const svBitVecVal *mip);
void riscv_cosim_set_nmi(Cosim *cosim, svBit nmi);
void riscv_cosim_set_debug_req(Cosim *cosim, svBit debug_req);
void riscv_cosim_set_mcycle(Cosim *cosim, svBitVecVal *mcycle);
void riscv_cosim_notify_dside_access(Cosim *cosim, svBit store,
svBitVecVal *addr, svBitVecVal *data,
svBitVecVal *be, svBit error,
svBit misaligned_first,
svBit misaligned_second);
void riscv_cosim_set_iside_error(Cosim *cosim, svBitVecVal *addr);
int riscv_cosim_get_num_errors(Cosim *cosim);
const char *riscv_cosim_get_error(Cosim *cosim, int index);
void riscv_cosim_clear_errors(Cosim *cosim);
void riscv_cosim_write_mem_byte(Cosim *cosim, const svBitVecVal *addr,
const svBitVecVal *d);
int riscv_cosim_get_insn_cnt(Cosim *cosim);
}
#endif // COSIM_DPI_H_

View file

@ -1,30 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// DPI interface to co-simulation model, see `cosim.h` for the interface description.
// Implemented as a header file as VCS needs `import` declarations included in each verilog file
// that uses them.
`ifndef COSIM_DPI_SVH
`define COSIM_DPI_SVH
import "DPI-C" function int riscv_cosim_step(chandle cosim_handle, bit [4:0] write_reg,
bit [31:0] write_reg_data, bit [31:0] pc, bit sync_trap);
import "DPI-C" function void riscv_cosim_set_mip(chandle cosim_handle, bit [31:0] mip);
import "DPI-C" function void riscv_cosim_set_nmi(chandle cosim_handle, bit nmi);
import "DPI-C" function void riscv_cosim_set_debug_req(chandle cosim_handle, bit debug_req);
import "DPI-C" function void riscv_cosim_set_mcycle(chandle cosim_handle, bit [63:0] mcycle);
import "DPI-C" function void riscv_cosim_notify_dside_access(chandle cosim_handle, bit store,
bit [31:0] addr, bit [31:0] data, bit [3:0] be, bit error, bit misaligned_first,
bit misaligned_second);
import "DPI-C" function int riscv_cosim_set_iside_error(chandle cosim_handle, bit [31:0] addr);
import "DPI-C" function int riscv_cosim_get_num_errors(chandle cosim_handle);
import "DPI-C" function string riscv_cosim_get_error(chandle cosim_handle, int index);
import "DPI-C" function void riscv_cosim_clear_errors(chandle cosim_handle);
import "DPI-C" function void riscv_cosim_write_mem_byte(chandle cosim_handle, bit [31:0] addr,
bit [7:0] d);
import "DPI-C" function int riscv_cosim_get_insn_cnt(chandle cosim_handle);
`endif

View file

@ -1,572 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "spike_cosim.h"
#include "riscv/config.h"
#include "riscv/decode.h"
#include "riscv/devices.h"
#include "riscv/log_file.h"
#include "riscv/processor.h"
#include "riscv/simif.h"
#include <cassert>
#include <iostream>
#include <sstream>
SpikeCosim::SpikeCosim(const std::string &isa_string, uint32_t start_pc,
uint32_t start_mtvec, const std::string &trace_log_path,
bool secure_ibex, bool icache_en)
: nmi_mode(false), pending_iside_error(false) {
FILE *log_file = nullptr;
if (trace_log_path.length() != 0) {
log = std::make_unique<log_file_t>(trace_log_path.c_str());
log_file = log->get();
}
processor = std::make_unique<processor_t>(
isa_string.c_str(), "MU", DEFAULT_VARCH, this, 0, false, log_file,
nullptr, secure_ibex, icache_en);
processor->set_mmu_capability(IMPL_MMU_SBARE);
processor->get_state()->pc = start_pc;
processor->get_state()->mtvec->write(start_mtvec);
if (log) {
processor->set_debug(true);
processor->enable_log_commits();
}
}
// always return nullptr so all memory accesses go via mmio_load/mmio_store
char *SpikeCosim::addr_to_mem(reg_t addr) { return nullptr; }
bool SpikeCosim::mmio_load(reg_t addr, size_t len, uint8_t *bytes) {
bool bus_error = !bus.load(addr, len, bytes);
bool dut_error = false;
// Incoming access may be an iside or dside access. Use PC to help determine
// which.
uint32_t pc = processor->get_state()->pc;
uint32_t aligned_addr = addr & 0xfffffffc;
if (pending_iside_error && (aligned_addr == pending_iside_err_addr)) {
// Check if the incoming access is subject to an iside error, in which case
// assume it's an iside access and produce an error.
pending_iside_error = false;
dut_error = true;
} else if (addr < pc || addr >= (pc + 8)) {
// Spike may attempt to access up to 8-bytes from the PC when fetching, so
// only check as a dside access when it falls outside that range.
// Otherwise check if the aligned PC matches with the aligned address or an
// incremented aligned PC (to capture the unaligned 4-byte instruction
// case). Assume a successful iside access if either of these checks are
// true, otherwise assume a dside access and check against DUT dside
// accesses. If the RTL produced a bus error for the access, or the
// checking failed produce a memory fault in spike.
dut_error = (check_mem_access(false, addr, len, bytes) != kCheckMemOk);
}
return !(bus_error || dut_error);
}
bool SpikeCosim::mmio_store(reg_t addr, size_t len, const uint8_t *bytes) {
bool bus_error = !bus.store(addr, len, bytes);
// If the RTL produced a bus error for the access, or the checking failed
// produce a memory fault in spike.
bool dut_error = (check_mem_access(true, addr, len, bytes) != kCheckMemOk);
return !(bus_error || dut_error);
}
void SpikeCosim::proc_reset(unsigned id) {}
const char *SpikeCosim::get_symbol(uint64_t addr) { return nullptr; }
void SpikeCosim::add_memory(uint32_t base_addr, size_t size) {
auto new_mem = std::make_unique<mem_t>(size);
bus.add_device(base_addr, new_mem.get());
mems.emplace_back(std::move(new_mem));
}
bool SpikeCosim::backdoor_write_mem(uint32_t addr, size_t len,
const uint8_t *data_in) {
return bus.store(addr, len, data_in);
}
bool SpikeCosim::backdoor_read_mem(uint32_t addr, size_t len,
uint8_t *data_out) {
return bus.load(addr, len, data_out);
}
bool SpikeCosim::step(uint32_t write_reg, uint32_t write_reg_data, uint32_t pc,
bool sync_trap) {
assert(write_reg < 32);
uint32_t initial_pc = (processor->get_state()->pc & 0xffffffff);
bool initial_pc_match = initial_pc == pc;
// Execute the next instruction
processor->step(1);
if (processor->get_state()->last_inst_pc == PC_INVALID) {
if (processor->get_state()->mcause->read() & 0x80000000) {
// Interrupt occurred, step again to execute first instruction of
// interrupt
processor->step(1);
// TODO: Deal with exception on first instruction of interrupt
assert(processor->get_state()->last_inst_pc != PC_INVALID);
} else {
// Otherwise a synchronous trap has occurred, check the DUT reported a
// synchronous trap at the same point
if (!sync_trap) {
std::stringstream err_str;
err_str << "Synchronous trap was expected at ISS PC: " << std::hex
<< processor->get_state()->pc
<< " but DUT didn't report one at PC " << pc;
errors.emplace_back(err_str.str());
return false;
}
if (!initial_pc_match) {
std::stringstream err_str;
err_str << "PC mismatch at synchronous trap, DUT: " << std::hex << pc
<< " expected: " << std::hex << initial_pc;
errors.emplace_back(err_str.str());
return false;
}
if (write_reg != 0) {
std::stringstream err_str;
err_str << "Synchronous trap occurred at PC: " << std::hex << pc
<< "but DUT wrote to register: x" << std::dec << write_reg;
errors.emplace_back(err_str.str());
return false;
}
// Errors may have been generated outside of step() (e.g. in
// check_mem_access()), return false if there are any.
return errors.size() == 0;
}
}
// Check PC of executed instruction matches the expected PC
// TODO: Confirm details of why spike sign extends PC, something to do with
// 32-bit address as 64-bit address must be sign extended?
if ((processor->get_state()->last_inst_pc & 0xffffffff) != pc) {
std::stringstream err_str;
err_str << "PC mismatch, DUT: " << std::hex << pc
<< " expected: " << std::hex
<< processor->get_state()->last_inst_pc;
errors.emplace_back(err_str.str());
return false;
}
if (!sync_trap && pc_is_mret(pc) && nmi_mode) {
// Do handling for recoverable NMI
leave_nmi_mode();
}
// Check register writes from executed instruction match what is expected
auto &reg_changes = processor->get_state()->log_reg_write;
bool gpr_write_seen = false;
for (auto reg_change : reg_changes) {
// reg_change.first provides register type in bottom 4 bits, then register
// index above that
// Ignore writes to x0
if (reg_change.first == 0)
continue;
if ((reg_change.first & 0xf) == 0) {
// register is GPR
// should never see more than one GPR write per step
assert(!gpr_write_seen);
if (!check_gpr_write(reg_change, write_reg, write_reg_data)) {
return false;
}
gpr_write_seen = true;
} else if ((reg_change.first & 0xf) == 4) {
// register is CSR
on_csr_write(reg_change);
} else {
// should never see other types
assert(false);
}
}
if (write_reg != 0 && !gpr_write_seen) {
std::stringstream err_str;
err_str << "DUT wrote register x" << write_reg
<< " but a write was not expected" << std::endl;
errors.emplace_back(err_str.str());
return false;
}
if (pending_iside_error) {
std::stringstream err_str;
err_str << "DUT generated an iside error for address: " << std::hex
<< pending_iside_err_addr << " but the ISS didn't produce one";
errors.emplace_back(err_str.str());
return false;
}
pending_iside_error = false;
// Errors may have been generated outside of step() (e.g. in
// check_mem_access()). Only increment insn_cnt and return true if there are
// no errors
if (errors.size() == 0) {
insn_cnt++;
return true;
}
return false;
}
bool SpikeCosim::check_gpr_write(const commit_log_reg_t::value_type &reg_change,
uint32_t write_reg, uint32_t write_reg_data) {
uint32_t cosim_write_reg = (reg_change.first >> 4) & 0x1f;
if (write_reg == 0) {
std::stringstream err_str;
err_str << "DUT didn't write to register x" << cosim_write_reg
<< ", but a write was expected";
errors.emplace_back(err_str.str());
return false;
}
if (write_reg != cosim_write_reg) {
std::stringstream err_str;
err_str << "Register write index mismatch, DUT: x" << write_reg
<< " expected: x" << cosim_write_reg;
errors.emplace_back(err_str.str());
return false;
}
// TODO: Investigate why this fails (may be because spike can produce PCs
// with high 32 bits set).
// assert((reg_change.second.v[0] & 0xffffffff00000000) == 0);
uint32_t cosim_write_reg_data = reg_change.second.v[0];
if (write_reg_data != cosim_write_reg_data) {
std::stringstream err_str;
err_str << "Register write data mismatch to x" << cosim_write_reg
<< " DUT: " << std::hex << write_reg_data
<< " expected: " << cosim_write_reg_data;
errors.emplace_back(err_str.str());
return false;
}
return true;
}
void SpikeCosim::on_csr_write(const commit_log_reg_t::value_type &reg_change) {
int cosim_write_csr = (reg_change.first >> 4) & 0xfff;
// TODO: Investigate why this fails (may be because spike can produce PCs
// with high 32 bits set).
// assert((reg_change.second.v[0] & 0xffffffff00000000) == 0);
uint32_t cosim_write_csr_data = reg_change.second.v[0];
// Spike and Ibex have different WARL behaviours so after any CSR write
// check the fields and adjust to match Ibex behaviour.
fixup_csr(cosim_write_csr, cosim_write_csr_data);
}
void SpikeCosim::leave_nmi_mode() {
nmi_mode = false;
// Restore CSR status from mstack
uint32_t mstatus = processor->get_csr(CSR_MSTATUS);
mstatus = set_field(mstatus, MSTATUS_MPP, mstack.mpp);
mstatus = set_field(mstatus, MSTATUS_MPIE, mstack.mpie);
processor->set_csr(CSR_MSTATUS, mstatus);
processor->set_csr(CSR_MEPC, mstack.epc);
processor->set_csr(CSR_MCAUSE, mstack.cause);
}
void SpikeCosim::set_mip(uint32_t mip) {
processor->get_state()->mip->write_with_mask(0xffffffff, mip);
}
void SpikeCosim::set_nmi(bool nmi) {
if (nmi && !nmi_mode && !processor->get_state()->debug_mode) {
processor->get_state()->nmi = true;
nmi_mode = true;
// When NMI is set it is guaranteed NMI trap will be taken at the next step
// so save CSR state for recoverable NMI to mstack now.
mstack.mpp = get_field(processor->get_csr(CSR_MSTATUS), MSTATUS_MPP);
mstack.mpie = get_field(processor->get_csr(CSR_MSTATUS), MSTATUS_MPIE);
mstack.epc = processor->get_csr(CSR_MEPC);
mstack.cause = processor->get_csr(CSR_MCAUSE);
}
}
void SpikeCosim::set_debug_req(bool debug_req) {
processor->halt_request =
debug_req ? processor_t::HR_REGULAR : processor_t::HR_NONE;
}
void SpikeCosim::set_mcycle(uint64_t mcycle) {
processor->get_state()->mcycle = mcycle;
}
void SpikeCosim::notify_dside_access(const DSideAccessInfo &access_info) {
// Address must be 32-bit aligned
assert((access_info.addr & 0x3) == 0);
pending_dside_accesses.emplace_back(
PendingMemAccess{.dut_access_info = access_info, .be_spike = 0});
}
void SpikeCosim::set_iside_error(uint32_t addr) {
// Address must be 32-bit aligned
assert((addr & 0x3) == 0);
pending_iside_error = true;
pending_iside_err_addr = addr;
}
const std::vector<std::string> &SpikeCosim::get_errors() { return errors; }
void SpikeCosim::clear_errors() { errors.clear(); }
void SpikeCosim::fixup_csr(int csr_num, uint32_t csr_val) {
switch (csr_num) {
case CSR_MSTATUS:
reg_t mask =
MSTATUS_MIE | MSTATUS_MPIE | MSTATUS_MPRV | MSTATUS_MPP | MSTATUS_TW;
reg_t new_val = csr_val & mask;
processor->set_csr(csr_num, new_val);
break;
}
}
SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
bool store, uint32_t addr, size_t len, const uint8_t *bytes) {
assert(len >= 1 && len <= 4);
// Expect that no spike memory accesses cross a 32-bit boundary
assert(((addr + (len - 1)) & 0xfffffffc) == (addr & 0xfffffffc));
std::string iss_action = store ? "store" : "load";
// Check if there are any pending DUT accesses to check against
if (pending_dside_accesses.size() == 0) {
std::stringstream err_str;
err_str << "A " << iss_action << " at address " << std::hex << addr
<< " was expected but there are no pending accesses";
errors.emplace_back(err_str.str());
return kCheckMemCheckFailed;
}
auto &top_pending_access = pending_dside_accesses.front();
auto &top_pending_access_info = top_pending_access.dut_access_info;
std::string dut_action = top_pending_access_info.store ? "store" : "load";
// Check for an address match
uint32_t aligned_addr = addr & 0xfffffffc;
if (aligned_addr != top_pending_access_info.addr) {
std::stringstream err_str;
err_str << "DUT generated " << dut_action << " at address " << std::hex
<< top_pending_access_info.addr << " but " << iss_action
<< " at address " << aligned_addr << " was expected";
errors.emplace_back(err_str.str());
return kCheckMemCheckFailed;
}
// Check access type match
if (store != top_pending_access_info.store) {
std::stringstream err_str;
err_str << "DUT generated " << dut_action << " at addr " << std::hex
<< top_pending_access_info.addr << " but a " << iss_action
<< " was expected";
errors.emplace_back(err_str.str());
return kCheckMemCheckFailed;
}
// Calculate bytes within aligned 32-bit word that spike has accessed
uint32_t expected_be = ((1 << len) - 1) << (addr & 0x3);
bool pending_access_done = false;
bool misaligned = top_pending_access_info.misaligned_first ||
top_pending_access_info.misaligned_second;
if (misaligned) {
// For misaligned accesses spike will generated multiple single byte
// accesses where the DUT will generate an access covering all bytes within
// an aligned 32-bit word.
// Check bytes accessed this time haven't already been been seen for the DUT
// access we are trying to match against
if ((expected_be & top_pending_access.be_spike) != 0) {
std::stringstream err_str;
err_str << "DUT generated " << dut_action << " at address " << std::hex
<< top_pending_access_info.addr << " with BE "
<< top_pending_access_info.be << " and expected BE "
<< expected_be << " has been seen twice, so far seen "
<< top_pending_access.be_spike;
errors.emplace_back(err_str.str());
return kCheckMemCheckFailed;
}
// Check expected access isn't trying to access bytes that the DUT access
// didn't access.
if ((expected_be & ~top_pending_access_info.be) != 0) {
std::stringstream err_str;
err_str << "DUT generated " << dut_action << " at address " << std::hex
<< top_pending_access_info.addr << " with BE "
<< top_pending_access_info.be << " but expected BE "
<< expected_be << " has other bytes enabled";
errors.emplace_back(err_str.str());
return kCheckMemCheckFailed;
}
// Record which bytes have been seen from spike
top_pending_access.be_spike |= expected_be;
// If all bytes have been seen from spike we're done with this DUT access
if (top_pending_access.be_spike == top_pending_access_info.be) {
pending_access_done = true;
}
} else {
// For aligned accesses bytes from spike access must precisely match bytes
// from DUT access in one go
if (expected_be != top_pending_access_info.be) {
std::stringstream err_str;
err_str << "DUT generated " << dut_action << " at address " << std::hex
<< top_pending_access_info.addr << " with BE "
<< top_pending_access_info.be << " but BE " << expected_be
<< " was expected";
errors.emplace_back(err_str.str());
return kCheckMemCheckFailed;
}
pending_access_done = true;
}
// Check data from expected access matches pending DUT access.
// Data is ignored on error responses to loads so don't check it. Always check
// store data.
if (store || !top_pending_access_info.error) {
// Combine bytes into a single word
uint32_t expected_data = 0;
for (int i = 0; i < len; ++i) {
expected_data |= bytes[i] << (i * 8);
}
// Shift bytes into their position within an aligned 32-bit word
expected_data <<= (addr & 0x3) * 8;
// Mask off bytes expected access doesn't touch and check bytes match for
// those that it does
uint32_t expected_be_bits = (((uint64_t)1 << (len * 8)) - 1)
<< ((addr & 0x3) * 8);
uint32_t masked_dut_data = top_pending_access_info.data & expected_be_bits;
if (expected_data != masked_dut_data) {
std::stringstream err_str;
err_str << "DUT generated " << iss_action << " at address " << std::hex
<< top_pending_access_info.addr << " with data "
<< masked_dut_data << " but data " << expected_data
<< " was expected with byte mask " << expected_be;
errors.emplace_back(err_str.str());
return kCheckMemCheckFailed;
}
}
bool pending_access_error = top_pending_access_info.error;
if (pending_access_error && misaligned) {
// When misaligned accesses see an error, if they have crossed a 32-bit
// boundary DUT will generate two accesses. If the top pending access from
// the DUT was the first half of a misaligned access which accesses the top
// byte, it must have crossed the 32-bit boundary and generated a second
// access
if (top_pending_access_info.misaligned_first &&
((top_pending_access_info.be & 0x8) != 0)) {
// Check the second access DUT exists
if ((pending_dside_accesses.size() < 2) ||
!pending_dside_accesses[1].dut_access_info.misaligned_second) {
std::stringstream err_str;
err_str << "DUT generated first half of misaligned " << iss_action
<< " at address " << std::hex << top_pending_access_info.addr
<< " but second half was expected and not seen";
errors.emplace_back(err_str.str());
return kCheckMemCheckFailed;
}
// Check the second access had the expected address
if (pending_dside_accesses[1].dut_access_info.addr !=
(top_pending_access_info.addr + 4)) {
std::stringstream err_str;
err_str << "DUT generated first half of misaligned " << iss_action
<< " at address " << std::hex << top_pending_access_info.addr
<< " but second half had incorrect address "
<< pending_dside_accesses[1].dut_access_info.addr;
errors.emplace_back(err_str.str());
return kCheckMemCheckFailed;
}
// TODO: How to check BE? May need length of transaction?
// Remove the top pending access now so both the first and second DUT
// accesses for this misaligned access are removed.
pending_dside_accesses.erase(pending_dside_accesses.begin());
}
// For any misaligned access that sees an error immediately indicate to
// spike the error has occured, so ensure the top pending access gets
// removed.
pending_access_done = true;
}
if (pending_access_done) {
pending_dside_accesses.erase(pending_dside_accesses.begin());
}
return pending_access_error ? kCheckMemBusError : kCheckMemOk;
}
bool SpikeCosim::pc_is_mret(uint32_t pc) {
uint32_t insn;
if (!backdoor_read_mem(pc, 4, reinterpret_cast<uint8_t *>(&insn))) {
return false;
}
return insn == 0x30200073;
}
int SpikeCosim::get_insn_cnt() { return insn_cnt; }

View file

@ -1,106 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#ifndef SPIKE_COSIM_H_
#define SPIKE_COSIM_H_
#include "cosim.h"
#include "riscv/devices.h"
#include "riscv/log_file.h"
#include "riscv/processor.h"
#include "riscv/simif.h"
#include <stdint.h>
#include <deque>
#include <memory>
#include <string>
#include <vector>
class SpikeCosim : public simif_t, public Cosim {
private:
std::unique_ptr<processor_t> processor;
std::unique_ptr<log_file_t> log;
bus_t bus;
std::vector<std::unique_ptr<mem_t>> mems;
std::vector<std::string> errors;
bool nmi_mode;
typedef struct {
uint8_t mpp;
bool mpie;
uint32_t epc;
uint32_t cause;
} mstack_t;
mstack_t mstack;
void fixup_csr(int csr_num, uint32_t csr_val);
struct PendingMemAccess {
DSideAccessInfo dut_access_info;
uint32_t be_spike;
};
std::vector<PendingMemAccess> pending_dside_accesses;
bool pending_iside_error;
uint32_t pending_iside_err_addr;
typedef enum {
kCheckMemOk, // Checks passed and access succeded in RTL
kCheckMemCheckFailed, // Checks failed
kCheckMemBusError // Checks passed, but access generated bus error in RTL
} check_mem_result_e;
check_mem_result_e check_mem_access(bool store, uint32_t addr, size_t len,
const uint8_t *bytes);
bool pc_is_mret(uint32_t pc);
bool check_gpr_write(const commit_log_reg_t::value_type &reg_change,
uint32_t write_reg, uint32_t write_reg_data);
void on_csr_write(const commit_log_reg_t::value_type &reg_change);
void leave_nmi_mode();
int insn_cnt;
public:
SpikeCosim(const std::string &isa_string, uint32_t start_pc,
uint32_t start_mtvec, const std::string &trace_log_path,
bool secure_ibex, bool icache_en);
// simif_t implementation
virtual char *addr_to_mem(reg_t addr) override;
virtual bool mmio_load(reg_t addr, size_t len, uint8_t *bytes) override;
virtual bool mmio_store(reg_t addr, size_t len,
const uint8_t *bytes) override;
virtual void proc_reset(unsigned id) override;
virtual const char *get_symbol(uint64_t addr) override;
// Cosim implementation
void add_memory(uint32_t base_addr, size_t size) override;
bool backdoor_write_mem(uint32_t addr, size_t len,
const uint8_t *data_in) override;
bool backdoor_read_mem(uint32_t addr, size_t len, uint8_t *data_out) override;
bool step(uint32_t write_reg, uint32_t write_reg_data, uint32_t pc,
bool sync_trap) override;
void set_mip(uint32_t mip) override;
void set_nmi(bool nmi) override;
void set_debug_req(bool debug_req) override;
void set_mcycle(uint64_t mcycle) override;
void notify_dside_access(const DSideAccessInfo &access_info) override;
// The spike co-simulator assumes iside and dside accesses within a step are
// disjoint. If both access the same address within a step memory faults may
// be incorrectly cause on one rather than the other or the access checking
// will break.
// TODO: Work on spike changes to remove this restriction
void set_iside_error(uint32_t addr) override;
const std::vector<std::string> &get_errors() override;
void clear_errors() override;
int get_insn_cnt() override;
};
#endif // SPIKE_COSIM_H_

View file

@ -1,74 +0,0 @@
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
TOOL = verilator
SO_NAME = reg_dpi.so
BUILDDIR = build
SRCEXT = cc
OBJDIR = $(BUILDDIR)/obj
BINDIR = $(BUILDDIR)/bin
SRCS := $(shell find . -name '*.$(SRCEXT)' ! -path './tb/*' ! -path './build/*')
SRCDIRS := $(shell find . -name '*.$(SRCEXT)' ! -name 'tb*' ! -name 'build*' -exec dirname {} \; | uniq)
OBJS := $(patsubst %.$(SRCEXT),$(OBJDIR)/%.o,$(SRCS))
DEBUG = -g
INCLUDES = -I./env -I./rst_driver -I./reg_driver -I./model
CFLAGS = -std=c++14 -m64 -fPIC -Wall -pedantic $(DEBUG) $(INCLUDES)
LDFLAGS = -shared
CC = $(CXX)
# Add svdpi include
TOOLDIR = $(subst bin/$(TOOL),,$(shell which $(TOOL)))
ifeq ($(TOOL),vcs)
INCLUDES += -I$(TOOLDIR)include
else
INCLUDES += -I$(TOOLDIR)share/verilator/include/vltstd
endif
.PHONY: all clean
all: build
build: $(BINDIR)/$(SO_NAME)
@echo "Build done"
$(BINDIR)/$(SO_NAME): buildrepo $(OBJS)
@mkdir -p `dirname $@`
@echo "Linking $@..."
@$(CC) $(OBJS) $(LDFLAGS) -o $@
$(OBJDIR)/%.o: %.$(SRCEXT)
@echo "Generating dependencies for $<..."
@$(call make-depend,$<,$@,$(subst .o,.d,$@))
@echo "Compiling $<..."
@$(CC) $(CFLAGS) -c $< -o $@
clean:
$(RM) -r $(BUILDDIR)
buildrepo:
@$(call make-repo)
define make-repo
for dir in $(SRCDIRS); \
do \
mkdir -p $(OBJDIR)/$$dir; \
done
endef
# usage: $(call make-depend,source-file,object-file,depend-file)
define make-depend
$(CC) -MM \
-MF $3 \
-MP \
-MT $2 \
$(CFLAGS) \
$1
endef

View file

@ -1,51 +0,0 @@
Ibex simulation Control/Status Registers Testbench
==================================================
This directory contains a basic sample testbench in C++ for testing correctness of some CS registers implemented in Ibex.
It is a work in progress and only tests a handful of registers, and is missing many features.
How to build and run the testbench
----------------------------------
Verilator version:
```sh
fusesoc --cores-root=. run --target=sim --tool=verilator lowrisc:ibex:tb_cs_registers
```
VCS version:
```sh
fusesoc --cores-root=. run --target=sim --tool=vcs lowrisc:ibex:tb_cs_registers
```
Testbench file structure
------------------------
`tb/tb_cs_registers.sv` - Is the verilog top level, it instantiates the DUT and DPI calls
`tb/tb_cs_registers.cc` - Is the C++ top level, it sets up the testbench components
`driver/` - Contains components to generate and drive random register transactions
`env/` - Contains components to generate and drive other signals
`model/` - Contains a model of the register state and checking functions
DPI methodology
---------------
The testbench relies on DPI calls to interface between the RTL and the C++ driver/checker components.
This methodology allows the testbench to support both Verilator and commercial simulators.
Each DPI call has a function in SV (which is called in the SV top-level), and a corresponding C function.
To support the instantiation of multiple instances of TB components, some DPI modules can register their objects referenced by name.
Each DPI call from the RTL then calls into the correct instance by matching the name.
Testbench structure
-------------------
The driver component contains one DPI "interface" to drive new random transactions into the DUT, and another to monitor issued transactions including the DUT's responses.
Each time a new transaction is detected by the monitor component, it is captured and sent to the model.
The model reads incoming transactions, updates its internal state and checks that the DUT outputs matches its own predictions.

View file

@ -1,48 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "register_environment.h"
#include "register_types.h"
#include "svdpi.h"
#include <map>
#include <string>
#ifdef __cplusplus
extern "C" {
#endif
RegisterEnvironment *reg_env;
void env_initial(svBitVecVal *seed, svBit PMPEnable,
svBitVecVal *PMPGranularity, svBitVecVal *PMPNumRegions,
svBitVecVal *MHPMCounterNum, svBitVecVal *MHPMCounterWidth) {
// Package up parameters
CSRParams params;
params.PMPEnable = PMPEnable;
params.PMPGranularity = *PMPGranularity;
params.PMPNumRegions = *PMPNumRegions;
params.MHPMCounterNum = *MHPMCounterNum;
params.MHPMCounterWidth = *MHPMCounterWidth;
// Create TB environment
reg_env = new RegisterEnvironment(params);
// Initial setup
reg_env->OnInitial(*seed);
}
void env_final() {
reg_env->OnFinal();
delete reg_env;
}
void env_tick(svBit *stop_req, svBit *test_passed) {
reg_env->GetStopReq(stop_req);
reg_env->GetTestPass(test_passed);
}
#ifdef __cplusplus
}
#endif

View file

@ -1,25 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
package env_dpi;
import "DPI-C"
function void env_initial(input bit [31:0] seed,
input bit PMPEnable,
input bit [31:0] PMPGranularity,
input bit [31:0] PMPNumRegions,
input bit [31:0] MHPMCounterNum,
input bit [31:0] MHPMCounterWidth);
import "DPI-C"
function void env_final();
import "DPI-C"
function void env_tick(
output bit stop_req,
output bit test_passed);
endpackage

View file

@ -1,35 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "register_environment.h"
RegisterEnvironment::RegisterEnvironment(CSRParams params)
: params_(params),
simctrl_(new SimCtrl()),
reg_model_(new RegisterModel(simctrl_, &params_)),
reg_driver_(new RegisterDriver("reg_driver", reg_model_, simctrl_)),
rst_driver_(new ResetDriver("rstn_driver")) {}
void RegisterEnvironment::OnInitial(unsigned int seed) {
rst_driver_->OnInitial(seed);
reg_driver_->OnInitial(seed);
}
void RegisterEnvironment::OnFinal() {
reg_driver_->OnFinal();
rst_driver_->OnFinal();
simctrl_->OnFinal();
delete rst_driver_;
delete reg_driver_;
delete reg_model_;
delete simctrl_;
}
void RegisterEnvironment::GetStopReq(unsigned char *stop_req) {
*stop_req = simctrl_->StopRequested();
}
void RegisterEnvironment::GetTestPass(unsigned char *test_passed) {
*test_passed = simctrl_->TestPassed();
}

View file

@ -1,36 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#ifndef REGISTER_ENVIRONMENT_H_
#define REGISTER_ENVIRONMENT_H_
#include "register_driver.h"
#include "register_model.h"
#include "register_types.h"
#include "reset_driver.h"
#include "simctrl.h"
/**
* Class to instantiate all tb components
*/
class RegisterEnvironment {
public:
RegisterEnvironment(CSRParams params);
void OnInitial(unsigned int seed);
void OnFinal();
void GetStopReq(unsigned char *stop_req);
void GetTestPass(unsigned char *test_passed);
private:
CSRParams params_;
SimCtrl *simctrl_;
RegisterModel *reg_model_;
RegisterDriver *reg_driver_;
ResetDriver *rst_driver_;
};
#endif // REGISTER_ENVIRONMENT_H_

View file

@ -1,16 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#ifndef REGISTER_TYPES_H_
#define REGISTER_TYPES_H_
struct CSRParams {
bool PMPEnable;
unsigned int PMPGranularity;
unsigned int PMPNumRegions;
unsigned int MHPMCounterNum;
unsigned int MHPMCounterWidth;
};
#endif // REGISTER_TYPES_H_

View file

@ -1,26 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "simctrl.h"
#include <iostream>
SimCtrl::SimCtrl() : stop_requested_(false), success_(true) {}
void SimCtrl::RequestStop(bool success) {
stop_requested_ = true;
success_ &= success;
}
bool SimCtrl::StopRequested() { return stop_requested_; }
bool SimCtrl::TestPassed() { return success_; }
void SimCtrl::OnFinal() {
std::cout << std::endl
<< "//-------------//" << std::endl
<< (success_ ? "// TEST PASSED //" : "// TEST FAILED //")
<< std::endl
<< "//-------------//" << std::endl;
}

View file

@ -1,21 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#ifndef SIMCTRL_H_
#define SIMCTRL_H_
class SimCtrl {
public:
SimCtrl();
void RequestStop(bool success);
bool StopRequested();
bool TestPassed();
void OnFinal();
private:
bool stop_requested_;
bool success_;
};
#endif

View file

@ -1,32 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// Lint waivers for processing simple_system RTL with Verilator
//
// This should be used for rules applying to things like testbench
// top-levels. For rules that apply to the actual design (files in the
// 'rtl' directory), see verilator_waiver_rtl.vlt in the same
// directory.
//
// See https://www.veripool.org/projects/verilator/wiki/Manual-verilator#CONFIGURATION-FILES
// for documentation.
//
// Important: This file must included *before* any other Verilog file is read.
// Otherwise, only global waivers are applied, but not file-specific waivers.
`verilator_config
// We use boolean top-level parameters.
// When building with fusesoc, these get set with defines like
// -GRV32E=1 (rather than -GRV32E=1'b1), leading to warnings like:
//
// Operator VAR '<varname>' expects 1 bits on the Initial value, but
// Initial value's CONST '32'h1' generates 32 bits.
//
// This signoff rule ignores errors like this. Note that it only
// matches when you set a 1-bit value to a literal 1, so it won't hide
// silly mistakes like setting it to 2.
//
lint_off -rule WIDTH -file "*/tb/tb_cs_registers.sv"
-match "*expects 1 bits*Initial value's CONST '32'h1'*"

View file

@ -1,253 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "base_register.h"
#include <cassert>
#include <iostream>
BaseRegister::BaseRegister(
uint32_t addr, std::vector<std::unique_ptr<BaseRegister>> *map_pointer)
: register_value_(0), register_address_(addr), map_pointer_(map_pointer) {}
uint32_t BaseRegister::RegisterWrite(uint32_t newval) {
uint32_t lock_mask = GetLockMask();
uint32_t read_value = register_value_;
register_value_ &= lock_mask;
register_value_ |= (newval & ~lock_mask);
return read_value;
}
uint32_t BaseRegister::RegisterSet(uint32_t newval) {
uint32_t lock_mask = GetLockMask();
uint32_t read_value = register_value_;
register_value_ |= (newval & ~lock_mask);
return read_value;
}
uint32_t BaseRegister::RegisterClear(uint32_t newval) {
uint32_t lock_mask = GetLockMask();
uint32_t read_value = register_value_;
register_value_ &= (~newval | lock_mask);
return read_value;
}
bool BaseRegister::MatchAddr(uint32_t addr, uint32_t addr_mask) {
return ((addr & addr_mask) == (register_address_ & addr_mask));
}
bool BaseRegister::ProcessTransaction(bool *match, RegisterTransaction *trans) {
uint32_t read_val;
if (!MatchAddr(trans->csr_addr)) {
return false;
}
*match = true;
switch (trans->csr_op) {
case kCSRRead:
read_val = RegisterRead();
break;
case kCSRWrite:
read_val = RegisterWrite(trans->csr_wdata);
break;
case kCSRSet:
read_val = RegisterSet(trans->csr_wdata);
break;
case kCSRClear:
read_val = RegisterClear(trans->csr_wdata);
break;
}
if (trans->csr_addr == kCSRMCycle || trans->csr_addr == kCSRMCycleH) {
// MCycle(H) can increment or even overflow without TB interaction
if (trans->csr_rdata < read_val) {
std::cout << "MCycle(H) overflow detected" << std::endl;
}
// else if (read_val != trans->csr_rdata) {
// std::cout << "MCycle(H) incrementing as expected" << std::endl;
//}
// Don't panic about MCycle(H) incremeting, this is expected behavior as
// the clock is always running. Silently ignore mismatches for MCycle(H).
} else if (read_val != trans->csr_rdata) {
std::cout << "Error, transaction:" << std::endl;
trans->Print();
std::cout << "Expected rdata: " << std::hex << read_val << std::dec
<< std::endl;
return true;
}
return false;
}
void BaseRegister::RegisterReset() { register_value_ = 0; }
uint32_t BaseRegister::RegisterRead() { return register_value_; }
uint32_t BaseRegister::GetLockMask() { return 0; }
BaseRegister *BaseRegister::GetRegisterFromMap(uint32_t addr) {
for (auto &reg : *map_pointer_) {
if (reg->MatchAddr(addr)) {
return reg.get();
}
}
return nullptr;
}
MSeccfgRegister::MSeccfgRegister(
uint32_t addr, std::vector<std::unique_ptr<BaseRegister>> *map_pointer)
: BaseRegister(addr, map_pointer) {}
bool MSeccfgRegister::AnyPmpCfgsLocked() {
for (auto &reg : *map_pointer_) {
// Iterate through PMPCfgX CSRs, returning true is any has a lock bit set
if (reg->MatchAddr(kCSRPMPCfg0, 0xfffffffc)) {
if ((reg->RegisterRead() & 0x80808080) != 0) {
return true;
}
}
}
return false;
}
uint32_t MSeccfgRegister::GetLockMask() {
uint32_t lock_mask = 0xFFFFFFF8;
// When RLB == 0 and any PMPCfgX has a lock bit set RLB must remain 0
if (AnyPmpCfgsLocked() && ((register_value_ & kMSeccfgRlb) == 0)) {
lock_mask |= kMSeccfgRlb;
}
// Once set MMWP cannot be unset
if (register_value_ & kMSeccfgMmwp) {
lock_mask |= kMSeccfgMmwp;
}
// Once set MML cannot be unset
if (register_value_ & kMSeccfgMml) {
lock_mask |= kMSeccfgMml;
}
return lock_mask;
}
uint32_t PmpCfgRegister::GetLockMask() {
BaseRegister *mseccfg = GetRegisterFromMap(kCSRMSeccfg);
assert(mseccfg);
if (mseccfg->RegisterRead() & kMSeccfgRlb) {
return 0;
}
uint32_t lock_mask = 0;
if (register_value_ & 0x80)
lock_mask |= 0xFF;
if (register_value_ & 0x8000)
lock_mask |= 0xFF00;
if (register_value_ & 0x800000)
lock_mask |= 0xFF0000;
if (register_value_ & 0x80000000)
lock_mask |= 0xFF000000;
return lock_mask;
}
uint32_t PmpCfgRegister::RegisterWrite(uint32_t newval) {
uint32_t lock_mask = GetLockMask();
uint32_t read_value = register_value_;
register_value_ &= lock_mask;
register_value_ |= (newval & ~lock_mask);
register_value_ = HandleReservedVals(register_value_);
return read_value;
}
uint32_t PmpCfgRegister::RegisterSet(uint32_t newval) {
uint32_t lock_mask = GetLockMask();
uint32_t read_value = register_value_;
register_value_ |= (newval & ~lock_mask);
register_value_ = HandleReservedVals(register_value_);
return read_value;
}
uint32_t PmpCfgRegister::RegisterClear(uint32_t newval) {
uint32_t lock_mask = GetLockMask();
uint32_t read_value = register_value_;
register_value_ &= (~newval | lock_mask);
register_value_ = HandleReservedVals(register_value_);
return read_value;
}
uint32_t PmpCfgRegister::HandleReservedVals(uint32_t cfg_val) {
BaseRegister *mseccfg = GetRegisterFromMap(kCSRMSeccfg);
assert(mseccfg);
cfg_val &= raz_mask_;
if (mseccfg->RegisterRead() & kMSeccfgMml) {
// No reserved L/R/W/X values when MML Set
return cfg_val;
}
for (int i = 0; i < 4; i++) {
// Reserved check, W = 1, R = 0
if (((cfg_val >> (8 * i)) & 0x3) == 0x2) {
cfg_val &= ~(0x3 << (8 * i));
}
}
return cfg_val;
}
uint32_t PmpAddrRegister::GetLockMask() {
// Calculate which region this is
uint32_t pmp_region = (register_address_ & 0xF);
// Form the address of the corresponding CFG register
uint32_t pmp_cfg_addr = 0x3A0 + (pmp_region / 4);
// Form the address of the CFG registerfor the next region
// For region 15, this will point to a non-existant register, which is fine
uint32_t pmp_cfg_plus1_addr = 0x3A0 + ((pmp_region + 1) / 4);
uint32_t cfg_value = 0;
uint32_t cfg_plus1_value = 0;
// Find and read the two CFG registers
for (auto it = map_pointer_->begin(); it != map_pointer_->end(); ++it) {
if ((*it)->MatchAddr(pmp_cfg_addr)) {
cfg_value = (*it)->RegisterRead();
}
if ((*it)->MatchAddr(pmp_cfg_plus1_addr)) {
cfg_plus1_value = (*it)->RegisterRead();
}
}
// Shift to the relevant bits in the CFG registers
cfg_value >>= ((pmp_region & 0x3) * 8);
cfg_plus1_value >>= (((pmp_region + 1) & 0x3) * 8);
// Locked if the lock bit is set, or the next region is TOR
if ((cfg_value & 0x80) || ((cfg_plus1_value & 0x18) == 0x8)) {
return 0xFFFFFFFF;
} else {
return 0;
}
}
uint32_t NonImpRegister::RegisterRead() { return 0; }
uint32_t NonImpRegister::RegisterWrite(uint32_t newval) { return 0; }
uint32_t NonImpRegister::RegisterSet(uint32_t newval) { return 0; }
uint32_t NonImpRegister::RegisterClear(uint32_t newval) { return 0; }
WARLRegister::WARLRegister(
uint32_t addr, std::vector<std::unique_ptr<BaseRegister>> *map_pointer,
uint32_t mask, uint32_t resval)
: BaseRegister{addr, map_pointer},
register_mask_(mask),
register_value_reset_(resval) {}
void WARLRegister::RegisterReset() { register_value_ = register_value_reset_; }
uint32_t WARLRegister::GetLockMask() { return register_mask_; }

View file

@ -1,113 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#ifndef BASE_REGISTER_H_
#define BASE_REGISTER_H_
#include "register_transaction.h"
#include <stdint.h>
#include <memory>
#include <vector>
/**
* Base register class, can be specialized to add advanced functionality
* required by different types of register
*/
class BaseRegister {
public:
BaseRegister(uint32_t addr,
std::vector<std::unique_ptr<BaseRegister>> *map_pointer);
virtual ~BaseRegister() = default;
virtual void RegisterReset();
virtual uint32_t RegisterWrite(uint32_t newval);
virtual uint32_t RegisterSet(uint32_t newval);
virtual uint32_t RegisterClear(uint32_t newval);
virtual uint32_t RegisterRead();
virtual bool ProcessTransaction(bool *match, RegisterTransaction *trans);
virtual bool MatchAddr(uint32_t addr, uint32_t addr_mask = 0xFFFFFFFF);
virtual uint32_t GetLockMask();
protected:
uint32_t register_value_;
uint32_t register_address_;
std::vector<std::unique_ptr<BaseRegister>> *map_pointer_;
BaseRegister *GetRegisterFromMap(uint32_t addr);
};
/**
* Machine Security Configuration class
*
* Has special behaviour for when bits are locked so requires a custom
* `GetLockMask` implementation
*/
class MSeccfgRegister : public BaseRegister {
public:
MSeccfgRegister(uint32_t addr,
std::vector<std::unique_ptr<BaseRegister>> *map_pointer);
uint32_t GetLockMask();
private:
bool AnyPmpCfgsLocked();
};
/**
* PMP configuration register class
*/
class PmpCfgRegister : public BaseRegister {
using BaseRegister::BaseRegister;
public:
uint32_t GetLockMask();
uint32_t RegisterWrite(uint32_t newval);
uint32_t RegisterSet(uint32_t newval);
uint32_t RegisterClear(uint32_t newval);
private:
uint32_t HandleReservedVals(uint32_t cfg_val);
const uint32_t raz_mask_ = 0x9F9F9F9F;
};
/**
* PMP address register class
*/
class PmpAddrRegister : public BaseRegister {
using BaseRegister::BaseRegister;
public:
uint32_t GetLockMask();
};
/**
* Generic class to model non-implemented (read as zero) registers
*/
class NonImpRegister : public BaseRegister {
using BaseRegister::BaseRegister;
public:
uint32_t RegisterRead();
uint32_t RegisterWrite(uint32_t newval);
uint32_t RegisterSet(uint32_t newval);
uint32_t RegisterClear(uint32_t newval);
};
/**
* Generic class of WARL registers
*/
class WARLRegister : public BaseRegister {
using BaseRegister::BaseRegister;
protected:
uint32_t register_mask_;
uint32_t register_value_reset_;
public:
WARLRegister(uint32_t addr,
std::vector<std::unique_ptr<BaseRegister>> *map_pointer,
uint32_t mask, uint32_t resval);
uint32_t GetLockMask();
void RegisterReset();
};
#endif // BASE_REGISTER_H_

View file

@ -1,123 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "register_model.h"
#include <iostream>
RegisterModel::RegisterModel(SimCtrl *sc, CSRParams *params) : simctrl_(sc) {
register_map_.push_back(
std::make_unique<MSeccfgRegister>(kCSRMSeccfg, &register_map_));
register_map_.push_back(
std::make_unique<NonImpRegister>(kCSRMSeccfgh, &register_map_));
// Instantiate all the registers
for (unsigned int i = 0; i < 4; i++) {
uint32_t reg_addr = 0x3A0 + i;
if (params->PMPEnable && (i < (params->PMPNumRegions / 4))) {
register_map_.push_back(
std::make_unique<PmpCfgRegister>(reg_addr, &register_map_));
} else {
register_map_.push_back(
std::make_unique<NonImpRegister>(reg_addr, &register_map_));
}
}
for (unsigned int i = 0; i < 16; i++) {
uint32_t reg_addr = 0x3B0 + i;
if (params->PMPEnable && (i < params->PMPNumRegions)) {
register_map_.push_back(
std::make_unique<PmpAddrRegister>(reg_addr, &register_map_));
} else {
register_map_.push_back(
std::make_unique<NonImpRegister>(reg_addr, &register_map_));
}
}
// mcountinhibit
// - MSBs are always 1: unused counters cannot be enabled
// - Bit 1 is always 0: time cannot be disabled
uint32_t mcountinhibit_mask =
(~((0x1 << params->MHPMCounterNum) - 1) << 3) | 0x2;
uint32_t mcountinhibit_resval = ~((0x1 << params->MHPMCounterNum) - 1) << 3;
register_map_.push_back(std::make_unique<WARLRegister>(
0x320, &register_map_, mcountinhibit_mask, mcountinhibit_resval));
// Performance counter setup
for (unsigned int i = 3; i < 32; i++) {
uint32_t reg_addr = 0x320 + i;
if (i < (params->MHPMCounterNum + 3)) {
register_map_.push_back(std::make_unique<WARLRegister>(
reg_addr, &register_map_, 0xFFFFFFFF, 0x1 << i));
} else {
register_map_.push_back(
std::make_unique<NonImpRegister>(reg_addr, &register_map_));
}
}
// mcycle
register_map_.push_back(
std::make_unique<BaseRegister>(0xB00, &register_map_));
// minstret
register_map_.push_back(
std::make_unique<BaseRegister>(0xB02, &register_map_));
// Generate masks from counter width parameter
uint32_t mhpmcounter_mask_low, mhpmcounter_mask_high;
if (params->MHPMCounterWidth >= 64) {
mhpmcounter_mask_low = 0x0;
mhpmcounter_mask_high = 0x0;
} else {
uint64_t mask = ~((0x1L << params->MHPMCounterWidth) - 1);
mhpmcounter_mask_low = mask & 0xFFFFFFFF;
mhpmcounter_mask_high = mask >> 32;
}
// Performance counter low word
for (unsigned int i = 3; i < 32; i++) {
uint32_t reg_addr = 0xB00 + i;
if (i < (params->MHPMCounterNum + 3)) {
register_map_.push_back(std::make_unique<WARLRegister>(
reg_addr, &register_map_, mhpmcounter_mask_low, 0));
} else {
register_map_.push_back(
std::make_unique<NonImpRegister>(reg_addr, &register_map_));
}
}
// mcycleh
register_map_.push_back(
std::make_unique<BaseRegister>(0xB80, &register_map_));
// minstreth
register_map_.push_back(
std::make_unique<BaseRegister>(0xB82, &register_map_));
// Performance counter high word
for (unsigned int i = 3; i < 32; i++) {
uint32_t reg_addr = 0xB80 + i;
if (i < (params->MHPMCounterNum + 3)) {
register_map_.push_back(std::make_unique<WARLRegister>(
reg_addr, &register_map_, mhpmcounter_mask_high, 0));
} else {
register_map_.push_back(
std::make_unique<NonImpRegister>(reg_addr, &register_map_));
}
}
}
void RegisterModel::RegisterReset() {
for (auto it = register_map_.begin(); it != register_map_.end(); ++it) {
(*it)->RegisterReset();
}
}
void RegisterModel::NewTransaction(std::unique_ptr<RegisterTransaction> trans) {
// TODO add machine mode permissions to registers
bool matched = false;
for (auto it = register_map_.begin(); it != register_map_.end(); ++it) {
if ((*it)->ProcessTransaction(&matched, trans.get())) {
simctrl_->RequestStop(false);
}
}
if (!matched) {
// Non existant register
if (!trans->illegal_csr) {
std::cout << "Non-existant register:" << std::endl;
trans->Print();
std::cout << "Should have signalled an error." << std::endl;
simctrl_->RequestStop(false);
}
}
}

View file

@ -1,32 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#ifndef REGISTER_MODEL_H_
#define REGISTER_MODEL_H_
#include <stdint.h>
#include <memory>
#include <vector>
#include "base_register.h"
#include "register_transaction.h"
#include "register_types.h"
#include "simctrl.h"
/**
* Class modelling CS register state and checking against RTL
*/
class RegisterModel {
public:
RegisterModel(SimCtrl *sc, CSRParams *params);
void NewTransaction(std::unique_ptr<RegisterTransaction> trans);
void RegisterReset();
private:
std::vector<std::unique_ptr<BaseRegister>> register_map_;
SimCtrl *simctrl_;
};
#endif // REGISTER_MODEL_H_

View file

@ -1,126 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// List all supported registers
#ifndef CSR
#error Define CSR
#endif
CSR(PMPCfg0, 0x3A0)
CSR(PMPCfg1, 0x3A1)
CSR(PMPCfg2, 0x3A2)
CSR(PMPCfg3, 0x3A3)
CSR(PMPAddr0, 0x3B0)
CSR(PMPAddr1, 0x3B1)
CSR(PMPAddr2, 0x3B2)
CSR(PMPAddr3, 0x3B3)
CSR(PMPAddr4, 0x3B4)
CSR(PMPAddr5, 0x3B5)
CSR(PMPAddr6, 0x3B6)
CSR(PMPAddr7, 0x3B7)
CSR(PMPAddr8, 0x3B8)
CSR(PMPAddr9, 0x3B9)
CSR(PMPAddr10, 0x3BA)
CSR(PMPAddr11, 0x3BB)
CSR(PMPAddr12, 0x3BC)
CSR(PMPAddr13, 0x3BD)
CSR(PMPAddr14, 0x3BE)
CSR(PMPAddr15, 0x3BF)
CSR(MCountInhibit, 0x320)
CSR(MHPMEvent3, 0x323)
CSR(MHPMEvent4, 0x324)
CSR(MHPMEvent5, 0x325)
CSR(MHPMEvent6, 0x326)
CSR(MHPMEvent7, 0x327)
CSR(MHPMEvent8, 0x328)
CSR(MHPMEvent9, 0x329)
CSR(MHPMEvent10, 0x32A)
CSR(MHPMEvent11, 0x32B)
CSR(MHPMEvent12, 0x32C)
CSR(MHPMEvent13, 0x32D)
CSR(MHPMEvent14, 0x32E)
CSR(MHPMEvent15, 0x32F)
CSR(MHPMEvent16, 0x330)
CSR(MHPMEvent17, 0x331)
CSR(MHPMEvent18, 0x332)
CSR(MHPMEvent19, 0x333)
CSR(MHPMEvent20, 0x334)
CSR(MHPMEvent21, 0x335)
CSR(MHPMEvent22, 0x336)
CSR(MHPMEvent23, 0x337)
CSR(MHPMEvent24, 0x338)
CSR(MHPMEvent25, 0x339)
CSR(MHPMEvent26, 0x33A)
CSR(MHPMEvent27, 0x33B)
CSR(MHPMEvent28, 0x33C)
CSR(MHPMEvent29, 0x33D)
CSR(MHPMEvent30, 0x33E)
CSR(MHPMEvent31, 0x33F)
CSR(MSeccfg, 0x747)
CSR(MSeccfgh, 0x757)
CSR(MCycle, 0xB00)
CSR(MInstret, 0xB02)
CSR(MHPMCounter3, 0xB03)
CSR(MHPMCounter4, 0xB04)
CSR(MHPMCounter5, 0xB05)
CSR(MHPMCounter6, 0xB06)
CSR(MHPMCounter7, 0xB07)
CSR(MHPMCounter8, 0xB08)
CSR(MHPMCounter9, 0xB09)
CSR(MHPMCounter10, 0xB0A)
CSR(MHPMCounter11, 0xB0B)
CSR(MHPMCounter12, 0xB0C)
CSR(MHPMCounter13, 0xB0D)
CSR(MHPMCounter14, 0xB0E)
CSR(MHPMCounter15, 0xB0F)
CSR(MHPMCounter16, 0xB10)
CSR(MHPMCounter17, 0xB11)
CSR(MHPMCounter18, 0xB12)
CSR(MHPMCounter19, 0xB13)
CSR(MHPMCounter20, 0xB14)
CSR(MHPMCounter21, 0xB15)
CSR(MHPMCounter22, 0xB16)
CSR(MHPMCounter23, 0xB17)
CSR(MHPMCounter24, 0xB18)
CSR(MHPMCounter25, 0xB19)
CSR(MHPMCounter26, 0xB1A)
CSR(MHPMCounter27, 0xB1B)
CSR(MHPMCounter28, 0xB1C)
CSR(MHPMCounter29, 0xB1D)
CSR(MHPMCounter30, 0xB1E)
CSR(MHPMCounter31, 0xB1F)
CSR(MCycleH, 0xB80)
CSR(MInstretH, 0xB82)
CSR(MHPMCounter3H, 0xB83)
CSR(MHPMCounter4H, 0xB84)
CSR(MHPMCounter5H, 0xB85)
CSR(MHPMCounter6H, 0xB86)
CSR(MHPMCounter7H, 0xB87)
CSR(MHPMCounter8H, 0xB88)
CSR(MHPMCounter9H, 0xB89)
CSR(MHPMCounter10H, 0xB8A)
CSR(MHPMCounter11H, 0xB8B)
CSR(MHPMCounter12H, 0xB8C)
CSR(MHPMCounter13H, 0xB8D)
CSR(MHPMCounter14H, 0xB8E)
CSR(MHPMCounter15H, 0xB8F)
CSR(MHPMCounter16H, 0xB90)
CSR(MHPMCounter17H, 0xB91)
CSR(MHPMCounter18H, 0xB92)
CSR(MHPMCounter19H, 0xB93)
CSR(MHPMCounter20H, 0xB94)
CSR(MHPMCounter21H, 0xB95)
CSR(MHPMCounter22H, 0xB96)
CSR(MHPMCounter23H, 0xB97)
CSR(MHPMCounter24H, 0xB98)
CSR(MHPMCounter25H, 0xB99)
CSR(MHPMCounter26H, 0xB9A)
CSR(MHPMCounter27H, 0xB9B)
CSR(MHPMCounter28H, 0xB9C)
CSR(MHPMCounter29H, 0xB9D)
CSR(MHPMCounter30H, 0xB9E)
CSR(MHPMCounter31H, 0xB9F)
#undef CSR

View file

@ -1,52 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "register_driver.h"
#include "svdpi.h"
#include <map>
#include <string>
#ifdef __cplusplus
extern "C" {
#endif
static std::map<std::string, RegisterDriver *> intfs;
void reg_register_intf(std::string name, RegisterDriver *intf) {
intfs.insert({name, intf});
}
void reg_deregister_intf(std::string name) { intfs.erase(name); }
void monitor_tick(const char *name, svBit rst_n, svBit illegal_csr,
svBit csr_access, const svBitVecVal *csr_op, svBit csr_op_en,
const svBitVecVal *csr_addr, const svBitVecVal *csr_wdata,
const svBitVecVal *csr_rdata) {
auto ptr = intfs.find(name);
if (ptr != intfs.end()) {
// Send inputs to monitor
if ((csr_access && (csr_op_en || illegal_csr)) || !rst_n) {
ptr->second->CaptureTransaction(rst_n, illegal_csr, *csr_op, *csr_addr,
*csr_rdata, *csr_wdata);
}
}
}
void driver_tick(const char *name, svBit *csr_access, svBitVecVal *csr_op,
svBit *csr_op_en, svBitVecVal *csr_addr,
svBitVecVal *csr_wdata) {
auto ptr = intfs.find(name);
if (ptr != intfs.end()) {
// Call OnClock method
ptr->second->OnClock();
// Drive outputs
ptr->second->DriveOutputs(csr_access, csr_op, csr_op_en, csr_addr,
csr_wdata);
}
}
#ifdef __cplusplus
}
#endif

View file

@ -1,29 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
package reg_dpi;
import "DPI-C"
function void monitor_tick (
input string name,
input bit rst_n,
input bit illegal_csr,
input bit csr_access,
input bit [1:0] csr_op,
input bit csr_op_en,
input bit [11:0] csr_addr,
input bit [31:0] csr_wdata,
input bit [31:0] csr_rdata);
import "DPI-C"
function void driver_tick (
input string name,
output bit csr_access,
output bit [1:0] csr_op,
output bit csr_op_en,
output bit [11:0] csr_addr,
output bit [31:0] csr_wdata);
endpackage

View file

@ -1,78 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "register_driver.h"
#include <iostream>
extern "C" void reg_register_intf(std::string name, RegisterDriver *intf);
extern "C" void reg_deregister_intf(std::string name);
RegisterDriver::RegisterDriver(std::string name, RegisterModel *model,
SimCtrl *sc)
: name_(name), reg_model_(model), simctrl_(sc) {}
void RegisterDriver::OnInitial(unsigned int seed) {
transactions_driven_ = 0;
delay_ = 1;
reg_access_ = false;
generator_.seed(seed);
delay_dist_ = std::uniform_int_distribution<int>(1, 20);
reg_register_intf(name_, this);
}
void RegisterDriver::OnFinal() {
reg_deregister_intf(name_);
std::cout << "[Reg driver] drove: " << transactions_driven_
<< " register transactions" << std::endl;
}
void RegisterDriver::Randomize() {
// generate new transaction
next_transaction_.Randomize(generator_);
// generate new delay
delay_ = delay_dist_(generator_);
reg_access_ = true;
}
void RegisterDriver::CaptureTransaction(unsigned char rst_n,
unsigned char illegal_csr, uint32_t op,
uint32_t addr, uint32_t rdata,
uint32_t wdata) {
if (!rst_n) {
reg_model_->RegisterReset();
} else {
auto trans = std::make_unique<RegisterTransaction>();
trans->illegal_csr = illegal_csr;
trans->csr_op = (CSRegisterOperation)op;
trans->csr_addr = addr;
trans->csr_rdata = rdata;
trans->csr_wdata = wdata;
// Ownership of trans is passed to the model
reg_model_->NewTransaction(std::move(trans));
}
}
void RegisterDriver::DriveOutputs(unsigned char *access, uint32_t *op,
unsigned char *csr_op_en, uint32_t *addr,
uint32_t *wdata) {
*access = reg_access_;
*csr_op_en = reg_access_;
*op = next_transaction_.csr_op;
*addr = next_transaction_.csr_addr;
*wdata = next_transaction_.csr_wdata;
}
void RegisterDriver::OnClock() {
if (transactions_driven_ >= 10000) {
simctrl_->RequestStop(true);
}
if (--delay_ == 0) {
Randomize();
++transactions_driven_;
} else {
reg_access_ = false;
}
}

View file

@ -1,50 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#ifndef REGISTER_DRIVER_H_
#define REGISTER_DRIVER_H_
#include "register_model.h"
#include "register_transaction.h"
#include "simctrl.h"
#include <random>
#include <string>
/**
* Class to randomize and drive CS register reads/writes
*/
class RegisterDriver {
public:
RegisterDriver(std::string name, RegisterModel *model, SimCtrl *sc);
void OnInitial(unsigned int seed);
void OnClock();
void OnFinal();
void CaptureTransaction(unsigned char rst_n, unsigned char illegal_csr,
uint32_t op, uint32_t addr, uint32_t rdata,
uint32_t wdata);
void DriveOutputs(unsigned char *access, uint32_t *op, unsigned char *op_en,
uint32_t *addr, uint32_t *wdata);
private:
void Randomize();
std::default_random_engine generator_;
int delay_;
bool reg_access_;
CSRegisterOperation reg_op_;
std::uniform_int_distribution<int> delay_dist_;
uint32_t reg_addr_;
uint32_t reg_wdata_;
int transactions_driven_;
RegisterTransaction next_transaction_;
std::string name_;
RegisterModel *reg_model_;
SimCtrl *simctrl_;
};
#endif // REGISTER_DRIVER_H_

View file

@ -1,62 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "register_transaction.h"
#include <iostream>
void RegisterTransaction::Randomize(std::default_random_engine &gen) {
std::uniform_int_distribution<int> addr_dist_ =
std::uniform_int_distribution<int>(
0, (sizeof(CSRAddresses) / sizeof(uint16_t)) - 1);
std::uniform_int_distribution<int> wdata_dist_ =
std::uniform_int_distribution<int>(0, 0xFFFFFFFF);
std::uniform_int_distribution<int> operation_dist_ =
std::uniform_int_distribution<int>(kCSRRead, kCSRClear);
// Generate a random array index, and get the address
csr_addr = CSRAddresses[addr_dist_(gen)];
// Generate a random op type
csr_op = static_cast<CSRegisterOperation>(operation_dist_(gen));
if (csr_op != kCSRRead) {
// Generate random wdata
csr_wdata = wdata_dist_(gen);
}
}
void RegisterTransaction::Print() {
std::cout << "Register transaction:" << std::endl
<< "Operation: " << RegOpString() << std::endl
<< "Address: " << RegAddrString() << std::endl;
if (csr_op != kCSRRead) {
std::cout << "Write data: " << std::hex << csr_wdata << std::endl;
}
std::cout << "Read data: " << std::hex << csr_rdata << std::dec << std::endl;
}
std::string RegisterTransaction::RegOpString() {
switch (csr_op) {
case kCSRRead:
return "CSR Read";
case kCSRWrite:
return "CSR Write";
case kCSRSet:
return "CSR Set";
case kCSRClear:
return "CSR Clear";
default:
return "Unknown op";
}
}
std::string RegisterTransaction::RegAddrString() {
// String representation created automatically by macro
switch (csr_addr) {
#define CSR(reg, addr) \
case kCSR##reg: \
return #reg;
#include "csr_listing.def"
default:
return "Undef reg: " + std::to_string(csr_addr);
}
}

View file

@ -1,53 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#ifndef REGISTER_TRANSACTION_H_
#define REGISTER_TRANSACTION_H_
#include <stdint.h>
#include <random>
#include <string>
// Enumerate the supported register types by macro expansion
enum CSRegisterAddr : int {
#define CSR(reg, addr) kCSR##reg = addr,
#include "csr_listing.def"
};
// Individual bits for MSECCFG CSR
const int kMSeccfgMml = 0x1;
const int kMSeccfgMmwp = 0x2;
const int kMSeccfgRlb = 0x4;
// Create an indexable array of all CSR addresses
static const uint16_t CSRAddresses[] = {
#define CSR(reg, addr) addr,
#include "csr_listing.def"
};
// Enumerate the four register operation types
enum CSRegisterOperation : int {
kCSRRead = 0,
kCSRWrite = 1,
kCSRSet = 2,
kCSRClear = 3
};
struct RegisterTransaction {
public:
void Randomize(std::default_random_engine &gen);
void Print();
CSRegisterOperation csr_op;
bool illegal_csr;
uint32_t csr_addr;
uint32_t csr_rdata;
uint32_t csr_wdata;
private:
std::string RegOpString();
std::string RegAddrString();
};
#endif // REGISTER_TRANSACTION_H_

View file

@ -1,34 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "reset_driver.h"
extern "C" void rst_register_intf(std::string name, ResetDriver *intf);
extern "C" void rst_deregister_intf(std::string name);
ResetDriver::ResetDriver(std::string name)
: reset_delay_(1), reset_duration_(0), name_(name) {}
void ResetDriver::OnInitial(unsigned int seed) {
generator_.seed(seed);
// 100 to 1000 cycles between resets
delay_dist_ = std::uniform_int_distribution<int>(100, 1000);
rst_register_intf(name_, this);
}
void ResetDriver::OnFinal() { rst_deregister_intf(name_); }
void ResetDriver::DriveReset(unsigned char *rst_n) {
reset_delay_--;
if (reset_delay_ == 0) {
reset_delay_ = delay_dist_(generator_);
reset_duration_ = 0;
}
if (reset_duration_ < 3) {
reset_duration_++;
*rst_n = false;
} else {
*rst_n = true;
}
}

View file

@ -1,29 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#ifndef RESET_DRIVER_H_
#define RESET_DRIVER_H_
#include <random>
#include <string>
/**
* Class to randomize and drive reset signals
*/
class ResetDriver {
public:
ResetDriver(std::string name);
void OnInitial(unsigned int seed);
void OnFinal();
void DriveReset(unsigned char *rst_n);
private:
int reset_delay_;
int reset_duration_;
std::string name_;
std::default_random_engine generator_;
std::uniform_int_distribution<int> delay_dist_;
};
#endif // RESET_DRIVER_H_

View file

@ -1,33 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "register_environment.h"
#include "svdpi.h"
#include <map>
#include <string>
#ifdef __cplusplus
extern "C" {
#endif
static std::map<std::string, ResetDriver *> intfs;
void rst_register_intf(std::string name, ResetDriver *intf) {
intfs.insert({name, intf});
}
void rst_deregister_intf(std::string name) { intfs.erase(name); }
void rst_tick(const char *name, svBit *rst_n) {
auto ptr = intfs.find(name);
if (ptr != intfs.end()) {
ptr->second->DriveReset(rst_n);
}
}
#ifdef __cplusplus
}
#endif

View file

@ -1,12 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
package rst_dpi;
import "DPI-C"
function void rst_tick (
input string name,
output bit rst_n);
endpackage

View file

@ -1,26 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "verilated_toplevel.h"
#include "verilator_sim_ctrl.h"
int main(int argc, char **argv) {
tb_cs_registers top;
VerilatorSimCtrl &simctrl = VerilatorSimCtrl::GetInstance();
simctrl.SetTop(&top, &top.clk_i, &top.in_rst_ni,
VerilatorSimCtrlFlags::ResetPolarityNegative);
// Get pass / fail from Verilator
auto pr = simctrl.Exec(argc, argv);
int ret_code = pr.first;
bool ran_simulation = pr.second;
if (ret_code != 0 || !ran_simulation) {
return ret_code;
}
// Get pass / fail from testbench
return !top.test_passed_o;
}

View file

@ -1,151 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
module tb_cs_registers #(
parameter bit DbgTriggerEn = 1'b0,
parameter bit ICache = 1'b0,
parameter int unsigned MHPMCounterNum = 8,
parameter int unsigned MHPMCounterWidth = 40,
parameter bit PMPEnable = 1'b0,
parameter int unsigned PMPGranularity = 0,
parameter int unsigned PMPNumRegions = 4,
parameter bit RV32E = 1'b0,
parameter ibex_pkg::rv32m_e RV32M = ibex_pkg::RV32MFast,
parameter ibex_pkg::rv32b_e RV32B = ibex_pkg::RV32BNone
) (
// Clock and Reset
inout logic clk_i,
inout logic in_rst_ni,
output logic test_passed_o
);
logic dpi_rst_ni;
logic rst_ni;
// Interface to registers (SRAM like)
logic csr_access_i;
ibex_pkg::csr_num_e csr_addr_i;
logic [31:0] csr_wdata_i;
ibex_pkg::csr_op_e csr_op_i;
logic csr_op_en_i;
logic [31:0] csr_rdata_o;
logic illegal_csr_insn_o;
logic csr_access_d;
ibex_pkg::csr_num_e csr_addr_d;
logic [31:0] csr_wdata_d;
ibex_pkg::csr_op_e csr_op_d;
logic csr_op_en_d;
//-----------------
// Reset generation
//-----------------
// Allow reset to be toggled by the top-level (in Verilator)
// or a DPI call
assign rst_ni = in_rst_ni & dpi_rst_ni;
//----------------------------------------
// Clock generation (not used in Verilator
//----------------------------------------
`ifndef VERILATOR
logic local_clk_i;
initial begin
local_clk_i = 1'b0;
while (1) begin
#10
local_clk_i = !local_clk_i;
end
end
assign clk_i = local_clk_i;
assign in_rst_ni = 1'b1;
`endif
/* verilator lint_off PINMISSING */
ibex_cs_registers #(
.DbgTriggerEn (DbgTriggerEn),
.ICache (ICache),
.MHPMCounterNum (MHPMCounterNum),
.MHPMCounterWidth (MHPMCounterWidth),
.PMPEnable (PMPEnable),
.PMPGranularity (PMPGranularity),
.PMPNumRegions (PMPNumRegions),
.RV32E (RV32E),
.RV32M (RV32M),
.RV32B (RV32B)
) i_cs_regs (
.clk_i (clk_i),
.rst_ni (rst_ni),
.csr_access_i (csr_access_i),
.csr_addr_i (csr_addr_i),
.csr_wdata_i (csr_wdata_i),
.csr_op_i (csr_op_i),
.csr_op_en_i (csr_op_en_i),
.csr_rdata_o (csr_rdata_o),
.illegal_csr_insn_o (illegal_csr_insn_o)
);
/* verilator lint_on PINMISSING */
// DPI calls
bit stop_simulation;
bit test_passed;
bit [31:0] seed;
initial begin
if (!$value$plusargs ("ntb_random_seed=%d", seed)) begin
seed = 32'd0;
end
env_dpi::env_initial(seed,
PMPEnable, PMPGranularity, PMPNumRegions,
MHPMCounterNum, MHPMCounterWidth);
end
final begin
env_dpi::env_final();
end
always_ff @(posedge clk_i) begin
env_dpi::env_tick(stop_simulation, test_passed);
rst_dpi::rst_tick("rstn_driver", dpi_rst_ni);
if (stop_simulation) begin
$finish();
end
end
// Signal test pass / fail as an output (Verilator sims can pick this up and use it as a
// return code)
assign test_passed_o = test_passed;
always_ff @(posedge clk_i or negedge rst_ni) begin
reg_dpi::monitor_tick("reg_driver",
rst_ni,
illegal_csr_insn_o,
csr_access_i,
csr_op_i,
csr_op_en_i,
csr_addr_i,
csr_wdata_i,
csr_rdata_o);
reg_dpi::driver_tick("reg_driver",
csr_access_d,
csr_op_d,
csr_op_en_d,
csr_addr_d,
csr_wdata_d);
// Use NBA to drive inputs to ensure correct scheduling.
// This always_ff block will be executed on the positive edge of the clock with undefined order
// vs all other always_ff triggered on the positive edge of the clock. If `driver_tick` drives
// the inputs directly some of the always_ff blocks will see the old version of the inputs and
// others will see the new version depending on scheduling order. This schedules all the inputs
// to be NBA updates to avoid the race condition (in effect acting like any other always_ff
// block with the _d values being computed via DPI rather than combinational logic).
csr_access_i <= csr_access_d;
csr_addr_i <= csr_addr_d;
csr_wdata_i <= csr_wdata_d;
csr_op_i <= csr_op_d;
csr_op_en_i <= csr_op_en_d;
end
endmodule

View file

@ -1,127 +0,0 @@
CAPI=2:
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
name: "lowrisc:ibex:tb_cs_registers"
description: "CS registers testbench"
filesets:
files_so:
files:
- Makefile
- rst_driver/rst_dpi.cc
- rst_driver/reset_driver.cc
- rst_driver/reset_driver.h
- reg_driver/csr_listing.def
- reg_driver/reg_dpi.cc
- reg_driver/register_driver.cc
- reg_driver/register_driver.h
- reg_driver/register_transaction.cc
- reg_driver/register_transaction.h
- env/env_dpi.cc
- env/register_environment.cc
- env/register_environment.h
- env/simctrl.cc
- env/simctrl.h
- env/register_types.h
- model/base_register.cc
- model/base_register.h
- model/register_model.cc
- model/register_model.h
file_type: user
files_verilator:
depend:
- lowrisc:dv_verilator:simutil_verilator
files:
- tb/tb_cs_registers.cc: { file_type: cppSource }
- lint/verilator_waiver.vlt: {file_type: vlt}
files_sim:
depend:
- lowrisc:ibex:ibex_core
files:
- env/env_dpi.sv
- rst_driver/rst_dpi.sv
- reg_driver/reg_dpi.sv
- tb/tb_cs_registers.sv
file_type: systemVerilogSource
# Call make to build C++ shared object (workaround until natively supported by
# fusesoc) see olofk/fusesoc#311
scripts:
build_so:
filesets:
- files_so
cmd:
- make
- -C
- ../src/lowrisc_ibex_tb_cs_registers_0
parameters:
PMPEnable:
datatype: int
paramtype: vlogparam
default: 1
description: PMP enabled [1/0]
PMPNumRegions:
datatype: int
paramtype: vlogparam
default: 4
description: Number of implemented PMP regions [0/16]
PMPGranularity:
datatype: int
paramtype: vlogparam
default: 0
description: Minimum PMP matching granularity [0/31]
MHPMCounterNum:
datatype: int
paramtype: vlogparam
default: 8
description: Number of performance monitor event counters [0/29]
MHPMCounterWidth:
datatype: int
paramtype: vlogparam
default: 40
description: Bit width of performance monitor event counters [32/64]
targets:
sim:
default_tool: verilator
toplevel: tb_cs_registers
filesets:
- files_sim
- tool_verilator ? (files_verilator)
hooks:
pre_build:
- build_so
parameters:
- PMPEnable
- PMPNumRegions
- PMPGranularity
- MHPMCounterNum
- MHPMCounterWidth
tools:
vcs:
vcs_options:
- '-xlrm uniq_prior_final'
- '../src/lowrisc_ibex_tb_cs_registers_0/build/bin/reg_dpi.so'
- '-debug_access+all'
verilator:
mode: cc
libs:
- '../src/lowrisc_ibex_tb_cs_registers_0/build/bin/reg_dpi.so'
verilator_options:
# Disabling tracing reduces compile times but doesn't have a
# huge influence on runtime performance.
- '--trace'
- '--trace-fst' # this requires -DVM_TRACE_FMT_FST in CFLAGS below!
- '--trace-structs'
- '--trace-params'
- '--trace-max-array 1024'
- '-CFLAGS "-std=c++14 -Wall -DTOPLEVEL_NAME=tb_cs_registers -DVM_TRACE_FMT_FST -g"'
- '-LDFLAGS "-pthread -lutil -lelf"'
- "-Wall"
- '-Wno-fatal' # Do not fail on (style) issues, only warn about them.

View file

@ -1,11 +0,0 @@
# Bus parameters for DV utilities
The Ibex DV code uses a `dv_utils` support library vendored from
OpenTitan. This library needs some basic parameters: things like
bus address and data widths and similar.
The `dv_utils` library is supposed to be parametric, in that it should
work with any such widths, but a project that imports the library
needs to supply them in a SystemVerilog package called
`bus_params_pkg`. This directory contains that package, which can be
imported with the fusesoc core `lowrisc:ibex:bus_params_pkg`.

View file

@ -1,24 +0,0 @@
CAPI=2:
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
#
# An Ibex-specific replacement for OpenTitan's top_pkg. This should
# *only* be used by Ibex DV code: it defines a (SystemVerilog) package
# with the same name as OpenTitan's top_pkg, so things will get very
# confused if both are used at once.
#
name: "lowrisc:ibex:bus_params_pkg"
description: "Top-level constants for Ibex, used by DV code"
filesets:
files_rtl:
files:
- bus_params_pkg.sv
file_type: systemVerilogSource
targets:
default:
filesets:
- files_rtl

View file

@ -1,29 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
package bus_params_pkg;
// Bus address width
localparam int BUS_AW = 32;
// Bus data width (must be a multiple of 8)
localparam int BUS_DW = 32;
// Bus data mask width (number of byte lanes)
localparam int BUS_DBW = (BUS_DW >> 3);
// Bus transfer size width (number of bits needed to select the number of bytes)
localparam int BUS_SZW = $clog2($clog2(BUS_DBW) + 1);
// Bus address info (source) width
localparam int BUS_AIW = 8;
// Bus data info (source) width
localparam int BUS_DIW = 1;
// Bus data user width
localparam int BUS_DUW = 16;
endpackage

View file

@ -1,18 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
{
project: ibex
// These keys are expected by dvsim.py, so we have to set them to something.
doc_server: bogus.doc.server
results_server: bogus.results.server
// Default directory structure for the output
scratch_base_path: "{scratch_root}/{dut}.{flow}.{tool}"
scratch_path: "{scratch_base_path}/{branch}"
tool_srcs_dir: "{scratch_path}/{tool}"
// The current design level
design_level: "ip"
}

View file

@ -1,490 +0,0 @@
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
GEN_DIR := $(realpath ../../../vendor/google_riscv-dv)
TOOLCHAIN := ${RISCV_TOOLCHAIN}
export IBEX_ROOT := $(realpath ../../../)
ifeq ($(COSIM),1)
ifndef IBEX_COSIM_ISS_ROOT
$(error IBEX_COSIM_ISS_ROOT must be set to the root of a suitable spike build if COSIM=1)
else
# Spike builds a libsoftfloat.so shared library that the simulator binary needs.
# Set LD_LIBRARY_PATH so it can be found.
export LD_LIBRARY_PATH := $(IBEX_COSIM_ISS_ROOT)/lib/:${LD_LIBRARY_PATH}
endif
endif
# Explicitly ask for the bash shell
SHELL := bash
# Seed for instruction generator and RTL simulation
#
# By default, SEED is set to a different value on each run by picking a random
# value in the Makefile. For overnight testing, a sensible seed might be
# something like the output of "date +%y%m%d". For regression testing, you'll
# need to make sure that a the seed for a failed test "sticks" (so we don't
# start passing again without fixing the bug).
SEED := $(shell echo $$RANDOM)
# This is the top-level output directory. Everything we generate goes in
# here. Most generated stuff actually goes in $(OUT)/seed-$(SEED), which allows
# us to run multiple times without deleting existing results.
OUT := out
OUT-SEED := $(OUT)/seed-$(SEED)
# Run time options for the instruction generator
GEN_OPTS :=
# Compile time options for ibex RTL simulation
COMPILE_OPTS +=
# Run time options for ibex RTL simulation
SIM_OPTS :=
# Enable waveform dumping
WAVES := 1
# Enable coverage dump
COV := 0
# RTL simulator
SIMULATOR := vcs
# ISS (spike, ovpsim)
ISS := spike
# ISS runtime options
ISS_OPTS :=
# ISA
ISA := rv32imcb
ISA_ISS := rv32imc_Zba_Zbb_Zbc_Zbs_Xbitmanip
# Test name (default: full regression)
TEST := all
TESTLIST := riscv_dv_extension/testlist.yaml
# Verbose logging
VERBOSE :=
# Number of iterations for each test, assign a non-zero value to override the
# iteration count in the test list
ITERATIONS := 0
# LSF CMD
LSF_CMD :=
# Generator timeout limit in seconds
TIMEOUT := 1800
# Privileged CSR YAML description file
CSR_FILE := riscv_dv_extension/csr_description.yaml
# Pass/fail signature address at the end of test
SIGNATURE_ADDR := 8ffffffc
### Ibex top level parameters ###
### Required by RISCV-DV, some ISS, and RTL ###
# PMP Regions
PMP_REGIONS := 16
# PMP Granularity
PMP_GRANULARITY := 0
IBEX_CONFIG := opentitan
# TODO(udinator) - might need options for SAIL/Whisper/Spike
ifeq (${ISS},ovpsim)
ISS_OPTS += --override riscvOVPsim/cpu/PMP_registers=${PMP_REGIONS}
ISS_OPTS += --override riscvOVPsim/cpu/PMP_grain=${PMP_GRANULARITY}
endif
# A version of $(OUT) with a trailing '/'. The point is that this will
# never match the name of a phony targets like "sim" (which causes
# strange rebuilds otherwise). The call to $(dir ) avoids adding
# another trailing slash if $(OUT) had one already.
OUT-DIR := $(dir $(OUT)/)
# This expands to '@' if VERBOSE is 0 or not set, and to the empty
# string otherwise. Prefix commands with it in order that they only
# get printed when VERBOSE.
verb = $(if $(filter-out 0,$(VERBOSE)),,@)
SHELL=/bin/bash
export PRJ_DIR:= $(realpath ../../..)
all: sim
instr: iss_sim
sim: post_compare $(if $(filter 1,$(COV)),gen_cov,)
.PHONY: clean
clean:
rm -rf $(OUT-DIR)
# Common options for all targets
COMMON_OPTS := $(if $(call equal,$(VERBOSE),1),--verbose,)
# Options for all targets that depend on the tests we're running.
TEST_OPTS := $(COMMON_OPTS) \
--start_seed=${SEED} \
--test="${TEST}" \
--testlist=${TESTLIST} \
--iterations=${ITERATIONS}
# Options used for privileged CSR test generation
CSR_OPTS=--csr_yaml=${CSR_FILE} \
--isa="${ISA}" \
--end_signature_addr=${SIGNATURE_ADDR}
RISCV_DV_OPTS=--custom_target=riscv_dv_extension \
--mabi=ilp32 \
# To avoid cluttering the output directory with stamp files, we place them in
# $(metadata).
metadata := $(OUT-SEED)/.metadata
# This is a list of directories that are automatically generated by some
# targets. To ensure the directory has been built, add a order-only dependency
# (with the pipe symbol before it) on the directory name and add the directory
# to this list.
gen-dirs := $(OUT-DIR) $(OUT-SEED) $(metadata) $(OUT-DIR)rtl_sim
$(gen-dirs): %:
mkdir -p $@
# sim-cfg-mk is a makefile fragment that sets-up anything simulator specific, it
# is generated by sim_makefrag_gen.py
sim-cfg-mk = $(OUT-DIR).sim-cfg.mk
# The include of $(sim-cfg-mk) below tells Make that it should ensure the file
# exists. This rule tells Make how to build it. We also want to ensure it's
# built on every run. Declaring the rule to be .PHONY doesn't work because it
# causes Make to no longer think the rule actually generates the file. If you
# do that, the file gets re-built, but Make doesn't read the new contents. So
# instead we depend on FORCE (a phony target). This ensures a rebuild, but also
# causes an infinite recursion: when we re-read this file to include the new
# contents of $(sim-cfg-mk), we run the rule again. To avoid that, we check for
# MAKE_RESTARTS, which is defined on re-runs. Phew!
ifndef MAKE_RESTARTS
$(sim-cfg-mk): FORCE | $(OUT-DIR)
@./sim_makefrag_gen.py $(SIMULATOR) $(IBEX_CONFIG) $(PRJ_DIR) $@
endif
include $(sim-cfg-mk)
.PHONY: test-cfg
test-cfg:
@echo "COMPILE_OPTS" $(COMPILE_OPTS)
@echo "SIM_OPTS" $(SIM_OPTS)
###############################################################################
# Utility functions.
#
# If VS is a list of variable names, P is a path and X is a string, then $(call
# dump-vars,P,X,VS) will expand to a list of 'file' commands that write each
# variable to P in Makefile syntax, but with "last-X-" prepended. At the start
# of the file, we also define last-X-vars-loaded to 1. You can use this to
# check whether there was a dump file at all.
#
# Note that this doesn't work by expanding to a command. Instead, *evaluating*
# dump-vars causes the variables to be dumped.
dump-var = $(file >>$(1),last-$(2)-$(3) := $($(3)))
dump-vars = $(file >$(1),last-$(2)-vars-loaded := .) \
$(foreach name,$(3),$(call dump-var,$(1),$(2),$(name)))
# equal checks whether two strings are equal, evaluating to '.' if they are and
# '' otherwise.
both-empty = $(if $(1),,$(if $(2),,.))
find-find = $(if $(and $(findstring $(1),$(2)),$(findstring $(2),$(1))),.,)
equal = $(or $(call both-empty,$(1),$(2)),$(call find-find,$(1),$(2)))
# var-differs is used to check whether a variable has changed since it was
# dumped. If it has changed, the function evaluates to '.' (with some
# whitespace) and prints a message to the console; if not, it evaluates to ''.
#
# Call it as $(call var-differs,X,TGT,V).
var-differs = \
$(if $(call equal,$(strip $($(3))),$(strip $(last-$(1)-$(3)))),,\
.$(info Repeating $(2) because variable $(3) has changed value.))
# vars-differ is used to check whether several variables have the same value as
# they had when they were dumped. If we haven't loaded the dumpfile, it
# silently evaluates to '!'. Otherwise, if all the variables match, it
# evaluates to '.'. If not, it evaluates to '.' and prints some messages to the
# console explaining why a rebuild is happening.
#
# Call it as $(call vars-differ,X,TGT,VS).
vars-differ-lst = $(foreach v,$(3),$(call var-differs,$(1),$(2),$(v)))
vars-differ-sp = \
$(if $(last-$(1)-vars-loaded),\
$(if $(strip $(call vars-differ-lst,$(1),$(2),$(3))),.,),\
!)
vars-differ = $(strip $(call vars-differ-sp,$(1),$(2),$(3)))
# A phony target which can be used to force recompilation.
.PHONY: FORCE
FORCE:
# vars-prereq is empty if every variable in VS matches the last run (loaded
# with tag X), otherwise it is set to FORCE (which will force a recompile and
# might print a message to the console explaining why we're rebuilding TGT).
#
# Call it as $(call vars-prereq,X,TGT,VS)
vars-prereq = $(if $(call vars-differ,$(1),$(2),$(3)),FORCE,)
###############################################################################
# Get a list of tests and seeds
#
# Run list_tests.py to list the things we need to run in the format
# TESTNAME.SEED and store it in a variable.
tests-and-seeds := \
$(shell ./list_tests.py \
--start_seed $(SEED) \
--test "$(TEST)" \
--iterations $(ITERATIONS) \
--ibex-config $(IBEX_CONFIG))
###############################################################################
# Generate random instructions
#
# This depends on the vendored in code in $(GEN_DIR). It also depends on the
# values of some variables (we want to regenerate things if, for example, the
# simulator changes). Since we're writing out to $(OUT-SEED), we don't have to
# depend on the value of SEED. However, we do have to make sure that the
# variables whose names are listed in $(gen-var-deps) haven't changed.
#
# To do this variable tracking, we dump each of the variables to a Makefile
# fragment and try to load it up the next time around.
gen-var-deps := GEN_OPTS SIMULATOR RISCV_DV_OPTS ISA CSR_OPTS \
SIGNATURE_ADDR PMP_REGIONS PMP_GRANULARITY TEST_OPTS
# Load up the generation stage's saved variable values. If this fails, that's
# no problem: we'll assume that the previous run either doesn't exist or
# something went wrong.
-include $(metadata)/gen-vars.mk
# gen-vars-prereq is empty if every variable in gen-var-deps matches the last run,
# otherwise it is set to FORCE (which will force a recompile). Note that we
# define it with '=', not ':=', so we don't evaluate it if we're not trying to
# run the gen target.
gen-vars-prereq = \
$(call vars-prereq,gen,building instruction generator,$(gen-var-deps))
# A variable containing a file list for the riscv-dv vendored-in module.
# Depending on these files gives a safe over-approximation that will ensure we
# rebuild things if that module changes.
#
# Note that this is defined with ":=". As a result, we'll always run the find
# command exactly once. Wasteful if we're trying to make clean, but much better
# than running it for every target otherwise.
risc-dv-files := $(shell find $(GEN_DIR) -type f)
# This actually runs the instruction generator. Note that the rule depends on
# the (phony) FORCE target if any variables have changed. If the rule actually
# runs, it starts by deleting any existing contents of $(OUT-SEED)/instr_gen.
$(metadata)/instr_gen.gen.stamp: \
$(gen-vars-prereq) $(risc-dv-files) $(TESTLIST) | $(metadata)
$(verb)rm -rf $(OUT-SEED)/instr_gen
$(verb)python3 ${GEN_DIR}/run.py \
--output=$(OUT-SEED)/instr_gen ${GEN_OPTS} \
--steps=gen \
--gen_timeout=${TIMEOUT} \
--lsf_cmd="${LSF_CMD}" \
--simulator="${SIMULATOR}" \
${RISCV_DV_OPTS} \
--isa=${ISA} \
${TEST_OPTS} \
${CSR_OPTS} \
--sim_opts="+uvm_set_inst_override=riscv_asm_program_gen,ibex_asm_program_gen,"uvm_test_top.asm_gen" \
+signature_addr=${SIGNATURE_ADDR} +pmp_num_regions=${PMP_REGIONS} \
+pmp_granularity=${PMP_GRANULARITY} +tvec_alignment=8"
$(call dump-vars,$(metadata)/gen-vars.mk,gen,$(gen-var-deps))
@touch $@
.PHONY: gen
gen: $(metadata)/instr_gen.gen.stamp
###############################################################################
# Compile the generated assembly programs
#
# We don't explicitly track dependencies on the RISCV toolchain, so this
# doesn't depend on anything more than the instr_gen stage did.
$(metadata)/instr_gen.compile.stamp: \
$(metadata)/instr_gen.gen.stamp $(TESTLIST)
$(verb)python3 ${GEN_DIR}/run.py \
--o=$(OUT-SEED)/instr_gen ${GEN_OPTS} \
--steps=gcc_compile \
${TEST_OPTS} \
--gcc_opts=-mno-strict-align \
${RISCV_DV_OPTS} \
--isa=${ISA} && \
touch $@
.PHONY: gcc_compile
gcc_compile: $(metadata)/instr_gen.compile.stamp
###############################################################################
# Run the instruction set simulator
#
# This (obviously) depends on having compiled the generated programs, so we
# don't have to worry about variables that affect the 'gen' stage. However, the
# ISS and ISS_OPTS variables do affect the output, so we need to dump them. See
# the 'gen' stage for more verbose explanations of how this works.
iss-var-deps := ISS ISS_OPTS
-include $(metadata)/iss-vars.mk
iss-vars-prereq = $(call vars-prereq,iss,running ISS,$(iss-var-deps))
$(metadata)/instr_gen.iss.stamp: \
$(iss-vars-prereq) $(TESTLIST) $(metadata)/instr_gen.compile.stamp
$(verb)python3 ${GEN_DIR}/run.py \
--o=$(OUT-SEED)/instr_gen ${GEN_OPTS} \
--steps=iss_sim \
${TEST_OPTS} \
--iss="${ISS}" \
--iss_opts="${ISS_OPTS}" \
--isa="${ISA_ISS}" \
${RISCV_DV_OPTS}
$(call dump-vars,$(metadata)/iss-vars.mk,iss,$(iss-var-deps))
@touch $@
.PHONY: iss_sim
iss_sim: $(metadata)/instr_gen.iss.stamp
###############################################################################
# Compile ibex core TB
#
# Note that (unlike everything else) this doesn't depend on the seed: the DUT
# doesn't depend on which test we're running!
#
# It does, however, depend on various variables. These are listed in
# compile-var-deps. See the 'gen' stage for more verbose explanations of how
# the variable dumping works.
#
# The compiled ibex testbench (obviously!) also depends on the design and the
# DV code. The clever way of doing this would be to look at a dependency
# listing generated by the simulator as a side-effect of doing the compile (a
# bit like using the -M flags with a C compiler). Unfortunately, that doesn't
# look like it's particularly easy, so we'll just depend on every .v, .sv or
# .svh file in the dv or rtl directories. Note that this variable is set with
# '=', rather than ':='. This means that we don't bother running the find
# commands unless we need the compiled testbench.
all-verilog = \
$(shell find ../../../rtl -name '*.v' -o -name '*.sv' -o -name '*.svh') \
$(shell find ../.. -name '*.v' -o -name '*.sv' -o -name '*.svh')
compile-var-deps := COMMON_OPTS SIMULATOR COV WAVES COMPILE_OPTS COSIM
-include $(OUT-DIR)rtl_sim/.compile-vars.mk
compile-vars-prereq = $(call vars-prereq,comp,compiling TB,$(compile-var-deps))
$(call dump-vars-match,$(compile-var-deps),comp)
cov-arg := $(if $(call equal,$(COV),1),--en_cov,)
wave-arg := $(if $(call equal,$(WAVES),1),--en_wave,)
cosim-arg := $(if $(call equal,$(COSIM),1),--en_cosim,)
lsf-arg := $(if $(LSF_CMD),--lsf_cmd="$(LSF_CMD)",)
$(OUT-DIR)rtl_sim/.compile.stamp: \
$(compile-vars-prereq) $(all-verilog) $(risc-dv-files) \
sim.py yaml/rtl_simulation.yaml \
| $(OUT-DIR)rtl_sim
$(verb)./sim.py \
--o=$(OUT-DIR) \
--steps=compile \
${COMMON_OPTS} \
--simulator="${SIMULATOR}" --simulator_yaml=yaml/rtl_simulation.yaml \
$(cov-arg) $(wave-arg) $(cosim-arg) $(lsf-arg) \
--cmp_opts="${COMPILE_OPTS}"
$(call dump-vars,$(OUT-DIR)rtl_sim/.compile-vars.mk,comp,$(compile-var-deps))
@touch $@
.PHONY: compile
compile: $(OUT-DIR)rtl_sim/.compile.stamp
###############################################################################
# Run ibex RTL simulation with generated programs
#
# Because we compile a TB once rather than for each seed, we have to copy in
# that directory before we start. We make this step (rather than actually
# running the test) dependent on having the right variables. That way, we'll
# correctly delete the sim directory and re-copy it if necessary.
#
# Note that the variables we depend on are gen-vars-prereq. We also depend on
# COV and WAVES, but these dependencies will come for free from the dependency
# on the compiled TB.
$(metadata)/rtl_sim.compile.stamp: \
$(gen-vars-prereq) $(risc-dv-files) $(OUT-DIR)rtl_sim/.compile.stamp
rm -rf $(OUT-SEED)/rtl_sim
cp -r $(OUT-DIR)rtl_sim $(OUT-SEED)
@touch $@
rtl-sim-dirs := $(addprefix $(OUT-SEED)/rtl_sim/,$(tests-and-seeds))
rtl-sim-logs := $(addsuffix /sim.log,$(rtl-sim-dirs))
$(rtl-sim-logs): \
%/sim.log: \
$(metadata)/rtl_sim.compile.stamp \
$(metadata)/instr_gen.compile.stamp \
run_rtl.py
@echo Running RTL simulation at $@
$(verb)mkdir -p $(@D)
$(verb)./run_rtl.py \
--simulator $(SIMULATOR) \
--simulator_yaml yaml/rtl_simulation.yaml \
$(cov-arg) $(wave-arg) $(lsf-arg) \
--start-seed $(SEED) \
--sim-opts="+signature_addr=${SIGNATURE_ADDR} ${SIM_OPTS}" \
--test-dot-seed $(notdir $*) \
--bin-dir $(OUT-SEED)/instr_gen/asm_test \
--rtl-sim-dir $(OUT-SEED)/rtl_sim
.PHONY: rtl_sim
rtl_sim: $(rtl-sim-logs)
###############################################################################
# Compare ISS and RTL sim results
#
# For a given TEST/SEED pair, the ISS and RTL logs appear at:
#
# $(OUT-SEED)/instr_gen/$(ISS)_sim/$(TEST).$(SEED).log
# $(OUT-SEED)/rtl_sim/$(TEST).$(SEED)/trace_core_00000000.log
#
# The comparison script compares these and writes to a result file at
#
# $(OUT-SEED)/rtl_sim/$(TEST).$(SEED)/test-result.yml
#
# with PASSED or FAILED, depending.
comp-results := $(addsuffix /test-result.yml,$(rtl-sim-dirs))
$(comp-results): \
%/test-result.yml: \
compare.py $(metadata)/instr_gen.iss.stamp $(rtl-sim-logs)
@echo Comparing traces for $*
$(verb)./compare.py \
--instr-gen-bin-dir $(OUT-SEED)/instr_gen/asm_test \
--iss $(ISS) \
--iss-log-dir $(OUT-SEED)/instr_gen/$(ISS)_sim \
--start-seed $(SEED) \
--test-dot-seed "$(notdir $*)" \
--output $@ \
--rtl-log-dir $(OUT-SEED)/rtl_sim/$(notdir $*)
$(OUT-SEED)/regr.log: collect_results.py $(comp-results)
@echo "Collecting up results (report at $@)"
$(verb)./collect_results.py -o $(@D) $(comp-results)
.PHONY: post_compare
post_compare: $(OUT-SEED)/regr.log
###############################################################################
# Generate RISCV-DV functional coverage
# TODO(udi) - add B extension
.PHONY: riscv_dv_cov
riscv_dv_fcov: $(rtl-sim-logs)
$(verb)python3 ${GEN_DIR}/cov.py \
--core ibex \
--dir ${OUT-SEED}/rtl_sim \
-o ${OUT-SEED}/fcov \
--isa rv32imcb \
--custom_target riscv_dv_extension
# Merge all output coverage directories into the <out>/rtl_sim directory
#
# Any coverage databases generated from the riscv_dv_fcov target will be merged as well.
.PHONY: gen_cov
gen_cov: riscv_dv_fcov
$(verb)rm -rf $(OUT-DIR)rtl_sim/test.vdb
$(verb)./sim.py --steps=cov --simulator="${SIMULATOR}" $(lsf-arg) --o="$(OUT-DIR)"
@if [ -d "test.vdb" ]; then \
mv -f test.vdb $(OUT-DIR)rtl_sim/; \
fi

View file

@ -1,220 +0,0 @@
#!/usr/bin/env python3
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
import argparse
import junit_xml
import os.path
import sys
import yaml
from test_run_result import (TestRunResult, test_run_result_fields,
check_test_run_result)
from typing import List, TextIO
def parse_test_run_result(path: str) -> TestRunResult:
try:
with open(path) as yaml_file:
test_run_result_dict = yaml.load(yaml_file, Loader=yaml.SafeLoader)
loaded_fields = test_run_result_dict.keys()
if set(loaded_fields) != set(test_run_result_fields):
raise RuntimeError(f'Error loading YAML at {path}: does not '
'contain the correct set of fields')
trr = TestRunResult(**test_run_result_dict)
try:
check_test_run_result(trr)
except AssertionError:
raise RuntimeError(f'Error loading YAML at path {path}: '
'field types were incorrect')
return trr
except (IOError, yaml.YAMLError) as e:
raise RuntimeError(f'Error loading YAML at path {path}: {e}')
def build_broken_test_run_result(err: str) -> TestRunResult:
return TestRunResult(
name='unknown',
idx=0,
seed=0,
binary=None,
uvm_log=None,
rtl_trace=None,
rtl_trace_csv=None,
iss_trace=None,
iss_trace_csv=None,
comparison_log=None,
passed=False,
failure_message=err
)
def box_comment(line: str) -> str:
hr = '#' * 80
return hr + '\n# ' + line + '\n' + hr
def gen_summary_line(passing_tests: List[TestRunResult], failing_tests:
List[TestRunResult]) -> str:
'''Generate a string summarising test results'''
total_tests = len(passing_tests) + len(failing_tests)
pass_pct = (len(passing_tests) / total_tests) * 100
return f'{pass_pct:0.2f}% PASS {len(passing_tests)} PASSED, ' \
f'{len(failing_tests)} FAILED'
def gen_test_run_result_text(test_run_result: TestRunResult) -> str:
'''Generate a string describing a TestRunResult.
The string includes details of logs, binary run and the failure message if
the test did not pass.'''
test_name_idx = f'{test_run_result.name}.{test_run_result.seed}'
test_underline = '-' * len(test_name_idx)
info_lines = [test_name_idx, test_underline]
if (test_run_result.binary):
info_lines.append(f'Test binary: {test_run_result.binary}')
if (test_run_result.uvm_log):
info_lines.append(f'UVM log: {test_run_result.uvm_log}')
if (test_run_result.rtl_trace):
info_lines.append(f'RTL trace: {test_run_result.rtl_trace}')
if (test_run_result.iss_trace):
info_lines.append(f'ISS trace: {test_run_result.iss_trace}')
if (test_run_result.comparison_log):
info_lines.append(f'Comparison log: {test_run_result.comparison_log}')
if (test_run_result.passed):
info_lines.append('')
info_lines.append('[PASSED]')
else:
info_lines.append('')
info_lines.append(test_run_result.failure_message)
return '\n'.join(info_lines) + '\n'
def output_results_text(passing_tests: List[TestRunResult], failing_tests:
List[TestRunResult], dest: TextIO):
'''Write results in text form to dest'''
if failing_tests:
print(box_comment('Details of failing tests'), file=dest)
for trr in failing_tests:
print(gen_test_run_result_text(trr), file=dest)
if passing_tests:
print(box_comment('Details of passing tests'), file=dest)
for trr in passing_tests:
print(gen_test_run_result_text(trr), file=dest)
dest.write('\n')
print(gen_summary_line(passing_tests, failing_tests), file=dest)
def output_run_results_junit_xml(passing_tests: List[TestRunResult],
failing_tests: List[TestRunResult],
junit_dest: TextIO,
junit_merged_dest: TextIO):
'''Write results to JUnit XML
Two versions are produced: a normal version and a merged version. In the
normal version there is a test suite per unique test name with a different
test case per seed run. In the merged version there is a single test case
under the test suite with information for the individual runs merged
together. This is to aid use of the Azure Pipelines JUnit dashboard, which
doesn't neatly handle the test suite/test case hierarchy
'''
all_tests = passing_tests + failing_tests
test_suite_info = {}
for trr in all_tests:
# test_case_info contains a tuple per unique test name. The first
# element is a list of junit_xml.TestCase, one per test run with that
# name. The other merges together all of the test outputs to produce
# the merged output.
unmerged, merged = \
test_suite_info.setdefault(trr.name, ([], {'stdout': '',
'failures': ''}))
result_text = gen_test_run_result_text(trr)
# Create a test case for the TestRunResult. stdout holds the text
# describing the run. Add the same text to failures if the test failed.
test_case = junit_xml.TestCase(f'{trr.name}.{trr.seed}')
test_case.stdout = result_text
merged['stdout'] += result_text + '\n'
if not trr.passed:
test_case.add_failure_info(output=result_text)
merged['failures'] += result_text
unmerged.append(test_case)
# Output the normal JUnit XML
test_suites = [junit_xml.TestSuite(name, test_cases) for
name, (test_cases, _) in test_suite_info.items()]
junit_dest.write(junit_xml.to_xml_report_string(test_suites))
# Output the merged version of the JUnit XML
merged_test_suites = []
for name, (_, merged_test_info) in test_suite_info.items():
test_case = junit_xml.TestCase(name)
test_case.stdout = merged_test_info['stdout']
test_case.add_failure_info(output=merged_test_info['failures'])
merged_test_suites.append(junit_xml.TestSuite(name, [test_case]))
junit_merged_dest.write(junit_xml.to_xml_report_string(merged_test_suites))
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument('--output_dir', '-o', required=True)
parser.add_argument('test_run_result', nargs='*')
args = parser.parse_args()
passing_tests = []
failing_tests = []
for test_run_result_path in args.test_run_result:
try:
test_run_result = parse_test_run_result(test_run_result_path)
if test_run_result.passed:
passing_tests.append(test_run_result)
else:
failing_tests.append(test_run_result)
except RuntimeError as e:
failing_tests.append(build_broken_test_run_result(str(e)))
regr_log_path = os.path.join(args.output_dir, 'regr.log')
junit_xml_path = os.path.join(args.output_dir, 'regr_junit.xml')
junit_xml_merged_path = os.path.join(args.output_dir,
'regr_junit_merged.xml')
with open(regr_log_path, 'w', encoding='UTF-8') as outfile:
output_results_text(passing_tests, failing_tests, outfile)
with open(junit_xml_path, 'w', encoding='UTF-8') as junit_xml, \
open(junit_xml_merged_path, 'w', encoding='UTF-8') as \
junit_merged_xml:
output_run_results_junit_xml(passing_tests, failing_tests, junit_xml,
junit_merged_xml)
print(gen_summary_line(passing_tests, failing_tests))
# Succeed if no tests failed
return 1 if failing_tests else 0
if __name__ == '__main__':
sys.exit(main())

View file

@ -1,28 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
interface core_ibex_ifetch_if(input logic clk);
logic reset;
logic fetch_ready;
logic fetch_valid;
logic [31:0] fetch_rdata;
logic [31:0] fetch_addr;
logic fetch_err;
logic fetch_err_plus2;
clocking monitor_cb @(posedge clk);
input reset;
input fetch_ready;
input fetch_valid;
input fetch_rdata;
input fetch_addr;
input fetch_err;
input fetch_err_plus2;
endclocking
task automatic wait_clks(input int num);
repeat (num) @(posedge clk);
endtask
endinterface

View file

@ -1,22 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
interface core_ibex_ifetch_pmp_if(input logic clk);
logic reset;
logic fetch_valid;
logic [31:0] fetch_addr;
logic fetch_pmp_err;
clocking monitor_cb @(posedge clk);
input reset;
input fetch_valid;
input fetch_addr;
input fetch_pmp_err;
endclocking
task automatic wait_clks(input int num);
repeat (num) @(posedge clk);
endtask
endinterface : core_ibex_ifetch_pmp_if

View file

@ -1,47 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
`include "cosim_dpi.svh"
class ibex_cosim_agent extends uvm_agent;
ibex_rvfi_monitor rvfi_monitor;
ibex_ifetch_monitor ifetch_monitor;
ibex_ifetch_pmp_monitor ifetch_pmp_monitor;
ibex_cosim_scoreboard scoreboard;
uvm_analysis_export#(ibex_mem_intf_seq_item) dmem_port;
uvm_analysis_export#(ibex_mem_intf_seq_item) imem_port;
`uvm_component_utils(ibex_cosim_agent)
function new(string name="", uvm_component parent=null);
super.new(name, parent);
dmem_port = new("dmem_port", this);
imem_port = new("imem_port", this);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
rvfi_monitor = ibex_rvfi_monitor::type_id::create("rvfi_monitor", this);
scoreboard = ibex_cosim_scoreboard::type_id::create("scoreboard", this);
ifetch_monitor = ibex_ifetch_monitor::type_id::create("ifetch_monitor", this);
ifetch_pmp_monitor = ibex_ifetch_pmp_monitor::type_id::create("ifetch_pmp_monitor", this);
endfunction: build_phase
virtual function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
rvfi_monitor.item_collected_port.connect(scoreboard.rvfi_port.analysis_export);
ifetch_monitor.item_collected_port.connect(scoreboard.ifetch_port.analysis_export);
ifetch_pmp_monitor.item_collected_port.connect(scoreboard.ifetch_pmp_port.analysis_export);
dmem_port.connect(scoreboard.dmem_port.analysis_export);
imem_port.connect(scoreboard.imem_port.analysis_export);
endfunction: connect_phase
function void write_mem_byte(bit [31:0] addr, bit [7:0] d);
riscv_cosim_write_mem_byte(scoreboard.cosim_handle, addr, d);
endfunction
endclass : ibex_cosim_agent

View file

@ -1,22 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
package ibex_cosim_agent_pkg;
`ifdef INC_IBEX_COSIM
import uvm_pkg::*;
import ibex_mem_intf_agent_pkg::*;
`include "uvm_macros.svh"
`include "ibex_cosim_cfg.sv"
`include "ibex_rvfi_seq_item.sv"
`include "ibex_rvfi_monitor.sv"
`include "ibex_ifetch_seq_item.sv"
`include "ibex_ifetch_monitor.sv"
`include "ibex_ifetch_pmp_seq_item.sv"
`include "ibex_ifetch_pmp_monitor.sv"
`include "ibex_cosim_scoreboard.sv"
`include "ibex_cosim_agent.sv"
`endif
endpackage

View file

@ -1,21 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class core_ibex_cosim_cfg extends uvm_object;
string isa_string;
bit [31:0] start_pc;
bit [31:0] start_mtvec;
bit probe_imem_for_errs;
string log_file;
`uvm_object_utils_begin(core_ibex_cosim_cfg)
`uvm_field_string(isa_string, UVM_DEFAULT)
`uvm_field_int(start_pc, UVM_DEFAULT)
`uvm_field_int(start_mtvec, UVM_DEFAULT)
`uvm_field_int(probe_imem_for_errs, UVM_DEFAULT)
`uvm_field_string(log_file, UVM_DEFAULT)
`uvm_object_utils_end
`uvm_object_new
endclass

View file

@ -1,276 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
`include "spike_cosim_dpi.svh"
`include "cosim_dpi.svh"
class ibex_cosim_scoreboard extends uvm_scoreboard;
chandle cosim_handle;
core_ibex_cosim_cfg cfg;
uvm_tlm_analysis_fifo #(ibex_rvfi_seq_item) rvfi_port;
uvm_tlm_analysis_fifo #(ibex_mem_intf_seq_item) dmem_port;
uvm_tlm_analysis_fifo #(ibex_mem_intf_seq_item) imem_port;
uvm_tlm_analysis_fifo #(ibex_ifetch_seq_item) ifetch_port;
uvm_tlm_analysis_fifo #(ibex_ifetch_pmp_seq_item) ifetch_pmp_port;
virtual core_ibex_instr_monitor_if instr_vif;
bit failed_iside_accesses [bit[31:0]];
bit iside_pmp_failure [bit[31:0]];
typedef struct {
bit [63:0] order;
bit [31:0] addr;
} iside_err_t;
iside_err_t iside_error_queue [$];
`uvm_component_utils(ibex_cosim_scoreboard)
function new(string name="", uvm_component parent=null);
super.new(name, parent);
rvfi_port = new("rvfi_port", this);
dmem_port = new("dmem_port", this);
imem_port = new("imem_port", this);
ifetch_port = new("ifetch_port", this);
ifetch_pmp_port = new("ifetch_pmp_port", this);
cosim_handle = null;
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(core_ibex_cosim_cfg)::get(this, "", "cosim_cfg", cfg)) begin
`uvm_fatal(`gfn, "Cannot get cosim configuration")
end
if (!uvm_config_db#(virtual core_ibex_instr_monitor_if)::get(null, "", "instr_monitor_if",
instr_vif)) begin
`uvm_fatal(`gfn, "Cannot get instr_monitor_if")
end
init_cosim();
endfunction : build_phase
protected function void init_cosim();
if (cosim_handle) begin
spike_cosim_release(cosim_handle);
end
// TODO: Ensure log file on reset gets append rather than overwrite?
cosim_handle = spike_cosim_init(cfg.isa_string, cfg.start_pc, cfg.start_mtvec, cfg.log_file);
if (cosim_handle == null) begin
`uvm_fatal(`gfn, "Could not initialise cosim")
end
endfunction
virtual task run_phase(uvm_phase phase);
wait (instr_vif.instr_cb.reset === 1'b0);
forever begin
fork begin: isolation_fork
fork
run_cosim_rvfi();
run_cosim_dmem();
run_cosim_imem_errors();
if (cfg.probe_imem_for_errs) begin
run_cosim_imem();
end else begin
fork
run_cosim_ifetch();
run_cosim_ifetch_pmp();
join_any
end
wait (instr_vif.instr_cb.reset === 1'b1);
join_any
disable fork;
end join
if (instr_vif.instr_cb.reset === 1'b1) handle_reset();
end
endtask : run_phase
task run_cosim_rvfi();
ibex_rvfi_seq_item rvfi_instr;
forever begin
rvfi_port.get(rvfi_instr);
// Remove entries from iside_error_queue where the instruction never reaches the RVFI
// interface because it was flushed.
while (iside_error_queue.size() > 0 && iside_error_queue[0].order < rvfi_instr.order) begin
iside_error_queue.pop_front();
end
// Check if the top of the iside_error_queue relates to the current RVFI instruction. If so
// notify the cosim environment of an instruction error.
if (iside_error_queue.size() !=0 && iside_error_queue[0].order == rvfi_instr.order) begin
riscv_cosim_set_iside_error(cosim_handle, iside_error_queue[0].addr);
iside_error_queue.pop_front();
end
riscv_cosim_set_nmi(cosim_handle, rvfi_instr.nmi);
riscv_cosim_set_mip(cosim_handle, rvfi_instr.mip);
riscv_cosim_set_debug_req(cosim_handle, rvfi_instr.debug_req);
riscv_cosim_set_mcycle(cosim_handle, rvfi_instr.mcycle);
if (!riscv_cosim_step(cosim_handle, rvfi_instr.rd_addr, rvfi_instr.rd_wdata, rvfi_instr.pc,
rvfi_instr.trap)) begin
// cosim instruction step doesn't match rvfi captured instruction, report a fatal error
// with the details
`uvm_fatal(`gfn, get_cosim_error_str())
end
end
endtask: run_cosim_rvfi
task run_cosim_dmem();
ibex_mem_intf_seq_item mem_op;
forever begin
dmem_port.get(mem_op);
// Notify the cosim of all dside accesses emitted by the RTL
riscv_cosim_notify_dside_access(cosim_handle, mem_op.read_write == WRITE, mem_op.addr,
mem_op.data, mem_op.be, mem_op.error, mem_op.misaligned_first, mem_op.misaligned_second);
end
endtask: run_cosim_dmem
task run_cosim_imem();
ibex_mem_intf_seq_item mem_op;
forever begin
// Take stream of transaction from imem monitor. Where an imem access has an error record it
// in failed_iside_accesses. If an access has succeeded remove it from failed_imem_accesses if
// it's there.
// Note all transactions are 32-bit aligned.
imem_port.get(mem_op);
if (mem_op.error) begin
failed_iside_accesses[mem_op.addr] = 1'b1;
end else begin
if (failed_iside_accesses.exists(mem_op.addr)) begin
failed_iside_accesses.delete(mem_op.addr);
end
end
end
endtask: run_cosim_imem
task run_cosim_ifetch();
ibex_ifetch_seq_item ifetch;
bit [31:0] aligned_fetch_addr;
bit [31:0] aligned_fetch_addr_next;
forever begin
ifetch_port.get(ifetch);
aligned_fetch_addr = {ifetch.fetch_addr[31:2], 2'b0};
aligned_fetch_addr_next = aligned_fetch_addr + 32'd4;
if (ifetch.fetch_err) begin
// Instruction error observed in fetch stage
bit [31:0] failing_addr;
// Determine which address failed.
if (ifetch.fetch_err_plus2) begin
// Instruction crosses a 32-bit boundary and second half failed
failing_addr = aligned_fetch_addr_next;
end else begin
failing_addr = aligned_fetch_addr;
end
failed_iside_accesses[failing_addr] = 1'b1;
end else begin
if (ifetch.fetch_addr[1:0] != 0 && ifetch.fetch_rdata[1:0] == 2'b11) begin
// Instruction crosses 32-bit boundary, so remove any failed accesses on the other side of
// the 32-bit boundary.
if (failed_iside_accesses.exists(aligned_fetch_addr_next)) begin
failed_iside_accesses.delete(aligned_fetch_addr_next);
end
end
if (failed_iside_accesses.exists(aligned_fetch_addr)) begin
failed_iside_accesses.delete(aligned_fetch_addr);
end
end
end
endtask: run_cosim_ifetch
task run_cosim_ifetch_pmp();
ibex_ifetch_pmp_seq_item ifetch_pmp;
// Keep track of which addresses have seen PMP failures.
forever begin
ifetch_pmp_port.get(ifetch_pmp);
if (ifetch_pmp.fetch_pmp_err) begin
iside_pmp_failure[ifetch_pmp.fetch_addr] = 1'b1;
end else begin
if (iside_pmp_failure.exists(ifetch_pmp.fetch_addr)) begin
iside_pmp_failure.delete(ifetch_pmp.fetch_addr);
end
end
end
endtask
task run_cosim_imem_errors();
bit [63:0] latest_order = 64'hffffffff_ffffffff;
bit [31:0] aligned_addr;
bit [31:0] aligned_next_addr;
forever begin
// Wait for new instruction to appear in ID stage
wait (instr_vif.instr_cb.valid_id &&
instr_vif.instr_cb.instr_new_id &&
latest_order != instr_vif.instr_cb.rvfi_order_id);
// Determine if the instruction comes from an address that has seen an error that wasn't a PMP
// error (the icache records both PMP errors and fetch errors with the same error bits). If a
// fetch error was seen add the instruction order ID and address to iside_error_queue.
aligned_addr = instr_vif.instr_cb.pc_id & 32'hfffffffc;
aligned_next_addr = aligned_addr + 32'd4;
if (failed_iside_accesses.exists(aligned_addr) && !iside_pmp_failure.exists(aligned_addr))
begin
iside_error_queue.push_back('{order : instr_vif.instr_cb.rvfi_order_id,
addr : aligned_addr});
end else if (!instr_vif.instr_cb.is_compressed_id &&
(instr_vif.instr_cb.pc_id & 32'h3) != 0 &&
failed_iside_accesses.exists(aligned_next_addr) &&
!iside_pmp_failure.exists(aligned_next_addr))
begin
// Where an instruction crosses a 32-bit boundary, check if an error was seen on the other
// side of the boundary
iside_error_queue.push_back('{order : instr_vif.instr_cb.rvfi_order_id,
addr : aligned_next_addr});
end
latest_order = instr_vif.instr_cb.rvfi_order_id;
end
endtask: run_cosim_imem_errors;
function string get_cosim_error_str();
string error = "Cosim mismatch ";
for (int i = 0; i < riscv_cosim_get_num_errors(cosim_handle); ++i) begin
error = {error, riscv_cosim_get_error(cosim_handle, i), "\n"};
end
riscv_cosim_clear_errors(cosim_handle);
return error;
endfunction : get_cosim_error_str
function void final_phase(uvm_phase phase);
super.final_phase(phase);
`uvm_info(`gfn, $sformatf("Co-simulation matched %d instructions",
riscv_cosim_get_insn_cnt(cosim_handle)), UVM_LOW)
if (cosim_handle) begin
spike_cosim_release(cosim_handle);
end
endfunction : final_phase
task handle_reset();
init_cosim();
wait (instr_vif.instr_cb.reset === 1'b0);
endtask
endclass : ibex_cosim_scoreboard

View file

@ -1,45 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class ibex_ifetch_monitor extends uvm_monitor;
protected virtual core_ibex_ifetch_if vif;
uvm_analysis_port#(ibex_ifetch_seq_item) item_collected_port;
`uvm_component_utils(ibex_ifetch_monitor)
`uvm_component_new
function void build_phase(uvm_phase phase);
super.build_phase(phase);
item_collected_port = new("item_collected_port", this);
if(!uvm_config_db#(virtual core_ibex_ifetch_if)::get(this, "", "ifetch_if", vif)) begin
`uvm_fatal("NOVIF", {"virtual interface must be set for: ", get_full_name(), ".vif"});
end
endfunction
virtual task run_phase(uvm_phase phase);
ibex_ifetch_seq_item trans_collected;
wait (vif.monitor_cb.reset === 1'b0);
forever begin
while(!(vif.monitor_cb.fetch_valid && vif.monitor_cb.fetch_ready)) vif.wait_clks(1);
trans_collected = ibex_ifetch_seq_item::type_id::create("trans_collected");
trans_collected.fetch_rdata = vif.monitor_cb.fetch_rdata;
trans_collected.fetch_addr = vif.monitor_cb.fetch_addr;
trans_collected.fetch_err = vif.monitor_cb.fetch_err;
trans_collected.fetch_err_plus2 = vif.monitor_cb.fetch_err_plus2;
`uvm_info(`gfn, $sformatf("Seen ifetch:\n%s", trans_collected.sprint()),
UVM_HIGH)
item_collected_port.write(trans_collected);
vif.wait_clks(1);
end
endtask: run_phase
endclass : ibex_ifetch_monitor

View file

@ -1,43 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class ibex_ifetch_pmp_monitor extends uvm_monitor;
protected virtual core_ibex_ifetch_pmp_if vif;
uvm_analysis_port#(ibex_ifetch_pmp_seq_item) item_collected_port;
`uvm_component_utils(ibex_ifetch_pmp_monitor)
`uvm_component_new
function void build_phase(uvm_phase phase);
super.build_phase(phase);
item_collected_port = new("item_collected_port", this);
if(!uvm_config_db#(virtual core_ibex_ifetch_pmp_if)::get(this, "", "ifetch_pmp_if", vif)) begin
`uvm_fatal("NOVIF", {"virtual interface must be set for: ", get_full_name(), ".vif"});
end
endfunction
virtual task run_phase(uvm_phase phase);
ibex_ifetch_pmp_seq_item trans_collected;
wait (vif.monitor_cb.reset === 1'b0);
forever begin
while(!vif.monitor_cb.fetch_valid) vif.wait_clks(1);
trans_collected = ibex_ifetch_pmp_seq_item::type_id::create("trans_collected");
trans_collected.fetch_addr = vif.monitor_cb.fetch_addr;
trans_collected.fetch_pmp_err = vif.monitor_cb.fetch_pmp_err;
`uvm_info(`gfn, $sformatf("Seen ifetch:\n%s", trans_collected.sprint()),
UVM_HIGH)
item_collected_port.write(trans_collected);
vif.wait_clks(1);
end
endtask: run_phase
endclass : ibex_ifetch_pmp_monitor

View file

@ -1,15 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class ibex_ifetch_pmp_seq_item extends uvm_sequence_item;
bit [31:0] fetch_addr;
bit fetch_pmp_err;
`uvm_object_utils_begin(ibex_ifetch_pmp_seq_item)
`uvm_field_int (fetch_addr, UVM_DEFAULT)
`uvm_field_int (fetch_pmp_err, UVM_DEFAULT)
`uvm_object_utils_end
`uvm_object_new
endclass : ibex_ifetch_pmp_seq_item

View file

@ -1,19 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class ibex_ifetch_seq_item extends uvm_sequence_item;
bit [31:0] fetch_rdata;
bit [31:0] fetch_addr;
bit fetch_err;
bit fetch_err_plus2;
`uvm_object_utils_begin(ibex_ifetch_seq_item)
`uvm_field_int (fetch_rdata, UVM_DEFAULT)
`uvm_field_int (fetch_addr, UVM_DEFAULT)
`uvm_field_int (fetch_err, UVM_DEFAULT)
`uvm_field_int (fetch_err_plus2, UVM_DEFAULT)
`uvm_object_utils_end
`uvm_object_new
endclass : ibex_ifetch_seq_item

View file

@ -1,52 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class ibex_rvfi_monitor extends uvm_monitor;
protected virtual core_ibex_rvfi_if vif;
uvm_analysis_port#(ibex_rvfi_seq_item) item_collected_port;
`uvm_component_utils(ibex_rvfi_monitor)
`uvm_component_new
function void build_phase(uvm_phase phase);
super.build_phase(phase);
item_collected_port = new("item_collected_port", this);
if(!uvm_config_db#(virtual core_ibex_rvfi_if)::get(this, "", "rvfi_if", vif)) begin
`uvm_fatal("NOVIF", {"virtual interface must be set for: ", get_full_name(), ".vif"});
end
endfunction: build_phase
virtual task run_phase(uvm_phase phase);
ibex_rvfi_seq_item trans_collected;
wait (vif.monitor_cb.reset === 1'b0);
forever begin
// Wait for a retired instruction
while(!vif.monitor_cb.valid) vif.wait_clks(1);
// Read instruction details from RVFI interface
trans_collected = ibex_rvfi_seq_item::type_id::create("trans_collected");
trans_collected.trap = vif.monitor_cb.trap;
trans_collected.pc = vif.monitor_cb.pc_rdata;
trans_collected.rd_addr = vif.monitor_cb.rd_addr;
trans_collected.rd_wdata = vif.monitor_cb.rd_wdata;
trans_collected.order = vif.monitor_cb.order;
trans_collected.mip = vif.monitor_cb.ext_mip;
trans_collected.nmi = vif.monitor_cb.ext_nmi;
trans_collected.debug_req = vif.monitor_cb.ext_debug_req;
trans_collected.mcycle = vif.monitor_cb.ext_mcycle;
`uvm_info(get_full_name(), $sformatf("Seen instruction:\n%s", trans_collected.sprint()),
UVM_HIGH)
item_collected_port.write(trans_collected);
vif.wait_clks(1);
end
endtask : run_phase
endclass : ibex_rvfi_monitor

View file

@ -1,30 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class ibex_rvfi_seq_item extends uvm_sequence_item;
bit trap;
bit [31:0] pc;
bit [4:0] rd_addr;
bit [31:0] rd_wdata;
bit [63:0] order;
bit [31:0] mip;
bit nmi;
bit debug_req;
bit [63:0] mcycle;
`uvm_object_utils_begin(ibex_rvfi_seq_item)
`uvm_field_int (trap, UVM_DEFAULT)
`uvm_field_int (pc, UVM_DEFAULT)
`uvm_field_int (rd_addr, UVM_DEFAULT)
`uvm_field_int (rd_wdata, UVM_DEFAULT)
`uvm_field_int (order, UVM_DEFAULT)
`uvm_field_int (mip, UVM_DEFAULT)
`uvm_field_int (nmi, UVM_DEFAULT)
`uvm_field_int (debug_req, UVM_DEFAULT)
`uvm_field_int (mcycle, UVM_DEFAULT)
`uvm_object_utils_end
`uvm_object_new
endclass : ibex_rvfi_seq_item

View file

@ -1,34 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include <svdpi.h>
#include <cassert>
#include "cosim.h"
#include "spike_cosim.h"
extern "C" {
void *spike_cosim_init(const char *isa_string, svBitVecVal *start_pc,
svBitVecVal *start_mtvec,
const char *log_file_path_cstr) {
assert(isa_string);
std::string log_file_path;
if (log_file_path_cstr) {
log_file_path = log_file_path_cstr;
}
SpikeCosim *cosim = new SpikeCosim(isa_string, start_pc[0], start_mtvec[0],
log_file_path, false, true);
cosim->add_memory(0x80000000, 0x80000000);
return static_cast<Cosim *>(cosim);
}
void spike_cosim_release(void *cosim_handle) {
auto cosim = static_cast<Cosim *>(cosim_handle);
delete cosim;
}
}

View file

@ -1,16 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
`ifndef SPIKE_COSIM_DPI_SVH
`define SPIKE_COSIM_DPI_SVH
import "DPI-C" function
chandle spike_cosim_init(string isa_string,
bit [31:0] start_pc,
bit [31:0] start_mtvec,
string log_file_path);
import "DPI-C" function void spike_cosim_release(chandle cosim_handle);
`endif

View file

@ -1,83 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
interface ibex_mem_intf#(
parameter int ADDR_WIDTH = 32,
parameter int DATA_WIDTH = 32,
parameter int INTG_WIDTH = 7
) (
input clk
);
wire reset;
wire request;
wire grant;
wire [ADDR_WIDTH-1:0] addr;
wire we;
wire [DATA_WIDTH/8-1:0] be;
wire rvalid;
wire [DATA_WIDTH-1:0] wdata;
wire [INTG_WIDTH-1:0] wintg;
wire [DATA_WIDTH-1:0] rdata;
wire [INTG_WIDTH-1:0] rintg;
wire error;
wire misaligned_first;
wire misaligned_second;
clocking request_driver_cb @(posedge clk);
input reset;
output request;
input grant;
output addr;
output we;
output be;
input rvalid;
output wdata;
output wintg;
input rdata;
input rintg;
input error;
endclocking
clocking response_driver_cb @(posedge clk);
input reset;
input request;
output grant;
input addr;
input we;
input be;
output rvalid;
input wdata;
input wintg;
output rdata;
output rintg;
output error;
endclocking
clocking monitor_cb @(posedge clk);
input reset;
input request;
input grant;
input addr;
input we;
input be;
input rvalid;
input wdata;
input wintg;
input rdata;
input rintg;
input error;
input misaligned_first;
input misaligned_second;
endclocking
task automatic wait_clks(input int num);
repeat (num) @(posedge clk);
endtask
task automatic wait_neg_clks(input int num);
repeat (num) @(negedge clk);
endtask
endinterface : ibex_mem_intf

View file

@ -1,27 +0,0 @@
CAPI=2:
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
name: "lowrisc:dv:ibex_mem_intf_agent:0.1"
description: "IBEX DV UVM environment"
filesets:
files_dv:
depend:
- lowrisc:dv:mem_model
files:
- ibex_mem_intf.sv
- ibex_mem_intf_agent_pkg.sv
- ibex_mem_intf_request_agent.sv: {is_include_file: true}
- ibex_mem_intf_request_driver.sv: {is_include_file: true}
- ibex_mem_intf_monitor.sv: {is_include_file: true}
- ibex_mem_intf_seq_item.sv: {is_include_file: true}
- ibex_mem_intf_response_agent.sv: {is_include_file: true}
- ibex_mem_intf_response_driver.sv: {is_include_file: true}
- ibex_mem_intf_response_seq_lib.sv: {is_include_file: true}
- ibex_mem_intf_response_sequencer.sv: {is_include_file: true}
file_type: systemVerilogSource
targets:
default:
filesets:
- files_dv

View file

@ -1,30 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
package ibex_mem_intf_agent_pkg;
import uvm_pkg::*;
import mem_model_pkg::*;
parameter int DATA_WIDTH = 32;
parameter int ADDR_WIDTH = 32;
parameter int INTG_WIDTH = 7;
typedef enum { READ, WRITE } rw_e;
`include "uvm_macros.svh"
`include "ibex_mem_intf_seq_item.sv"
typedef uvm_sequencer#(ibex_mem_intf_seq_item) ibex_mem_intf_request_sequencer;
`include "ibex_mem_intf_monitor.sv"
`include "ibex_mem_intf_response_agent_cfg.sv"
`include "ibex_mem_intf_response_driver.sv"
`include "ibex_mem_intf_response_sequencer.sv"
`include "ibex_mem_intf_response_seq_lib.sv"
`include "ibex_mem_intf_response_agent.sv"
`include "ibex_mem_intf_request_driver.sv"
`include "ibex_mem_intf_request_agent.sv"
endpackage

View file

@ -1,94 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//------------------------------------------------------------------------------
// CLASS: ibex_mem_intf_monitor
//------------------------------------------------------------------------------
class ibex_mem_intf_monitor extends uvm_monitor;
protected virtual ibex_mem_intf vif;
mailbox #(ibex_mem_intf_seq_item) collect_response_queue;
uvm_analysis_port#(ibex_mem_intf_seq_item) item_collected_port;
uvm_analysis_port#(ibex_mem_intf_seq_item) addr_ph_port;
`uvm_component_utils(ibex_mem_intf_monitor)
`uvm_component_new
function void build_phase(uvm_phase phase);
super.build_phase(phase);
item_collected_port = new("item_collected_port", this);
addr_ph_port = new("addr_ph_port_monitor", this);
collect_response_queue = new();
if(!uvm_config_db#(virtual ibex_mem_intf)::get(this, "", "vif", vif)) begin
`uvm_fatal("NOVIF",{"virtual interface must be set for: ",get_full_name(),".vif"});
end
endfunction: build_phase
virtual task run_phase(uvm_phase phase);
wait (vif.monitor_cb.reset === 1'b0);
forever begin
fork : check_mem_intf
collect_address_phase();
collect_response_phase();
wait (vif.monitor_cb.reset === 1'b1);
join_any
// Will only reach this point when mid-test reset is asserted
disable fork;
handle_reset();
end
endtask : run_phase
virtual protected task handle_reset();
ibex_mem_intf_seq_item mailbox_result;
// Clear the mailbox of any content
while (collect_response_queue.try_get(mailbox_result));
wait (vif.monitor_cb.reset === 1'b0);
endtask
virtual protected task collect_address_phase();
ibex_mem_intf_seq_item trans_collected;
forever begin
trans_collected = ibex_mem_intf_seq_item::type_id::create("trans_collected");
while(!(vif.monitor_cb.request && vif.monitor_cb.grant)) vif.wait_clks(1);
trans_collected.addr = vif.monitor_cb.addr;
trans_collected.be = vif.monitor_cb.be;
trans_collected.misaligned_first = vif.monitor_cb.misaligned_first;
trans_collected.misaligned_second = vif.monitor_cb.misaligned_second;
`uvm_info(get_full_name(), $sformatf("Detect request with address: %0x",
trans_collected.addr), UVM_HIGH)
if(vif.monitor_cb.we) begin
trans_collected.read_write = WRITE;
trans_collected.data = vif.monitor_cb.wdata;
trans_collected.intg = vif.monitor_cb.wintg;
end else begin
trans_collected.read_write = READ;
end
addr_ph_port.write(trans_collected);
`uvm_info(get_full_name(),"Send through addr_ph_port", UVM_HIGH)
collect_response_queue.put(trans_collected);
vif.wait_clks(1);
end
endtask : collect_address_phase
virtual protected task collect_response_phase();
ibex_mem_intf_seq_item trans_collected;
forever begin
collect_response_queue.get(trans_collected);
do
vif.wait_clks(1);
while(vif.monitor_cb.rvalid === 0);
if (trans_collected.read_write == READ) begin
trans_collected.data = vif.monitor_cb.rdata;
trans_collected.intg = vif.monitor_cb.rintg;
end
trans_collected.error = vif.monitor_cb.error;
item_collected_port.write(trans_collected);
end
endtask : collect_response_phase
endclass : ibex_mem_intf_monitor

View file

@ -1,34 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//------------------------------------------------------------------------------
// CLASS: ibex_mem_intf_request_agent
//------------------------------------------------------------------------------
class ibex_mem_intf_request_agent extends uvm_agent;
ibex_mem_intf_request_driver driver;
ibex_mem_intf_request_sequencer sequencer;
ibex_mem_intf_monitor monitor;
`uvm_component_utils(ibex_mem_intf_request_agent)
`uvm_component_new
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
monitor = ibex_mem_intf_monitor::type_id::create("monitor", this);
if(get_is_active() == UVM_ACTIVE) begin
driver = ibex_mem_intf_request_driver::type_id::create("driver", this);
sequencer = ibex_mem_intf_request_sequencer::type_id::create("sequencer", this);
end
endfunction : build_phase
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
if(get_is_active() == UVM_ACTIVE) begin
driver.seq_item_port.connect(sequencer.seq_item_export);
end
endfunction : connect_phase
endclass : ibex_mem_intf_request_agent

View file

@ -1,92 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//------------------------------------------------------------------------------
// CLASS: ibex_mem_intf_request_driver
//------------------------------------------------------------------------------
class ibex_mem_intf_request_driver extends uvm_driver #(ibex_mem_intf_seq_item);
protected virtual ibex_mem_intf vif;
`uvm_component_utils(ibex_mem_intf_request_driver)
`uvm_component_new
mailbox #(ibex_mem_intf_seq_item) rdata_queue;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
rdata_queue = new();
if(!uvm_config_db#(virtual ibex_mem_intf)::get(this, "", "vif", vif))
`uvm_fatal("NOVIF",{"virtual interface must be set for: ",get_full_name(),".vif"});
endfunction: build_phase
virtual task run_phase(uvm_phase phase);
fork
get_and_drive();
reset_signals();
collect_response();
join
endtask : run_phase
virtual protected task get_and_drive();
@(negedge vif.request_driver_cb.reset);
forever begin
vif.wait_clks(1);
seq_item_port.get_next_item(req);
vif.wait_clks(req.req_delay);
$cast(rsp, req.clone());
rsp.set_id_info(req);
drive_transfer(rsp);
seq_item_port.item_done();
end
endtask : get_and_drive
virtual protected task reset_signals();
forever begin
@(posedge vif.request_driver_cb.reset);
vif.request_driver_cb.request <= 'h0;
vif.request_driver_cb.addr <= 'hz;
vif.request_driver_cb.wdata <= 'hz;
vif.request_driver_cb.wintg <= 'hz;
vif.request_driver_cb.be <= 'bz;
vif.request_driver_cb.we <= 'bz;
end
endtask : reset_signals
virtual protected task drive_transfer (ibex_mem_intf_seq_item trans);
if (trans.req_delay > 0) begin
vif.wait_clks(trans.req_delay);
end
vif.request_driver_cb.request <= 1'b1;
vif.request_driver_cb.addr <= trans.addr;
vif.request_driver_cb.be <= trans.be;
vif.request_driver_cb.we <= trans.read_write;
vif.request_driver_cb.wdata <= trans.data;
vif.request_driver_cb.wintg <= trans.intg;
wait (vif.request_driver_cb.grant === 1'b1);
vif.wait_clks(1);
vif.request_driver_cb.request <= 'h0;
vif.request_driver_cb.addr <= 'hz;
vif.request_driver_cb.wdata <= 'hz;
vif.request_driver_cb.wintg <= 'hz;
vif.request_driver_cb.be <= 'bz;
vif.request_driver_cb.we <= 'bz;
rdata_queue.put(trans);
endtask : drive_transfer
virtual protected task collect_response();
ibex_mem_intf_seq_item tr;
forever begin
rdata_queue.get(tr);
vif.wait_clks(1);
while(vif.rvalid !== 1'b1) vif.wait_clks(1);
if(tr.read_write == READ)
tr.data = vif.request_driver_cb.rdata;
tr.intg = vif.request_driver_cb.rintg;
seq_item_port.put_response(tr);
end
endtask : collect_response
endclass : ibex_mem_intf_request_driver

View file

@ -1,45 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//------------------------------------------------------------------------------
// CLASS: ibex_mem_intf_response_agent
//------------------------------------------------------------------------------
class ibex_mem_intf_response_agent extends uvm_agent;
ibex_mem_intf_response_driver driver;
ibex_mem_intf_response_sequencer sequencer;
ibex_mem_intf_monitor monitor;
ibex_mem_intf_response_agent_cfg cfg;
`uvm_component_utils(ibex_mem_intf_response_agent)
`uvm_component_new
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
monitor = ibex_mem_intf_monitor::type_id::create("monitor", this);
cfg = ibex_mem_intf_response_agent_cfg::type_id::create("cfg", this);
if(get_is_active() == UVM_ACTIVE) begin
driver = ibex_mem_intf_response_driver::type_id::create("driver", this);
sequencer = ibex_mem_intf_response_sequencer::type_id::create("sequencer", this);
end
if(!uvm_config_db#(virtual ibex_mem_intf)::get(this, "", "vif", cfg.vif))
`uvm_fatal("NOVIF",{"virtual interface must be set for: ",get_full_name(),".vif"});
endfunction : build_phase
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
if(get_is_active() == UVM_ACTIVE) begin
driver.seq_item_port.connect(sequencer.seq_item_export);
monitor.addr_ph_port.connect(sequencer.addr_ph_port.analysis_export);
end
driver.cfg = cfg;
sequencer.cfg = cfg;
endfunction : connect_phase
function void reset();
sequencer.reset();
endfunction
endclass : ibex_mem_intf_response_agent

View file

@ -1,51 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//------------------------------------------------------------------------------
// CLASS: ibex_mem_intf_response_agent_cfg
//------------------------------------------------------------------------------
class ibex_mem_intf_response_agent_cfg extends uvm_object;
// interface handle used by driver & monitor
virtual ibex_mem_intf vif;
// delay between request and grant
int unsigned gnt_delay_min = 0;
int unsigned gnt_delay_max = 10;
// Pick the weight assigned to choosing medium and long gaps between request and grant
int unsigned gnt_pick_medium_speed_weight = 1;
int unsigned gnt_pick_slow_speed_weight = 1;
// delay between grant and rvalid
int unsigned valid_delay_min = 0;
int unsigned valid_delay_max = 20;
// Pick the weight assigned to choosing medium and long gaps between grant and rvalid
int unsigned valid_pick_medium_speed_weight = 1;
int unsigned valid_pick_slow_speed_weight = 1;
// Enables/disable all protocol delays.
rand bit zero_delays;
// Knob to enable percentage of zero delay in auto-response sequence.
// Default set to 50% for zero delay to be picked
int unsigned zero_delay_pct = 50;
`uvm_object_new
constraint zero_delays_c {
zero_delays dist {1 :/ zero_delay_pct,
0 :/ 100 - zero_delay_pct};
}
`uvm_object_utils_begin(ibex_mem_intf_response_agent_cfg)
`uvm_field_int(gnt_delay_min, UVM_DEFAULT)
`uvm_field_int(gnt_delay_max, UVM_DEFAULT)
`uvm_field_int(valid_delay_min, UVM_DEFAULT)
`uvm_field_int(valid_delay_max, UVM_DEFAULT)
`uvm_field_int(zero_delays, UVM_DEFAULT)
`uvm_field_int(zero_delay_pct, UVM_DEFAULT)
`uvm_object_utils_end
endclass

View file

@ -1,130 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//------------------------------------------------------------------------------
// CLASS: ibex_mem_intf_response_driver
//------------------------------------------------------------------------------
class ibex_mem_intf_response_driver extends uvm_driver #(ibex_mem_intf_seq_item);
ibex_mem_intf_response_agent_cfg cfg;
`uvm_component_utils(ibex_mem_intf_response_driver)
`uvm_component_new
mailbox #(ibex_mem_intf_seq_item) rdata_queue;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
rdata_queue = new();
endfunction: build_phase
virtual task run_phase(uvm_phase phase);
reset_signals();
wait (cfg.vif.response_driver_cb.reset === 1'b0);
forever begin
fork : drive_stimulus
send_grant();
get_and_drive();
wait (cfg.vif.response_driver_cb.reset === 1'b1);
join_any
// Will only be reached after mid-test reset
disable fork;
handle_reset();
end
endtask : run_phase
virtual protected task handle_reset();
ibex_mem_intf_seq_item req;
// Clear mailbox
while (rdata_queue.try_get(req));
// Clear seq_item_port
do begin
seq_item_port.try_next_item(req);
if (req != null) begin
seq_item_port.item_done();
end
end while (req != null);
reset_signals();
wait (cfg.vif.response_driver_cb.reset === 1'b0);
endtask
virtual protected task reset_signals();
cfg.vif.response_driver_cb.rvalid <= 1'b0;
cfg.vif.response_driver_cb.grant <= 1'b0;
cfg.vif.response_driver_cb.rdata <= 'b0;
cfg.vif.response_driver_cb.rintg <= 'b0;
cfg.vif.response_driver_cb.error <= 1'b0;
endtask : reset_signals
virtual protected task get_and_drive();
wait (cfg.vif.response_driver_cb.reset === 1'b0);
fork
begin
forever begin
ibex_mem_intf_seq_item req, req_c;
cfg.vif.wait_clks(1);
seq_item_port.get_next_item(req);
$cast(req_c, req.clone());
if(~cfg.vif.response_driver_cb.reset) begin
rdata_queue.put(req_c);
end
seq_item_port.item_done();
end
end
begin
send_read_data();
end
join
endtask : get_and_drive
virtual protected task send_grant();
int gnt_delay;
forever begin
while(cfg.vif.response_driver_cb.request !== 1'b1) begin
cfg.vif.wait_neg_clks(1);
end
if(cfg.zero_delays) begin
gnt_delay = 0;
end else begin
if (!std::randomize(gnt_delay) with {
gnt_delay dist {
cfg.gnt_delay_min :/ 10,
[cfg.gnt_delay_min+1 : cfg.gnt_delay_max-1] :/ cfg.valid_pick_medium_speed_weight,
cfg.gnt_delay_max :/ cfg.valid_pick_slow_speed_weight
};
}) begin
`uvm_fatal(`gfn, $sformatf("Cannot randomize grant"))
end
end
cfg.vif.wait_neg_clks(gnt_delay);
if(~cfg.vif.response_driver_cb.reset) begin
cfg.vif.response_driver_cb.grant <= 1'b1;
cfg.vif.wait_neg_clks(1);
cfg.vif.response_driver_cb.grant <= 1'b0;
end
end
endtask : send_grant
virtual protected task send_read_data();
ibex_mem_intf_seq_item tr;
forever begin
cfg.vif.wait_clks(1);
cfg.vif.response_driver_cb.rvalid <= 1'b0;
cfg.vif.response_driver_cb.rdata <= 'x;
cfg.vif.response_driver_cb.rintg <= 'x;
cfg.vif.response_driver_cb.error <= 'x;
rdata_queue.get(tr);
if(cfg.vif.response_driver_cb.reset) continue;
cfg.vif.wait_clks(tr.rvalid_delay);
if(~cfg.vif.response_driver_cb.reset) begin
cfg.vif.response_driver_cb.rvalid <= 1'b1;
cfg.vif.response_driver_cb.error <= tr.error;
cfg.vif.response_driver_cb.rdata <= tr.data;
cfg.vif.response_driver_cb.rintg <= tr.intg;
end
end
endtask : send_read_data
endclass : ibex_mem_intf_response_driver

View file

@ -1,103 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//------------------------------------------------------------------------------
// SEQUENCE: ibex_mem_intf_response_seq
//------------------------------------------------------------------------------
class ibex_mem_intf_response_seq extends uvm_sequence #(ibex_mem_intf_seq_item);
ibex_mem_intf_seq_item item;
mem_model m_mem;
bit enable_error = 1'b0;
// Used to ensure that whenever inject_error() is called, the very next transaction will inject an
// error, and that enable_error will not be flipped back to 0 immediately
bit error_synch = 1'b1;
bit is_dmem_seq = 1'b0;
`uvm_object_utils(ibex_mem_intf_response_seq)
`uvm_declare_p_sequencer(ibex_mem_intf_response_sequencer)
`uvm_object_new
virtual task body();
if(m_mem == null)
`uvm_fatal(get_full_name(), "Cannot get memory model")
`uvm_info(`gfn, $sformatf("is_dmem_seq: 0x%0x", is_dmem_seq), UVM_LOW)
forever
begin
bit [ADDR_WIDTH-1:0] aligned_addr;
bit [DATA_WIDTH-1:0] rand_data;
bit [DATA_WIDTH-1:0] read_data;
bit [INTG_WIDTH-1:0] read_intg;
p_sequencer.addr_ph_port.get(item);
req = ibex_mem_intf_seq_item::type_id::create("req");
error_synch = 1'b0;
if (!req.randomize() with {
addr == item.addr;
read_write == item.read_write;
data == item.data;
intg == item.intg;
be == item.be;
if (p_sequencer.cfg.zero_delays) {
rvalid_delay == 0;
} else {
rvalid_delay dist {
p_sequencer.cfg.valid_delay_min :/ 5,
[p_sequencer.cfg.valid_delay_min + 1 : p_sequencer.cfg.valid_delay_max / 2 - 1] :/ 3,
[p_sequencer.cfg.valid_delay_max / 2 : p_sequencer.cfg.valid_delay_max - 1]
:/ p_sequencer.cfg.valid_pick_medium_speed_weight,
p_sequencer.cfg.valid_delay_max
:/ p_sequencer.cfg.valid_pick_slow_speed_weight
};
}
error == enable_error;
}) begin
`uvm_fatal(`gfn, "Cannot randomize response request")
end
enable_error = 1'b0;
error_synch = 1'b1;
aligned_addr = {req.addr[DATA_WIDTH-1:2], 2'b0};
if (req.error) begin
`DV_CHECK_STD_RANDOMIZE_FATAL(rand_data)
req.data = rand_data;
end else begin
if(req.read_write == READ) begin : READ_block
if (is_dmem_seq) begin
for (int i = DATA_WIDTH / 8 - 1; i >= 0; i--) begin
read_data = read_data << 8;
if (req.be[i])
read_data[7:0] = m_mem.read_byte(aligned_addr + i);
end
req.data = read_data;
end else begin
req.data = m_mem.read(aligned_addr);
end
end
end
// Add correct integrity bits
{req.intg, req.data} = prim_secded_pkg::prim_secded_inv_39_32_enc(req.data);
`uvm_info(get_full_name(), $sformatf("Response transfer:\n%0s", req.sprint()), UVM_HIGH)
start_item(req);
finish_item(req);
if(item.read_write == WRITE) begin : WRITE_block
bit [DATA_WIDTH-1:0] data;
data = req.data;
for (int i = 0; i < DATA_WIDTH / 8; i++) begin
if (req.be[i])
m_mem.write_byte(aligned_addr + i, data[7:0]);
data = data >> 8;
end
end
end
endtask : body
virtual function void inject_error();
this.enable_error = 1'b1;
endfunction
virtual function bit get_error_synch();
return this.error_synch;
endfunction
endclass : ibex_mem_intf_response_seq

View file

@ -1,27 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//------------------------------------------------------------------------------
// CLASS: ibex_mem_intf_response_sequencer
//------------------------------------------------------------------------------
class ibex_mem_intf_response_sequencer extends uvm_sequencer #(ibex_mem_intf_seq_item);
// TLM port to peek the address phase from the response monitor
uvm_tlm_analysis_fifo #(ibex_mem_intf_seq_item) addr_ph_port;
ibex_mem_intf_response_agent_cfg cfg;
`uvm_component_utils(ibex_mem_intf_response_sequencer)
function new (string name, uvm_component parent);
super.new(name, parent);
addr_ph_port = new("addr_ph_port_sequencer", this);
endfunction : new
// On reset, empty the tlm fifo
function void reset();
addr_ph_port.flush();
endfunction
endclass : ibex_mem_intf_response_sequencer

View file

@ -1,38 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//------------------------------------------------------------------------------
// CLASS: ibex_mem_intf_seq_item
//------------------------------------------------------------------------------
class ibex_mem_intf_seq_item extends uvm_sequence_item;
rand bit [ADDR_WIDTH-1:0] addr;
rand rw_e read_write;
rand bit [DATA_WIDTH-1:0] data;
rand bit [INTG_WIDTH-1:0] intg;
rand bit [DATA_WIDTH/8-1:0] be;
rand bit [3:0] gnt_delay;
rand bit [3:0] req_delay;
rand bit [5:0] rvalid_delay;
rand bit error;
bit misaligned_first;
bit misaligned_second;
`uvm_object_utils_begin(ibex_mem_intf_seq_item)
`uvm_field_int (addr, UVM_DEFAULT)
`uvm_field_enum (rw_e, read_write, UVM_DEFAULT)
`uvm_field_int (be, UVM_DEFAULT)
`uvm_field_int (data, UVM_DEFAULT)
`uvm_field_int (intg, UVM_DEFAULT)
`uvm_field_int (gnt_delay, UVM_DEFAULT)
`uvm_field_int (rvalid_delay, UVM_DEFAULT)
`uvm_field_int (error, UVM_DEFAULT)
`uvm_field_int (misaligned_first, UVM_DEFAULT)
`uvm_field_int (misaligned_second, UVM_DEFAULT)
`uvm_object_utils_end
`uvm_object_new
endclass : ibex_mem_intf_seq_item

View file

@ -1,21 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
package irq_agent_pkg;
import uvm_pkg::*;
parameter int DATA_WIDTH = 32;
parameter int ADDR_WIDTH = 32;
`include "uvm_macros.svh"
`include "irq_seq_item.sv"
typedef uvm_sequencer#(irq_seq_item) irq_request_sequencer;
`include "irq_monitor.sv"
`include "irq_request_driver.sv"
`include "irq_request_agent.sv"
endpackage

View file

@ -1,40 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
interface irq_if(input clk);
logic reset;
logic irq_software;
logic irq_timer;
logic irq_external;
logic [14:0] irq_fast;
logic irq_nm; // non-maskeable interrupt
clocking driver_cb @(posedge clk);
default output negedge;
input reset;
output irq_software;
output irq_timer;
output irq_external;
output irq_fast;
output irq_nm;
endclocking
clocking monitor_cb @(posedge clk);
input reset;
input irq_software;
input irq_timer;
input irq_external;
input irq_fast;
input irq_nm;
endclocking
task automatic wait_clks(input int num);
repeat (num) @(posedge clk);
endtask
task automatic wait_neg_clks(input int num);
repeat (num) @(negedge clk);
endtask
endinterface

View file

@ -1,72 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class irq_monitor extends uvm_monitor;
protected virtual irq_if vif;
uvm_analysis_port#(irq_seq_item) irq_port;
`uvm_component_utils(irq_monitor)
function new(string name, uvm_component parent=null);
super.new(name, parent);
irq_port = new("irq_port", this);
endfunction : new
function void build_phase(uvm_phase phase);
if (!uvm_config_db#(virtual irq_if)::get(this, "", "vif", vif)) begin
`uvm_fatal("NOVIF",{"virtual interface must be set for: ",get_full_name(),".vif"});
end
endfunction: build_phase
virtual task run_phase(uvm_phase phase);
forever begin
wait (vif.monitor_cb.reset === 1'b0);
fork : monitor_irq
collect_irq();
wait (vif.monitor_cb.reset === 1'b1);
join_any
// Will only reach here on mid-test reset
disable fork;
end
endtask : run_phase
// We know that for Ibex, any given interrupt stimulus will be asserted until the core signals the
// testbench that it has finished handling, and this stimulus will not change until the testbench
// receives the signal, at which point it will drop.
// Given this, as well as how the interrupt handshakes are designed, sending an irq_seq_item every
// cycle is not useful at all.
// In order to not send unnecessary sequence items, but to also send enough information that the
// testbench can handle nested interrupt scenarios, the monitor will send out a sequence
// item every time the interrupt lines change.
virtual protected task collect_irq();
irq_seq_item irq;
bit[DATA_WIDTH-1:0] stored_irq_val = '0;
bit[DATA_WIDTH-1:0] current_irq = '0;
forever begin
current_irq = {vif.monitor_cb.irq_nm,
vif.monitor_cb.irq_fast,
4'b0,
vif.monitor_cb.irq_external,
3'b0,
vif.monitor_cb.irq_timer,
3'b0,
vif.monitor_cb.irq_software,
3'b0};
if (current_irq !== stored_irq_val) begin
stored_irq_val = current_irq;
irq = irq_seq_item::type_id::create("irq");
irq.irq_software = vif.monitor_cb.irq_software;
irq.irq_timer = vif.monitor_cb.irq_timer;
irq.irq_external = vif.monitor_cb.irq_external;
irq.irq_fast = vif.monitor_cb.irq_fast;
irq.irq_nm = vif.monitor_cb.irq_nm;
irq_port.write(irq);
end
vif.wait_clks(1);
end
endtask : collect_irq
endclass : irq_monitor

View file

@ -1,29 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class irq_request_agent extends uvm_agent;
irq_request_driver driver;
irq_request_sequencer sequencer;
irq_monitor monitor;
`uvm_component_utils(irq_request_agent)
`uvm_component_new
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
monitor = irq_monitor::type_id::create("monitor", this);
if (get_is_active() == UVM_ACTIVE) begin
driver = irq_request_driver::type_id::create("driver", this);
sequencer = irq_request_sequencer::type_id::create("sequencer", this);
end
endfunction : build_phase
function void connect_phase(uvm_phase phase);
if (get_is_active() == UVM_ACTIVE) begin
driver.seq_item_port.connect(sequencer.seq_item_export);
end
endfunction : connect_phase
endclass : irq_request_agent

View file

@ -1,81 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class irq_request_driver extends uvm_driver #(irq_seq_item);
// The virtual interface used to drive and view HDL signals.
protected virtual irq_if vif;
`uvm_component_utils(irq_request_driver)
`uvm_component_new
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual irq_if)::get(this, "", "vif", vif)) begin
`uvm_fatal("NOVIF",{"virtual interface must be set for: ",get_full_name(),".vif"});
end
endfunction: build_phase
virtual task run_phase(uvm_phase phase);
reset_signals();
wait (vif.driver_cb.reset === 1'b0);
forever begin
fork : drive_irq
get_and_drive();
wait (vif.driver_cb.reset === 1'b1);
join_any
// Will only reach here on mid-test reset
disable fork;
handle_reset();
end
endtask : run_phase
virtual protected task handle_reset();
irq_seq_item req;
// Clear seq_item_port
do begin
seq_item_port.try_next_item(req);
if (req != null) begin
seq_item_port.item_done();
end
end while (req != null);
reset_signals();
endtask
virtual protected task get_and_drive();
forever begin
seq_item_port.try_next_item(req);
if (req != null) begin
$cast(rsp, req.clone());
rsp.set_id_info(req);
drive_seq_item(rsp);
seq_item_port.item_done(rsp);
end else begin
vif.wait_neg_clks(1);
end
end
endtask : get_and_drive
virtual protected task reset_signals();
@(posedge vif.driver_cb.reset);
drive_reset_value();
endtask : reset_signals
virtual protected task drive_seq_item (irq_seq_item trans);
vif.driver_cb.irq_software <= trans.irq_software;
vif.driver_cb.irq_timer <= trans.irq_timer;
vif.driver_cb.irq_external <= trans.irq_external;
vif.driver_cb.irq_fast <= trans.irq_fast;
vif.driver_cb.irq_nm <= trans.irq_nm;
endtask : drive_seq_item
task drive_reset_value();
vif.driver_cb.irq_software <= '0;
vif.driver_cb.irq_timer <= '0;
vif.driver_cb.irq_external <= '0;
vif.driver_cb.irq_fast <= '0;
vif.driver_cb.irq_nm <= '0;
endtask : drive_reset_value
endclass : irq_request_driver

View file

@ -1,33 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class irq_seq_item extends uvm_sequence_item;
rand bit irq_software;
rand bit irq_timer;
rand bit irq_external;
rand bit [14:0] irq_fast;
rand bit irq_nm;
rand int num_of_interrupt;
constraint num_of_interrupt_c {
num_of_interrupt inside {[0:DATA_WIDTH-1]};
$countones({irq_software, irq_timer, irq_external, irq_fast, irq_nm}) == num_of_interrupt;
}
constraint bring_up_c {
soft num_of_interrupt == 1;
}
`uvm_object_utils_begin(irq_seq_item)
`uvm_field_int(irq_software, UVM_DEFAULT)
`uvm_field_int(irq_timer, UVM_DEFAULT)
`uvm_field_int(irq_external, UVM_DEFAULT)
`uvm_field_int(irq_fast, UVM_DEFAULT)
`uvm_field_int(irq_nm, UVM_DEFAULT)
`uvm_object_utils_end
`uvm_object_new
endclass : irq_seq_item

View file

@ -1,24 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// Abstract primitives wrapper.
//
// This file is a stop-gap until the DV file list is generated by FuseSoC.
// Its contents are taken from the file which would be generated by FuseSoC.
// https://github.com/lowRISC/ibex/issues/893
module prim_buf #(
parameter int Width = 1
) (
input [Width-1:0] in_i,
output logic [Width-1:0] out_o
);
if (1) begin : gen_generic
prim_generic_buf#(.Width(Width)) u_impl_generic (
.*
);
end
endmodule

View file

@ -1,35 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// Abstract primitives wrapper.
//
// This file is a stop-gap until the DV file list is generated by FuseSoC.
// Its contents are taken from the file which would be generated by FuseSoC.
// https://github.com/lowRISC/ibex/issues/893
`ifndef PRIM_DEFAULT_IMPL
`define PRIM_DEFAULT_IMPL prim_pkg::ImplGeneric
`endif
module prim_clock_gating (
input clk_i,
input en_i,
input test_en_i,
output logic clk_o
);
parameter prim_pkg::impl_e Impl = `PRIM_DEFAULT_IMPL;
if (Impl == prim_pkg::ImplGeneric) begin : gen_generic
prim_generic_clock_gating u_impl_generic (
.*
);
end else if (Impl == prim_pkg::ImplXilinx) begin : gen_xilinx
prim_xilinx_clock_gating u_impl_xilinx (
.*
);
end else begin : gen_failure
// TODO: Find code that works across tools and causes a compile failure
end
endmodule

View file

@ -1,28 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// Abstract primitives wrapper.
//
// This file is a stop-gap until the DV file list is generated by FuseSoC.
// Its contents are taken from the file which would be generated by FuseSoC.
// https://github.com/lowRISC/ibex/issues/893
module prim_clock_mux2 #(
parameter bit NoFpgaBufG = 1'b0
) (
input clk0_i,
input clk1_i,
input sel_i,
output logic clk_o
);
if (1) begin : gen_generic
prim_generic_clock_mux2 #(
.NoFpgaBufG(NoFpgaBufG)
) u_impl_generic (
.*
);
end
endmodule

View file

@ -1,30 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// Abstract primitives wrapper.
//
// This file is a stop-gap until the DV file list is generated by FuseSoC.
// Its contents are taken from the file which would be generated by FuseSoC.
// https://github.com/lowRISC/ibex/issues/893
module prim_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = 0
) (
input clk_i,
input rst_ni,
input [Width-1:0] d_i,
output logic [Width-1:0] q_o
);
if (1) begin : gen_generic
prim_generic_flop #(
.ResetValue(ResetValue),
.Width(Width)
) u_impl_generic (
.*
);
end
endmodule

View file

@ -1,18 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Constants for use in primitives
//
// This file is a stop-gap until the DV file list is generated by FuseSoC.
// Its contents are taken from the file which would be generated by FuseSoC.
// https://github.com/lowRISC/ibex/issues/893
package prim_pkg;
// Implementation target specialization
typedef enum integer {
ImplGeneric,
ImplXilinx
} impl_e;
endpackage : prim_pkg

View file

@ -1,46 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// Abstract primitives wrapper.
//
// This file is a stop-gap until the DV file list is generated by FuseSoC.
// Its contents are taken from the file which would be generated by FuseSoC.
// https://github.com/lowRISC/ibex/issues/893
module prim_ram_1p import prim_ram_1p_pkg::*;
#(
parameter int Width = 32, // bit
parameter int Depth = 128,
parameter int DataBitsPerMask = 1, // Number of data bits per bit of write mask
parameter MemInitFile = "", // VMEM file to initialize the memory width
localparam int Aw = $clog2(Depth) // derived parameter
) (
input logic clk_i,
input ram_1p_cfg_t cfg_i,
input logic req_i,
input logic write_i,
input logic [Aw-1:0] addr_i,
input logic [Width-1:0] wdata_i,
input logic [Width-1:0] wmask_i,
output logic [Width-1:0] rdata_o // Read data. Data is returned one cycle after req_i is high.
);
if (1) begin : gen_generic
prim_generic_ram_1p #(
.Depth(Depth),
.MemInitFile(MemInitFile),
.Width(Width),
.DataBitsPerMask(DataBitsPerMask)
) u_impl_generic (
.*
);
end
endmodule

View file

@ -1,264 +0,0 @@
#!/usr/bin/env python3
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
'''
A script to compare an ISS and RTL run to make sure nothing has diverged.
'''
import argparse
import os
import re
import sys
from typing import Dict, Optional, TextIO, Tuple, Union
from test_entry import TestEntry, get_test_entry
from test_run_result import TestRunResult
_CORE_IBEX = os.path.normpath(os.path.join(os.path.dirname(__file__)))
_IBEX_ROOT = os.path.normpath(os.path.join(_CORE_IBEX, '../../..'))
_RISCV_DV_ROOT = os.path.join(_IBEX_ROOT, 'vendor/google_riscv-dv')
_OLD_SYS_PATH = sys.path
# Import riscv_trace_csv and lib from _DV_SCRIPTS before putting sys.path back
# as it started.
try:
sys.path = ([os.path.join(_CORE_IBEX, 'riscv_dv_extension'),
os.path.join(_RISCV_DV_ROOT, 'scripts')] +
sys.path)
from spike_log_to_trace_csv import process_spike_sim_log # type: ignore
from ovpsim_log_to_trace_csv import process_ovpsim_sim_log # type: ignore
from instr_trace_compare import compare_trace_csv # type: ignore
from ibex_log_to_trace_csv import (process_ibex_sim_log, # type: ignore
check_ibex_uvm_log)
finally:
sys.path = _OLD_SYS_PATH
_TestAndSeed = Tuple[str, int]
_CompareResult = Tuple[bool, Optional[str], Dict[str, str]]
def read_test_dot_seed(arg: str) -> _TestAndSeed:
'''Read a value for --test-dot-seed'''
match = re.match(r'([^.]+)\.([0-9]+)$', arg)
if match is None:
raise argparse.ArgumentTypeError('Bad --test-dot-seed ({}): '
'should be of the form TEST.SEED.'
.format(arg))
return (match.group(1), int(match.group(2), 10))
def compare_test_run(test: TestEntry,
idx: int,
seed: int,
rtl_log_dir: str,
iss: str,
iss_log_dir: str,
instr_gen_bin_dir: str) -> TestRunResult:
'''Compare results for a single run of a single test
Here, test is a dictionary describing the test (read from the testlist YAML
file). idx is the iteration index and seed is the corresponding seed. iss
is the chosen instruction set simulator (currently supported: spike and
ovpsim).
rtl_log_dir is the directory containing log output from the RTL simulation.
iss_log_dir is the directory that contains logs for ISS runs.
Returns a _CompareResult with a pass/fail flag, together with some
information about the run (to be written to the log file).
'''
test_name = test['test']
assert isinstance(test_name, str)
uvm_log = os.path.join(rtl_log_dir, 'sim.log')
elf = os.path.join(instr_gen_bin_dir, '{}_{}.o'.format(test_name, idx))
rtl_trace = os.path.join(rtl_log_dir, 'trace_core_00000000.log')
kv_data = {
'name': test_name,
'idx': idx,
'seed': seed,
'binary': elf,
'uvm_log': uvm_log,
'rtl_trace': rtl_trace,
'rtl_trace_csv': None,
'iss_trace': None,
'iss_trace_csv': None,
'comparison_log': None,
'passed': False,
'failure_message': None
}
# Have a look at the UVM log. Report a failure if an issue is seen in the
# log.
try:
uvm_pass, uvm_log_lines = check_ibex_uvm_log(uvm_log)
except IOError as e:
kv_data['failure_message'] = str(e)
kv_data['failure_message'] += \
'\n[FAILED] Could not open simulation log'
return TestRunResult(**kv_data)
if not uvm_pass:
kv_data['failure_message'] = '\n'.join(uvm_log_lines)
kv_data['failure_message'] += '\n[FAILED]: sim error seen'
return TestRunResult(**kv_data)
rtl_trace_csv = os.path.join(rtl_log_dir, 'trace_core_00000000.csv')
kv_data['rtl_trace_csv'] = rtl_trace_csv
try:
# Convert the RTL log file to a trace CSV.
process_ibex_sim_log(rtl_trace, rtl_trace_csv, 1)
except (OSError, RuntimeError) as e:
kv_data['failure_message'] = \
'[FAILED]: Log processing failed: {}'.format(e)
return TestRunResult(**kv_data)
no_post_compare = test.get('no_post_compare', False)
assert isinstance(no_post_compare, bool)
# no_post_compare skips the final ISS v RTL log check, so if we've reached
# here we're done when no_post_compare is set.
if no_post_compare:
kv_data['passed'] = True
return TestRunResult(**kv_data)
# There were no UVM errors. Process the log file from the ISS.
iss_log = os.path.join(iss_log_dir, '{}.{}.log'.format(test_name, idx))
iss_csv = os.path.join(iss_log_dir, '{}.{}.csv'.format(test_name, idx))
kv_data['iss_trace'] = iss_log
kv_data['iss_trace_csv'] = iss_csv
try:
if iss == "spike":
process_spike_sim_log(iss_log, iss_csv)
else:
assert iss == 'ovpsim' # (should be checked by argparse)
process_ovpsim_sim_log(iss_log, iss_csv)
except (OSError, RuntimeError) as e:
kv_data['failure_message'] = \
'[FAILED]: Log processing failed: {}'.format(e)
return TestRunResult(**kv_data)
compare_log = os.path.join(rtl_log_dir, 'compare.log')
kv_data['comparison_log'] = compare_log
# Delete any existing file at compare_log (the compare_trace_csv function
# would append to it, which is rather confusing).
try:
os.remove(compare_log)
except FileNotFoundError:
pass
compare_result = \
compare_trace_csv(rtl_trace_csv, iss_csv, "ibex", iss, compare_log,
**test.get('compare_opts', {}))
try:
compare_log_file = open(compare_log)
compare_log_contents = compare_log_file.read()
compare_log_file.close()
except IOError as e:
kv_data['failure_message'] = \
'[FAILED]: Could not read compare log: {}'.format(e)
return TestRunResult(**kv_data)
# Rather oddly, compare_result is a string. The comparison passed if it
# starts with '[PASSED]' and failed otherwise.
compare_passed = compare_result.startswith('[PASSED]: ')
if not compare_passed:
assert compare_result.startswith('[FAILED]: ')
kv_data['failure_message'] = ('RTL / ISS trace comparison failed\n' +
compare_log_contents)
return TestRunResult(**kv_data)
kv_data['passed'] = True
return TestRunResult(**kv_data)
# If any of these characters are present in a string output it in multi-line
# mode. This will either be because the string contains newlines or other
# characters that would otherwise need escaping
_YAML_MULTILINE_CHARS = ['[', ']', ':', "'", '"', '\n']
def yaml_format(val: Union[int, str, bool]) -> str:
'''Format a value for yaml output.
For int, str and bool value can just be converted to str with special
handling for some string
'''
# If val is a multi-line string
if isinstance(val, str) and any(c in val for c in _YAML_MULTILINE_CHARS):
# Split into individual lines and output them after a suitable yaml
# multi-line string indicator ('|-') indenting each line.
lines = val.split('\n')
return '|-\n' + '\n'.join([f' {line}' for line in lines])
if val is None:
return ''
return str(val)
def on_result(result: TestRunResult, output: TextIO) -> None:
kv_data = result._asdict()
klen = 1
for k in kv_data:
klen = max(klen, len(k))
for k, v in kv_data.items():
kpad = ' ' * (klen - len(k))
output.write(f'{k}:{kpad} {yaml_format(v)}\n')
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument('--instr-gen-bin-dir', required=True)
parser.add_argument('--iss', required=True, choices=['spike', 'ovpsim'])
parser.add_argument('--iss-log-dir', required=True)
parser.add_argument('--start-seed', type=int, required=True)
parser.add_argument('--test-dot-seed',
type=read_test_dot_seed,
required=True)
parser.add_argument('--rtl-log-dir', required=True)
parser.add_argument('--output', required=True)
args = parser.parse_args()
if args.start_seed < 0:
raise RuntimeError('Invalid start seed: {}'.format(args.start_seed))
testname, seed = args.test_dot_seed
if seed < args.start_seed:
raise RuntimeError('Start seed is greater than test seed '
f'({args.start_seed} > {seed}).')
iteration = seed - args.start_seed
entry = get_test_entry(testname)
result = compare_test_run(entry, iteration, seed,
args.rtl_log_dir, args.iss, args.iss_log_dir,
args.instr_gen_bin_dir)
with open(args.output, 'w', encoding='UTF-8') as outfile:
on_result(result, outfile)
# Always return 0 (success), even if the test failed. We've successfully
# generated a comparison log either way and we don't want to stop Make from
# gathering them all up for us.
return 0
if __name__ == '__main__':
sys.exit(main())

View file

@ -1,4 +0,0 @@
+tree core_ibex_tb_top.dut
begin tgl
-tree core_ibex_tb_top.dut.*
end

View file

@ -1,21 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// Interface to probe CSR accesses
interface core_ibex_csr_if(input logic clk);
logic csr_access;
ibex_pkg::csr_num_e csr_addr;
logic [31:0] csr_wdata;
logic [31:0] csr_rdata;
ibex_pkg::csr_op_e csr_op;
clocking csr_cb @(posedge clk);
input csr_access;
input csr_addr;
input csr_wdata;
input csr_rdata;
input csr_op;
endclocking
endinterface

View file

@ -1,43 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// Interface to probe DUT internal signal
interface core_ibex_dut_probe_if(input logic clk);
logic reset;
logic illegal_instr;
logic ecall;
logic wfi;
logic ebreak;
logic dret;
logic mret;
ibex_pkg::fetch_enable_t fetch_enable;
logic core_sleep;
logic alert_minor;
logic alert_major_internal;
logic alert_major_bus;
logic debug_req;
ibex_pkg::priv_lvl_e priv_mode;
clocking dut_cb @(posedge clk);
output fetch_enable;
output debug_req;
input reset;
input illegal_instr;
input ecall;
input wfi;
input ebreak;
input dret;
input mret;
input core_sleep;
input alert_minor;
input alert_major_internal;
input alert_major_bus;
input priv_mode;
endclocking
initial begin
debug_req = 1'b0;
end
endinterface

View file

@ -1,65 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// ---------------------------------------------
// ibex processor core environment class
// ---------------------------------------------
class core_ibex_env extends uvm_env;
ibex_mem_intf_response_agent data_if_response_agent;
ibex_mem_intf_response_agent instr_if_response_agent;
irq_request_agent irq_agent;
`ifdef INC_IBEX_COSIM
ibex_cosim_agent cosim_agent;
`endif
core_ibex_vseqr vseqr;
core_ibex_env_cfg cfg;
`uvm_component_utils(core_ibex_env)
`uvm_component_new
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(core_ibex_env_cfg)::get(this, "", "cfg", cfg)) begin
`uvm_fatal(get_full_name(), "Cannot get cfg")
end
data_if_response_agent = ibex_mem_intf_response_agent::type_id::
create("data_if_response_agent", this);
instr_if_response_agent = ibex_mem_intf_response_agent::type_id::
create("instr_if_response_agent", this);
irq_agent = irq_request_agent::type_id::create("irq_agent", this);
`ifdef INC_IBEX_COSIM
if (!cfg.disable_cosim) begin
cosim_agent = ibex_cosim_agent::type_id::create("cosim_agent", this);
end else begin
cosim_agent = null;
end
`endif
// Create virtual sequencer
vseqr = core_ibex_vseqr::type_id::create("vseqr", this);
endfunction : build_phase
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
vseqr.data_if_seqr = data_if_response_agent.sequencer;
vseqr.instr_if_seqr = instr_if_response_agent.sequencer;
vseqr.irq_seqr = irq_agent.sequencer;
`ifdef INC_IBEX_COSIM
if (cosim_agent != null) begin
data_if_response_agent.monitor.item_collected_port.connect(
cosim_agent.dmem_port);
instr_if_response_agent.monitor.item_collected_port.connect(
cosim_agent.imem_port);
end
`endif
endfunction : connect_phase
function void reset();
data_if_response_agent.reset();
instr_if_response_agent.reset();
endfunction
endclass

View file

@ -1,44 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
class core_ibex_env_cfg extends uvm_object;
bit enable_irq_single_seq;
bit enable_irq_multiple_seq;
bit enable_irq_nmi_seq;
bit enable_nested_irq;
bit enable_debug_seq;
bit disable_cosim;
bit[31:0] max_interval;
bit require_signature_addr;
string signature_addr_str;
bit[31:0] signature_addr;
`uvm_object_utils_begin(core_ibex_env_cfg)
`uvm_field_int(enable_irq_single_seq, UVM_DEFAULT)
`uvm_field_int(enable_irq_multiple_seq, UVM_DEFAULT)
`uvm_field_int(enable_irq_nmi_seq, UVM_DEFAULT)
`uvm_field_int(enable_nested_irq, UVM_DEFAULT)
`uvm_field_int(enable_debug_seq, UVM_DEFAULT)
`uvm_field_int(disable_cosim, UVM_DEFAULT)
`uvm_field_int(max_interval, UVM_DEFAULT)
`uvm_field_int(require_signature_addr, UVM_DEFAULT)
`uvm_field_int(signature_addr, UVM_DEFAULT)
`uvm_object_utils_end
function new(string name = "");
super.new(name);
void'($value$plusargs("enable_irq_single_seq=%0d", enable_irq_single_seq));
void'($value$plusargs("enable_irq_multiple_seq=%0d", enable_irq_multiple_seq));
void'($value$plusargs("enable_irq_nmi_seq=%0d", enable_irq_nmi_seq));
void'($value$plusargs("enable_nested_irq=%0d", enable_nested_irq));
void'($value$plusargs("enable_debug_seq=%0d", enable_debug_seq));
void'($value$plusargs("disable_cosim=%0d", disable_cosim));
void'($value$plusargs("max_interval=%0d", max_interval));
void'($value$plusargs("require_signature_addr=%0d", require_signature_addr));
void'($value$plusargs("signature_addr=%s", signature_addr_str));
signature_addr = signature_addr_str.atohex();
endfunction
endclass

View file

@ -1,20 +0,0 @@
// Copyright 2018 lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// ---------------------------------------------
// Core ibex environment package
// ---------------------------------------------
package core_ibex_env_pkg;
import uvm_pkg::*;
import ibex_mem_intf_agent_pkg::*;
import irq_agent_pkg::*;
import ibex_cosim_agent_pkg::*;
`include "core_ibex_vseqr.sv"
`include "core_ibex_env_cfg.sv"
`include "core_ibex_env.sv"
endpackage

View file

@ -1,46 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// Interface to probe the instruction moving through the pipeline
//
// TODO: does not support dummy instruction insertion right now,
// might have to revisit and update.
interface core_ibex_instr_monitor_if #(
parameter DATA_WIDTH = 32
) (
input clk
);
// ID stage
logic reset;
logic valid_id;
logic instr_new_id;
logic err_id;
logic is_compressed_id;
logic [15:0] instr_compressed_id;
logic [DATA_WIDTH-1:0] instr_id;
logic [DATA_WIDTH-1:0] pc_id;
logic branch_taken_id;
logic [DATA_WIDTH-1:0] branch_target_id;
logic stall_id;
logic jump_set_id;
logic [63:0] rvfi_order_id;
clocking instr_cb @(posedge clk);
input reset;
input valid_id;
input instr_new_id;
input err_id;
input is_compressed_id;
input instr_compressed_id;
input instr_id;
input pc_id;
input branch_taken_id;
input branch_target_id;
input stall_id;
input jump_set_id;
input rvfi_order_id;
endclocking
endinterface

View file

@ -1,66 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// Interface to probe Ibex RVFI interface
interface core_ibex_rvfi_if(input logic clk);
logic reset;
logic valid;
logic [63:0] order;
logic [31:0] insn;
logic trap;
logic halt;
logic intr;
logic [1:0] mode;
logic [1:0] ixl;
logic [4:0] rs1_addr;
logic [4:0] rs2_addr;
logic [31:0] rs1_rdata;
logic [31:0] rs2_rdata;
logic [4:0] rd_addr;
logic [31:0] rd_wdata;
logic [31:0] pc_rdata;
logic [31:0] pc_wdata;
logic [31:0] mem_addr;
logic [3:0] mem_rmask;
logic [3:0] mem_wmask;
logic [31:0] mem_rdata;
logic [31:0] mem_wdata;
logic [31:0] ext_mip;
logic ext_nmi;
logic [31:0] ext_debug_req;
logic [63:0] ext_mcycle;
clocking monitor_cb @(posedge clk);
input reset;
input valid;
input order;
input insn;
input trap;
input halt;
input intr;
input mode;
input ixl;
input rs1_addr;
input rs2_addr;
input rs1_rdata;
input rs2_rdata;
input rd_addr;
input rd_wdata;
input pc_rdata;
input pc_wdata;
input mem_addr;
input mem_rmask;
input mem_wmask;
input mem_rdata;
input mem_wdata;
input ext_mip;
input ext_nmi;
input ext_debug_req;
input ext_mcycle;
endclocking
task automatic wait_clks(input int num);
repeat (num) @(posedge clk);
endtask
endinterface

View file

@ -1,17 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// ---------------------------------------------
// Core ibex environment virtual sequencer
// ---------------------------------------------
class core_ibex_vseqr extends uvm_sequencer;
ibex_mem_intf_response_sequencer data_if_seqr;
ibex_mem_intf_response_sequencer instr_if_seqr;
irq_request_sequencer irq_seqr;
`uvm_component_utils(core_ibex_vseqr)
`uvm_component_new
endclass

View file

@ -1,9 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
module core_ibex_fcov_bind;
bind ibex_core core_ibex_fcov_if u_fcov_bind (
.*
);
endmodule

View file

@ -1,314 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
`include "prim_assert.sv"
interface core_ibex_fcov_if import ibex_pkg::*; (
input clk_i,
input rst_ni,
input priv_lvl_e priv_mode_id,
input priv_lvl_e priv_mode_lsu
);
`include "dv_fcov_macros.svh"
import uvm_pkg::*;
typedef enum {
InstrCategoryALU,
InstrCategoryMul,
InstrCategoryDiv,
InstrCategoryBranch,
InstrCategoryJump,
InstrCategoryLoad,
InstrCategoryStore,
InstrCategoryOther,
InstrCategoryCSRAccess,
InstrCategoryEBreakDbg,
InstrCategoryEBreakExc,
InstrCategoryECall,
InstrCategoryMRet,
InstrCategoryDRet,
InstrCategoryWFI,
InstrCategoryFenceI,
InstrCategoryNone,
InstrCategoryFetchError,
InstrCategoryCompressedIllegal,
InstrCategoryUncompressedIllegal,
InstrCategoryCSRIllegal,
InstrCategoryOtherIllegal
} instr_category_e;
typedef enum {
IdStallTypeNone,
IdStallTypeInstr,
IdStallTypeLdHz,
IdStallTypeMem
} id_stall_type_e;
instr_category_e id_instr_category;
// Set `id_instr_category` to the appropriate category for the uncompressed instruction in the
// ID/EX stage. Compressed instructions are not handled (`id_stage_i.instr_rdata_i` is always
// uncompressed). When the `id_stage.instr_valid_i` isn't set `InstrCategoryNone` is the given
// instruction category.
always_comb begin
id_instr_category = InstrCategoryOther;
case (id_stage_i.instr_rdata_i[6:0])
ibex_pkg::OPCODE_LUI: id_instr_category = InstrCategoryALU;
ibex_pkg::OPCODE_AUIPC: id_instr_category = InstrCategoryALU;
ibex_pkg::OPCODE_JAL: id_instr_category = InstrCategoryJump;
ibex_pkg::OPCODE_JALR: id_instr_category = InstrCategoryJump;
ibex_pkg::OPCODE_BRANCH: id_instr_category = InstrCategoryBranch;
ibex_pkg::OPCODE_LOAD: id_instr_category = InstrCategoryLoad;
ibex_pkg::OPCODE_STORE: id_instr_category = InstrCategoryStore;
ibex_pkg::OPCODE_OP_IMM: id_instr_category = InstrCategoryALU;
ibex_pkg::OPCODE_OP: begin
if ({id_stage_i.instr_rdata_i[26], id_stage_i.instr_rdata_i[13:12]} == {1'b1, 2'b01}) begin
id_instr_category = InstrCategoryALU; // reg-reg/reg-imm ops
end else if (id_stage_i.instr_rdata_i[31:25] inside {7'b000_0000, 7'b010_0000, 7'b011_0000,
7'b011_0100, 7'b001_0100, 7'b001_0000, 7'b000_0101, 7'b000_0100, 7'b010_0100}) begin
id_instr_category = InstrCategoryALU; // RV32I and RV32B reg-reg/reg-imm ops
end else if (id_stage_i.instr_rdata_i[31:25] == 7'b000_0001) begin
if (id_stage_i.instr_rdata_i[14]) begin
id_instr_category = InstrCategoryDiv; // DIV*
end else begin
id_instr_category = InstrCategoryMul; // MUL*
end
end
end
ibex_pkg::OPCODE_SYSTEM: begin
if (id_stage_i.instr_rdata_i[14:12] == 3'b000) begin
case (id_stage_i.instr_rdata_i[31:20])
12'h000: id_instr_category = InstrCategoryECall;
12'h001: begin
if (id_stage_i.debug_ebreakm_i && priv_mode_id == PRIV_LVL_M) begin
id_instr_category = InstrCategoryEBreakDbg;
end else if (id_stage_i.debug_ebreaku_i && priv_mode_id == PRIV_LVL_U) begin
id_instr_category = InstrCategoryEBreakDbg;
end else begin
id_instr_category = InstrCategoryEBreakExc;
end
end
12'h302: id_instr_category = InstrCategoryMRet;
12'h7b2: id_instr_category = InstrCategoryDRet;
12'h105: id_instr_category = InstrCategoryWFI;
endcase
end else begin
id_instr_category = InstrCategoryCSRAccess;
end
end
ibex_pkg::OPCODE_MISC_MEM: begin
if (id_stage_i.instr_rdata_i[14:12] == 3'b001) begin
id_instr_category = InstrCategoryFenceI;
end
end
default: id_instr_category = InstrCategoryOther;
endcase
if (id_stage_i.instr_valid_i) begin
if (id_stage_i.instr_fetch_err_i) begin
id_instr_category = InstrCategoryFetchError;
end else if (id_stage_i.illegal_c_insn_i) begin
id_instr_category = InstrCategoryCompressedIllegal;
end else if (id_stage_i.illegal_insn_dec) begin
id_instr_category = InstrCategoryUncompressedIllegal;
end else if (id_stage_i.illegal_csr_insn_i) begin
id_instr_category = InstrCategoryCSRIllegal;
end else if (id_stage_i.illegal_insn_o) begin
id_instr_category = InstrCategoryOtherIllegal;
end
end else begin
id_instr_category = InstrCategoryNone;
end
end
// Check instruction categories calculated from instruction bits match what decoder has produced.
// The ALU category is tricky as there's no specific ALU enable and instructions that actively use
// the result of the ALU but aren't themselves ALU operations (such as load/store and JALR). This
// categorizes anything that selects the ALU as the source of register write data and enables
// register writes minus some exclusions as an ALU operation.
`ASSERT(InstrCategoryALUCorrect, id_instr_category == InstrCategoryALU |->
(id_stage_i.rf_wdata_sel == RF_WD_EX) && id_stage_i.rf_we_dec && ~id_stage_i.mult_sel_ex_o &&
~id_stage_i.div_sel_ex_o && ~id_stage_i.lsu_req_dec && ~id_stage_i.jump_in_dec);
`ASSERT(InstrCategoryMulCorrect,
id_instr_category == InstrCategoryMul |-> id_stage_i.mult_sel_ex_o)
`ASSERT(InstrCategoryDivCorrect,
id_instr_category == InstrCategoryDiv |-> id_stage_i.div_sel_ex_o)
`ASSERT(InstrCategoryBranchCorrect,
id_instr_category == InstrCategoryBranch |-> id_stage_i.branch_in_dec)
`ASSERT(InstrCategoryJumpCorrect,
id_instr_category == InstrCategoryJump |-> id_stage_i.jump_in_dec)
`ASSERT(InstrCategoryLoadCorrect,
id_instr_category == InstrCategoryLoad |-> id_stage_i.lsu_req_dec && !id_stage_i.lsu_we)
`ASSERT(InstrCategoryStoreCorrect,
id_instr_category == InstrCategoryStore |-> id_stage_i.lsu_req_dec && id_stage_i.lsu_we)
`ASSERT(InstrCategoryCSRAccessCorrect,
id_instr_category == InstrCategoryCSRAccess |-> id_stage_i.csr_access_o)
`ASSERT(InstrCategoryEBreakDbgCorrect, id_instr_category == InstrCategoryEBreakDbg |->
id_stage_i.ebrk_insn && id_stage_i.controller_i.ebreak_into_debug)
`ASSERT(InstrCategoryEBreakExcCorrect, id_instr_category == InstrCategoryEBreakExc |->
id_stage_i.ebrk_insn && !id_stage_i.controller_i.ebreak_into_debug)
`ASSERT(InstrCategoryECallCorrect,
id_instr_category == InstrCategoryECall |-> id_stage_i.ecall_insn_dec)
`ASSERT(InstrCategoryMRetCorrect,
id_instr_category == InstrCategoryMRet |-> id_stage_i.mret_insn_dec)
`ASSERT(InstrCategoryDRetCorrect,
id_instr_category == InstrCategoryDRet |-> id_stage_i.dret_insn_dec)
`ASSERT(InstrCategoryWFICorrect,
id_instr_category == InstrCategoryWFI |-> id_stage_i.wfi_insn_dec)
`ASSERT(InstrCategoryFenceICorrect,
id_instr_category == InstrCategoryFenceI && id_stage_i.instr_first_cycle |->
id_stage_i.icache_inval_o)
id_stall_type_e id_stall_type;
// Set `id_stall_type` to the appropriate type based on signals in the ID/EX stage
always_comb begin
id_stall_type = IdStallTypeNone;
if (id_stage_i.instr_valid_i) begin
if (id_stage_i.stall_mem) begin
id_stall_type = IdStallTypeMem;
end
if (id_stage_i.stall_ld_hz) begin
id_stall_type = IdStallTypeLdHz;
end
if (id_stage_i.stall_multdiv || id_stage_i.stall_branch ||
id_stage_i.stall_jump) begin
id_stall_type = IdStallTypeInstr;
end
end
end
logic instr_unstalled;
logic instr_unstalled_last;
logic id_stall_type_last_valid;
id_stall_type_e id_stall_type_last;
instr_category_e id_instr_category_last;
// Keep track of previous values for some signals. These are used for some of the crosses relating
// to exception and debug entry. We want to cross different instruction categories and stalling
// behaviour with exception and debug entry but signals indicating entry occur a cycle after the
// relevant information is flushed from the pipeline.
always @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
// First cycle out of reset there is no last stall, use valid bit to deal with this case
id_stall_type_last_valid <= 1'b0;
id_stall_type_last <= IdStallTypeNone;
instr_unstalled_last <= 1'b0;
id_instr_category_last <= InstrCategoryNone;
end else begin
id_stall_type_last_valid <= 1'b1;
id_stall_type_last <= id_stall_type;
instr_unstalled_last <= instr_unstalled;
id_instr_category_last <= id_instr_category;
end
end
assign instr_unstalled =
(id_stall_type == IdStallTypeNone) && (id_stall_type_last != IdStallTypeNone) &&
id_stall_type_last_valid;
covergroup uarch_cg @(posedge clk_i);
cp_instr_category_id: coverpoint id_instr_category;
cp_stall_type_id: coverpoint id_stall_type;
cp_wb_reg_hz: coverpoint id_stage_i.fcov_rf_rd_wb_hz;
cp_wb_load_hz: coverpoint id_stage_i.fcov_rf_rd_wb_hz &&
wb_stage_i.outstanding_load_wb_o;
cp_ls_error_exception: coverpoint load_store_unit_i.fcov_ls_error_exception;
cp_ls_pmp_exception: coverpoint load_store_unit_i.fcov_ls_pmp_exception;
cp_branch_taken: coverpoint id_stage_i.fcov_branch_taken;
cp_branch_not_taken: coverpoint id_stage_i.fcov_branch_not_taken;
cp_priv_mode_id: coverpoint priv_mode_id {
illegal_bins illegal = {PRIV_LVL_H, PRIV_LVL_S};
}
cp_priv_mode_lsu: coverpoint priv_mode_lsu {
illegal_bins illegal = {PRIV_LVL_H, PRIV_LVL_S};
}
cp_irq_pending: coverpoint id_stage_i.irq_pending_i | id_stage_i.irq_nm_i;
cp_debug_req: coverpoint id_stage_i.controller_i.fcov_debug_req;
`DV_FCOV_EXPR_SEEN(interrupt_taken, id_stage_i.controller_i.fcov_interrupt_taken)
`DV_FCOV_EXPR_SEEN(debug_entry_if, id_stage_i.controller_i.fcov_debug_entry_if)
`DV_FCOV_EXPR_SEEN(debug_entry_id, id_stage_i.controller_i.fcov_debug_entry_id)
`DV_FCOV_EXPR_SEEN(pipe_flush, id_stage_i.controller_i.fcov_pipe_flush)
priv_mode_instr_cross: cross cp_priv_mode_id, cp_instr_category_id;
stall_cross: cross cp_instr_category_id, cp_stall_type_id {
illegal_bins illegal =
// Only Div, Mul, Branch and Jump instructions can see an instruction stall
(!binsof(cp_instr_category_id) intersect {InstrCategoryDiv, InstrCategoryMul,
InstrCategoryBranch, InstrCategoryJump} &&
binsof(cp_stall_type_id) intersect {IdStallTypeInstr})
||
// Only ALU, Mul, Div, Branch, Jump, Load, Store and CSR Access can see a load hazard stall
(!binsof(cp_instr_category_id) intersect {InstrCategoryALU, InstrCategoryMul,
InstrCategoryDiv, InstrCategoryBranch,
InstrCategoryJump, InstrCategoryLoad,
InstrCategoryStore, InstrCategoryCSRAccess} &&
binsof(cp_stall_type_id) intersect {IdStallTypeLdHz});
}
wb_reg_hz_instr_cross: cross cp_instr_category_id, cp_wb_reg_hz;
pipe_cross: cross cp_instr_category_id, if_stage_i.if_instr_valid,
wb_stage_i.fcov_wb_valid;
interrupt_taken_instr_cross: cross cp_interrupt_taken, instr_unstalled_last, id_instr_category_last {
illegal_bins illegal = binsof(instr_unstalled_last) intersect {0} &&
binsof(id_instr_category_last) intersect {InstrCategoryDiv};
}
debug_entry_if_instr_cross: cross cp_debug_entry_if, instr_unstalled_last, id_instr_category_last;
pipe_flush_instr_cross: cross cp_pipe_flush, instr_unstalled, cp_instr_category_id;
exception_stall_instr_cross: cross cp_ls_pmp_exception, cp_ls_error_exception,
cp_instr_category_id, cp_stall_type_id, instr_unstalled, cp_irq_pending, cp_debug_req {
illegal_bins illegal =
// Only Div, Mul, Branch and Jump instructions can see an instruction stall
(!binsof(cp_instr_category_id) intersect {InstrCategoryDiv, InstrCategoryMul,
InstrCategoryBranch, InstrCategoryJump} &&
binsof(cp_stall_type_id) intersect {IdStallTypeInstr})
||
// Only ALU, Mul, Div, Branch, Jump, Load, Store and CSR Access can see a load hazard stall
(!binsof(cp_instr_category_id) intersect {InstrCategoryALU, InstrCategoryMul,
InstrCategoryDiv, InstrCategoryBranch,
InstrCategoryJump, InstrCategoryLoad,
InstrCategoryStore, InstrCategoryCSRAccess} &&
binsof(cp_stall_type_id) intersect {IdStallTypeLdHz});
}
endgroup
bit en_uarch_cov;
initial begin
void'($value$plusargs("enable_uarch_cov=%d", en_uarch_cov));
end
`DV_FCOV_INSTANTIATE_CG(uarch_cg, en_uarch_cov)
endinterface

View file

@ -1,132 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// The boot address is specified as a hex number and is used in SystemVerilog
// code like this: 32'h `BOOT_ADDR.
//
// We can't put a ' character in the constant, because Riviera's TCL machinery
// doesn't support them in defines. We also can't just use the decimal value
// (2147483648), because an undecorated integer literal is interpreted as a
// signed 32-bit integer.
+define+BOOT_ADDR=8000_0000
+define+TRACE_EXECUTION
+define+RVFI
// Shared lowRISC code
+incdir+${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_assert.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_cipher_pkg.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_lfsr.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_inv_28_22_enc.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_inv_28_22_dec.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_inv_39_32_enc.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_inv_39_32_dec.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_inv_72_64_enc.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_inv_72_64_dec.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_prince.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_subst_perm.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_28_22_enc.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_28_22_dec.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_39_32_enc.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_39_32_dec.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_72_64_enc.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_72_64_dec.sv
// Until this list is generated by FuseSoC, we have to use manually generated
// wrappers around the prim_* modules to instantiate the prim_generic_* ones,
// see https://github.com/lowRISC/ibex/issues/893.
${PRJ_DIR}/dv/uvm/core_ibex/common/prim/prim_pkg.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_util_pkg.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_pkg.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_22_16_dec.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_22_16_enc.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_64_57_dec.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_64_57_enc.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_hamming_22_16_dec.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_hamming_22_16_enc.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_hamming_39_32_dec.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_hamming_39_32_enc.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_hamming_72_64_dec.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_hamming_72_64_enc.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_ram_1p_pkg.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_ram_1p_adv.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_ram_1p_scr.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim_generic/rtl/prim_generic_ram_1p.sv
${PRJ_DIR}/dv/uvm/core_ibex/common/prim/prim_ram_1p.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim_generic/rtl/prim_generic_clock_gating.sv
${PRJ_DIR}/dv/uvm/core_ibex/common/prim/prim_clock_gating.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim_generic/rtl/prim_generic_buf.sv
${PRJ_DIR}/dv/uvm/core_ibex/common/prim/prim_buf.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim_generic/rtl/prim_generic_clock_mux2.sv
${PRJ_DIR}/dv/uvm/core_ibex/common/prim/prim_clock_mux2.sv
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim_generic/rtl/prim_generic_flop.sv
${PRJ_DIR}/dv/uvm/core_ibex/common/prim/prim_flop.sv
// ibex CORE RTL files
+incdir+${PRJ_DIR}/rtl
${PRJ_DIR}/rtl/ibex_pkg.sv
${PRJ_DIR}/rtl/ibex_tracer_pkg.sv
${PRJ_DIR}/rtl/ibex_tracer.sv
${PRJ_DIR}/rtl/ibex_alu.sv
${PRJ_DIR}/rtl/ibex_branch_predict.sv
${PRJ_DIR}/rtl/ibex_compressed_decoder.sv
${PRJ_DIR}/rtl/ibex_controller.sv
${PRJ_DIR}/rtl/ibex_csr.sv
${PRJ_DIR}/rtl/ibex_cs_registers.sv
${PRJ_DIR}/rtl/ibex_counter.sv
${PRJ_DIR}/rtl/ibex_decoder.sv
${PRJ_DIR}/rtl/ibex_dummy_instr.sv
${PRJ_DIR}/rtl/ibex_ex_block.sv
${PRJ_DIR}/rtl/ibex_wb_stage.sv
${PRJ_DIR}/rtl/ibex_id_stage.sv
${PRJ_DIR}/rtl/ibex_icache.sv
${PRJ_DIR}/rtl/ibex_if_stage.sv
${PRJ_DIR}/rtl/ibex_load_store_unit.sv
${PRJ_DIR}/rtl/ibex_lockstep.sv
${PRJ_DIR}/rtl/ibex_multdiv_slow.sv
${PRJ_DIR}/rtl/ibex_multdiv_fast.sv
${PRJ_DIR}/rtl/ibex_prefetch_buffer.sv
${PRJ_DIR}/rtl/ibex_fetch_fifo.sv
${PRJ_DIR}/rtl/ibex_register_file_ff.sv
${PRJ_DIR}/rtl/ibex_register_file_fpga.sv
${PRJ_DIR}/rtl/ibex_register_file_latch.sv
${PRJ_DIR}/rtl/ibex_pmp.sv
${PRJ_DIR}/rtl/ibex_core.sv
${PRJ_DIR}/rtl/ibex_top.sv
${PRJ_DIR}/rtl/ibex_top_tracing.sv
// Core DV files
${PRJ_DIR}/vendor/google_riscv-dv/src/riscv_signature_pkg.sv
+incdir+${PRJ_DIR}/dv/uvm/core_ibex/env
+incdir+${PRJ_DIR}/dv/uvm/core_ibex/tests
+incdir+${PRJ_DIR}/dv/uvm/core_ibex/common/ibex_mem_intf_agent
+incdir+${PRJ_DIR}/dv/uvm/core_ibex/common/irq_agent
+incdir+${PRJ_DIR}/dv/uvm/core_ibex/common/ibex_cosim_agent
+incdir+${PRJ_DIR}/vendor/lowrisc_ip/dv/sv/mem_model
+incdir+${PRJ_DIR}/vendor/lowrisc_ip/dv/sv/dv_utils
+incdir+${PRJ_DIR}/vendor/lowrisc_ip/dv/sv/str_utils
+incdir+${PRJ_DIR}/dv/cosim
${PRJ_DIR}/dv/uvm/bus_params_pkg/bus_params_pkg.sv
${PRJ_DIR}/vendor/lowrisc_ip/dv/sv/common_ifs/clk_rst_if.sv
${PRJ_DIR}/vendor/lowrisc_ip/dv/sv/common_ifs/pins_if.sv
${PRJ_DIR}/vendor/lowrisc_ip/dv/sv/str_utils/str_utils_pkg.sv
${PRJ_DIR}/vendor/lowrisc_ip/dv/sv/dv_utils/dv_test_status_pkg.sv
${PRJ_DIR}/vendor/lowrisc_ip/dv/sv/dv_utils/dv_utils_pkg.sv
${PRJ_DIR}/vendor/lowrisc_ip/dv/sv/mem_model/mem_model_pkg.sv
${PRJ_DIR}/dv/uvm/core_ibex/common/ibex_mem_intf_agent/ibex_mem_intf.sv
${PRJ_DIR}/dv/uvm/core_ibex/common/ibex_mem_intf_agent/ibex_mem_intf_agent_pkg.sv
${PRJ_DIR}/dv/uvm/core_ibex/common/irq_agent/irq_if.sv
${PRJ_DIR}/dv/uvm/core_ibex/common/irq_agent/irq_agent_pkg.sv
${PRJ_DIR}/dv/uvm/core_ibex/env/core_ibex_rvfi_if.sv
${PRJ_DIR}/dv/uvm/core_ibex/common/ibex_cosim_agent/core_ibex_ifetch_if.sv
${PRJ_DIR}/dv/uvm/core_ibex/common/ibex_cosim_agent/core_ibex_ifetch_pmp_if.sv
${PRJ_DIR}/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_cosim_agent_pkg.sv
${PRJ_DIR}/dv/uvm/core_ibex/env/core_ibex_instr_monitor_if.sv
${PRJ_DIR}/dv/uvm/core_ibex/env/core_ibex_dut_probe_if.sv
${PRJ_DIR}/dv/uvm/core_ibex/env/core_ibex_csr_if.sv
${PRJ_DIR}/dv/uvm/core_ibex/env/core_ibex_env_pkg.sv
${PRJ_DIR}/dv/uvm/core_ibex/tests/core_ibex_test_pkg.sv
${PRJ_DIR}/dv/uvm/core_ibex/fcov/core_ibex_fcov_if.sv
${PRJ_DIR}/dv/uvm/core_ibex/fcov/core_ibex_fcov_bind.sv
${PRJ_DIR}/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv

View file

@ -1,7 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
${PRJ_DIR}/dv/uvm/core_ibex/common/ibex_cosim_agent/spike_cosim_dpi.cc
${PRJ_DIR}/dv/cosim/cosim_dpi.cc
${PRJ_DIR}/dv/cosim/spike_cosim.cc

View file

@ -1,135 +0,0 @@
#!/usr/bin/env python3
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
import argparse
import os
import sys
from typing import Dict, List
_CORE_IBEX = os.path.normpath(os.path.join(os.path.dirname(__file__)))
_IBEX_ROOT = os.path.normpath(os.path.join(_CORE_IBEX, '../../..'))
_RISCV_DV_ROOT = os.path.join(_IBEX_ROOT, 'vendor/google_riscv-dv')
_OLD_SYS_PATH = sys.path
# Import riscv_trace_csv and lib from _DV_SCRIPTS before putting sys.path back
# as it started.
try:
sys.path = ([os.path.join(_CORE_IBEX, 'riscv_dv_extension'),
os.path.join(_IBEX_ROOT, 'util'),
os.path.join(_RISCV_DV_ROOT, 'scripts')] +
sys.path)
from lib import process_regression_list # type: ignore
from ibex_config import parse_config # type: ignore
finally:
sys.path = _OLD_SYS_PATH
_TestEntry = Dict[str, object]
_TestEntries = List[_TestEntry]
def filter_tests_by_config(cfg: str, test_list: _TestEntries) -> _TestEntries:
'''Filter out any unsupported tests from being executed.
This function will parse the set of RTL parameters required by a given
test (if any) and ensure that those parameters are supported by the
selected core config.
Doing this allows the run flow to be smarter about running regressions
with different configs (useful for CI flows).
Arguments:
cfg: string name of the ibex config being tested, should match a
config name from ibex_configs.yaml.
test_list: list of test entry objects parsed from the YAML testlist
Returns:
filtered_test_list: a list of test entry objects, filtered such that
all tests incompatible with the specified ibex
config have been removed.
e.g. if the "small" config has been specified, this
function will filter out all tests that require
B-extension and PMP parameters
'''
filtered_test_list = []
config = parse_config(cfg, os.path.join(_IBEX_ROOT, "ibex_configs.yaml"))
for test in test_list:
good = True
if "rtl_params" in test:
param_dict = test['rtl_params']
assert isinstance(param_dict, dict)
for p, p_val in param_dict.items():
config_val = config.get(p, None)
# Throw an error if required RTL parameters in the testlist
# have been formatted incorrectly (typos, wrong parameters,
# etc)
if config_val is None:
raise ValueError('Parameter {} not found in config {}'
.format(p, cfg))
# Ibex has some enum parameters, so as a result some tests are
# able to run with several of these parameter values (like
# bitmanipulation tests). If this is the case, the testlist
# will specify all legal enum values, check if any of them
# match the config.
if isinstance(p_val, list):
good = (config_val in p_val)
else:
good = (p_val == config_val)
# If there is any parameter mismatch, we can terminate
# immediately and exclude the test from being executed
if not good:
break
if good:
filtered_test_list.append(test)
return filtered_test_list
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument('--start_seed', type=int, default=1)
parser.add_argument('--test', required=True)
parser.add_argument('--iterations', type=int, default=0)
parser.add_argument('--ibex-config', required=True)
args = parser.parse_args()
if args.iterations < 0:
raise RuntimeError('Bad --iterations argument: must be non-negative')
if args.start_seed < 0:
raise RuntimeError('Bad --start_seed argument: must be non-negative')
# Get all the tests that match --test, scaling as necessary with the
# --iterations argument.
matched_list = [] # type: _TestEntries
testlist = os.path.join(_CORE_IBEX, 'riscv_dv_extension', 'testlist.yaml')
process_regression_list(testlist, args.test, args.iterations,
matched_list, _RISCV_DV_ROOT)
if not matched_list:
raise RuntimeError("Cannot find {} in {}".format(args.test, testlist))
# Filter tests by the chosen configuration
matched_list = filter_tests_by_config(args.ibex_config, matched_list)
# Print the tests crossed by seeds, one to a line, in the format TEST.SEED.
for test in matched_list:
name = test['test']
iterations = test['iterations']
assert isinstance(name, str) and isinstance(iterations, int)
assert iterations > 0
for iteration in range(iterations):
print('{}.{}'.format(name, args.start_seed + iteration))
return 0
if __name__ == '__main__':
sys.exit(main())

View file

@ -1,18 +0,0 @@
- test: riscv_instr_cov_debug_test
description: >
Functional coverage debug test, this is not a functional test to the core.
iterations: 1
gen_test: riscv_instr_cov_debug_test
no_iss: 1
no_gcc: 1
no_post_compare: 1
- test: riscv_instr_cov_test
description: >
Parse the instruction information from the CSV trace log, sample functional
coverage from the instruction trace.
iterations: 1
gen_test: riscv_instr_cov_test
no_iss: 1
no_gcc: 1
no_post_compare: 1

View file

@ -1,403 +0,0 @@
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
#
#
# Base CSR template that should be followed when describing all processor supported CSRs to enable correct generation of directed test sequences
#- csr: CSR_NAME
# description: >
# BRIEF_DESCRIPTION
# address: 0x###
# privilege_mode: MODE (D/M/S/H/U)
# rv32:
# - MSB_FIELD_NAME:
# - description: >
# BRIEF_DESCRIPTION
# - type: TYPE (WPRI/WLRL/WARL)
# - reset_val: RESET_VAL
# - msb: MSB_POS
# - lsb: LSB_POS
# - ...
# - ...
# - LSB_FIELD_NAME:
# - description: ...
# - type: ...
# - ...
# rv64:
# - MSB_FIELD_NAME:
# - description: >
# BRIEF_DESCRIPTION
# - type: TYPE (WPRI/WLRL/WARL)
# - reset_val: RESET_VAL
# - msb: MSB_POS
# - lsb: LSB_POS
# - ...
# - ...
# - LSB_FIELD_NAME:
# - description: ...
# - type: ...
# - ...
# TODO: Enable multiple configurations for CSR test. MISA read value will depend
# on whether B and M are available. https://github.com/lowRISC/ibex/issues/1333
# Ibex MISA CSR
#- csr: misa
# description: >
# Machine ISA Register
# address: 0x301
# privilege_mode: M
# rv32:
# - field_name: MXL
# description: >
# Encodes native base ISA width
# type: R
# reset_val: 1
# msb: 31
# lsb: 30
# - field_name: Extensions
# description: >
# Encodes all supported ISA extensions
# type: R
# reset_val: 0x101106
# msb: 25
# lsb: 0
# CSR test generation cannot deal with read-only fields. Leaving these here
# commented out so they can be used once the read-only issue is fixed.
# https://github.com/lowRISC/ibex/issues/1337 tracks the required improvements
#- csr: mvendorid
# description: >
# Machine Vendor ID Register providing JEDEC manufacturer ID
# address: 0xF11
# privilege_mode: M
# rv32:
# - field_name: Bank
# description: >
# Number of continuation codes in JEDEC ID
# type: R
# reset_val: 0
# msb: 31
# lsb: 7
# - field_name: Offset
# description: >
# JEDEC ID final byte
# type: R
# reset_val: 0
# msb: 6
# lsb: 0
#
#- csr: marchid
# description: >
# Machine Architecture ID Register
# address: 0xF12
# privilege_mode: M
# rv32:
# - field_name: Architecture ID
# description: >
# ID indicating the base microarchitecture
# type: R
# reset_val: 22
# msb: 31
# lsb: 0
#
#- csr: mimpid
# description: >
# Machine Implementation ID Register
# address: 0xF13
# privilege_mode: M
# rv32:
# - field_name: Implementation
# description: >
# Unique encoding of processor implementation version
# type: R
# reset_val: 0
# msb: 31
# lsb: 0
# Ibex's implementation of MHARTID is read-only
# MHARTID
#- csr: mhartid
# description: >
# Contains integer ID of hardware thread running code
# address: 0xF14
# privilege_mode: M
# rv32:
# - field_name: cluster_id
# description: >
# ID of the cluster
# type: R
# reset_val: 0
# msb: 10
# lsb: 5
# - field_name: core_id
# description: >
# ID of the core within cluster
# type: R
# reset_val: 0
# msb: 3
# lsb: 0
# TODO(udinator) - wait until riscv-config yaml format is ready to deal with xSTATUS CSRs, as mpp
# fields need to be constrained such that their value after every operation is within the allowed
# range of values - too much complexity for the current format
# MSTATUS
#- csr: mstatus
# description: >
# Controls hart's current operating state
# address: 0x300
# privilege_mode: M
# rv32:
# - field_name: tw
# description: >
# Timeout Wait (WFI from U-mode will trap to M-mode)
# type: WARL
# reset_val: 0
# msb: 21
# lsb: 21
# - field_name: mprv
# description: >
# Modify Privilege (Loads and stores use MPP for privilege checking)
# type: WARL
# reset_val: 0
# msb: 17
# lsb: 17
# - field_name: mpp
# desription : >
# Previous privilege mode
# type: R
# reset_val: 0
# msb: 12
# lsb: 11
# - field_name: mpie
# description: >
# Previous value of interrupt-enable bit
# type: WARL
# reset_val: 1
# msb: 7
# lsb: 7
# - field_name: mie
# description: >
# M-mode interrupt enable
# type: WARL
# reset_val: 0
# msb: 3
# lsb: 3
# MIP
- csr: mip
description: >
Contains pending interrupt information
address: 0x344
privilege_mode: M
rv32:
- field_name: fast
description: >
platform-specific interrupts
type: R
reset_val: 0
msb: 30
lsb: 16
- field_name: meip
description: >
M-mode external interrupts
type: R
reset_val: 0
msb: 11
lsb: 11
- field_name: mtip
description: >
M-mode timer interrupts
type: R
reset_val: 0
msb: 7
lsb: 7
- field_name: msip
description: >
M-mode software interrupts
type: R
reset_val: 0
msb: 3
lsb: 3
# MIE
- csr: mie
description: >
Contains interrupt information
address: 0x304
privilege_mode: M
rv32:
- field_name: fast
description: >
platform-specific interrupts
type: WARL
reset_val: 0
msb: 30
lsb: 16
- field_name: meip
description: >
M-mode external interrupts
type: WARL
reset_val: 0
msb: 11
lsb: 11
- field_name: mtip
description: >
M-mode timer interrupts
type: WARL
reset_val: 0
msb: 7
lsb: 7
- field_name: msip
description: >
M-mode software interrupts
type: WARL
reset_val: 0
msb: 3
lsb: 3
# MSCRATCH
- csr: mscratch
description: >
M-mode scratch register
address: 0x340
privilege_mode: M
rv32:
- field_name: mscratch
description: >
scratch register
type: WARL
reset_val: 0
msb: 31
lsb: 0
# MEPC
- csr: mepc
description: >
Stores the address of the instruction that was interrupted or caused exception
address: 0x341
privilege_mode: M
rv32:
- field_name: mepc
description: >
M-mode exception PC register
type: WARL
reset_val: 0
msb: 31
lsb: 1
# MCAUSE
- csr: mcause
description: >
Indicates trap cause
address: 0x342
privilege_mode: M
rv32:
- field_name: Interrupt
description: >
Indicates if trap caused by interrupt
type: WARL
reset_val: 0
msb: 31
lsb: 31
- field_name: Exception Code
type: WLRL
reset_val: 0
msb: 4
lsb: 0
# MTVAL
- csr: mtval
description: >
Machine Trap Value register
address: 0x343
privilege_mode: M
rv32:
- field_name: mtval
description: >
Address of faulting instruction
type: WARL
reset_val: 0
msb: 31
lsb: 0
# MTVEC
- csr: mtvec
description: >
Machine trap vector base address
address: 0x305
privilege_mode: M
rv32:
- field_name: base
description: >
trap vector base address (256 byte aligned)
type: WARL
reset_val: 0x800000
msb: 31
lsb: 8
- field_name: base_zero_end
description: >
bottom six bits of base always set to zero
type: R
reset_val: 0
msb: 7
lsb: 2
- field_name: mode
description: >
trap handling mode
type: R
reset_val: 0x1
msb: 1
lsb: 0
# CPUCTRL
#
- csr: cpuctrl
description: >
CPU control register (custom)
address: 0x7C0
privilege_mode: M
rv32:
- field_name: dumm_instr_mask
description: >
Mask to control frequency of dummy instruction insertion
type: WARL
reset_val: 0
msb: 5
lsb: 3
- field_name: dummy_instr_en
description: >
Enable or disable dummy instruction insertion
type: WARL
reset_val: 0
msb: 2
lsb: 2
- field_name: data_ind_timing
description: >
Enable or disable data-independent timing features
type: WARL
reset_val: 0
msb: 1
lsb: 1
- field_name: icache_enable
description: >
Enable or disable the instruction cache
type: WARL
reset_val: 0
msb: 0
lsb: 0
# SECURESEED
- csr: secureseed
description: >
CSR for re-seeding security-related pseudo-random number generators (custom)
address: 0x7C1
privilege_mode: M
rv32:
- field_name: secureseed
description: >
New seed value (write-only), reads always return 0.
type: R
reset_val: 0
msb: 31
lsb: 0

View file

@ -1,42 +0,0 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//-----------------------------------------------------------------------------------------
// RISC-V assembly program generator for ibex
//-----------------------------------------------------------------------------------------
class ibex_asm_program_gen extends riscv_asm_program_gen;
`uvm_object_utils(ibex_asm_program_gen)
`uvm_object_new
virtual function void gen_program_header();
// Override the cfg value, below fields are not supported by ibex
cfg.mstatus_mprv = 0;
cfg.mstatus_mxr = 0;
cfg.mstatus_sum = 0;
cfg.mstatus_tvm = 0;
// Disable below fields checking against spike as spike implementation is different compared
// with ibex.
cfg.check_misa_init_val = 1'b0;
cfg.check_xstatus = 1'b0;
instr_stream.push_back(".section .text");
instr_stream.push_back(".globl _start");
instr_stream.push_back(".option norvc");
// 0x0 debug mode entry
instr_stream.push_back("j debug_rom");
// 0x4 debug mode exception handler
instr_stream.push_back("j debug_exception");
// Align the start section to 0x80
instr_stream.push_back(".align 7");
instr_stream.push_back(".option rvc");
instr_stream.push_back("_start:");
endfunction
virtual function void init_custom_csr(ref string instr[$]);
// Write 1 to cpuctrl.icache_enable to enable Icache during simulation
instr.push_back("csrwi 0x7c0, 1");
endfunction
endclass

View file

@ -1,254 +0,0 @@
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
#
# Convert ibex log to the standard trace CSV format
import argparse
import os
import re
import sys
_IBEX_ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__),
'../../../..'))
_DV_SCRIPTS = os.path.join(_IBEX_ROOT, 'vendor/google_riscv-dv/scripts')
_OLD_SYS_PATH = sys.path
# Import riscv_trace_csv and lib from _DV_SCRIPTS before putting sys.path back
# as it started.
try:
sys.path.insert(0, _DV_SCRIPTS)
from riscv_trace_csv import (RiscvInstructionTraceCsv,
RiscvInstructionTraceEntry,
get_imm_hex_val)
from lib import RET_FATAL, gpr_to_abi, sint_to_hex, convert_pseudo_instr
import logging
finally:
sys.path = _OLD_SYS_PATH
INSTR_RE = \
re.compile(r"^\s*(?P<time>\d+)\s+(?P<cycle>\d+)\s+(?P<pc>[0-9a-f]+)\s+"
r"(?P<bin>[0-9a-f]+)\s+(?P<instr>\S+\s+\S+)\s*")
RD_RE = re.compile(r"(x(?P<rd>[1-9]\d*)=0x(?P<rd_val>[0-9a-f]+))")
ADDR_RE = re.compile(r"(?P<rd>[a-z0-9]+?),"
r"(?P<imm>[\-0-9]+?)"
r"\((?P<rs1>[a-z0-9]+)\)")
def _process_ibex_sim_log_fd(log_fd, csv_fd, full_trace=True):
"""Process ibex simulation log.
Reads from log_fd, which should be a file object containing a trace from an
Ibex simulation. Writes in a standard CSV format to csv_fd, which should be
a file object opened for writing.
If full_trace is true, this dumps information about operands for, replacing
absolute branch destinations with offsets relative to the current pc.
"""
instr_cnt = 0
trace_csv = RiscvInstructionTraceCsv(csv_fd)
trace_csv.start_new_trace()
trace_entry = None
for line in log_fd:
if re.search("ecall", line):
break
# Extract instruction information
m = INSTR_RE.search(line)
if m is None:
continue
instr_cnt += 1
# Write the extracted instruction to a csvcol buffer file
trace_entry = RiscvInstructionTraceEntry()
trace_entry.instr_str = m.group("instr")
trace_entry.instr = m.group("instr").split()[0]
trace_entry.pc = m.group("pc")
trace_entry.binary = m.group("bin")
if full_trace:
expand_trace_entry(trace_entry, m.group("instr").split()[1])
c = RD_RE.search(line)
if c:
abi_name = gpr_to_abi("x{}".format(c.group("rd")))
gpr_entry = "{}:{}".format(abi_name, c.group("rd_val"))
trace_entry.gpr.append(gpr_entry)
trace_csv.write_trace_entry(trace_entry)
return instr_cnt
def process_ibex_sim_log(ibex_log, csv, full_trace=1):
"""Process ibex simulation log.
Extract instruction and affected register information from ibex simulation
log and save to a standard CSV format.
"""
logging.info("Processing ibex log : %s" % ibex_log)
try:
with open(ibex_log, "r") as log_fd, open(csv, "w") as csv_fd:
count = _process_ibex_sim_log_fd(log_fd, csv_fd,
True if full_trace else False)
except FileNotFoundError:
raise RuntimeError("Logfile %s not found" % ibex_log)
logging.info("Processed instruction count : %d" % count)
if not count:
raise RuntimeError("No instructions in logfile: %s" % ibex_log)
logging.info("CSV saved to : %s" % csv)
def convert_operands_to_abi(operand_str):
"""Convert the operand string to use ABI register naming.
At this stage in the conversion of the Ibex log to CSV format, the operand
string is in this format:
"x6,x6,1000".
This function converts the register names to their ABI equivalents as shown below:
"t1,t1,1000".
This step is needed for the RISCV-DV functional coverage step, as it assumes that
all operand registers already follow the ABI naming scheme.
Args:
operand_str : A string of the operands for a given instruction
Returns:
A string of the operands for the instruction, with register names converted to ABI.
"""
operand_list = operand_str.split(",")
for i in range(len(operand_list)):
converted_op = gpr_to_abi(operand_list[i])
if converted_op != "na":
operand_list[i] = converted_op
return ",".join(operand_list)
def expand_trace_entry(trace, operands):
'''Expands a CSV trace entry for a single instruction.
Operands are added to the CSV entry, converting from the raw
register naming scheme (x0, x1, etc...) to ABI naming (a1, s1, etc...).
'''
operands = process_imm(trace.instr, trace.pc, operands)
trace.instr, operands = \
convert_pseudo_instr(trace.instr, operands, trace.binary)
# process any instructions of the form:
# <instr> <reg> <imm>(<reg>)
n = ADDR_RE.search(operands)
if n:
trace.imm = get_imm_hex_val(n.group("imm"))
operands = ','.join([n.group("rd"), n.group("rs1"), n.group("imm")])
# Convert the operands into ABI format for the function coverage flow,
# and store them into the CSV trace entry.
trace.operand = convert_operands_to_abi(operands)
def process_imm(instr_name, pc, operands):
"""Process imm to follow RISC-V standard convention"""
if instr_name not in ['beq', 'bne', 'blt', 'bge', 'bltu', 'bgeu', 'c.beqz',
'c.bnez', 'beqz', 'bnez', 'bgez', 'bltz', 'blez',
'bgtz', 'c.j', 'j', 'c.jal', 'jal']:
return operands
idx = operands.rfind(',')
if idx == -1:
imm = operands
return str(sint_to_hex(int(imm, 16) - int(pc, 16)))
imm = operands[idx + 1:]
imm = str(sint_to_hex(int(imm, 16) - int(pc, 16)))
return operands[0:idx + 1] + imm
def check_ibex_uvm_log(uvm_log):
"""Process Ibex UVM simulation log.
Process the UVM simulation log produced by the test to check for
correctness, reports failure if an explicit error or failure is seen in the
log or there's no explicit pass.
Args:
uvm_log: the uvm simulation log
Returns:
A tuple of (passed, log_out).
`passed` indicates whether the test passed or failed based on the
log.
`log_out` a list of relevant lines from the log that may indicate the
source of the failure, if `passed` is true it will be empty.
"""
passed = False
failed = False
log_out = []
with open(uvm_log, "r") as log:
# Simulation log has report summary at the end, which references
# 'UVM_ERROR' which can cause false failures. The summary appears after
# the test result so ignore any lines after the test result is seen for
# 'UVM_ERROR' checking. If the loop terminated immediately when a test
# result was seen it would miss issues where the test result is
# (erronously) repeated multiple times with different results.
test_result_seen = False
for line in log:
if ('UVM_ERROR' in line or 'UVM_FATAL' in line or 'Error' in line) \
and not test_result_seen:
log_out.append(line.strip())
failed = True
if 'RISC-V UVM TEST PASSED' in line:
test_result_seen = True
passed = True
if 'RISC-V UVM TEST FAILED' in line:
test_result_seen = True
failed = True
break
# If we saw PASSED and FAILED, that's a bit odd. But we should treat the
# test as having failed.
if failed:
passed = False
return (passed, log_out)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--log",
help="Input ibex simulation log (default: stdin)",
type=argparse.FileType('r'),
default=sys.stdin)
parser.add_argument("--csv",
help="Output trace csv file (default: stdout)",
type=argparse.FileType('w'),
default=sys.stdout)
parser.add_argument("--full_trace", type=int, default=1,
help="Enable full log trace")
args = parser.parse_args()
_process_ibex_sim_log_fd(args.log, args.csv,
True if args.full_trace else False)
if __name__ == "__main__":
try:
main()
except RuntimeError as err:
sys.stderr.write('Error: {}\n'.format(err))
sys.exit(RET_FATAL)

View file

@ -1,399 +0,0 @@
# Copyright 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.
# ================================================================================
# Regression test list format
# --------------------------------------------------------------------------------
# test : Assembly test name
# description : Description of this test
# gen_opts : Instruction generator options
# iterations : Number of iterations of this test
# no_iss : Enable/disable ISS simulator (Optional)
# gen_test : Test name used by the instruction generator
# asm_tests : Path to directed, hand-coded assembly test file or directory
# rtl_test : RTL simulation test name
# cmp_opts : Compile options passed to the instruction generator
# sim_opts : Simulation options passed to the instruction generator
# no_post_compare : Enable/disable comparison of trace log and ISS log (Optional)
# compare_opts : Options for the RTL & ISS trace comparison
# gcc_opts : gcc compile options
# --------------------------------------------------------------------------------
# --------------------------------------------------------------------------------
# ML Parameter Constraints - riscv_rand_test
# --------------------------------------------------------------------------------
# A description of each generation parameter can be found in the 'Configuration'
# section of the RISC-DV documentation
# (https://github.com/google/riscv-dv/blob/master/docs/source/configuration.rst)
#
# This section will provide some constraints that must be placed on the main
# parameter set for the riscv_rand_test to avoid failures or unexpected results
# These constraints apply to every test that is written or used.
# --------------------------------------------------------------------------------
# 1) +no_wfi must be 1 to prevent the core from hanging during tests.
# 2) if +no_directed_instr is 1, any directed instruction streams specified by the
# +stream_name_... parameters will be ignored and will not be generated.
# 3) +no_data_page must be 0 (default) if there are any directed streams
# involving memory loads and stores.
# 4) The +enable_misaligned_instr parameter is only used by the riscv_jal_instr
# directed stream, and will have no effect if this stream is disabled.
# 5) The +enable_unaligned_load_store parameter is only used by load/store
# directed instruction streams, and will have no effect if these streams
# are disabled.
- test: riscv_rand_test
description: >
Random test with all useful knobs
gen_opts: >
+instr_cnt=10000
+num_of_sub_program=5
+enable_write_pmp_csr=1
+illegal_instr_ratio=5
+hint_instr_ratio=5
+no_ebreak=0
+no_dret=0
+no_wfi=1
+set_mstatus_tw=0
+no_branch_jump=0
+no_csr_instr=0
+fix_sp=0
+enable_illegal_csr_instruction=0
+enable_access_invalid_csr_level=0
+enable_misaligned_instr=0
+enable_dummy_csr_write=0
+no_data_page=0
+no_directed_instr=0
+no_fence=0
+enable_unaligned_load_store=1
+disable_compressed_instr=0
+randomize_csr=0
+enable_b_extension=1
+enable_bitmanip_groups=zbb,zb_tmp,zbt,zbs,zbp,zbf,zbe,zbc,zbr
+boot_mode=u
+stream_name_0=riscv_load_store_rand_instr_stream
+stream_freq_0=4
+stream_name_1=riscv_loop_instr
+stream_freq_1=4
+stream_name_2=riscv_hazard_instr_stream
+stream_freq_2=4
+stream_name_3=riscv_load_store_hazard_instr_stream
+stream_freq_3=4
+stream_name_4=riscv_mem_region_stress_test
+stream_freq_4=4
+stream_name_5=riscv_jal_instr
+stream_freq_5=4
+stream_name_6=riscv_int_numeric_corner_stream
+stream_freq_6=4
+stream_name_7=riscv_multi_page_load_store_instr_stream
+stream_freq_7=4
+stream_name_8=riscv_load_store_rand_addr_instr_stream
+stream_freq_8=4
+stream_name_9=riscv_single_load_store_instr_stream
+stream_freq_9=4
+stream_name_10=riscv_load_store_stress_instr_stream
+stream_freq_10=4
iterations: 1
no_iss: 1
gcc_opts: >
-mno-strict-align
gen_test: riscv_ml_test
rtl_test: core_ibex_reset_test
no_post_compare: 1
# --------------------------------------------------------------------------------
# ML Parameter Constraints - riscv_rand_debug_test
# --------------------------------------------------------------------------------
# A description of each generation parameter can be found in the 'Configuration'
# section of the RISC-DV documentation
# (https://github.com/google/riscv-dv/blob/master/docs/source/configuration.rst)
#
# This section will provide some constraints that must be placed on the set of
# parameters relating to debug ROM generation and RTL simulation to avoid
# failures or unexpected results.
# --------------------------------------------------------------------------------
# 1) +require_signature_address must be 1.
# 2) If +gen_debug_section is 0, none of the values of the other debug ROM
# parameters will matter.
# 3) At most 1 of the parameters +enable_ebreak_in_debug_rom, +set_dcsr_ebreak,
# and +enable_debug_single_step may be enabled at once.
# 4) +illegal_instr_ratio must be 0.
# 5) +no_ebreak and +no_dret must be 1.
# 6) +set_mstatus_tw must be 0.
# 7) The RTL simulation plusarg +require_signature_addr under the sim_opts
# section of the test entry must be enabled.
# 8) The RTL simulation plusarg +enable_debug_seq must be enabled.
# 9) The RTL simulation plusarg +max_interval controls the maximum interval
# between debug request assertions.
# NOTE: keep this value very large for the time being.
# 10) While not a constraint, it is recommended to keep +num_debug_sub_program
# fairly small, as larger values can easily cause frequent test timeouts.
- test: riscv_rand_debug_test
description: >
Random debug test with all useful knobs
gen_opts: >
+require_signature_addr=1
+gen_debug_section=1
+num_debug_sub_program=1
+enable_ebreak_in_debug_rom=0
+set_dcsr_ebreak=0
+enable_debug_single_step=1
+instr_cnt=10000
+num_of_sub_program=5
+enable_write_pmp_csr=1
+illegal_instr_ratio=0
+hint_instr_ratio=5
+no_ebreak=1
+no_dret=1
+no_wfi=0
+set_mstatus_tw=0
+no_branch_jump=0
+no_load_store=0
+no_csr_instr=0
+fix_sp=0
+enable_illegal_csr_instruction=0
+enable_access_invalid_csr_level=0
+enable_misaligned_instr=1
+enable_dummy_csr_write=0
+no_data_page=0
+no_directed_instr=0
+no_fence=0
+enable_unaligned_load_store=1
+disable_compressed_instr=0
+randomize_csr=0
+enable_b_extension=1
+enable_bitmanip_groups=zbb,zb_tmp,zbt,zbs,zbp,zbf,zbe,zbc,zbr
+boot_mode=u
+stream_name_0=riscv_load_store_rand_instr_stream
+stream_freq_0=4
+stream_name_1=riscv_loop_instr
+stream_freq_1=4
+stream_name_2=riscv_hazard_instr_stream
+stream_freq_2=4
+stream_name_3=riscv_load_store_hazard_instr_stream
+stream_freq_3=4
+stream_name_4=riscv_mem_region_stress_test
+stream_freq_4=4
+stream_name_5=riscv_jal_instr
+stream_freq_5=4
+stream_name_6=riscv_int_numeric_corner_stream
+stream_freq_6=4
+stream_name_7=riscv_multi_page_load_store_instr_stream
+stream_freq_7=4
+stream_name_8=riscv_load_store_rand_addr_instr_stream
+stream_freq_8=4
+stream_name_9=riscv_single_load_store_instr_stream
+stream_freq_9=4
+stream_name_10=riscv_load_store_stress_instr_stream
+stream_freq_10=4
iterations: 1
no_iss: 1
gcc_opts: >
-mno-strict-align
gen_test: riscv_ml_test
sim_opts: >
+require_signature_addr=1
+max_interval=100000
+enable_debug_seq=1
rtl_test: core_ibex_debug_intr_basic_test
no_post_compare: 1
# --------------------------------------------------------------------------------
# ML Parameter Constraints - riscv_rand_irq_test
# --------------------------------------------------------------------------------
# A description of each generation parameter can be found in the 'Configuration'
# section of the RISC-DV documentation
# (https://github.com/google/riscv-dv/blob/master/docs/source/configuration.rst)
#
# This section will provide some constraints that must be placed on the set of
# parameters relating to simulations with external interrupts.
# --------------------------------------------------------------------------------
# 1) +enable_interrupt and +require_signature_addr must both be 1.
# 2) As before, +illegal_instr_ratio must be 0, +no_ebreak must be 1,
# and +no_dret must be 1.
# 3) +set_mstatus_tw must be 0.
# 4) One of the RTL simulation options +enable_irq_single_seq or
# +enable_irq_multiple_seq must be enabled.
# 5) The RTL simulation option +require_signature_addr must be 1.
# 6) Do not randomize the value of both +enable_nested_interrupt in gen_opts
# and +enable_nested_irq in sim_opts.
- test: riscv_rand_irq_test
description: >
Random test with all useful knobs
gen_opts: >
+require_signature_addr=1
+enable_interrupt=1
+enable_timer_irq=1
+enable_nested_interrupt=1
+instr_cnt=10000
+enable_write_pmp_csr=1
+num_of_sub_program=5
+illegal_instr_ratio=0
+hint_instr_ratio=5
+no_ebreak=1
+no_dret=1
+no_wfi=0
+set_mstatus_tw=0
+no_branch_jump=0
+no_load_store=0
+no_csr_instr=0
+fix_sp=0
+enable_illegal_csr_instruction=0
+enable_access_invalid_csr_level=0
+enable_misaligned_instr=0
+enable_dummy_csr_write=0
+no_data_page=0
+no_directed_instr=0
+no_fence=0
+enable_unaligned_load_store=1
+disable_compressed_instr=0
+randomize_csr=1
+enable_b_extension=1
+enable_bitmanip_groups=zbb,zb_tmp,zbt,zbs,zbp,zbf,zbe,zbc,zbr
+boot_mode=u
+stream_name_0=riscv_load_store_rand_instr_stream
+stream_freq_0=4
+stream_name_1=riscv_loop_instr
+stream_freq_1=4
+stream_name_2=riscv_hazard_instr_stream
+stream_freq_2=4
+stream_name_3=riscv_load_store_hazard_instr_stream
+stream_freq_3=4
+stream_name_4=riscv_mem_region_stress_test
+stream_freq_4=4
+stream_name_5=riscv_jal_instr
+stream_freq_5=4
+stream_name_6=riscv_int_numeric_corner_stream
+stream_freq_6=4
+stream_name_7=riscv_multi_page_load_store_instr_stream
+stream_freq_7=4
+stream_name_8=riscv_load_store_rand_addr_instr_stream
+stream_freq_8=4
+stream_name_9=riscv_single_load_store_instr_stream
+stream_freq_9=4
+stream_name_10=riscv_load_store_stress_instr_stream
+stream_freq_10=4
iterations: 1
no_iss: 1
gcc_opts: >
-mno-strict-align
gen_test: riscv_ml_test
sim_opts: >
+require_signature_addr=1
+enable_irq_single_seq=1
+enable_irq_multiple_seq=0
+enable_nested_irq=1
rtl_test: core_ibex_nested_irq_test
no_post_compare: 1
# --------------------------------------------------------------------------------
# ML Parameter Constraints - riscv_rand_mem_error_test
# --------------------------------------------------------------------------------
# A description of each generation parameter can be found in the 'Configuration'
# section of the RISC-DV documentation
# (https://github.com/google/riscv-dv/blob/master/docs/source/configuration.rst)
#
# This section will provide some constraints that must be placed on the set of
# parameters relating to simulations with memory bus errors.
# --------------------------------------------------------------------------------
# 1) +require_signature_addr must be 1 in gen_opts as well as in sim_opts.
# 2) +illegal_instr_ratio must be 0.
# 3) +no_ebreak must be 1.
# 4) +no_dret must be 1.
# 5) +no_wfi must be 1.
# 6) +enable_illegal_csr_instruction must be 0.
# 7) +enable_access_invalid_csr_level must be 0.
# 8) +no_csr_instr must be 1.
- test: riscv_rand_mem_error_test
description: >
Randomly insert memory bus errors in both IMEM and DMEM.
gen_opts: >
+instr_cnt=10000
+num_of_sub_program=5
+require_signature_addr=1
+enable_write_pmp_csr=1
+illegal_instr_ratio=0
+hint_instr_ratio=5
+no_ebreak=1
+no_dret=1
+no_wfi=1
+set_mstatus_tw=0
+no_branch_jump=0
+no_csr_instr=0
+fix_sp=0
+enable_illegal_csr_instruction=0
+enable_access_invalid_csr_level=0
+enable_misaligned_instr=0
+enable_dummy_csr_write=0
+no_data_page=0
+no_directed_instr=0
+no_fence=0
+enable_unaligned_load_store=1
+disable_compressed_instr=0
+randomize_csr=0
+enable_b_extension=1
+enable_bitmanip_groups=zbb,zb_tmp,zbt,zbs,zbp,zbf,zbe,zbc,zbr
+boot_mode=u
+stream_name_0=riscv_load_store_rand_instr_stream
+stream_freq_0=4
+stream_name_1=riscv_loop_instr
+stream_freq_1=4
+stream_name_2=riscv_hazard_instr_stream
+stream_freq_2=4
+stream_name_3=riscv_load_store_hazard_instr_stream
+stream_freq_3=4
+stream_name_4=riscv_mem_region_stress_test
+stream_freq_4=4
+stream_name_5=riscv_jal_instr
+stream_freq_5=4
+stream_name_6=riscv_int_numeric_corner_stream
+stream_freq_6=4
+stream_name_7=riscv_multi_page_load_store_instr_stream
+stream_freq_7=4
+stream_name_8=riscv_load_store_rand_addr_instr_stream
+stream_freq_8=4
+stream_name_9=riscv_single_load_store_instr_stream
+stream_freq_9=4
+stream_name_10=riscv_load_store_stress_instr_stream
+stream_freq_10=4
iterations: 1
no_iss: 1
gcc_opts: >
-mno-strict-align
gen_test: riscv_ml_test
rtl_test: core_ibex_mem_error_test
sim_opts: >
+require_signature_addr=1
no_post_compare: 1
# --------------------------------------------------------------------------------
# ML Parameter Constraints - riscv_csr_test
# --------------------------------------------------------------------------------
# This test has no controllable parameters, and will just provide an increase
# in coverage relating to control-status registers.
- test: riscv_csr_test
description: >
Test all CSR instructions on all implement CSRs
iterations: 1
no_iss: 1
rtl_test: core_ibex_csr_test
no_post_compare: 1

Some files were not shown because too many files have changed in this diff Show more