Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,38 @@ jobs:
build/riscv-sim
build/libdbt-rise-riscv.so.2.1.0

systemc-tests:
name: SystemC Wrapper Tests
runs-on: ubuntu-24.04
timeout-minutes: 60
env:
WITH_SYSTEMC: 1
steps:
- uses: actions/checkout@v4

- name: Update submodules
run: git submodule update --init --recursive

- name: Cache Conan
uses: actions/cache@v4
with:
path: ~/.conan2
key: conan-${{ runner.os }}-systemc-${{ hashFiles('conanfile.py') }}

- name: Install dependencies
run: |
sudo apt-get install -y g++ python3-pip cmake
pip3 install conan

- name: Configure
run: cmake --preset Release -B build -DDBT_RISE_RISCV_BUILD_SC_TESTS=ON -DDBT_RISE_RISCV_ENABLE_VECTOR=OFF

- name: Build
run: cmake --build build -j --target sysc_tests

- name: Test
run: ctest --test-dir build --output-on-failure

pmp-tests:
name: PMP Functional Tests
runs-on: ubuntu-24.04
Expand Down
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ option(UPDATE_EXTERNAL_PROJECT "Whether to pull changes in external projects" ON
option(OPTIMIZE_FOR_NATIVE "Build with -march=native" OFF)
option(NO_GENERIC_CORES "Leave out the generic cores to improve build times" OFF)
option(DBT_RISE_RISCV_ENABLE_VECTOR "Build RISC-V vector cores and VMs" ON)
option(DBT_RISE_RISCV_BUILD_SC_TESTS "Build the GoogleTest suite for the SystemC wrapper (needs SystemC, CCI and GTest)" OFF)
include(GNUInstallDirs)
include(flink)

Expand Down Expand Up @@ -357,6 +358,10 @@ if(TARGET scc-sysc)
else()
add_library(${PROJECT_NAME} ALIAS ${PROJECT_NAME}_tlm)
endif()
if(DBT_RISE_RISCV_BUILD_SC_TESTS)
enable_testing()
add_subdirectory(tests/sysc)
endif()
endif()

project(elfio-test)
Expand Down
4 changes: 4 additions & 0 deletions conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ def requirements(self):
self.requires("asmjit/cci.20240531")
if "WITH_LLVM" in os.environ:
self.requires("llvm-core/19.1.7")
if "WITH_SYSTEMC" in os.environ:
self.requires("systemc/3.0.1", override=True)
self.requires("systemc-cci/1.0.0")
self.requires("gtest/1.15.0")
if os.path.isdir("dbt-rise-plugins"):
self.requires("lua/5.4.3")

Expand Down
19 changes: 18 additions & 1 deletion src/sysc/core_complex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,17 @@ int core_complex<BUSWIDTH, QK>::cmd_sysc(int argc, char* argv[], debugger::out_f
return Err;
}

template <unsigned int BUSWIDTH, typename QK> void core_complex<BUSWIDTH, QK>::reset(uint64_t addr) { vm->reset(addr); }
template <unsigned int BUSWIDTH, typename QK> void core_complex<BUSWIDTH, QK>::reset(uint64_t addr) {
if(iss_running) {
// the core may be suspended mid-instruction, so stop it at the next instruction boundary and let run() apply the reset
pending_reset_addr = addr;
core->set_interrupt_execution(true);
return;
}
vm->reset(addr);
// the reset clears the cycle counter, qk_sync would otherwise see a wrapped delta and stall the core
last_sync_cycle = vm->get_arch()->get_instrumentation_if()->get_total_cycles();
}
template <unsigned int BUSWIDTH, typename QK>
inline std::pair<uint64_t, bool> core_complex<BUSWIDTH, QK>::load_file(std::string const& name) {
iss::arch_if* cc = vm->get_arch();
Expand Down Expand Up @@ -385,7 +395,14 @@ template <unsigned int BUSWIDTH, typename QK> void core_complex<BUSWIDTH, QK>::r
}
quantum_keeper.reset(sc_core::sc_time_stamp());
core->set_interrupt_execution(false);
iss_running = true;
run_iss();
iss_running = false;
if(pending_reset_addr) {
reset(*pending_reset_addr);
pending_reset_addr.reset();
core->set_interrupt_execution(false);
}
} while(!core->get_interrupt_execution());
if(finish_evt_inuse)
finish_evt.notify();
Expand Down
3 changes: 3 additions & 0 deletions src/sysc/core_complex.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
#endif
#endif
#include <memory>
#include <optional>
#include <tlm>
#include <tlm_utils/tlm_quantumkeeper.h>
#include <util/range_lut.h>
Expand Down Expand Up @@ -309,6 +310,8 @@ class core_complex : public sc_core::sc_module, public scc::traceable, public co
//
///////////////////////////////////////////////////////////////////////////////
uint64_t last_sync_cycle = 0;
bool iss_running{false};
std::optional<uint64_t> pending_reset_addr;
uint64_t qk_preaccounted_cycles{0};
util::range_lut<tlm_dmi_ext> fetch_lut{tlm_dmi_ext()};
util::range_lut<tlm_dmi_ext>& get_read_lut(unsigned space) {
Expand Down
11 changes: 11 additions & 0 deletions tests/sysc/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
find_package(GTest REQUIRED)
include(GoogleTest)

add_executable(sysc_tests
test_main.cpp
core_complex_test.cpp
)
target_link_libraries(sysc_tests PRIVATE scc::components GTest::gtest)
# whole-archive so the static core registrations in register_cores.cpp are kept
target_force_link_libraries(sysc_tests PRIVATE dbt-rise-riscv_sc_tlm)
gtest_discover_tests(sysc_tests PROPERTIES TIMEOUT 60)
92 changes: 92 additions & 0 deletions tests/sysc/core_complex_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#include "rv32_program.h"
#include "test_main.h"
#include "testbench.h"

#include <gtest/gtest.h>
#include <memory>
#include <tlm>

namespace {

using namespace sc_core;

constexpr uint64_t reset_address = 0x0;
constexpr int32_t start_count_addr = 0x400;
constexpr int32_t loop_count_addr = 0x404;

// Counts its own starts in one RAM word, then spins incrementing another.
// RAM survives a core reset, so the start count tells how often the core
// came out of reset.
std::vector<uint32_t> counting_program() {
using namespace rv32;
return {
lw(5, start_count_addr, 0),
addi(5, 5, 1),
sw(5, start_count_addr, 0),
lw(6, loop_count_addr, 0), // loop:
addi(6, 6, 1),
sw(6, loop_count_addr, 0),
jal(0, -12), // j loop
};
}

class CoreComplexTest : public SystemCTestFixture {
protected:
void SetUp() override {
tlm::tlm_global_quantum::instance().set(sc_time(1, SC_US));
tb = std::make_unique<testbench>("tb");
tb->load(reset_address, counting_program());
}

void TearDown() override {
tb.reset();
SystemCTestFixture::TearDown();
}

uint32_t start_count() { return tb->read_word(start_count_addr); }
uint32_t loop_count() { return tb->read_word(loop_count_addr); }

std::unique_ptr<testbench> tb;
};

TEST_F(CoreComplexTest, RunsAProgramFromTheResetAddress) {
sc_start(sc_time(100, SC_US));

EXPECT_EQ(start_count(), 1u);
EXPECT_GT(loop_count(), 0u);
}

TEST_F(CoreComplexTest, AdvancesSimulationTimeWhileRunning) {
sc_start(sc_time(100, SC_US));
auto loops_at_100us = loop_count();
sc_start(sc_time(100, SC_US));

EXPECT_EQ(sc_time_stamp(), sc_time(200, SC_US));
EXPECT_GT(loop_count(), loops_at_100us);
}

// A peripheral may reset the core from its own process while the core thread
// is suspended in its quantum keeper.
TEST_F(CoreComplexTest, KeepsExecutingAfterAResetWhileRunning) {
sc_spawn([this] {
wait(sc_time(100, SC_US));
tb->cpu.reset(reset_address);
});
sc_start(sc_time(100, SC_US) + sc_time(1, SC_NS));
auto loops_at_reset = loop_count();
sc_start(sc_time(100, SC_US));

EXPECT_GT(loop_count(), loops_at_reset);
}

TEST_F(CoreComplexTest, RestartsAtTheResetAddressAfterAResetWhileRunning) {
sc_spawn([this] {
wait(sc_time(100, SC_US));
tb->cpu.reset(reset_address);
});
sc_start(sc_time(200, SC_US));

EXPECT_EQ(start_count(), 2u);
}

} // namespace
28 changes: 28 additions & 0 deletions tests/sysc/rv32_program.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once

#include <cstdint>

// Minimal RV32I encoder, enough to hand-assemble test programs without a
// RISC-V toolchain.
namespace rv32 {

constexpr uint32_t i_type(int32_t imm, unsigned rs1, unsigned funct3, unsigned rd, unsigned opcode) {
return (static_cast<uint32_t>(imm & 0xfff) << 20) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | opcode;
}

constexpr uint32_t lw(unsigned rd, int32_t offset, unsigned rs1) { return i_type(offset, rs1, 2, rd, 0x03); }

constexpr uint32_t addi(unsigned rd, unsigned rs1, int32_t imm) { return i_type(imm, rs1, 0, rd, 0x13); }

constexpr uint32_t sw(unsigned rs2, int32_t offset, unsigned rs1) {
auto imm = static_cast<uint32_t>(offset & 0xfff);
return ((imm >> 5) << 25) | (rs2 << 20) | (rs1 << 15) | (2 << 12) | ((imm & 0x1f) << 7) | 0x23;
}

constexpr uint32_t jal(unsigned rd, int32_t offset) {
auto imm = static_cast<uint32_t>(offset);
return (((imm >> 20) & 0x1) << 31) | (((imm >> 1) & 0x3ff) << 21) | (((imm >> 11) & 0x1) << 20) | (((imm >> 12) & 0xff) << 12) |
(rd << 7) | 0x6f;
}

} // namespace rv32
29 changes: 29 additions & 0 deletions tests/sysc/test_main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include "test_main.h"

#include <algorithm>
#include <cstdlib>
#include <gtest/gtest.h>
#include <scc/report.h>
#include <systemc>

int sc_main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
// Also registers the global CCI broker, which must exist exactly once and
// outlive every simulation context the fixture creates.
auto* verbose = std::getenv("SCC_TEST_VERBOSE");
auto level = verbose ? static_cast<scc::log>(std::min(std::strtoul(verbose, nullptr, 10) + 4, 7UL)) : scc::log::WARNING;
scc::init_logging(scc::LogConfig().logLevel(level).logAsync(false));
return RUN_ALL_TESTS();
}

