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