[DV] Add registers testbench

- Sample C++ unit testbench for system registers module
- Only tests a few PMP registers at the moment
This commit is contained in:
Tom Roberts 2019-08-29 16:16:30 +01:00 committed by Tom Roberts
parent e5cf0c0fcf
commit 70b53068db
28 changed files with 1525 additions and 64 deletions

84
dv/cs_registers/Makefile Normal file
View file

@ -0,0 +1,84 @@
# 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)
# Reg TB specific parameters / defines
PMP_ENABLE = 1
PMP_NUM_REGIONS = 4
PMP_GRANULARITY = 0
CFLAGS += -DPMP_ENABLE=$(PMP_ENABLE) -DPMP_NUM_REGIONS=$(PMP_NUM_REGIONS)
CFLAGS += -DPMP_GRANULARITY=$(PMP_GRANULARITY)
# 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)
fusesoc --cores-root=../../ run --setup --build --target=sim --tool=$(TOOL) \
lowrisc:ibex:tb_cs_registers --PMPEnable \
--PMPNumRegions=$(PMP_NUM_REGIONS) --PMPGranularity=$(PMP_GRANULARITY)
$(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

55
dv/cs_registers/README.md Normal file
View file

@ -0,0 +1,55 @@
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
----------------------------------
(from this directory)
Verilator version:
```sh
make TOOL=verilator
./build/lowrisc_ibex_tb_cs_registers_0/sim-verilator/Vtb_cs_registers
```
VCS version:
```sh
make TOOL=vcs
./build/lowrisc_ibex_tb_cs_registers_0/sim-vcs/lowrisc_ibex_tb_cs_registers_0
```
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.

35
dv/cs_registers/env/env_dpi.cc vendored Normal file
View file

@ -0,0 +1,35 @@
// 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
RegisterEnvironment *reg_env;
void env_initial(svBitVecVal *seed) {
// Create TB environment
reg_env = new RegisterEnvironment();
// Initial setup
reg_env->OnInitial(*seed);
}
void env_final() {
reg_env->OnFinal();
delete reg_env;
}
void env_tick(svBit *stop_req) { reg_env->GetStopReq(stop_req); }
#ifdef __cplusplus
}
#endif

19
dv/cs_registers/env/env_dpi.sv vendored Normal file
View file

@ -0,0 +1,19 @@
// 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);
import "DPI-C"
function void env_final();
import "DPI-C"
function void env_tick(
output bit stop_req);
endpackage

View file

@ -0,0 +1,30 @@
// 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()
: simctrl_(new SimCtrl()),
reg_model_(new RegisterModel(simctrl_)),
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();
}

View file

@ -0,0 +1,32 @@
// 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 "reset_driver.h"
#include "simctrl.h"
/**
* Class to instantiate all tb components
*/
class RegisterEnvironment {
public:
RegisterEnvironment();
void OnInitial(unsigned int seed);
void OnFinal();
void GetStopReq(unsigned char *stop_req);
private:
SimCtrl *simctrl_;
RegisterModel *reg_model_;
RegisterDriver *reg_driver_;
ResetDriver *rst_driver_;
};
#endif // REGISTER_ENVIRONMENT_H_

24
dv/cs_registers/env/simctrl.cc vendored Normal file
View file

@ -0,0 +1,24 @@
// 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_; }
void SimCtrl::OnFinal() {
std::cout << std::endl
<< "//-------------//" << std::endl
<< (success_ ? "// TEST PASSED //" : "// TEST FAILED //")
<< std::endl
<< "//-------------//" << std::endl;
}

20
dv/cs_registers/env/simctrl.h vendored Normal file
View file

@ -0,0 +1,20 @@
// 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();
void OnFinal();
private:
bool stop_requested_;
bool success_;
};
#endif

View file

