vj-sim 0.2.0

Netlist, scheduler and process model of the vj SystemVerilog simulator.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
//! Interface `extern` tasks and functions (IEEE 1800-2023 25.7): a method
//! declared in an interface body with `extern task`/`extern function`/
//! `extern forkjoin task`, exported by the modules that connect to the
//! interface through a modport (`export put;` ... `task b.put(...)`), and
//! called through the interface (`b.put(...)`).
//!
//! slang models the declaration as a `MethodPrototype` symbol with the
//! `INTERFACE_EXTERN` flag whose `method_prototype_subroutine()` is a
//! synthesized body-less stub, and every module-side definition as a
//! `Subroutine` that is not a member of any scope (it is reachable only
//! through the prototype's implementation list, newest first). A call
//! expression's `call_subroutine()` is the stub; this module maps it to the
//! implementations the call runs.

use crate::symid::SymbolUid;
use sv_lang::{Expression, MethodFlags, Symbol};
use sv_lang_kinds::{ExpressionKind, SymbolKind};

/// The implementations an interface `extern` call runs.
#[derive(Clone)]
pub struct ExternTargets<'d> {
    /// The interface's `extern` prototype.
    pub proto: Symbol<'d>,
    /// Every module-side definition, in connection (elaboration) order.
    pub impls: Vec<Symbol<'d>>,
    /// `extern forkjoin task` (25.7.5): every implementation runs, as the
    /// branches of a `fork`...`join`.
    pub fork_join: bool,
}

/// The `INTERFACE_EXTERN` prototype `sym` stands for -- `sym` itself, or the
/// prototype of the stub subroutine -- if it is one.
pub fn extern_prototype<'d>(sym: &Symbol<'d>) -> Option<Symbol<'d>> {
    // Any other symbol is no subroutine, so no extern prototype.
    let proto = if sym.kind() == SymbolKind::MethodPrototype {
        *sym
    } else if sym.kind() == SymbolKind::Subroutine {
        sym.subroutine_prototype()?
    } else {
        return None;
    };
    (proto.kind() == SymbolKind::MethodPrototype
        && proto
            .method_prototype_flags()
            .contains(MethodFlags::INTERFACE_EXTERN))
    .then_some(proto)
}

/// The implementations of the interface `extern` method `callee` (the stub
/// or its prototype); `None` if `callee` is not one.
pub fn extern_targets<'d>(callee: &Symbol<'d>) -> Option<ExternTargets<'d>> {
    let proto = extern_prototype(callee)?;
    let mut impls = Vec::new();
    let mut cur = proto.method_prototype_first_extern_impl();
    while let Some(i) = cur {
        impls.push(i.implementation());
        cur = i.next();
    }
    // `addExternImpl` prepends: restore connection order.
    impls.reverse();
    Some(ExternTargets {
        proto,
        impls,
        fork_join: proto
            .method_prototype_flags()
            .contains(MethodFlags::FORK_JOIN),
    })
}

/// The module-side `extern` method definitions (`task b.put(...)`) of the
/// instance body `body`: slang keeps them out of the body's member list, so
/// they are found through the interface instances its interface ports connect
/// to, from the prototypes' implementation lists.
pub fn extern_impls_in<'d>(body: &Symbol<'d>) -> Vec<Symbol<'d>> {
    fn protos_of<'d>(inst: Symbol<'d>, out: &mut Vec<Symbol<'d>>) {
        if inst.kind() == SymbolKind::InstanceArray {
            for e in inst.members() {
                protos_of(e, out);
            }
        } else if let Some(b) = inst.instance_body() {
            out.extend(b.members().filter(|m| extern_prototype(m).is_some()));
        }
    }
    let mut protos = Vec::new();
    for port in body.members() {
        if port.kind() == SymbolKind::InterfacePort
            && let (Some(inst), _) = port.interface_port_connection()
        {
            protos_of(inst, &mut protos);
        }
    }
    let mut out: Vec<Symbol<'d>> = Vec::new();
    for p in protos {
        let mut cur = p.method_prototype_first_extern_impl();
        while let Some(i) = cur {
            let imp = i.implementation();
            if imp.parent().is_some_and(|b| b.uid() == body.uid())
                && !out.iter().any(|o| o.uid() == imp.uid())
            {
                out.push(imp);
            }
            cur = i.next();
        }
    }
    out.reverse();
    out
}