SystemCTestFixture::SystemCTestFixture() {
sc_core::sc_report_handler::set_actions(sc_core::SC_ID_NO_SC_START_ACTIVITY_, sc_core::SC_DO_NOTHING);
ResetSystemCContext();
}

void SystemCTestFixture::ResetSystemCContext() {
sc_core::sc_curr_simcontext = new sc_core::sc_simcontext();
sc_core::sc_default_global_context = sc_core::sc_curr_simcontext;
}

void SystemCTestFixture::TearDown() { ResetSystemCContext(); }
19 changes: 19 additions & 0 deletions tests/sysc/test_main.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include <gtest/gtest.h>
#include <systemc>

// Gives every test its own SystemC simulation context, so one process can
// elaborate and simulate a fresh design per test.
class SystemCTestFixture : public ::testing::Test {
protected:
SystemCTestFixture();

// SystemC cannot destroy a simulation context, so each reset leaks the
// previous one on purpose.
void ResetSystemCContext();

// Derived fixtures must destroy the modules they own before calling this:
// a module has to die while the context that registered it is current.
void TearDown() override;
};
56 changes: 56 additions & 0 deletions tests/sysc/testbench.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#pragma once

#include <cstdint>
#include <scc/memory.h>
#include <scc/router.h>
#include <sysc/core_complex.h>
#include <systemc>
#include <tlm>
#include <vector>