@ -0,0 +1,167 @@
// 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 <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) {
return (addr == register_address_);
}
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 (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; }
uint32_t PmpCfgRegister::GetLockMask() {
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_ &= raz_mask_;
for (int i = 0; i < 4; i++) {
// Reserved check, W = 1, R = 0
if (((register_value_ >> (8 * i)) & 0x3) == 0x2) {
register_value_ &= ~(0x3 << (8 * i));
}
}
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_ &= raz_mask_;
for (int i = 0; i < 4; i++) {
// Reserved check, W = 1, R = 0
if (((register_value_ >> (8 * i)) & 0x3) == 0x2) {
register_value_ &= ~(0x3 << (8 * i));
}
}
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_ &= raz_mask_;
for (int i = 0; i < 4; i++) {
// Reserved check, W = 1, R = 0
if (((register_value_ >> (8 * i)) & 0x3) == 0x2) {
register_value_ &= ~(0x3 << (8 * i));
}
}
return read_value;
}
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; }

View file

@ -0,0 +1,77 @@
// 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);
virtual uint32_t GetLockMask();
protected:
uint32_t register_value_;
uint32_t register_address_;
std::vector<std::unique_ptr<BaseRegister>> *map_pointer_;
};
/**
* 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:
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);
};
#endif // BASE_REGISTER_H_

View file

@ -0,0 +1,56 @@
// 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) : simctrl_(sc) {
// Instantiate all the registers
for (int i = 0; i < 4; i++) {
uint32_t reg_addr = 0x3A0 + i;
if (PMP_ENABLE && (i < (PMP_NUM_REGIONS / 4))) {
register_map_.push_back(
std::make_unique<PmpCfgRegister>(reg_addr, &register_map_));
} else {
register_map_.push_back(
std::make_unique<NonImpRegister>(reg_addr, &register_map_));
}
}
for (int i = 0; i < 16; i++) {
uint32_t reg_addr = 0x3B0 + i;
if (PMP_ENABLE && (i < PMP_NUM_REGIONS)) {
register_map_.push_back(
std::make_unique<PmpAddrRegister>(reg_addr, &register_map_));
} else {
register_map_.push_back(
std::make_unique<NonImpRegister>(reg_addr, &register_map_));
}
}
}
void RegisterModel::RegisterReset() {
for (auto it = register_map_.begin(); it != register_map_.end(); ++it) {
(*it)->RegisterReset();
}
}
void RegisterModel::NewTransaction(std::unique_ptr<RegisterTransaction> trans) {
// TODO add machine mode permissions to registers
bool matched = false;
for (auto it = register_map_.begin(); it != register_map_.end(); ++it) {
if ((*it)->ProcessTransaction(&matched, trans.get())) {
simctrl_->RequestStop(false);
}
}
if (!matched) {
// Non existant register
if (!trans->illegal_csr) {
std::cout << "Non-existant register:" << std::endl;
trans->Print();
std::cout << "Should have signalled an error." << std::endl;
simctrl_->RequestStop(false);
}
}
}

View file

@ -0,0 +1,31 @@
// 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 "simctrl.h"
/**
* Class modelling CS register state and checking against RTL
*/
class RegisterModel {
public:
RegisterModel(SimCtrl *sc);
void NewTransaction(std::unique_ptr<RegisterTransaction> trans);
void RegisterReset();
private:
std::vector<std::unique_ptr<BaseRegister>> register_map_;
SimCtrl *simctrl_;
};
#endif // REGISTER_MODEL_H_

View file

@ -0,0 +1,50 @@
// 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,
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 || !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,
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_addr, csr_wdata);
}
}
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,27 @@
// 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 [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 [11:0] csr_addr,
output bit [31:0] csr_wdata);
endpackage

View file

@ -0,0 +1,76 @@
// 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,
uint32_t *addr, uint32_t *wdata) {
*access = reg_access_;
*op = next_transaction_.csr_op;
*addr = next_transaction_.csr_addr;
*wdata = next_transaction_.csr_wdata;
}
void RegisterDriver::OnClock() {
if (transactions_driven_ >= 10000) {
simctrl_->RequestStop(true);
}
if (--delay_ == 0) {
Randomize();
++transactions_driven_;
} else {
reg_access_ = false;
}
}

