From 1e6845d6763844580ce742f4f4104e6277aafc3d Mon Sep 17 00:00:00 2001 From: Shupei Fan Date: Tue, 10 Sep 2024 14:15:48 +0000 Subject: [PATCH 1/8] [profiler] add profiler code --- profiler/Cargo.lock | 348 +++++++++++++++++++++++++ profiler/Cargo.toml | 12 + profiler/src/input_hier.rs | 510 +++++++++++++++++++++++++++++++++++++ profiler/src/main.rs | 131 ++++++++++ profiler/src/vcd_util.rs | 220 ++++++++++++++++ 5 files changed, 1221 insertions(+) create mode 100644 profiler/Cargo.lock create mode 100644 profiler/Cargo.toml create mode 100644 profiler/src/input_hier.rs create mode 100644 profiler/src/main.rs create mode 100644 profiler/src/vcd_util.rs diff --git a/profiler/Cargo.lock b/profiler/Cargo.lock new file mode 100644 index 000000000..25b587fde --- /dev/null +++ b/profiler/Cargo.lock @@ -0,0 +1,348 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" + +[[package]] +name = "clap" +version = "4.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "env_filter" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiler" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "env_logger", + "itertools", + "log", + "vcd", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "vcd" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4505774fc84de82eb04caa74d859342b0daa376f4c6df45149593245dd62c004" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/profiler/Cargo.toml b/profiler/Cargo.toml new file mode 100644 index 000000000..0f1b2bd5c --- /dev/null +++ b/profiler/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "profiler" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.87" +clap = { version = "4.5.17", features = ["derive"] } +env_logger = "0.11.5" +itertools = "0.13.0" +log = "0.4.22" +vcd = "0.7.0" diff --git a/profiler/src/input_hier.rs b/profiler/src/input_hier.rs new file mode 100644 index 000000000..93c22b4c5 --- /dev/null +++ b/profiler/src/input_hier.rs @@ -0,0 +1,510 @@ +use std::{cell::OnceCell, collections::HashMap, ops::Range, rc::Rc}; + +use itertools::izip; +use vcd::IdCode; + +pub enum SignalData { + Scalar { + data: Rc>>, + }, + Vector { + width: u32, + data: Rc>>, + }, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct ValueRecord { + pub cycle: u32, + pub is_x: bool, + pub value: T, +} + +pub struct SVar { + code: IdCode, + name: String, + record: OnceCell>>>, +} + +impl SVar { + fn get_record(&self, cycle: u32) -> &ValueRecord { + let records = self.records(); + let idx = records.partition_point(|c| c.cycle <= cycle); + assert!(idx > 0, "cycle={cycle} is before FIRST_CYCLE"); + &records[idx - 1] + } + pub fn get_may_x(&self, cycle: u32) -> Option { + let r = self.get_record(cycle); + if r.is_x { + None + } else { + Some(r.value) + } + } + + #[track_caller] + pub fn get(&self, cycle: u32) -> bool { + let r = self.get_record(cycle); + assert!(!r.is_x, "signal '{}' is X at cycle={cycle}", self.name); + r.value + } + pub fn records(&self) -> &[ValueRecord] { + self.record.get().expect("record not set") + } + pub fn debug_print(&self) { + match self.record.get() { + None => println!("signal {} not set", self.code), + Some(record) => { + println!("signal {}:", self.code); + for r in &record[..] { + let value = match (r.is_x, r.value) { + (true, _) => "x", + (false, true) => "1", + (false, false) => "0", + }; + println!("- {} : {}", r.cycle, value); + } + } + } + } +} + +pub struct VVar { + code: IdCode, + name: String, + width: u32, + records: OnceCell>>>, +} + +impl VVar { + fn get_record(&self, cycle: u32) -> &ValueRecord { + let records = self.records(); + let idx = records.partition_point(|c| c.cycle <= cycle); + assert!(idx > 0, "cycle={cycle} is before FIRST_CYCLE"); + &records[idx - 1] + } + pub fn get(&self, cycle: u32) -> Option { + let r = self.get_record(cycle); + if r.is_x { + None + } else { + Some(r.value) + } + } + + #[track_caller] + pub fn get_u8(&self, cycle: u32) -> u8 { + assert_eq!(self.width, 8); + let r = self.get_record(cycle); + assert!(!r.is_x, "signal '{}' is X at cycle={cycle}", self.name); + r.value as u8 + } + + #[track_caller] + pub fn get_u8_w(&self, cycle: u32, width: u32) -> u8 { + assert_eq!(self.width, width); + let r = self.get_record(cycle); + assert!(!r.is_x, "signal '{}' is X at cycle={cycle}", self.name); + r.value as u8 + } + + #[track_caller] + pub fn get_u32(&self, cycle: u32) -> u32 { + assert_eq!(self.width, 32); + let r = self.get_record(cycle); + assert!(!r.is_x, "signal '{}' is X at cycle={cycle}", self.name); + r.value as u32 + } + + pub fn records(&self) -> &[ValueRecord] { + self.records.get().expect("record not set") + } +} + +fn s(vars: &HashMap)>, name: &str) -> SVar { + let (code, width) = *vars + .get(name) + .unwrap_or_else(|| panic!("unable to find var '{name}'")); + assert!(width.is_none()); + + SVar { + code, + name: name.into(), + record: OnceCell::new(), + } +} + +fn v(vars: &HashMap)>, name: &str, width: u32) -> VVar { + let (code, width_) = *vars + .get(name) + .unwrap_or_else(|| panic!("unable to find var '{name}'")); + assert_eq!(width_, Some(width)); + + VVar { + code, + name: name.into(), + width, + records: OnceCell::new(), + } +} + +fn ct<'a>(c: &mut VarCollector<'a>, s: &'a impl Collect) { + Collect::collect_to(s, c); +} + +#[derive(Default)] +pub struct VarCollector<'a> { + vars: Vec>, +} + +impl<'a> VarCollector<'a> { + pub fn new() -> Self { + Self::default() + } + pub fn id_list(&self) -> Vec<(IdCode, Option)> { + self.vars + .iter() + .map(|var| match var { + VarRef::SVar(svar) => (svar.code, None), + VarRef::VVar(vvar) => (vvar.code, Some(vvar.width)), + }) + .collect() + } + pub fn set_with_signal_map(&self, signal_map: &HashMap) { + for &var in &self.vars { + match var { + VarRef::SVar(var) => match &signal_map[&var.code] { + SignalData::Scalar { data } => { + var.record + .set(data.clone()) + .expect("signal record already set"); + } + SignalData::Vector { .. } => unreachable!(), + }, + VarRef::VVar(var) => match &signal_map[&var.code] { + SignalData::Vector { width, data } => { + assert_eq!(*width, var.width); + var.records + .set(data.clone()) + .expect("signal record already set"); + } + SignalData::Scalar { .. } => unreachable!(), + }, + } + } + } +} + +#[derive(Clone, Copy)] +enum VarRef<'a> { + SVar(&'a SVar), + VVar(&'a VVar), +} + +pub trait Collect { + fn collect_to<'a>(&'a self, c: &mut VarCollector<'a>); +} + +impl Collect for SVar { + fn collect_to<'a>(&'a self, c: &mut VarCollector<'a>) { + c.vars.push(VarRef::SVar(self)); + } +} + +impl Collect for VVar { + fn collect_to<'a>(&'a self, c: &mut VarCollector<'a>) { + c.vars.push(VarRef::VVar(self)); + } +} + +pub struct InputVars { + pub issue_enq: IssueEnq, + pub issue_deq: IssueDeq, + pub issue_req_deq: IssueRegDeq, +} + +impl Collect for InputVars { + fn collect_to<'a>(&'a self, c: &mut VarCollector<'a>) { + ct(c, &self.issue_enq); + ct(c, &self.issue_deq); + ct(c, &self.issue_req_deq); + } +} + +pub struct IssueEnq { + pub valid: SVar, + pub ready: SVar, + pub pc: VVar, + pub inst: VVar, + pub rs1: VVar, + pub rs2: VVar, +} + +impl Collect for IssueEnq { + fn collect_to<'a>(&'a self, c: &mut VarCollector<'a>) { + ct(c, &self.valid); + ct(c, &self.ready); + ct(c, &self.pc); + ct(c, &self.inst); + ct(c, &self.rs1); + ct(c, &self.rs2); + } +} + +pub struct IssueDeq { + pub valid: SVar, + pub ready: SVar, + pub inst: VVar, + pub rs1: VVar, + pub rs2: VVar, +} + +impl Collect for IssueDeq { + fn collect_to<'a>(&'a self, c: &mut VarCollector<'a>) { + ct(c, &self.valid); + ct(c, &self.ready); + ct(c, &self.inst); + ct(c, &self.rs1); + ct(c, &self.rs2); + } +} + +pub struct IssueRegDeq { + pub valid: SVar, + pub ready: SVar, + pub inst_idx: VVar, + pub inst: VVar, + pub rs1: VVar, + pub rs2: VVar, +} + +impl Collect for IssueRegDeq { + fn collect_to<'a>(&'a self, c: &mut VarCollector<'a>) { + ct(c, &self.valid); + ct(c, &self.ready); + ct(c, &self.inst_idx); + ct(c, &self.inst); + ct(c, &self.rs1); + ct(c, &self.rs2); + } +} + +impl InputVars { + pub fn from_vars(vars: &HashMap)>) -> Self { + InputVars { + issue_enq: IssueEnq { + valid: s(vars, "t1IssueEnq_valid"), + ready: s(vars, "t1IssueEnq_ready"), + pc: v(vars, "t1IssueEnqPc", 32), + inst: v(vars, "t1IssueEnq_bits_instruction", 32), + rs1: v(vars, "t1IssueEnq_bits_rs1Data", 32), + rs2: v(vars, "t1IssueEnq_bits_rs2Data", 32), + }, + issue_deq: IssueDeq { + valid: s(vars, "t1IssueDeq_valid"), + ready: s(vars, "t1IssueDeq_ready"), + inst: v(vars, "t1IssueDeq_bits_instruction", 32), + rs1: v(vars, "t1IssueDeq_bits_rs1Data", 32), + rs2: v(vars, "t1IssueDeq_bits_rs2Data", 32), + }, + issue_req_deq: IssueRegDeq { + valid: s(vars, "t1IssueRegDeq_valid"), + ready: s(vars, "t1IssueRegDeqReady"), + inst_idx: v(vars, "t1IssueRegDeq_bits_instructionIndex", 3), + inst: v(vars, "t1IssueRegDeq_bits_issue_instruction", 32), + rs1: v(vars, "t1IssueRegDeq_bits_issue_rs1Data", 32), + rs2: v(vars, "t1IssueRegDeq_bits_issue_rs2Data", 32), + }, + } + } + + pub fn collect(&self) -> VarCollector<'_> { + let mut c = VarCollector::new(); + self.collect_to(&mut c); + c + } +} + +pub trait CompatibleWith { + fn compatible_with(&self, u: &U); +} + +pub fn compatible(t: &T, u: &U) +where + T: CompatibleWith, +{ + t.compatible_with(u); +} + +pub struct IssueEnqData { + cycle: u32, + pc: u32, + inst: u32, + rs1: u32, + rs2: u32, +} + +pub struct IssueDeqData { + cycle: u32, + inst: u32, + rs1: u32, + rs2: u32, +} + +pub struct IssueRegDeqData { + cycle: u32, + inst_idx: u8, + inst: u32, + rs1: u32, + rs2: u32, +} + +impl CompatibleWith for IssueEnqData { + fn compatible_with(&self, u: &IssueDeqData) { + let t = self; + assert_eq!(t.inst, u.inst); + assert_eq!(t.rs1, u.rs1); + assert_eq!(t.rs2, u.rs2); + assert!(t.cycle < u.cycle); + } +} + +impl CompatibleWith for IssueDeqData { + fn compatible_with(&self, u: &IssueRegDeqData) { + let t = self; + assert_eq!(t.inst, u.inst); + assert_eq!(t.rs1, u.rs1); + assert_eq!(t.rs2, u.rs2); + assert!(t.cycle < u.cycle); + } +} +pub struct IssueData { + cycle_enq: u32, + cycle_deq: u32, + cycle_reg_deq: u32, + inst_idx: u8, + pc: u32, + inst: u32, + rs1: u32, + rs2: u32, +} + +pub fn process(vars: &InputVars, range: Range) { + let enq_data = process_issue_enq(&vars.issue_enq, range.clone()); + let deq_data = process_issue_deq(&vars.issue_deq, range.clone()); + let reg_deq_data = process_issue_reg_deq(&vars.issue_req_deq, range.clone()); + assert_eq!(enq_data.len(), deq_data.len()); + assert_eq!(enq_data.len(), reg_deq_data.len()); + + // combine IssueEnq and IssueDeq + let mut issue_data = vec![]; + for (enq, deq, reg_deq) in izip!(&enq_data, &deq_data, ®_deq_data) { + compatible(enq, deq); + compatible(deq, reg_deq); + + issue_data.push(IssueData { + cycle_enq: enq.cycle, + cycle_deq: deq.cycle, + cycle_reg_deq: reg_deq.cycle, + inst_idx: reg_deq.inst_idx, + pc: enq.pc, + inst: enq.inst, + rs1: enq.rs1, + rs2: enq.rs2, + }) + } + + for &IssueData { + cycle_enq, + cycle_deq, + cycle_reg_deq, + inst_idx, + pc, + inst, + rs1, + rs2, + } in &issue_data + { + let diff_deq_enq = cycle_deq - cycle_enq; + let diff_reg_deq_enq = cycle_reg_deq - cycle_enq; + println!("- enq={cycle_enq:5}, idx={inst_idx}, deq-enq={diff_deq_enq:3}, reg_deq-enq={diff_reg_deq_enq:3}, pc = 0x{pc:08x}, inst = 0x{inst:08x}, rs1 = 0x{rs1:08x}, rs2 = 0x{rs2:08x}"); + } +} + +fn process_issue_enq(vars: &IssueEnq, range: Range) -> Vec { + let mut data = vec![]; + for cycle in range { + match (vars.valid.get(cycle), vars.ready.get(cycle)) { + (true, true) => { + let pc = vars.pc.get_u32(cycle); + let inst = vars.inst.get_u32(cycle); + let rs1 = vars.rs1.get_u32(cycle); + let rs2 = vars.rs2.get_u32(cycle); + data.push(IssueEnqData { + cycle, + pc, + inst, + rs1, + rs2, + }); + } + (_, _) => {} + } + } + data +} + +fn process_issue_deq(vars: &IssueDeq, range: Range) -> Vec { + let mut data = vec![]; + for cycle in range { + match (vars.valid.get(cycle), vars.ready.get(cycle)) { + (true, true) => { + let inst = vars.inst.get_u32(cycle); + let rs1 = vars.rs1.get_u32(cycle); + let rs2 = vars.rs2.get_u32(cycle); + data.push(IssueDeqData { + cycle, + inst, + rs1, + rs2, + }); + } + (_, _) => {} + } + } + data +} + +fn process_issue_reg_deq(vars: &IssueRegDeq, range: Range) -> Vec { + let mut data = vec![]; + for cycle in range { + match (vars.valid.get(cycle), vars.ready.get(cycle)) { + (true, true) => { + let inst_idx = vars.inst_idx.get_u8_w(cycle, 3); + let inst = vars.inst.get_u32(cycle); + let rs1 = vars.rs1.get_u32(cycle); + let rs2 = vars.rs2.get_u32(cycle); + data.push(IssueRegDeqData { + cycle, + inst_idx, + inst, + rs1, + rs2, + }); + } + (_, _) => {} + } + } + data +} + +mod op { + pub fn and(op1: Option, op2: Option) -> Option { + match (op1, op2) { + (Some(true), Some(true)) => Some(true), + (Some(false), _) => Some(false), + (_, Some(false)) => Some(false), + _ => None, + } + } +} diff --git a/profiler/src/main.rs b/profiler/src/main.rs new file mode 100644 index 000000000..d057d79f6 --- /dev/null +++ b/profiler/src/main.rs @@ -0,0 +1,131 @@ +use std::{ + collections::{hash_map, HashMap}, + fs::File, + io::{BufRead, BufReader}, + path::PathBuf, +}; + +use anyhow::{anyhow, ensure}; +use clap::Parser; +use input_hier::InputVars; +use vcd::IdCode; +use vcd_util::{ + collect_vars, process_signal_map, process_vcd, time_to_cycle, ProcessReport, RawValueRecord, + Value, FIRST_CYCLE, RESET_DEASSERT_TIME, +}; + +mod input_hier; + +mod vcd_util; + +#[derive(Parser)] +struct Cli { + input_vcd: PathBuf, +} + +fn main() -> anyhow::Result<()> { + let cli = Cli::parse(); + env_logger::builder().format_timestamp(None).init(); + + let input = File::open(cli.input_vcd)?; + + let mut input = vcd::Parser::new(BufReader::new(input)); + let header = input.parse_header()?; + + ensure!(header.timescale == Some((1, vcd::TimescaleUnit::PS))); + + let prof_scope = header + .find_scope(&["TestBench", "verification", "profData"]) + .ok_or_else(|| anyhow!("can not find scope /TestBench/profData"))?; + + // Though `Scope` has `find_var` method, however it uses linear search. + // Collect to a hashmap to avoid quadratic behavior. + let vars = collect_vars(prof_scope); + + let input_vars = InputVars::from_vars(&vars); + let c = input_vars.collect(); + + let clock = vars["clock"]; + let reset = vars["reset"]; + let mut clock_values = vec![]; + let mut reset_values = vec![]; + assert_eq!(clock.1, None); + assert_eq!(reset.1, None); + + let mut signal_map = HashMap::, Vec)>::new(); + for (code, width) in c.id_list() { + match signal_map.entry(code) { + hash_map::Entry::Occupied(e) => assert_eq!(e.get().0, width), + hash_map::Entry::Vacant(e) => { + e.insert((width, vec![])); + } + } + } + + let ProcessReport { max_time } = process_vcd(&mut input, |time, code, value| { + if code == clock.0 { + match value { + Value::Scalar(value) => clock_values.push(RawValueRecord { + time, + is_x: value.is_none(), + value: value.unwrap_or(false) as u32, + }), + _ => unreachable!(), + } + } + + if code == reset.0 { + match value { + Value::Scalar(value) => reset_values.push(RawValueRecord { + time, + is_x: value.is_none(), + value: value.unwrap_or(false) as u32, + }), + _ => unreachable!(), + } + } + + if let Some(v) = signal_map.get_mut(&code) { + match value { + Value::Scalar(value) => { + assert_eq!(v.0, None); + v.1.push(RawValueRecord { + time, + is_x: value.is_none(), + value: value.unwrap_or(false) as u32, + }); + } + Value::Vector { width, data } => { + assert_eq!(v.0, Some(width as u32)); + v.1.push(RawValueRecord { + time, + is_x: data.is_none(), + value: data.unwrap_or(0), + }); + } + } + } + })?; + + let max_cycle = time_to_cycle(max_time); + log::info!("first_cycle = {FIRST_CYCLE}"); + log::info!("max_cycle = {max_cycle}"); + + assert_eq!(reset_values.len(), 2); + assert_eq!( + reset_values[1], + RawValueRecord { + time: RESET_DEASSERT_TIME, + is_x: false, + value: 0, + } + ); + + let signal_map = process_signal_map(signal_map); + + c.set_with_signal_map(&signal_map); + + let _ = input_hier::process(&input_vars, FIRST_CYCLE..max_cycle + 1); + + Ok(()) +} diff --git a/profiler/src/vcd_util.rs b/profiler/src/vcd_util.rs new file mode 100644 index 000000000..ef3432d9c --- /dev/null +++ b/profiler/src/vcd_util.rs @@ -0,0 +1,220 @@ +use std::{collections::HashMap, io::BufRead, rc::Rc}; + +use vcd::{IdCode, ScopeItem}; + +use crate::input_hier::{SignalData, ValueRecord}; + +pub fn time_to_cycle(time: u64) -> u32 { + let cycle = (time / 20000) as u32; + assert_eq!(time, 20000 * (cycle as u64) + 10000); + cycle +} + +// sync with t1rocketemu/TestBench +pub const RESET_DEASSERT_TIME: u64 = 100000; +pub const FIRST_CYCLE: u32 = 5; + +pub fn process_signal_map( + signal_map: HashMap, Vec)>, +) -> HashMap { + signal_map + .into_iter() + .map(|(code, (width, data))| { + let data = match width { + None => { + let mut started = false; + let mut last_value = (true, 0); + let mut data2 = vec![]; + for x in data { + if x.time >= RESET_DEASSERT_TIME { + let cycle = time_to_cycle(x.time); + if !started { + started = true; + if cycle > FIRST_CYCLE { + data2.push(ValueRecord { + cycle: FIRST_CYCLE, + is_x: last_value.0, + value: last_value.1 != 0, + }); + } + } + data2.push(ValueRecord { + cycle, + is_x: x.is_x, + value: x.value != 0, + }); + } else { + last_value = (x.is_x, x.value); + } + } + if !started { + data2.push(ValueRecord { + cycle: FIRST_CYCLE, + is_x: last_value.0, + value: last_value.1 != 0, + }); + } + SignalData::Scalar { + data: Rc::new(data2), + } + } + Some(width) => { + let mut started = false; + let mut last_value = (true, 0); + let mut data2 = vec![]; + for x in data { + if x.time >= RESET_DEASSERT_TIME { + let cycle = time_to_cycle(x.time); + if !started { + started = true; + if cycle > FIRST_CYCLE { + data2.push(ValueRecord { + cycle: FIRST_CYCLE, + is_x: last_value.0, + value: last_value.1 as u64, + }); + } + } + data2.push(ValueRecord { + cycle, + is_x: x.is_x, + value: x.value as u64, + }); + } else { + last_value = (x.is_x, x.value); + } + } + if !started { + data2.push(ValueRecord { + cycle: FIRST_CYCLE, + is_x: last_value.0, + value: last_value.1 as u64, + }); + } + SignalData::Vector { + width, + data: Rc::new(data2), + } + } + }; + (code, data) + }) + .collect() +} + +pub fn collect_vars(scope: &vcd::Scope) -> HashMap)> { + let mut signals = HashMap::new(); + for item in &scope.items { + match item { + ScopeItem::Var(var) => { + let name = &var.reference; + let width = match var.index { + None => { + // scalar + assert_eq!(var.size, 1); + None + } + Some(index) => { + // vector + assert_eq!(index, vcd::ReferenceIndex::Range(var.size as i32 - 1, 0)); + Some(var.size) + } + }; + + signals.insert(name.into(), (var.code, width)); + } + ScopeItem::Scope(_) => { + // ignore subscopes + } + _ => {} + } + } + + signals +} + +#[derive(Debug, PartialEq, Eq)] +pub struct RawValueRecord { + pub time: u64, + pub is_x: bool, + pub value: u32, +} + +#[derive(Debug)] +pub enum Value { + Scalar(Option), + Vector { width: u8, data: Option }, +} + +impl Value { + fn from_scalar(value: vcd::Value) -> Self { + let value = match value { + vcd::Value::V1 => Some(true), + vcd::Value::V0 => Some(false), + _ => None, + }; + Value::Scalar(value) + } + + fn from_vector(value: vcd::Vector) -> Self { + let width = value.len().try_into().unwrap(); + assert!(1 <= width && width <= 32); + + let mut data: u32 = 0; + + // value[0] is MSB, value[width-1] is LSB + // here we reverse the bit order + for s in &value { + match s { + vcd::Value::V1 => { + data = (data << 1) + 1; + } + vcd::Value::V0 => { + data = data << 1; + } + _ => return Value::Vector { width, data: None }, + } + } + + Value::Vector { + width, + data: Some(data), + } + } +} + +pub struct ProcessReport { + pub max_time: u64, +} + +// return max time +pub fn process_vcd( + vcd: &mut vcd::Parser, + mut callback: Callback, +) -> anyhow::Result +where + Callback: FnMut(u64, vcd::IdCode, Value), +{ + let mut time: u64 = 0; + while let Some(cmd) = vcd.next().transpose()? { + // println!("{cmd:?}"); + match cmd { + vcd::Command::Begin(_) | vcd::Command::End(_) => { + // ignored + } + vcd::Command::Timestamp(new_time) => { + time = new_time; + } + vcd::Command::ChangeScalar(id, value) => { + callback(time, id, Value::from_scalar(value)); + } + vcd::Command::ChangeVector(id, value) => { + callback(time, id, Value::from_vector(value)); + } + _ => { + unreachable!("unexpected command: {cmd:?}") + } + } + } + Ok(ProcessReport { max_time: time }) +} From a760e8bf7fdc3df36d3dc9e9d04c8112d13f697e Mon Sep 17 00:00:00 2001 From: Shupei Fan Date: Mon, 28 Oct 2024 09:52:48 +0000 Subject: [PATCH 2/8] [profiler] add disasm support --- profiler/src/disasm.rs | 62 +++++++++++ profiler/src/input_hier.rs | 213 ++++++++++++++++++++++++++++--------- profiler/src/main.rs | 4 +- 3 files changed, 229 insertions(+), 50 deletions(-) create mode 100644 profiler/src/disasm.rs diff --git a/profiler/src/disasm.rs b/profiler/src/disasm.rs new file mode 100644 index 000000000..73ea34d09 --- /dev/null +++ b/profiler/src/disasm.rs @@ -0,0 +1,62 @@ +use std::{ + io::{BufRead as _, BufReader, Write as _}, + process::{ChildStdin, ChildStdout, Command, Stdio}, +}; + +pub struct DisasmDefault { + inner: Option, +} + +impl DisasmDefault { + pub fn new() -> DisasmDefault { + // currently we utilize spike-dasm to disassemble RISCV instructions, + // the path of spike-dasm is passed by env SPIKE_DASM. + match std::env::var("SPIKE_DASM") { + Ok(spike_dasm_path) => DisasmDefault { + inner: Some(Disasm::new(&spike_dasm_path)), + }, + Err(_e) => DisasmDefault { inner: None }, + } + } + + pub fn disasm(&mut self, inst: u32) -> String { + match &mut self.inner { + Some(inner) => inner.disasm(inst), + None => "".into(), + } + } +} + +pub struct Disasm { + _child: std::process::Child, + stdin: ChildStdin, + stdout: BufReader, +} + +impl Disasm { + pub fn new(spike_dasm_path: &str) -> Self { + let mut child = Command::new(spike_dasm_path) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + let stdin = child.stdin.take().unwrap(); + let stdout = BufReader::new(child.stdout.take().unwrap()); + Disasm { + _child: child, + stdin, + stdout, + } + } + + pub fn disasm(&mut self, inst: u32) -> String { + let input = format!("DASM(0x{inst:08x})\n"); + self.stdin.write_all(input.as_bytes()).unwrap(); + self.stdin.flush().unwrap(); + + let mut buf = String::new(); + self.stdout.read_line(&mut buf).unwrap(); + + buf.trim().into() + } +} diff --git a/profiler/src/input_hier.rs b/profiler/src/input_hier.rs index 93c22b4c5..a1075e1af 100644 --- a/profiler/src/input_hier.rs +++ b/profiler/src/input_hier.rs @@ -3,6 +3,8 @@ use std::{cell::OnceCell, collections::HashMap, ops::Range, rc::Rc}; use itertools::izip; use vcd::IdCode; +use crate::disasm::Disasm; + pub enum SignalData { Scalar { data: Rc>>, @@ -108,6 +110,17 @@ impl VVar { r.value as u8 } + #[track_caller] + pub fn get_option_u32(&self, cycle: u32) -> Option { + assert_eq!(self.width, 32); + let r = self.get_record(cycle); + if !r.is_x { + Some(r.value as u32) + } else { + None + } + } + #[track_caller] pub fn get_u32(&self, cycle: u32) -> u32 { assert_eq!(self.width, 32); @@ -238,6 +251,10 @@ pub struct IssueEnq { pub inst: VVar, pub rs1: VVar, pub rs2: VVar, + pub vtype: VVar, + pub vl: VVar, + pub vstart: VVar, + pub vcsr: VVar, } impl Collect for IssueEnq { @@ -248,6 +265,10 @@ impl Collect for IssueEnq { ct(c, &self.inst); ct(c, &self.rs1); ct(c, &self.rs2); + ct(c, &self.vtype); + ct(c, &self.vl); + ct(c, &self.vstart); + ct(c, &self.vcsr); } } @@ -257,6 +278,10 @@ pub struct IssueDeq { pub inst: VVar, pub rs1: VVar, pub rs2: VVar, + pub vtype: VVar, + pub vl: VVar, + pub vstart: VVar, + pub vcsr: VVar, } impl Collect for IssueDeq { @@ -266,6 +291,10 @@ impl Collect for IssueDeq { ct(c, &self.inst); ct(c, &self.rs1); ct(c, &self.rs2); + ct(c, &self.vtype); + ct(c, &self.vl); + ct(c, &self.vstart); + ct(c, &self.vcsr); } } @@ -276,6 +305,10 @@ pub struct IssueRegDeq { pub inst: VVar, pub rs1: VVar, pub rs2: VVar, + pub vtype: VVar, + pub vl: VVar, + pub vstart: VVar, + pub vcsr: VVar, } impl Collect for IssueRegDeq { @@ -286,6 +319,10 @@ impl Collect for IssueRegDeq { ct(c, &self.inst); ct(c, &self.rs1); ct(c, &self.rs2); + ct(c, &self.vtype); + ct(c, &self.vl); + ct(c, &self.vstart); + ct(c, &self.vcsr); } } @@ -299,6 +336,10 @@ impl InputVars { inst: v(vars, "t1IssueEnq_bits_instruction", 32), rs1: v(vars, "t1IssueEnq_bits_rs1Data", 32), rs2: v(vars, "t1IssueEnq_bits_rs2Data", 32), + vtype: v(vars, "t1IssueEnq_bits_vtype", 32), + vl: v(vars, "t1IssueEnq_bits_vl", 32), + vstart: v(vars, "t1IssueEnq_bits_vstart", 32), + vcsr: v(vars, "t1IssueEnq_bits_vcsr", 32), }, issue_deq: IssueDeq { valid: s(vars, "t1IssueDeq_valid"), @@ -306,6 +347,10 @@ impl InputVars { inst: v(vars, "t1IssueDeq_bits_instruction", 32), rs1: v(vars, "t1IssueDeq_bits_rs1Data", 32), rs2: v(vars, "t1IssueDeq_bits_rs2Data", 32), + vtype: v(vars, "t1IssueDeq_bits_vtype", 32), + vl: v(vars, "t1IssueDeq_bits_vl", 32), + vstart: v(vars, "t1IssueDeq_bits_vstart", 32), + vcsr: v(vars, "t1IssueDeq_bits_vcsr", 32), }, issue_req_deq: IssueRegDeq { valid: s(vars, "t1IssueRegDeq_valid"), @@ -314,6 +359,10 @@ impl InputVars { inst: v(vars, "t1IssueRegDeq_bits_issue_instruction", 32), rs1: v(vars, "t1IssueRegDeq_bits_issue_rs1Data", 32), rs2: v(vars, "t1IssueRegDeq_bits_issue_rs2Data", 32), + vtype: v(vars, "t1IssueRegDeq_bits_issue_vtype", 32), + vl: v(vars, "t1IssueRegDeq_bits_issue_vl", 32), + vstart: v(vars, "t1IssueRegDeq_bits_issue_vstart", 32), + vcsr: v(vars, "t1IssueRegDeq_bits_issue_vcsr", 32), }, } } @@ -336,35 +385,68 @@ where t.compatible_with(u); } +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct InstData { + inst: u32, + rs1: Option, + rs2: Option, + vtype: u32, + vl: u32, + vstart: u32, + vcsr: u32, +} + +impl InstData { + pub fn csr_description(&self) -> String { + assert!(self.vtype <= 0xFF); + let sew = match (self.vtype >> 3) & 0x07 { + 0 => 8, + 1 => 16, + 2 => 32, + 3 => 64, + _ => unreachable!(), + }; + let lmul = match self.vtype & 0x07 { + 0 => "1", + 1 => "2", + 2 => "4", + 3 => "8", + 5 => "1/8", + 6 => "1/4", + 7 => "1/2", + _ => unreachable!(), + }; + let mut buf = format!("sew={sew:<2}, lmul={lmul}, vl={}", self.vl); + + if self.vstart != 0 { + buf += &format!(", vstart={}", self.vstart); + } + + buf + } +} + pub struct IssueEnqData { cycle: u32, pc: u32, - inst: u32, - rs1: u32, - rs2: u32, + inst_data: InstData, } pub struct IssueDeqData { cycle: u32, - inst: u32, - rs1: u32, - rs2: u32, + inst_data: InstData, } pub struct IssueRegDeqData { cycle: u32, inst_idx: u8, - inst: u32, - rs1: u32, - rs2: u32, + inst_data: InstData, } impl CompatibleWith for IssueEnqData { fn compatible_with(&self, u: &IssueDeqData) { let t = self; - assert_eq!(t.inst, u.inst); - assert_eq!(t.rs1, u.rs1); - assert_eq!(t.rs2, u.rs2); + assert_eq!(t.inst_data, u.inst_data); assert!(t.cycle < u.cycle); } } @@ -372,9 +454,7 @@ impl CompatibleWith for IssueEnqData { impl CompatibleWith for IssueDeqData { fn compatible_with(&self, u: &IssueRegDeqData) { let t = self; - assert_eq!(t.inst, u.inst); - assert_eq!(t.rs1, u.rs1); - assert_eq!(t.rs2, u.rs2); + assert_eq!(t.inst_data, u.inst_data); assert!(t.cycle < u.cycle); } } @@ -384,9 +464,7 @@ pub struct IssueData { cycle_reg_deq: u32, inst_idx: u8, pc: u32, - inst: u32, - rs1: u32, - rs2: u32, + inst_data: InstData, } pub fn process(vars: &InputVars, range: Range) { @@ -408,26 +486,35 @@ pub fn process(vars: &InputVars, range: Range) { cycle_reg_deq: reg_deq.cycle, inst_idx: reg_deq.inst_idx, pc: enq.pc, - inst: enq.inst, - rs1: enq.rs1, - rs2: enq.rs2, + inst_data: enq.inst_data, }) } - for &IssueData { - cycle_enq, - cycle_deq, - cycle_reg_deq, - inst_idx, - pc, - inst, - rs1, - rs2, - } in &issue_data + let mut dis = Disasm::new(); + for ( + idx, + &IssueData { + cycle_enq, + cycle_deq, + cycle_reg_deq, + inst_idx, + pc, + inst_data, + }, + ) in issue_data.iter().enumerate() { - let diff_deq_enq = cycle_deq - cycle_enq; + let InstData { inst, rs1, rs2, .. } = inst_data; let diff_reg_deq_enq = cycle_reg_deq - cycle_enq; - println!("- enq={cycle_enq:5}, idx={inst_idx}, deq-enq={diff_deq_enq:3}, reg_deq-enq={diff_reg_deq_enq:3}, pc = 0x{pc:08x}, inst = 0x{inst:08x}, rs1 = 0x{rs1:08x}, rs2 = 0x{rs2:08x}"); + let disasm = dis.disasm(inst); + let vcsr_desc = inst_data.csr_description(); + + let mut q_len = 0; + while q_len < idx && issue_data[idx - q_len].cycle_reg_deq > cycle_enq { + q_len += 1; + } + let rs1 = rs1.unwrap_or(0xFFFFFFFF); + let rs2 = rs2.unwrap_or(0xFFFFFFFF); + println!("[PC=0x{pc:08x}, inst=0x{inst:08x}] {disasm:<24} |({inst_idx}) q_len={q_len:2}, enq={cycle_enq:5}, reg_deq-enq={diff_reg_deq_enq:4}, rs1 = 0x{rs1:08x}, rs2 = 0x{rs2:08x} | {vcsr_desc}"); } } @@ -438,14 +525,24 @@ fn process_issue_enq(vars: &IssueEnq, range: Range) -> Vec { (true, true) => { let pc = vars.pc.get_u32(cycle); let inst = vars.inst.get_u32(cycle); - let rs1 = vars.rs1.get_u32(cycle); - let rs2 = vars.rs2.get_u32(cycle); + let rs1 = vars.rs1.get_option_u32(cycle); + let rs2 = vars.rs2.get_option_u32(cycle); + let vtype = vars.vtype.get_u32(cycle); + let vl = vars.vl.get_u32(cycle); + let vstart = vars.vstart.get_u32(cycle); + let vcsr = vars.vcsr.get_u32(cycle); data.push(IssueEnqData { cycle, pc, - inst, - rs1, - rs2, + inst_data: InstData { + inst, + rs1, + rs2, + vtype, + vl, + vstart, + vcsr, + }, }); } (_, _) => {} @@ -460,13 +557,23 @@ fn process_issue_deq(vars: &IssueDeq, range: Range) -> Vec { match (vars.valid.get(cycle), vars.ready.get(cycle)) { (true, true) => { let inst = vars.inst.get_u32(cycle); - let rs1 = vars.rs1.get_u32(cycle); - let rs2 = vars.rs2.get_u32(cycle); + let rs1 = vars.rs1.get_option_u32(cycle); + let rs2 = vars.rs2.get_option_u32(cycle); + let vtype = vars.vtype.get_u32(cycle); + let vl = vars.vl.get_u32(cycle); + let vstart = vars.vstart.get_u32(cycle); + let vcsr = vars.vcsr.get_u32(cycle); data.push(IssueDeqData { cycle, - inst, - rs1, - rs2, + inst_data: InstData { + inst, + rs1, + rs2, + vtype, + vl, + vstart, + vcsr, + }, }); } (_, _) => {} @@ -482,14 +589,24 @@ fn process_issue_reg_deq(vars: &IssueRegDeq, range: Range) -> Vec { let inst_idx = vars.inst_idx.get_u8_w(cycle, 3); let inst = vars.inst.get_u32(cycle); - let rs1 = vars.rs1.get_u32(cycle); - let rs2 = vars.rs2.get_u32(cycle); + let rs1 = vars.rs1.get_option_u32(cycle); + let rs2 = vars.rs2.get_option_u32(cycle); + let vtype = vars.vtype.get_u32(cycle); + let vl = vars.vl.get_u32(cycle); + let vstart = vars.vstart.get_u32(cycle); + let vcsr = vars.vcsr.get_u32(cycle); data.push(IssueRegDeqData { cycle, inst_idx, - inst, - rs1, - rs2, + inst_data: InstData { + inst, + rs1, + rs2, + vtype, + vl, + vstart, + vcsr, + }, }); } (_, _) => {} diff --git a/profiler/src/main.rs b/profiler/src/main.rs index d057d79f6..1f8fc70d0 100644 --- a/profiler/src/main.rs +++ b/profiler/src/main.rs @@ -1,7 +1,7 @@ use std::{ collections::{hash_map, HashMap}, fs::File, - io::{BufRead, BufReader}, + io::BufReader, path::PathBuf, }; @@ -14,8 +14,8 @@ use vcd_util::{ Value, FIRST_CYCLE, RESET_DEASSERT_TIME, }; +mod disasm; mod input_hier; - mod vcd_util; #[derive(Parser)] From fef54d5700037c47e9fad23c6139c64e1a62d2fa Mon Sep 17 00:00:00 2001 From: Shupei Fan Date: Thu, 31 Oct 2024 14:58:53 +0000 Subject: [PATCH 3/8] [profiler] output to directory --- profiler/src/input_hier.rs | 75 +++++++++++++++++++------------------- profiler/src/main.rs | 20 +++++++++- 2 files changed, 56 insertions(+), 39 deletions(-) diff --git a/profiler/src/input_hier.rs b/profiler/src/input_hier.rs index a1075e1af..e2528f367 100644 --- a/profiler/src/input_hier.rs +++ b/profiler/src/input_hier.rs @@ -1,9 +1,16 @@ -use std::{cell::OnceCell, collections::HashMap, ops::Range, rc::Rc}; +use std::{ + cell::OnceCell, + collections::HashMap, + io::{BufWriter, Write as _}, + ops::Range, + path::Path, + rc::Rc, +}; use itertools::izip; use vcd::IdCode; -use crate::disasm::Disasm; +use crate::disasm::DisasmDefault; pub enum SignalData { Scalar { @@ -467,7 +474,7 @@ pub struct IssueData { inst_data: InstData, } -pub fn process(vars: &InputVars, range: Range) { +pub fn process(vars: &InputVars, range: Range, output: &Path) { let enq_data = process_issue_enq(&vars.issue_enq, range.clone()); let deq_data = process_issue_deq(&vars.issue_deq, range.clone()); let reg_deq_data = process_issue_reg_deq(&vars.issue_req_deq, range.clone()); @@ -490,31 +497,36 @@ pub fn process(vars: &InputVars, range: Range) { }) } - let mut dis = Disasm::new(); - for ( - idx, - &IssueData { - cycle_enq, - cycle_deq, - cycle_reg_deq, - inst_idx, - pc, - inst_data, - }, - ) in issue_data.iter().enumerate() { - let InstData { inst, rs1, rs2, .. } = inst_data; - let diff_reg_deq_enq = cycle_reg_deq - cycle_enq; - let disasm = dis.disasm(inst); - let vcsr_desc = inst_data.csr_description(); - - let mut q_len = 0; - while q_len < idx && issue_data[idx - q_len].cycle_reg_deq > cycle_enq { - q_len += 1; + // generate inst_map.txt + + let inst_map = std::fs::File::create(output.join("inst_map.txt")).unwrap(); + let mut dis = DisasmDefault::new(); + for ( + idx, + &IssueData { + cycle_enq, + cycle_deq, + cycle_reg_deq, + inst_idx, + pc, + inst_data, + }, + ) in issue_data.iter().enumerate() + { + let InstData { inst, rs1, rs2, .. } = inst_data; + let diff_reg_deq_enq = cycle_reg_deq - cycle_enq; + let disasm = dis.disasm(inst); + let vcsr_desc = inst_data.csr_description(); + + let mut q_len = 0; + while q_len < idx && issue_data[idx - q_len].cycle_reg_deq > cycle_enq { + q_len += 1; + } + let rs1 = rs1.unwrap_or(0xFFFFFFFF); + let rs2 = rs2.unwrap_or(0xFFFFFFFF); + writeln!(&inst_map, "[PC=0x{pc:08x}, inst=0x{inst:08x}] {disasm:<24} |({inst_idx}) q_len={q_len:2}, enq={cycle_enq:5}, reg_deq-enq={diff_reg_deq_enq:4}, rs1 = 0x{rs1:08x}, rs2 = 0x{rs2:08x} | {vcsr_desc}").unwrap(); } - let rs1 = rs1.unwrap_or(0xFFFFFFFF); - let rs2 = rs2.unwrap_or(0xFFFFFFFF); - println!("[PC=0x{pc:08x}, inst=0x{inst:08x}] {disasm:<24} |({inst_idx}) q_len={q_len:2}, enq={cycle_enq:5}, reg_deq-enq={diff_reg_deq_enq:4}, rs1 = 0x{rs1:08x}, rs2 = 0x{rs2:08x} | {vcsr_desc}"); } } @@ -614,14 +626,3 @@ fn process_issue_reg_deq(vars: &IssueRegDeq, range: Range) -> Vec, op2: Option) -> Option { - match (op1, op2) { - (Some(true), Some(true)) => Some(true), - (Some(false), _) => Some(false), - (_, Some(false)) => Some(false), - _ => None, - } - } -} diff --git a/profiler/src/main.rs b/profiler/src/main.rs index 1f8fc70d0..f41dc1239 100644 --- a/profiler/src/main.rs +++ b/profiler/src/main.rs @@ -2,7 +2,7 @@ use std::{ collections::{hash_map, HashMap}, fs::File, io::BufReader, - path::PathBuf, + path::{Path, PathBuf}, }; use anyhow::{anyhow, ensure}; @@ -21,6 +21,7 @@ mod vcd_util; #[derive(Parser)] struct Cli { input_vcd: PathBuf, + output: PathBuf, } fn main() -> anyhow::Result<()> { @@ -125,7 +126,22 @@ fn main() -> anyhow::Result<()> { c.set_with_signal_map(&signal_map); - let _ = input_hier::process(&input_vars, FIRST_CYCLE..max_cycle + 1); + create_dir(&cli.output)?; + + let _ = input_hier::process(&input_vars, FIRST_CYCLE..max_cycle + 1, &cli.output); Ok(()) } + +fn create_dir(path: &Path) -> anyhow::Result<()> { + match std::fs::create_dir(path) { + Ok(()) => Ok(()), + Err(e) => { + if e.kind() == std::io::ErrorKind::AlreadyExists { + Ok(()) + } else { + Err(e.into()) + } + } + } +} From 1b6d5346400947c2e4880f7001322484c7074f84 Mon Sep 17 00:00:00 2001 From: Shupei Fan Date: Tue, 10 Sep 2024 09:05:54 +0000 Subject: [PATCH 4/8] [t1rocketemu] add profiler probe --- rocketv/src/RocketCore.scala | 9 +++++++++ t1/src/T1.scala | 31 ++++++++++++++++++------------- t1rocket/src/T1RocketTile.scala | 9 +++++++++ t1rocketemu/src/TestBench.scala | 26 ++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 13 deletions(-) diff --git a/rocketv/src/RocketCore.scala b/rocketv/src/RocketCore.scala index bb43c153c..5436960ae 100644 --- a/rocketv/src/RocketCore.scala +++ b/rocketv/src/RocketCore.scala @@ -51,6 +51,10 @@ class RocketProbe(param: RocketParameter) extends Bundle { val idle: Bool = Bool() // fpu score board val fpuScoreboard: Option[FPUScoreboardProbe] = Option.when(param.usingFPU)(new FPUScoreboardProbe) + + val wbRegPc: UInt = UInt(param.iBufParameter.vaddrBitsExtended.W) + val t1IssueEnq: Option[DecoupledIO[T1Issue]] = + Option.when(param.usingT1)(DecoupledIO(new T1Issue(param.xLen, param.vLen))) } object RocketParameter { @@ -1636,6 +1640,11 @@ class Rocket(val parameter: RocketParameter) probeWire.isVectorWrite := t1RetireQueue.map(q => q.deq.fire).getOrElse(false.B) probeWire.idle := vectorEmpty + probeWire.wbRegPc := wbRegPc + probeWire.t1IssueEnq.foreach { case t1IssueEnq => + t1IssueEnq := t1IssueQueue.enq + } + probeWire.fpuScoreboard.foreach { case fpProbe => fpProbe.memSetScoreBoard := wbValid && wbDcacheMiss && wbRegDecodeOutput(parameter.decoderParameter.wfd) fpProbe.vectorSetScoreBoard := wbValid && wbRegDecodeOutput(parameter.decoderParameter.wfd) && Option diff --git a/t1/src/T1.scala b/t1/src/T1.scala index b87b3d043..6a78a2b7c 100644 --- a/t1/src/T1.scala +++ b/t1/src/T1.scala @@ -325,23 +325,26 @@ case class T1Parameter( } class T1Probe(parameter: T1Parameter) extends Bundle { - val instructionCounter: UInt = UInt(parameter.instructionIndexBits.W) - val instructionIssue: Bool = Bool() - val issueTag: UInt = UInt(parameter.instructionIndexBits.W) - val retireValid: Bool = Bool() + val instructionCounter: UInt = UInt(parameter.instructionIndexBits.W) + val instructionIssue: Bool = Bool() + val issueTag: UInt = UInt(parameter.instructionIndexBits.W) + val retireValid: Bool = Bool() + // for profiler + val requestReg: ValidIO[InstructionPipeBundle] = ValidIO(new InstructionPipeBundle(parameter)) + val requestRegReady: Bool = Bool() // write queue enq for mask unit - val writeQueueEnq: ValidIO[UInt] = Valid(UInt(parameter.instructionIndexBits.W)) - val writeQueueEnqMask: UInt = UInt((parameter.datapathWidth / 8).W) + val writeQueueEnq: ValidIO[UInt] = Valid(UInt(parameter.instructionIndexBits.W)) + val writeQueueEnqMask: UInt = UInt((parameter.datapathWidth / 8).W) // mask unit instruction valid - val instructionValid: UInt = UInt((parameter.chainingSize * 2).W) + val instructionValid: UInt = UInt((parameter.chainingSize * 2).W) // instruction index for check rd - val responseCounter: UInt = UInt(parameter.instructionIndexBits.W) + val responseCounter: UInt = UInt(parameter.instructionIndexBits.W) // probes - val lsuProbe: LSUProbe = new LSUProbe(parameter.lsuParameters) - val laneProbes: Vec[LaneProbe] = Vec(parameter.laneNumber, new LaneProbe(parameter.laneParam)) - val issue: ValidIO[UInt] = Valid(UInt(parameter.instructionIndexBits.W)) - val retire: ValidIO[UInt] = Valid(UInt(parameter.xLen.W)) - val idle: Bool = Bool() + val lsuProbe: LSUProbe = new LSUProbe(parameter.lsuParameters) + val laneProbes: Vec[LaneProbe] = Vec(parameter.laneNumber, new LaneProbe(parameter.laneParam)) + val issue: ValidIO[UInt] = Valid(UInt(parameter.instructionIndexBits.W)) + val retire: ValidIO[UInt] = Valid(UInt(parameter.xLen.W)) + val idle: Bool = Bool() } class T1Interface(parameter: T1Parameter) extends Record { @@ -1778,6 +1781,8 @@ class T1(val parameter: T1Parameter) probeWire.instructionIssue := requestRegDequeue.fire probeWire.issueTag := requestReg.bits.instructionIndex probeWire.retireValid := retire + probeWire.requestReg := requestReg + probeWire.requestRegReady := requestRegDequeue.ready // maskUnitWrite maskUnitWriteReady probeWire.writeQueueEnq.valid := maskUnitWrite.valid && maskUnitWriteReady probeWire.writeQueueEnq.bits := maskUnitWrite.bits.instructionIndex diff --git a/t1rocket/src/T1RocketTile.scala b/t1rocket/src/T1RocketTile.scala index 8b2fbc520..222289819 100644 --- a/t1rocket/src/T1RocketTile.scala +++ b/t1rocket/src/T1RocketTile.scala @@ -31,6 +31,9 @@ import org.chipsalliance.rvdecoderdb.Instruction import org.chipsalliance.t1.rtl.vrf.RamType import org.chipsalliance.t1.rtl.vrf.RamType.{p0rp1w, p0rw, p0rwp1rw} import org.chipsalliance.t1.rtl.{T1, T1Parameter, T1Probe, VFUInstantiateParameter} +import chisel3.util.DecoupledIO +import org.chipsalliance.t1.rtl.T1Issue +import org.chipsalliance.rocketv.T1Retire object T1RocketTileParameter { implicit def bitSetP: upickle.default.ReadWriter[BitSet] = upickle.default @@ -405,6 +408,9 @@ class T1RocketProbe(parameter: T1RocketTileParameter) extends Bundle { val rocketProbe: RocketProbe = Output(new RocketProbe(parameter.rocketParameter)) val fpuProbe: Option[FPUProbe] = parameter.fpuParameter.map(param => Output(new FPUProbe(param))) val t1Probe: T1Probe = Output(new T1Probe(parameter.t1Parameter)) + + val t1IssueDeq: DecoupledIO[T1Issue] = DecoupledIO(new T1Issue(parameter.xLen, parameter.vLen)) + val t1Retire: T1Retire = Output(new T1Retire(parameter.xLen)) } class T1RocketTileInterface(parameter: T1RocketTileParameter) extends Bundle { @@ -555,5 +561,8 @@ class T1RocketTile(val parameter: T1RocketTileParameter) probeWire.fpuProbe.foreach { fpuProbe => fpuProbe := probe.read(fpu.get.io.fpuProbe) } + + probeWire.t1IssueDeq := t1.io.issue + probeWire.t1Retire := t1.io.retire } } diff --git a/t1rocketemu/src/TestBench.scala b/t1rocketemu/src/TestBench.scala index 35adc1911..b9a208bac 100644 --- a/t1rocketemu/src/TestBench.scala +++ b/t1rocketemu/src/TestBench.scala @@ -14,6 +14,8 @@ import chisel3.util.{HasExtModuleInline, PopCount, UIntToOH, Valid} import org.chipsalliance.amba.axi4.bundle._ import org.chipsalliance.t1.t1rocketemu.dpi._ import org.chipsalliance.t1.tile.{T1RocketTile, T1RocketTileParameter} +import org.chipsalliance.t1.rtl.T1Probe +import org.chipsalliance.t1.tile.T1RocketProbe @instantiable class TestBenchOM extends Class { @@ -353,4 +355,28 @@ class TestBench(val parameter: T1RocketTileParameter) when(quitFlag && t1Probe.idle && rocketProbe.idle) { stop(cf"""{"event":"SimulationEnd", "cycle":${simulationTime}}\n""") } + + // t1rocket ProfData + layer.block(layers.Verification) { + val profData = Module(new Module { + override def desiredName: String = "ProfData" + val probe = IO(Input(new T1RocketProbe(parameter))) + + val t1IssueEnqPc = WireInit(probe.rocketProbe.wbRegPc) + val t1IssueEnq = WireInit(probe.rocketProbe.t1IssueEnq.get) + val t1IssueDeq = WireInit(probe.t1IssueDeq) + val t1IssueRegDeq = WireInit(probe.t1Probe.requestReg) + val t1IssueRegDeqReady = WireInit(probe.t1Probe.requestRegReady) + val t1Retire = WireInit(probe.t1Retire) + + dontTouch(this.clock) + dontTouch(this.reset) + dontTouch(t1IssueEnq) + dontTouch(t1IssueDeq) + dontTouch(t1IssueRegDeq) + dontTouch(t1IssueRegDeqReady) + dontTouch(t1Retire) + }) + profData.probe := t1RocketProbe + } } From 741938b06272e8f5161b13808b8660349341921d Mon Sep 17 00:00:00 2001 From: Shupei Fan Date: Mon, 28 Oct 2024 09:51:44 +0000 Subject: [PATCH 5/8] [nix] add profiler --- nix/t1/default.nix | 1 + profiler/default.nix | 13 +++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 profiler/default.nix diff --git a/nix/t1/default.nix b/nix/t1/default.nix index 1672fd33f..73fc8b81a 100644 --- a/nix/t1/default.nix +++ b/nix/t1/default.nix @@ -55,6 +55,7 @@ lib.makeScope newScope elaborator = t1Scope._t1MillModules.elaborator // { meta.mainProgram = "elaborator"; }; omreader-unwrapped = t1Scope._t1MillModules.omreader // { meta.mainProgram = "omreader"; }; t1package = t1Scope._t1MillModules.t1package; + profiler = t1Scope.callPackage ../../profiler { }; # --------------------------------------------------------------------------------- # Lowering utilities diff --git a/profiler/default.nix b/profiler/default.nix new file mode 100644 index 000000000..417596e82 --- /dev/null +++ b/profiler/default.nix @@ -0,0 +1,13 @@ +{ lib +, rustPlatform +, rustfmt +}: + +rustPlatform.buildRustPackage { + src = ./.; + name = "profiler"; + + cargoLock = { + lockFile = ./Cargo.lock; + }; +} From 8cc49527a88d8b56b2ed2776b0fd8748490b1c1e Mon Sep 17 00:00:00 2001 From: Shupei Fan Date: Wed, 30 Oct 2024 08:18:30 +0000 Subject: [PATCH 6/8] [nix] expand vcs-fhs-env to support fsdb2vcd --- nix/pkgs/vcs-fhs-env.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nix/pkgs/vcs-fhs-env.nix b/nix/pkgs/vcs-fhs-env.nix index 97aa336b8..1d39da806 100644 --- a/nix/pkgs/vcs-fhs-env.nix +++ b/nix/pkgs/vcs-fhs-env.nix @@ -76,6 +76,8 @@ lockedPkgs.buildFHSEnv { expat sqlite nssmdns + fontconfig + numactl (krb5.overrideAttrs rec { version = "1.18.2"; src = fetchurl { @@ -113,6 +115,8 @@ lockedPkgs.buildFHSEnv { xorg.libXrender xorg.libXcomposite xorg.libXi + xorg.libXt + xorg.libXmu zlib ]); } From 846f00a4288a4a3ba33bd385a0eb31e17d7fab33 Mon Sep 17 00:00:00 2001 From: Shupei Fan Date: Wed, 30 Oct 2024 08:45:45 +0000 Subject: [PATCH 7/8] [nix] add vcs-prof-vcd derivation --- nix/t1/run/default.nix | 2 ++ nix/t1/run/run-fsdb2vcd.nix | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 nix/t1/run/run-fsdb2vcd.nix diff --git a/nix/t1/run/default.nix b/nix/t1/run/default.nix index 252f08574..922dbf02c 100644 --- a/nix/t1/run/default.nix +++ b/nix/t1/run/default.nix @@ -12,6 +12,7 @@ let runVerilatorEmu = callPackage ./run-verilator-emu.nix { }; runVCSEmu = callPackage ./run-vcs-emu.nix { }; + runFsdb2vcd = callPackage ./run-fsdb2vcd.nix { }; # cases is now { mlir = { hello = ...; ... }; ... } emuAttrs = lib.pipe cases [ @@ -32,6 +33,7 @@ let verilator-emu-trace = runVerilatorEmu verilator-emu-trace case; vcs-emu = runVCSEmu vcs-emu case; vcs-emu-trace = runVCSEmu vcs-emu-trace case; + vcs-prof-vcd = runFsdb2vcd (runVCSEmu vcs-emu-trace case); }; in # Now we have { caseName = "hello", case = } diff --git a/nix/t1/run/run-fsdb2vcd.nix b/nix/t1/run/run-fsdb2vcd.nix new file mode 100644 index 000000000..2ea642597 --- /dev/null +++ b/nix/t1/run/run-fsdb2vcd.nix @@ -0,0 +1,22 @@ +{ lib, stdenvNoCC, vcs-fhs-env }: +vcs-emu-trace: + +let + caseName = vcs-emu-trace.caseName; +in +stdenvNoCC.mkDerivation (finalAttr: { + name = "${caseName}-vcs-prof-vcd"; + + __noChroot = true; + + dontUnpack = true; + + buildCommand = '' + mkdir -p "$out" + + fhsEnv="${vcs-fhs-env}/bin/vcs-fhs-env" + "$fhsEnv" -c "fsdb2vcd ${vcs-emu-trace}/*.fsdb -o ${caseName}.prof.vcd -s /TestBench/verification/profData" + + cp -t "$out" "${caseName}.prof.vcd" + ''; +}) From 5c1d27c56ceb266a1c87751806c1914a3c29c869 Mon Sep 17 00:00:00 2001 From: Shupei Fan Date: Thu, 31 Oct 2024 14:59:19 +0000 Subject: [PATCH 8/8] [profiler] add README.md --- profiler/README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 profiler/README.md diff --git a/profiler/README.md b/profiler/README.md new file mode 100644 index 000000000..61d8e4274 --- /dev/null +++ b/profiler/README.md @@ -0,0 +1,32 @@ +# Usage + +## Prepare Prof Input Vcd + +This step is integrated into nix build flow. + +e.g., the following command will generate prof vcd for case 'codegen.vadd_vv' to directory 'result-prof-vcd' +``` +nix build --impure .#t1.blastoise.t1rocketemu.run.codegen.vadd_vv.vcs-prof-vcd -o result-prof-vcd +``` + +NOTE: only 't1rocketemu' is supported + +## Run profiler + +``` +nix run .#t1.profiler +``` + +e.g. run `nix run .#t1.profiler result-prof-vcd/*.vcd prof-result`, +the profiler will generate various analysis results in 'prof-result' directory. + +### Disassemble Support + +The profiler depends on `spike-dasm` to do disassemble and +you need to set path of 'spike-dasm' to environment variable `SPIKE_DASM` + +``` +export SPIKE_DASM=$(nix build --print-out-paths .#spike)/bin/spike-dasm +``` + +If `SPIKE_DASM` is not set, all disassembles in `inst_map.txt` become ''.