/// Forces the body of every module-side `extern` method definition
/// (`task b.put(...)`).
///
/// slang creates such a definition as a `Subroutine` that is never added to a
/// scope, so neither its own elaboration nor the freeze sweep of
/// `Compilation::compile` ever visits it: its body (and the lazily-resolved
/// types of everything in it) stay unbound, and a read of the sealed design
/// then finds no body. This lifts the seal once, binds each definition's body
/// and forces the same memos the sweep forces (declared types, canonical
/// expression types), and re-seals. Call it on a freshly compiled design,
/// before anything shares it.
pub fn force_extern_impl_bodies(design: &mut sv_lang::Design) {
    {
        let guard = design.unfreeze();
        for imp in all_impls(guard.design()) {
            imp.visit(|s| {
                if let Some(t) = s.declared_type() {
                    let _ = t.canonical();
                }
                let _ = s.initializer();
                sv_lang::Walk::Continue
            });
            if let Some(body) = imp.body() {
                let mut stack = vec![body.as_sem_node()];
                while let Some(n) = stack.pop() {
                    if let Some(e) = n.as_expression()
                        && let Some(t) = e.expr_type()
                    {
                        let _ = t.canonical();
                    }
                    stack.extend(n.children());
                }
            }
        }
    }
    // The folded value the sweep memoizes on every expression (a parameter
    // read has no other value): evaluating it caches it on the node.
    let eval = design.eval_session();
    for imp in all_impls(eval.design()) {
        let Some(body) = imp.body() else { continue };
        let mut stack = vec![body.as_sem_node()];
        while let Some(n) = stack.pop() {
            if let Some(e) = n.as_expression() {
                let _ = eval.eval_constant(e);
            }
            stack.extend(n.children());
        }
    }
}

/// Every module-side `extern` method definition of `design`.
fn all_impls<'d>(design: &'d sv_lang::Design) -> Vec<Symbol<'d>> {
    let mut impls = Vec::new();
    design.root().visit(|s| {
        if s.kind() == SymbolKind::MethodPrototype {
            let mut cur = s.method_prototype_first_extern_impl();
            while let Some(i) = cur {
                impls.push(i.implementation());
                cur = i.next();
            }
        }
        sv_lang::Walk::Continue
    });
    impls
}

/// The module-side `extern` method definitions whose body slang could not
/// bind (an `Invalid` statement or expression: a name the modport does not
/// expose, a call it rejects, ...), as `(definition path, offset)`.
///
/// slang collects its diagnostics once, when the design is compiled, and
/// never visits an out-of-line `extern` body then, so what it finds while
/// [`force_extern_impl_bodies`] binds the body is not in
/// `Design::diagnostics`; this is the substitute for that missing error.
pub fn invalid_impl_bodies(design: &sv_lang::Design) -> Vec<(String, usize)> {
    let mut impls = Vec::new();
    design.root().visit(|s| {
        if s.kind() == SymbolKind::MethodPrototype {
            let mut cur = s.method_prototype_first_extern_impl();
            while let Some(i) = cur {
                impls.push(i.implementation());
                cur = i.next();
            }
        }
        sv_lang::Walk::Continue
    });
    let mut out = Vec::new();
    for imp in impls {
        let Some(body) = imp.body() else { continue };
        let mut stack = vec![body.as_sem_node()];
        while let Some(n) = stack.pop() {
            let bad = n
                .as_statement()
                .map(|s| {
                    (s.kind() == sv_lang_kinds::StatementKind::Invalid)
                        .then(|| s.source_range().start.offset)
                })
                .or_else(|| {
                    n.as_expression().map(|e| {
                        (e.kind() == ExpressionKind::Invalid).then(|| e.source_range().start.offset)
                    })
                })
                .flatten();
            if let Some(off) = bad {
                out.push((imp.hierarchical_path(), off as usize));
                break;
            }
            stack.extend(n.children());
        }
    }
    out
}