View file

@ -0,0 +1,50 @@
// 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, uint32_t *addr,
uint32_t *wdata);
private:
void Randomize();
std::default_random_engine generator_;
int delay_;
bool reg_access_;
CSRegisterOperation reg_op_;
std::uniform_int_distribution<int> delay_dist_;
uint32_t reg_addr_;
uint32_t reg_wdata_;
int transactions_driven_;
RegisterTransaction next_transaction_;
std::string name_;
RegisterModel *reg_model_;
SimCtrl *simctrl_;
};
#endif // REGISTER_DRIVER_H_

View file

@ -0,0 +1,93 @@
// 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>(kCSRPMPCfg0, kCSRPMPAddr15);
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);
csr_addr = addr_dist_(gen);
csr_op = static_cast<CSRegisterOperation>(operation_dist_(gen));
if (csr_op != kCSRRead) {
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() {
switch (csr_addr) {
case kCSRPMPCfg0:
return "PMP Cfg 0";
case kCSRPMPCfg1:
return "PMP Cfg 1";
case kCSRPMPCfg2:
return "PMP Cfg 2";
case kCSRPMPCfg3:
return "PMP Cfg 3";
case kCSRPMPAddr0:
return "PMP Addr 0";
case kCSRPMPAddr1:
return "PMP Addr 1";
case kCSRPMPAddr2:
return "PMP Addr 2";
case kCSRPMPAddr3:
return "PMP Addr 3";
case kCSRPMPAddr4:
return "PMP Addr 4";
case kCSRPMPAddr5:
return "PMP Addr 5";
case kCSRPMPAddr6:
return "PMP Addr 6";
case kCSRPMPAddr7:
return "PMP Addr 7";
case kCSRPMPAddr8:
return "PMP Addr 8";
case kCSRPMPAddr9:
return "PMP Addr 9";
case kCSRPMPAddr10:
return "PMP Addr 10";
case kCSRPMPAddr11:
return "PMP Addr 11";
case kCSRPMPAddr12:
return "PMP Addr 12";
case kCSRPMPAddr13:
return "PMP Addr 13";
case kCSRPMPAddr14:
return "PMP Addr 14";
case kCSRPMPAddr15:
return "PMP Addr 15";
default:
return "Undef reg: " + std::to_string(csr_addr);
}
}

View file

@ -0,0 +1,60 @@
// 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 four register operation types
enum CSRegisterOperation : int {
kCSRRead = 0,
kCSRWrite = 1,
kCSRSet = 2,
kCSRClear = 3
};
// Enumerate the supported register types
enum CSRegisterAddr : int {
kCSRPMPCfg0 = 0x3A0,
kCSRPMPCfg1 = 0x3A1,
kCSRPMPCfg2 = 0x3A2,
kCSRPMPCfg3 = 0x3A3,
kCSRPMPAddr0 = 0x3B0,
kCSRPMPAddr1 = 0x3B1,
kCSRPMPAddr2 = 0x3B2,
kCSRPMPAddr3 = 0x3B3,
kCSRPMPAddr4 = 0x3B4,
kCSRPMPAddr5 = 0x3B5,
kCSRPMPAddr6 = 0x3B6,
kCSRPMPAddr7 = 0x3B7,
kCSRPMPAddr8 = 0x3B8,
kCSRPMPAddr9 = 0x3B9,
kCSRPMPAddr10 = 0x3BA,
kCSRPMPAddr11 = 0x3BB,
kCSRPMPAddr12 = 0x3BC,
kCSRPMPAddr13 = 0x3BD,
kCSRPMPAddr14 = 0x3BE,
kCSRPMPAddr15 = 0x3BF
};
struct RegisterTransaction {
public:
void Randomize(std::default_random_engine &gen);
void Print();
CSRegisterOperation csr_op;
bool illegal_csr;
uint32_t csr_addr;
uint32_t csr_rdata;
uint32_t csr_wdata;
private:
std::string RegOpString();
std::string RegAddrString();
};
#endif // REGISTER_TRANSACTION_H_

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,35 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "Vtb_cs_registers.h"
#include "verilated_toplevel.h"
#include "verilator_sim_ctrl.h"
tb_cs_registers *top;
VerilatorSimCtrl *simctrl;
// dummy definition since this DPI call doesn't exist
// TODO : remove this - see Ibex #317
extern "C" {
void simutil_verilator_memload(const char *file) {}
}
int main(int argc, char **argv) {
int retcode = 0;
// init top verilog instance
top = new tb_cs_registers;
// Create SimCtrl instance
simctrl = new VerilatorSimCtrl(top, top->clk_i, top->in_rst_ni,
VerilatorSimCtrlFlags::ResetPolarityNegative);
// Setup the simulation
retcode = simctrl->SetupSimulation(argc, argv);
// Run the simulation
simctrl->RunSimulation();
delete top;
delete simctrl;
return retcode;
}

View file

@ -0,0 +1,166 @@
// 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 int unsigned MHPMCounterNum = 8,
parameter int unsigned MHPMCounterWidth = 40,
parameter bit PMPEnable = 0,
parameter int unsigned PMPGranularity = 0,
parameter int unsigned PMPNumRegions = 4,
parameter bit RV32E = 0,
parameter bit RV32M = 0
) (
// Clock and Reset
inout wire clk_i,
inout wire in_rst_ni
);
logic dpi_rst_ni;
logic rst_ni;
logic [31:0] hart_id_i;
// Privilege mode
ibex_pkg::priv_lvl_e priv_mode_id_o;
ibex_pkg::priv_lvl_e priv_mode_if_o;
ibex_pkg::priv_lvl_e priv_mode_lsu_o;
logic csr_mstatus_tw_o;
// mtvec
logic [31:0] csr_mtvec_o;
logic csr_mtvec_init_i;
logic [31:0] boot_addr_i;
// 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 [31:0] csr_rdata_o;
// interrupts
logic irq_software_i;
logic irq_timer_i;
logic irq_external_i;
logic [14:0] irq_fast_i;
logic irq_pending_o; // interupt request pending
logic csr_msip_o; // software interrupt pending
logic csr_mtip_o; // timer interrupt pending
logic csr_meip_o; // external interrupt pending
logic [14:0] csr_mfip_o; // fast interrupt pending
logic csr_mstatus_mie_o;
logic [31:0] csr_mepc_o;
// PMP
ibex_pkg::pmp_cfg_t csr_pmp_cfg_o [PMPNumRegions];
logic [33:0] csr_pmp_addr_o [PMPNumRegions];
// debug
logic debug_mode_i;
ibex_pkg::dbg_cause_e debug_cause_i;
logic debug_csr_save_i;
logic [31:0] csr_depc_o;
logic debug_single_step_o;
logic debug_ebreakm_o;
logic [31:0] pc_if_i;
logic [31:0] pc_id_i;
logic csr_save_if_i;
logic csr_save_id_i;
logic csr_restore_mret_i;
logic csr_save_cause_i;
ibex_pkg::exc_cause_e csr_mcause_i;
logic [31:0] csr_mtval_i;
logic illegal_csr_insn_o; // access to non-existent CSR,
// with wrong priviledge level, or
// missing write permissions
logic instr_new_id_i; // ID stage sees a new instr
// Performance Counters
logic instr_ret_i; // instr retired in ID/EX stage
logic instr_ret_compressed_i; // compressed instr retired
logic imiss_i; // instr fetch
logic pc_set_i; // PC was set to a new value
logic jump_i; // jump instr seen (j, jr, jal, jalr)
logic branch_i; // branch instr seen (bf, bnf)
logic branch_taken_i; // branch was taken
logic mem_load_i; // load from memory in this cycle
logic mem_store_i; // store to memory in this cycle
logic lsu_busy_i;
//-----------------
// 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
ibex_cs_registers #(
.MHPMCounterNum (MHPMCounterNum),
.MHPMCounterWidth (MHPMCounterWidth),
.PMPEnable (PMPEnable),
.PMPGranularity (PMPGranularity),
.PMPNumRegions (PMPNumRegions),
.RV32E (RV32E),
.RV32M (RV32M)
) i_cs_regs (.*);
// DPI calls
bit stop_simulation;
bit [31:0] seed;
initial begin
if (!$value$plusargs ("ntb_random_seed=%d", seed)) begin
seed = 32'd0;
end
env_dpi::env_initial(seed);
end
final begin
env_dpi::env_final();
end
always_ff @(posedge clk_i) begin
env_dpi::env_tick(stop_simulation);
rst_dpi::rst_tick("rstn_driver", dpi_rst_ni);
if (stop_simulation) begin
$finish();
end
end
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_addr_i,
csr_wdata_i,
csr_rdata_o);
reg_dpi::driver_tick("reg_driver",
csr_access_i,
csr_op_i,
csr_addr_i,
csr_wdata_i);
end
// Note that CSR accesses only happen if instr_new_id_i is high
assign instr_new_id_i = csr_access_i;
endmodule

