1use std::cmp::Reverse;
7use std::collections::{BTreeMap, BinaryHeap};
8use std::sync::Arc;
9
10use ahash::{AHashMap, AHashSet};
11use camino::{Utf8Path, Utf8PathBuf};
12use eyre::Result;
13use fst_writer::{
14 FstBodyWriter, FstFileType, FstHeaderWriter, FstInfo, FstScopeType, FstSignalId, FstSignalType,
15 FstVarDirection, FstVarType, open_fst,
16};
17use rfd::FileHandle;
18use tracing::{error, warn};
19use wellen::{
20 Hierarchy, ScopeRef, ScopeType, Signal, SignalEncoding, SignalRef, SignalValueRef, Time,
21 TimeTable, TimeTableIdx, Timescale, TimescaleUnit, VarDirection, VarRef, VarType,
22};
23
24use crate::SystemState;
25use crate::async_util::{AsyncJob, perform_async_work};
26use crate::channels::checked_send_many;
27use crate::displayed_item::DisplayedItem;
28use crate::file_dialog::FST_EXPORT_FILTER;
29use crate::message::Message;
30use crate::state_file_io::{sanitize_file_stem, source_file_stem};
31use crate::time::{TimeScale, TimeUnit};
32use crate::wave_container::VariableRef;
33use crate::wellen::WellenContainer;
34
35impl SystemState {
36 fn displayed_variables(&self) -> Vec<VariableRef> {
39 let Some(waves) = self.user.waves.as_ref() else {
40 return vec![];
41 };
42 waves
43 .items_tree
44 .iter()
45 .filter_map(|node| match waves.displayed_items.get(&node.item_ref) {
46 Some(DisplayedItem::Variable(v)) => Some(v.variable_ref.clone()),
47 _ => None,
48 })
49 .collect()
50 }
51
52 fn default_fst_export_file_name(&self) -> String {
55 let stem = self
56 .user
57 .waves
58 .as_ref()
59 .and_then(|waves| source_file_stem(&waves.source))
60 .map(|stem| sanitize_file_stem(stem, "export"));
61 match stem {
62 Some(stem) => format!("{stem}_export.fst"),
63 None => "export.fst".to_string(),
64 }
65 }
66
67 pub(crate) fn export_signals_to_fst(&mut self, path: Option<Utf8PathBuf>) {
71 let variables = self.displayed_variables();
72 if variables.is_empty() {
73 error!("No displayed variables to export to FST");
74 return;
75 }
76 let Some(waves) = self.user.waves.as_ref() else {
77 return;
78 };
79 let export = match waves
80 .inner
81 .as_waves()
82 .ok_or_else(|| eyre::eyre!("No waveform data to export"))
83 .and_then(|w| w.prepare_fst_export(&variables))
84 {
85 Ok(export) => export,
86 Err(e) => {
87 error!("Failed to prepare FST export: {e:#?}");
88 return;
89 }
90 };
91
92 let messages = move |destination: FileHandle| async move {
93 let result = match Utf8Path::from_path(destination.path()) {
94 Some(path) => export.write_to_file(path),
95 None => Err(eyre::eyre!(
96 "File path '{}' contains invalid UTF-8",
97 destination.path().display()
98 )),
99 };
100 match result {
101 Ok(()) => vec![Message::AsyncDone(AsyncJob::ExportFst)],
102 Err(e) => {
103 error!("Failed to export variables to FST: {e:#?}");
104 vec![Message::AsyncDone(AsyncJob::ExportFst)]
105 }
106 }
107 };
108
109 if let Some(path) = path {
110 let sender = self.channels.msg_sender.clone();
111 perform_async_work(async move {
112 checked_send_many(&sender, messages(path.into_std_path_buf().into()).await);
113 });
114 } else {
115 self.file_dialog_save(
116 "Export variables to FST",
117 &FST_EXPORT_FILTER,
118 Some(self.default_fst_export_file_name()),
119 messages,
120 );
121 }
122 }
123}
124
125pub(crate) enum FstExport {
127 Wellen(FstExportData),
128}
129
130impl FstExport {
131 pub(crate) fn write_to_file(&self, path: &Utf8Path) -> Result<()> {
133 match self {
134 FstExport::Wellen(data) => data.write_to_file(path),
135 }
136 }
137}
138
139#[derive(Default)]
144pub(crate) struct FstExportNode {
145 pub(crate) id: Option<ScopeRef>,
147 pub(crate) children: BTreeMap<String, FstExportNode>,
149 pub(crate) vars: Vec<VarRef>,
151}
152
153pub(crate) struct FstExportData {
159 pub(crate) hierarchy: Arc<Hierarchy>,
161 pub(crate) time_table: Arc<TimeTable>,
163 pub(crate) root: FstExportNode,
165 pub(crate) signals: AHashMap<SignalRef, Arc<Signal>>,
167 pub(crate) version: String,
169 pub(crate) date: String,
171 pub(crate) timescale_exponent: i8,
174 pub(crate) time_scale_divisor: u128,
177 pub(crate) start_time: Time,
179 pub(crate) end_time: Time,
182}
183
184impl FstExportData {
185 pub(crate) fn write_to_file(&self, path: &Utf8Path) -> Result<()> {
187 let h: &Hierarchy = &self.hierarchy;
188 let info = FstInfo {
189 start_time: self.start_time,
190 timescale_exponent: self.timescale_exponent,
191 version: self.version.clone(),
192 date: self.date.clone(),
193 file_type: FstFileType::VerilogVhdl,
194 };
195
196 let mut header = open_fst(path, &info)?;
197 let mut signal_ids: AHashMap<SignalRef, FstSignalId> = AHashMap::new();
198 write_fst_export_node(h, &mut header, &self.root, &mut signal_ids)?;
199 let mut body = header.finish()?;
200
201 let signals = signal_ids
203 .iter()
204 .map(|(signal_ref, fst_id)| (*fst_id, Arc::clone(&self.signals[signal_ref])))
205 .collect::<Vec<_>>();
206
207 self.write_fst_value_changes(&mut body, &signals)?;
208 body.finish()?;
209 Ok(())
210 }
211
212 fn write_fst_value_changes<W: std::io::Write + std::io::Seek>(
215 &self,
216 out: &mut FstBodyWriter<W>,
217 signals: &[(FstSignalId, Arc<Signal>)],
218 ) -> Result<()> {
219 let mut iters = signals
220 .iter()
221 .map(|(fst_id, signal)| (*fst_id, signal.iter_changes().peekable()))
222 .collect::<Vec<_>>();
223
224 let mut heap = BinaryHeap::new();
225 for (i, (_, it)) in iters.iter_mut().enumerate() {
226 if let Some((t, _)) = it.peek() {
227 heap.push(Reverse((*t, i)));
228 }
229 }
230
231 let mut last_time_idx: Option<TimeTableIdx> = None;
232 while let Some(Reverse((time_idx, i))) = heap.pop() {
233 if last_time_idx != Some(time_idx) {
234 let time = self.time_table.get(time_idx as usize).copied().unwrap_or(0);
235 out.time_change(scale_time(time, self.time_scale_divisor))?;
236 last_time_idx = Some(time_idx);
237 }
238
239 let (fst_id, it) = &mut iters[i];
240 let (_, value) = it.next().expect("time index was just peeked");
241 match value {
242 SignalValueRef::Event => out.signal_change(*fst_id, &[])?,
243 SignalValueRef::BitVec(bv) => {
244 out.signal_change(*fst_id, bv.bit_string().as_bytes())?;
245 }
246 SignalValueRef::Real(v) => out.signal_change(*fst_id, &v.to_le_bytes())?,
247 SignalValueRef::String(_) => {
248 }
250 }
251
252 if let Some((next_idx, _)) = it.peek() {
253 heap.push(Reverse((*next_idx, i)));
254 }
255 }
256
257 let last_idx = self
260 .time_table
261 .len()
262 .checked_sub(1)
263 .map(|idx| idx as TimeTableIdx);
264 if !self.time_table.is_empty() && last_time_idx != last_idx {
265 out.time_change(self.end_time)?;
266 }
267 Ok(())
268 }
269}
270
271impl WellenContainer {
272 pub(crate) fn prepare_fst_export(&self, variables: &[VariableRef]) -> Result<FstExportData> {
275 let h: &Hierarchy = &self.hierarchy;
276
277 let mut root = FstExportNode::default();
278 let mut seen = AHashSet::new();
279 for variable in variables {
280 let var_ref = self.get_var_ref(variable)?;
281 if !seen.insert(var_ref) {
282 continue;
283 }
284 let mut node = &mut root;
285 let mut prefix: Vec<String> = Vec::new();
286 for scope in &variable.path.strs {
287 prefix.push(scope.clone());
288 node = node.children.entry(scope.clone()).or_default();
289 if node.id.is_none() {
290 node.id = h.lookup_scope(&prefix);
291 }
292 }
293 node.vars.push(var_ref);
294 }
295 if seen.is_empty() {
296 eyre::bail!("No variables selected for FST export");
297 }
298
299 let mut signals = AHashMap::with_capacity(seen.len());
300 for var_ref in &seen {
301 let signal_ref = h[*var_ref].signal_ref();
302 if signals.contains_key(&signal_ref) {
303 continue;
304 }
305 let signal = self.signals.get(&signal_ref).cloned().ok_or_else(|| {
306 eyre::eyre!(
307 "Signal for variable '{}' is not loaded",
308 h[*var_ref].name(h)
309 )
310 })?;
311 signals.insert(signal_ref, signal);
312 }
313
314 let timescale = h
315 .timescale()
316 .unwrap_or(Timescale::new(1, TimescaleUnit::Unknown));
317 let base_exponent = timescale.unit.to_exponent().unwrap_or(0);
318 let factor_digits = u32::from(
322 TimeScale {
323 unit: TimeUnit::from(timescale.unit),
324 multiplier: Some(timescale.factor),
325 }
326 .multiplier_digits(),
327 );
328 let time_scale_shift = common_power_of_ten(&self.time_table);
329 let time_scale_divisor = 10u128.pow(time_scale_shift);
330 let timescale_exponent = base_exponent
331 .saturating_add(factor_digits as i8)
332 .saturating_add(time_scale_shift as i8);
333 let start_time = scale_time(
334 self.time_table.first().copied().unwrap_or(0),
335 time_scale_divisor,
336 );
337 let end_time = scale_time(
338 self.time_table.last().copied().unwrap_or(0),
339 time_scale_divisor,
340 );
341 Ok(FstExportData {
342 hierarchy: Arc::clone(&self.hierarchy),
343 time_table: Arc::clone(&self.time_table),
344 root,
345 signals,
346 version: h.version().to_string(),
347 date: h.date().to_string(),
348 timescale_exponent,
349 time_scale_divisor,
350 start_time,
351 end_time,
352 })
353 }
354}
355
356fn trailing_zeros_base10(mut n: u128) -> u32 {
360 debug_assert_ne!(n, 0, "trailing_zeros_base10(0) would loop forever");
361 let mut count = 0;
362 while n.is_multiple_of(10) {
363 n /= 10;
364 count += 1;
365 }
366 count
367}
368
369fn common_power_of_ten(times: &[Time]) -> u32 {
374 let mut common = u32::MAX;
375 for &t in times {
376 if t == 0 {
377 continue;
378 }
379 let zeros = trailing_zeros_base10(u128::from(t));
380 common = common.min(zeros);
381 if common == 0 {
382 return 0;
383 }
384 }
385 if common == u32::MAX { 0 } else { common }
386}
387
388fn scale_time(t: Time, divisor: u128) -> Time {
391 (u128::from(t) / divisor) as Time
392}
393
394fn write_fst_export_node<W: std::io::Write + std::io::Seek>(
396 h: &Hierarchy,
397 out: &mut FstHeaderWriter<W>,
398 node: &FstExportNode,
399 signal_ids: &mut AHashMap<SignalRef, FstSignalId>,
400) -> Result<()> {
401 for var_ref in &node.vars {
402 write_fst_export_var(h, out, signal_ids, *var_ref)?;
403 }
404 for (name, child) in &node.children {
405 let (component, tpe) = match child.id {
406 Some(id) => {
407 let scope = &h[id];
408 (
409 scope.component(h).unwrap_or("").to_string(),
410 fst_scope_type(scope.scope_type()),
411 )
412 }
413 None => (String::new(), FstScopeType::Module),
414 };
415 out.scope(name, &component, tpe)?;
416 write_fst_export_node(h, out, child, signal_ids)?;
417 out.up_scope()?;
418 }
419 Ok(())
420}
421
422fn write_fst_export_var<W: std::io::Write + std::io::Seek>(
425 h: &Hierarchy,
426 out: &mut FstHeaderWriter<W>,
427 signal_ids: &mut AHashMap<SignalRef, FstSignalId>,
428 var_ref: VarRef,
429) -> Result<()> {
430 let var = &h[var_ref];
431 let name = var.name(h);
432 let signal_tpe = match var.signal_encoding(h) {
433 SignalEncoding::String => {
434 warn!(
435 "Variable-length string signal '{name}' is not supported by FST export, skipping it"
436 );
437 return Ok(());
438 }
439 SignalEncoding::Real => FstSignalType::real(),
440 SignalEncoding::BitVector(len) => FstSignalType::bit_vec(len),
441 };
442 let tpe = fst_var_type(var.var_type());
443 let dir = fst_var_direction(var.direction());
444 let signal_ref = var.signal_ref();
445 let alias = signal_ids.get(&signal_ref).copied();
446 let fst_id = out.var(name, signal_tpe, tpe, dir, alias)?;
447 signal_ids.entry(signal_ref).or_insert(fst_id);
448 Ok(())
449}
450
451fn fst_scope_type(tpe: ScopeType) -> FstScopeType {
453 match tpe {
454 ScopeType::Module => FstScopeType::Module,
455 ScopeType::Task => FstScopeType::Task,
456 ScopeType::Function | ScopeType::VhdlFunction => FstScopeType::Function,
457 ScopeType::Begin => FstScopeType::Begin,
458 ScopeType::Fork => FstScopeType::Fork,
459 ScopeType::Generate => FstScopeType::Generate,
460 ScopeType::Struct | ScopeType::VhdlRecord => FstScopeType::Struct,
461 ScopeType::Union => FstScopeType::Union,
462 ScopeType::Class => FstScopeType::Class,
463 ScopeType::Interface => FstScopeType::Interface,
464 ScopeType::Package | ScopeType::VhdlPackage => FstScopeType::Package,
465 ScopeType::Program => FstScopeType::Program,
466 ScopeType::VhdlArchitecture => FstScopeType::VhdlArchitecture,
467 ScopeType::VhdlProcedure => FstScopeType::VhdlProcedure,
468 ScopeType::VhdlProcess => FstScopeType::VhdlProcess,
469 ScopeType::VhdlBlock => FstScopeType::VhdlBlock,
470 ScopeType::VhdlForGenerate => FstScopeType::VhdlForGenerate,
471 ScopeType::VhdlIfGenerate => FstScopeType::VhdlIfGenerate,
472 ScopeType::VhdlGenerate | ScopeType::GhwGeneric => FstScopeType::VhdlGenerate,
473 ScopeType::VhdlArray | ScopeType::SvArray => FstScopeType::Struct,
474 _ => FstScopeType::Module,
475 }
476}
477
478fn fst_var_type(tpe: VarType) -> FstVarType {
480 match tpe {
481 VarType::Event | VarType::EventParameter => FstVarType::Event,
482 VarType::Integer => FstVarType::Integer,
483 VarType::Parameter => FstVarType::Parameter,
484 VarType::Real | VarType::RealParameter => FstVarType::Real,
485 VarType::Reg => FstVarType::Reg,
486 VarType::Supply0 => FstVarType::Supply0,
487 VarType::Supply1 => FstVarType::Supply1,
488 VarType::Time => FstVarType::Time,
489 VarType::Tri => FstVarType::Tri,
490 VarType::TriAnd => FstVarType::TriAnd,
491 VarType::TriOr => FstVarType::TriOr,
492 VarType::TriReg => FstVarType::TriReg,
493 VarType::Tri0 => FstVarType::Tri0,
494 VarType::Tri1 => FstVarType::Tri1,
495 VarType::WAnd => FstVarType::Wand,
496 VarType::Wire => FstVarType::Wire,
497 VarType::WOr => FstVarType::Wor,
498 VarType::String => FstVarType::GenericString,
499 VarType::Port => FstVarType::Port,
500 VarType::SparseArray => FstVarType::SparseArray,
501 VarType::RealTime => FstVarType::RealTime,
502 VarType::Bit | VarType::Boolean | VarType::StdLogic | VarType::StdULogic => FstVarType::Bit,
503 VarType::Logic
504 | VarType::BitVector
505 | VarType::StdLogicVector
506 | VarType::StdULogicVector => FstVarType::Logic,
507 VarType::Int => FstVarType::Int,
508 VarType::ShortInt => FstVarType::ShortInt,
509 VarType::LongInt => FstVarType::LongInt,
510 VarType::Byte => FstVarType::Byte,
511 VarType::Enum => FstVarType::Enum,
512 VarType::ShortReal => FstVarType::ShortReal,
513 }
514}
515
516fn fst_var_direction(dir: VarDirection) -> FstVarDirection {
518 match dir {
519 VarDirection::Unknown | VarDirection::Implicit => FstVarDirection::Implicit,
520 VarDirection::Input => FstVarDirection::Input,
521 VarDirection::Output => FstVarDirection::Output,
522 VarDirection::InOut => FstVarDirection::InOut,
523 VarDirection::Buffer => FstVarDirection::Buffer,
524 VarDirection::Linkage => FstVarDirection::Linkage,
525 }
526}
527
528#[cfg(test)]
529mod tests {
530 use super::*;
531
532 #[test]
533 fn trailing_zeros_base10_counts_correctly() {
534 assert_eq!(trailing_zeros_base10(1), 0);
535 assert_eq!(trailing_zeros_base10(7), 0);
536 assert_eq!(trailing_zeros_base10(10), 1);
537 assert_eq!(trailing_zeros_base10(120), 1);
538 assert_eq!(trailing_zeros_base10(100), 2);
539 assert_eq!(trailing_zeros_base10(123_000), 3);
540 assert_eq!(trailing_zeros_base10(1_000_000_000_000), 12);
541 }
542
543 #[test]
544 fn common_power_of_ten_of_empty_or_all_zero_is_zero() {
545 assert_eq!(common_power_of_ten(&[]), 0);
546 assert_eq!(common_power_of_ten(&[0, 0, 0]), 0);
547 }
548
549 #[test]
550 fn common_power_of_ten_ignores_zero_time_stamps() {
551 assert_eq!(common_power_of_ten(&[0, 100, 200]), 2);
553 }
554
555 #[test]
556 fn common_power_of_ten_uses_the_smallest_shared_shift() {
557 assert_eq!(common_power_of_ten(&[10, 20, 30]), 1);
558 assert_eq!(common_power_of_ten(&[100, 200, 300]), 2);
559 assert_eq!(common_power_of_ten(&[100, 200, 305]), 0);
561 }
562
563 #[test]
564 fn common_power_of_ten_short_circuits_at_zero() {
565 assert_eq!(common_power_of_ten(&[1, 100, 1_000_000]), 0);
566 }
567
568 #[test]
569 fn scale_time_divides_by_the_given_divisor() {
570 assert_eq!(scale_time(1_234, 1), 1_234);
571 assert_eq!(scale_time(1_230, 10), 123);
572 assert_eq!(scale_time(1_200_000, 100), 12_000);
573 assert_eq!(scale_time(0, 1000), 0);
574 }
575
576 #[test]
577 fn fst_scope_type_maps_common_variants() {
578 assert_eq!(fst_scope_type(ScopeType::Module), FstScopeType::Module);
579 assert_eq!(fst_scope_type(ScopeType::Struct), FstScopeType::Struct);
580 assert_eq!(fst_scope_type(ScopeType::VhdlRecord), FstScopeType::Struct);
581 assert_eq!(fst_scope_type(ScopeType::VhdlArray), FstScopeType::Struct);
582 assert_eq!(fst_scope_type(ScopeType::Unknown), FstScopeType::Module);
583 }
584
585 #[test]
586 fn fst_var_type_maps_common_variants() {
587 assert_eq!(fst_var_type(VarType::Wire), FstVarType::Wire);
588 assert_eq!(fst_var_type(VarType::Reg), FstVarType::Reg);
589 assert_eq!(fst_var_type(VarType::Event), FstVarType::Event);
590 assert_eq!(fst_var_type(VarType::EventParameter), FstVarType::Event);
591 assert_eq!(fst_var_type(VarType::Bit), FstVarType::Bit);
592 assert_eq!(fst_var_type(VarType::Logic), FstVarType::Logic);
593 assert_eq!(fst_var_type(VarType::BitVector), FstVarType::Logic);
594 }
595
596 #[test]
597 fn fst_var_direction_maps_common_variants() {
598 assert_eq!(
599 fst_var_direction(VarDirection::Unknown),
600 FstVarDirection::Implicit
601 );
602 assert_eq!(
603 fst_var_direction(VarDirection::Implicit),
604 FstVarDirection::Implicit
605 );
606 assert_eq!(
607 fst_var_direction(VarDirection::Input),
608 FstVarDirection::Input
609 );
610 assert_eq!(
611 fst_var_direction(VarDirection::Output),
612 FstVarDirection::Output
613 );
614 assert_eq!(
615 fst_var_direction(VarDirection::InOut),
616 FstVarDirection::InOut
617 );
618 }
619}