mirror of
https://github.com/openhwgroup/cve2.git
synced 2025-04-22 21:17:59 -04:00
closes #21 : clean design verification [dv] folder
Signed-off-by: Szymon Bieganski <szymon.bieganski@oss.nxp.com>
This commit is contained in:
parent
520888bedf
commit
3834421ba5
211 changed files with 0 additions and 19120 deletions
|
@ -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
|
141
dv/cosim/cosim.h
141
dv/cosim/cosim.h
|
@ -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_
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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 ®_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 ®_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 ®_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; }
|
|
@ -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 ®_change,
|
||||
uint32_t write_reg, uint32_t write_reg_data);
|
||||
|
||||
void on_csr_write(const commit_log_reg_t::value_type ®_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_
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
48
dv/cs_registers/env/env_dpi.cc
vendored
48
dv/cs_registers/env/env_dpi.cc
vendored
|
@ -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
|
25
dv/cs_registers/env/env_dpi.sv
vendored
25
dv/cs_registers/env/env_dpi.sv
vendored
|
@ -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
|
||||
|
||||
|
35
dv/cs_registers/env/register_environment.cc
vendored
35
dv/cs_registers/env/register_environment.cc
vendored
|
@ -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_, ¶ms_)),
|
||||
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();
|
||||
}
|
36
dv/cs_registers/env/register_environment.h
vendored
36
dv/cs_registers/env/register_environment.h
vendored
|
@ -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_
|
16
dv/cs_registers/env/register_types.h
vendored
16
dv/cs_registers/env/register_types.h
vendored
|
@ -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_
|
26
dv/cs_registers/env/simctrl.cc
vendored
26
dv/cs_registers/env/simctrl.cc
vendored
|
@ -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;
|
||||
}
|
21
dv/cs_registers/env/simctrl.h
vendored
21
dv/cs_registers/env/simctrl.h
vendored
|
@ -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
|
|
@ -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'*"
|
|
@ -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 ® : *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 ® : *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_; }
|
|
@ -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_
|
|
@ -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, ®ister_map_));
|
||||
register_map_.push_back(
|
||||
std::make_unique<NonImpRegister>(kCSRMSeccfgh, ®ister_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, ®ister_map_));
|
||||
} else {
|
||||
register_map_.push_back(
|
||||
std::make_unique<NonImpRegister>(reg_addr, ®ister_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, ®ister_map_));
|
||||
} else {
|
||||
register_map_.push_back(
|
||||
std::make_unique<NonImpRegister>(reg_addr, ®ister_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, ®ister_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, ®ister_map_, 0xFFFFFFFF, 0x1 << i));
|
||||
} else {
|
||||
register_map_.push_back(
|
||||
std::make_unique<NonImpRegister>(reg_addr, ®ister_map_));
|
||||
}
|
||||
}
|
||||
// mcycle
|
||||
register_map_.push_back(
|
||||
std::make_unique<BaseRegister>(0xB00, ®ister_map_));
|
||||
// minstret
|
||||
register_map_.push_back(
|
||||
std::make_unique<BaseRegister>(0xB02, ®ister_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, ®ister_map_, mhpmcounter_mask_low, 0));
|
||||
} else {
|
||||
register_map_.push_back(
|
||||
std::make_unique<NonImpRegister>(reg_addr, ®ister_map_));
|
||||
}
|
||||
}
|
||||
// mcycleh
|
||||
register_map_.push_back(
|
||||
std::make_unique<BaseRegister>(0xB80, ®ister_map_));
|
||||
// minstreth
|
||||
register_map_.push_back(
|
||||
std::make_unique<BaseRegister>(0xB82, ®ister_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, ®ister_map_, mhpmcounter_mask_high, 0));
|
||||
} else {
|
||||
register_map_.push_back(
|
||||
std::make_unique<NonImpRegister>(reg_addr, ®ister_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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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_
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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_
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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_
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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_
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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.
|
|
@ -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`.
|
|
@ -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
|
|
@ -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
|
|
@ -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"
|
||||
}
|
|
@ -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
|
|
@ -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())
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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())
|
|
@ -1,4 +0,0 @@
|
|||
+tree core_ibex_tb_top.dut
|
||||
begin tgl
|
||||
-tree core_ibex_tb_top.dut.*
|
||||
end
|
21
dv/uvm/core_ibex/env/core_ibex_csr_if.sv
vendored
21
dv/uvm/core_ibex/env/core_ibex_csr_if.sv
vendored
|
@ -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
|
43
dv/uvm/core_ibex/env/core_ibex_dut_probe_if.sv
vendored
43
dv/uvm/core_ibex/env/core_ibex_dut_probe_if.sv
vendored
|
@ -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
|
65
dv/uvm/core_ibex/env/core_ibex_env.sv
vendored
65
dv/uvm/core_ibex/env/core_ibex_env.sv
vendored
|
@ -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
|
44
dv/uvm/core_ibex/env/core_ibex_env_cfg.sv
vendored
44
dv/uvm/core_ibex/env/core_ibex_env_cfg.sv
vendored
|
@ -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
|
20
dv/uvm/core_ibex/env/core_ibex_env_pkg.sv
vendored
20
dv/uvm/core_ibex/env/core_ibex_env_pkg.sv
vendored
|
@ -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
|
|
@ -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
|
66
dv/uvm/core_ibex/env/core_ibex_rvfi_if.sv
vendored
66
dv/uvm/core_ibex/env/core_ibex_rvfi_if.sv
vendored
|
@ -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
|
17
dv/uvm/core_ibex/env/core_ibex_vseqr.sv
vendored
17
dv/uvm/core_ibex/env/core_ibex_vseqr.sv
vendored
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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())
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue