From 067f71b3ab89271e005c804fd9d08a4267e2b615 Mon Sep 17 00:00:00 2001 From: Clo91eaf Date: Mon, 10 Jun 2024 15:59:38 +0800 Subject: [PATCH] [difftest] add t1 simulator [ipemu] implement chisel printf Signed-off-by: Avimitin [nix] fix incorrect devShell Signed-off-by: Avimitin [tests] remove mmio store on exit Signed-off-by: Avimitin [nix] add rtl-event.log derivation Signed-off-by: Avimitin [script] add new difftest main target Signed-off-by: Avimitin --- .../libspike_interfaces/spike_interfaces.cc | 18 +- .../libspike_interfaces/spike_interfaces_c.h | 1 + difftest/t1-simulator/default.nix | 30 +- difftest/t1-simulator/readme.md | 11 + difftest/t1-simulator/src/difftest.rs | 203 ++++++ difftest/t1-simulator/src/difftest/dut.rs | 127 ++++ difftest/t1-simulator/src/difftest/spike.rs | 656 ++++++++++++++++++ .../spike/libspike_interfaces.rs | 11 +- .../src/difftest/spike/spike_event.rs | 402 +++++++++++ difftest/t1-simulator/src/main.rs | 96 ++- difftest/t1-simulator/src/spike.rs | 148 ---- flake.nix | 2 +- ipemu/src/TestBench.scala | 41 +- nix/overlay.nix | 4 +- nix/t1/default.nix | 2 +- script/default.nix | 2 +- script/src/Main.scala | 141 ++-- tests/builder.nix | 89 ++- tests/default.nix | 3 + tests/t1_main.S | 3 - 20 files changed, 1697 insertions(+), 293 deletions(-) create mode 100644 difftest/t1-simulator/readme.md create mode 100644 difftest/t1-simulator/src/difftest.rs create mode 100644 difftest/t1-simulator/src/difftest/dut.rs create mode 100644 difftest/t1-simulator/src/difftest/spike.rs rename difftest/t1-simulator/src/{ => difftest}/spike/libspike_interfaces.rs (96%) create mode 100644 difftest/t1-simulator/src/difftest/spike/spike_event.rs delete mode 100644 difftest/t1-simulator/src/spike.rs diff --git a/difftest/libspike_interfaces/spike_interfaces.cc b/difftest/libspike_interfaces/spike_interfaces.cc index 0b2031e4a2..5e3229b3f9 100644 --- a/difftest/libspike_interfaces/spike_interfaces.cc +++ b/difftest/libspike_interfaces/spike_interfaces.cc @@ -172,10 +172,6 @@ void state_set_pc(spike_state_t* state, uint64_t pc) { state->s->pc = pc; } -uint32_t state_get_reg_write_size(spike_state_t* state) { - return state->s->log_reg_write.size(); -} - uint32_t state_get_reg(spike_state_t* state, uint32_t index, bool is_fp) { if (is_fp) { auto &fr = state->s->FPR; @@ -185,6 +181,20 @@ uint32_t state_get_reg(spike_state_t* state, uint32_t index, bool is_fp) { return (uint32_t)xr[index]; } +uint32_t state_get_reg_write_size(spike_state_t* state) { + return state->s->log_reg_write.size(); +} + +uint32_t state_get_reg_write_index(spike_state_t* state) { + int vec_idx = 0; + int i = 0; + for (auto [idx, data] : state->s->log_reg_write) { + vec_idx |= (idx & 0xf) << (i * 4); + i++; + } + return vec_idx; +} + uint32_t state_get_mem_write_size(spike_state_t* state) { return state->s->log_mem_write.size(); } diff --git a/difftest/libspike_interfaces/spike_interfaces_c.h b/difftest/libspike_interfaces/spike_interfaces_c.h index b1f93d2f7c..98d215e7a2 100644 --- a/difftest/libspike_interfaces/spike_interfaces_c.h +++ b/difftest/libspike_interfaces/spike_interfaces_c.h @@ -40,6 +40,7 @@ uint64_t state_handle_pc(spike_state_t* state, uint64_t new_pc); void state_set_pc(spike_state_t* state, uint64_t pc); uint32_t state_get_reg(spike_state_t* state, uint32_t index, bool is_fp); uint32_t state_get_reg_write_size(spike_state_t* state); +uint32_t state_get_reg_write_index(spike_state_t* state); uint32_t state_get_mem_write_size(spike_state_t* state); uint32_t state_get_mem_write_addr(spike_state_t* state, uint32_t index); uint64_t state_get_mem_write_value(spike_state_t* state, uint32_t index); diff --git a/difftest/t1-simulator/default.nix b/difftest/t1-simulator/default.nix index 643c2e51e8..9138d31181 100644 --- a/difftest/t1-simulator/default.nix +++ b/difftest/t1-simulator/default.nix @@ -1,18 +1,26 @@ { lib , libspike , rustPlatform +, rust-analyzer , libspike_interfaces -, rtl }: -rustPlatform.buildRustPackage { - name = "t1-simulator"; - src = with lib.fileset; toSource { - root = ./.; - fileset = fileFilter (file: file.name != "default.nix") ./.; +let + self = rustPlatform.buildRustPackage { + name = "t1-simulator"; + src = with lib.fileset; toSource { + root = ./.; + fileset = fileFilter (file: file.name != "default.nix") ./.; + }; + passthru.devShell = self.overrideAttrs (old: { + nativeBuildInputs = old.nativeBuildInputs ++ [ + rust-analyzer + ]; + }); + buildInputs = [ libspike libspike_interfaces ]; + cargoLock = { + lockFile = ./Cargo.lock; + }; }; - buildInputs = [ libspike libspike_interfaces ]; - cargoLock = { - lockFile = ./Cargo.lock; - }; -} +in +self diff --git a/difftest/t1-simulator/readme.md b/difftest/t1-simulator/readme.md new file mode 100644 index 0000000000..f82e152a01 --- /dev/null +++ b/difftest/t1-simulator/readme.md @@ -0,0 +1,11 @@ +## Build + +```bash +nix build ".#t1-simulator" +``` + +## Develop + +```bash +nix develop '.#t1-simulator.devShell' +``` diff --git a/difftest/t1-simulator/src/difftest.rs b/difftest/t1-simulator/src/difftest.rs new file mode 100644 index 0000000000..dabf9b082c --- /dev/null +++ b/difftest/t1-simulator/src/difftest.rs @@ -0,0 +1,203 @@ +mod dut; +mod spike; + +use dut::*; +pub use spike::SpikeHandle; +use std::path::Path; +use tracing::{error, trace}; + +pub struct Difftest { + spike: SpikeHandle, + dut: Dut, +} + +impl Difftest { + pub fn new( + size: usize, + elf_file: String, + log_file: String, + vlen: u32, + dlen: u32, + set: String, + ) -> Self { + Self { + spike: SpikeHandle::new(size, Path::new(&elf_file), vlen, dlen, set), + dut: Dut::new(Path::new(&log_file)), + } + } + + fn peek_issue(&mut self, issue: IssueEvent) -> anyhow::Result<()> { + self.spike.peek_issue(issue).unwrap(); + + Ok(()) + } + + fn update_lsu_idx(&mut self, lsu_enq: LsuEnqEvent) -> anyhow::Result<()> { + self.spike.update_lsu_idx(lsu_enq).unwrap(); + + Ok(()) + } + + fn poke_inst(&mut self) -> anyhow::Result<()> { + loop { + let se = self.spike.find_se_to_issue(); + if (se.is_vfence_insn || se.is_exit_insn) && self.spike.to_rtl_queue.len() == 1 { + if se.is_exit_insn { + error!("Simulation quit graceful"); + return Err(anyhow::anyhow!("graceful exit")); + } + + self.spike.to_rtl_queue.pop_back(); + } else { + break; + } + } + + // TODO: remove these, now just for aligning online difftest + if let Some(se) = self.spike.to_rtl_queue.front() { + // it is ensured there are some other instruction not committed, thus + // se_to_issue should not be issued + if se.is_vfence_insn || se.is_exit_insn { + assert!( + self.spike.to_rtl_queue.len() > 1, + "to_rtl_queue are smaller than expected" + ); + if se.is_exit_insn { + trace!("DPIPokeInst: exit waiting for fence"); + } else { + trace!("DPIPokeInst: waiting for fence, no issuing new instruction"); + } + } else { + trace!( + "DPIPokeInst: poke instruction: pc={:#x}, inst={}", + se.pc, + se.disasm + ); + } + } + Ok(()) + } + + pub fn diff(&mut self) -> anyhow::Result<()> { + self.poke_inst().unwrap(); + + let event = self.dut.step()?; + + match &*event.event { + "peekTL" => { + let idx = event.parameter.idx.unwrap(); + // assert!(idx < self.spike.config.dlen / 32); + let opcode = event.parameter.opcode.unwrap(); + let param = event.parameter.param.unwrap(); + let source = event.parameter.source.unwrap(); + let mask = event.parameter.mask.unwrap(); + let data = event.parameter.data.unwrap(); + let corrupt = event.parameter.corrupt.unwrap(); + let dready = event.parameter.dready.unwrap() != 0; + let cycle = event.parameter.cycle.unwrap(); + // check align + let addr = event.parameter.address.unwrap(); + let size = (1 << event.parameter.size.unwrap()) as usize; + assert_eq!( + addr as usize % size, + 0, + "[{cycle}] unaligned access (addr={addr:08X}, size={size}" + ); + + let opcode = Opcode::from_u32(opcode); + self + .spike + .peek_tl(&PeekTLEvent { + idx, + opcode, + param, + size, + source, + addr, + mask, + data, + corrupt, + dready, + cycle, + }) + .unwrap(); + } + "issue" => { + let idx = event.parameter.idx.unwrap(); + let cycle = event.parameter.cycle.unwrap(); + self.peek_issue(IssueEvent { idx, cycle }).unwrap(); + } + "lsuEnq" => { + let enq = event.parameter.enq.unwrap(); + let cycle = event.parameter.cycle.unwrap(); + self.update_lsu_idx(LsuEnqEvent { enq, cycle }).unwrap(); + } + "vrfWriteFromLsu" => { + let idx = event.parameter.idx.unwrap(); + let vd = event.parameter.vd.unwrap(); + let offset = event.parameter.offset.unwrap(); + let mask = event.parameter.mask.unwrap(); + let data = event.parameter.data.unwrap(); + let instruction = event.parameter.instruction.unwrap(); + let lane = event.parameter.lane.unwrap(); + let cycle = event.parameter.cycle.unwrap(); + assert!(idx < self.spike.config.dlen / 32); + + self + .spike + .peek_vrf_write_from_lsu(VrfWriteEvent { + idx: lane.trailing_zeros(), + vd, + offset, + mask, + data, + instruction, + cycle, + }) + .unwrap(); + } + "vrfWriteFromLane" => { + let idx = event.parameter.idx.unwrap(); + let vd = event.parameter.vd.unwrap(); + let offset = event.parameter.offset.unwrap(); + let mask = event.parameter.mask.unwrap(); + let data = event.parameter.data.unwrap(); + let instruction = event.parameter.instruction.unwrap(); + let cycle = event.parameter.cycle.unwrap(); + assert!(idx < self.spike.config.dlen / 32); + self + .spike + .peek_vrf_write_from_lane(VrfWriteEvent { + idx, + vd, + offset, + mask, + data, + instruction, + cycle, + }) + .unwrap(); + } + "inst" => { + let data = event.parameter.data.unwrap() as u32; + let cycle = event.parameter.cycle.unwrap(); + // let vxsat = event.parameter.vxsat.unwrap(); + // let rd_valid = event.parameter.rd_valid.unwrap(); + // let rd = event.parameter.rd.unwrap(); + // let mem = event.parameter.mem.unwrap(); + + let se = self.spike.to_rtl_queue.back().unwrap(); + se.record_rd_write(data).unwrap(); + se.check_is_ready_for_commit(cycle).unwrap(); + + self.spike.to_rtl_queue.pop_back(); + } + "skip" => {} + _ => { + panic!("unknown event: {}", event.event) + } + } + + Ok(()) + } +} diff --git a/difftest/t1-simulator/src/difftest/dut.rs b/difftest/t1-simulator/src/difftest/dut.rs new file mode 100644 index 0000000000..bd80384f03 --- /dev/null +++ b/difftest/t1-simulator/src/difftest/dut.rs @@ -0,0 +1,127 @@ +use serde::Deserialize; +use std::io::BufRead; +use std::path::Path; + +#[derive(Deserialize, Debug, PartialEq, Clone)] +pub enum Opcode { + PutFullData = 0, + PutPartialData = 1, + Get = 4, + // AccessAckData = 0, + // AccessAck = 0, +} + +impl Opcode { + pub fn from_u32(n: u32) -> Self { + match n { + 0 => Opcode::PutFullData, + 1 => Opcode::PutPartialData, + 4 => Opcode::Get, + _ => panic!("unknown opcode"), + } + } +} + +#[derive(Deserialize, Debug)] +pub struct Parameter { + pub idx: Option, + pub enq: Option, + pub opcode: Option, + pub param: Option, + pub size: Option, + pub source: Option, + pub address: Option, + pub mask: Option, + pub data: Option, + pub corrupt: Option, + pub dready: Option, + pub vd: Option, + pub offset: Option, + pub instruction: Option, + pub lane: Option, + pub vxsat: Option, + pub rd_valid: Option, + pub rd: Option, + pub mem: Option, + pub cycle: Option, +} + +#[derive(Deserialize, Debug)] +pub struct JsonEvents { + pub event: String, + pub parameter: Parameter, +} + +pub struct IssueEvent { + pub idx: u32, + pub cycle: usize, +} + +pub struct LsuEnqEvent { + pub enq: u32, + pub cycle: usize, +} + +#[derive(Debug)] +pub struct PeekTLEvent { + pub idx: u32, + pub opcode: Opcode, + pub param: u32, + pub size: usize, + pub source: u16, + pub addr: u32, + pub mask: u32, + pub data: u64, + pub corrupt: u32, + pub dready: bool, + pub cycle: usize, +} + +pub struct VrfWriteEvent { + pub idx: u32, + pub vd: u32, + pub offset: u32, + pub mask: u32, + pub data: u64, + pub instruction: u32, + pub cycle: usize, +} + +#[derive(Debug)] +pub struct Dut { + events: Vec, + idx: u32, +} + +impl Dut { + fn read_json(path: &Path) -> anyhow::Result> { + let file = std::fs::File::open(path).unwrap(); + let reader = std::io::BufReader::new(file); + + let mut events = Vec::new(); + + for line in reader.lines() { + let line = line.expect("line read error"); + let event: JsonEvents = serde_json::from_str(&line)?; + events.push(event); + } + + Ok(events) + } + + pub fn new(path: &Path) -> Self { + let events = Self::read_json(path).unwrap(); + let idx = 0; + Self { events, idx } + } + + pub fn step(&mut self) -> anyhow::Result<&JsonEvents> { + let event = match self.events.get(self.idx as usize) { + Some(event) => event, + None => return Err(anyhow::anyhow!("no more events")), + }; + self.idx += 1; + + Ok(event) + } +} diff --git a/difftest/t1-simulator/src/difftest/spike.rs b/difftest/t1-simulator/src/difftest/spike.rs new file mode 100644 index 0000000000..20fb2cab36 --- /dev/null +++ b/difftest/t1-simulator/src/difftest/spike.rs @@ -0,0 +1,656 @@ +use lazy_static::lazy_static; +use std::collections::{HashMap, VecDeque}; +use std::fs::File; +use std::io::Read; +use std::path::Path; +use std::sync::Mutex; +use tracing::{info, trace, warn}; +use xmas_elf::{ + header, + program::{ProgramHeader, Type}, + ElfFile, +}; + +mod libspike_interfaces; +use libspike_interfaces::*; + +mod spike_event; +use spike_event::*; + +use super::dut::*; + +const LSU_IDX_DEFAULT: u8 = 0xff; +const DATAPATH_WIDTH_IN_BYTES: usize = 8; // 8 = config.datapath_width_in_bytes = beatbyte = lsuBankParameters(0).beatbyte for blastoise + +// read the addr from spike memory +// caller should make sure the address is valid +#[no_mangle] +pub extern "C" fn rs_addr_to_mem(addr: u64) -> *mut u8 { + let addr = addr as usize; + let mut spike_mem = SPIKE_MEM.lock().unwrap(); + let spike_mut = spike_mem.as_mut().unwrap(); + &mut spike_mut.mem[addr] as *mut u8 +} + +pub struct SpikeMem { + pub mem: Vec, + pub size: usize, +} + +lazy_static! { + static ref SPIKE_MEM: Mutex>> = Mutex::new(None); +} + +fn init_memory(size: usize) { + let mut spike_mem = SPIKE_MEM.lock().unwrap(); + if spike_mem.is_none() { + info!("Creating SpikeMem with size: 0x{:x}", size); + *spike_mem = Some(Box::new(SpikeMem { + mem: vec![0; size], + size, + })); + } +} + +fn ld(addr: usize, len: usize, bytes: Vec) -> anyhow::Result<()> { + trace!("ld: addr: 0x{:x}, len: 0x{:x}", addr, len); + let mut spike_mem = SPIKE_MEM.lock().unwrap(); + let spike_ref = spike_mem.as_mut().unwrap(); + + assert!(addr + len <= spike_ref.size); + + let dst = &mut spike_ref.mem[addr..addr + len]; + for (i, byte) in bytes.iter().enumerate() { + dst[i] = *byte; + } + + Ok(()) +} + +fn read_mem(addr: usize) -> anyhow::Result { + let mut spike_mem = SPIKE_MEM.lock().unwrap(); + let spike_ref = spike_mem.as_mut().unwrap(); + + let dst = &mut spike_ref.mem[addr]; + + Ok(*dst) +} + +fn load_elf(fname: &Path) -> anyhow::Result { + let mut file = File::open(fname).unwrap(); + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer).unwrap(); + + let elf_file = ElfFile::new(&buffer).unwrap(); + + let header = elf_file.header; + assert_eq!(header.pt2.machine().as_machine(), header::Machine::RISC_V); + assert_eq!(header.pt1.class(), header::Class::ThirtyTwo); + + for ph in elf_file.program_iter() { + if let ProgramHeader::Ph32(ph) = ph { + if ph.get_type() == Ok(Type::Load) { + let offset = ph.offset as usize; + let size = ph.file_size as usize; + let addr = ph.virtual_addr as usize; + + let slice = &buffer[offset..offset + size]; + ld(addr, size, slice.to_vec()).unwrap(); + } + } + } + + Ok(header.pt2.entry_point()) +} + +pub fn clip(binary: u64, a: i32, b: i32) -> u32 { + assert!(a <= b, "a should be less than or equal to b"); + let nbits = b - a + 1; + let mask = if nbits >= 32 { + u32::MAX + } else { + (1 << nbits) - 1 + }; + (binary as u32 >> a) & mask +} + +pub struct Config { + pub vlen: u32, + pub dlen: u32, +} + +pub fn add_rtl_write(se: &mut SpikeEvent, vrf_write: VrfWriteEvent, record_idx_base: usize) { + (0..4).for_each(|j| { + if ((vrf_write.mask >> j) & 1) != 0 { + let written_byte = ((vrf_write.data >> (8 * j)) & 0xff) as u8; + let record_iter = se + .vrf_access_record + .all_writes + .get_mut(&(record_idx_base + j)); + + if let Some(record) = record_iter { + assert_eq!( + record.byte, + written_byte, + "{j}th byte incorrect ({:02X} != {written_byte:02X}) for vrf \ + write (lane={}, vd={}, offset={}, mask={:04b}) \ + [vrf_idx={}] (lsu_idx={}, disasm: {}, pc: {:#x}, bits: {:#x})", + record.byte, + vrf_write.idx, + vrf_write.vd, + vrf_write.offset, + vrf_write.mask, + record_idx_base + j, + se.lsu_idx, + se.disasm, + se.pc, + se.inst_bits + ); + record.executed = true; + } + } // end if mask + }) // end for j +} + +#[derive(Debug, Clone)] +pub struct TLReqRecord { + cycle: usize, + size_by_byte: usize, + addr: u32, + + muxin_read_required: bool, + + // For writes, as soon as the transaction is sent to the controller, the request is resolved, so we don't have to track the number + // of bytes that have been processed by the memory controller + + // Only meaningful for writes, this is the number of bytes written by user. + bytes_received: usize, + + // This is the number of bytes(or worth-of transaction for reads) sent to the memory controller + bytes_committed: usize, + + // This is the number of bytes that have been processed by the memory controller + bytes_processed: usize, + + // For read, number of bytes returned to user + bytes_returned: usize, + + op: Opcode, +} + +impl TLReqRecord { + pub fn new(cycle: usize, size_by_byte: usize, addr: u32, op: Opcode, burst_size: usize) -> Self { + TLReqRecord { + cycle, + size_by_byte, + addr, + muxin_read_required: op == Opcode::PutFullData && size_by_byte < burst_size, + bytes_received: 0, + bytes_committed: 0, + bytes_processed: 0, + bytes_returned: 0, + op, + } + } + + fn skip(&mut self) { + self.muxin_read_required = false; + self.bytes_committed = if self.op == Opcode::PutFullData { + self.bytes_received + } else { + self.size_by_byte + }; + self.bytes_processed = self.bytes_committed; + } + + fn commit_tl_respones(&mut self, tl_bytes: usize) -> anyhow::Result<()> { + self.bytes_returned += tl_bytes; + + Ok(()) + } + + fn done_return(&mut self) -> anyhow::Result { + if self.muxin_read_required { + return Ok(false); + } + if self.op == Opcode::PutFullData { + Ok(self.bytes_returned > 0) + } else { + Ok(self.bytes_returned >= self.size_by_byte) + } + } +} + +pub struct SpikeHandle { + spike: Spike, + + /// to rtl stack + /// 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 str_stack. in the RTL thread, the RTL driver will + /// consume from this queue, drive signal based on the queue. size of this + /// queue should be as big as enough to make rtl free to run, reducing the + /// context switch overhead. + pub to_rtl_queue: VecDeque, + + /// config for v extension + pub config: Config, + + /// tl request record of bank, indexed by issue_idx + pub tl_req_record_of_bank: Vec>, + + /// the get_t() of a req response waiting for ready + pub tl_req_waiting_ready: Vec>, + + /// the get_t() of a req with ongoing burst + pub tl_req_ongoing_burst: Vec>, +} + +impl SpikeHandle { + pub fn new(size: usize, fname: &Path, vlen: u32, dlen: u32, set: String) -> Self { + // register the addr_to_mem callback + unsafe { spike_register_callback(rs_addr_to_mem) } + + // create a new spike memory instance + init_memory(size); + + // load the elf file + let entry_addr = load_elf(fname).unwrap(); + + // initialize spike + let arch = &format!("vlen:{vlen},elen:32"); + let lvl = "M"; + + let spike = Spike::new(arch, &set, lvl); + + // initialize processor + let proc = spike.get_proc(); + let state = proc.get_state(); + proc.reset(); + state.set_pc(entry_addr); + + SpikeHandle { + spike, + to_rtl_queue: VecDeque::new(), + config: Config { vlen, dlen }, + // config.tl_bank_number = 13 + tl_req_record_of_bank: (0..13).map(|_| HashMap::new()).collect(), + tl_req_waiting_ready: vec![None; 13], + tl_req_ongoing_burst: vec![None; 13], + } + } + + // just execute one instruction for no-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(); + + let ret = state.exit(); + + if ret == 0 { + return Err(anyhow::anyhow!("simulation finished!")); + } + + Ok(()) + } + + // execute the spike processor for one instruction and record + // the spike event for difftest + pub fn spike_step(&mut self) -> Option { + let proc = self.spike.get_proc(); + let state = proc.get_state(); + + let pc = state.get_pc(); + let disasm = proc.disassemble(); + + let mut event = self.create_spike_event(); + state.clear(); + + let new_pc; + match event { + // inst is load / store / v / quit + Some(ref mut se) => { + info!( + "SpikeStep: pc={:#x}, disasm={:?}, spike run vector insn", + pc, disasm + ); + se.pre_log_arch_changes(&self.spike, self.config.vlen) + .unwrap(); + new_pc = proc.func(); + se.log_arch_changes(&self.spike, self.config.vlen).unwrap(); + } + None => { + info!( + "SpikeStep: pc={:#x}, disasm={:?}, spike run scalar insn", + pc, disasm + ); + new_pc = proc.func(); + } + } + + state.handle_pc(new_pc).unwrap(); + + event + } + + // step the spike processor until the instruction is load/store/v/quit + // if the instruction is load/store/v/quit, execute it and return + fn create_spike_event(&mut self) -> Option { + let spike = &self.spike; + let proc = spike.get_proc(); + + let insn = proc.get_insn(); + + let opcode = clip(insn, 0, 6); + let width = clip(insn, 12, 14); + let rs1 = clip(insn, 15, 19); + let csr = clip(insn, 20, 31); + + // early return vsetvl scalar instruction + let is_vsetvl = opcode == 0b1010111 && width == 0b111; + if is_vsetvl { + return None; + } + + let is_load_type = opcode == 0b0000111 && (((width - 1) & 0b100) != 0); + let is_store_type = opcode == 0b0100111 && (((width - 1) & 0b100) != 0); + let is_v_type = opcode == 0b1010111; + + let is_csr_type = opcode == 0b1110011 && ((width & 0b011) != 0); + let is_csr_write = is_csr_type && (((width & 0b100) | rs1) != 0); + + let is_quit = is_csr_write && csr == 0x7cc; + + if is_load_type || is_store_type || is_v_type || is_quit { + return SpikeEvent::new(spike); + } + None + } + + pub fn find_se_to_issue(&mut self) -> SpikeEvent { + // find the first instruction that is not issued from the back + for se in self.to_rtl_queue.iter().rev() { + if !se.is_issued { + return se.clone(); + } + } + + loop { + if let Some(se) = self.spike_step() { + self.to_rtl_queue.push_front(se.clone()); + return se; + } + } + } + + pub fn peek_issue(&mut self, issue: IssueEvent) -> anyhow::Result<()> { + let se = self.to_rtl_queue.front_mut().unwrap(); + if se.is_vfence_insn || se.is_exit_insn { + return Ok(()); + } + + se.is_issued = true; + se.issue_idx = issue.idx as u8; + + info!( + "[{}] SpikePeekIssue: idx={}, pc={:#x}, inst={}", + issue.cycle, issue.idx, se.pc, se.disasm + ); + + Ok(()) + } + + pub fn update_lsu_idx(&mut self, lsu_enq: LsuEnqEvent) -> anyhow::Result<()> { + let enq = lsu_enq.enq; + assert!(enq > 0, "enq should be greater than 0"); + let cycle = lsu_enq.cycle; + + if let Some(se) = self + .to_rtl_queue + .iter_mut() + .rev() + .find(|se| se.is_issued && (se.is_load || se.is_store) && se.lsu_idx == LSU_IDX_DEFAULT) + { + let index = enq.trailing_zeros() as u8; + se.lsu_idx = index; + info!("[{cycle}] UpdateLSUIdx: Instruction is allocated with pc: {:#x}, inst: {} and lsu_idx: {index}", se.pc, se.disasm); + } + Ok(()) + } + + pub fn peek_vrf_write_from_lsu(&mut self, vrf_write: VrfWriteEvent) -> anyhow::Result<()> { + let cycle = vrf_write.cycle; + let vlen_in_bytes = self.config.vlen / 8; + let lane_number = self.config.dlen / 32; + let record_idx_base = (vrf_write.vd * vlen_in_bytes + + (vrf_write.idx + lane_number * vrf_write.offset) * 4) as usize; + + if let Some(se) = self + .to_rtl_queue + .iter_mut() + .rev() + .find(|se| se.issue_idx == vrf_write.instruction as u8) + { + info!("[{cycle}] RecordRFAccesses: lane={}, vd={}, offset={}, mask={:04b}, data={:08x}, instruction={}, rtl detect vrf queue write" , vrf_write.idx, vrf_write.vd, vrf_write.offset, vrf_write.mask, vrf_write.data, vrf_write.instruction); + + add_rtl_write(se, vrf_write, record_idx_base); + return Ok(()); + } + + panic!( + "[{cycle}] cannot find se with issue_idx={}", + vrf_write.instruction + ) + } + + pub fn peek_vrf_write_from_lane(&mut self, vrf_write: VrfWriteEvent) -> anyhow::Result<()> { + let cycle = vrf_write.cycle; + let vlen_in_bytes = self.config.vlen / 8; + let lane_number = self.config.dlen / 32; + let record_idx_base = (vrf_write.vd * vlen_in_bytes + + (vrf_write.idx + lane_number * vrf_write.offset) * 4) as usize; + + if let Some(se) = self + .to_rtl_queue + .iter_mut() + .rev() + .find(|se| se.issue_idx == vrf_write.instruction as u8) + { + if !se.is_load { + info!("[{cycle}] RecordRFAccesses: lane={}, vd={}, offset={}, mask={:04b}, data={:08x}, instruction={}, rtl detect vrf write", vrf_write.idx, vrf_write.vd, vrf_write.offset, vrf_write.mask, vrf_write.data, vrf_write.instruction); + + add_rtl_write(se, vrf_write, record_idx_base); + } + return Ok(()); + } + + info!("[{cycle}] RecordRFAccess: index={} rtl detect vrf write which cannot find se, maybe from committed load insn", vrf_write.idx); + Ok(()) + } + + pub fn peek_tl(&mut self, peek_tl: &PeekTLEvent) -> anyhow::Result<()> { + // config.tl_bank_number + assert!(peek_tl.idx < 13); + self.receive_tl_d_ready(peek_tl).unwrap(); + self.receive_tl_req(peek_tl).unwrap(); + Ok(()) + } + + pub fn receive_tl_d_ready(&mut self, peek_tl: &PeekTLEvent) -> anyhow::Result<()> { + let idx = peek_tl.idx as usize; + if !peek_tl.dready { + return Ok(()); + } + + if let Some(addr) = self.tl_req_waiting_ready[idx] { + let req_record = self.tl_req_record_of_bank[idx] + .get_mut(&addr) + .unwrap_or_else(|| panic!("cannot find current request with addr {addr:08X}")); + + req_record + .commit_tl_respones(DATAPATH_WIDTH_IN_BYTES) + .unwrap(); + + if req_record.done_return().unwrap() { + info!( + "ReceiveTlDReady channel: {idx}, addr: {addr:08x}, -> tl response for {} reaches d_ready", + match req_record.op { + Opcode::Get => "Get", + _ => "PutFullData", + } + ); + } + + self.tl_req_waiting_ready[idx] = None; + + // TODO(Meow): add this check back + // panic!(format!("unknown opcode {}", req_record.op as i32)); + } + Ok(()) + } + // the info in peek tl should have cycle to debug + pub fn receive_tl_req(&mut self, peek_tl: &PeekTLEvent) -> anyhow::Result<()> { + let idx = peek_tl.idx as usize; + let tl_data = peek_tl.data; + let mask = peek_tl.mask; + let cycle = peek_tl.cycle; + let size = peek_tl.size; + let source = peek_tl.source; + let base_addr = peek_tl.addr; + let lsu_idx = (peek_tl.source & 3) as u8; + if let Some(se) = self + .to_rtl_queue + .iter_mut() + .find(|se| se.lsu_idx == lsu_idx) + { + match peek_tl.opcode { + Opcode::Get => { + let mut actual_data = vec![0u8; size]; + for (offset, actual) in actual_data.iter_mut().enumerate().take(size) { + let addr = base_addr + offset as u32; + match se.mem_access_record.all_reads.get_mut(&addr) { + Some(mem_read) => { + *actual = mem_read.reads[mem_read.num_completed_reads].val; + mem_read.num_completed_reads += 1; + } + None => { + warn!("[{cycle}] ReceiveTLReq addr: {addr:08X} insn: {} send falsy data 0xDE for accessing unexpected memory", format!("{:x}", se.inst_bits)); + *actual = 0xDE; // falsy data + } + } + } + + let hex_actual_data = actual_data + .iter() + .fold(String::new(), |acc, x| acc + &format!("{:02X} ", x)); + info!("[{cycle}] SpikeReceiveTLReq: <- receive rtl mem get req: channel={idx}, base_addr={base_addr:08X}, size={size}, mask={mask:b}, source={source}, return_data={hex_actual_data}"); + + self.tl_req_record_of_bank[idx].insert( + cycle, + TLReqRecord::new(cycle, size, base_addr, Opcode::Get, 1), + ); + + self.tl_req_record_of_bank[idx] + .get_mut(&cycle) + .unwrap() + .skip(); + } + + Opcode::PutFullData => { + let mut cur_record: Option<&mut TLReqRecord> = None; + // determine if it is a beat of ongoing burst + // the first Some match the result of get, the second Some match the result determined by if the + // tl_req_ongoing_burst[idx] is Some / None + if let Some(tl_req_ongoing_burst) = self.tl_req_ongoing_burst[idx] { + if let Some(record) = self.tl_req_record_of_bank[idx].get_mut(&tl_req_ongoing_burst) { + if record.bytes_received < record.size_by_byte { + assert_eq!(record.addr, base_addr, "inconsistent burst addr"); + assert_eq!(record.size_by_byte, size, "inconsistent burst size"); + info!( + "[{cycle}] ReceiveTLReq: continue burst, channel: {idx}, base_addr: {base_addr:08X}, offset: {}", + record.bytes_received + ); + cur_record = Some(record); + } else { + panic!("[{cycle}] invalid record") + } + } + } + + // else create a new record + if cur_record.is_none() { + // 1 is dummy value, won't be effective whatsoever. 1 is to ensure that no sub-line write is possible + // here we do not use dramsim3. + let record = TLReqRecord::new(cycle, size, base_addr, Opcode::PutFullData, 1); + self.tl_req_record_of_bank[idx].insert(cycle, record); + + // record moved into self.tl_req_record_of_bank, so we should get it from there + cur_record = self.tl_req_record_of_bank[idx].get_mut(&cycle); + } + + let mut data = vec![0u8; size]; + let actual_beat_size = std::cmp::min(size, DATAPATH_WIDTH_IN_BYTES); // since tl require alignment + let data_begin_pos = cur_record.as_ref().unwrap().bytes_received; + + // receive put data + // if actual beat size is bigger than 8, there maybe some problems + // TODO: fix this + for offset in 0..actual_beat_size { + data[data_begin_pos + offset] = (tl_data >> (offset * 8)) as u8; + } + info!("[{cycle}] RTLMemPutReq: <- receive rtl mem put req, channel: {idx}, base_addr: {base_addr:08X}, offset: {data_begin_pos}, size: {size}, source: {source:04X}, data: {tl_data:08X}, mask: {mask:04X}"); + + // compare with spike event record + for offset in 0..actual_beat_size { + // config.datapath_width_in_bytes - 1 = 3 + let byte_lane_idx = (base_addr & 3) + offset as u32; + // if byte_lane_idx > 32, there maybe some problem + if (mask >> byte_lane_idx) & 1 != 0 { + let byte_addr = + base_addr + cur_record.as_ref().unwrap().bytes_received as u32 + offset as u32; + let tl_data_byte = (tl_data >> (8 * byte_lane_idx)) as u8; + let mem_write = se + .mem_access_record + .all_writes + .get_mut(&byte_addr) + .unwrap_or_else(|| { + panic!("[{cycle}] cannot find mem write of byte_addr {byte_addr:08x}") + }); + assert!( + mem_write.num_completed_writes < mem_write.writes.len(), + "[{cycle}] written size:{} should be smaller than completed writes:{}", + mem_write.writes.len(), + mem_write.num_completed_writes + ); + let single_mem_write_val = mem_write.writes[mem_write.num_completed_writes].val; + mem_write.num_completed_writes += 1; + assert_eq!(single_mem_write_val, tl_data_byte, "[{cycle}] expect mem write of byte {single_mem_write_val:02X}, actual byte {tl_data_byte:02X} (channel={idx}, byte_addr={byte_addr:08X}, pc = {:#x}, disasm = {})", se.pc, se.disasm); + } + } + + cur_record.as_mut().unwrap().bytes_received += actual_beat_size; + cur_record.as_mut().unwrap().skip(); + + // update tl_req_ongoing_burst + if cur_record.as_ref().unwrap().bytes_received < size { + self.tl_req_ongoing_burst[idx] = Some(cur_record.as_ref().unwrap().cycle); + } else { + self.tl_req_ongoing_burst[idx] = None; + } + } + _ => { + panic!("not implemented") + } + } + + return Ok(()); + } + + panic!("[{cycle}] cannot find se with lsu_idx={lsu_idx}") + } +} diff --git a/difftest/t1-simulator/src/spike/libspike_interfaces.rs b/difftest/t1-simulator/src/difftest/spike/libspike_interfaces.rs similarity index 96% rename from difftest/t1-simulator/src/spike/libspike_interfaces.rs rename to difftest/t1-simulator/src/difftest/spike/libspike_interfaces.rs index ea6df0d5e6..b08c19b0da 100644 --- a/difftest/t1-simulator/src/spike/libspike_interfaces.rs +++ b/difftest/t1-simulator/src/difftest/spike/libspike_interfaces.rs @@ -15,7 +15,7 @@ impl Spike { } pub fn get_proc(&self) -> Processor { - let processor = unsafe { spike_get_proc(self.spike as *mut ()) }; + let processor = unsafe { spike_get_proc(self.spike) }; Processor { processor } } } @@ -126,12 +126,16 @@ impl State { } } + pub fn get_reg(&self, idx: u32, is_fp: bool) -> u32 { + unsafe { state_get_reg(self.state, idx, is_fp) } + } + pub fn get_reg_write_size(&self) -> u32 { unsafe { state_get_reg_write_size(self.state) } } - pub fn get_reg(&self, idx: u32, is_fp: bool) -> u32 { - unsafe { state_get_reg(self.state, idx, is_fp) } + pub fn get_reg_write_index(&self, index: u32) -> u32 { + unsafe { state_get_reg_write_index(self.state) >> (index * 4) } } pub fn get_mem_write_size(&self) -> u32 { @@ -201,6 +205,7 @@ extern "C" { fn state_get_pc(state: *mut ()) -> u64; fn state_get_reg(state: *mut (), index: u32, is_fp: bool) -> u32; fn state_get_reg_write_size(state: *mut ()) -> u32; + fn state_get_reg_write_index(state: *mut ()) -> u32; fn state_get_mem_write_size(state: *mut ()) -> u32; fn state_get_mem_write_addr(state: *mut (), index: u32) -> u32; fn state_get_mem_write_value(state: *mut (), index: u32) -> u64; diff --git a/difftest/t1-simulator/src/difftest/spike/spike_event.rs b/difftest/t1-simulator/src/difftest/spike/spike_event.rs new file mode 100644 index 0000000000..374be52f7d --- /dev/null +++ b/difftest/t1-simulator/src/difftest/spike/spike_event.rs @@ -0,0 +1,402 @@ +use super::Spike; +use super::{clip, read_mem}; +use std::collections::HashMap; +use tracing::{info, trace}; + +#[derive(Debug, Clone)] +pub struct SingleMemWrite { + pub val: u8, + pub executed: bool, // set to true when rtl execute this mem access +} + +#[derive(Debug, Clone)] +pub struct SingleMemRead { + pub val: u8, + pub executed: bool, // set to true when rtl execute this mem access +} + +#[derive(Debug, Clone)] +pub struct MemWriteRecord { + pub writes: Vec, + pub num_completed_writes: usize, +} + +#[derive(Debug, Clone)] +pub struct MemReadRecord { + pub reads: Vec, + pub num_completed_reads: usize, +} + +#[derive(Debug, Clone)] +pub struct SingleVrfWrite { + pub byte: u8, + pub executed: bool, // set to true when rtl execute this mem access +} + +#[derive(Default, Debug, Clone)] +pub struct VdWriteRecord { + vd_bytes: Vec, +} + +#[derive(Default, Debug, Clone)] +pub struct MemAccessRecord { + pub all_writes: HashMap, + pub all_reads: HashMap, +} + +#[derive(Default, Debug, Clone)] +pub struct VrfAccessRecord { + pub all_writes: HashMap, +} + +#[derive(Default, Debug, Clone)] +pub struct SpikeEvent { + pub lsu_idx: u8, + pub issue_idx: u8, + + pub is_issued: bool, + + pub is_load: bool, + pub is_store: bool, + pub is_whole: bool, + pub is_widening: bool, + pub is_mask_vd: bool, + pub is_exit_insn: bool, + pub is_vfence_insn: bool, + + pub pc: u64, + pub inst_bits: u64, + + // scalar to vector interface(used for driver) + pub rs1_bits: u32, + pub rs2_bits: u32, + pub rd_idx: u32, + + // vtype + pub vsew: u32, + pub vlmul: u32, + pub vma: bool, + pub vta: bool, + pub vxrm: u32, + pub vnf: u32, + + // other CSR + pub vill: bool, + pub vxsat: bool, + + pub vl: u32, + pub vstart: u16, + pub disasm: String, + + pub vd_write_record: VdWriteRecord, + + pub is_rd_written: bool, + pub rd_bits: u32, + pub is_rd_fp: bool, // whether rd is a fp register + + pub mem_access_record: MemAccessRecord, + pub vrf_access_record: VrfAccessRecord, +} + +impl SpikeEvent { + pub fn new(spike: &Spike) -> Option { + let inst_bits = spike.get_proc().get_insn(); + // inst info + let opcode = clip(inst_bits, 0, 6); + let width = clip(inst_bits, 12, 14); // also funct3 + let funct6 = clip(inst_bits, 26, 31); + let mop = clip(inst_bits, 26, 27); + let lumop = clip(inst_bits, 20, 24); + let vm = clip(inst_bits, 25, 25); + + // rs1, rs2 + let is_rs_fp = opcode == 0b1010111 && width == 0b101/* OPFVF */; + let proc = spike.get_proc(); + let state = proc.get_state(); + let (rs1, rs2) = (proc.get_rs1(), proc.get_rs2()); + + // vtype + let vtype = proc.vu_get_vtype(); + + Some(SpikeEvent { + lsu_idx: 255, + issue_idx: 255, + inst_bits, + rs1_bits: state.get_reg(rs1, is_rs_fp), + rs2_bits: state.get_reg(rs2, is_rs_fp), + // rd + is_rd_fp: (opcode == 0b1010111) + && (rs1 == 0) + && (funct6 == 0b010000) + && (vm == 1) + && (width == 0b001), + rd_idx: proc.get_rd(), + is_rd_written: false, + + // vtype + vlmul: clip(vtype, 0, 2), + vma: clip(vtype, 7, 7) != 0, + vta: clip(vtype, 6, 6) != 0, + vsew: clip(vtype, 3, 5), + vxrm: proc.vu_get_vxrm(), + vnf: proc.vu_get_vnf(), + + vill: proc.vu_get_vill(), + vxsat: proc.vu_get_vxsat(), + vl: proc.vu_get_vl(), + vstart: proc.vu_get_vstart(), + + // se info + disasm: spike.get_proc().disassemble(), + pc: proc.get_state().get_pc(), + is_load: opcode == 0b0000111, + is_store: opcode == 0b0100111, + is_whole: mop == 0 && lumop == 8, + is_widening: opcode == 0b1010111 && (funct6 >> 4) == 0b11, + is_mask_vd: opcode == 0b1010111 && (funct6 >> 3 == 0b011 || funct6 == 0b010001), + is_exit_insn: opcode == 0b1110011, + is_vfence_insn: false, + + is_issued: false, + ..Default::default() + }) + } + + pub fn get_vrf_write_range(&self, vlen_in_bytes: u32) -> anyhow::Result<(u32, u32)> { + if self.is_store { + return Ok((0, 0)); + } + + if self.is_load { + let vd_bytes_start = self.rd_idx * vlen_in_bytes; + if self.is_whole { + return Ok((vd_bytes_start, vlen_in_bytes * (1 + self.vnf))); + } + let len = if self.vlmul & 0b100 != 0 { + vlen_in_bytes * (1 + self.vnf) + } else { + (vlen_in_bytes * (1 + self.vnf)) << self.vlmul + }; + return Ok((vd_bytes_start, len)); + } + + let vd_bytes_start = self.rd_idx * vlen_in_bytes; + + if self.is_mask_vd { + return Ok((vd_bytes_start, vlen_in_bytes)); + } + + let len = if self.vlmul & 0b100 != 0 { + vlen_in_bytes >> (8 - self.vlmul) + } else { + vlen_in_bytes << self.vlmul + }; + + Ok((vd_bytes_start, if self.is_widening { len * 2 } else { len })) + } + + pub fn pre_log_arch_changes(&mut self, spike: &Spike, vlen: u32) -> anyhow::Result<()> { + self.rd_bits = spike.get_proc().get_rd(); + + // record the vrf writes before executing the insn + let vlen_in_bytes = vlen; + + let proc = spike.get_proc(); + let (start, len) = self.get_vrf_write_range(vlen_in_bytes).unwrap(); + self.vd_write_record.vd_bytes.resize(len as usize, 0u8); + for i in 0..len { + let offset = start + i; + let vreg_index = offset / vlen_in_bytes; + let vreg_offset = offset % vlen_in_bytes; + let cur_byte = proc.get_vreg_data(vreg_index, vreg_offset); + self.vd_write_record.vd_bytes[i as usize] = cur_byte; + } + + Ok(()) + } + + pub fn log_arch_changes(&mut self, spike: &Spike, vlen: u32) -> anyhow::Result<()> { + self.log_vrf_write(spike, vlen).unwrap(); + self.log_reg_write(spike).unwrap(); + self.log_mem_write(spike).unwrap(); + self.log_mem_read(spike).unwrap(); + + Ok(()) + } + + fn log_vrf_write(&mut self, spike: &Spike, vlen: u32) -> anyhow::Result<()> { + let proc = spike.get_proc(); + // record vrf writes + // note that we do not need log_reg_write to find records, we just decode the + // insn and compare bytes + let vlen_in_bytes = vlen / 8; + let (start, len) = self.get_vrf_write_range(vlen_in_bytes).unwrap(); + trace!("start: {start}, len: {len}"); + for i in 0..len { + let offset = start + i; + let origin_byte = self.vd_write_record.vd_bytes[i as usize]; + let vreg_index = offset / vlen_in_bytes; + let vreg_offset = offset % vlen_in_bytes; + let cur_byte = proc.get_vreg_data(vreg_index, vreg_offset); + if origin_byte != cur_byte { + self + .vrf_access_record + .all_writes + .entry(offset as usize) + .or_insert(SingleVrfWrite { + byte: cur_byte, + executed: false, + }); + trace!( + "SpikeVRFChange: vrf={:?}, change_from={origin_byte}, change_to={cur_byte}, vrf_idx={offset}", + vec![offset / vlen_in_bytes, offset % vlen_in_bytes], + ); + } + } + Ok(()) + } + + fn log_reg_write(&mut self, spike: &Spike) -> anyhow::Result<()> { + let proc = spike.get_proc(); + let state = proc.get_state(); + // in spike, log_reg_write is arrange: + // xx0000 <- x + // xx0001 <- f + // xx0010 <- vreg + // xx0011 <- vec + // xx0100 <- csr + let reg_write_size = state.get_reg_write_size(); + // TODO: refactor it. + (0..reg_write_size).for_each(|idx| match state.get_reg_write_index(idx) & 0xf { + 0b0000 => { + // scalar rf + let data = state.get_reg(self.rd_idx, false); + if data != self.rd_bits { + trace!( + "ScalarRFChange: idx={}, change_from={}, change_to={data}", + self.rd_idx, self.rd_bits + ); + self.rd_bits = data; + self.is_rd_written = true; + } + } + 0b0001 => { + let data = state.get_reg(self.rd_idx, true); + if data != self.rd_bits { + trace!( + "FloatRFChange: idx={}, change_from={}, change_to={data}", + self.rd_idx, self.rd_bits + ); + self.rd_bits = data; + self.is_rd_written = true; + } + } + _ => trace!("UnknownRegChange, idx={idx}, spike detect unknown reg change"), + }); + + Ok(()) + } + + fn log_mem_write(&mut self, spike: &Spike) -> anyhow::Result<()> { + let proc = spike.get_proc(); + let state = proc.get_state(); + + let mem_write_size = state.get_mem_write_size(); + (0..mem_write_size).for_each(|i| { + let (addr, value, size) = state.get_mem_write(i); + (0..size).for_each(|offset| { + self + .mem_access_record + .all_writes + .entry(addr + offset as u32) + .or_insert(MemWriteRecord { + writes: vec![], + num_completed_writes: 0, + }) + .writes + .push(SingleMemWrite { + val: (value >> (offset * 8)) as u8, + executed: false, + }); + }); + info!("SpikeMemWrite: addr={addr:x}, value={value:x}, size={size}"); + }); + + Ok(()) + } + + fn log_mem_read(&mut self, spike: &Spike) -> anyhow::Result<()> { + let proc = spike.get_proc(); + let state = proc.get_state(); + + let mem_read_size = state.get_mem_read_size(); + (0..mem_read_size).for_each(|i| { + let (addr, size) = state.get_mem_read(i); + let mut value = 0; + (0..size).for_each(|offset| { + let byte = read_mem(addr as usize + offset as usize).unwrap(); + value |= (byte as u64) << (offset * 8); + // record the read + self + .mem_access_record + .all_reads + .entry(addr + offset as u32) + .or_insert(MemReadRecord { + reads: vec![], + num_completed_reads: 0, + }) + .reads + .push(SingleMemRead { + val: byte, + executed: false, + }); + }); + info!("SpikeMemRead: addr={addr:x}, value={value:x}, size={size}"); + }); + + Ok(()) + } + + pub fn record_rd_write(&self, data: u32) -> anyhow::Result<()> { + // TODO: rtl should indicate whether resp_bits_data is valid + if self.is_rd_written { + assert_eq!( + data, self.rd_bits, + "expect to write rd[{}] = {}, actual {}", + self.rd_idx, self.rd_bits, data + ); + } + + Ok(()) + } + + pub fn check_is_ready_for_commit(&self, cycle: usize) -> anyhow::Result<()> { + for (addr, record) in &self.mem_access_record.all_writes { + assert_eq!( + record.num_completed_writes, + record.writes.len(), + "[{cycle}] expect to write mem {addr:#x}, not executed when commit (pc={:#x}, inst={})", + self.pc, + self.disasm + ); + } + for (addr, record) in &self.mem_access_record.all_reads { + assert_eq!( + record.num_completed_reads, + record.reads.len(), + "[{cycle}] expect to read mem {addr:#x}, not executed when commit (pc={:#x}, inst={})", + self.pc, + self.disasm + ); + } + for (idx, record) in &self.vrf_access_record.all_writes { + assert!( + record.executed, + "[{cycle}] expect to write vrf {idx}, not executed when commit (pc={:#x}, inst={})", + self.pc, self.disasm + ); + } + + Ok(()) + } +} diff --git a/difftest/t1-simulator/src/main.rs b/difftest/t1-simulator/src/main.rs index 3f75f10ca4..86c9bd03cd 100644 --- a/difftest/t1-simulator/src/main.rs +++ b/difftest/t1-simulator/src/main.rs @@ -1,7 +1,8 @@ -mod spike; +mod difftest; use clap::Parser; -use spike::SpikeHandle; +use difftest::Difftest; +use difftest::SpikeHandle; use std::path::Path; use tracing::{info, Level}; use tracing_subscriber::{EnvFilter, FmtSubscriber}; @@ -14,19 +15,62 @@ struct Args { #[arg(short, long)] elf_file: String, - /// count step of instruction trace - #[arg(long, default_value = "100000")] - step: u64, + /// Path to the log file + #[arg(short, long)] + log_file: Option, + + /// Log level: trace, debug, info, warn, error + #[arg(short, long, default_value = "info")] + log_level: String, - /// vlen of the vector extension - #[arg(long, default_value = "1024")] + /// vlen config (default blastoise 512) + #[arg(short, long, default_value = "512")] vlen: u32, + + /// dlen config (default blastoise 256) + #[arg(short, long, default_value = "256")] + dlen: u32, + + /// ISA config + #[arg(short, long, default_value = "rv32gcv")] + set: String, +} + +fn run_spike(args: Args) -> anyhow::Result<()> { + let mut count: u64 = 0; + + let spike = SpikeHandle::new( + 1usize << 32, + Path::new(&args.elf_file), + args.vlen, + args.dlen, + args.set, + ); + loop { + count += 1; + if count % 1000000 == 0 { + info!("count = {}", count); + } + match spike.exec() { + Ok(_) => {} + Err(_) => { + info!("total v instrucions count = {}", count); + info!("Simulation quit graceful"); + return Ok(()); + } + }; + } } fn main() -> anyhow::Result<()> { + // parse args + let args = Args::parse(); + + // setup log + let log_level: Level = args.log_level.parse()?; let global_logger = FmtSubscriber::builder() .with_env_filter(EnvFilter::from_default_env()) - .with_max_level(Level::TRACE) + .with_max_level(log_level) .without_time() .with_target(false) .compact() @@ -34,31 +78,27 @@ fn main() -> anyhow::Result<()> { tracing::subscriber::set_global_default(global_logger) .expect("internal error: fail to setup log subscriber"); - let args = Args::parse(); + // if there is no log file, just run spike and quit + if args.log_file.is_none() { + run_spike(args)?; + return Ok(()); + } - // count the instruction - let mut count: u64 = 0; + // if there is a log file, run difftest + let mut diff = Difftest::new( + 1usize << 32, + args.elf_file, + args.log_file.unwrap(), + args.vlen, + args.dlen, + args.set, + ); - // if there is no log file, just run spike and quit - let spike = SpikeHandle::new(1usize << 32, Path::new(&args.elf_file), args.vlen); loop { - count += 1; - if count % args.step == 0 { - info!( - "count = {}, pc = {:#x}, inst = {}", - count, - spike.get_pc(), - spike.get_disasm() - ); - } - - // TODO:Thinking about add features to capture exceptions like Illegal Instruction. - // And T1 will add more SoC-level checker, e.g. memory boundary checker. - match spike.exec() { + match diff.diff() { Ok(_) => {} Err(e) => { - info!("total instrucions count = {}", count); - info!("Simulation quit with error/quit: {:?}", e); + info!("Simulation quit/error with {}", e); return Ok(()); } } diff --git a/difftest/t1-simulator/src/spike.rs b/difftest/t1-simulator/src/spike.rs deleted file mode 100644 index e9279185d2..0000000000 --- a/difftest/t1-simulator/src/spike.rs +++ /dev/null @@ -1,148 +0,0 @@ -use lazy_static::lazy_static; -use std::fs::File; -use std::io::Read; -use std::path::Path; -use std::sync::Mutex; -use tracing::{info, trace}; -use xmas_elf::{ - header, - program::{ProgramHeader, Type}, - ElfFile, -}; - -mod libspike_interfaces; -use libspike_interfaces::*; - -// read the addr from spike memory -// caller should make sure the address is valid -#[no_mangle] -pub extern "C" fn rs_addr_to_mem(addr: u64) -> *mut u8 { - let addr = addr as usize; - let mut spike_mem = SPIKE_MEM.lock().unwrap(); - let spike_mut = spike_mem.as_mut().unwrap(); - &mut spike_mut.mem[addr] as *mut u8 -} - -pub struct SpikeMem { - pub mem: Vec, - pub size: usize, -} - -lazy_static! { - static ref SPIKE_MEM: Mutex>> = Mutex::new(None); -} - -fn init_memory(size: usize) { - let mut spike_mem = SPIKE_MEM.lock().unwrap(); - if spike_mem.is_none() { - info!("Creating SpikeMem with size: 0x{:x}", size); - *spike_mem = Some(Box::new(SpikeMem { - mem: vec![0; size], - size, - })); - } -} - -fn ld(addr: usize, len: usize, bytes: Vec) -> anyhow::Result<()> { - trace!("ld: addr: 0x{:x}, len: 0x{:x}", addr, len); - let mut spike_mem = SPIKE_MEM.lock().unwrap(); - let spike_ref = spike_mem.as_mut().unwrap(); - - assert!(addr + len <= spike_ref.size); - - let dst = &mut spike_ref.mem[addr..addr + len]; - for (i, byte) in bytes.iter().enumerate() { - dst[i] = *byte; - } - - Ok(()) -} - -fn load_elf(fname: &Path) -> anyhow::Result { - let mut file = File::open(fname).unwrap(); - let mut buffer = Vec::new(); - file.read_to_end(&mut buffer).unwrap(); - - let elf_file = ElfFile::new(&buffer).unwrap(); - - let header = elf_file.header; - assert_eq!(header.pt2.machine().as_machine(), header::Machine::RISC_V); - assert_eq!(header.pt1.class(), header::Class::ThirtyTwo); - - for ph in elf_file.program_iter() { - match ph { - ProgramHeader::Ph32(ph) => { - if ph.get_type() == Ok(Type::Load) { - let offset = ph.offset as usize; - let size = ph.file_size as usize; - let addr = ph.virtual_addr as usize; - - let slice = &buffer[offset..offset + size]; - ld(addr, size, slice.to_vec()).unwrap(); - } - } - _ => (), - } - } - - Ok(header.pt2.entry_point()) -} - -pub struct SpikeHandle { - spike: Spike, -} - -impl SpikeHandle { - pub fn new(size: usize, fname: &Path, vlen: u32) -> Self { - // register the addr_to_mem callback - unsafe { spike_register_callback(rs_addr_to_mem) } - - // create a new spike memory instance - init_memory(size); - - // load the elf file - let entry_addr = load_elf(fname).unwrap(); - - // initialize spike - let arch = &format!("vlen:{},elen:32", vlen); - let set = "rv32imacv"; - let lvl = "M"; - - let spike = Spike::new(arch, set, lvl); - - // initialize processor - let proc = spike.get_proc(); - let state = proc.get_state(); - proc.reset(); - state.set_pc(entry_addr); - - SpikeHandle { spike } - } - - // just execute one instruction for no-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(); - - let ret = state.exit(); - - if ret == 0 { - return Err(anyhow::anyhow!("simulation finished!")); - } - - Ok(()) - } - - pub fn get_pc(&self) -> u64 { - self.spike.get_proc().get_state().get_pc() - } - - pub fn get_disasm(&self) -> String { - format!("{:?}", self.spike.get_proc().disassemble()) - } -} diff --git a/flake.nix b/flake.nix index 73c28df8a6..44c0f12799 100644 --- a/flake.nix +++ b/flake.nix @@ -22,8 +22,8 @@ # TODO: The dev shell will only depends on the T1 script package, let it manage different dev/ci/release flows. default = pkgs.mkShell { buildInputs = with pkgs; [ - # To develop T1-script, run nix develop .#t1-script.dev ammonite + # To develop T1-script, run nix develop .#t1-script.withLsp t1-script ]; }; diff --git a/ipemu/src/TestBench.scala b/ipemu/src/TestBench.scala index 1570aadd1b..07649af228 100644 --- a/ipemu/src/TestBench.scala +++ b/ipemu/src/TestBench.scala @@ -6,7 +6,6 @@ package org.chipsalliance.t1.ipemu import chisel3._ import chisel3.experimental.SerializableModuleGenerator import chisel3.probe._ -import chisel3.util.experimental.BoringUtils.bore import org.chipsalliance.t1.ipemu.dpi._ import org.chipsalliance.t1.rtl.{T1, T1Parameter} @@ -34,33 +33,39 @@ class TestBench(generator: SerializableModuleGenerator[T1, T1Parameter]) extends val dut: T1 = withClockAndReset(clock, reset)(Module(generator.module())) dut.storeBufferClear := true.B - val lsuProbe = probe.read(dut.lsuProbe).suggestName("lsuProbe") - val laneProbes = dut.laneProbes.zipWithIndex.map{case (p, idx) => val wire = Wire(p.cloneType).suggestName(s"lane${idx}Probe") wire := probe.read(p) - wire } - val laneVrfProbes = dut.laneVrfProbes.zipWithIndex.map{case (p, idx) => + val lsuProbe = probe.read(dut.lsuProbe).suggestName("lsuProbe") + + val laneVrfProbes = dut.laneVrfProbes.zipWithIndex.map{ case (p, idx) => val wire = Wire(p.cloneType).suggestName(s"lane${idx}VrfProbe") wire := probe.read(p) wire } - val t1Probe = probe.read(dut.t1Probe).suggestName("instructionCountProbe") - - // Monitor - withClockAndReset(clock, reset)(Module(new Module { - // h/t: GrandCentral - override def desiredName: String = "XiZhiMen" - val lsuProbeMonitor = bore(lsuProbe) - dontTouch(lsuProbeMonitor) - val laneProbesMonitor = laneProbes.map(bore(_)) - laneProbesMonitor.foreach(dontTouch(_)) - val laneVrfProbesMonitor = laneVrfProbes.map(bore(_)) - laneVrfProbesMonitor.foreach(dontTouch(_)) - })) + val t1Probe = probe.read(dut.t1Probe) + + withClockAndReset(clock, reset) { + // count cycle for peek tl + val cycleCounter = RegInit(0.U(64.W)) + cycleCounter := cycleCounter + 1.U + + // memory write + lsuProbe.slots.zipWithIndex.foreach { case (mshr, i) => when(mshr.writeValid)(printf(cf"""{"event":"vrfWriteFromLsu","parameter":{"idx":$i,"vd":${mshr.dataVd},"offset":${mshr.dataOffset},"mask":${mshr.dataMask},"data":${mshr.dataData},"instruction":${mshr.dataInstruction},"lane":${mshr.targetLane},"cycle": ${cycleCounter}}}\n""")) } + // vrf write + laneVrfProbes.zipWithIndex.foreach { case (lane, i) => when(lane.valid)(printf(cf"""{"event":"vrfWriteFromLane","parameter":{"idx":$i,"vd":${lane.requestVd},"offset":${lane.requestOffset},"mask":${lane.requestMask},"data":${lane.requestData},"instruction":${lane.requestInstruction},"cycle": ${cycleCounter}}}\n""")) } + // issue + when(dut.request.fire)(printf(cf"""{"event":"issue","parameter":{"idx":${t1Probe.instructionCounter},"cycle": ${cycleCounter}}}\n""")) + // inst + when(dut.response.valid)(printf(cf"""{"event":"inst","parameter":{"data":${dut.response.bits.data},"vxsat":${dut.response.bits.vxsat},"rd_valid":${dut.response.bits.rd.valid},"rd":${dut.response.bits.rd.bits},"mem":${dut.response.bits.mem},"cycle": ${cycleCounter}}}\n""")) + // peekTL + dut.memoryPorts.zipWithIndex.foreach { case (bundle, i) => when(bundle.a.valid)(printf(cf"""{"event":"peekTL","parameter":{"idx":$i,"opcode":${bundle.a.bits.opcode},"param":${bundle.a.bits.param},"size":${bundle.a.bits.size},"source":${bundle.a.bits.source},"address":${bundle.a.bits.address},"mask":${bundle.a.bits.mask},"data":${bundle.a.bits.data},"corrupt":${bundle.a.bits.corrupt},"dready":${bundle.d.ready},"cycle": ${cycleCounter}}}\n""")) } + // lsu enq + when(lsuProbe.reqEnq.orR)(printf(cf"""{"event":"lsuEnq","parameter":{"enq":${lsuProbe.reqEnq},"cycle": ${cycleCounter}}}\n""")) + } // Monitors // TODO: These monitors should be purged out after offline difftest is landed diff --git a/nix/overlay.nix b/nix/overlay.nix index 5db1f1a3ae..17dac14b31 100644 --- a/nix/overlay.nix +++ b/nix/overlay.nix @@ -23,7 +23,9 @@ in fetchMillDeps = final.callPackage ./pkgs/mill-builder.nix { }; circt-full = final.callPackage ./pkgs/circt-full.nix { }; rvv-codegen = final.callPackage ./pkgs/rvv-codegen.nix { }; - add-determinism = final.callPackage ./pkgs/add-determinism { }; # faster strip-undetereminism + add-determinism = final.callPackage ./pkgs/add-determinism { }; # faster strip-undetereminism + # difftest simulator + t1-simulator = final.callPackage ../difftest/t1-simulator { }; mill = let jre = final.jdk21; in (prev.mill.override { inherit jre; }).overrideAttrs (_: { diff --git a/nix/t1/default.nix b/nix/t1/default.nix index cefaf79ed0..dfeea63acb 100644 --- a/nix/t1/default.nix +++ b/nix/t1/default.nix @@ -51,7 +51,7 @@ lib.makeScope newScope elaborateConfigJson = configPath; elaborateConfig = builtins.fromJSON (lib.readFile configPath); - cases = innerSelf.callPackage ../../tests { }; + cases = innerSelf.callPackage ../../tests { ip-emu = ip.emu; }; # for the convenience to use x86 cases on non-x86 machines, avoiding the extra build time cases-x86 = diff --git a/script/default.nix b/script/default.nix index 389d968f4e..b83ee6eb12 100644 --- a/script/default.nix +++ b/script/default.nix @@ -31,7 +31,7 @@ let millDepsHash = "sha256-J8bBgM/F+8x8EQ1DR6Va/ZY2hnsjkkzk4a+ctDMKK3k="; }; - passthru.dev = self.overrideAttrs (old: { + passthru.withLsp = self.overrideAttrs (old: { nativeBuildInputs = old.nativeBuildInputs ++ [ metals # Metals require java to work correctly diff --git a/script/src/Main.scala b/script/src/Main.scala index 234f553b4b..427fb037ca 100644 --- a/script/src/Main.scala +++ b/script/src/Main.scala @@ -10,17 +10,17 @@ object Logger { val level = sys.env.getOrElse("LOG_LEVEL", "INFO") match case "TRACE" | "trace" => 0 case "ERROR" | "error" => 1 - case "WARN" | "warn" => 2 - case _ => 3 + case "INFO" | "info" => 2 + case _ => 4 + + def info(message: String) = + if level <= 2 then println(s"${BOLD}${GREEN}[INFO]${RESET} ${message}") - def info(message: String) = println( - s"${BOLD}${GREEN}[INFO]${RESET} ${message}" - ) def trace(message: String) = if level <= 0 then println(s"${BOLD}${GREEN}[TRACE]${RESET} ${message}") - def error(message: String) = println( - s"${BOLD}${RED}[ERROR]${RESET} ${message}" - ) + + def error(message: String) = + if level <= 2 then println(s"${BOLD}${RED}[ERROR]${RESET} ${message}") } object Main: @@ -84,10 +84,8 @@ object Main: def resolveElaborateConfig( configName: String ): os.Path = - if os.exists(os.Path(configName, os.pwd)) then - os.Path(configName) - else - os.pwd / "configgen" / "generated" / s"$configName.json" + if os.exists(os.Path(configName, os.pwd)) then os.Path(configName) + else os.pwd / "configgen" / "generated" / s"$configName.json" end resolveElaborateConfig def prepareOutputDir( @@ -98,7 +96,10 @@ object Main: caseName: String ): os.Path = val pathTail = - if os.exists(os.Path(caseName, os.pwd)) || os.exists(os.Path(config, os.pwd)) then + if os.exists(os.Path(caseName, os.pwd)) || os.exists( + os.Path(config, os.pwd) + ) + then // It is hard to canoncalize user specify path, so here we use date time instead val now = java.time.LocalDateTime .now() @@ -175,6 +176,10 @@ object Main: name = "emulator-log-file-path", doc = "Set the logging output path" ) emulatorLogFilePath: Option[os.Path] = None, + @arg( + name = "event-log-path", + doc = "Set the event log path" + ) eventLogFilePath: Option[os.Path] = None, @arg( name = "out-dir", doc = "path to save wave file and perf result file" @@ -223,6 +228,9 @@ object Main: val emulatorLogPath = if emulatorLogFilePath.isDefined then emulatorLogFilePath.get else outputPath / "emulator.log" + val eventLogPath = + if eventLogFilePath.isDefined then eventLogFilePath.get + else outputPath / "rtl-event.log" def dumpCycleAsFloat() = val ratio = dumpCycle.toFloat @@ -231,8 +239,7 @@ object Main: s"Can't use $dumpCycle as ratio, use 0 as waveform dump start point" ) 0 - else if ratio == 0.0 then - 0 + else if ratio == 0.0 then 0 else val cycleRecordFilePath = os.pwd / ".github" / "cases" / config / "default.json" @@ -254,8 +261,7 @@ object Main: scala.math.floor(cycle * 10 * ratio).toInt val dumpStartPoint: Int = - try - dumpCycle.toInt + try dumpCycle.toInt catch case _ => try dumpCycleAsFloat() @@ -317,13 +323,17 @@ object Main: Logger.info(s"Starting IP emulator: `${processArgs.mkString(" ")}`") if dryRun.value then return + if os.exists(eventLogPath) then + os.remove(eventLogPath) os.proc(processArgs) - .call(env = - Map( + .call( + env = Map( "EMULATOR_FILE_LOG_LEVEL" -> emulatorLogLevel, "EMULATOR_CONSOLE_LOG_LEVEL" -> emulatorLogLevel - ) + ), + stderr = eventLogPath, ) + Logger.info(s"RTL event log saved to ${eventLogPath}") if (!noFileLog.value) then Logger.info(s"Emulator log save to ${emulatorLogPath}") @@ -418,7 +428,8 @@ object Main: case (_, cycle) => cycle <= 0 // Initialize a list of buckets - val cargoInit = (0 until math.min(bucketSize, allCycleData.length)).map(_ => Bucket()) + val cargoInit = + (0 until math.min(bucketSize, allCycleData.length)).map(_ => Bucket()) // Group tests that have cycle data into subset by their cycle size val cargoStaged = normalData .sortBy(_._2)(Ordering[Int].reverse) @@ -439,8 +450,7 @@ object Main: cargo.updated(idx, newBucket) cargoFinal.map(_.buffer.mkString(";")).toSeq - else - cargoStaged.map(_.buffer.mkString(";")).toSeq + else cargoStaged.map(_.buffer.mkString(";")).toSeq end scheduleTasks // Turn Seq( "A;B", "C;D" ) to GitHub Action matrix style json: { "include": [ { "jobs": "A;B", id: 1 }, { "jobs": "C;D", id: 2 } ] } @@ -567,6 +577,7 @@ object Main: err => val outDir = testRunDir / config / caseName Logger.error(s"Test case $testName failed") + Logger.error(s"Detail error: $err") allFailedTest :+ testName os.write.over( @@ -580,8 +591,12 @@ object Main: os.write.over(actualResultDir / "failed-tests.md", listOfFailJobs) val failedJobsWithError = failed .map(testName => - s"* $testName\n >>> ERROR SUMMARY <<<\n${os - .read(actualResultDir / "failed-logs" / s"${testName.replaceAll(",", "-")}.txt")}" + val failLogPath = + actualResultDir / "failed-logs" / s"${testName.replaceAll(",", "-")}.txt" + if os.exists(failLogPath) then + s"* $testName\n >>> ERROR SUMMARY <<<\n${os.read(failLogPath)}" + else + s"* $testName\nNo error log found, error may not been caught in previous steps" ) .appended("") .mkString("\n") @@ -639,6 +654,18 @@ object Main: println(ujson.write(Map("config" -> testPlans))) end generateTestPlan + def nixResolvePath(attr: String): String = + os.proc( + "nix", + "build", + "--no-link", + "--no-warn-dirty", + "--print-out-paths", + attr + ).call() + .out + .trim() + @main def generateRegressionTestPlan(runnersAmount: Int): Unit = // Find emulator configs @@ -651,21 +678,9 @@ object Main: // but all we need is the name. path.segments.toSeq.reverse.drop(1).head - def nixBuild(attr: String): String = - os.proc( - "nix", - "build", - "--no-link", - "--no-warn-dirty", - "--print-out-paths", - attr - ).call() - .out - .trim() - import scala.util.chaining._ val testPlans: Seq[String] = emulatorConfigs.flatMap: configName => - val allCasesPath = nixBuild(s".#t1.$configName.cases.all") + val allCasesPath = nixResolvePath(s".#t1.$configName.cases.all") os.walk(os.Path(allCasesPath) / "configs") .filter: path => path.ext == "json" @@ -698,11 +713,59 @@ object Main: .toSeq .map(_.mkString(";")) - val finalTestPlan = (testPlans.toSet -- currentTestPlan.toSet -- perfCases.toSet).toSeq + val finalTestPlan = + (testPlans.toSet -- currentTestPlan.toSet -- perfCases.toSet).toSeq buckets(finalTestPlan, runnersAmount) .pipe(toMatrixJson) .pipe(println) end generateRegressionTestPlan + @main + def difftest( + @arg( + name = "config", + short = 'c', + doc = "specify the elaborate config for running test case" + ) config: String, + @arg( + name = "case-attr", + short = 'C', + doc = "Specify test case attribute to run diff test" + ) caseAttr: String + ): Unit = + Logger.info("Building simulator") + val difftest = nixResolvePath(".#t1-simulator") + + val fullCaseAttr = s".#t1.${config}.cases.${caseAttr}" + Logger.info(s"Building cases ${fullCaseAttr}") + val caseElf = nixResolvePath(fullCaseAttr) + + import scala.util.chaining._ + val configJson = nixResolvePath(s".#t1.${config}.elaborateConfigJson") + .pipe(p => os.Path(p)) + .pipe(p => os.read(p)) + .pipe(text => ujson.read(text)) + val dLen = configJson.obj("parameter").obj("dLen").num.toInt + val vLen = configJson.obj("parameter").obj("vLen").num.toInt + Logger.info(s"Using DLEN ${dLen}, VLEN ${vLen}") + + Logger.info(s"Running emulator to get event log") + val eventLog = nixResolvePath(s"${fullCaseAttr}.emu-result") + + os.proc( + Seq( + s"${difftest}/bin/t1-simulator", + "--vlen", + vLen.toString(), + "--dlen", + dLen.toString(), + "--elf-file", + s"${caseElf}/bin/${caseAttr}.elf", + "--log-file", + s"${eventLog}/rtl-event.log" + ) + ).call(stdout = os.Inherit, stderr = os.Inherit) + end difftest + def main(args: Array[String]): Unit = ParserForMethods(this).runOrExit(args) end Main diff --git a/tests/builder.nix b/tests/builder.nix index 69832ac210..441a131391 100644 --- a/tests/builder.nix +++ b/tests/builder.nix @@ -5,6 +5,13 @@ , elaborateConfig , isFp , vLen + + # ip-emu here is used for running the simulation to get event log for the specific test case. +, ip-emu + # t1-script contains many decent default for running the emulator, I don't want to replicate those simulation argument here. +, t1-script +, runCommand +, elaborateConfigJson }: # args from makeBuilder @@ -18,47 +25,59 @@ let # avoid adding jq to buildInputs, since it will make overriding buildInputs more error prone jqBin = "${jq}/bin/jq"; -in -stdenv.mkDerivation (self: rec { - # don't set name directory, since it will be suffixed with target triple - pname = "${casePrefix}.${caseName}"; - name = pname; + caseDrv = stdenv.mkDerivation (self: rec { + # don't set name directory, since it will be suffixed with target triple + pname = "${casePrefix}.${caseName}"; + name = pname; - CC = "${stdenv.targetPlatform.config}-cc"; + CC = "${stdenv.targetPlatform.config}-cc"; - NIX_CFLAGS_COMPILE = - let - march = (if isFp then "rv32gc_zve32f" else "rv32gc_zve32x") + NIX_CFLAGS_COMPILE = + let + march = (if isFp then "rv32gc_zve32f" else "rv32gc_zve32x") + "_zvl${toString (lib.min 1024 vLen)}b"; - in - [ - "-mabi=ilp32f" - "-march=${march}" - "-mno-relax" - "-static" - "-mcmodel=medany" - "-fvisibility=hidden" - "-fno-PIC" - "-g" - "-O3" - ]; + in + [ + "-mabi=ilp32f" + "-march=${march}" + "-mno-relax" + "-static" + "-mcmodel=medany" + "-fvisibility=hidden" + "-fno-PIC" + "-g" + "-O3" + ]; - installPhase = '' - runHook preInstall + installPhase = '' + runHook preInstall - mkdir -p $out/bin - cp ${pname}.elf $out/bin + mkdir -p $out/bin + cp ${pname}.elf $out/bin - ${jqBin} --null-input \ - --arg name ${pname} \ - --arg type ${casePrefix} \ - --arg elfPath "$out/bin/${pname}.elf" \ - '{ "name": $name, "elf": { "path": $elfPath } }' \ - > $out/${pname}.json + ${jqBin} --null-input \ + --arg name ${pname} \ + --arg type ${casePrefix} \ + --arg elfPath "$out/bin/${pname}.elf" \ + '{ "name": $name, "elf": { "path": $elfPath } }' \ + > $out/${pname}.json - runHook postInstall - ''; + runHook postInstall + ''; - dontFixup = true; -} // overrides) + dontFixup = true; + + passthru.emu-result = runCommand "get-event-log" { } '' + ${t1-script}/bin/t1-helper \ + "ipemu" \ + --emulator-path ${ip-emu}/bin/emulator \ + --config ${elaborateConfigJson} \ + --case ${caseDrv}/bin/${pname}.elf \ + --no-console-logging \ + --no-file-logging \ + --out-dir $out + ''; + } // overrides); +in +caseDrv diff --git a/tests/default.nix b/tests/default.nix index 9ab1cef9a4..795b14b877 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -3,6 +3,7 @@ , newScope , rv32-stdenv , runCommand +, ip-emu }: let @@ -17,6 +18,8 @@ let scope = lib.recurseIntoAttrs (lib.makeScope newScope (casesSelf: { recurseForDerivations = true; + inherit ip-emu; + makeBuilder = casesSelf.callPackage ./builder.nix { }; findAndBuild = dir: build: diff --git a/tests/t1_main.S b/tests/t1_main.S index 70f676b455..85ed6ac321 100644 --- a/tests/t1_main.S +++ b/tests/t1_main.S @@ -10,9 +10,6 @@ _start: call test // exit - li a0, 0x10000000 - li a1, -1 - sw a1, 4(a0) csrwi 0x7cc, 0 .p2align 2