View file

@ -0,0 +1,83 @@
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_sim_verilator:
depend:
- lowrisc:dv_verilator:simutil_verilator
files:
- tb/tb_cs_registers.cc: { file_type: cppSource }
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
parameters:
PMPEnable:
datatype: bool
paramtype: vlogparam
default: true
description: PMP enabled [true/false]
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]
targets:
sim:
default_tool: verilator
toplevel: tb_cs_registers
filesets:
- files_sim
- files_sim_verilator
parameters:
- PMPEnable
- PMPNumRegions
- PMPGranularity
tools:
vcs:
vcs_options:
- '${PWD}/build/bin/reg_dpi.so'
- '-debug_access+all'
verilator:
mode: cc
libs:
- '${PWD}/build/bin/reg_dpi.so'
verilator_options:
# Disabling tracing reduces compile times by multiple times, but doesn't have a
# huge influence on runtime performance. (Based on early observations.)
- '--trace'
- '--trace-fst' # this requires -DVM_TRACE_FMT_FST in CFLAGS below!
- '--trace-structs'
- '--trace-params'
- '--trace-max-array 1024'
# compiler flags
#
# -O
# Optimization levels have a large impact on the runtime performance of the
# simulation model. -O2 and -O3 are pretty similar, -Os is slower than -O2/-O3
- '-CFLAGS "-std=c++14 -Wall -DTOPLEVEL_NAME=tb_cs_registers -DVM_TRACE_FMT_FST -g -O0"'
- '-LDFLAGS "-pthread -lutil"'
- "-Wall"
- "-Wno-PINCONNECTEMPTY"
# XXX: Cleanup all warnings and remove this option
# (or make it more fine-grained at least)
- "-Wno-fatal"