/// Whether `e` calls an interface `extern` task through a virtual interface
/// (`vif.ping(...)`). Each candidate instance has its own implementations,
/// and no lowering path selects among them; slang only accepts such a call
/// for an `extern forkjoin` task (an implementation-less plain `extern` one is
/// its own error), so this is the one shape to reject loudly instead of
/// running the body-less stub as a silent no-op.
pub fn is_vif_extern_call(e: &Expression<'_>) -> bool {
    super::vif::call_targets(e).is_some()
        && e.call_subroutine()
            .is_some_and(|c| extern_prototype(&c).is_some())
}

/// The loud rejection of [`is_vif_extern_call`] calls.
pub fn vif_extern_message(e: &Expression<'_>) -> String {
    format!(
        "call to the interface extern task `{}` through a virtual interface is not supported: \
         selecting the implementations of the chosen instance (IEEE 1800-2023 25.7.5, 25.9) \
         is not implemented; call it through the interface instance instead",
        e.call_subroutine()
            .map(|c| c.name().to_string())
            .unwrap_or_default()
    )
}

/// The extern targets of the call expression `e`, if it calls an interface
/// `extern` method.
pub fn call_extern_targets<'d>(e: &Expression<'d>) -> Option<ExternTargets<'d>> {
    if e.kind() != ExpressionKind::Call
        || e.is_system_call() == Some(true)
        || super::vif::call_targets(e).is_some()
    {
        return None;
    }
    let mut t = extern_targets(&e.call_subroutine()?)?;
    if let Some(route) = module_route(e) {
        // 25.7.5: a call named through a module instance's interface port
        // (`top.mem1.a.Read`) runs that instance's definition only.
        let routed: Vec<Symbol<'d>> = t
            .impls
            .iter()
            .copied()
            .filter(|imp| imp.parent().is_some_and(|b| route.names(&b)))
            .collect();
        if !routed.is_empty() {
            t.impls = routed;
        }
    }
    Some(t)
}

/// The `instance.port` a call names its extern method through, from the
/// call's own source text (the binding exposes no name path on a call): the
/// two segments before the method name. `None` when the call names the
/// method on its own, or through an interface instance.
struct ModuleRoute {
    segments: Vec<String>,
    port: String,
}

impl ModuleRoute {
    /// Whether `body` is the instance this route names: its path ends with
    /// the route's instance segments and it has an interface port of the
    /// route's port name.
    fn names(&self, body: &Symbol<'_>) -> bool {
        let path = body.hierarchical_path();
        let want = self.segments.join(".");
        (path == want || path.ends_with(&format!(".{want}")))
            && body
                .members()
                .any(|m| m.kind() == SymbolKind::InterfacePort && m.name() == self.port)
    }
}

fn module_route(e: &Expression<'_>) -> Option<ModuleRoute> {
    let text = crate::process::cfg::source_text_range(e.source_range())?;
    let head = text.split('(').next()?.trim();
    let mut parts: Vec<&str> = head.split('.').map(str::trim).collect();
    let _method = parts.pop()?;
    if parts.len() < 2 {
        return None;
    }
    if !parts.iter().all(|p| {
        !p.is_empty()
            && p.chars()
                .all(|c| matches!(c, '_' | '$' | '[' | ']') || c.is_ascii_alphanumeric())
    }) {
        return None;
    }
    let port = parts.pop()?.to_owned();
    Some(ModuleRoute {
        segments: parts.into_iter().map(str::to_owned).collect(),
        port,
    })
}

/// Whether `sub` is a module-side definition of an interface `extern`
/// method (`task b.put(...)`), as opposed to the interface's own stub.
pub fn is_extern_impl(sub: &Symbol<'_>) -> bool {
    sub.kind() == SymbolKind::Subroutine
        && sub.body().is_some()
        && sub
            .subroutine_prototype()
            .is_some_and(|p| extern_prototype(&p).is_some())
        && extern_prototype(sub)
            .and_then(|p| p.method_prototype_subroutine())
            .is_none_or(|stub| stub.uid() != sub.uid())
}

