1use std::collections::BTreeSet;
5use std::str::FromStr;
6use std::{collections::HashMap, io};
7use std::{fs, hash, path};
8
9use clap::Parser;
10use rayon::prelude::*;
11use stats::PackedStats;
12use wellen::{self, Hierarchy, ScopeRef, SignalRef, Var, VarRef, simple::Waveform};
13
14mod exporters;
15pub mod netlist;
16pub mod stats;
17pub mod util;
18
19use netlist::Netlist;
20use util::VarRefsIter;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23struct HashVarRef(VarRef);
24
25impl hash::Hash for HashVarRef {
26 fn hash<H: hash::Hasher>(&self, state: &mut H) {
27 self.0.index().hash(state);
28 }
29}
30
31#[derive(Parser)]
33pub struct Args {
34 pub input_file: path::PathBuf,
36 #[arg(short, long, value_parser = clap::value_parser!(f64))]
38 pub clk_freq: f64,
39 #[arg(long)]
41 pub clock_name: Option<String>,
42 #[arg(short = 'f', long, default_value = "tcl")]
44 pub output_format: OutputFormat,
45 #[arg(long, short)]
47 pub limit_scope: Option<String>,
48 #[arg(long)]
51 pub limit_scope_power: Option<String>,
52 #[arg(short, long)]
55 pub netlist: Option<path::PathBuf>,
56 #[arg(short, long)]
58 pub top: Option<String>,
59 #[arg(short = 'T', long)]
61 pub top_scope: Option<String>,
62 #[arg(short, long)]
65 pub blackboxes_only: bool,
66 #[arg(long)]
68 pub remove_virtual_pins: bool,
69 #[arg(short, long)]
72 pub output: Option<path::PathBuf>,
73 #[arg(long)]
75 pub ignore_date: bool,
76 #[arg(long)]
78 pub ignore_version: bool,
79 #[arg(long)]
81 pub per_clock_cycle: bool,
82 #[arg(long)]
84 pub only_glitches: bool,
85 #[arg(long)]
87 pub export_empty: bool,
88 #[arg(long)]
90 pub input_ports_activity: bool,
91}
92
93impl Args {
94 pub fn from_cli() -> Self {
95 Args::parse()
96 }
97}
98
99fn indexed_name(mut name: String, variable: &Var) -> String {
100 if let Some(idx) = variable.index() {
101 name += format!("[{}]", idx.lsb()).as_str();
102 }
103 name
104}
105
106fn get_scope_by_full_name(hier: &Hierarchy, scope_str: &str) -> Option<ScopeRef> {
107 hier.lookup_scope(scope_str.split('.').collect::<Vec<_>>().as_slice())
108}
109
110#[derive(Copy, Clone)]
113enum LookupPoint {
114 Top,
115 Scope(ScopeRef),
116}
117
118#[derive(Copy, Clone)]
119pub enum OutputFormat {
120 Tcl,
121 Saif,
122}
123
124impl clap::ValueEnum for OutputFormat {
125 fn value_variants<'a>() -> &'a [Self] {
126 &[Self::Tcl, Self::Saif]
127 }
128 fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
129 use clap::builder::PossibleValue;
130 match self {
131 Self::Tcl => Some(PossibleValue::new("tcl")),
132 Self::Saif => Some(PossibleValue::new("saif")),
133 }
134 }
135}
136
137impl FromStr for OutputFormat {
138 type Err = io::Error;
139 fn from_str(s: &str) -> Result<Self, Self::Err> {
140 match s.to_lowercase().as_str() {
141 "tcl" => Ok(Self::Tcl),
142 "saif" => Ok(Self::Saif),
143 other @ _ => Err(io::Error::new(
144 io::ErrorKind::InvalidInput,
145 format!(
146 "Format {} is not a valid output format forthis program",
147 other
148 ),
149 )),
150 }
151 }
152}
153
154struct Context {
155 wave: Waveform,
156 clk_period: f64,
157 stats: HashMap<HashVarRef, Vec<PackedStats>>,
158 pub num_of_iterations: u64,
159 lookup_point: LookupPoint,
160 output_fmt: OutputFormat,
161 scope_prefix_length: usize,
162 netlist: Option<Netlist>,
163 top: String,
164 top_scope: Option<ScopeRef>,
165 blackboxes_only: bool,
166 remove_virtual_pins: bool,
167 ignore_date: bool,
168 ignore_version: bool,
169 export_empty: bool,
170 power_scope_prefix: String,
171 input_ports_activity: bool,
172}
173
174impl Context {
175 pub fn build_from_args(args: &Args) -> Self {
176 const LOAD_OPTS: wellen::LoadOptions = wellen::LoadOptions {
177 multi_thread: true,
178 remove_scopes_with_empty_name: false,
179 };
180
181 let mut wave = wellen::simple::read_with_options(
182 args.input_file
183 .to_str()
184 .expect("Arguments should contain a path to input trace file"),
185 &LOAD_OPTS,
186 )
187 .expect("Waveform parsing should end successfully");
188
189 let wave_hierarchy = wave.hierarchy();
190
191 let clk_period = 1.0_f64 / args.clk_freq;
192 let timescale = wave_hierarchy
193 .timescale()
194 .expect("Trace file should contain a timescale");
195 let timescale_norm = (timescale.factor as f64)
196 * (10.0_f64).powf(
197 timescale
198 .unit
199 .to_exponent()
200 .expect("Waveform should contain time unit") as f64,
201 );
202
203 let lookup_point = match &args.limit_scope {
204 None => LookupPoint::Top,
205 Some(scope_str) => LookupPoint::Scope(
206 get_scope_by_full_name(wave_hierarchy, scope_str)
207 .expect("Requested scope not found"),
208 ),
209 };
210
211 let lookup_scope_name_prefix = match lookup_point {
212 LookupPoint::Top => "".to_string(),
213 LookupPoint::Scope(scope_ref) => {
214 let scope = &wave_hierarchy[scope_ref];
215 scope.full_name(wave_hierarchy).to_string() + "."
216 }
217 };
218
219 let (all_vars, all_signals): (Vec<_>, Vec<_>) = match lookup_point {
220 LookupPoint::Top => wave_hierarchy
221 .var_refs_iter()
222 .map(|var_ref| (var_ref, wave_hierarchy[var_ref].signal_ref()))
223 .unzip(),
224 LookupPoint::Scope(_) => wave_hierarchy
225 .var_refs_iter()
226 .map(|var_ref| (var_ref, &wave_hierarchy[var_ref]))
227 .filter(|(_, var)| {
228 let fname = indexed_name(var.full_name(wave_hierarchy.into()), var);
229 fname.starts_with(&lookup_scope_name_prefix)
230 })
231 .map(|(var_ref, var)| (var_ref, var.signal_ref()))
232 .unzip(),
233 };
234
235 let all_signals_power: BTreeSet<_> = match &args.limit_scope_power {
236 None => wave_hierarchy
237 .var_refs_iter()
238 .map(|var_ref| &wave_hierarchy[var_ref])
239 .map(|var| indexed_name(var.full_name(wave_hierarchy.into()), var))
240 .collect::<BTreeSet<_>>(),
241 Some(scope_str) => wave_hierarchy
242 .var_refs_iter()
243 .map(|var_ref| &wave_hierarchy[var_ref])
244 .filter(|var| {
245 let fname = indexed_name(var.full_name(wave_hierarchy.into()), var);
246 fname.starts_with(scope_str)
247 })
248 .map(|var| indexed_name(var.full_name(wave_hierarchy.into()), &var))
249 .collect::<BTreeSet<_>>(),
250 };
251
252 let clk_signal: Option<SignalRef> = match &args.clock_name {
253 None => None,
254 Some(clock_name) => {
255 let mut found: Option<SignalRef> = None;
256
257 for var_ref in wave_hierarchy.var_refs_iter() {
258 let net = &wave_hierarchy[var_ref];
259 let sig_ref = net.signal_ref();
260 if net.name(wave_hierarchy) == clock_name {
261 found = Some(sig_ref)
262 }
263 }
264
265 found
266 }
267 };
268
269 wave.load_signals_multi_threaded(&all_signals);
271
272 let last_time_stamp = *wave
273 .time_table()
274 .last()
275 .expect("Given waveform shouldn't be empty");
276 let num_of_iterations = if args.per_clock_cycle {
277 (last_time_stamp as f64 * timescale_norm / clk_period) as u64
278 } else {
279 1
280 };
281
282 let stats: HashMap<HashVarRef, Vec<stats::PackedStats>> = all_vars
287 .par_iter()
288 .zip(all_signals)
289 .map(|(var_ref, sig_ref)| {
290 let fname = indexed_name(
291 wave.hierarchy()[*var_ref].full_name(wave.hierarchy().into()),
292 &wave.hierarchy()[*var_ref],
293 );
294 if args.limit_scope_power.is_none() || all_signals_power.contains(&fname) {
295 return (
296 HashVarRef(*var_ref),
297 stats::calc_stats_for_each_time_span(
298 &wave,
299 args.only_glitches,
300 clk_signal,
301 sig_ref,
302 num_of_iterations,
303 ),
304 );
305 }
306 (
307 HashVarRef(*var_ref),
308 vec![stats::empty_stats(&wave, sig_ref); num_of_iterations as usize],
309 )
310 })
311 .collect();
312
313 let top_scope = args.top_scope.as_ref().map(|s| {
314 get_scope_by_full_name(wave.hierarchy(), s)
315 .unwrap_or_else(|| panic!("Couldn't find top scope `{}`", s))
316 });
317
318 Self {
319 wave,
320 clk_period,
321 stats,
322 num_of_iterations,
323 lookup_point,
324 output_fmt: args.output_format,
325 scope_prefix_length: lookup_scope_name_prefix.len(),
326 netlist: args.netlist.as_ref().map(|path| {
327 let f = fs::File::open(path).expect("Couldn't open the netlist file");
328 let reader = io::BufReader::new(f);
329 serde_json::from_reader::<_, Netlist>(reader)
330 .expect("Couldn't parse the netlist file")
331 }),
332 top: args.top.clone().unwrap_or_else(String::new),
333 top_scope,
334 blackboxes_only: args.blackboxes_only,
335 remove_virtual_pins: args.remove_virtual_pins,
336 ignore_date: args.ignore_date,
337 ignore_version: args.ignore_version,
338 export_empty: args.export_empty,
339 power_scope_prefix: args
340 .limit_scope_power
341 .clone()
342 .unwrap_or_else(|| lookup_scope_name_prefix),
343 input_ports_activity: args.input_ports_activity,
344 }
345 }
346}
347
348pub fn process(args: Args) {
349 let ctx = Context::build_from_args(&args);
350 if ctx.num_of_iterations > 1 {
351 process_trace_iterations(&ctx, args.output);
352 } else {
353 process_single_iteration_trace(&ctx, args.output);
354 }
355}
356
357fn process_trace(ctx: &Context, out: impl io::Write, iteration: usize) {
358 match &ctx.output_fmt {
359 OutputFormat::Tcl => exporters::tcl::export(&ctx, out, iteration),
360 OutputFormat::Saif => exporters::saif::export(&ctx, out, iteration),
361 }
362 .expect("Output format should be either 'tcl' or 'saif'")
363}
364
365fn process_trace_iterations(ctx: &Context, output_path: Option<path::PathBuf>) {
366 if let Some(mut path) = output_path {
367 for iteration in 0..ctx.num_of_iterations as usize {
369 path.push(format!("{:05}", iteration));
370 let f = fs::File::create(&path).expect("Created file should be valid");
371 let writer = io::BufWriter::new(f);
372 process_trace(&ctx, writer, iteration);
373 path.pop();
374 }
375 } else {
376 for iteration in 0..ctx.num_of_iterations as usize {
377 println!("{1} Iteration {:05} {1}", iteration, str::repeat("-", 10));
378 process_trace(&ctx, io::stdout(), iteration);
379 }
380 }
381}
382
383fn process_single_iteration_trace(ctx: &Context, output_path: Option<path::PathBuf>) {
384 match output_path {
385 None => process_trace(&ctx, io::stdout(), 0),
386 Some(ref path) => {
387 let f = fs::File::create(path).expect("Created file should be valid");
388 let writer = io::BufWriter::new(f);
389 process_trace(&ctx, writer, 0);
390 }
391 }
392}