View file

@ -12,78 +12,21 @@
ibex_riscv_compliance *top;
VerilatorSimCtrl *simctrl;
static void SignalHandler(int sig) {
if (!simctrl) {
return;
}
switch (sig) {
case SIGINT:
simctrl->RequestStop();
break;
case SIGUSR1:
if (simctrl->TracingEnabled()) {
simctrl->TraceOff();
} else {
simctrl->TraceOn();
}
break;
}
}
static void SetupSignalHandler() {
struct sigaction sigIntHandler;
sigIntHandler.sa_handler = SignalHandler;
sigemptyset(&sigIntHandler.sa_mask);
sigIntHandler.sa_flags = 0;
sigaction(SIGINT, &sigIntHandler, NULL);
sigaction(SIGUSR1, &sigIntHandler, NULL);
}
/**
* Get the current simulation time
*
* Called by $time in Verilog, converts to double, to match what SystemC does
*/
double sc_time_stamp() { return simctrl->GetTime(); }
int main(int argc, char **argv) {
int retcode;
top = new ibex_riscv_compliance;
simctrl = new VerilatorSimCtrl(top, top->IO_CLK, top->IO_RST_N,
VerilatorSimCtrlFlags::ResetPolarityNegative);
SetupSignalHandler();
if (!simctrl->ParseCommandArgs(argc, argv, retcode)) {
goto free_return;
}
std::cout << "Simulation of Ibex" << std::endl
<< "==================" << std::endl
<< std::endl;
if (simctrl->TracingPossible()) {
std::cout << "Tracing can be toggled by sending SIGUSR1 to this process:"
<< std::endl
<< "$ kill -USR1 " << getpid() << std::endl;
}
// Setup simctrl
retcode = simctrl->SetupSimulation(argc, argv);
// Initialize RAM
simctrl->InitRam("TOP.ibex_riscv_compliance.u_ram");
simctrl->Run();
simctrl->PrintStatistics();
// Run the simulation
simctrl->RunSimulation();
if (simctrl->TracingEverEnabled()) {
std::cout << std::endl
<< "You can view the simulation traces by calling" << std::endl
<< "$ gtkwave " << simctrl->GetSimulationFileName() << std::endl;
}
free_return:
delete top;
delete simctrl;
return retcode;

View file

@ -5,6 +5,7 @@
#include "verilator_sim_ctrl.h"
#include <getopt.h>
#include <signal.h>
#include <sys/stat.h>
#include <unistd.h>
#include <vltstd/svdpi.h>
@ -16,6 +17,42 @@
#define VM_TRACE 0
#endif
// Static pointer to a single simctrl instance
// used by SignalHandler
static VerilatorSimCtrl *simctrl;
static void SignalHandler(int sig) {
if (!simctrl) {
return;
}
switch (sig) {
case SIGINT:
simctrl->RequestStop(true);
break;
case SIGUSR1:
if (simctrl->TracingEnabled()) {
simctrl->TraceOff();
} else {
simctrl->TraceOn();
}
break;
}
}
/**
* Get the current simulation time
*
* Called by $time in Verilog, converts to double, to match what SystemC does
*/
double sc_time_stamp() {
if (simctrl) {
return simctrl->GetTime();
} else {
return 0;
}
}
// DPI Exports
extern "C" {
extern void simutil_verilator_memload(const char *file);
@ -38,10 +75,60 @@ VerilatorSimCtrl::VerilatorSimCtrl(VerilatedToplevel *top, CData &sig_clk,
initial_reset_delay_cycles_(2),
reset_duration_cycles_(2),
request_stop_(false),
simulation_success_(true),
tracer_(VerilatedTracer()),
term_after_cycles_(0) {}
term_after_cycles_(0),
callback_(nullptr) {}
void VerilatorSimCtrl::RequestStop() { request_stop_ = true; }
int VerilatorSimCtrl::SetupSimulation(int argc, char **argv) {
int retval;
// Setup the signal handler for this instance
RegisterSignalHandler();
// Parse the command line argumanets
if (!ParseCommandArgs(argc,argv,retval)) {
return retval;
}
return 0;
}
void VerilatorSimCtrl::RunSimulation() {
// Print helper message for tracing
if (TracingPossible()) {
std::cout << "Tracing can be toggled by sending SIGUSR1 to this process:"
<< std::endl
<< "$ kill -USR1 " << getpid() << std::endl;
}
// Run the simulation
Run();
// Print simulation speed info
PrintStatistics();
// Print helper message for tracing
if (TracingEverEnabled()) {
std::cout << std::endl
<< "You can view the simulation traces by calling" << std::endl
<< "$ gtkwave " << GetSimulationFileName() << std::endl;
}
}
void VerilatorSimCtrl::RegisterSignalHandler() {
struct sigaction sigIntHandler;
// Point the static simctrl pointer at this object
simctrl = this;
sigIntHandler.sa_handler = SignalHandler;
sigemptyset(&sigIntHandler.sa_mask);
sigIntHandler.sa_flags = 0;
sigaction(SIGINT, &sigIntHandler, NULL);
sigaction(SIGUSR1, &sigIntHandler, NULL);
}
void VerilatorSimCtrl::RequestStop(bool simulation_success) {
request_stop_ = true;
simulation_success_ &= simulation_success;
}
bool VerilatorSimCtrl::TraceOn() {
bool old_tracing_enabled = tracing_enabled_;
@ -259,6 +346,10 @@ const char *VerilatorSimCtrl::GetSimulationFileName() const {
#endif
}
void VerilatorSimCtrl::SetOnClockCallback(SimCtrlCallBack callback) {
callback_ = callback;
}
void VerilatorSimCtrl::Run() {
// We always need to enable this as tracing can be enabled at runtime
if (tracing_possible_) {
@ -285,6 +376,10 @@ void VerilatorSimCtrl::Run() {
sig_clk_ = !sig_clk_;
if (sig_clk_ && (callback_ != nullptr)) {
callback_(time_);
}
top_->eval();
time_++;

View file

@ -9,6 +9,7 @@
#include <chrono>
#include <string>
#include <functional>
#include "verilated_toplevel.h"
@ -17,11 +18,43 @@ enum VerilatorSimCtrlFlags {
ResetPolarityNegative = 1,
};
// Callback function to be called every clock cycle
// The parameter is simulation time
typedef std::function<void(int /* sim time */)> SimCtrlCallBack;
class VerilatorSimCtrl {
public:
VerilatorSimCtrl(VerilatedToplevel *top, CData &clk, CData &rst_n,
VerilatorSimCtrlFlags flags = Defaults);
/**
* A helper function to execute some standard setup commands.
*
* This function performs the followind tasks:
* 1. Sets up a signal handler to enable tracing to be turned on/off during
* a run by sending SIGUSR1 to the process
* 2. Parses a C-style set of command line arguments (see ParseCommandArgs)
*
* @return return code (0 = success)
*/
int SetupSimulation(int argc, char **argv);
/**
* A helper function to execute a standard set of run commands.
*
* This function performs the followind tasks:
* 1. Prints some tracer-related helper messages
* 2. Runs the simulation
* 3. Prints some further helper messages and statistics once the simulation
* has run to completion
*/
void RunSimulation();
/**
* Register the signal handler
*/
void RegisterSignalHandler();
/**
* Print help how to use this tool
*/
@ -65,6 +98,11 @@ class VerilatorSimCtrl {
*/
unsigned long GetTime() { return time_; }
/**
* Get the simulation result
*/
bool WasSimulationSuccessful() { return simulation_success_; }
/**
* Set the number of clock cycles (periods) before the reset signal is
* activated
@ -79,7 +117,7 @@ class VerilatorSimCtrl {
/**
* Request the simulation to stop
*/
void RequestStop();
void RequestStop(bool simulation_success);
/**
* Enable tracing (if possible)
@ -122,6 +160,11 @@ class VerilatorSimCtrl {
const char *GetSimulationFileName() const;
/**
* Set a callback function to run every cycle
*/
void SetOnClockCallback(SimCtrlCallBack callback);
private:
VerilatedToplevel *top_;
CData &sig_clk_;
@ -141,10 +184,12 @@ class VerilatorSimCtrl {
unsigned int initial_reset_delay_cycles_;
unsigned int reset_duration_cycles_;
volatile unsigned int request_stop_;
volatile bool simulation_success_;
std::chrono::steady_clock::time_point time_begin_;
std::chrono::steady_clock::time_point time_end_;
VerilatedTracer tracer_;
int term_after_cycles_;
SimCtrlCallBack callback_;
unsigned int GetExecutionTimeMs();
void SetReset();