1use crate::symid::{SymbolId, SymbolUid};
62use std::collections::BTreeSet;
63use sv_lang::{EdgeKind, Expression, KnownSystemName, SemNode, Symbol, TimingControl};
64use sv_lang_kinds::{ExpressionKind, SymbolKind, TimingControlKind};
65
66#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct SampledError(pub String);
69
70impl core::fmt::Display for SampledError {
71 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
72 f.write_str(&self.0)
73 }
74}
75
76fn err<T>(msg: impl Into<String>) -> Result<T, SampledError> {
77 Err(SampledError(msg.into()))
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum SampledOp {
83 Sampled,
84 Rose,
85 Fell,
86 Stable,
87 Changed,
88 Past,
89 Future,
92 Rising,
93 Falling,
94 Steady,
95 Changing,
96}
97
98impl SampledOp {
99 pub fn is_future(self) -> bool {
102 matches!(
103 self,
104 Self::Future | Self::Rising | Self::Falling | Self::Steady | Self::Changing
105 )
106 }
107
108 pub fn needs_clock(self) -> bool {
110 self != Self::Sampled
111 }
112
113 pub fn depth(self, ticks: u32) -> u32 {
116 match self {
117 Self::Sampled
119 | Self::Future
120 | Self::Rising
121 | Self::Falling
122 | Self::Steady
123 | Self::Changing => 0,
124 Self::Past => ticks,
125 _ => 1,
126 }
127 }
128}
129
130pub fn op_of(id: KnownSystemName) -> Option<(SampledOp, bool)> {
133 use SampledOp as O;
134 Some(match id {
135 KnownSystemName::Sampled => (O::Sampled, false),
136 KnownSystemName::Rose => (O::Rose, false),
137 KnownSystemName::Fell => (O::Fell, false),
138 KnownSystemName::Stable => (O::Stable, false),
139 KnownSystemName::Changed => (O::Changed, false),
140 KnownSystemName::Past => (O::Past, false),
141 KnownSystemName::PastGclk => (O::Past, true),
142 KnownSystemName::RoseGclk => (O::Rose, true),
143 KnownSystemName::FellGclk => (O::Fell, true),
144 KnownSystemName::StableGclk => (O::Stable, true),
145 KnownSystemName::ChangedGclk => (O::Changed, true),
146 KnownSystemName::FutureGclk => (O::Future, true),
147 KnownSystemName::RisingGclk => (O::Rising, true),
148 KnownSystemName::FallingGclk => (O::Falling, true),
149 KnownSystemName::SteadyGclk => (O::Steady, true),
150 KnownSystemName::ChangingGclk => (O::Changing, true),
151 _ => return None,
152 })
153}
154
155#[derive(Clone, Copy)]
159pub struct ClockEvent<'d> {
160 pub edge: EdgeKind,
161 pub signal: Expression<'d>,
162 pub iff: Option<Expression<'d>>,
163}
164
165pub fn clock_event_of<'d>(tc: TimingControl<'d>) -> Result<ClockEvent<'d>, SampledError> {
169 let ev = assertion_clock_event_of(tc)?;
170 if ev.signal.kind() == ExpressionKind::AssertionInstance {
175 return err(
176 "a sequence instance as a clocking event here (`@seq`, LRM 16.5.2) is only supported as the clocking event of an assertion"
177 .to_string(),
178 );
179 }
180 Ok(ev)
181}
182
183pub fn assertion_clock_event_of<'d>(tc: TimingControl<'d>) -> Result<ClockEvent<'d>, SampledError> {
188 if tc.kind() != TimingControlKind::SignalEvent {
189 return err(format!(
190 "a sampled value function's clocking event is a {:?} control; only a single \
191 `@(edge signal [iff cond])` event is supported",
192 tc.kind()
193 ));
194 }
195 let children = tc.children();
196 let signal = children
197 .first()
198 .and_then(|c| c.as_expression())
199 .ok_or_else(|| SampledError("a clocking event with no signal expression".into()))?;
200 let iff = if children.len() == 2 {
202 children[1].as_expression()
203 } else {
204 None
205 };
206 Ok(ClockEvent {
207 edge: tc.edge(),
208 signal,
209 iff,
210 })
211}
212
213pub fn is_global_clock_event(tc: TimingControl<'_>) -> bool {
216 tc.kind() == TimingControlKind::SignalEvent
217 && tc.edge() == EdgeKind::None
218 && tc
219 .children()
220 .first()
221 .and_then(|c| c.as_expression())
222 .is_some_and(|x| {
223 x.kind() == ExpressionKind::Call
224 && x.is_system_call() == Some(true)
225 && x.system_subroutine()
226 .is_some_and(|s| s.known_name_id() == KnownSystemName::GlobalClock)
227 })
228}
229
230pub fn mentions_global_clock_event(tc: TimingControl<'_>) -> bool {
234 if tc.kind() == TimingControlKind::EventList {
235 return tc
236 .children()
237 .into_iter()
238 .filter_map(|c| c.as_timing_control())
239 .any(mentions_global_clock_event);
240 }
241 is_global_clock_event(tc)
242}
243
244pub fn is_future_call(e: Expression<'_>) -> bool {
247 e.kind() == ExpressionKind::Call
248 && e.is_system_call() == Some(true)
249 && e.system_subroutine()
250 .and_then(|s| op_of(s.known_name_id()))
251 .is_some_and(|(op, _)| op.is_future())
252}
253
254#[derive(Clone, Copy)]
256pub struct SampledCall<'d> {
257 pub op: SampledOp,
258 pub global: bool,
261 pub call: Expression<'d>,
263 pub arg: Expression<'d>,
265 pub ticks: u32,
267 pub gate: Option<Expression<'d>>,
269 pub clock: Option<ClockEvent<'d>>,
271}
272
273fn is_empty_arg(e: &Expression<'_>) -> bool {
274 e.kind() == ExpressionKind::EmptyArgument
275}
276
277pub fn decode<'d>(e: Expression<'d>) -> Result<Option<SampledCall<'d>>, SampledError> {
280 if e.kind() != ExpressionKind::Call || e.is_system_call() != Some(true) {
281 return Ok(None);
282 }
283 let Some(sub) = e.system_subroutine() else {
284 return Ok(None);
285 };
286 let Some((op, mut global)) = op_of(sub.known_name_id()) else {
287 return Ok(None);
288 };
289 let operands = e.operands();
290 let name = sub.name();
291 let Some(&arg) = operands.first() else {
292 return err(format!("{name} with no argument expression"));
293 };
294 let mut ticks = 1u32;
295 let mut gate = None;
296 let mut clock = None;
297 for (i, o) in operands.iter().enumerate().skip(1) {
298 if o.kind() == ExpressionKind::ClockingEvent {
299 if clock.is_some() {
300 return err(format!("{name} with two clocking events"));
301 }
302 let tc = o
303 .as_sem_node()
304 .children()
305 .into_iter()
306 .find_map(|c| c.as_timing_control())
307 .ok_or_else(|| SampledError(format!("{name}: a clocking event with no control")))?;
308 if is_global_clock_event(tc) {
309 if global {
311 return err(format!("{name} with two clocking events"));
312 }
313 global = true;
314 } else {
315 clock = Some(clock_event_of(tc)?);
316 }
317 } else if is_empty_arg(o) {
318 } else if op == SampledOp::Past && i == 1 {
320 let n = o
321 .constant_value()
322 .and_then(|cv| cv.as_i64())
323 .ok_or_else(|| {
324 SampledError(
325 "$past's number_of_ticks is not an elaboration-time constant".into(),
326 )
327 })?;
328 if n < 1 {
329 return err(format!(
330 "$past's number_of_ticks is {n}; it shall be 1 or greater"
331 ));
332 }
333 ticks = u32::try_from(n)
334 .map_err(|_| SampledError(format!("$past's number_of_ticks {n} is too large")))?;
335 } else if op == SampledOp::Past && i == 2 {
336 gate = Some(*o);
337 } else {
338 return err(format!("{name}: unexpected argument {i}"));
339 }
340 }
341 Ok(Some(SampledCall {
342 op,
343 global,
344 call: e,
345 arg,
346 ticks,
347 gate,
348 clock,
349 }))
350}
351
352pub type InferredDisable<'d> = Option<Option<(Symbol<'d>, bool)>>;
362
363pub type InferredDisableResolver = for<'d> fn(&Symbol<'d>) -> Result<InferredDisable<'d>, String>;
365
366thread_local! {
367 static INFERRED_DISABLE_RESOLVER: std::cell::Cell<Option<InferredDisableResolver>> =
368 const { std::cell::Cell::new(None) };
369}
370
371pub fn set_inferred_disable_resolver(
374 f: Option<InferredDisableResolver>,
375) -> Option<InferredDisableResolver> {
376 INFERRED_DISABLE_RESOLVER.with(|c| c.replace(f))
377}
378
379pub fn resolve_inferred_disable<'d>(sym: &Symbol<'d>) -> Result<InferredDisable<'d>, String> {
383 match INFERRED_DISABLE_RESOLVER.with(std::cell::Cell::get) {
384 Some(f) => f(sym),
385 None => Err("the design's syntax is not available here".into()),
386 }
387}
388
389fn is_inferred_disable_call(e: &Expression<'_>) -> bool {
390 e.kind() == ExpressionKind::Call
391 && e.call_subroutine_name().as_deref() == Some("$inferred_disable")
392}
393
394fn reads_inferred_disable(root: SemNode<'_>) -> bool {
397 let mut visited = BTreeSet::new();
398 let mut work = vec![root];
399 while let Some(node) = work.pop() {
400 let mut found = false;
401 walk(node, &mut |n| {
402 if let Some(e) = n.as_expression() {
403 if is_inferred_disable_call(&e) {
404 found = true;
405 } else if e.kind() == ExpressionKind::Call
406 && e.is_system_call() != Some(true)
407 && let Some(callee) = crate::netlist::itf::call_target(&e)
408 && visited.insert(callee.uid())
409 && let Some(body) = crate::process::cfg::proc_body(&callee)
410 {
411 work.push(body.as_sem_node());
412 }
413 }
414 !found
415 });
416 if found {
417 return true;
418 }
419 }
420 false
421}
422
423pub fn sampleable_symbol(sym: &Symbol<'_>) -> bool {
426 let kind = sym.kind();
427 if kind != SymbolKind::Net
428 && !(kind == SymbolKind::Variable
429 && sym.variable_lifetime() == sv_lang::VariableLifetime::Static)
430 {
431 return false;
432 }
433 sym.value_type().is_some_and(|t| t.is_integral())
434}
435
436fn is_integral_memory(sym: &Symbol<'_>) -> bool {
439 if sym.kind() != SymbolKind::Variable && sym.kind() != SymbolKind::Net {
440 return false;
441 }
442 let Some(ty) = sym.value_type() else {
443 return false;
444 };
445 if !ty.is_unpacked_array() || crate::state::storage::is_collection(&ty) {
446 return false;
447 }
448 let mut t = ty;
449 while t.is_unpacked_array() {
450 match t.element_type() {
451 Some(e) => t = e,
452 None => return false,
453 }
454 }
455 t.is_integral()
456}
457
458pub fn is_state_symbol(sym: &Symbol<'_>) -> bool {
462 let kind = sym.kind();
463 kind == SymbolKind::Net
464 || (kind == SymbolKind::Variable
465 && sym.variable_lifetime() == sv_lang::VariableLifetime::Static)
466}
467
468fn walk<'d>(node: SemNode<'d>, f: &mut impl FnMut(SemNode<'d>) -> bool) {
469 let mut stack = vec![node];
470 while let Some(n) = stack.pop() {
471 if f(n) {
472 let mut kids = crate::process::cfg::live_children(&n);
473 kids.reverse();
474 stack.extend(kids);
475 }
476 }
477}
478
479pub fn signals_in(node: SemNode<'_>) -> BTreeSet<SymbolId> {
481 let mut out = BTreeSet::new();
482 walk(node, &mut |n| {
483 if let Some(e) = n.as_expression()
484 && e.kind() == ExpressionKind::NamedValue
485 && let Some(sym) = e.referenced_symbol()
486 && sampleable_symbol(&sym)
487 {
488 out.insert(sym.uid());
489 }
490 true
491 });
492 out
493}
494
495pub fn strict_signals_in(e: Expression<'_>) -> Result<BTreeSet<SymbolId>, SampledError> {
508 strict_signals(e, true, true)
511}
512
513pub fn strict_site_signals_in(e: Expression<'_>) -> Result<BTreeSet<SymbolId>, SampledError> {
517 strict_signals(e, true, true)
518}
519
520fn strict_hidden_signals_in(e: Expression<'_>) -> Result<BTreeSet<SymbolId>, SampledError> {
524 strict_signals(e, true, false)
525}
526
527fn strict_signals(
528 e: Expression<'_>,
529 allow_hier: bool,
530 allow_mem: bool,
531) -> Result<BTreeSet<SymbolId>, SampledError> {
532 let mut failure = None;
533 let mut hier = BTreeSet::new();
534 walk(e.as_sem_node(), &mut |n| {
535 if failure.is_some() {
536 return false;
537 }
538 let Some(x) = n.as_expression() else {
539 return true;
540 };
541 let kind = x.kind();
545 if kind == ExpressionKind::HierarchicalValue {
546 match x.referenced_symbol() {
549 Some(sym) if allow_hier && sampleable_symbol(&sym) => {
550 hier.insert(sym.uid());
551 }
552 _ => {
553 failure = Some(SampledError(
554 "a hierarchical reference inside a sampled value function's expression \
555 is not supported unless it names an integral variable or net"
556 .into(),
557 ));
558 }
559 }
560 } else if kind == ExpressionKind::NamedValue {
561 if let Some(sym) = x.referenced_symbol()
562 && is_state_symbol(&sym)
563 && !sampleable_symbol(&sym)
564 {
565 if allow_mem && is_integral_memory(&sym) {
569 hier.insert(sym.uid());
570 return true;
571 }
572 failure = Some(SampledError(format!(
573 "`{}` inside a sampled value function's expression has a type \
574 (real/string/array/event/collection/handle) whose sampled value is not \
575 supported",
576 sym.name()
577 )));
578 }
579 } else if kind == ExpressionKind::Call {
580 match decode(x) {
581 Ok(Some(_)) => {
582 failure = Some(SampledError(
583 "a sampled value function nested inside the expression of another is \
584 not supported"
585 .into(),
586 ));
587 }
588 Ok(None) => {
589 if x.is_system_call() == Some(false) {
590 failure = Some(SampledError(
591 "a user subroutine call inside a sampled value function's expression \
592 is not supported"
593 .into(),
594 ));
595 }
596 }
597 Err(e) => failure = Some(e),
598 }
599 }
600 failure.is_none()
601 });
602 match failure {
603 Some(e) => Err(e),
604 None => {
605 let mut out = signals_in(e.as_sem_node());
606 out.extend(hier);
607 Ok(out)
608 }
609 }
610}
611
612pub fn is_checker_always_ff(block: &Symbol<'_>) -> bool {
615 if block.kind() != SymbolKind::ProceduralBlock
616 || block.procedural_block_procedure_kind() != sv_lang::ProceduralBlockKind::AlwaysFF
617 {
618 return false;
619 }
620 let mut scope = block.parent();
621 while let Some(s) = scope {
622 let kind = s.kind();
623 if kind == SymbolKind::CheckerInstanceBody {
624 return true;
625 }
626 if kind == SymbolKind::InstanceBody {
627 return false;
628 }
629 scope = s.parent();
630 }
631 false
632}
633
634pub fn checker_ff_sampled_signals(block: &Symbol<'_>) -> Result<BTreeSet<SymbolId>, SampledError> {
647 if !is_checker_always_ff(block) {
648 return Ok(BTreeSet::new());
649 }
650 let Some(body) = crate::process::cfg::proc_body(block) else {
651 return Ok(BTreeSet::new());
652 };
653 let mut event = BTreeSet::new();
654 let mut read = BTreeSet::new();
655 let mut failure = None;
656 walk(body.as_sem_node(), &mut |n| {
657 if failure.is_some() {
658 return false;
659 }
660 if n.as_assertion_expr().is_some() {
661 return false;
662 }
663 if let Some(tc) = n.as_timing_control() {
664 event.extend(signals_in(tc.as_sem_node()));
665 return false;
666 }
667 if let Some(e) = n.as_expression() {
668 let kind = e.kind();
669 if kind == ExpressionKind::Assignment && e.is_nonblocking() != Some(true) {
670 failure = Some(SampledError(
671 "a blocking assignment in a checker's `always_ff` (LRM 17.5 allows \
672 nonblocking assignments only there)"
673 .into(),
674 ));
675 } else if kind == ExpressionKind::HierarchicalValue {
676 failure = Some(SampledError(
677 "a hierarchical reference inside a checker's `always_ff` cannot be \
678 sampled (LRM 17.5): its P cell lives in another instance"
679 .into(),
680 ));
681 } else if kind == ExpressionKind::NamedValue
682 && let Some(sym) = e.referenced_symbol()
683 {
684 if sampleable_symbol(&sym) {
685 read.insert(sym.uid());
686 } else if is_state_symbol(&sym) {
687 failure = Some(SampledError(format!(
688 "`{}` in a checker's `always_ff` has a type \
689 (real/string/array/event/collection/handle) whose sampled value (LRM \
690 17.5) is not supported",
691 sym.name()
692 )));
693 }
694 }
695 }
696 failure.is_none()
697 });
698 if let Some(e) = failure {
699 return Err(e);
700 }
701 let mut sampled = &read - &event;
702 if reads_inferred_disable(body.as_sem_node()) {
706 match resolve_inferred_disable(block) {
707 Ok(Some(Some((sig, _)))) if sampleable_symbol(&sig) => {
708 sampled.insert(sig.uid());
709 }
710 Ok(Some(None)) => {}
711 Ok(Some(Some((sig, _)))) => {
712 return err(format!(
713 "`$inferred_disable` in a checker's `always_ff` stands for `{}`, whose type \
714 has no sampled value (LRM 17.5)",
715 sig.name()
716 ));
717 }
718 Ok(None) => {
719 return err(
720 "`$inferred_disable` in an `always_ff` of a checker that is not a static \
721 instance has no sampled `default disable iff` (LRM 17.4)",
722 );
723 }
724 Err(why) => {
725 return err(format!(
726 "`$inferred_disable` in a checker's `always_ff`: {why}"
727 ));
728 }
729 }
730 }
731 Ok(sampled)
732}
733
734pub fn relocate_clock<'d>(
741 block: &Symbol<'d>,
742 ev: &ClockEvent<'_>,
743) -> Result<ClockEvent<'d>, SampledError> {
744 let want = ev.signal.source_range();
745 let mut found = None;
746 let mut look = |root: SemNode<'d>| {
747 walk(root, &mut |n| {
748 if found.is_none()
749 && let Some(tc) = n.as_timing_control()
750 && tc.kind() == TimingControlKind::SignalEvent
751 && let Ok(cand) = clock_event_of(tc)
752 && cand.edge == ev.edge
753 && cand.signal.source_range() == want
754 {
755 found = Some(cand);
756 }
757 found.is_none()
758 });
759 };
760 if let Some(body) = crate::process::cfg::proc_body(block) {
761 look(body.as_sem_node());
762 }
763 if let Some(scope) = block.parent() {
764 for cb in crate::netlist::module_items(&scope)
765 .into_iter()
766 .filter(|m| m.kind() == SymbolKind::ClockingBlock)
767 {
768 if let Some(cev) = cb.clocking_block_event() {
769 look(cev);
770 }
771 }
772 }
773 found.ok_or_else(|| {
774 SampledError(
775 "the inferred clocking event of another instance of this module has no counterpart \
776 in this instance"
777 .into(),
778 )
779 })
780}
781
782pub fn relocate_clock_node<'d>(block: &Symbol<'d>, tc: &TimingControl<'_>) -> Option<SemNode<'d>> {
788 if tc.kind() != TimingControlKind::SignalEvent {
789 return None;
790 }
791 let kids = tc.children();
792 let want = kids.first()?.as_expression()?.source_range();
793 let edge = tc.edge();
794 let arity = kids.len();
795 let mut found = None;
796 walk(
797 crate::process::cfg::proc_body(block)?.as_sem_node(),
798 &mut |n| {
799 if found.is_none()
800 && let Some(cand) = n.as_timing_control()
801 && cand.kind() == TimingControlKind::SignalEvent
802 && cand.edge() == edge
803 && cand.children().len() == arity
804 && cand
805 .children()
806 .first()
807 .and_then(|c| c.as_expression())
808 .is_some_and(|e| e.source_range() == want)
809 {
810 found = Some(cand.as_sem_node());
811 }
812 found.is_none()
813 },
814 );
815 found
816}
817
818pub struct Site<'d> {
820 pub call: SampledCall<'d>,
821 pub arg_signals: BTreeSet<SymbolId>,
823 pub p_signals: BTreeSet<SymbolId>,
826 pub foreign: std::collections::BTreeMap<SymbolId, ForeignShape>,
830}
831
832pub fn site_loc(call: &Expression<'_>) -> String {
836 let start = call.source_range().start;
837 format!("{}.{}", start.buffer, start.offset)
838}
839
840pub fn sites_of_block<'d>(block: &Symbol<'d>) -> Result<Vec<Site<'d>>, SampledError> {
845 let Some(body) = crate::process::cfg::proc_body(block) else {
846 return Ok(Vec::new());
847 };
848 let mut calls = Vec::new();
849 let mut failure = None;
850 walk(body.as_sem_node(), &mut |n| {
851 if failure.is_some() {
852 return false;
853 }
854 if n.as_assertion_expr().is_some() {
861 return false;
862 }
863 if let Some(e) = n.as_expression() {
864 match decode(e) {
865 Ok(Some(c)) => {
866 calls.push(c);
867 return false;
869 }
870 Ok(None) => {}
871 Err(e) => {
872 failure = Some(e);
873 return false;
874 }
875 }
876 }
877 true
878 });
879 if let Some(e) = failure {
880 return Err(e);
881 }
882 let mut sites = Vec::new();
883 let own_body = crate::netlist::enclosing_instance_body(block).map(|b| b.uid());
884 for call in calls {
885 let arg_signals = strict_hidden_signals_in(call.arg)?;
886 let mut p_signals = arg_signals.clone();
887 let mut roots = vec![call.arg];
888 if let Some(g) = call.gate {
889 p_signals.extend(strict_hidden_signals_in(g)?);
890 roots.push(g);
891 }
892 if call.op.needs_clock() {
893 match call.clock {
894 Some(ev) => {
895 p_signals.extend(strict_hidden_signals_in(ev.signal)?);
896 roots.push(ev.signal);
897 if let Some(iff) = ev.iff {
898 p_signals.extend(strict_hidden_signals_in(iff)?);
899 roots.push(iff);
900 }
901 }
902 None => {
903 walk(body.as_sem_node(), &mut |n| {
910 if let Some(tc) = n.as_timing_control()
911 && tc.kind() == TimingControlKind::SignalEvent
912 {
913 p_signals.extend(signals_in(tc.as_sem_node()));
914 }
915 true
916 });
917 if let Some(scope) = block.parent() {
918 for cb in crate::netlist::module_items(&scope)
919 .into_iter()
920 .filter(|m| m.kind() == SymbolKind::ClockingBlock)
921 {
922 if let Some(ev) = cb.clocking_block_event() {
923 p_signals.extend(signals_in(ev));
924 }
925 }
926 }
927 }
928 }
929 }
930 let mut foreign = std::collections::BTreeMap::new();
933 for root in roots {
934 walk(root.as_sem_node(), &mut |n| {
935 if let Some(x) = n.as_expression()
936 && x.kind() == ExpressionKind::HierarchicalValue
937 && let Some(sym) = x.referenced_symbol()
938 && sampleable_symbol(&sym)
939 && crate::netlist::enclosing_instance_body(&sym).map(|b| b.uid()) != own_body
940 && let Some(shape) = foreign_shape(&sym)
941 {
942 foreign.insert(sym.uid(), shape);
943 }
944 true
945 });
946 }
947 sites.push(Site {
948 call,
949 arg_signals,
950 p_signals,
951 foreign,
952 });
953 }
954 Ok(sites)
955}
956
957pub fn p_id(sig: &SymbolId) -> SymbolId {
960 sig.derive("p")
961}
962
963pub fn n_id(sig: &SymbolId) -> SymbolId {
966 sig.derive("n")
967}
968
969pub fn s_id(owner: &SymbolId, loc: &str, sig: &SymbolId, k: u32) -> SymbolId {
971 owner.derive(&format!("s{loc}@{}#{k}", sig.key_string()))
972}
973
974fn fnv1a(s: &str) -> u64 {
976 let mut h = 0xcbf2_9ce4_8422_2325u64;
977 for b in s.bytes() {
978 h ^= u64::from(b);
979 h = h.wrapping_mul(0x0100_0000_01b3);
980 }
981 h
982}
983
984pub fn guard_name(owner: &SymbolId, loc: &str) -> String {
988 format!(
989 "vj_sva_guard_{:016x}",
990 fnv1a(&format!("{}@{loc}", owner.key_string()))
991 )
992}
993
994#[derive(Debug, Clone, PartialEq)]
996pub struct HiddenSlot {
997 pub id: SymbolId,
998 pub source: SymbolId,
999 pub foreign: Option<ForeignShape>,
1004}
1005
1006#[derive(Debug, Clone, PartialEq)]
1008pub struct ForeignShape {
1009 pub width: u32,
1010 pub signed: bool,
1011 pub initial: Option<crate::state::FourStateValue>,
1014 pub required: bool,
1019}
1020
1021#[derive(Debug, Clone, PartialEq, Eq)]
1023pub struct ShiftStep {
1024 pub from: SymbolId,
1025 pub to: SymbolId,
1026}
1027
1028#[derive(Debug, Clone, PartialEq, Eq)]
1030pub struct SiteShift {
1031 pub guard_name: String,
1033 pub shifts: Vec<ShiftStep>,
1036}
1037
1038#[derive(Debug, Clone, Default, PartialEq)]
1040pub struct Hidden {
1041 pub p: Vec<HiddenSlot>,
1043 pub s: Vec<HiddenSlot>,
1045 pub sites: Vec<SiteShift>,
1047}
1048
1049impl Hidden {
1050 pub fn slots(&self) -> impl Iterator<Item = &HiddenSlot> {
1053 self.p.iter().chain(self.s.iter())
1054 }
1055
1056 pub fn is_empty(&self) -> bool {
1057 self.p.is_empty() && self.s.is_empty()
1058 }
1059}
1060
1061fn enclosing_clock_signals(body: &Symbol<'_>) -> Vec<(SymbolId, ForeignShape)> {
1065 let mut out = Vec::new();
1066 let mut cur = *body;
1067 while let Some(parent) = super::global_clock::parent_body(&cur) {
1068 for cb in crate::netlist::module_items(&parent)
1069 .into_iter()
1070 .filter(|m| m.kind() == SymbolKind::ClockingBlock)
1071 {
1072 let Some(ev) = cb.clocking_block_event() else {
1073 continue;
1074 };
1075 let mut syms = Vec::new();
1076 walk(ev, &mut |n| {
1077 if let Some(e) = n.as_expression()
1078 && e.kind() == ExpressionKind::NamedValue
1079 && let Some(sym) = e.referenced_symbol()
1080 && sampleable_symbol(&sym)
1081 {
1082 syms.push(sym);
1083 }
1084 true
1085 });
1086 for sym in syms {
1087 if let Some(shape) = foreign_shape(&sym) {
1088 out.push((sym.uid(), shape));
1089 }
1090 }
1091 }
1092 cur = parent;
1093 }
1094 out
1095}
1096
1097fn foreign_shape(sym: &Symbol<'_>) -> Option<ForeignShape> {
1099 use crate::state::{FourStateValue, value};
1100 let ty = sym.value_type()?;
1101 let width = u32::try_from(ty.bit_width()).ok().filter(|w| *w > 0)?;
1102 let signed = ty.is_signed();
1103 let initial = sym
1104 .initializer()
1105 .and_then(|init| init.constant_value())
1106 .and_then(|cv| value::four_state_of_constant(&cv))
1107 .and_then(|fv| {
1108 value::owned_svint_to_four_state(&value::four_state_to_owned_svint(&fv, width, signed))
1109 })
1110 .or_else(|| {
1111 (!ty.is_four_state()).then(|| {
1112 if width <= 64 {
1113 FourStateValue::Narrow(0)
1114 } else {
1115 let words = width.div_ceil(64) as usize;
1116 FourStateValue::Wide {
1117 aval: vec![0; words],
1118 bval: vec![0; words],
1119 }
1120 }
1121 })
1122 });
1123 Some(ForeignShape {
1124 width,
1125 signed,
1126 initial,
1127 required: false,
1128 })
1129}
1130
1131pub fn hidden_slots(body: &Symbol<'_>) -> Result<Hidden, SampledError> {
1134 let mut p: BTreeSet<SymbolId> = BTreeSet::new();
1135 let mut foreign: std::collections::BTreeMap<SymbolId, ForeignShape> = Default::default();
1136 let mut out = Hidden::default();
1137 let mut seen = BTreeSet::new();
1138 for item in crate::netlist::module_items(body) {
1139 if !matches!(
1143 item.kind(),
1144 SymbolKind::ProceduralBlock | SymbolKind::Subroutine
1145 ) {
1146 continue;
1147 }
1148 let owner = item.uid();
1149 if item.kind() == SymbolKind::ProceduralBlock {
1151 p.extend(checker_ff_sampled_signals(&item)?);
1152 }
1153 for site in sites_of_block(&item)? {
1154 let loc = site_loc(&site.call.call);
1155 if !seen.insert((owner.clone(), loc.clone())) {
1156 return err(format!(
1157 "two sampled value calls share one source position ({loc}); a macro that \
1158 expands to several of them is not supported"
1159 ));
1160 }
1161 p.extend(site.p_signals.iter().cloned());
1162 for (id, shape) in &site.foreign {
1163 foreign.insert(
1164 id.clone(),
1165 ForeignShape {
1166 required: true,
1167 ..shape.clone()
1168 },
1169 );
1170 }
1171 if site.call.global && site.call.clock.is_none() && !site.call.op.is_future() {
1176 for (id, shape) in enclosing_clock_signals(body) {
1177 p.insert(id.clone());
1178 foreign.insert(id, shape);
1179 }
1180 }
1181 let depth = site.call.op.depth(site.call.ticks);
1182 if depth == 0 || site.call.op.is_future() {
1183 continue;
1184 }
1185 let mut shifts = Vec::new();
1186 for sig in &site.arg_signals {
1187 for k in 0..depth {
1188 out.s.push(HiddenSlot {
1189 id: s_id(&owner, &loc, sig, k),
1190 source: sig.clone(),
1191 foreign: site.foreign.get(sig).cloned(),
1192 });
1193 }
1194 for k in (1..depth).rev() {
1195 shifts.push(ShiftStep {
1196 from: s_id(&owner, &loc, sig, k - 1),
1197 to: s_id(&owner, &loc, sig, k),
1198 });
1199 }
1200 shifts.push(ShiftStep {
1201 from: p_id(sig),
1202 to: s_id(&owner, &loc, sig, 0),
1203 });
1204 }
1205 out.sites.push(SiteShift {
1206 guard_name: guard_name(&owner, &loc),
1207 shifts,
1208 });
1209 }
1210 }
1211 out.p = p
1212 .into_iter()
1213 .map(|sig| HiddenSlot {
1214 id: p_id(&sig),
1215 foreign: foreign.get(&sig).cloned(),
1216 source: sig,
1217 })
1218 .collect();
1219 Ok(out)
1220}
1221
1222#[derive(Debug, Clone, Default, PartialEq, Eq)]
1227pub struct UseViews {
1228 pub cur: Vec<(SymbolId, SymbolId)>,
1229 pub prev: Vec<(SymbolId, SymbolId)>,
1230 pub next: Vec<(SymbolId, SymbolId)>,
1232}
1233
1234pub fn use_views(
1237 owner: &SymbolId,
1238 call: &SampledCall<'_>,
1239 arg_signals: &BTreeSet<SymbolId>,
1240) -> UseViews {
1241 let loc = site_loc(&call.call);
1242 let mut v = UseViews::default();
1243 for sig in arg_signals {
1244 match call.op {
1245 SampledOp::Sampled => v.cur.push((sig.clone(), p_id(sig))),
1246 SampledOp::Rose | SampledOp::Fell | SampledOp::Stable | SampledOp::Changed => {
1247 v.cur.push((sig.clone(), p_id(sig)));
1248 v.prev.push((sig.clone(), s_id(owner, &loc, sig, 0)));
1249 }
1250 SampledOp::Past => v
1251 .cur
1252 .push((sig.clone(), s_id(owner, &loc, sig, call.ticks - 1))),
1253 SampledOp::Future
1256 | SampledOp::Rising
1257 | SampledOp::Falling
1258 | SampledOp::Steady
1259 | SampledOp::Changing => {
1260 v.cur.push((sig.clone(), p_id(sig)));
1261 v.next.push((sig.clone(), n_id(sig)));
1262 }
1263 }
1264 }
1265 v
1266}
1267
1268#[cfg(test)]
1273pub(crate) mod model {
1274 pub struct Trace<'a> {
1277 pub preponed: &'a [u8],
1278 pub tick: &'a [bool],
1279 pub init: u8,
1281 }
1282
1283 impl Trace<'_> {
1284 pub fn sampled(&self, t: usize) -> u8 {
1286 self.preponed[t]
1287 }
1288
1289 pub fn past(&self, t: usize, k: usize) -> u8 {
1292 let ticks: Vec<usize> = (0..t).filter(|&s| self.tick[s]).collect();
1293 if ticks.len() >= k {
1294 self.preponed[ticks[ticks.len() - k]]
1295 } else {
1296 self.init
1297 }
1298 }
1299
1300 pub fn rose(&self, t: usize) -> bool {
1301 self.sampled(t) == b'1' && self.past(t, 1) != b'1'
1302 }
1303
1304 pub fn fell(&self, t: usize) -> bool {
1305 self.sampled(t) == b'0' && self.past(t, 1) != b'0'
1306 }
1307
1308 pub fn stable(&self, t: usize) -> bool {
1309 self.sampled(t) == self.past(t, 1)
1310 }
1311
1312 pub fn changed(&self, t: usize) -> bool {
1313 !self.stable(t)
1314 }
1315 }
1316
1317 #[cfg(test)]
1318 mod tests {
1319 use super::*;
1320
1321 fn bits(s: &str) -> Vec<u8> {
1322 s.bytes().collect()
1323 }
1324
1325 #[test]
1331 fn figure_16_3_value_change_expressions() {
1332 let req = bits("00110001110000");
1333 let ack = bits("00001000110000");
1334 let tick = vec![true; 14];
1335 let rq = Trace {
1336 preponed: &req,
1337 tick: &tick,
1338 init: b'0',
1339 };
1340 let ak = Trace {
1341 preponed: &ack,
1342 tick: &tick,
1343 init: b'0',
1344 };
1345 let e1: Vec<usize> = (0..14).filter(|&t| rq.rose(t)).map(|t| t + 1).collect();
1346 let e2: Vec<usize> = (0..14).filter(|&t| ak.fell(t)).map(|t| t + 1).collect();
1347 assert_eq!(e1, [3, 8]);
1348 assert_eq!(e2, [6, 11]);
1349 assert!(e1.contains(&3) && e2.contains(&6));
1350 }
1351
1352 #[test]
1355 fn value_change_compares_with_the_previous_tick_not_the_previous_step() {
1356 let x = bits("00110");
1358 let tick = [true, false, false, true, true];
1359 let t = Trace {
1360 preponed: &x,
1361 tick: &tick,
1362 init: b'0',
1363 };
1364 assert!(t.rose(3));
1366 assert!(t.fell(4) && t.changed(4));
1368 assert!(t.rose(2));
1370 }
1371
1372 #[test]
1376 fn before_the_first_tick_the_default_sampled_value_is_used() {
1377 let x = bits("1x0");
1378 let tick = [true, true, true];
1379 let t = Trace {
1380 preponed: &x,
1381 tick: &tick,
1382 init: b'x',
1383 };
1384 assert!(t.rose(0));
1385 assert!(!t.stable(0));
1386 assert!(!t.rose(1) && !t.fell(1) && t.changed(1));
1388 assert!(t.fell(2));
1389 }
1390
1391 #[test]
1395 fn past_counts_gated_ticks() {
1396 let q = bits("0101100101");
1397 let tick = [
1399 false, true, true, false, true, false, false, true, false, false,
1400 ];
1401 let t = Trace {
1402 preponed: &q,
1403 tick: &tick,
1404 init: b'x',
1405 };
1406 assert_eq!(t.past(9, 1), q[7]);
1409 assert_eq!(t.past(9, 2), q[4]);
1410 assert_eq!(t.past(9, 4), q[1]);
1411 assert_eq!(t.past(9, 5), b'x');
1412 assert_eq!(t.past(4, 1), q[2]);
1414 assert_eq!(t.past(4, 3), b'x');
1415 }
1416 }
1417}
1418
1419#[cfg(test)]
1420mod tests {
1421 use super::*;
1422 use crate::state::compile_source;
1423
1424 fn hidden_of(src: &str) -> Result<Hidden, SampledError> {
1425 let design = compile_source(src).expect("valid SV source compiles");
1426 let body = design
1427 .top_instances()
1428 .next()
1429 .expect("one top instance")
1430 .instance_body()
1431 .expect("instance has a body");
1432 hidden_slots(&body)
1433 }
1434
1435 fn names(slots: &[HiddenSlot]) -> Vec<String> {
1436 slots.iter().map(|s| s.id.path().to_owned()).collect()
1437 }
1438
1439 #[test]
1442 fn rose_gets_p_cells_for_arg_and_clock_and_one_s_cell() {
1443 let h = hidden_of(
1444 "module top; logic clk, b, r;\n\
1445 always @(posedge clk) r <= $rose(b);\nendmodule\n",
1446 )
1447 .unwrap();
1448 assert_eq!(names(&h.p), ["top.b$p", "top.clk$p"]);
1449 assert_eq!(h.s.len(), 1);
1450 assert_eq!(h.s[0].source.path(), "top.b");
1451 assert_eq!(h.sites.len(), 1);
1452 assert_eq!(h.sites[0].shifts.len(), 1);
1454 assert_eq!(h.sites[0].shifts[0].from, p_id(&h.s[0].source));
1455 assert_eq!(h.sites[0].shifts[0].to, h.s[0].id);
1456 }
1457
1458 #[test]
1460 fn past_depth_n_keeps_n_cells_shifted_oldest_first() {
1461 let h = hidden_of(
1462 "module top; logic clk; logic [3:0] x, y;\n\
1463 always @(posedge clk) y <= $past(x, 3);\nendmodule\n",
1464 )
1465 .unwrap();
1466 assert_eq!(h.s.len(), 3);
1467 let sh = &h.sites[0].shifts;
1468 assert_eq!(sh.len(), 3);
1469 assert_eq!(sh[0].to, h.s[2].id);
1470 assert_eq!(sh[0].from, h.s[1].id);
1471 assert_eq!(sh[1].to, h.s[1].id);
1472 assert_eq!(sh[1].from, h.s[0].id);
1473 assert_eq!(sh[2].to, h.s[0].id);
1474 assert_eq!(sh[2].from, p_id(&h.s[0].source));
1475 }
1476
1477 #[test]
1479 fn sampled_needs_only_a_p_cell() {
1480 let h = hidden_of(
1481 "module top; logic clk, a, r;\n\
1482 always @(posedge clk) r <= $sampled(a);\nendmodule\n",
1483 )
1484 .unwrap();
1485 assert_eq!(names(&h.p), ["top.a$p"]);
1486 assert!(h.s.is_empty() && h.sites.is_empty());
1487 }
1488
1489 #[test]
1491 fn past_gate_signals_get_p_cells() {
1492 let h = hidden_of(
1493 "module top; logic clk, en, q, r;\n\
1494 always @(posedge clk) r <= $past(q, 2, en);\nendmodule\n",
1495 )
1496 .unwrap();
1497 assert_eq!(names(&h.p), ["top.clk$p", "top.en$p", "top.q$p"]);
1498 }
1499
1500 #[test]
1502 fn no_sampled_calls_no_hidden_cells() {
1503 let h = hidden_of("module top; logic clk, a; always @(posedge clk) a <= ~a;\nendmodule\n")
1504 .unwrap();
1505 assert!(h.is_empty() && h.sites.is_empty());
1506 }
1507
1508 #[test]
1510 fn enumeration_is_deterministic() {
1511 let src = "module top; logic clk, a, b, r;\n\
1512 always @(posedge clk) r <= $rose(a) ^ $fell(b) ^ $past(a, 2);\nendmodule\n";
1513 assert_eq!(hidden_of(src).unwrap(), hidden_of(src).unwrap());
1514 }
1515
1516 #[test]
1518 fn nested_sampled_calls_are_rejected() {
1519 let e = hidden_of(
1520 "module top; logic clk, a, r;\n\
1521 always @(posedge clk) r <= $past($rose(a));\nendmodule\n",
1522 )
1523 .map(|_| ())
1524 .unwrap_err();
1525 assert!(e.0.contains("nested"), "{e}");
1526 }
1527
1528 #[test]
1531 fn automatic_loop_variable_gets_no_cell() {
1532 let h = hidden_of(
1533 "module top; logic clk; logic [3:0] b, r1;\n\
1534 always @(posedge clk) for (int i = 0; i < 4; i++) r1[i] <= $past(b[i]);\n\
1535 endmodule\n",
1536 )
1537 .unwrap();
1538 assert_eq!(names(&h.p), ["top.b$p", "top.clk$p"]);
1539 assert_eq!(h.s.len(), 1);
1540 }
1541
1542 #[test]
1544 fn past_zero_ticks_is_rejected() {
1545 let e = hidden_of(
1546 "module top; logic clk, a, r;\n\
1547 always @(posedge clk) r <= $past(a, 0);\nendmodule\n",
1548 )
1549 .map(|_| ())
1550 .unwrap_err();
1551 assert!(e.0.contains("number_of_ticks"), "{e}");
1552 }
1553
1554 #[test]
1557 fn unsampleable_signal_types_are_rejected() {
1558 for (decl, call) in [
1559 ("logic [3:0] m [0:3]; logic r;", "$past(m[1])"),
1560 ("real x; logic r;", "$rose(x > 1.0)"),
1561 ("string s; logic r;", "$changed(s == \"a\")"),
1562 ] {
1563 let src = format!(
1564 "module top; logic clk; {decl}\n\
1565 always @(posedge clk) r <= {call};\nendmodule\n"
1566 );
1567 let e = hidden_of(&src).map(|_| ()).unwrap_err();
1568 assert!(
1569 e.0.contains("sampled value is not supported"),
1570 "{call}: {e}"
1571 );
1572 }
1573 }
1574
1575 #[test]
1578 fn user_function_call_in_the_argument_is_rejected() {
1579 let e = hidden_of(
1580 "module top; logic clk, a, r;\n\
1581 function logic f(logic x); return x; endfunction\n\
1582 always @(posedge clk) r <= $rose(f(a));\nendmodule\n",
1583 )
1584 .map(|_| ())
1585 .unwrap_err();
1586 assert!(e.0.contains("user subroutine call"), "{e}");
1587 }
1588
1589 #[test]
1592 fn hierarchical_reference_in_the_argument_gets_foreign_cells() {
1593 let h = hidden_of(
1594 "module top; logic clk, r;\n\
1595 sub u();\n\
1596 always @(posedge clk) r <= $rose(u.v);\nendmodule\n\
1597 module sub; logic v; endmodule\n",
1598 )
1599 .expect("accepted");
1600 assert!(
1601 h.p.iter()
1602 .any(|c| c.foreign.as_ref().is_some_and(|f| f.required))
1603 );
1604 assert!(h.s.iter().all(|c| c.foreign.is_some()), "{:?}", h.s);
1605 }
1606
1607 #[test]
1610 fn use_views_pick_the_cell_each_function_reads() {
1611 let design = compile_source(
1612 "module top; logic clk, a, b, c, r;\n\
1613 always @(posedge clk) begin\n\
1614 r <= $sampled(a); r <= $rose(b); r <= $past(c, 3);\n\
1615 end\nendmodule\n",
1616 )
1617 .expect("compiles");
1618 let body = design
1619 .top_instances()
1620 .next()
1621 .unwrap()
1622 .instance_body()
1623 .unwrap();
1624 let block = crate::netlist::module_items(&body)
1625 .into_iter()
1626 .find(|m| m.kind() == SymbolKind::ProceduralBlock)
1627 .expect("one procedural block");
1628 let owner = block.uid();
1629 let sites = sites_of_block(&block).unwrap();
1630 assert_eq!(sites.len(), 3);
1631 let views: Vec<UseViews> = sites
1632 .iter()
1633 .map(|s| use_views(&owner, &s.call, &s.arg_signals))
1634 .collect();
1635 let a = body.find("a").unwrap().uid();
1636 let b = body.find("b").unwrap().uid();
1637 let c = body.find("c").unwrap().uid();
1638 let loc = |i: usize| site_loc(&sites[i].call.call);
1639 assert_eq!(views[0].cur, [(a.clone(), p_id(&a))]);
1640 assert!(views[0].prev.is_empty());
1641 assert_eq!(views[1].cur, [(b.clone(), p_id(&b))]);
1642 assert_eq!(views[1].prev, [(b.clone(), s_id(&owner, &loc(1), &b, 0))]);
1643 assert_eq!(views[2].cur, [(c.clone(), s_id(&owner, &loc(2), &c, 2))]);
1644 assert!(views[2].prev.is_empty());
1645 }
1646
1647 #[test]
1650 fn relocate_clock_finds_the_same_event_in_another_instance() {
1651 let design = compile_source(
1652 "module sub(input logic clk, input logic d, output logic r);\n\
1653 always @(negedge clk) r <= $rose(d);\nendmodule\n\
1654 module top; logic clk, d, r1, r2;\n\
1655 sub u1(clk, d, r1); sub u2(clk, d, r2);\nendmodule\n",
1656 )
1657 .expect("compiles");
1658 let top = design
1659 .top_instances()
1660 .next()
1661 .unwrap()
1662 .instance_body()
1663 .unwrap();
1664 let block_of = |inst: &str| {
1665 let body = top.find(inst).unwrap().instance_body().unwrap();
1666 crate::netlist::module_items(&body)
1667 .into_iter()
1668 .find(|m| m.kind() == SymbolKind::ProceduralBlock)
1669 .unwrap()
1670 };
1671 let (b1, b2) = (block_of("u1"), block_of("u2"));
1672 let ev1 = {
1673 let mut found = None;
1674 walk(
1675 crate::process::cfg::proc_body(&b1).unwrap().as_sem_node(),
1676 &mut |n| {
1677 if found.is_none() {
1678 found = n.as_timing_control();
1679 }
1680 found.is_none()
1681 },
1682 );
1683 clock_event_of(found.expect("the always has an event control")).unwrap()
1684 };
1685 let ev2 = relocate_clock(&b2, &ev1).unwrap();
1686 assert_eq!(ev2.edge, EdgeKind::NegEdge);
1687 assert_eq!(
1688 ev2.signal.referenced_symbol().unwrap().uid().path(),
1689 "top.u2.clk"
1690 );
1691 assert_eq!(
1692 ev1.signal.referenced_symbol().unwrap().uid().path(),
1693 "top.u1.clk"
1694 );
1695 }
1696
1697 #[test]
1701 fn gclk_spellings_and_explicit_global_clock_event_decode_alike() {
1702 let design = compile_source(
1703 "module top; logic gclk; logic [3:0] v, r;\n\
1704 global clocking sys @(posedge gclk); endclocking\n\
1705 always @(posedge gclk) begin\n\
1706 r <= $past_gclk(v); r <= $past(v, , , @$global_clock);\n\
1707 r <= $rose_gclk(v); r <= $rose(v, @$global_clock);\n\
1708 r <= $past_gclk(v) + $past(v, , , @(posedge gclk));\n\
1709 end\nendmodule\n",
1710 )
1711 .expect("compiles");
1712 let body = design
1713 .top_instances()
1714 .next()
1715 .unwrap()
1716 .instance_body()
1717 .unwrap();
1718 let block = crate::netlist::module_items(&body)
1719 .into_iter()
1720 .find(|m| m.kind() == SymbolKind::ProceduralBlock)
1721 .unwrap();
1722 let sites = sites_of_block(&block).unwrap();
1723 let shape: Vec<(SampledOp, bool, bool)> = sites
1724 .iter()
1725 .map(|s| (s.call.op, s.call.global, s.call.clock.is_some()))
1726 .collect();
1727 assert_eq!(
1728 shape,
1729 [
1730 (SampledOp::Past, true, false),
1731 (SampledOp::Past, true, false),
1732 (SampledOp::Rose, true, false),
1733 (SampledOp::Rose, true, false),
1734 (SampledOp::Past, true, false),
1735 (SampledOp::Past, false, true),
1737 ]
1738 );
1739 let h = hidden_slots(&body).unwrap();
1741 assert!(names(&h.p).contains(&"top.gclk$p".to_owned()), "{:?}", h.p);
1742 }
1743
1744 #[test]
1746 fn explicit_clock_signals_get_p_cells() {
1747 let h = hidden_of(
1748 "module top; logic clk, clk2, b, r;\n\
1749 always @(posedge clk) r <= $rose(b, @(posedge clk2));\nendmodule\n",
1750 )
1751 .unwrap();
1752 assert!(names(&h.p).contains(&"top.clk2$p".to_owned()));
1753 }
1754}