diff --git a/dv/cs_registers/Makefile b/dv/cs_registers/Makefile new file mode 100644 index 00000000..1038f27f --- /dev/null +++ b/dv/cs_registers/Makefile @@ -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 + diff --git a/dv/cs_registers/README.md b/dv/cs_registers/README.md new file mode 100644 index 00000000..3622e7fa --- /dev/null +++ b/dv/cs_registers/README.md @@ -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. + diff --git a/dv/cs_registers/env/env_dpi.cc b/dv/cs_registers/env/env_dpi.cc new file mode 100644 index 00000000..8ead85b9 --- /dev/null +++ b/dv/cs_registers/env/env_dpi.cc @@ -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 +#include + +#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 diff --git a/dv/cs_registers/env/env_dpi.sv b/dv/cs_registers/env/env_dpi.sv new file mode 100644 index 00000000..c3075c10 --- /dev/null +++ b/dv/cs_registers/env/env_dpi.sv @@ -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 + + diff --git a/dv/cs_registers/env/register_environment.cc b/dv/cs_registers/env/register_environment.cc new file mode 100644 index 00000000..81736bc7 --- /dev/null +++ b/dv/cs_registers/env/register_environment.cc @@ -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(); +} diff --git a/dv/cs_registers/env/register_environment.h b/dv/cs_registers/env/register_environment.h new file mode 100644 index 00000000..72abad13 --- /dev/null +++ b/dv/cs_registers/env/register_environment.h @@ -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_ diff --git a/dv/cs_registers/env/simctrl.cc b/dv/cs_registers/env/simctrl.cc new file mode 100644 index 00000000..253fbfb3 --- /dev/null +++ b/dv/cs_registers/env/simctrl.cc @@ -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 + +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; +} diff --git a/dv/cs_registers/env/simctrl.h b/dv/cs_registers/env/simctrl.h new file mode 100644 index 00000000..b7848814 --- /dev/null +++ b/dv/cs_registers/env/simctrl.h @@ -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 diff --git a/dv/cs_registers/model/base_register.cc b/dv/cs_registers/model/base_register.cc new file mode 100644 index 00000000..a5425de8 --- /dev/null +++ b/dv/cs_registers/model/base_register.cc @@ -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 + +BaseRegister::BaseRegister( + uint32_t addr, std::vector> *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; } diff --git a/dv/cs_registers/model/base_register.h b/dv/cs_registers/model/base_register.h new file mode 100644 index 00000000..05ad18eb --- /dev/null +++ b/dv/cs_registers/model/base_register.h @@ -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 +#include +#include + +/** + * 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> *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> *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_ diff --git a/dv/cs_registers/model/register_model.cc b/dv/cs_registers/model/register_model.cc new file mode 100644 index 00000000..78d7e37c --- /dev/null +++ b/dv/cs_registers/model/register_model.cc @@ -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 + +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(reg_addr, ®ister_map_)); + } else { + register_map_.push_back( + std::make_unique(reg_addr, ®ister_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(reg_addr, ®ister_map_)); + } else { + register_map_.push_back( + std::make_unique(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 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); + } + } +} diff --git a/dv/cs_registers/model/register_model.h b/dv/cs_registers/model/register_model.h new file mode 100644 index 00000000..ba1d507b --- /dev/null +++ b/dv/cs_registers/model/register_model.h @@ -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 +#include +#include + +#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 trans); + void RegisterReset(); + + private: + std::vector> register_map_; + SimCtrl *simctrl_; +}; + +#endif // REGISTER_MODEL_H_ diff --git a/dv/cs_registers/reg_driver/reg_dpi.cc b/dv/cs_registers/reg_driver/reg_dpi.cc new file mode 100644 index 00000000..284a0b08 --- /dev/null +++ b/dv/cs_registers/reg_driver/reg_dpi.cc @@ -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 +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static std::map 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 diff --git a/dv/cs_registers/reg_driver/reg_dpi.sv b/dv/cs_registers/reg_driver/reg_dpi.sv new file mode 100644 index 00000000..13449ec3 --- /dev/null +++ b/dv/cs_registers/reg_driver/reg_dpi.sv @@ -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 + diff --git a/dv/cs_registers/reg_driver/register_driver.cc b/dv/cs_registers/reg_driver/register_driver.cc new file mode 100644 index 00000000..d8a51444 --- /dev/null +++ b/dv/cs_registers/reg_driver/register_driver.cc @@ -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 + +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(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(); + 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; + } +} diff --git a/dv/cs_registers/reg_driver/register_driver.h b/dv/cs_registers/reg_driver/register_driver.h new file mode 100644 index 00000000..61dc4427 --- /dev/null +++ b/dv/cs_registers/reg_driver/register_driver.h @@ -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 +#include + +/** + * 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 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_ diff --git a/dv/cs_registers/reg_driver/register_transaction.cc b/dv/cs_registers/reg_driver/register_transaction.cc new file mode 100644 index 00000000..3d5e2681 --- /dev/null +++ b/dv/cs_registers/reg_driver/register_transaction.cc @@ -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 + +void RegisterTransaction::Randomize(std::default_random_engine &gen) { + std::uniform_int_distribution addr_dist_ = + std::uniform_int_distribution(kCSRPMPCfg0, kCSRPMPAddr15); + std::uniform_int_distribution wdata_dist_ = + std::uniform_int_distribution(0, 0xFFFFFFFF); + std::uniform_int_distribution operation_dist_ = + std::uniform_int_distribution(kCSRRead, kCSRClear); + csr_addr = addr_dist_(gen); + csr_op = static_cast(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); + } +} diff --git a/dv/cs_registers/reg_driver/register_transaction.h b/dv/cs_registers/reg_driver/register_transaction.h new file mode 100644 index 00000000..1a8fe8d3 --- /dev/null +++ b/dv/cs_registers/reg_driver/register_transaction.h @@ -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 +#include +#include + +// 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_ diff --git a/dv/cs_registers/rst_driver/reset_driver.cc b/dv/cs_registers/rst_driver/reset_driver.cc new file mode 100644 index 00000000..229ebb4a --- /dev/null +++ b/dv/cs_registers/rst_driver/reset_driver.cc @@ -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(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; + } +} diff --git a/dv/cs_registers/rst_driver/reset_driver.h b/dv/cs_registers/rst_driver/reset_driver.h new file mode 100644 index 00000000..67a1f5a9 --- /dev/null +++ b/dv/cs_registers/rst_driver/reset_driver.h @@ -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 +#include + +/** + * 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 delay_dist_; +}; + +#endif // RESET_DRIVER_H_ diff --git a/dv/cs_registers/rst_driver/rst_dpi.cc b/dv/cs_registers/rst_driver/rst_dpi.cc new file mode 100644 index 00000000..eb66af7c --- /dev/null +++ b/dv/cs_registers/rst_driver/rst_dpi.cc @@ -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 +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static std::map 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 diff --git a/dv/cs_registers/rst_driver/rst_dpi.sv b/dv/cs_registers/rst_driver/rst_dpi.sv new file mode 100644 index 00000000..8965b2a2 --- /dev/null +++ b/dv/cs_registers/rst_driver/rst_dpi.sv @@ -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 diff --git a/dv/cs_registers/tb/tb_cs_registers.cc b/dv/cs_registers/tb/tb_cs_registers.cc new file mode 100644 index 00000000..02147af7 --- /dev/null +++ b/dv/cs_registers/tb/tb_cs_registers.cc @@ -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; +} diff --git a/dv/cs_registers/tb/tb_cs_registers.sv b/dv/cs_registers/tb/tb_cs_registers.sv new file mode 100644 index 00000000..b844e40c --- /dev/null +++ b/dv/cs_registers/tb/tb_cs_registers.sv @@ -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 diff --git a/dv/cs_registers/tb_cs_registers.core b/dv/cs_registers/tb_cs_registers.core new file mode 100644 index 00000000..6f17641f --- /dev/null +++ b/dv/cs_registers/tb_cs_registers.core @@ -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" diff --git a/dv/riscv_compliance/ibex_riscv_compliance.cc b/dv/riscv_compliance/ibex_riscv_compliance.cc index e5f02eef..8ea05ea1 100644 --- a/dv/riscv_compliance/ibex_riscv_compliance.cc +++ b/dv/riscv_compliance/ibex_riscv_compliance.cc @@ -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; diff --git a/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.cc b/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.cc index 58c9d21f..b2419b82 100644 --- a/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.cc +++ b/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.cc @@ -5,6 +5,7 @@ #include "verilator_sim_ctrl.h" #include +#include #include #include #include @@ -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_++; diff --git a/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.h b/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.h index 183939f6..e6f98444 100644 --- a/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.h +++ b/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.h @@ -9,6 +9,7 @@ #include #include +#include #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 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();