From 4261994a42349bd0db85ae8110dd2ac4f1dac415 Mon Sep 17 00:00:00 2001 From: Shupei Fan Date: Thu, 29 Aug 2024 13:17:34 +0000 Subject: [PATCH 1/7] [difftest] remove online_drive and online_vcs (temporally break verilator) --- difftest/online_drive/Cargo.toml | 14 --- difftest/online_drive/build.rs | 21 ---- difftest/online_drive/src/main.rs | 31 ------ .../verilator_shim/CMakeLists.txt | 38 ------- .../verilator_shim/verilator_shim.cc | 40 -------- difftest/online_vcs/Cargo.toml | 15 --- difftest/online_vcs/default.nix | 48 --------- difftest/online_vcs/src/lib.rs | 2 - difftest/run-emulator.nix | 99 ------------------- 9 files changed, 308 deletions(-) delete mode 100644 difftest/online_drive/Cargo.toml delete mode 100644 difftest/online_drive/build.rs delete mode 100644 difftest/online_drive/src/main.rs delete mode 100644 difftest/online_drive/verilator_shim/CMakeLists.txt delete mode 100644 difftest/online_drive/verilator_shim/verilator_shim.cc delete mode 100644 difftest/online_vcs/Cargo.toml delete mode 100644 difftest/online_vcs/default.nix delete mode 100644 difftest/online_vcs/src/lib.rs delete mode 100644 difftest/run-emulator.nix diff --git a/difftest/online_drive/Cargo.toml b/difftest/online_drive/Cargo.toml deleted file mode 100644 index ad2ef5a5a..000000000 --- a/difftest/online_drive/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "online_drive" -version = "0.1.0" -edition = "2021" - -[dependencies] -online_dpi = { path = "../online_dpi" } -svdpi = { workspace = true, features = ["sv2023"] } - -[build-dependencies] -cmake = "0.1.50" - -[features] -trace = ["online_dpi/trace"] diff --git a/difftest/online_drive/build.rs b/difftest/online_drive/build.rs deleted file mode 100644 index fe883aaa8..000000000 --- a/difftest/online_drive/build.rs +++ /dev/null @@ -1,21 +0,0 @@ -use cmake::Config; - -fn main() { - #[cfg(feature = "trace")] - let dst = - Config::new("verilator_shim").define("VM_TRACE", "1").very_verbose(true).always_configure(true).build(); - #[cfg(not(feature = "trace"))] - let dst = Config::new("verilator_shim").very_verbose(true).always_configure(true).build(); - - println!("cargo::rustc-link-search=native={}/lib", dst.display()); - - // link order matters! so we use +whole-archive here - // verilator_main <- VTestBench <-- verilated <- verilator_shim <- stdc++ - // verilated <- libz - println!("cargo::rustc-link-lib=static:+whole-archive=verilator_shim"); - println!("cargo::rustc-link-lib=static:+whole-archive=VTestBench"); - println!("cargo::rustc-link-lib=static:+whole-archive=verilated"); - println!("cargo::rustc-link-lib=stdc++"); - println!("cargo::rustc-link-lib=z"); - println!("cargo::rerun-if-env-changed=VERILATED_LIB_DIR"); -} diff --git a/difftest/online_drive/src/main.rs b/difftest/online_drive/src/main.rs deleted file mode 100644 index 3a6a2aa13..000000000 --- a/difftest/online_drive/src/main.rs +++ /dev/null @@ -1,31 +0,0 @@ -// force link with online_dpi -extern crate online_dpi; - -use std::{ - ffi::{c_char, c_int, CString}, - ptr, -}; - -fn main() { - let c_args: Vec = std::env::args().map(|arg| CString::new(arg).unwrap()).collect(); - - let mut c_args_ptr: Vec<*const c_char> = c_args.iter().map(|arg| arg.as_ptr()).collect(); - c_args_ptr.push(ptr::null()); - - let argc = c_args.len() as c_int; - let argv = c_args_ptr.as_ptr() as *mut *mut c_char; - - unsafe { - verilator_main_c(argc, argv); - } - - std::fs::write( - "perf.txt", - format!("total_cycles: {}", online_dpi::get_t()), - ) - .expect("fail to write into perf.txt"); -} - -extern "C" { - fn verilator_main_c(argc: c_int, argv: *mut *mut c_char) -> c_int; -} diff --git a/difftest/online_drive/verilator_shim/CMakeLists.txt b/difftest/online_drive/verilator_shim/CMakeLists.txt deleted file mode 100644 index e7aefb74f..000000000 --- a/difftest/online_drive/verilator_shim/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(verilator_shim) -set(CMAKE_CXX_STANDARD 17) - -message(STATUS "Project '${PROJECT_NAME}' build type: ${CMAKE_BUILD_TYPE}") - -set(THREADS_PREFER_PTHREAD_FLAG ON) - -add_library(verilator_shim - STATIC - verilator_shim.cc -) - -if (NOT DEFINED VERILATED_LIB_DIR) - set(VERILATED_LIB_DIR "$ENV{VERILATED_LIB_DIR}") - if (VERILATED_LIB_DIR STREQUAL "") - message(FATAL_ERROR "You should specify verilated libs via -DVERILATE_LIB_DIR or environment variable VERILATED_LIB_DIR, but it seems not") - endif() -endif() - -if (NOT DEFINED VERILATED_INC_DIR) - set(VERILATED_INC_DIR "$ENV{VERILATED_INC_DIR}") - if (VERILATED_INC_DIR STREQUAL "") - message(FATAL_ERROR "You should specify verilated libs via -DVERILATED_INC_DIR or environment variable VERILATED_INC_DIR, but it seems not") - endif() -endif() - -# include verilator headers -find_package(verilator REQUIRED) -message(STATUS "Found verilator: ${verilator_DIR}") -target_include_directories(verilator_shim PUBLIC ${verilator_DIR}/include) -target_include_directories(verilator_shim PUBLIC ${verilator_DIR}/include/vltstd) - -if(DEFINED VM_TRACE) - target_compile_definitions(verilator_shim PRIVATE VM_TRACE=1) -endif() - -install(TARGETS verilator_shim ARCHIVE) diff --git a/difftest/online_drive/verilator_shim/verilator_shim.cc b/difftest/online_drive/verilator_shim/verilator_shim.cc deleted file mode 100644 index 1c0a479d7..000000000 --- a/difftest/online_drive/verilator_shim/verilator_shim.cc +++ /dev/null @@ -1,40 +0,0 @@ -#include -#include - -class VTestBench; - -extern "C" int verilator_main_c(int argc, char **argv) { - // Setup context, defaults, and parse command line - Verilated::debug(0); - VerilatedContext* contextp = new VerilatedContext(); - contextp->fatalOnError(false); - contextp->commandArgs(argc, argv); -#ifdef VM_TRACE - contextp->traceEverOn(true); -#endif - - // Construct the Verilated model, from Vtop.h generated from Verilating - VTestBench* topp = new VTestBench(contextp); - - // Simulate until $finish - while (!contextp->gotFinish()) { - // Evaluate model - topp->eval(); - // Advance time - if (!topp->eventsPending()) - break; - contextp->time(topp->nextTimeSlot()); - } - - if (!contextp->gotFinish()) { - VL_DEBUG_IF(VL_PRINTF("+ Exiting without $finish; no events left\n");); - } - - // Final model cleanup - topp->final(); - - delete topp; - delete contextp; - - return 0; -} diff --git a/difftest/online_vcs/Cargo.toml b/difftest/online_vcs/Cargo.toml deleted file mode 100644 index f0b513916..000000000 --- a/difftest/online_vcs/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "online_vcs" -edition = "2021" -version.workspace = true - -[lib] -crate-type = ["staticlib"] -name = "dpi" - -[dependencies] -online_dpi = { path = "../online_dpi" } -svdpi = { workspace = true, features = ["vpi"] } - -[features] -trace = ["online_dpi/trace"] diff --git a/difftest/online_vcs/default.nix b/difftest/online_vcs/default.nix deleted file mode 100644 index 12b4823a1..000000000 --- a/difftest/online_vcs/default.nix +++ /dev/null @@ -1,48 +0,0 @@ -{ lib -, elaborateConfig -, rustPlatform -, libspike -, libspike_interfaces -, enable-trace ? false -, vcStaticHome -}: - -rustPlatform.buildRustPackage { - name = "vcs-dpi-lib"; - src = with lib.fileset; toSource { - root = ../.; - fileset = unions [ - ../spike_rs - ../offline - ../online_dpi - ../online_drive - ../online_vcs - ../test_common - ../Cargo.lock - ../Cargo.toml - ]; - }; - - buildFeatures = lib.optionals enable-trace [ "trace" ]; - buildAndTestSubdir = "./online_vcs"; - - env = { - VCS_LIB_DIR = "${vcStaticHome}/vcs-mx/linux64/lib"; - SPIKE_LIB_DIR = "${libspike}/lib"; - SPIKE_INTERFACES_LIB_DIR = "${libspike_interfaces}/lib"; - DESIGN_VLEN = elaborateConfig.parameter.vLen; - DESIGN_DLEN = elaborateConfig.parameter.dLen; - SPIKE_ISA_STRING = - "rv32gc_" + - (builtins.concatStringsSep "_" elaborateConfig.parameter.extensions) - + "_Zvl${toString elaborateConfig.parameter.vLen}b"; - }; - - cargoLock = { - lockFile = ../Cargo.lock; - }; - - passthru = { - inherit enable-trace; - }; -} diff --git a/difftest/online_vcs/src/lib.rs b/difftest/online_vcs/src/lib.rs deleted file mode 100644 index be27f2116..000000000 --- a/difftest/online_vcs/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -// force link with online_dpi -extern crate online_dpi; diff --git a/difftest/run-emulator.nix b/difftest/run-emulator.nix deleted file mode 100644 index c9071c96a..000000000 --- a/difftest/run-emulator.nix +++ /dev/null @@ -1,99 +0,0 @@ -{ lib, zstd, jq, stdenvNoCC }: -emulator: -testCase: - -stdenvNoCC.mkDerivation (finalAttr: { - name = "${testCase.pname}-emu-result" + (lib.optionalString emulator.enable-trace "-trace"); - nativeBuildInputs = [ zstd jq ]; - - offlineLogLevel = "ERROR"; - onlineDriveLogLevel = "ERROR"; - passthru.debug = finalAttr.finalPackage.overrideAttrs { offlineLogLevel = "TRACE"; onlineDriveLogLevel = "TRACE"; }; - - buildCommand = '' - mkdir -p "$out" - - emuDriverArgsArray=( - "--elf-file" - "${testCase}/bin/${testCase.pname}.elf" - "--log-file" - "$out/emu.log" - "--log-level" - "$onlineDriveLogLevel" - ${lib.optionalString emulator.enable-trace "--wave-path"} - ${lib.optionalString emulator.enable-trace "$out/wave.fst"} - ) - emuDriverArgs="''${emuDriverArgsArray[@]}" - emuDriver="${emulator}/bin/online_drive" - - rtlEventOutPath="$out/${testCase.pname}-rtl-event.jsonl" - - echo -e "[nix] Running test case ${testCase.pname} with args \033[0;34m$emuDriverArgs\033[0m" - - printError() { - echo -e "\033[0;31m[nix]\033[0m: online driver run failed" - cat $rtlEventOutPath - echo -e "\033[0;31m[nix]\033[0m: Try rerun with '\033[0;34m$emuDriver $emuDriverArgs\033[0m'" - exit 1 - } - - export RUST_BACKTRACE=full - - if [ "$onlineDriveLogLevel" != "ERROR" ]; then - "$emuDriver" $emuDriverArgs 1>$out/online-drive-journal 2> "$rtlEventOutPath" || printError - else - "$emuDriver" $emuDriverArgs 2> "$rtlEventOutPath" || printError - fi - - echo "[nix] online driver run done" - - if [ ! -r "$rtlEventOutPath" ]; then - echo -e "[nix] \033[0;31mInternal Error\033[0m: no $rtlEventOutPath found in output" - exit 1 - fi - - if ! jq --stream -c -e '.[]' "$rtlEventOutPath" >/dev/null 2>&1; then - echo -e "[nix] \033[0;31mInternal Error\033[0m: invalid JSON file $rtlEventOutPath, showing original file:" - echo "--------------------------------------------" - cat $rtlEventOutPath - echo "--------------------------------------------" - exit 1 - fi - - set +e - offlineCheckArgsArray=( - "--elf-file" - "${testCase}/bin/${testCase.pname}.elf" - "--log-file" - "$rtlEventOutPath" - "--log-level" - "$offlineLogLevel" - ) - offlineCheckArgs="''${offlineCheckArgsArray[@]}" - echo -e "[nix] running offline check: \033[0;34m${emulator}/bin/offline $offlineCheckArgs\033[0m" - "${emulator}/bin/offline" $offlineCheckArgs &> $out/offline-check-journal - - printf "$?" > $out/offline-check-status - if [ "$(cat $out/offline-check-status)" != "0" ]; then - echo "[nix] Offline check FAIL" - else - echo "[nix] Offline check PASS" - fi - set -e - - echo "[nix] compressing event log" - zstd $rtlEventOutPath -o $rtlEventOutPath.zstd - rm $rtlEventOutPath - - if [ -r perf.txt ]; then - mv perf.txt $out/ - fi - - ${lib.optionalString emulator.enable-trace '' - if [ ! -r "$out/wave.fst" ]; then - echo -e "[nix] \033[0;31mInternal Error\033[0m: waveform not found in output" - exit 1 - fi - ''} - ''; -}) From 48a5a084d1a6b4f6f7161f1874ee7ac61b08d5cc Mon Sep 17 00:00:00 2001 From: Shupei Fan Date: Thu, 29 Aug 2024 13:19:11 +0000 Subject: [PATCH 2/7] [difftest] move SpikeRunner to spike_rs --- difftest/spike_rs/build.rs | 10 +- difftest/spike_rs/src/lib.rs | 1 + difftest/spike_rs/src/runner.rs | 159 ++++++++++++++++++++++++++++++++ 3 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 difftest/spike_rs/src/runner.rs diff --git a/difftest/spike_rs/build.rs b/difftest/spike_rs/build.rs index 9399fdaf0..a67a4ba1f 100644 --- a/difftest/spike_rs/build.rs +++ b/difftest/spike_rs/build.rs @@ -1,14 +1,20 @@ use std::env; fn main() { - println!("cargo::rustc-link-search=native={}", env::var("SPIKE_LIB_DIR").expect("SPIKE_LIB_DIR should be set")); + println!( + "cargo::rustc-link-search=native={}", + env::var("SPIKE_LIB_DIR").expect("SPIKE_LIB_DIR should be set") + ); println!("cargo::rustc-link-lib=static=riscv"); println!("cargo::rustc-link-lib=static=softfloat"); println!("cargo::rustc-link-lib=static=disasm"); println!("cargo::rustc-link-lib=static=fesvr"); println!("cargo::rustc-link-lib=static=fdt"); - println!("cargo::rustc-link-search=native={}", env::var("SPIKE_INTERFACES_LIB_DIR").expect("SPIKE_INTERFACES_LIB_DIR should be set")); + println!( + "cargo::rustc-link-search=native={}", + env::var("SPIKE_INTERFACES_LIB_DIR").expect("SPIKE_INTERFACES_LIB_DIR should be set") + ); println!("cargo::rustc-link-lib=static=spike_interfaces"); println!("cargo::rerun-if-env-changed=SPIKE_LIB_DIR"); diff --git a/difftest/spike_rs/src/lib.rs b/difftest/spike_rs/src/lib.rs index 5dd021b0b..f352c5c0b 100644 --- a/difftest/spike_rs/src/lib.rs +++ b/difftest/spike_rs/src/lib.rs @@ -1,3 +1,4 @@ +pub mod runner; pub mod spike_event; pub mod util; diff --git a/difftest/spike_rs/src/runner.rs b/difftest/spike_rs/src/runner.rs new file mode 100644 index 000000000..e5e24407a --- /dev/null +++ b/difftest/spike_rs/src/runner.rs @@ -0,0 +1,159 @@ +use std::collections::VecDeque; +use std::path::{Path, PathBuf}; +use tracing::debug; + +use crate::spike_event::SpikeEvent; +use crate::util::load_elf; +use crate::Spike; + +pub struct SpikeRunner { + spike: Box, + + /// commit queue + /// in the spike thread, spike should detech if this queue is full, if not + /// full, execute until a vector instruction, record the behavior of this + /// instruction, and send to commit queue. + /// Note: + /// - The event issued earliest is at the back of the queue + /// - The queue may contain at most one unissued event. If so, the unissued event must be at the + /// front of the queue, and it must be a fence + pub commit_queue: VecDeque, + + /// config for v extension + pub vlen: u32, + pub dlen: u32, + + /// implement the get_t() for mcycle csr update + pub cycle: u64, + + /// for mcycle csr update + pub spike_cycle: u64, + + pub do_log_vrf: bool, +} + +pub struct SpikeArgs { + /// Path to the ELF file + pub elf_file: PathBuf, + + /// Path to the log file + pub log_file: Option, + + /// vlen config + pub vlen: u32, + + /// dlen config + pub dlen: u32, + + /// ISA config + pub set: String, +} + +impl SpikeArgs { + fn to_spike_c_handler(&self) -> Box { + let lvl = "M"; + Spike::new(&self.set, lvl, (self.dlen / 32) as usize, MEM_SIZE) + } +} + +pub const MEM_SIZE: usize = 1usize << 32; + +impl SpikeRunner { + pub fn new(args: &SpikeArgs, do_log_vrf: bool) -> Self { + // load the elf file + // initialize spike + let mut spike = args.to_spike_c_handler(); + + let entry_addr = load_elf(&mut spike, Path::new(&args.elf_file)).unwrap(); + + // initialize processor + let proc = spike.get_proc(); + let state = proc.get_state(); + proc.reset(); + state.set_pc(entry_addr); + + SpikeRunner { + spike, + commit_queue: VecDeque::new(), + vlen: args.vlen, + dlen: args.dlen, + cycle: 0, + spike_cycle: 0, + do_log_vrf, + } + } + + pub fn load_elf(&mut self, fname: &Path) -> anyhow::Result { + load_elf(&mut *self.spike, fname) + } + + // just execute one instruction for non-difftest + pub fn exec(&self) -> anyhow::Result<()> { + let spike = &self.spike; + let proc = spike.get_proc(); + let state = proc.get_state(); + + let new_pc = proc.func(); + + state.handle_pc(new_pc).unwrap(); + + Ok(()) + } + + // execute the spike processor for one instruction and record + // the spike event for difftest + pub fn spike_step(&mut self) -> SpikeEvent { + let spike = &self.spike; + let proc = self.spike.get_proc(); + let state = proc.get_state(); + + let mcycle = (self.cycle + self.spike_cycle) as usize; + state.set_mcycle(0); + + let mut event = SpikeEvent::new(spike, self.do_log_vrf); + state.clear(); + + let new_pc = if event.is_v() || event.is_exit() { + // inst is v / quit + debug!( + "SpikeStep: spike run vector insn ({}), mcycle={mcycle}", + event.describe_insn(), + ); + event.pre_log_arch_changes(spike, self.vlen).unwrap(); + let new_pc_ = proc.func(); + event.log_arch_changes(spike, self.vlen).unwrap(); + new_pc_ + } else { + // inst is scalar + debug!( + "SpikeStep: spike run scalar insn ({}), mcycle={mcycle}", + event.describe_insn(), + ); + let new_pc_ = proc.func(); + event.log_mem_write(spike).unwrap(); + new_pc_ + }; + + state.handle_pc(new_pc).unwrap(); + + self.spike_cycle += 1; + + event + } + + pub fn find_v_se_to_issue(&mut self) -> SpikeEvent { + if !self.commit_queue.is_empty() && self.commit_queue.front().unwrap().is_vfence() { + // if the front (latest) se is a vfence, return the vfence + self.commit_queue.front().unwrap().clone() + } else { + // else, loop until find a se, and push the se to the front + loop { + let se = self.spike_step(); + if se.is_v() { + self.commit_queue.push_front(se.clone()); + break se.clone(); + } + } + } + } +} From d177766909dae8c362de2527ccb15f47fbde0cbc Mon Sep 17 00:00:00 2001 From: Shupei Fan Date: Thu, 29 Aug 2024 13:21:28 +0000 Subject: [PATCH 3/7] [difftest] add dpi_common crate --- difftest/Cargo.toml | 4 +- difftest/dpi_common/Cargo.toml | 14 +++ difftest/dpi_common/src/dpi_target.rs | 42 +++++++++ difftest/dpi_common/src/dump.rs | 130 ++++++++++++++++++++++++++ difftest/dpi_common/src/lib.rs | 19 ++++ difftest/dpi_common/src/plusarg.rs | 29 ++++++ 6 files changed, 235 insertions(+), 3 deletions(-) create mode 100644 difftest/dpi_common/Cargo.toml create mode 100644 difftest/dpi_common/src/dpi_target.rs create mode 100644 difftest/dpi_common/src/dump.rs create mode 100644 difftest/dpi_common/src/lib.rs create mode 100644 difftest/dpi_common/src/plusarg.rs diff --git a/difftest/Cargo.toml b/difftest/Cargo.toml index 35ce5dbf7..ebfe60483 100644 --- a/difftest/Cargo.toml +++ b/difftest/Cargo.toml @@ -4,9 +4,7 @@ members = [ "test_common", "spike_rs", "offline", - "online_dpi", - "online_drive", - "online_vcs", + "dpi_common", ] exclude = [ "spike_interfaces" diff --git a/difftest/dpi_common/Cargo.toml b/difftest/dpi_common/Cargo.toml new file mode 100644 index 000000000..bc3d2081c --- /dev/null +++ b/difftest/dpi_common/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "dpi_common" +edition = "2021" +version.workspace = true + +[dependencies] +svdpi = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } + +[features] +vcs = ["svdpi/vpi"] +verilator = ["svdpi/sv2023"] +trace = [] diff --git a/difftest/dpi_common/src/dpi_target.rs b/difftest/dpi_common/src/dpi_target.rs new file mode 100644 index 000000000..ac8d11c6d --- /dev/null +++ b/difftest/dpi_common/src/dpi_target.rs @@ -0,0 +1,42 @@ +use std::sync::Mutex; + +pub struct DpiTarget { + target: Mutex>, +} + +impl DpiTarget { + pub const fn new() -> Self { + Self { target: Mutex::new(None) } + } + + #[track_caller] + pub fn init(&self, init_fn: impl FnOnce() -> T) { + let mut target = self.target.lock().unwrap(); + if target.is_some() { + panic!("DpiTarget is already initialized"); + } + *target = Some(init_fn()); + } + + #[track_caller] + pub fn with(&self, f: impl FnOnce(&mut T) -> R) -> R { + let mut target = self.target.lock().unwrap(); + let target = target.as_mut().expect("DpiTarget is not initialized"); + f(target) + } + + #[track_caller] + pub fn with_optional(&self, f: impl FnOnce(Option<&mut T>) -> R) -> R { + let mut target = self.target.lock().unwrap(); + f(target.as_mut()) + } + + #[track_caller] + pub fn dispose(&self) { + let mut target = self.target.lock().unwrap(); + if target.is_none() { + panic!("DpiTarget is not initialized"); + } + *target = None; + } +} diff --git a/difftest/dpi_common/src/dump.rs b/difftest/dpi_common/src/dump.rs new file mode 100644 index 000000000..9236dda84 --- /dev/null +++ b/difftest/dpi_common/src/dump.rs @@ -0,0 +1,130 @@ +use svdpi::SvScope; +use tracing::error; + +use crate::plusarg::PlusArgMatcher; + +pub struct DumpEndError; + +#[cfg(feature = "trace")] +pub type DumpControl = RealDumpControl; +#[cfg(not(feature = "trace"))] +pub type DumpControl = EmptyDumpControl; + +#[cfg(feature = "trace")] +mod dpi_export { + use std::ffi::c_char; + extern "C" { + /// `export "DPI-C" function dump_wave(input string file)` + pub fn dump_wave(path: *const c_char); + } +} + +#[cfg(feature = "trace")] +fn dump_wave(scope: svdpi::SvScope, path: &str) { + use std::ffi::CString; + let path_cstring = CString::new(path).unwrap(); + + svdpi::set_scope(scope); + unsafe { + dpi_export::dump_wave(path_cstring.as_ptr()); + } +} + +#[cfg(feature = "trace")] +pub struct RealDumpControl { + svscope: svdpi::SvScope, + wave_path: String, + dump_start: u64, + dump_end: u64, + + started: bool, +} + +#[cfg(feature = "trace")] +impl RealDumpControl { + fn new(svscope: SvScope, wave_path: &str, dump_range: &str) -> Self { + let (dump_start, dump_end) = parse_range(dump_range); + Self { + svscope, + wave_path: wave_path.to_owned(), + dump_start, + dump_end, + + started: false, + } + } + + pub fn from_plusargs(svscope: SvScope, marcher: &PlusArgMatcher) -> Self { + let wave_path = marcher.match_("t1_wave_path"); + let dump_range = marcher.try_match("t1_dump_range").unwrap_or(""); + Self::new(svscope, wave_path, dump_range) + } + + pub fn start(&mut self) { + if !self.started { + dump_wave(self.svscope, &self.wave_path); + self.started = true; + } + } + + pub fn trigger_watchdog(&mut self, tick: u64) -> Result<(), DumpEndError> { + if self.dump_end != 0 && tick > self.dump_end { + return Err(DumpEndError); + } + + if tick >= self.dump_start { + self.start(); + } + + Ok(()) + } +} + +pub struct EmptyDumpControl {} +impl EmptyDumpControl { + pub fn from_plusargs(svscope: SvScope, marcher: &PlusArgMatcher) -> Self { + // do nothing + let _ = svscope; + let _ = marcher; + Self {} + } + pub fn start(&mut self) { + // do nothing + } + pub fn trigger_watchdog(&mut self, tick: u64) -> Result<(), DumpEndError> { + // do nothing + let _ = tick; + Ok(()) + } +} + +fn parse_range(input: &str) -> (u64, u64) { + if input.is_empty() { + return (0, 0); + } + + let parts: Vec<&str> = input.split(",").collect(); + + if parts.len() != 1 && parts.len() != 2 { + error!("invalid dump wave range: `{input}` was given"); + return (0, 0); + } + + const INVALID_NUMBER: &'static str = "invalid number"; + + if parts.len() == 1 { + return (parts[0].parse().expect(INVALID_NUMBER), 0); + } + + if parts[0].is_empty() { + return (0, parts[1].parse().expect(INVALID_NUMBER)); + } + + let start = parts[0].parse().expect(INVALID_NUMBER); + let end = parts[1].parse().expect(INVALID_NUMBER); + if start > end { + panic!("dump start is larger than end: `{input}`"); + } + + (start, end) +} diff --git a/difftest/dpi_common/src/lib.rs b/difftest/dpi_common/src/lib.rs new file mode 100644 index 000000000..279350ec2 --- /dev/null +++ b/difftest/dpi_common/src/lib.rs @@ -0,0 +1,19 @@ +pub mod dpi_target; +pub mod dump; +pub mod plusarg; + +pub use dpi_target::DpiTarget; + +use tracing_subscriber::{EnvFilter, FmtSubscriber}; + +pub fn setup_logger() { + let global_logger = FmtSubscriber::builder() + .with_env_filter(EnvFilter::from_default_env()) // default level: error + .without_time() + .with_target(false) + .with_ansi(true) + .compact() + .finish(); + tracing::subscriber::set_global_default(global_logger) + .expect("internal error: fail to setup log subscriber"); +} diff --git a/difftest/dpi_common/src/plusarg.rs b/difftest/dpi_common/src/plusarg.rs new file mode 100644 index 000000000..d87861049 --- /dev/null +++ b/difftest/dpi_common/src/plusarg.rs @@ -0,0 +1,29 @@ +pub struct PlusArgMatcher { + plusargs: Vec, +} + +impl PlusArgMatcher { + pub fn from_args() -> Self { + let plusargs = std::env::args().filter(|arg| arg.starts_with('+')).collect(); + + Self { plusargs } + } + + pub fn try_match(&self, arg_name: &str) -> Option<&str> { + let prefix = &format!("+{arg_name}="); + + for plusarg in &self.plusargs { + if plusarg.starts_with(prefix) { + return Some(&plusarg[prefix.len()..]); + } + } + None + } + + pub fn match_(&self, arg_name: &str) -> &str { + self.try_match(arg_name).unwrap_or_else(|| { + tracing::error!("required plusarg '+{arg_name}=' not found"); + panic!("failed to march '+{arg_name}='"); + }) + } +} From 27902079f3df45787920967cd7365ecdf634a9d3 Mon Sep 17 00:00:00 2001 From: Shupei Fan Date: Thu, 29 Aug 2024 13:28:21 +0000 Subject: [PATCH 4/7] [difftest] online_dpi -> dpi_t1 Switch to new dpi framework, and use $plusagr for cmd parsing. --- difftest/Cargo.lock | 64 ++++------- difftest/Cargo.toml | 1 + difftest/{online_dpi => dpi_t1}/Cargo.toml | 9 +- difftest/{online_dpi => dpi_t1}/src/dpi.rs | 105 +++++++----------- difftest/{online_dpi => dpi_t1}/src/drive.rs | 110 +++++-------------- difftest/dpi_t1/src/lib.rs | 53 +++++++++ difftest/online_dpi/src/lib.rs | 31 ------ 7 files changed, 151 insertions(+), 222 deletions(-) rename difftest/{online_dpi => dpi_t1}/Cargo.toml (75%) rename difftest/{online_dpi => dpi_t1}/src/dpi.rs (73%) rename difftest/{online_dpi => dpi_t1}/src/drive.rs (78%) create mode 100644 difftest/dpi_t1/src/lib.rs delete mode 100644 difftest/online_dpi/src/lib.rs diff --git a/difftest/Cargo.lock b/difftest/Cargo.lock index 014775917..104ef1a24 100644 --- a/difftest/Cargo.lock +++ b/difftest/Cargo.lock @@ -72,12 +72,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" -[[package]] -name = "cc" -version = "1.0.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac367972e516d45567c7eafc73d24e1c193dcf200a8d94e9db7b3d38b349572d" - [[package]] name = "cfg-if" version = "1.0.0" @@ -124,15 +118,6 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" -[[package]] -name = "cmake" -version = "0.1.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" -dependencies = [ - "cc", -] - [[package]] name = "colorchoice" version = "1.0.1" @@ -150,6 +135,26 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "dpi_common" +version = "0.1.0" +dependencies = [ + "svdpi", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "dpi_t1" +version = "0.1.0" +dependencies = [ + "dpi_common", + "hex", + "spike_rs", + "svdpi", + "tracing", +] + [[package]] name = "heck" version = "0.5.0" @@ -278,35 +283,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "online_dpi" -version = "0.1.0" -dependencies = [ - "clap", - "common", - "hex", - "spike_rs", - "svdpi", - "tracing", -] - -[[package]] -name = "online_drive" -version = "0.1.0" -dependencies = [ - "cmake", - "online_dpi", - "svdpi", -] - -[[package]] -name = "online_vcs" -version = "0.1.0" -dependencies = [ - "online_dpi", - "svdpi", -] - [[package]] name = "overload" version = "0.1.1" diff --git a/difftest/Cargo.toml b/difftest/Cargo.toml index ebfe60483..1d5ec214d 100644 --- a/difftest/Cargo.toml +++ b/difftest/Cargo.toml @@ -4,6 +4,7 @@ members = [ "test_common", "spike_rs", "offline", + "dpi_t1", "dpi_common", ] exclude = [ diff --git a/difftest/online_dpi/Cargo.toml b/difftest/dpi_t1/Cargo.toml similarity index 75% rename from difftest/online_dpi/Cargo.toml rename to difftest/dpi_t1/Cargo.toml index 1d33a18b3..b25a52139 100644 --- a/difftest/online_dpi/Cargo.toml +++ b/difftest/dpi_t1/Cargo.toml @@ -1,17 +1,18 @@ [package] -name = "online_dpi" +name = "dpi_t1" edition = "2021" version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["staticlib"] + [dependencies] -common = { path = "../test_common" } +dpi_common = { path = "../dpi_common" } spike_rs = { path = "../spike_rs" } -clap = { workspace = true } tracing = { workspace = true } svdpi = { workspace = true } hex = "0.4.3" [features] -trace = [] diff --git a/difftest/online_dpi/src/dpi.rs b/difftest/dpi_t1/src/dpi.rs similarity index 73% rename from difftest/online_dpi/src/dpi.rs rename to difftest/dpi_t1/src/dpi.rs index d431e8124..6cf7319a5 100644 --- a/difftest/online_dpi/src/dpi.rs +++ b/difftest/dpi_t1/src/dpi.rs @@ -1,13 +1,14 @@ #![allow(non_snake_case)] #![allow(unused_variables)] -use clap::Parser; +use dpi_common::dump::DumpControl; +use dpi_common::plusarg::PlusArgMatcher; +use dpi_common::DpiTarget; use std::ffi::{c_char, c_longlong}; -use std::sync::Mutex; -use tracing::debug; +use tracing::{debug, error}; use crate::drive::Driver; -use crate::OfflineArgs; +use crate::OnlineArgs; use svdpi::SvScope; pub type SvBitVecVal = u32; @@ -16,7 +17,7 @@ pub type SvBitVecVal = u32; // preparing data structures // -------------------------- -static DPI_TARGET: Mutex>> = Mutex::new(None); +static TARGET: DpiTarget = DpiTarget::new(); pub(crate) struct AxiReadPayload { pub(crate) data: Vec, @@ -125,10 +126,10 @@ unsafe extern "C" fn axi_write_highBandwidthPort( awlen={awlen}, awsize={awsize}, awburst={awburst}, awlock={awlock}, awcache={awcache}, \ awprot={awprot}, awqos={awqos}, awregion={awregion})" ); - let mut driver = DPI_TARGET.lock().unwrap(); - let driver = driver.as_mut().unwrap(); - let (strobe, data) = load_from_payload(payload, driver.dlen); - driver.axi_write_high_bandwidth(awaddr as u32, awsize as u64, &strobe, data); + TARGET.with(|driver| { + let (strobe, data) = load_from_payload(payload, driver.dlen); + driver.axi_write_high_bandwidth(awaddr as u32, awsize as u64, &strobe, data); + }); } /// evaluate at AR fire at corresponding channel_id. @@ -153,10 +154,10 @@ unsafe extern "C" fn axi_read_highBandwidthPort( arlen={arlen}, arsize={arsize}, arburst={arburst}, arlock={arlock}, arcache={arcache}, \ arprot={arprot}, arqos={arqos}, arregion={arregion})" ); - let mut driver = DPI_TARGET.lock().unwrap(); - let driver = driver.as_mut().unwrap(); - let response = driver.axi_read_high_bandwidth(araddr as u32, arsize as u64); - fill_axi_read_payload(payload, driver.dlen, &response); + TARGET.with(|driver| { + let response = driver.axi_read_high_bandwidth(araddr as u32, arsize as u64); + fill_axi_read_payload(payload, driver.dlen, &response); + }); } /// evaluate at AR fire at corresponding channel_id. @@ -181,10 +182,10 @@ unsafe extern "C" fn axi_read_indexedAccessPort( arlen={arlen}, arsize={arsize}, arburst={arburst}, arlock={arlock}, arcache={arcache}, \ arprot={arprot}, arqos={arqos}, arregion={arregion})" ); - let mut driver = DPI_TARGET.lock().unwrap(); - let driver = driver.as_mut().unwrap(); - let response = driver.axi_read_indexed(araddr as u32, arsize as u64); - fill_axi_read_payload(payload, driver.dlen, &response); + TARGET.with(|driver| { + let response = driver.axi_read_indexed(araddr as u32, arsize as u64); + fill_axi_read_payload(payload, driver.dlen, &response); + }); } /// evaluate after AW and W is finished at corresponding channel_id. @@ -209,26 +210,23 @@ unsafe extern "C" fn axi_write_indexedAccessPort( awlen={awlen}, awsize={awsize}, awburst={awburst}, awlock={awlock}, awcache={awcache}, \ awprot={awprot}, awqos={awqos}, awregion={awregion})" ); - let mut driver = DPI_TARGET.lock().unwrap(); - let driver = driver.as_mut().unwrap(); let (strobe, data) = load_from_payload(payload, 32); - driver.axi_write_indexed_access_port(awaddr as u32, awsize as u64, &strobe, data); + TARGET.with(|driver| { + driver.axi_write_indexed_access_port(awaddr as u32, awsize as u64, &strobe, data); + }); } #[no_mangle] unsafe extern "C" fn t1_cosim_init() { - let args = OfflineArgs::parse(); - args.common_args.setup_logger().unwrap(); + let plusargs = PlusArgMatcher::from_args(); + let args = OnlineArgs::from_plusargs(&plusargs); + + dpi_common::setup_logger(); let scope = SvScope::get_current().expect("failed to get scope in t1_cosim_init"); + let dump_control = DumpControl::from_plusargs(scope, &plusargs); - let driver = Box::new(Driver::new(scope, &args)); - let mut dpi_target = DPI_TARGET.lock().unwrap(); - assert!( - dpi_target.is_none(), - "t1_cosim_init should be called only once" - ); - *dpi_target = Some(driver); + TARGET.init(|| Driver::new(scope, dump_control, &args)); } /// evaluate at every 1024 cycles, return reason = 0 to continue simulation, @@ -236,57 +234,38 @@ unsafe extern "C" fn t1_cosim_init() { #[no_mangle] unsafe extern "C" fn cosim_watchdog(reason: *mut c_char) { // watchdog dpi call would be called before initialization, guard on null target - let mut driver = DPI_TARGET.lock().unwrap(); - if let Some(driver) = driver.as_mut() { - *reason = driver.watchdog() as c_char - } + TARGET.with_optional(|driver| { + if let Some(driver) = driver { + *reason = driver.watchdog() as c_char + } + }); } /// evaluate at instruction queue is not empty /// arg issue will be type cast from a struct to svBitVecVal*(uint32_t*) #[no_mangle] unsafe extern "C" fn issue_vector_instruction(issue_dst: *mut SvBitVecVal) { - let mut driver = DPI_TARGET.lock().unwrap(); - let driver = driver.as_mut().unwrap(); - let issue = driver.issue_instruction(); - *(issue_dst as *mut IssueData) = issue; + TARGET.with(|driver| { + let issue = driver.issue_instruction(); + *(issue_dst as *mut IssueData) = issue; + }); } #[no_mangle] unsafe extern "C" fn retire_vector_instruction(retire_src: *const SvBitVecVal) { - let mut driver = DPI_TARGET.lock().unwrap(); - let driver = driver.as_mut().unwrap(); let retire = &*(retire_src as *const Retire); - driver.retire_instruction(retire) + TARGET.with(|driver| { + driver.retire_instruction(retire); + }); } #[no_mangle] unsafe extern "C" fn retire_vector_mem(dummy: *const SvBitVecVal) { - let mut driver = DPI_TARGET.lock().unwrap(); - let driver = driver.as_mut().unwrap(); - driver.retire_memory(); + TARGET.with(|driver| { + driver.retire_memory(); + }); } //-------------------------------- // import functions and wrappers //-------------------------------- - -#[cfg(feature = "trace")] -mod dpi_export { - use std::ffi::c_char; - extern "C" { - /// `export "DPI-C" function dump_wave(input string file)` - pub fn dump_wave(path: *const c_char); - } -} - -#[cfg(feature = "trace")] -pub(crate) fn dump_wave(scope: svdpi::SvScope, path: &str) { - use std::ffi::CString; - let path_cstring = CString::new(path).unwrap(); - - svdpi::set_scope(scope); - unsafe { - dpi_export::dump_wave(path_cstring.as_ptr()); - } -} diff --git a/difftest/online_dpi/src/drive.rs b/difftest/dpi_t1/src/drive.rs similarity index 78% rename from difftest/online_dpi/src/drive.rs rename to difftest/dpi_t1/src/drive.rs index 145918114..be1102774 100644 --- a/difftest/online_dpi/src/drive.rs +++ b/difftest/dpi_t1/src/drive.rs @@ -1,5 +1,6 @@ -use common::spike_runner::SpikeRunner; -use common::MEM_SIZE; +use dpi_common::dump::{DumpControl, DumpEndError}; +use spike_rs::runner::SpikeRunner; +use spike_rs::runner::{SpikeArgs, MEM_SIZE}; use spike_rs::spike_event::MemAccessRecord; use spike_rs::spike_event::SpikeEvent; use spike_rs::util::load_elf_to_buffer; @@ -7,7 +8,7 @@ use tracing::{debug, error, info, trace}; use crate::dpi::*; use crate::get_t; -use crate::OfflineArgs; +use crate::OnlineArgs; use svdpi::SvScope; struct ShadowMem { @@ -99,17 +100,9 @@ pub(crate) struct Driver { spike_runner: SpikeRunner, // SvScope from t1_cosim_init - #[cfg(feature = "trace")] scope: SvScope, - #[cfg(feature = "trace")] - wave_path: String, - #[cfg(feature = "trace")] - dump_start: u64, - #[cfg(feature = "trace")] - dump_end: u64, - #[cfg(feature = "trace")] - dump_started: bool, + dump_control: DumpControl, pub(crate) dlen: u32, @@ -123,58 +116,24 @@ pub(crate) struct Driver { shadow_mem: ShadowMem, } -#[cfg(feature = "trace")] -fn parse_range(input: &str) -> (u64, u64) { - if input.is_empty() { - return (0, 0); - } - - let parts: Vec<&str> = input.split(",").collect(); - - if parts.len() != 1 && parts.len() != 2 { - error!("invalid dump wave range: `{input}` was given"); - return (0, 0); - } - - const INVALID_NUMBER: &'static str = "invalid number"; - - if parts.len() == 1 { - return (parts[0].parse().expect(INVALID_NUMBER), 0); - } - - if parts[0].is_empty() { - return (0, parts[1].parse().expect(INVALID_NUMBER)); - } - - let start = parts[0].parse().expect(INVALID_NUMBER); - let end = parts[1].parse().expect(INVALID_NUMBER); - if start > end { - panic!("dump start is larger than end: `{input}`"); - } - - (start, end) -} - impl Driver { - pub(crate) fn new(scope: SvScope, args: &OfflineArgs) -> Self { - #[cfg(feature = "trace")] - let (dump_start, dump_end) = parse_range(&args.dump_range); - + pub(crate) fn new(scope: SvScope, dump_control: DumpControl, args: &OnlineArgs) -> Self { let mut self_ = Self { - spike_runner: SpikeRunner::new(&args.common_args, false), + spike_runner: SpikeRunner::new( + &SpikeArgs { + elf_file: args.elf_file.clone(), + log_file: args.log_file.clone(), + vlen: args.vlen, + dlen: args.dlen, + set: args.set.clone(), + }, + false, + ), - #[cfg(feature = "trace")] scope, - #[cfg(feature = "trace")] - wave_path: args.wave_path.to_owned(), - #[cfg(feature = "trace")] - dump_start, - #[cfg(feature = "trace")] - dump_end, - #[cfg(feature = "trace")] - dump_started: false, - - dlen: args.common_args.dlen, + dump_control, + + dlen: args.dlen, timeout: args.timeout, last_commit_cycle: 0, @@ -182,9 +141,9 @@ impl Driver { vector_lsu_count: 0, shadow_mem: ShadowMem::new(), }; - self_.spike_runner.load_elf(&args.common_args.elf_file).unwrap(); + self_.spike_runner.load_elf(&args.elf_file).unwrap(); - load_elf_to_buffer(&mut self_.shadow_mem.mem, &args.common_args.elf_file).unwrap(); + load_elf_to_buffer(&mut self_.shadow_mem.mem, &args.elf_file).unwrap(); self_ } @@ -254,19 +213,15 @@ impl Driver { ); WATCHDOG_TIMEOUT } else { - #[cfg(feature = "trace")] - if self.dump_end != 0 && tick > self.dump_end { - info!( - "[{tick}] run to dump end, exiting (last_commit_cycle={})", - self.last_commit_cycle - ); - return WATCHDOG_TIMEOUT; - } - - #[cfg(feature = "trace")] - if !self.dump_started && tick >= self.dump_start { - self.start_dump_wave(); - self.dump_started = true; + match self.dump_control.trigger_watchdog(tick) { + Ok(()) => {} + Err(DumpEndError) => { + info!( + "[{tick}] run to dump end, exiting (last_commit_cycle={})", + self.last_commit_cycle + ); + return WATCHDOG_TIMEOUT; + } } trace!("[{}] watchdog continue", get_t()); @@ -274,11 +229,6 @@ impl Driver { } } - #[cfg(feature = "trace")] - fn start_dump_wave(&mut self) { - dump_wave(self.scope, &self.wave_path); - } - pub(crate) fn step(&mut self) -> SpikeEvent { // there will be a vfence / scalar load / scalar store in the commit queue's front if let Some(se) = self.spike_runner.commit_queue.front() { diff --git a/difftest/dpi_t1/src/lib.rs b/difftest/dpi_t1/src/lib.rs new file mode 100644 index 000000000..86483f515 --- /dev/null +++ b/difftest/dpi_t1/src/lib.rs @@ -0,0 +1,53 @@ +use std::path::PathBuf; + +use dpi_common::plusarg::PlusArgMatcher; + +pub mod dpi; +pub mod drive; + +pub(crate) struct OnlineArgs { + /// Path to the ELF file + pub elf_file: PathBuf, + + /// Path to the log file + pub log_file: Option, + + /// vlen config + pub vlen: u32, + + /// dlen config + pub dlen: u32, + + /// ISA config + pub set: String, + + // default to TIMEOUT_DEFAULT + pub timeout: u64, +} + +const TIMEOUT_DEFAULT: u64 = 1000000; + +impl OnlineArgs { + pub fn from_plusargs(marcher: &PlusArgMatcher) -> Self { + Self { + elf_file: marcher.match_("t1_elf_file").into(), + log_file: marcher.try_match("t1_log_file").map(|x| x.into()), + + vlen: env!("DESIGN_VLEN").parse().unwrap(), + dlen: env!("DESIGN_DLEN").parse().unwrap(), + set: env!("SPIKE_ISA_STRING").parse().unwrap(), + timeout: marcher + .try_match("t1_timeout") + .map(|x| x.parse().unwrap()) + .unwrap_or(TIMEOUT_DEFAULT), + } + } +} + +// keep in sync with TestBench.ClockGen +pub const CYCLE_PERIOD: u64 = 20; + +/// get cycle +pub fn get_t() -> u64 { + svdpi::get_time() / CYCLE_PERIOD +} diff --git a/difftest/online_dpi/src/lib.rs b/difftest/online_dpi/src/lib.rs deleted file mode 100644 index ff10cc8e8..000000000 --- a/difftest/online_dpi/src/lib.rs +++ /dev/null @@ -1,31 +0,0 @@ -use clap::Parser; -use common::CommonArgs; - -pub mod dpi; -pub mod drive; - -#[derive(Parser)] -pub(crate) struct OfflineArgs { - #[command(flatten)] - pub common_args: CommonArgs, - - #[cfg(feature = "trace")] - #[arg(long)] - pub wave_path: String, - - #[cfg(feature = "trace")] - #[arg(long, default_value = "")] - pub dump_range: String, - - #[arg(long, default_value_t = 1000000)] - pub timeout: u64, -} - -// keep in sync with TestBench.ClockGen -pub const CYCLE_PERIOD: u64 = 20; - -/// get cycle -pub fn get_t() -> u64 { - svdpi::get_time() / CYCLE_PERIOD -} - From 7a09534f93b03353b640bc7821f4eefa351e85b1 Mon Sep 17 00:00:00 2001 From: Shupei Fan Date: Thu, 29 Aug 2024 13:28:48 +0000 Subject: [PATCH 5/7] [nix] fix t1 vcs build --- difftest/offline.nix | 40 ++++++++++++++++++++++++++++++ difftest/vcs.nix | 47 ++++++++++++++++++++++++++++++++++++ ipemu/src/TestBench.scala | 2 ++ nix/t1/default.nix | 4 ++- nix/t1/run-vcs-emulation.nix | 11 +++------ nix/t1/vcs.nix | 4 +-- 6 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 difftest/offline.nix diff --git a/difftest/offline.nix b/difftest/offline.nix new file mode 100644 index 000000000..6cb220d28 --- /dev/null +++ b/difftest/offline.nix @@ -0,0 +1,40 @@ +{ lib +, elaborateConfig +, rustPlatform +, libspike +, libspike_interfaces +}: + +rustPlatform.buildRustPackage { + name = "offline"; + src = with lib.fileset; toSource { + root = ./.; + fileset = unions [ + ./spike_rs + ./offline + ./dpi_common + ./dpi_t1 + ./test_common + ./Cargo.lock + ./Cargo.toml + ]; + }; + + buildFeatures = []; + buildAndTestSubdir = "./offline"; + + env = { + SPIKE_LIB_DIR = "${libspike}/lib"; + SPIKE_INTERFACES_LIB_DIR = "${libspike_interfaces}/lib"; + DESIGN_VLEN = elaborateConfig.parameter.vLen; + DESIGN_DLEN = elaborateConfig.parameter.dLen; + SPIKE_ISA_STRING = + "rv32gc_" + + (builtins.concatStringsSep "_" elaborateConfig.parameter.extensions) + + "_Zvl${toString elaborateConfig.parameter.vLen}b"; + }; + + cargoLock = { + lockFile = ./Cargo.lock; + }; +} diff --git a/difftest/vcs.nix b/difftest/vcs.nix index e69de29bb..2b2f55910 100644 --- a/difftest/vcs.nix +++ b/difftest/vcs.nix @@ -0,0 +1,47 @@ +{ lib +, elaborateConfig +, rustPlatform +, libspike +, libspike_interfaces +, enable-trace ? false +, vcStaticHome +}: + +rustPlatform.buildRustPackage { + name = "vcs-dpi-lib"; + src = with lib.fileset; toSource { + root = ./.; + fileset = unions [ + ./spike_rs + ./offline + ./dpi_common + ./dpi_t1 + ./test_common + ./Cargo.lock + ./Cargo.toml + ]; + }; + + buildFeatures = ["dpi_common/vcs"] ++ lib.optionals enable-trace [ "dpi_common/trace" ]; + buildAndTestSubdir = "./dpi_t1"; + + env = { + VCS_LIB_DIR = "${vcStaticHome}/vcs-mx/linux64/lib"; + SPIKE_LIB_DIR = "${libspike}/lib"; + SPIKE_INTERFACES_LIB_DIR = "${libspike_interfaces}/lib"; + DESIGN_VLEN = elaborateConfig.parameter.vLen; + DESIGN_DLEN = elaborateConfig.parameter.dLen; + SPIKE_ISA_STRING = + "rv32gc_" + + (builtins.concatStringsSep "_" elaborateConfig.parameter.extensions) + + "_Zvl${toString elaborateConfig.parameter.vLen}b"; + }; + + cargoLock = { + lockFile = ./Cargo.lock; + }; + + passthru = { + inherit enable-trace; + }; +} diff --git a/ipemu/src/TestBench.scala b/ipemu/src/TestBench.scala index 897931d49..59d3b9ff9 100644 --- a/ipemu/src/TestBench.scala +++ b/ipemu/src/TestBench.scala @@ -40,6 +40,7 @@ class TestBench(generator: SerializableModuleGenerator[T1, T1Parameter]) setInline( s"$desiredName.sv", s"""module $desiredName(output reg clock, output reg reset); + |`ifdef T1_ENABLE_TRACE | export "DPI-C" function dump_wave; | function dump_wave(input string file); |`ifdef VCS @@ -52,6 +53,7 @@ class TestBench(generator: SerializableModuleGenerator[T1, T1Parameter]) | $$dumpvars(0); |`endif | endfunction; + |`endif | | import "DPI-C" context function void t1_cosim_init(); | initial begin diff --git a/nix/t1/default.nix b/nix/t1/default.nix index b6bdead5c..87158d38c 100644 --- a/nix/t1/default.nix +++ b/nix/t1/default.nix @@ -126,11 +126,13 @@ lib.makeScope newScope "--strip-debug-info" ]; }; - vcs-dpi-lib = ipSelf.callPackage ../../difftest/online_vcs { }; + vcs-dpi-lib = ipSelf.callPackage ../../difftest/vcs.nix { }; vcs-dpi-lib-trace = ipSelf.vcs-dpi-lib.override { enable-trace = true; }; # FIXME: vcs-emu should have offline check instead of using verilator one vcs-emu = ipSelf.callPackage ./vcs.nix { }; vcs-emu-trace = ipSelf.callPackage ./vcs.nix { vcs-dpi-lib = ipSelf.vcs-dpi-lib-trace; }; + + offline = ipSelf.callPackage ../../difftest/offline.nix { }; }); subsystem = rec { diff --git a/nix/t1/run-vcs-emulation.nix b/nix/t1/run-vcs-emulation.nix index 1575979e8..8a2a340e6 100644 --- a/nix/t1/run-vcs-emulation.nix +++ b/nix/t1/run-vcs-emulation.nix @@ -1,5 +1,4 @@ -# FIXME: we should have offline check for VCS, importing offline check from verilator-emu is weird -{ lib, stdenvNoCC, zstd, jq, verilator-emu }: +{ lib, stdenvNoCC, zstd, jq, offline }: emulator: testCase: @@ -18,10 +17,8 @@ stdenvNoCC.mkDerivation (finalAttr: { mkdir -p "$out" emuDriverArgsArray=( - "--elf-file" - "${testCase}/bin/${testCase.pname}.elf" - ${lib.optionalString emulator.enable-trace "--wave-path"} - ${lib.optionalString emulator.enable-trace "${testCase.pname}.fsdb"} + "+t1_elf_file=${testCase}/bin/${testCase.pname}.elf" + ${lib.optionalString emulator.enable-trace "+t1_wave_path=${testCase.pname}.fsdb"} ) emuDriverArgs="''${emuDriverArgsArray[@]}" emuDriver="${emulator}/bin/t1-vcs-simulator" @@ -71,7 +68,7 @@ stdenvNoCC.mkDerivation (finalAttr: { ) offlineCheckArgs="''${offlineCheckArgsArray[@]}" echo -e "[nix] running offline check: \033[0;34m${emulator}/bin/offline $offlineCheckArgs\033[0m" - "${verilator-emu}/bin/offline" $offlineCheckArgs &> $out/offline-check-journal + "${offline}/bin/offline" $offlineCheckArgs &> $out/offline-check-journal printf "$?" > $out/offline-check-status if [ "$(cat $out/offline-check-status)" != "0" ]; then diff --git a/nix/t1/vcs.nix b/nix/t1/vcs.nix index 11c18f29d..13a692092 100644 --- a/nix/t1/vcs.nix +++ b/nix/t1/vcs.nix @@ -29,12 +29,12 @@ let -sverilog \ -full64 \ -timescale=1ns/1ps \ - -P $VERDI_HOME/share/PLI/VCS/LINUX64/novas.tab $VERDI_HOME/share/PLI/VCS/LINUX64/pli.a \ ${lib.optionalString vcs-dpi-lib.enable-trace '' + +define+T1_ENABLE_TRACE \ -debug_access+pp+dmptf+thread \ -kdb=common_elab,hgldd_all''} \ -file filelist.f \ - ${vcs-dpi-lib}/lib/libdpi.a \ + ${vcs-dpi-lib}/lib/libdpi_t1.a \ -o t1-vcs-simulator runHook postBuild From 7ef69ced67d5257503505f90591fb9ae1b797a18 Mon Sep 17 00:00:00 2001 From: Shupei Fan Date: Thu, 29 Aug 2024 15:02:16 +0000 Subject: [PATCH 6/7] [difftest & t1rocket] migrate t1rocket dpi lib (now at difftest/dpi_t1rocket) --- difftest/Cargo.lock | 19 ++ difftest/Cargo.toml | 1 + difftest/dpi_t1rocket/Cargo.toml | 20 ++ difftest/dpi_t1rocket/src/dpi.rs | 319 ++++++++++++++++++++++++++ difftest/dpi_t1rocket/src/drive.rs | 352 +++++++++++++++++++++++++++++ difftest/dpi_t1rocket/src/lib.rs | 44 ++++ difftest/offline.nix | 1 + difftest/vcs.nix | 1 + 8 files changed, 757 insertions(+) create mode 100644 difftest/dpi_t1rocket/Cargo.toml create mode 100644 difftest/dpi_t1rocket/src/dpi.rs create mode 100644 difftest/dpi_t1rocket/src/drive.rs create mode 100644 difftest/dpi_t1rocket/src/lib.rs diff --git a/difftest/Cargo.lock b/difftest/Cargo.lock index 104ef1a24..a6e2dc63c 100644 --- a/difftest/Cargo.lock +++ b/difftest/Cargo.lock @@ -155,6 +155,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "dpi_t1rocket" +version = "0.1.0" +dependencies = [ + "anyhow", + "dpi_common", + "elf", + "hex", + "spike_rs", + "svdpi", + "tracing", +] + +[[package]] +name = "elf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" + [[package]] name = "heck" version = "0.5.0" diff --git a/difftest/Cargo.toml b/difftest/Cargo.toml index 1d5ec214d..d5f4d98f2 100644 --- a/difftest/Cargo.toml +++ b/difftest/Cargo.toml @@ -5,6 +5,7 @@ members = [ "spike_rs", "offline", "dpi_t1", + "dpi_t1rocket", "dpi_common", ] exclude = [ diff --git a/difftest/dpi_t1rocket/Cargo.toml b/difftest/dpi_t1rocket/Cargo.toml new file mode 100644 index 000000000..45253a8d3 --- /dev/null +++ b/difftest/dpi_t1rocket/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "dpi_t1rocket" +edition = "2021" +version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["staticlib"] + +[dependencies] +dpi_common = { path = "../dpi_common" } +spike_rs = { path = "../spike_rs" } +tracing = { workspace = true } +svdpi = { workspace = true } +anyhow = { workspace = true } +hex = "0.4.3" +elf = "0.7.4" + +[features] diff --git a/difftest/dpi_t1rocket/src/dpi.rs b/difftest/dpi_t1rocket/src/dpi.rs new file mode 100644 index 000000000..9f4db2699 --- /dev/null +++ b/difftest/dpi_t1rocket/src/dpi.rs @@ -0,0 +1,319 @@ +#![allow(non_snake_case)] +#![allow(unused_variables)] + +use dpi_common::dump::DumpControl; +use dpi_common::plusarg::PlusArgMatcher; +use dpi_common::DpiTarget; +use svdpi::SvScope; +use std::ffi::{c_char, c_longlong}; +use tracing::debug; + +use crate::drive::Driver; +use crate::OnlineArgs; + +pub type SvBitVecVal = u32; + +// -------------------------- +// preparing data structures +// -------------------------- + +static TARGET: DpiTarget = DpiTarget::new(); + +pub(crate) struct AxiReadPayload { + pub(crate) data: Vec, +} + +unsafe fn write_to_pointer(dst: *mut u8, data: &[u8]) { + let dst = std::slice::from_raw_parts_mut(dst, data.len()); + dst.copy_from_slice(data); +} + +unsafe fn fill_axi_read_payload(dst: *mut SvBitVecVal, dlen: u32, payload: &AxiReadPayload) { + let data_len = 256 * (dlen / 8) as usize; + assert!(payload.data.len() <= data_len); + write_to_pointer(dst as *mut u8, &payload.data); +} + +// Return (strobe in bit, data in byte) +unsafe fn load_from_payload( + payload: &*const SvBitVecVal, + data_width: usize, + size: usize, +) -> (Vec, &[u8]) { + let src = *payload as *mut u8; + let data_width_in_byte = std::cmp::max(size, 4); + let strb_width_per_byte = if data_width < 64 { 4 } else { 8 }; + let strb_width_in_byte = size.div_ceil(strb_width_per_byte); + + let payload_size_in_byte = strb_width_in_byte + data_width_in_byte; // data width in byte + let byte_vec = std::slice::from_raw_parts(src, payload_size_in_byte); + let strobe = &byte_vec[0..strb_width_in_byte]; + let data = &byte_vec[strb_width_in_byte..]; + + let masks: Vec = strobe + .into_iter() + .flat_map(|strb| { + let mask: Vec = (0..strb_width_per_byte).map(|i| (strb & (1 << i)) != 0).collect(); + mask + }) + .collect(); + assert_eq!( + masks.len(), data.len(), + "strobe bit width is not aligned with data byte width" + ); + + debug!( + "load {payload_size_in_byte} byte from payload: raw_data={} strb={} data={}", + hex::encode(byte_vec), + hex::encode(strobe), + hex::encode(data), + ); + + (masks, data) +} + +//---------------------- +// dpi functions +//---------------------- + +/// evaluate after AW and W is finished at corresponding channel_id. +#[no_mangle] +unsafe extern "C" fn axi_write_highBandwidthAXI( + channel_id: c_longlong, + awid: c_longlong, + awaddr: c_longlong, + awlen: c_longlong, + awsize: c_longlong, + awburst: c_longlong, + awlock: c_longlong, + awcache: c_longlong, + awprot: c_longlong, + awqos: c_longlong, + awregion: c_longlong, + // struct packed {bit [255:0][DLEN:0] data; + // bit [255:0][DLEN/8:0] strb; } payload + payload: *const SvBitVecVal, +) { + debug!( + "axi_write_highBandwidth (channel_id={channel_id}, awid={awid}, awaddr={awaddr:#x}, \ + awlen={awlen}, awsize={awsize}, awburst={awburst}, awlock={awlock}, awcache={awcache}, \ + awprot={awprot}, awqos={awqos}, awregion={awregion})" + ); + TARGET.with(|driver| { + let (strobe, data) = load_from_payload(&payload, driver.dlen as usize, (1 << awsize) as usize); + driver.axi_write_high_bandwidth(awaddr as u32, awsize as u64, &strobe, data); + }); +} + +/// evaluate at AR fire at corresponding channel_id. +#[no_mangle] +unsafe extern "C" fn axi_read_highBandwidthAXI( + channel_id: c_longlong, + arid: c_longlong, + araddr: c_longlong, + arlen: c_longlong, + arsize: c_longlong, + arburst: c_longlong, + arlock: c_longlong, + arcache: c_longlong, + arprot: c_longlong, + arqos: c_longlong, + arregion: c_longlong, + // struct packed {bit [255:0][DLEN:0] data; byte beats; } payload + payload: *mut SvBitVecVal, +) { + debug!( + "axi_read_highBandwidth (channel_id={channel_id}, arid={arid}, araddr={araddr:#x}, \ + arlen={arlen}, arsize={arsize}, arburst={arburst}, arlock={arlock}, arcache={arcache}, \ + arprot={arprot}, arqos={arqos}, arregion={arregion})" + ); + TARGET.with(|driver| { + let response = driver.axi_read_high_bandwidth(araddr as u32, arsize as u64); + fill_axi_read_payload(payload, driver.dlen, &response); + }); +} + +/// evaluate after AW and W is finished at corresponding channel_id. +#[no_mangle] +unsafe extern "C" fn axi_write_highOutstandingAXI( + channel_id: c_longlong, + awid: c_longlong, + awaddr: c_longlong, + awlen: c_longlong, + awsize: c_longlong, + awburst: c_longlong, + awlock: c_longlong, + awcache: c_longlong, + awprot: c_longlong, + awqos: c_longlong, + awregion: c_longlong, + // struct packed {bit [255:0][31:0] data; bit [255:0][3:0] strb; } payload + payload: *const SvBitVecVal, +) { + debug!( + "axi_write_high_outstanding (channel_id={channel_id}, awid={awid}, awaddr={awaddr:#x}, \ + awlen={awlen}, awsize={awsize}, awburst={awburst}, awlock={awlock}, awcache={awcache}, \ + awprot={awprot}, awqos={awqos}, awregion={awregion})" + ); + TARGET.with(|driver| { + let (strobe, data) = load_from_payload(&payload, 32, (1 << awsize) as usize); + driver.axi_write_high_outstanding(awaddr as u32, awsize as u64, &strobe, data); + }); +} + +/// evaluate at AR fire at corresponding channel_id. +#[no_mangle] +unsafe extern "C" fn axi_read_highOutstandingAXI( + channel_id: c_longlong, + arid: c_longlong, + araddr: c_longlong, + arlen: c_longlong, + arsize: c_longlong, + arburst: c_longlong, + arlock: c_longlong, + arcache: c_longlong, + arprot: c_longlong, + arqos: c_longlong, + arregion: c_longlong, + // struct packed {bit [255:0][DLEN:0] data; byte beats; } payload + payload: *mut SvBitVecVal, +) { + debug!( + "axi_read_high_outstanding (channel_id={channel_id}, arid={arid}, araddr={araddr:#x}, \ + arlen={arlen}, arsize={arsize}, arburst={arburst}, arlock={arlock}, arcache={arcache}, \ + arprot={arprot}, arqos={arqos}, arregion={arregion})" + ); + TARGET.with(|driver| { + let response = driver.axi_read_high_outstanding(araddr as u32, arsize as u64); + fill_axi_read_payload(payload, driver.dlen, &response); + }); +} + +#[no_mangle] +unsafe extern "C" fn axi_write_loadStoreAXI( + channel_id: c_longlong, + awid: c_longlong, + awaddr: c_longlong, + awlen: c_longlong, + awsize: c_longlong, + awburst: c_longlong, + awlock: c_longlong, + awcache: c_longlong, + awprot: c_longlong, + awqos: c_longlong, + awregion: c_longlong, + payload: *const SvBitVecVal, +) { + debug!( + "axi_write_loadStore (channel_id={channel_id}, awid={awid}, awaddr={awaddr:#x}, \ + awlen={awlen}, awsize={awsize}, awburst={awburst}, awlock={awlock}, awcache={awcache}, \ + awprot={awprot}, awqos={awqos}, awregion={awregion})" + ); + TARGET.with(|driver| { + let data_width = if awsize <= 2 { 32 } else { 8 * (1 << awsize) } as usize; + let (strobe, data) = load_from_payload(&payload, data_width, (driver.dlen / 8) as usize); + driver.axi_write_load_store(awaddr as u32, awsize as u64, &strobe, data); + }); +} + +#[no_mangle] +unsafe extern "C" fn axi_read_loadStoreAXI( + channel_id: c_longlong, + arid: c_longlong, + araddr: c_longlong, + arlen: c_longlong, + arsize: c_longlong, + arburst: c_longlong, + arlock: c_longlong, + arcache: c_longlong, + arprot: c_longlong, + arqos: c_longlong, + arregion: c_longlong, + payload: *mut SvBitVecVal, +) { + debug!( + "axi_read_loadStoreAXI (channel_id={channel_id}, arid={arid}, araddr={araddr:#x}, \ + arlen={arlen}, arsize={arsize}, arburst={arburst}, arlock={arlock}, arcache={arcache}, \ + arprot={arprot}, arqos={arqos}, arregion={arregion})" + ); + TARGET.with(|driver| { + let response = driver.axi_read_load_store(araddr as u32, arsize as u64); + fill_axi_read_payload(payload, driver.dlen, &response); + }); +} + +#[no_mangle] +unsafe extern "C" fn axi_read_instructionFetchAXI( + channel_id: c_longlong, + arid: c_longlong, + araddr: c_longlong, + arlen: c_longlong, + arsize: c_longlong, + arburst: c_longlong, + arlock: c_longlong, + arcache: c_longlong, + arprot: c_longlong, + arqos: c_longlong, + arregion: c_longlong, + payload: *mut SvBitVecVal, +) { + debug!( + "axi_read_instructionFetchAXI (channel_id={channel_id}, arid={arid}, araddr={araddr:#x}, \ + arlen={arlen}, arsize={arsize}, arburst={arburst}, arlock={arlock}, arcache={arcache}, \ + arprot={arprot}, arqos={arqos}, arregion={arregion})" + ); + TARGET.with(|driver| { + let response = driver.axi_read_instruction_fetch(araddr as u32, arsize as u64); + fill_axi_read_payload(payload, driver.dlen, &response); + }); +} + +#[no_mangle] +unsafe extern "C" fn t1rocket_cosim_init() { + let plusargs = PlusArgMatcher::from_args(); + let args = OnlineArgs::from_plusargs(&plusargs); + + dpi_common::setup_logger(); + + let scope = SvScope::get_current().expect("failed to get scope in t1rocket_cosim_init"); + let dump_control = DumpControl::from_plusargs(scope, &plusargs); + + TARGET.init(|| Driver::new(scope, dump_control, &args)); +} + +/// evaluate at every 1024 cycles, return reason = 0 to continue simulation, +/// other value is used as error code. +#[no_mangle] +unsafe extern "C" fn cosim_watchdog(reason: *mut c_char) { + // watchdog dpi call would be called before initialization, guard on null target + TARGET.with_optional(|driver| { + if let Some(driver) = driver { + *reason = driver.watchdog() as c_char; + } + }); +} + +/// evaluate at every cycle, return quit_flag = false to continue simulation, +#[no_mangle] +unsafe extern "C" fn cosim_quit(quit_flag: *mut bool) { + // watchdog dpi call would be called before initialization, guard on null target + TARGET.with_optional(|driver| { + if let Some(driver) = driver { + *quit_flag = driver.quit as bool; + } + }); +} + +#[no_mangle] +unsafe extern "C" fn get_resetvector(resetvector: *mut c_longlong) { + TARGET.with_optional(|driver| { + if let Some(driver) = driver { + *resetvector = driver.e_entry as c_longlong; + } + }); +} + +//-------------------------------- +// import functions and wrappers +//-------------------------------- + diff --git a/difftest/dpi_t1rocket/src/drive.rs b/difftest/dpi_t1rocket/src/drive.rs new file mode 100644 index 000000000..8f8cbca3b --- /dev/null +++ b/difftest/dpi_t1rocket/src/drive.rs @@ -0,0 +1,352 @@ +use crate::dpi::*; +use svdpi::SvScope; +use crate::OnlineArgs; +use crate::{get_t, EXIT_CODE, EXIT_POS}; + +use anyhow::Context; +use dpi_common::dump::{DumpControl, DumpEndError}; +use elf::{ + abi::{EM_RISCV, ET_EXEC, PT_LOAD, STT_FUNC}, + endian::LittleEndian, + ElfStream, +}; +use std::collections::HashMap; +use std::os::unix::fs::FileExt; +use std::{fs, path::Path}; +use tracing::{debug, error, info, trace}; + +struct ShadowMem { + mem: Vec, +} + +const MEM_SIZE: usize = 1 << 32; + +impl ShadowMem { + pub fn new() -> Self { + Self { mem: vec![0; MEM_SIZE] } + } + + pub fn read_mem(&self, addr: u32, size: u32) -> &[u8] { + let start = addr as usize; + let end = (addr + size) as usize; + &self.mem[start..end] + } + + // size: 1 << arsize + // bus_size: AXI bus width in bytes + // return: Vec with len=bus_size + // if size < bus_size, the result is padded due to AXI narrow transfer rules + pub fn read_mem_axi(&self, addr: u32, size: u32, bus_size: u32) -> Vec { + assert!( + addr % size == 0 && bus_size % size == 0, + "unaligned access addr={addr:#x} size={size}B dlen={bus_size}B" + ); + + let data = self.read_mem(addr, size); + if size < bus_size { + // narrow + let mut data_padded = vec![0; bus_size as usize]; + let start = (addr % bus_size) as usize; + let end = start + data.len(); + data_padded[start..end].copy_from_slice(data); + + data_padded + } else { + // normal + data.to_vec() + } + } + + // size: 1 << awsize + // bus_size: AXI bus width in bytes + // masks: write strokes, len=bus_size + // data: write data, len=bus_size + pub fn write_mem_axi( + &mut self, + addr: u32, + size: u32, + bus_size: u32, + masks: &[bool], + data: &[u8], + ) { + assert!( + addr % size == 0 && bus_size % size == 0, + "unaligned write access addr={addr:#x} size={size}B dlen={bus_size}B" + ); + + // handle strb=0 AXI payload + if !masks.iter().any(|&x| x) { + trace!("Mask 0 write detect"); + return; + } + + // TODO: we do not check strobe is compatible with (addr, awsize) + let addr_align = addr & ((!bus_size) + 1); + + let bus_size = bus_size as usize; + // should not check this, in scalar narrow write, bus_size is not equal to data.len() + // assert_eq!(bus_size, masks.len()); + // assert_eq!(bus_size, data.len()); + + for i in 0..bus_size { + if masks[i] { + self.mem[addr_align as usize + i] = data[i]; + } + } + } +} + +#[derive(Debug)] +#[allow(dead_code)] +pub struct FunctionSym { + #[allow(dead_code)] + pub(crate) name: String, + #[allow(dead_code)] + pub(crate) info: u8, +} +pub type FunctionSymTab = HashMap; + +pub(crate) struct Driver { + // SvScope from t1rocket_cosim_init + scope: SvScope, + + dump_control: DumpControl, + + pub(crate) dlen: u32, + pub(crate) e_entry: u64, + + timeout: u64, + last_commit_cycle: u64, + + shadow_mem: ShadowMem, + + pub(crate) quit: bool, +} + +impl Driver { + pub(crate) fn new(scope: SvScope, dump_control: DumpControl, args: &OnlineArgs) -> Self { + // pass e_entry to rocket + let (e_entry, shadow_mem, _fn_sym_tab) = + Self::load_elf(&args.elf_file).expect("fail creating simulator"); + + Self { + scope, + dump_control, + + dlen: args.dlen, + e_entry, + + timeout: args.timeout, + last_commit_cycle: 0, + + shadow_mem, + + quit: false + } + } + + pub fn load_elf(path: &Path) -> anyhow::Result<(u64, ShadowMem, FunctionSymTab)> { + let file = fs::File::open(path).with_context(|| "reading ELF file")?; + let mut elf: ElfStream = + ElfStream::open_stream(&file).with_context(|| "parsing ELF file")?; + + if elf.ehdr.e_machine != EM_RISCV { + anyhow::bail!("ELF is not in RISC-V"); + } + + if elf.ehdr.e_type != ET_EXEC { + anyhow::bail!("ELF is not an executable"); + } + + if elf.ehdr.e_phnum == 0 { + anyhow::bail!("ELF has zero size program header"); + } + + debug!("ELF entry: 0x{:x}", elf.ehdr.e_entry); + let mut mem = ShadowMem::new(); + elf.segments().iter().filter(|phdr| phdr.p_type == PT_LOAD).for_each(|phdr| { + let vaddr: usize = phdr.p_vaddr.try_into().expect("fail converting vaddr(u64) to usize"); + let filesz: usize = phdr.p_filesz.try_into().expect("fail converting p_filesz(u64) to usize"); + debug!( + "Read loadable segments 0x{:x}..0x{:x} to memory 0x{:x}", + phdr.p_offset, + phdr.p_offset + filesz as u64, + vaddr + ); + + // Load file start from offset into given mem slice + // The `offset` of the read_at method is relative to the start of the file and thus independent from the current cursor. + let mem_slice = &mut mem.mem[vaddr..vaddr + filesz]; + file.read_at(mem_slice, phdr.p_offset).unwrap_or_else(|err| { + panic!( + "fail reading ELF into mem with vaddr={}, filesz={}, offset={}. Error detail: {}", + vaddr, filesz, phdr.p_offset, err + ) + }); + }); + + // FIXME: now the symbol table doesn't contain any function value + let mut fn_sym_tab = FunctionSymTab::new(); + let symbol_table = + elf.symbol_table().with_context(|| "reading symbol table(SHT_SYMTAB) from ELF")?; + if let Some((parsed_table, string_table)) = symbol_table { + parsed_table + .iter() + // st_symtype = symbol.st_info & 0xf (But why masking here?) + .filter(|sym| sym.st_symtype() == STT_FUNC) + .for_each(|sym| { + let name = string_table + .get(sym.st_name as usize) + .unwrap_or_else(|_| panic!("fail to get name at st_name={}", sym.st_name)); + fn_sym_tab.insert( + sym.st_value, + FunctionSym { name: name.to_string(), info: sym.st_symtype() }, + ); + }); + } else { + debug!("load_elf: symtab not found"); + }; + + Ok((elf.ehdr.e_entry, mem, fn_sym_tab)) + } + + pub(crate) fn axi_read_high_bandwidth(&mut self, addr: u32, arsize: u64) -> AxiReadPayload { + let size = 1 << arsize; + let data = self.shadow_mem.read_mem_axi(addr, size, self.dlen / 8); + let data_hex = hex::encode(&data); + self.last_commit_cycle = get_t(); + trace!( + "[{}] axi_read_high_bandwidth (addr={addr:#x}, size={size}, data={data_hex})", + get_t() + ); + AxiReadPayload { data } + } + + pub(crate) fn axi_write_high_bandwidth( + &mut self, + addr: u32, + awsize: u64, + strobe: &[bool], + data: &[u8], + ) { + let size = 1 << awsize; + self.shadow_mem.write_mem_axi(addr, size, self.dlen / 8, &strobe, data); + let data_hex = hex::encode(data); + self.last_commit_cycle = get_t(); + trace!( + "[{}] axi_write_high_bandwidth (addr={addr:#x}, size={size}, data={data_hex})", + get_t() + ); + } + + pub(crate) fn axi_read_high_outstanding(&mut self, addr: u32, arsize: u64) -> AxiReadPayload { + let size = 1 << arsize; + assert!(size <= 4); + let data = self.shadow_mem.read_mem_axi(addr, size, 4); + let data_hex = hex::encode(&data); + self.last_commit_cycle = get_t(); + trace!( + "[{}] axi_read_high_outstanding (addr={addr:#x}, size={size}, data={data_hex})", + get_t() + ); + AxiReadPayload { data } + } + + pub(crate) fn axi_write_high_outstanding( + &mut self, + addr: u32, + awsize: u64, + strobe: &[bool], + data: &[u8], + ) { + let size = 1 << awsize; + self.shadow_mem.write_mem_axi(addr, size, 4, strobe, data); + let data_hex = hex::encode(data); + self.last_commit_cycle = get_t(); + trace!( + "[{}] axi_write_high_outstanding (addr={addr:#x}, size={size}, data={data_hex})", + get_t() + ); + } + + pub(crate) fn axi_read_load_store(&mut self, addr: u32, arsize: u64) -> AxiReadPayload { + let size = 1 << arsize; + let bus_size = if size == 32 { 32 } else { 4 }; + let data = self.shadow_mem.read_mem_axi(addr, size, bus_size); + let data_hex = hex::encode(&data); + self.last_commit_cycle = get_t(); + trace!( + "[{}] axi_read_load_store (addr={addr:#x}, size={size}, data={data_hex})", + get_t() + ); + AxiReadPayload { data } + } + + pub(crate) fn axi_write_load_store( + &mut self, + addr: u32, + awsize: u64, + strobe: &[bool], + data: &[u8], + ) { + let size = 1 << awsize; + let bus_size = if size == 32 { 32 } else { 4 }; + self.shadow_mem.write_mem_axi(addr, size, bus_size, strobe, data); + let data_hex = hex::encode(data); + self.last_commit_cycle = get_t(); + + trace!( + "[{}] axi_write_load_store (addr={addr:#x}, size={size}, data={data_hex})", + get_t() + ); + + // check exit with code + if addr == EXIT_POS { + let exit_data_slice = data[..4].try_into().expect("slice with incorrect length"); + if u32::from_le_bytes(exit_data_slice) == EXIT_CODE { + info!("driver is ready to quit"); + self.quit = true; + } + } + } + + pub(crate) fn axi_read_instruction_fetch(&mut self, addr: u32, arsize: u64) -> AxiReadPayload { + let size = 1 << arsize; + let data = self.shadow_mem.read_mem_axi(addr, size, 32); + let data_hex = hex::encode(&data); + trace!( + "[{}] axi_read_instruction_fetch (addr={addr:#x}, size={size}, data={data_hex})", + get_t() + ); + AxiReadPayload { data } + } + + pub(crate) fn watchdog(&mut self) -> u8 { + const WATCHDOG_CONTINUE: u8 = 0; + const WATCHDOG_TIMEOUT: u8 = 1; + + let tick = get_t(); + if tick - self.last_commit_cycle > self.timeout { + error!( + "[{}] watchdog timeout (last_commit_cycle={})", + get_t(), + self.last_commit_cycle + ); + WATCHDOG_TIMEOUT + } else { + match self.dump_control.trigger_watchdog(tick) { + Ok(()) => {}, + Err(DumpEndError) => { + info!( + "[{tick}] run to dump end, exiting (last_commit_cycle={})", + self.last_commit_cycle + ); + return WATCHDOG_TIMEOUT; + } + } + + trace!("[{}] watchdog continue", get_t()); + WATCHDOG_CONTINUE + } + } +} diff --git a/difftest/dpi_t1rocket/src/lib.rs b/difftest/dpi_t1rocket/src/lib.rs new file mode 100644 index 000000000..2ff131f0e --- /dev/null +++ b/difftest/dpi_t1rocket/src/lib.rs @@ -0,0 +1,44 @@ +use std::path::PathBuf; + +use dpi_common::plusarg::PlusArgMatcher; + +pub mod dpi; +pub mod drive; + +pub(crate) struct OnlineArgs { + /// Path to the ELF file + pub elf_file: PathBuf, + + /// dlen config + pub dlen: u32, + + // default to TIMEOUT_DEFAULT + pub timeout: u64, +} + +const TIMEOUT_DEFAULT: u64 = 1000000; + +impl OnlineArgs { + pub fn from_plusargs(marcher: &PlusArgMatcher) -> Self { + Self { + elf_file: marcher.match_("t1_elf_file").into(), + dlen: env!("DESIGN_DLEN").parse().unwrap(), + timeout: marcher + .try_match("t1_timeout") + .map(|x| x.parse().unwrap()) + .unwrap_or(TIMEOUT_DEFAULT), + } + } +} + +// quit signal +const EXIT_POS: u32 = 0x4000_0000; +const EXIT_CODE: u32 = 0xdead_beef; + +// keep in sync with TestBench.ClockGen +pub const CYCLE_PERIOD: u64 = 20; + +/// get cycle +pub fn get_t() -> u64 { + svdpi::get_time() / CYCLE_PERIOD +} diff --git a/difftest/offline.nix b/difftest/offline.nix index 6cb220d28..d08dc40c6 100644 --- a/difftest/offline.nix +++ b/difftest/offline.nix @@ -14,6 +14,7 @@ rustPlatform.buildRustPackage { ./offline ./dpi_common ./dpi_t1 + ./dpi_t1rocket ./test_common ./Cargo.lock ./Cargo.toml diff --git a/difftest/vcs.nix b/difftest/vcs.nix index 2b2f55910..4321ee9be 100644 --- a/difftest/vcs.nix +++ b/difftest/vcs.nix @@ -16,6 +16,7 @@ rustPlatform.buildRustPackage { ./offline ./dpi_common ./dpi_t1 + ./dpi_t1rocket ./test_common ./Cargo.lock ./Cargo.toml From a91f196d5fd6c36412af516b0d17cf3142c79da9 Mon Sep 17 00:00:00 2001 From: Shupei Fan Date: Thu, 29 Aug 2024 15:03:57 +0000 Subject: [PATCH 7/7] [nix] switch to use dpi_t1rocket --- difftest/vcs_t1rocket.nix | 43 +++++++++++++++++++++++++++ t1rocketemu/default.nix | 5 +++- t1rocketemu/nix/run-vcs-emulation.nix | 6 ++-- t1rocketemu/nix/vcs.nix | 2 +- 4 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 difftest/vcs_t1rocket.nix diff --git a/difftest/vcs_t1rocket.nix b/difftest/vcs_t1rocket.nix new file mode 100644 index 000000000..2a5f721b3 --- /dev/null +++ b/difftest/vcs_t1rocket.nix @@ -0,0 +1,43 @@ +{ lib +, libspike +, libspike_interfaces +, rustPlatform +, rtlDesignMetadata +, enable-trace ? false +}: + +rustPlatform.buildRustPackage { + name = "vcs-dpi-lib"; + src = with lib.fileset; toSource { + root = ./.; + fileset = unions [ + ./spike_rs + ./offline + ./dpi_common + ./dpi_t1 + ./dpi_t1rocket + ./test_common + ./Cargo.lock + ./Cargo.toml + ]; + }; + + buildFeatures = ["dpi_common/vcs"] ++ lib.optionals enable-trace [ "dpi_common/trace" ]; + buildAndTestSubdir = "./dpi_t1rocket"; + + env = { + SPIKE_LIB_DIR = "${libspike}/lib"; + SPIKE_INTERFACES_LIB_DIR = "${libspike_interfaces}/lib"; + DESIGN_VLEN = rtlDesignMetadata.vlen; + DESIGN_DLEN = rtlDesignMetadata.dlen; + SPIKE_ISA_STRING = rtlDesignMetadata.march; + }; + + cargoLock = { + lockFile = ./Cargo.lock; + }; + + passthru = { + inherit enable-trace; + }; +} diff --git a/t1rocketemu/default.nix b/t1rocketemu/default.nix index e6d8014fb..4d2b1f5c0 100644 --- a/t1rocketemu/default.nix +++ b/t1rocketemu/default.nix @@ -14,7 +14,10 @@ verilator-emu-trace = scope.callPackage ./nix/verilator.nix { verilated-c-lib = scope.verilated-c-lib-trace; }; vcs-rust-package = scope.callPackage ./online_vcs { }; - inherit (scope.vcs-rust-package) vcs-dpi-lib vcs-dpi-lib-trace vcs-offline-checker; + inherit (scope.vcs-rust-package) vcs-offline-checker; + + vcs-dpi-lib = scope.callPackage ../difftest/vcs_t1rocket.nix { }; + vcs-dpi-lib-trace = scope.vcs-dpi-lib.override { enable-trace = true; }; vcs-emu = scope.callPackage ./nix/vcs.nix { }; vcs-emu-trace = scope.callPackage ./nix/vcs.nix { vcs-dpi-lib = scope.vcs-dpi-lib-trace; }; diff --git a/t1rocketemu/nix/run-vcs-emulation.nix b/t1rocketemu/nix/run-vcs-emulation.nix index 902a2c5b0..59a2478cf 100644 --- a/t1rocketemu/nix/run-vcs-emulation.nix +++ b/t1rocketemu/nix/run-vcs-emulation.nix @@ -17,10 +17,8 @@ stdenvNoCC.mkDerivation (finalAttr: { mkdir -p "$out" emuDriverArgsArray=( - "--elf-file" - "${testCase}/bin/${testCase.pname}.elf" - ${lib.optionalString emulator.enable-trace "--wave-path"} - ${lib.optionalString emulator.enable-trace "${testCase.pname}.fsdb"} + "+t1_elf_file=${testCase}/bin/${testCase.pname}.elf" + ${lib.optionalString emulator.enable-trace "+t1_wave_path=${testCase.pname}.fsdb"} ) emuDriverArgs="''${emuDriverArgsArray[@]}" emuDriver="${emulator}/bin/t1-vcs-simulator" diff --git a/t1rocketemu/nix/vcs.nix b/t1rocketemu/nix/vcs.nix index 48758bd4f..5051bbeaf 100644 --- a/t1rocketemu/nix/vcs.nix +++ b/t1rocketemu/nix/vcs.nix @@ -33,7 +33,7 @@ let -debug_access+pp+dmptf+thread \ -kdb=common_elab,hgldd_all''} \ -file filelist.f \ - ${vcs-dpi-lib}/lib/libdpi.a \ + ${vcs-dpi-lib}/lib/libdpi_t1rocket.a \ -o t1-vcs-simulator runHook postBuild