/// The subroutines a call can run, as the walkers that follow calls see
/// them: the implementations of an interface `extern` method (every one, in
/// connection order), else the call's own callee.
pub fn call_callees<'d>(e: &Expression<'d>) -> Vec<Symbol<'d>> {
    if e.kind() != ExpressionKind::Call || e.is_system_call() == Some(true) {
        return Vec::new();
    }
    match call_extern_targets(e) {
        Some(t) => t.impls,
        None => e.call_subroutine().into_iter().collect(),
    }
}

/// The one subroutine a call's body is: `e`'s callee, with an interface
/// `extern` stub replaced by its first implementation (a non-forkjoin one
/// has exactly one, LRM 25.7.3; an `extern forkjoin` task is called through
/// [`call_extern_targets`] instead). `None` for a call with no callee or an
/// `extern forkjoin` task nothing implements.
pub fn call_target<'d>(e: &Expression<'d>) -> Option<Symbol<'d>> {
    match call_extern_targets(e) {
        Some(t) => t.impls.into_iter().next(),
        None => e.call_subroutine(),
    }
}

/// The disable-scope tag of the extern method `leaf` as activated through the
/// instance `inst`: a marker byte no source label can contain, the kind
/// (`m` = the module instance's own definition, `i` = the interface
/// instance's method, whichever module implements it), the instance's
/// hierarchical path and the method name.
fn scope_tag(kind: char, inst: &Symbol<'_>, leaf: &str) -> String {
    format!("\u{1}ext-{kind}:{}.{leaf}", inst.hierarchical_path())
}

/// Whether `label` is a scope tag ([`extern_scope_tags`]).
pub fn is_scope_tag(label: &str) -> bool {
    label.starts_with('\u{1}')
}

/// The scope tags an activation of the module-side definition `task` of an
/// interface `extern` method registers (LRM 25.7.4): the one naming its
/// module instance (`disable m1.a.slow;`) and the one naming the interface
/// instance (`disable b.slow;`). Empty if `task` is no such definition.
pub fn extern_scope_tags(task: &Symbol<'_>) -> Vec<String> {
    let Some(proto) = extern_prototype(task) else {
        return Vec::new();
    };
    fn inst_of<'d>(s: &Symbol<'d>) -> Option<Symbol<'d>> {
        s.parent()?.instance_body_parent_instance()
    }
    let (Some(module), Some(ifc)) = (inst_of(task), inst_of(&proto)) else {
        return Vec::new();
    };
    let leaf = task.name().to_string();
    vec![scope_tag('m', &module, &leaf), scope_tag('i', &ifc, &leaf)]
}

/// The scope tag `disable <path>;` written in the instance `requester` names
/// (LRM 25.7.4, 9.6.2): `b.slow` with `b` an interface instance (or interface
/// port) holding the `extern` method `slow` names the interface instance --
/// every call -- and `m1.a.slow` with `a` an interface port of the child
/// instance `m1` names `m1`'s own call. `None` for any other path.
pub fn extern_disable_tag(requester: &Symbol<'_>, path: &[String]) -> Option<String> {
    let (leaf, hops) = path.split_last()?;
    let mut cur = *requester;
    let mut via_port_of: Option<Symbol<'_>> = None;
    for (k, seg) in hops.iter().enumerate() {
        let body = cur.instance_body()?;
        let mut next = None;
        for m in body.members() {
            if m.name() != seg.as_str() {
                continue;
            }
            let kind = m.kind();
            if kind == SymbolKind::Instance {
                next = Some((m, None));
            } else if kind == SymbolKind::InterfacePort
                && let (Some(i), _) = m.interface_port_connection()
                && i.kind() == SymbolKind::Instance
            {
                // A port of the requester itself is the interface instance.
                next = Some((i, (k > 0).then_some(cur)));
            }
        }
        let (n, via) = next?;
        via_port_of = via;
        cur = n;
    }
    if let Some(module) = via_port_of {
        // `m1.a.slow`: the port hop was the last one.
        return Some(scope_tag('m', &module, leaf));
    }
    let body = cur.instance_body()?;
    body.members()
        .any(|m| m.name() == leaf.as_str() && extern_prototype(&m).is_some())
        .then(|| scope_tag('i', &cur, leaf))
}