// One rv32 core whose instruction and data buses reach a single RAM through a
// router, since a TLM target socket accepts only one initiator.
struct testbench : sc_core::sc_module {
static constexpr uint64_t ram_size = 1ULL << 20;

sysc::riscv::core_complex<> cpu{"cpu"};
scc::router<> bus{"bus", 1, 2};
scc::memory<ram_size> ram{"ram"};
sc_core::sc_signal<sc_core::sc_time> clk{"clk", sc_core::sc_time(10, sc_core::SC_NS)};
sc_core::sc_signal<bool> rst{"rst", false};

explicit testbench(sc_core::sc_module_name const& nm)
: sc_core::sc_module(nm) {
cpu.ibus(bus.target[0]);
cpu.dbus(bus.target[1]);
bus.bind_target(ram.target, 0, 0, ram_size);
cpu.clk_i(clk);
cpu.rst_i(rst);
}

void load(uint64_t addr, std::vector<uint32_t> const& words) {
for(auto w : words) {
write_word(addr, w);
addr += 4;
}
}

void write_word(uint64_t addr, uint32_t value) { debug_access(tlm::TLM_WRITE_COMMAND, addr, value); }

uint32_t read_word(uint64_t addr) {
uint32_t value = 0;
debug_access(tlm::TLM_READ_COMMAND, addr, value);
return value;
}

private:
void debug_access(tlm::tlm_command cmd, uint64_t addr, uint32_t& value) {
tlm::tlm_generic_payload gp;
gp.set_command(cmd);
gp.set_address(addr);
gp.set_data_ptr(reinterpret_cast<unsigned char*>(&value));
gp.set_data_length(sizeof(value));
gp.set_streaming_width(sizeof(value));
ram.target.get_base_interface().transport_dbg(gp);
}
};
Loading