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