Skip to main content

vj_sim/netlist/
itf.rs

1//! Interface `extern` tasks and functions (IEEE 1800-2023 25.7): a method
2//! declared in an interface body with `extern task`/`extern function`/
3//! `extern forkjoin task`, exported by the modules that connect to the
4//! interface through a modport (`export put;` ... `task b.put(...)`), and
5//! called through the interface (`b.put(...)`).
6//!
7//! slang models the declaration as a `MethodPrototype` symbol with the
8//! `INTERFACE_EXTERN` flag whose `method_prototype_subroutine()` is a
9//! synthesized body-less stub, and every module-side definition as a
10//! `Subroutine` that is not a member of any scope (it is reachable only
11//! through the prototype's implementation list, newest first). A call
12//! expression's `call_subroutine()` is the stub; this module maps it to the
13//! implementations the call runs.
14
15use crate::symid::SymbolUid;
16use sv_lang::{Expression, MethodFlags, Symbol};
17use sv_lang_kinds::{ExpressionKind, SymbolKind};
18
19/// The implementations an interface `extern` call runs.
20#[derive(Clone)]
21pub struct ExternTargets<'d> {
22    /// The interface's `extern` prototype.
23    pub proto: Symbol<'d>,
24    /// Every module-side definition, in connection (elaboration) order.
25    pub impls: Vec<Symbol<'d>>,
26    /// `extern forkjoin task` (25.7.5): every implementation runs, as the
27    /// branches of a `fork`...`join`.
28    pub fork_join: bool,
29}
30
31/// The `INTERFACE_EXTERN` prototype `sym` stands for -- `sym` itself, or the
32/// prototype of the stub subroutine -- if it is one.
33pub fn extern_prototype<'d>(sym: &Symbol<'d>) -> Option<Symbol<'d>> {
34    // Any other symbol is no subroutine, so no extern prototype.
35    let proto = if sym.kind() == SymbolKind::MethodPrototype {
36        *sym
37    } else if sym.kind() == SymbolKind::Subroutine {
38        sym.subroutine_prototype()?
39    } else {
40        return None;
41    };
42    (proto.kind() == SymbolKind::MethodPrototype
43        && proto
44            .method_prototype_flags()
45            .contains(MethodFlags::INTERFACE_EXTERN))
46    .then_some(proto)
47}
48
49/// The implementations of the interface `extern` method `callee` (the stub
50/// or its prototype); `None` if `callee` is not one.
51pub fn extern_targets<'d>(callee: &Symbol<'d>) -> Option<ExternTargets<'d>> {
52    let proto = extern_prototype(callee)?;
53    let mut impls = Vec::new();
54    let mut cur = proto.method_prototype_first_extern_impl();
55    while let Some(i) = cur {
56        impls.push(i.implementation());
57        cur = i.next();
58    }
59    // `addExternImpl` prepends: restore connection order.
60    impls.reverse();
61    Some(ExternTargets {
62        proto,
63        impls,
64        fork_join: proto
65            .method_prototype_flags()
66            .contains(MethodFlags::FORK_JOIN),
67    })
68}
69
70/// The module-side `extern` method definitions (`task b.put(...)`) of the
71/// instance body `body`: slang keeps them out of the body's member list, so
72/// they are found through the interface instances its interface ports connect
73/// to, from the prototypes' implementation lists.
74pub fn extern_impls_in<'d>(body: &Symbol<'d>) -> Vec<Symbol<'d>> {
75    fn protos_of<'d>(inst: Symbol<'d>, out: &mut Vec<Symbol<'d>>) {
76        if inst.kind() == SymbolKind::InstanceArray {
77            for e in inst.members() {
78                protos_of(e, out);
79            }
80        } else if let Some(b) = inst.instance_body() {
81            out.extend(b.members().filter(|m| extern_prototype(m).is_some()));
82        }
83    }
84    let mut protos = Vec::new();
85    for port in body.members() {
86        if port.kind() == SymbolKind::InterfacePort
87            && let (Some(inst), _) = port.interface_port_connection()
88        {
89            protos_of(inst, &mut protos);
90        }
91    }
92    let mut out: Vec<Symbol<'d>> = Vec::new();
93    for p in protos {
94        let mut cur = p.method_prototype_first_extern_impl();
95        while let Some(i) = cur {
96            let imp = i.implementation();
97            if imp.parent().is_some_and(|b| b.uid() == body.uid())
98                && !out.iter().any(|o| o.uid() == imp.uid())
99            {
100                out.push(imp);
101            }
102            cur = i.next();
103        }
104    }
105    out.reverse();
106    out
107}
108
109/// Forces the body of every module-side `extern` method definition
110/// (`task b.put(...)`).
111///
112/// slang creates such a definition as a `Subroutine` that is never added to a
113/// scope, so neither its own elaboration nor the freeze sweep of
114/// `Compilation::compile` ever visits it: its body (and the lazily-resolved
115/// types of everything in it) stay unbound, and a read of the sealed design
116/// then finds no body. This lifts the seal once, binds each definition's body
117/// and forces the same memos the sweep forces (declared types, canonical
118/// expression types), and re-seals. Call it on a freshly compiled design,
119/// before anything shares it.
120pub fn force_extern_impl_bodies(design: &mut sv_lang::Design) {
121    {
122        let guard = design.unfreeze();
123        for imp in all_impls(guard.design()) {
124            imp.visit(|s| {
125                if let Some(t) = s.declared_type() {
126                    let _ = t.canonical();
127                }
128                let _ = s.initializer();
129                sv_lang::Walk::Continue
130            });
131            if let Some(body) = imp.body() {
132                let mut stack = vec![body.as_sem_node()];
133                while let Some(n) = stack.pop() {
134                    if let Some(e) = n.as_expression()
135                        && let Some(t) = e.expr_type()
136                    {
137                        let _ = t.canonical();
138                    }
139                    stack.extend(n.children());
140                }
141            }
142        }
143    }
144    // The folded value the sweep memoizes on every expression (a parameter
145    // read has no other value): evaluating it caches it on the node.
146    let eval = design.eval_session();
147    for imp in all_impls(eval.design()) {
148        let Some(body) = imp.body() else { continue };
149        let mut stack = vec![body.as_sem_node()];
150        while let Some(n) = stack.pop() {
151            if let Some(e) = n.as_expression() {
152                let _ = eval.eval_constant(e);
153            }
154            stack.extend(n.children());
155        }
156    }
157}
158
159/// Every module-side `extern` method definition of `design`.
160fn all_impls<'d>(design: &'d sv_lang::Design) -> Vec<Symbol<'d>> {
161    let mut impls = Vec::new();
162    design.root().visit(|s| {
163        if s.kind() == SymbolKind::MethodPrototype {
164            let mut cur = s.method_prototype_first_extern_impl();
165            while let Some(i) = cur {
166                impls.push(i.implementation());
167                cur = i.next();
168            }
169        }
170        sv_lang::Walk::Continue
171    });
172    impls
173}
174
175/// The module-side `extern` method definitions whose body slang could not
176/// bind (an `Invalid` statement or expression: a name the modport does not
177/// expose, a call it rejects, ...), as `(definition path, offset)`.
178///
179/// slang collects its diagnostics once, when the design is compiled, and
180/// never visits an out-of-line `extern` body then, so what it finds while
181/// [`force_extern_impl_bodies`] binds the body is not in
182/// `Design::diagnostics`; this is the substitute for that missing error.
183pub fn invalid_impl_bodies(design: &sv_lang::Design) -> Vec<(String, usize)> {
184    let mut impls = Vec::new();
185    design.root().visit(|s| {
186        if s.kind() == SymbolKind::MethodPrototype {
187            let mut cur = s.method_prototype_first_extern_impl();
188            while let Some(i) = cur {
189                impls.push(i.implementation());
190                cur = i.next();
191            }
192        }
193        sv_lang::Walk::Continue
194    });
195    let mut out = Vec::new();
196    for imp in impls {
197        let Some(body) = imp.body() else { continue };
198        let mut stack = vec![body.as_sem_node()];
199        while let Some(n) = stack.pop() {
200            let bad = n
201                .as_statement()
202                .map(|s| {
203                    (s.kind() == sv_lang_kinds::StatementKind::Invalid)
204                        .then(|| s.source_range().start.offset)
205                })
206                .or_else(|| {
207                    n.as_expression().map(|e| {
208                        (e.kind() == ExpressionKind::Invalid).then(|| e.source_range().start.offset)
209                    })
210                })
211                .flatten();
212            if let Some(off) = bad {
213                out.push((imp.hierarchical_path(), off as usize));
214                break;
215            }
216            stack.extend(n.children());
217        }
218    }
219    out
220}
221
222/// Whether `e` calls an interface `extern` task through a virtual interface
223/// (`vif.ping(...)`). Each candidate instance has its own implementations,
224/// and no lowering path selects among them; slang only accepts such a call
225/// for an `extern forkjoin` task (an implementation-less plain `extern` one is
226/// its own error), so this is the one shape to reject loudly instead of
227/// running the body-less stub as a silent no-op.
228pub fn is_vif_extern_call(e: &Expression<'_>) -> bool {
229    super::vif::call_targets(e).is_some()
230        && e.call_subroutine()
231            .is_some_and(|c| extern_prototype(&c).is_some())
232}
233
234/// The loud rejection of [`is_vif_extern_call`] calls.
235pub fn vif_extern_message(e: &Expression<'_>) -> String {
236    format!(
237        "call to the interface extern task `{}` through a virtual interface is not supported: \
238         selecting the implementations of the chosen instance (IEEE 1800-2023 25.7.5, 25.9) \
239         is not implemented; call it through the interface instance instead",
240        e.call_subroutine()
241            .map(|c| c.name().to_string())
242            .unwrap_or_default()
243    )
244}
245
246/// The extern targets of the call expression `e`, if it calls an interface
247/// `extern` method.
248pub fn call_extern_targets<'d>(e: &Expression<'d>) -> Option<ExternTargets<'d>> {
249    if e.kind() != ExpressionKind::Call
250        || e.is_system_call() == Some(true)
251        || super::vif::call_targets(e).is_some()
252    {
253        return None;
254    }
255    let mut t = extern_targets(&e.call_subroutine()?)?;
256    if let Some(route) = module_route(e) {
257        // 25.7.5: a call named through a module instance's interface port
258        // (`top.mem1.a.Read`) runs that instance's definition only.
259        let routed: Vec<Symbol<'d>> = t
260            .impls
261            .iter()
262            .copied()
263            .filter(|imp| imp.parent().is_some_and(|b| route.names(&b)))
264            .collect();
265        if !routed.is_empty() {
266            t.impls = routed;
267        }
268    }
269    Some(t)
270}
271
272/// The `instance.port` a call names its extern method through, from the
273/// call's own source text (the binding exposes no name path on a call): the
274/// two segments before the method name. `None` when the call names the
275/// method on its own, or through an interface instance.
276struct ModuleRoute {
277    segments: Vec<String>,
278    port: String,
279}
280
281impl ModuleRoute {
282    /// Whether `body` is the instance this route names: its path ends with
283    /// the route's instance segments and it has an interface port of the
284    /// route's port name.
285    fn names(&self, body: &Symbol<'_>) -> bool {
286        let path = body.hierarchical_path();
287        let want = self.segments.join(".");
288        (path == want || path.ends_with(&format!(".{want}")))
289            && body
290                .members()
291                .any(|m| m.kind() == SymbolKind::InterfacePort && m.name() == self.port)
292    }
293}
294
295fn module_route(e: &Expression<'_>) -> Option<ModuleRoute> {
296    let text = crate::process::cfg::source_text_range(e.source_range())?;
297    let head = text.split('(').next()?.trim();
298    let mut parts: Vec<&str> = head.split('.').map(str::trim).collect();
299    let _method = parts.pop()?;
300    if parts.len() < 2 {
301        return None;
302    }
303    if !parts.iter().all(|p| {
304        !p.is_empty()
305            && p.chars()
306                .all(|c| matches!(c, '_' | '$' | '[' | ']') || c.is_ascii_alphanumeric())
307    }) {
308        return None;
309    }
310    let port = parts.pop()?.to_owned();
311    Some(ModuleRoute {
312        segments: parts.into_iter().map(str::to_owned).collect(),
313        port,
314    })
315}
316
317/// Whether `sub` is a module-side definition of an interface `extern`
318/// method (`task b.put(...)`), as opposed to the interface's own stub.
319pub fn is_extern_impl(sub: &Symbol<'_>) -> bool {
320    sub.kind() == SymbolKind::Subroutine
321        && sub.body().is_some()
322        && sub
323            .subroutine_prototype()
324            .is_some_and(|p| extern_prototype(&p).is_some())
325        && extern_prototype(sub)
326            .and_then(|p| p.method_prototype_subroutine())
327            .is_none_or(|stub| stub.uid() != sub.uid())
328}
329
330/// The subroutines a call can run, as the walkers that follow calls see
331/// them: the implementations of an interface `extern` method (every one, in
332/// connection order), else the call's own callee.
333pub fn call_callees<'d>(e: &Expression<'d>) -> Vec<Symbol<'d>> {
334    if e.kind() != ExpressionKind::Call || e.is_system_call() == Some(true) {
335        return Vec::new();
336    }
337    match call_extern_targets(e) {
338        Some(t) => t.impls,
339        None => e.call_subroutine().into_iter().collect(),
340    }
341}
342
343/// The one subroutine a call's body is: `e`'s callee, with an interface
344/// `extern` stub replaced by its first implementation (a non-forkjoin one
345/// has exactly one, LRM 25.7.3; an `extern forkjoin` task is called through
346/// [`call_extern_targets`] instead). `None` for a call with no callee or an
347/// `extern forkjoin` task nothing implements.
348pub fn call_target<'d>(e: &Expression<'d>) -> Option<Symbol<'d>> {
349    match call_extern_targets(e) {
350        Some(t) => t.impls.into_iter().next(),
351        None => e.call_subroutine(),
352    }
353}
354
355/// The disable-scope tag of the extern method `leaf` as activated through the
356/// instance `inst`: a marker byte no source label can contain, the kind
357/// (`m` = the module instance's own definition, `i` = the interface
358/// instance's method, whichever module implements it), the instance's
359/// hierarchical path and the method name.
360fn scope_tag(kind: char, inst: &Symbol<'_>, leaf: &str) -> String {
361    format!("\u{1}ext-{kind}:{}.{leaf}", inst.hierarchical_path())
362}
363
364/// Whether `label` is a scope tag ([`extern_scope_tags`]).
365pub fn is_scope_tag(label: &str) -> bool {
366    label.starts_with('\u{1}')
367}
368
369/// The scope tags an activation of the module-side definition `task` of an
370/// interface `extern` method registers (LRM 25.7.4): the one naming its
371/// module instance (`disable m1.a.slow;`) and the one naming the interface
372/// instance (`disable b.slow;`). Empty if `task` is no such definition.
373pub fn extern_scope_tags(task: &Symbol<'_>) -> Vec<String> {
374    let Some(proto) = extern_prototype(task) else {
375        return Vec::new();
376    };
377    fn inst_of<'d>(s: &Symbol<'d>) -> Option<Symbol<'d>> {
378        s.parent()?.instance_body_parent_instance()
379    }
380    let (Some(module), Some(ifc)) = (inst_of(task), inst_of(&proto)) else {
381        return Vec::new();
382    };
383    let leaf = task.name().to_string();
384    vec![scope_tag('m', &module, &leaf), scope_tag('i', &ifc, &leaf)]
385}
386
387/// The scope tag `disable <path>;` written in the instance `requester` names
388/// (LRM 25.7.4, 9.6.2): `b.slow` with `b` an interface instance (or interface
389/// port) holding the `extern` method `slow` names the interface instance --
390/// every call -- and `m1.a.slow` with `a` an interface port of the child
391/// instance `m1` names `m1`'s own call. `None` for any other path.
392pub fn extern_disable_tag(requester: &Symbol<'_>, path: &[String]) -> Option<String> {
393    let (leaf, hops) = path.split_last()?;
394    let mut cur = *requester;
395    let mut via_port_of: Option<Symbol<'_>> = None;
396    for (k, seg) in hops.iter().enumerate() {
397        let body = cur.instance_body()?;
398        let mut next = None;
399        for m in body.members() {
400            if m.name() != seg.as_str() {
401                continue;
402            }
403            let kind = m.kind();
404            if kind == SymbolKind::Instance {
405                next = Some((m, None));
406            } else if kind == SymbolKind::InterfacePort
407                && let (Some(i), _) = m.interface_port_connection()
408                && i.kind() == SymbolKind::Instance
409            {
410                // A port of the requester itself is the interface instance.
411                next = Some((i, (k > 0).then_some(cur)));
412            }
413        }
414        let (n, via) = next?;
415        via_port_of = via;
416        cur = n;
417    }
418    if let Some(module) = via_port_of {
419        // `m1.a.slow`: the port hop was the last one.
420        return Some(scope_tag('m', &module, leaf));
421    }
422    let body = cur.instance_body()?;
423    body.members()
424        .any(|m| m.name() == leaf.as_str() && extern_prototype(&m).is_some())
425        .then(|| scope_tag('i', &cur, leaf))
426}