use std::collections::{HashMap, HashSet};
use sv_lang::kinds::{ExpressionKind, SymbolKind};
use sv_lang::{ArgumentDirection, Expression, Symbol, SyntaxTree};
use vj_codegen::process_lower::{SignalSlot, SignalSlotClass};
use vj_sim::netlist::InstanceId;
use vj_sim::state::{DriveStrength, DriveStrengthPair, NetKind};
use vj_sim::symid::{SymbolId, SymbolUid};
pub(crate) type NetId = (InstanceId, SymbolId);
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub(crate) enum DriverKey {
Hidden(InstanceId, u32),
Assign(InstanceId, SymbolId, SymbolId),
Port(InstanceId, SymbolId, SymbolId),
Prim(InstanceId, SymbolId, SymbolId),
PrimElem(InstanceId, SymbolId, SymbolId, Vec<i64>),
SpecifyPath(InstanceId, SymbolId),
ProcVar(InstanceId, SymbolId),
}
type PendingSwitch = (
(InstanceId, SymbolId, u32),
(InstanceId, SymbolId, u32),
Conducts,
bool,
(Option<u32>, Option<u32>),
);
fn end_joint(n: NetId, row: Option<u32>) -> Joint {
match row {
Some(r) => Joint::Row(n, r),
None => Joint::Typed(n),
}
}
pub(crate) struct Group {
pub kind: NetKind,
pub width: u32,
pub nets: Vec<NetId>,
pub drivers: Vec<(u32, DriveStrengthPair)>,
pub switched: bool,
pub user: Option<usize>,
pub rows: Vec<(NetId, u32)>,
pub cells: Vec<u32>,
pub driver_rows: Vec<Option<u32>>,
}
#[derive(Clone)]
struct Driver {
key: DriverKey,
strength: DriveStrengthPair,
path: Option<ElemPath>,
}
struct OutCand {
child: NetId,
parents: Vec<NetId>,
port: (InstanceId, SymbolId),
keys: Vec<DriverKey>,
switches: Option<Vec<PendingSwitch>>,
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum Control {
Bit(InstanceId, SignalSlot, u32),
Const(u8),
Bank(u32),
}
#[derive(Clone, Debug)]
pub(crate) struct DelayedControl {
pub inst: InstanceId,
pub prim: SymbolId,
pub source: (InstanceId, SignalSlot, u32),
pub cell: u32,
pub on_is_one: bool,
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum Conducts {
Always,
When(Control, bool),
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Switch {
pub a: (usize, u32),
pub b: (usize, u32),
pub conducts: Conducts,
pub resistive: bool,
}
pub(crate) struct Network {
pub groups: Vec<usize>,
pub switches: Vec<Switch>,
}
#[derive(Default)]
pub(crate) struct Resolution {
pub groups: Vec<Group>,
pub contrib: HashMap<DriverKey, u32>,
pub bank_widths: Vec<u32>,
pub inout_ports: HashSet<(InstanceId, SymbolId)>,
pub networks: Vec<Network>,
pub ambiguous: HashMap<u32, u32>,
pub strength_src: HashMap<u32, vj_sim::image::StrengthSrc>,
pub strength_ticks: HashMap<usize, u32>,
pub bank_mem: HashMap<u32, SignalSlot>,
pub ic_elem: HashMap<IcElem, u32>,
pub open_pulls: Vec<(InstanceId, SymbolId, SymbolId, bool)>,
pub delayed_controls: Vec<DelayedControl>,
}
pub(crate) type IcElem = (InstanceId, SymbolId, Vec<i64>);
impl Resolution {
pub(crate) fn slot(&self, key: &DriverKey) -> Option<u32> {
self.contrib.get(key).copied()
}
}
fn strong() -> DriveStrengthPair {
DriveStrengthPair {
s0: DriveStrength::Strong,
s1: DriveStrength::Strong,
}
}
fn strength_of(p: sv_lang::DriveStrengthPair) -> DriveStrengthPair {
let one = |s: Option<sv_lang::DriveStrength>| match s {
None | Some(sv_lang::DriveStrength::Strong) => DriveStrength::Strong,
Some(sv_lang::DriveStrength::Supply) => DriveStrength::Supply,
Some(sv_lang::DriveStrength::Pull) => DriveStrength::Pull,
Some(sv_lang::DriveStrength::HighZ) => DriveStrength::HighZ,
Some(_) => DriveStrength::Weak,
};
DriveStrengthPair {
s0: one(p.strength0),
s1: one(p.strength1),
}
}
pub(crate) type DrivenWindows = HashMap<(InstanceId, SymbolId), Vec<Option<(u32, u32)>>>;
fn is_four_state_net(net: Symbol<'_>) -> bool {
net.value_type().is_none_or(|t| t.is_four_state())
}
pub(crate) fn driven_windows<'d>(
instances: &[(InstanceId, Symbol<'d>, Symbol<'d>, Option<InstanceId>)],
signals: &HashMap<InstanceId, HashMap<SymbolId, SignalSlot>>,
trees: &[SyntaxTree],
) -> DrivenWindows {
type Windows = DrivenWindows;
fn add<'d>(
out: &mut Windows,
id: InstanceId,
e: Expression<'d>,
own: &HashMap<SymbolId, SignalSlot>,
) {
for (root, window) in lvalue_windows(e, own) {
if root.kind() == SymbolKind::Net && is_four_state_net(root) {
out.entry((id, root.uid())).or_default().push(window);
}
}
}
let mut out: Windows = HashMap::new();
for (id, body, _, _) in instances {
for m in vj_sim::netlist::module_items(body) {
if m.kind() == SymbolKind::Net && m.initializer().is_some() {
out.entry((*id, m.uid())).or_default().push(None);
} else if m.kind() == SymbolKind::ContinuousAssign
&& let Some(lhs) = m.continuous_assign_assignment().and_then(|a| a.left())
{
add(&mut out, *id, lhs, &signals[id]);
}
}
for inst in crate::primitives::instances(body) {
if let Ok(sh) = crate::primitives::shape(&inst, trees) {
for o in sh.outputs {
add(&mut out, *id, o, &signals[id]);
}
}
}
}
for (id, _, inst, parent) in instances {
let Some(parent) = parent else {
continue;
};
for conn in inst.instance_port_connections() {
let port = conn.port();
if port.kind() == SymbolKind::InterfacePort {
continue;
}
let Some(expr) = conn.expression() else {
continue;
};
let member = port.port_internal_symbol().unwrap_or(port);
match port.port_direction() {
ArgumentDirection::In
if member.kind() == SymbolKind::Net && is_four_state_net(member) =>
{
out.entry((*id, member.uid())).or_default().push(None);
}
ArgumentDirection::Out => {
let lhs = if expr.kind() == ExpressionKind::Assignment {
expr.left()
} else {
Some(expr)
};
if let Some(lhs) = lhs {
add(&mut out, *parent, lhs, &signals[parent]);
}
}
ArgumentDirection::In | ArgumentDirection::InOut | ArgumentDirection::Ref => {}
}
}
}
out
}
fn memory_window(
e: Expression<'_>,
row_width: u32,
num_rows: u32,
lower_index: i64,
) -> Option<(u32, u32)> {
let mut row_sel = e;
loop {
let base = row_sel.select_value()?;
if !matches!(
base.kind(),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect
) {
break;
}
row_sel = base;
}
if row_sel.kind() != ExpressionKind::ElementSelect
|| row_sel.expr_type().is_none_or(|t| t.is_unpacked_array())
{
return None;
}
let row = row_sel
.selector()?
.constant_value()?
.as_i64()?
.checked_sub(lower_index)?;
if row < 0 || row >= i64::from(num_rows) {
return None;
}
let (lo, width) = vj_codegen::select::constant_window_skip(e, row_width, 1).ok()?;
Some((row as u32 * row_width + lo, width))
}
fn lvalue_windows<'d>(
e: Expression<'d>,
own: &HashMap<SymbolId, SignalSlot>,
) -> Vec<(Symbol<'d>, Option<(u32, u32)>)> {
let mut e = e;
while e.kind() == ExpressionKind::Conversion {
match e.operand() {
Some(o) => e = o,
None => break,
}
}
match e.kind() {
ExpressionKind::Concatenation => e
.operands()
.into_iter()
.flat_map(|o| lvalue_windows(o, own))
.collect(),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect => {
let mut base = e;
while let ExpressionKind::ElementSelect | ExpressionKind::RangeSelect = base.kind() {
match base.select_value() {
Some(b) => base = b,
None => break,
}
}
match base
.referenced_symbol()
.filter(|_| base.kind() == ExpressionKind::NamedValue)
{
Some(sym) => {
let window = own.get(&sym.uid()).and_then(|slot| match slot.class {
SignalSlotClass::Memory {
num_rows,
lower_index,
} => memory_window(e, slot.width, num_rows, lower_index),
_ => vj_codegen::select::constant_window(e, slot.width).ok(),
});
vec![(sym, window)]
}
None => lvalue_roots(e).into_iter().map(|r| (r, None)).collect(),
}
}
_ => lvalue_roots(e).into_iter().map(|r| (r, None)).collect(),
}
}
pub(crate) fn trireg_nets(body: &Symbol<'_>, trees: &[SyntaxTree]) -> Vec<SymbolId> {
vj_sim::netlist::module_items(body)
.into_iter()
.filter(|m| m.kind() == SymbolKind::Net && net_kind_of(m, trees) == NetKind::TriReg)
.map(|m| m.uid())
.collect()
}
pub(crate) fn charge_code_of(net: &Symbol<'_>, trees: &[SyntaxTree]) -> u8 {
use sv_lang::AstNode;
use sv_lang::kinds::{SyntaxKind, TokenKind};
use sv_lang::nodes::{NetDeclarationSyntax, NetStrengthSyntax};
for tree in trees {
let Some(mut node) = net.syntax(tree) else {
continue;
};
loop {
if node.kind() == SyntaxKind::NetDeclaration
&& let Some(n) = NetDeclarationSyntax::cast(node)
{
return match n.strength() {
Some(NetStrengthSyntax::ChargeStrength(c)) => {
match c.strength().map(|t| t.kind()) {
Some(TokenKind::SmallKeyword) => 5,
Some(TokenKind::LargeKeyword) => 6,
_ => 4,
}
}
_ => 4,
};
}
match node.parent() {
Some(p) => node = p,
None => break,
}
}
break;
}
4
}
pub(crate) fn expansion_hint_of(net: &Symbol<'_>, trees: &[SyntaxTree]) -> Option<bool> {
use sv_lang::AstNode;
use sv_lang::kinds::{SyntaxKind, TokenKind};
use sv_lang::nodes::NetDeclarationSyntax;
for tree in trees {
let Some(mut node) = net.syntax(tree) else {
continue;
};
loop {
if node.kind() == SyntaxKind::NetDeclaration
&& let Some(n) = NetDeclarationSyntax::cast(node)
{
return match n.expansion_hint().map(|t| t.kind()) {
Some(TokenKind::ScalaredKeyword) => Some(true),
Some(TokenKind::VectoredKeyword) => Some(false),
_ => None,
};
}
match node.parent() {
Some(p) => node = p,
None => break,
}
}
break;
}
None
}
pub(crate) fn net_kind_of(net: &Symbol<'_>, trees: &[SyntaxTree]) -> NetKind {
use sv_lang::AstNode;
use sv_lang::kinds::{SyntaxKind, TokenKind};
use sv_lang::nodes::{
ImplicitAnsiPortSyntax, NetDeclarationSyntax, PortDeclarationSyntax, PortHeaderSyntax,
};
let of_token = |tok: sv_lang::kinds::TokenKind| match tok {
TokenKind::TriKeyword => NetKind::Tri,
TokenKind::Tri0Keyword => NetKind::Tri0,
TokenKind::Tri1Keyword => NetKind::Tri1,
TokenKind::WAndKeyword => NetKind::WAnd,
TokenKind::TriAndKeyword => NetKind::TriAnd,
TokenKind::WOrKeyword => NetKind::WOr,
TokenKind::TriOrKeyword => NetKind::TriOr,
TokenKind::TriRegKeyword => NetKind::TriReg,
TokenKind::Supply0Keyword => NetKind::Supply0,
TokenKind::Supply1Keyword => NetKind::Supply1,
TokenKind::UWireKeyword => NetKind::UWire,
_ => NetKind::Wire,
};
if net.net_is_implicit()
&& let Some(k) = std::iter::successors(net.parent(), |s| s.parent())
.find_map(|s| s.default_net_type())
.map(|t| t.net_kind())
{
use sv_lang::NetKind as K;
match k {
K::WAnd => return NetKind::WAnd,
K::WOr => return NetKind::WOr,
K::Tri => return NetKind::Tri,
K::TriAnd => return NetKind::TriAnd,
K::TriOr => return NetKind::TriOr,
K::Tri0 => return NetKind::Tri0,
K::Tri1 => return NetKind::Tri1,
K::TriReg => return NetKind::TriReg,
K::Supply0 => return NetKind::Supply0,
K::Supply1 => return NetKind::Supply1,
K::UWire => return NetKind::UWire,
_ => {}
}
}
let header_kind = |h: PortHeaderSyntax<'_>| match h {
PortHeaderSyntax::NetPortHeader(h) => h.net_type().map(|t| of_token(t.kind())),
_ => None,
};
for tree in trees {
let Some(mut node) = net.syntax(tree) else {
continue;
};
loop {
if node.kind() == SyntaxKind::NetDeclaration
&& let Some(n) = NetDeclarationSyntax::cast(node)
&& let Some(tok) = n.net_type()
{
return of_token(tok.kind());
}
let port_kind = if let Some(p) = ImplicitAnsiPortSyntax::cast(node) {
header_kind(p.header())
} else if let Some(p) = PortDeclarationSyntax::cast(node) {
header_kind(p.header())
} else {
None
};
if let Some(k) = port_kind {
return k;
}
match node.parent() {
Some(p) => node = p,
None => break,
}
}
break;
}
NetKind::Wire
}
fn dominating_kind(internal: NetKind, external: NetKind) -> NetKind {
let index = |k: NetKind| match k {
NetKind::Wire | NetKind::Tri => Some(0),
NetKind::WAnd | NetKind::TriAnd => Some(1),
NetKind::WOr | NetKind::TriOr => Some(2),
NetKind::TriReg => Some(3),
NetKind::Tri0 => Some(4),
NetKind::Tri1 => Some(5),
NetKind::UWire => Some(6),
NetKind::Supply0 => Some(7),
NetKind::Supply1 => Some(8),
_ => None,
};
let (Some(i), Some(e)) = (index(internal), index(external)) else {
return external;
};
let internal_wins = match i {
1..=3 if e == 0 => true,
4 | 5 => e == 0 || e == 3,
6 => e <= 5,
7 | 8 => e <= 6,
_ => false,
};
if internal_wins { internal } else { external }
}
fn resolves_alone(kind: NetKind) -> bool {
matches!(
kind,
NetKind::Tri0
| NetKind::Tri1
| NetKind::WAnd
| NetKind::TriAnd
| NetKind::WOr
| NetKind::TriOr
| NetKind::Supply0
| NetKind::Supply1
| NetKind::TriReg
)
}
pub(crate) fn lvalue_roots<'d>(e: Expression<'d>) -> Vec<Symbol<'d>> {
let kind = e.kind();
if vj_sim::symid::is_value_name(kind) {
e.referenced_symbol().into_iter().collect()
} else if matches!(
kind,
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect
) {
e.select_value().map(lvalue_roots).unwrap_or_default()
} else if kind == ExpressionKind::MemberAccess {
e.operand().map(lvalue_roots).unwrap_or_default()
} else if kind == ExpressionKind::Concatenation {
e.operands().into_iter().flat_map(lvalue_roots).collect()
} else if kind == ExpressionKind::Conversion {
e.operand().map(lvalue_roots).unwrap_or_default()
} else {
Vec::new()
}
}
fn element_path(e: Expression<'_>) -> Option<ElemPath> {
let constant = |x: Option<Expression<'_>>| {
x.and_then(|s| s.constant_value())
.as_ref()
.and_then(sv_lang::ConstantValue::as_integer)
.and_then(sv_lang::SVInt::as_i64)
};
let mut idx: Vec<Option<i64>> = Vec::new();
let mut bits: Option<(i64, i64)> = None;
let mut cur = e;
loop {
let kind = cur.kind();
if kind == ExpressionKind::ElementSelect {
let base = cur.select_value()?;
if base.expr_type().is_some_and(|t| t.is_unpacked_array()) {
idx.push(constant(cur.selector()));
} else {
idx.clear();
bits = constant(cur.selector()).map(|c| (c, c));
}
cur = base;
} else if kind == ExpressionKind::RangeSelect {
let base = cur.select_value()?;
idx.clear();
bits = if base.expr_type().is_some_and(|t| t.is_unpacked_array()) {
None
} else {
range_bits(cur, constant(cur.range_left()), constant(cur.range_right()))
};
cur = base;
} else if kind == ExpressionKind::MemberAccess {
bits = None;
cur = cur.operand()?;
} else if kind == ExpressionKind::Conversion {
cur = cur.operand()?;
} else if vj_sim::symid::is_value_name(kind) {
break;
} else {
return None;
}
}
idx.reverse();
let full = idx.iter().all(Option::is_some);
Some(ElemPath {
idx: idx.into_iter().map_while(|i| i).collect(),
bits: bits.filter(|_| full),
})
}
fn range_bits(e: Expression<'_>, left: Option<i64>, right: Option<i64>) -> Option<(i64, i64)> {
let (left, right) = (left?, right?);
use vj_codegen::expr::RangeSelectionKindClass as K;
let kind = vj_codegen::expr::classify_range_selection_kind(e.range_selection_kind()?).ok()?;
if kind == K::Simple {
Some((left.min(right), left.max(right)))
} else if right <= 0 {
None
} else if kind == K::IndexedUp {
Some((left, left + right - 1))
} else {
Some((left - right + 1, left))
}
}
#[derive(Clone, Debug)]
struct ElemPath {
idx: Vec<i64>,
bits: Option<(i64, i64)>,
}
fn paths_overlap(a: &ElemPath, b: &ElemPath) -> bool {
let n = a.idx.len().min(b.idx.len());
if a.idx[..n] != b.idx[..n] {
return false;
}
match (a.bits, b.bits) {
(Some((alo, ahi)), Some((blo, bhi))) if a.idx.len() == b.idx.len() => {
alo <= bhi && blo <= ahi
}
_ => true,
}
}
struct Find<K = NetId>(HashMap<K, K>);
impl<K: Clone + Eq + std::hash::Hash> Find<K> {
fn root(&mut self, n: &K) -> K {
let mut cur = n.clone();
while let Some(p) = self.0.get(&cur).cloned() {
if p == cur {
break;
}
cur = p;
}
cur
}
fn union(&mut self, a: &K, b: &K) {
let (ra, rb) = (self.root(a), self.root(b));
if ra != rb {
self.0.insert(ra, rb);
}
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
enum Joint {
Interconnect(InstanceId, SymbolId, Vec<i64>),
Typed(NetId),
Row(NetId, u32),
}
fn is_memory(slot: &SignalSlot) -> bool {
matches!(slot.class, SignalSlotClass::Memory { .. })
}
fn interconnect_elements(dims: &[(i32, i32)], prefix: &[i64]) -> Vec<Vec<i64>> {
let mut out: Vec<Vec<i64>> = vec![Vec::new()];
for (d, &(l, r)) in dims.iter().enumerate() {
let idxs: Vec<i64> = match prefix.get(d) {
Some(&p) => vec![p],
None if l <= r => (i64::from(l)..=i64::from(r)).collect(),
None => (i64::from(r)..=i64::from(l)).rev().collect(),
};
out = out
.into_iter()
.flat_map(|t| {
idxs.iter().map(move |i| {
let mut t = t.clone();
t.push(*i);
t
})
})
.collect();
}
out
}
fn interconnect_dims_of(s: &Symbol<'_>) -> Option<Vec<(i32, i32)>> {
if s.kind() != SymbolKind::Net {
return None;
}
vj_sim::state::storage::interconnect_dims(&s.value_type()?)
}
pub(crate) fn ic_element<'d>(
e: Expression<'d>,
inst: InstanceId,
) -> Result<Option<(Symbol<'d>, IcElem)>, String> {
let e = connection_target(e);
if e.kind() != ExpressionKind::ElementSelect {
return Ok(None);
}
let roots = lvalue_roots(e);
let [root] = roots[..] else {
return Ok(None);
};
let Some(dims) = interconnect_dims_of(&root) else {
return Ok(None);
};
match element_path(e) {
Some(p) if p.idx.len() == dims.len() && p.bits.is_none() => {
Ok(Some((root, (inst, root.uid(), p.idx))))
}
_ => Err(format!(
"{}: a primitive terminal must select one element (every unpacked dimension, with constant indices) of an interconnect array (LRM 23.3.3.7.2: it is treated as a scalar wire)",
root.hierarchical_path()
)),
}
}
pub(crate) fn connects_interconnect<'d>(port: &Symbol<'d>, expr: Expression<'d>) -> bool {
let member = port.port_internal_symbol().unwrap_or(*port);
interconnect_dims_of(&member).is_some()
|| lvalue_roots(connection_target(expr))
.iter()
.any(|r| interconnect_dims_of(r).is_some())
}
pub(crate) fn connection_target<'d>(e: Expression<'d>) -> Expression<'d> {
let e = if e.kind() == ExpressionKind::Assignment {
e.left().unwrap_or(e)
} else {
e
};
if e.kind() == ExpressionKind::Conversion {
e.operand().unwrap_or(e)
} else {
e
}
}
type RowsOf = HashMap<NetId, Option<Vec<(u32, Vec<i64>)>>>;
fn net_rows(s: &Symbol<'_>, num_rows: u32) -> Option<Vec<(u32, Vec<i64>)>> {
let mut ty = s.value_type()?;
let mut dims: Vec<(i64, i64)> = Vec::new();
while ty.is_unpacked_array() {
let r = ty.fixed_unpacked_array_range();
dims.push((i64::from(r.left), i64::from(r.right)));
ty = ty.element_type()?;
}
let lens: Vec<i64> = dims.iter().map(|(l, r)| (l - r).abs() + 1).collect();
if lens.iter().product::<i64>() != i64::from(num_rows) {
return None;
}
let mut out: Vec<(i64, Vec<i64>)> = vec![(0, Vec::new())];
for (&(l, r), &n) in dims.iter().zip(&lens) {
let mut next = Vec::new();
for (row, idx) in &out {
for p in 0..n {
let (value, pos) = if l <= r {
(l + p, p)
} else {
(l - p, n - 1 - p)
};
let mut idx = idx.clone();
idx.push(value);
next.push((row * n + pos, idx));
}
}
out = next;
}
Some(
out.into_iter()
.map(|(row, idx)| (row as u32, idx))
.collect(),
)
}
fn elements_of(net: &NetId, rows_of: &RowsOf) -> Result<Vec<Joint>, String> {
match rows_of.get(net) {
None => Ok(vec![Joint::Typed(net.clone())]),
Some(Some(rows)) => Ok(rows
.iter()
.map(|(r, _)| Joint::Row(net.clone(), *r))
.collect()),
Some(None) => Err(format!(
"{}: an unpacked-array net of this shape joined to another net is not supported",
net.1.path()
)),
}
}
fn union_nets(
find: &mut Find<Joint>,
a: &NetId,
b: &NetId,
rows_of: &RowsOf,
) -> Result<(), String> {
let (ea, eb) = (elements_of(a, rows_of)?, elements_of(b, rows_of)?);
if ea.len() != eb.len() {
return Err(format!(
"{}: a connection of unpacked-array nets of {} and {} elements",
a.1.path(),
ea.len(),
eb.len()
));
}
for (x, y) in ea.iter().zip(&eb) {
find.union(x, y);
}
Ok(())
}
fn covers(path: &Option<ElemPath>, idx: &[i64]) -> bool {
path.as_ref()
.is_none_or(|p| p.idx.len() <= idx.len() && p.idx[..] == idx[..p.idx.len()])
}
struct Class {
root: Joint,
nets: Vec<NetId>,
rows: Vec<(NetId, u32)>,
cells: Vec<IcElem>,
owners: Vec<NetId>,
ds: Vec<(Driver, Option<u32>, NetId)>,
kind: NetKind,
user: Option<usize>,
switched: bool,
width: u32,
}
static TRACK_STRENGTHS: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
pub(crate) fn track_strengths() {
TRACK_STRENGTHS.store(true, std::sync::atomic::Ordering::Relaxed);
}
pub(crate) fn tracks_strengths() -> bool {
TRACK_STRENGTHS.load(std::sync::atomic::Ordering::Relaxed)
}
pub(crate) fn shows_strength(trees: &[SyntaxTree]) -> bool {
let has_v = |s: &str| {
let b = s.as_bytes();
let mut i = 0;
while i < b.len() {
if b[i] == b'%' {
let mut j = i + 1;
while j < b.len() && matches!(b[j], b'-' | b'#' | b'.' | b'0'..=b'9') {
j += 1;
}
if j < b.len() && matches!(b[j], b'v' | b'V') {
return true;
}
i = j.max(i + 1);
} else {
i += 1;
}
}
false
};
let mut found = false;
for tree in trees {
tree.root().visit_with_tokens(|c| {
if let sv_lang::Child::Token(t) = c
&& ((t.kind() == sv_lang::kinds::TokenKind::StringLiteral && has_v(t.text()))
|| matches!(
t.text(),
"nmos" | "pmos" | "cmos" | "rnmos" | "rpmos" | "rcmos"
| "$countdrivers"
| "$dumpports"
))
{
found = true;
return sv_lang::Walk::Break;
}
sv_lang::Walk::Continue
});
if found {
return true;
}
}
false
}
pub(crate) fn uses_dumpports(trees: &[SyntaxTree]) -> bool {
let mut found = false;
for tree in trees {
tree.root().visit_with_tokens(|c| {
if let sv_lang::Child::Token(t) = c
&& t.text() == "$dumpports"
{
found = true;
return sv_lang::Walk::Break;
}
sv_lang::Walk::Continue
});
if found {
return true;
}
}
false
}
fn uses_sdf(trees: &[SyntaxTree]) -> bool {
let mut found = false;
for tree in trees {
tree.root().visit_with_tokens(|c| {
if let sv_lang::Child::Token(t) = c
&& t.text() == "$sdf_annotate"
{
found = true;
return sv_lang::Walk::Break;
}
sv_lang::Walk::Continue
});
if found {
return true;
}
}
false
}
fn instance_reads_net<'d>(
instances: &[(InstanceId, Symbol<'d>, Symbol<'d>, Option<InstanceId>)],
id: InstanceId,
net: SymbolId,
) -> bool {
let mentions = |roots: Vec<sv_lang::SemNode<'d>>| {
crate::run::referenced_values(roots)
.iter()
.any(|s| s.uid() == net)
};
for (i, body, _, _) in instances {
if *i != id {
continue;
}
let mut roots = Vec::new();
for m in vj_sim::netlist::module_items(body) {
match m.kind() {
SymbolKind::ProceduralBlock => {
roots.extend(vj_sim::process::cfg::proc_body(&m).map(|b| b.as_sem_node()))
}
SymbolKind::ContinuousAssign => roots.extend(
m.continuous_assign_assignment()
.and_then(|a| a.right())
.map(|e| e.as_sem_node()),
),
SymbolKind::Net | SymbolKind::Variable => {
roots.extend(m.initializer().map(|e| e.as_sem_node()));
}
_ => {}
}
}
for prim in crate::primitives::instances(body) {
roots.extend(
prim.primitive_instance_port_connections()
.skip(1)
.map(|e| e.as_sem_node()),
);
}
if mentions(roots) {
return true;
}
}
let mut roots = Vec::new();
for (_, _, inst, parent) in instances {
if *parent != Some(id) {
continue;
}
for conn in inst.instance_port_connections() {
if conn.port().port_direction() != ArgumentDirection::Out
&& let Some(e) = conn.expression()
{
roots.push(e.as_sem_node());
}
}
}
mentions(roots)
}
pub(crate) fn analyze<'d>(
instances: &[(InstanceId, Symbol<'d>, Symbol<'d>, Option<InstanceId>)],
signals: &HashMap<InstanceId, HashMap<SymbolId, SignalSlot>>,
trees: &[SyntaxTree],
user: &crate::usernets::UserNets<'d>,
specify_dests: &HashSet<NetId>,
) -> Result<Resolution, String> {
let shows_strength = shows_strength(trees);
let windows = if specify_dests.is_empty() {
DrivenWindows::new()
} else {
driven_windows(instances, signals, trees)
};
let mut drivers: HashMap<NetId, Vec<Driver>> = HashMap::new();
let mut out_inherit: Vec<(NetId, DriverKey, NetId)> = Vec::new();
let mut kinds: HashMap<NetId, NetKind> = HashMap::new();
let mut net_syms: HashMap<NetId, Symbol<'d>> = HashMap::new();
let mut find = Find::<Joint>(HashMap::new());
let mut cell_drivers: HashMap<IcElem, Vec<Driver>> = HashMap::new();
let mut out = Resolution::default();
let is_net = |s: &Symbol<'_>| s.kind() == SymbolKind::Net;
for (id, body, _, _) in instances {
let own = &signals[id];
for m in vj_sim::netlist::module_items(body) {
let kind = m.kind();
if kind == SymbolKind::Net && own.contains_key(&m.uid()) {
let net = (*id, m.uid());
kinds.insert(
net.clone(),
if user.by_net.contains_key(&net) {
NetKind::UserDefined
} else {
net_kind_of(&m, trees)
},
);
net_syms.insert(net.clone(), m);
if m.initializer().is_some() {
drivers.entry(net).or_default().push(Driver {
key: DriverKey::Assign(*id, m.uid(), m.uid()),
strength: strength_of(m.net_drive_strength()),
path: None,
});
}
} else if kind == SymbolKind::ContinuousAssign
&& let Some(lhs) = m.continuous_assign_assignment().and_then(|a| a.left())
{
for root in lvalue_roots(lhs) {
if is_net(&root) && own.contains_key(&root.uid()) {
drivers.entry((*id, root.uid())).or_default().push(Driver {
key: DriverKey::Assign(*id, m.uid(), root.uid()),
strength: strength_of(m.continuous_assign_drive_strength()),
path: element_path(lhs),
});
}
}
}
}
for checker in vj_sim::netlist::checker_instances(body) {
for out in vj_sim::netlist::checker_outputs(&checker) {
let Some(lhs) = out.assign.and_then(|a| a.left()) else {
continue;
};
for root in lvalue_roots(lhs) {
if is_net(&root) && own.contains_key(&root.uid()) {
drivers.entry((*id, root.uid())).or_default().push(Driver {
key: DriverKey::Assign(*id, out.formal.uid(), root.uid()),
strength: strong(),
path: element_path(lhs),
});
}
}
}
}
for cb in vj_sim::netlist::module_items(body) {
if cb.kind() != SymbolKind::ClockingBlock {
continue;
}
for cv in cb.members() {
if cv.kind() != SymbolKind::ClockVar
|| cv.clock_var_direction() == ArgumentDirection::In
{
continue;
}
let Some(target) = cv.initializer() else {
continue;
};
for root in lvalue_roots(target) {
if !is_net(&root) || !own.contains_key(&root.uid()) {
continue;
}
let key = DriverKey::ProcVar(*id, root.uid());
let list = drivers.entry((*id, root.uid())).or_default();
if list.iter().all(|d| d.key != key) {
list.push(Driver {
key,
strength: strong(),
path: element_path(target),
});
}
}
}
}
for inst in crate::primitives::instances(body) {
if crate::primitives::is_switch(&inst, trees) {
continue;
}
let Ok(sh) = crate::primitives::shape(&inst, trees) else {
continue;
};
let strength = crate::primitives::strength(&inst, sh.gate, sh.resistive);
for out in sh.outputs {
if let Some((root, elem)) = ic_element(out, *id)? {
let key = DriverKey::PrimElem(*id, inst.uid(), root.uid(), elem.2.clone());
cell_drivers.entry(elem).or_default().push(Driver {
key,
strength,
path: None,
});
continue;
}
for root in lvalue_roots(out) {
if is_net(&root) && own.contains_key(&root.uid()) {
drivers.entry((*id, root.uid())).or_default().push(Driver {
key: DriverKey::Prim(*id, inst.uid(), root.uid()),
strength,
path: None,
});
}
}
}
}
}
let order = |a: &NetId, b: &NetId| (a.0.0, &a.1).cmp(&(b.0.0, &b.1));
let mut all: Vec<NetId> = kinds.keys().cloned().collect();
all.sort_by(order);
let mut rows_of: RowsOf = HashMap::new();
for n in &all {
if let SignalSlotClass::Memory { num_rows, .. } = signals[&n.0][&n.1].class {
rows_of.insert(n.clone(), net_rows(&net_syms[n], num_rows));
}
}
let row_idx: HashMap<(NetId, u32), &Vec<i64>> = rows_of
.iter()
.filter_map(|(n, rows)| rows.as_ref().map(|r| (n, r)))
.flat_map(|(n, rows)| rows.iter().map(move |(r, idx)| ((n.clone(), *r), idx)))
.collect();
let mut ic_wires: HashSet<NetId> = HashSet::new();
let mut ic_cells: Vec<IcElem> = Vec::new();
for (id, body, _, _) in instances {
for inst in crate::primitives::instances(body) {
for e in inst.primitive_instance_port_connections() {
if let Some((_, elem)) = ic_element(e, *id)? {
if !ic_cells.contains(&elem) {
ic_cells.push(elem);
}
continue;
}
for root in lvalue_roots(e) {
let Some(dims) = interconnect_dims_of(&root) else {
continue;
};
if !dims.is_empty() {
return Err(format!(
"{}: a primitive terminal connected to an interconnect array as a whole (LRM 23.3.3.7.2: a terminal is a scalar wire)",
inst.hierarchical_path()
));
}
ic_wires.insert((*id, root.uid()));
}
}
}
}
ic_cells.sort_by(|a, b| (a.0.0, &a.1, &a.2).cmp(&(b.0.0, &b.1, &b.2)));
let mut joints = Find::<Joint>(HashMap::new());
let mut joint_list: Vec<Joint> = Vec::new();
let mut joint_seen: HashSet<Joint> = HashSet::new();
let mut merges: Vec<(NetId, NetId)> = Vec::new();
let mut ic_ports: Vec<(InstanceId, SymbolId)> = Vec::new();
let mut select_port_switches: Vec<PendingSwitch> = Vec::new();
let mut out_cands: Vec<OutCand> = Vec::new();
for (id, _, inst, parent) in instances {
let Some(parent) = parent else {
continue;
};
for conn in inst.instance_port_connections() {
let port = conn.port();
if port.kind() == SymbolKind::InterfacePort {
continue;
}
let Some(expr) = conn.expression() else {
let pull_one = match inst
.instance_definition()
.and_then(|d| d.definition_unconnected_drive())
{
Some(sv_lang::UnconnectedDrive::Pull0) => Some(false),
Some(sv_lang::UnconnectedDrive::Pull1) => Some(true),
_ => None,
};
let member = port.port_internal_symbol().unwrap_or(port);
if let Some(one) = pull_one
&& port.port_direction() == ArgumentDirection::In
&& is_net(&member)
&& let Some(slot) = signals[id].get(&member.uid())
{
if is_memory(slot) {
return Err(format!(
"{}: `unconnected_drive on an unconnected unpacked-array input port `{}` is not supported",
inst.hierarchical_path(),
port.name()
));
}
drivers
.entry((*id, member.uid()))
.or_default()
.push(Driver {
key: DriverKey::Prim(*id, port.uid(), member.uid()),
strength: DriveStrengthPair {
s0: DriveStrength::Pull,
s1: DriveStrength::Pull,
},
path: None,
});
out.open_pulls.push((*id, port.uid(), member.uid(), one));
}
continue;
};
let member = port.port_internal_symbol().unwrap_or(port);
let target = connection_target(expr);
let roots = lvalue_roots(target);
let member_ic = interconnect_dims_of(&member);
let expr_ic = roots
.iter()
.find_map(|r| interconnect_dims_of(r).map(|d| (*r, d)));
if member_ic.is_some() || expr_ic.is_some() {
let what = || format!("{}: port `{}`", inst.hierarchical_path(), port.name());
let typed = |side: InstanceId, s: &Symbol<'d>| -> Result<Vec<Joint>, String> {
if is_net(s) && signals[&side].contains_key(&s.uid()) {
elements_of(&(side, s.uid()), &rows_of)
} else {
Err(format!(
"{}: an interconnect port is connected to something other than a whole net or an interconnect net: a variable, including a ref port's, may not be connected to an interconnect net or port (LRM 23.3.3.2, 6.6.8)",
what()
))
}
};
let member_side = match &member_ic {
Some(dims) => interconnect_elements(dims, &[])
.into_iter()
.map(|t| Joint::Interconnect(*id, member.uid(), t))
.collect(),
None => typed(*id, &member)?,
};
let expr_side = match &expr_ic {
Some((root, dims)) => {
if roots.len() != 1 {
return Err(format!(
"{}: an interconnect net in a concatenation on a port connection is not supported",
what()
));
}
let prefix = element_path(target).map(|p| p.idx).ok_or_else(|| {
format!("{}: an interconnect select that is not constant", what())
})?;
interconnect_elements(dims, &prefix)
.into_iter()
.map(|t| Joint::Interconnect(*parent, root.uid(), t))
.collect()
}
None => {
let whole = vj_sim::symid::is_value_name(target.kind())
.then(|| target.referenced_symbol())
.flatten();
match whole {
Some(s) => typed(*parent, &s)?,
None => {
return Err(format!(
"{}: an interconnect port is connected to an expression that is not a net (LRM 6.6.8)",
what()
));
}
}
}
};
if member_side.len() != expr_side.len() {
return Err(format!(
"{}: an interconnect connection of {} elements to {}",
what(),
member_side.len(),
expr_side.len()
));
}
for (a, b) in member_side.iter().zip(&expr_side) {
for j in [a, b] {
if joint_seen.insert(j.clone()) {
joint_list.push(j.clone());
}
}
joints.union(a, b);
}
ic_ports.push((*id, port.uid()));
continue;
}
if let Some(&child_ty) = user.by_net.get(&(*id, member.uid())) {
let parent_net = vj_sim::symid::is_value_name(target.kind())
.then(|| target.referenced_symbol())
.flatten()
.filter(|pn| is_net(pn) && signals[parent].contains_key(&pn.uid()));
if let Some(pn) = parent_net
&& let Some(&parent_ty) = user.by_net.get(&(*parent, pn.uid()))
{
if parent_ty != child_ty {
return Err(format!(
"{}: port `{}` of nettype `{}` is connected to net `{}` of nettype `{}` (LRM 23.3.3: both sides must be the same nettype)",
inst.hierarchical_path(),
port.name(),
user.types[child_ty].name,
pn.name(),
user.types[parent_ty].name
));
}
union_nets(
&mut find,
&(*id, member.uid()),
&(*parent, pn.uid()),
&rows_of,
)?;
out.inout_ports.insert((*id, port.uid()));
continue;
}
}
match port.port_direction() {
ArgumentDirection::In => {
if is_net(&member) && signals[id].contains_key(&member.uid()) {
drivers
.entry((*id, member.uid()))
.or_default()
.push(Driver {
key: DriverKey::Port(*id, port.uid(), member.uid()),
strength: strong(),
path: None,
});
}
}
ArgumentDirection::Out => {
let lhs = if expr.kind() == ExpressionKind::Assignment {
expr.left()
} else {
Some(expr)
};
if vj_sim::symid::is_value_name(target.kind())
&& let Some(pn) = target.referenced_symbol()
&& is_net(&pn)
&& is_net(&member)
&& !user.by_net.contains_key(&(*parent, pn.uid()))
&& !user.by_net.contains_key(&(*id, member.uid()))
&& let Some(ps) = signals[parent].get(&pn.uid())
&& let Some(cs) = signals[id].get(&member.uid())
&& !is_memory(ps)
&& !is_memory(cs)
&& ps.width == cs.width
{
out_cands.push(OutCand {
child: (*id, member.uid()),
parents: vec![(*parent, pn.uid())],
port: (*id, port.uid()),
keys: vec![DriverKey::Port(*id, port.uid(), pn.uid())],
switches: None,
});
} else if is_net(&member)
&& !user.by_net.contains_key(&(*id, member.uid()))
&& let Some(cs) = signals[id].get(&member.uid())
&& !is_memory(cs)
&& let Some((planned, bases)) = out_select_plan(
target,
cs.width,
(*id, member.uid()),
*parent,
&signals[parent],
&user.by_net,
)
{
out_cands.push(OutCand {
child: (*id, member.uid()),
keys: bases
.iter()
.map(|b| DriverKey::Port(*id, port.uid(), b.clone()))
.collect(),
parents: bases.into_iter().map(|b| (*parent, b)).collect(),
port: (*id, port.uid()),
switches: Some(planned),
});
}
for root in lhs.map(lvalue_roots).unwrap_or_default() {
if is_net(&root) && signals[parent].contains_key(&root.uid()) {
if is_net(&member) {
out_inherit.push((
(*parent, root.uid()),
DriverKey::Port(*id, port.uid(), root.uid()),
(*id, member.uid()),
));
}
drivers
.entry((*parent, root.uid()))
.or_default()
.push(Driver {
key: DriverKey::Port(*id, port.uid(), root.uid()),
strength: strong(),
path: lhs.and_then(element_path),
});
}
}
}
ArgumentDirection::InOut => {
if vj_sim::symid::is_value_name(target.kind())
&& let Some(pn) = target.referenced_symbol()
&& is_net(&pn)
&& is_net(&member)
&& signals[parent].contains_key(&pn.uid())
&& signals[id].contains_key(&member.uid())
{
union_nets(
&mut find,
&(*id, member.uid()),
&(*parent, pn.uid()),
&rows_of,
)?;
merges.push(((*id, member.uid()), (*parent, pn.uid())));
out.inout_ports.insert((*id, port.uid()));
} else if let Some((pn, row)) =
inout_element_target(target, signals, parent, &rows_of)
&& is_net(&pn)
&& is_net(&member)
&& !user.by_net.contains_key(&(*parent, pn.uid()))
&& !user.by_net.contains_key(&(*id, member.uid()))
&& signals[id].contains_key(&member.uid())
&& !rows_of.contains_key(&(*id, member.uid()))
&& kinds.get(&(*id, member.uid())) == kinds.get(&(*parent, pn.uid()))
&& signals[parent][&pn.uid()].width == signals[id][&member.uid()].width
{
find.union(
&Joint::Typed((*id, member.uid())),
&Joint::Row((*parent, pn.uid()), row),
);
out.inout_ports.insert((*id, port.uid()));
} else if let Some((sel, base)) = inout_select_target(target)
&& let Some(pn) = base.referenced_symbol()
&& is_net(&pn)
&& is_net(&member)
&& !user.by_net.contains_key(&(*parent, pn.uid()))
&& !user.by_net.contains_key(&(*id, member.uid()))
&& let Some(ps) = signals[parent].get(&pn.uid())
&& let Some(cs) = signals[id].get(&member.uid())
&& !is_memory(ps)
&& !is_memory(cs)
&& let Ok((lo, w)) = vj_codegen::select::constant_window(sel, ps.width)
&& w == cs.width
{
for k in 0..w {
select_port_switches.push((
(*id, member.uid(), k),
(*parent, pn.uid(), lo + k),
Conducts::Always,
false,
(None, None),
));
}
out.inout_ports.insert((*id, port.uid()));
} else if let Some(leaves) = inout_concat_leaves(target)
&& is_net(&member)
&& !user.by_net.contains_key(&(*id, member.uid()))
&& let Some(cs) = signals[id].get(&member.uid())
&& !is_memory(cs)
{
let mut planned: Vec<PendingSwitch> = Vec::new();
let mut top = cs.width;
let mut ok = true;
for (base, sel) in leaves {
let Some(pn) = base.referenced_symbol() else {
ok = false;
break;
};
let ps = signals[parent].get(&pn.uid());
let win = match (ps, sel) {
(Some(ps), None) if !is_memory(ps) => Some((0, ps.width)),
(Some(ps), Some(sel)) if !is_memory(ps) => {
vj_codegen::select::constant_window(sel, ps.width).ok()
}
_ => None,
};
let Some((lo, w)) = win else {
ok = false;
break;
};
if !is_net(&pn)
|| user.by_net.contains_key(&(*parent, pn.uid()))
|| w > top
{
ok = false;
break;
}
top -= w;
for k in 0..w {
planned.push((
(*id, member.uid(), top + k),
(*parent, pn.uid(), lo + k),
Conducts::Always,
false,
(None, None),
));
}
}
if ok && top == 0 {
select_port_switches.extend(planned);
out.inout_ports.insert((*id, port.uid()));
}
}
}
ArgumentDirection::Ref => {}
}
}
}
let mut ic_classes: Vec<(Joint, Vec<Joint>)> = Vec::new();
{
let mut index: HashMap<Joint, usize> = HashMap::new();
for j in &joint_list {
let r = joints.root(j);
let k = *index.entry(r.clone()).or_insert_with(|| {
ic_classes.push((r, Vec::new()));
ic_classes.len() - 1
});
ic_classes[k].1.push(j.clone());
}
}
for (_, members) in &ic_classes {
let typed: Vec<Joint> = members
.iter()
.filter_map(|j| match j {
Joint::Typed(_) | Joint::Row(..) => Some(j.clone()),
Joint::Interconnect(i, s, _) if ic_wires.contains(&(*i, s.clone())) => {
Some(Joint::Typed((*i, s.clone())))
}
Joint::Interconnect(i, s, t) if ic_cells.contains(&(*i, s.clone(), t.clone())) => {
Some(j.clone())
}
Joint::Interconnect(..) => None,
})
.collect();
let name = members
.iter()
.find_map(|j| match j {
Joint::Interconnect(_, s, _) => Some(s.path()),
Joint::Typed(_) | Joint::Row(..) => None,
})
.unwrap_or_default();
let Some(first) = typed
.iter()
.find(|j| !matches!(j, Joint::Interconnect(..)))
.or(typed.first())
else {
return Err(format!(
"interconnect net `{name}` is never connected to a net with a type: a simulated net of interconnect type is illegal (LRM 23.3.3.7.1)"
));
};
let owner = |j: &Joint| match j {
Joint::Typed(n) | Joint::Row(n, _) => n.clone(),
Joint::Interconnect(i, s, _) => (*i, s.clone()),
};
let width_of = |j: &Joint| match j {
Joint::Interconnect(..) => 1,
_ => signals[&owner(j).0][&owner(j).1].width,
};
let width = width_of(first);
let user_ty = match first {
Joint::Interconnect(..) => None,
_ => user.by_net.get(&owner(first)),
};
for j in &typed {
let n = owner(j);
let n_user = match j {
Joint::Interconnect(..) => None,
_ => user.by_net.get(&n),
};
if width_of(j) != width {
return Err(format!(
"interconnect net `{name}` joins nets of different widths ({} and {})",
n.1.path(),
owner(first).1.path()
));
}
if n_user != user_ty {
return Err(format!(
"interconnect net `{name}` joins nets of different net types ({} and {}) (LRM 23.3.3)",
n.1.path(),
owner(first).1.path()
));
}
find.union(first, j);
if !matches!(first, Joint::Interconnect(..)) && !matches!(j, Joint::Interconnect(..)) {
merges.push((owner(first), n));
}
}
}
out.inout_ports.extend(ic_ports);
for (id, body, inst, parent) in instances {
let ports: HashSet<SymbolId> = if parent.is_some() {
inst.instance_port_connections()
.map(|c| {
let p = c.port();
p.port_internal_symbol().unwrap_or(p).uid()
})
.collect()
} else {
HashSet::new()
};
for m in vj_sim::netlist::module_items(body) {
if interconnect_dims_of(&m).is_none()
|| ports.contains(&m.uid())
|| ic_wires.contains(&(*id, m.uid()))
|| ic_cells.iter().any(|(i, s, _)| i == id && *s == m.uid())
{
continue;
}
if !joint_seen
.iter()
.any(|j| matches!(j, Joint::Interconnect(i, s, _) if i == id && *s == m.uid()))
{
return Err(format!(
"{}: interconnect net `{}` is never connected to a net with a type: a simulated net of interconnect type is illegal (LRM 23.3.3.7.1)",
m.hierarchical_path(),
m.name()
));
}
}
}
let mut pending_switches: Vec<PendingSwitch> = select_port_switches;
for (id, body, _, _) in instances {
let own = &signals[id];
let mut join = |what: &str, refs: Vec<Expression<'d>>, at: &Symbol<'d>| {
let whole_net = |e: Expression<'d>| {
let e = connection_target(e);
vj_sim::symid::is_value_name(e.kind())
.then(|| e.referenced_symbol())
.flatten()
.filter(|s| is_net(s) && own.contains_key(&s.uid()))
};
if refs.iter().any(|&e| whole_net(e).is_none()) {
fn bits_of<'d>(
e: Expression<'d>,
own: &HashMap<SymbolId, SignalSlot>,
out: &mut Vec<(SymbolId, u32)>,
) -> bool {
let e = connection_target(e);
if e.kind() == ExpressionKind::Concatenation {
let mut ops = e.operands();
ops.reverse();
return ops.into_iter().all(|o| bits_of(o, own, out));
}
let Some((net, lo, w, None)) =
switch_terminal(e, own, &HashMap::new(), InstanceId(0))
else {
return false;
};
out.extend((0..w).map(|k| (net.clone(), lo + k)));
true
}
let mut lanes: Vec<Vec<(SymbolId, u32)>> = Vec::new();
for &e in &refs {
let mut bits = Vec::new();
if !bits_of(e, own, &mut bits) {
return Err(format!(
"{}: {what} of something other than nets, bit-selects, part-selects or concatenations of them (LRM 10.11)",
at.hierarchical_path()
));
}
lanes.push(bits);
}
if lanes.windows(2).any(|w| w[0].len() != w[1].len()) {
return Err(format!(
"{}: {what} of operands of different widths",
at.hierarchical_path()
));
}
if lanes
.iter()
.flatten()
.any(|(n, _)| user.by_net.contains_key(&(*id, n.clone())))
{
return Err(format!(
"{}: {what} of a bit-select or concatenation of a net of a user-defined nettype (only whole nets of one nettype alias, LRM 6.6.7)",
at.hierarchical_path()
));
}
for w in lanes.windows(2) {
for (a, b) in w[0].iter().zip(&w[1]) {
pending_switches.push((
(*id, a.0.clone(), a.1),
(*id, b.0.clone(), b.1),
Conducts::Always,
false,
(None, None),
));
}
}
return Ok(());
}
let mut nets = Vec::new();
for e in refs {
nets.push(whole_net(e).expect("checked above"));
}
let width = |n: &Symbol<'_>| own[&n.uid()].width;
if nets.windows(2).any(|w| width(&w[0]) != width(&w[1])) {
return Err(format!(
"{}: {what} of nets of different widths",
at.hierarchical_path()
));
}
for w in nets.windows(2) {
union_nets(&mut find, &(*id, w[0].uid()), &(*id, w[1].uid()), &rows_of)?;
}
Ok(())
};
for m in vj_sim::netlist::module_items(body) {
if m.kind() == SymbolKind::NetAlias {
join("an alias", m.net_alias_net_references().collect(), &m)?;
}
}
for inst in crate::primitives::instances(body) {
let Some(name) = crate::primitives::type_name(&inst, trees) else {
continue;
};
let (resistive, base) = match name.strip_prefix('r') {
Some(rest) if rest.starts_with("tran") => (true, rest.to_string()),
_ => (false, name.clone()),
};
if !base.starts_with("tran") {
continue;
}
let at = || inst.hierarchical_path();
let conns: Vec<Expression<'d>> = inst.primitive_instance_port_connections().collect();
let conducts_on = match base.as_str() {
"tran" => None,
"tranif1" => Some(true),
"tranif0" => Some(false),
other => return Err(format!("{}: unknown switch `{other}`", at())),
};
let expected = if conducts_on.is_some() { 3 } else { 2 };
if conns.len() != expected {
return Err(format!("{}: a switch with {} terminals", at(), conns.len()));
}
let (a_net, a_lo, a_w, a_row) = switch_terminal(conns[0], own, &rows_of, *id)
.ok_or_else(|| {
format!(
"{}: a switch terminal that is not a net or a bit of one",
at()
)
})?;
let (b_net, b_lo, b_w, b_row) = switch_terminal(conns[1], own, &rows_of, *id)
.ok_or_else(|| {
format!(
"{}: a switch terminal that is not a net or a bit of one",
at()
)
})?;
let (arr_n, arr_pos) =
switch_array_position(&inst).map_err(|e| format!("{}: {e}", at()))?;
let slice = |lo: u32, w: u32| {
array_slice(lo, w, arr_n, arr_pos).ok_or_else(|| {
format!(
"{}: a switch-array terminal of {w} bits does not divide among {arr_n} elements (LRM 28.3.5)",
at()
)
})
};
let (a_lo, a_w) = slice(a_lo, a_w)?;
let (b_lo, b_w) = slice(b_lo, b_w)?;
if a_w != b_w {
return Err(format!("{}: switch terminals of different widths", at()));
}
let is_user = |n: &SymbolId| user.by_net.contains_key(&(*id, n.clone()));
let n_bits = if is_user(&a_net) || is_user(&b_net) {
for (net, w) in [(&a_net, a_w), (&b_net, b_w)] {
if is_user(net) && w != own[net].width {
return Err(format!(
"{}: a switch terminal that is a bit-select of a net of a user-defined nettype (LRM 28.8: a switch connects whole nets)",
at()
));
}
}
1
} else {
a_w
};
let conducts = match conducts_on {
None => Conducts::Always,
Some(on) => {
let control = control_terminal(conns[2], *id, own, (arr_n, arr_pos))
.ok_or_else(|| {
format!(
"{}: a switch control that is not a signal bit or a constant",
at()
)
})?;
let control = match control {
Control::Bit(ci, cslot, cbit)
if inst.primitive_instance_delay().is_some() =>
{
if matches!(cslot.class, SignalSlotClass::Memory { .. }) {
return Err(format!(
"{}: a `{name}` switch with a turn-on/turn-off delay whose control is an element of an unpacked array (LRM 28.16) is not supported",
at()
));
}
let cell = out.bank_widths.len() as u32;
out.bank_widths.push(1);
out.delayed_controls.push(DelayedControl {
inst: *id,
prim: inst.uid(),
source: (ci, cslot, cbit),
cell,
on_is_one: on,
});
Control::Bank(cell)
}
c => c,
};
Conducts::When(control, on)
}
};
for k in 0..n_bits {
pending_switches.push((
(*id, a_net.clone(), a_lo + k),
(*id, b_net.clone(), b_lo + k),
conducts,
resistive,
(a_row, b_row),
));
}
}
}
let mut pending = std::mem::take(&mut out_cands);
let sdf = uses_sdf(trees);
let specified: HashSet<InstanceId> = instances
.iter()
.filter(|(_, body, _, _)| body.members().any(|m| m.kind() == SymbolKind::SpecifyBlock))
.map(|(id, ..)| *id)
.collect();
pending.retain(|c| !specified.contains(&c.child.0));
while !pending.is_empty() {
let mut per_root: HashMap<Joint, usize> = HashMap::new();
for (n, ds) in &drivers {
*per_root
.entry(find.root(&Joint::Typed(n.clone())))
.or_default() += ds.len();
}
let before = pending.len();
let mut rest = Vec::new();
for c in pending {
let ck = kinds[&c.child];
let mut roots: Vec<Joint> = Vec::new();
for p in &c.parents {
let r = find.root(&Joint::Typed(p.clone()));
if !roots.contains(&r) {
roots.push(r);
}
}
let total: usize = roots
.iter()
.map(|r| per_root.get(r).copied().unwrap_or(0))
.sum();
let own: usize = c
.parents
.iter()
.collect::<HashSet<_>>()
.into_iter()
.map(|p| {
drivers.get(p).map_or(0, |ds| {
ds.iter().filter(|d| c.keys.contains(&d.key)).count()
})
})
.sum();
let kinds_ok = c
.parents
.iter()
.all(|p| ck == kinds[p] || ck == NetKind::Wire || kinds[p] == NetKind::Wire);
if total - own == 0 || !kinds_ok {
rest.push(c);
continue;
}
if sdf && instance_reads_net(instances, c.child.0, c.child.1.clone()) {
return Err(format!(
"the child of output port `{}` reads its own port net, which shares its net with drivers outside the child, which port collapsing (LRM 23.3.3.7) would merge into one net with the parent's other drivers, but this design calls $sdf_annotate and SDF INTERCONNECT names sources by output port; the combination is not supported (drive the parent net through a separate net, or drop the annotation)",
c.port.1.path()
));
}
if sdf {
rest.push(c);
continue;
}
match &c.switches {
None => {
union_nets(&mut find, &c.child, &c.parents[0], &rows_of)?;
merges.push((c.child.clone(), c.parents[0].clone()));
}
Some(sw) => pending_switches.extend(sw.iter().cloned()),
}
for p in &c.parents {
if let Some(ds) = drivers.get_mut(p) {
ds.retain(|d| !c.keys.contains(&d.key));
}
}
out_inherit.retain(|(_, k, _)| !c.keys.contains(k));
out.inout_ports.insert(c.port);
}
if rest.len() == before {
break;
}
pending = rest;
}
let mut collapse: Find<NetId> = Find(HashMap::new());
let mut collapsed: HashMap<NetId, NetKind> = HashMap::new();
for (internal, external) in &merges {
let (ri, re) = (collapse.root(internal), collapse.root(external));
let ki = collapsed.get(&ri).copied().unwrap_or(kinds[internal]);
let ke = collapsed.get(&re).copied().unwrap_or(kinds[external]);
let k = dominating_kind(ki, ke);
if ri != re {
collapse.union(internal, external);
}
let root = collapse.root(internal);
collapsed.insert(root, k);
}
let mut elems: Vec<Joint> = Vec::new();
let mut legacy_mem: Vec<NetId> = Vec::new();
for n in &all {
match rows_of.get(n) {
Some(Some(rows)) => {
elems.extend(rows.iter().map(|(r, _)| Joint::Row(n.clone(), *r)));
}
Some(None) => legacy_mem.push(n.clone()),
None => elems.push(Joint::Typed(n.clone())),
}
}
elems.extend(
ic_cells
.iter()
.map(|(i, s, t)| Joint::Interconnect(*i, s.clone(), t.clone())),
);
for _ in 0..out_inherit.len().min(32) {
let mut changed = false;
for (pnet, key, cnet) in &out_inherit {
let Some(cs) = drivers.get(cnet) else {
continue;
};
let Some(first) = cs.first().map(|d| d.strength) else {
continue;
};
if cs.iter().any(|d| d.strength != first) {
continue;
}
if let Some(d) = drivers
.get_mut(pnet)
.and_then(|v| v.iter_mut().find(|d| d.key == *key))
&& d.strength != first
{
d.strength = first;
changed = true;
}
}
if !changed {
break;
}
}
for n in &legacy_mem {
let paths: Vec<&ElemPath> = drivers
.get(n)
.map(|ds| ds.iter().filter_map(|d| d.path.as_ref()).collect())
.unwrap_or_default();
for (i, a) in paths.iter().enumerate() {
if paths[i + 1..].iter().any(|b| paths_overlap(a, b)) {
return Err(format!(
"{}: two continuous drivers of one element of the unpacked-array net `{}` are not supported (an unpacked-array net's elements are not resolved)",
n.1.path(),
n.1.path()
));
}
}
}
let mut classes: Vec<Class> = Vec::new();
{
let mut index: HashMap<Joint, usize> = HashMap::new();
let mut members: Vec<Vec<Joint>> = Vec::new();
let mut roots: Vec<Joint> = Vec::new();
for j in &elems {
let r = find.root(j);
let k = *index.entry(r.clone()).or_insert_with(|| {
members.push(Vec::new());
roots.push(r);
members.len() - 1
});
members[k].push(j.clone());
}
let switched_roots: HashSet<Joint> = pending_switches
.iter()
.flat_map(|(a, b, _, _, (ra, rb))| {
[((a.0, a.1.clone()), *ra), ((b.0, b.1.clone()), *rb)]
})
.map(|(n, row)| find.root(&end_joint(n, row)))
.collect();
for (root, js) in roots.into_iter().zip(members) {
let mut nets = Vec::new();
let mut rows = Vec::new();
let mut cells: Vec<IcElem> = Vec::new();
let mut owners: Vec<NetId> = Vec::new();
for j in &js {
let n = match j {
Joint::Typed(n) => {
nets.push(n.clone());
n
}
Joint::Row(n, r) => {
rows.push((n.clone(), *r));
n
}
Joint::Interconnect(i, s, t) => {
cells.push((*i, s.clone(), t.clone()));
continue;
}
};
if !owners.contains(n) {
owners.push(n.clone());
}
}
let mut ds = Vec::new();
for cell in &cells {
for d in cell_drivers
.get(cell)
.map(Vec::as_slice)
.unwrap_or_default()
{
ds.push((d.clone(), None, (cell.0, cell.1.clone())));
}
}
for o in &owners {
for d in drivers.get(o).map(Vec::as_slice).unwrap_or_default() {
if nets.contains(o) {
ds.push((d.clone(), None, o.clone()));
}
for (n, r) in rows.iter().filter(|(n, _)| n == o) {
if covers(&d.path, row_idx[&(n.clone(), *r)]) {
ds.push((d.clone(), Some(*r), o.clone()));
}
}
}
}
let kind = owners
.iter()
.map(|n| {
collapsed
.get(&collapse.root(n))
.copied()
.unwrap_or(kinds[n])
})
.find(|k| *k != NetKind::Wire)
.unwrap_or(NetKind::Wire);
let user_ty = owners.iter().find_map(|n| user.by_net.get(n).copied());
let switched = switched_roots.contains(&root);
let width = owners.first().map_or(1, |o| signals[&o.0][&o.1].width);
classes.push(Class {
root,
nets,
rows,
cells,
owners,
ds,
kind,
user: user_ty,
switched,
width,
});
}
}
let mut made: Vec<bool> = Vec::with_capacity(classes.len());
for c in &classes {
let first_name = c
.owners
.first()
.map(|o| o.1.path())
.or_else(|| c.cells.first().map(|e| e.1.path()))
.unwrap_or_default();
if let Some(t) = c.user {
let ty = &user.types[t];
if ty.func.is_none() && c.ds.len() > 1 {
return Err(format!(
"{}: net of nettype `{}` has {} drivers, but the nettype has no resolution function (LRM 6.6.7: at most one driver)",
first_name,
ty.name,
c.ds.len()
));
}
if ty.func.is_some() && ty.data_array {
return Err(format!(
"{}: nettype `{}` has an unpacked-array data type AND a resolution function, which is not supported: the function's `input T driver[]` is a dynamic array of unpacked arrays, which the compiler cannot lower yet (LRM 6.6.7)",
first_name, ty.name
));
}
}
if !c.rows.is_empty() && c.kind == NetKind::TriReg {
return Err(format!(
"{}: an element of an unpacked array of `trireg` nets is not supported (LRM 28.16.2)",
first_name
));
}
let user_resolves = c.user.is_some_and(|t| user.types[t].func.is_some());
let disjoint_prims = c.ds.len() >= 2
&& c.nets.len() == 1
&& c.rows.is_empty()
&& c.cells.is_empty()
&& !c.switched
&& c.user.is_none()
&& !resolves_alone(c.kind)
&& specify_dests.contains(&c.nets[0])
&& c.ds.iter().all(|(d, row, _)| {
row.is_none()
&& matches!(d.key, DriverKey::Prim(..))
&& d.strength == vj_sim::state::DriveStrengthPair::STRONG
})
&& windows.get(&c.nets[0]).is_some_and(|w| {
w.len() == c.ds.len()
&& w.iter().enumerate().all(|(i, a)| {
a.is_some_and(|(alo, aw)| {
w[i + 1..].iter().all(|b| {
b.is_some_and(|(blo, bw)| alo + aw <= blo || blo + bw <= alo)
})
})
})
});
made.push(
(c.ds.len() >= 2 && !disjoint_prims)
|| c.nets.len() + c.rows.len() + c.cells.len() >= 2
|| !c.cells.is_empty()
|| resolves_alone(c.kind)
|| c.switched
|| user_resolves
|| (shows_strength && !c.ds.is_empty())
|| (tracks_strengths()
&& c.ds.iter().any(|(d, _, _)| {
d.strength != vj_sim::state::DriveStrengthPair::STRONG
})),
);
}
let mut classes_of: HashMap<NetId, Vec<usize>> = HashMap::new();
for (k, c) in classes.iter().enumerate() {
for (n, _) in &c.rows {
let list = classes_of.entry(n.clone()).or_default();
if !list.contains(&k) {
list.push(k);
}
}
}
let mut changed = true;
while changed {
changed = false;
for list in classes_of.values() {
if list.iter().any(|&k| made[k]) && list.iter().any(|&k| !made[k]) {
for &k in list {
made[k] = true;
}
changed = true;
}
}
}
let mut group_of_root: HashMap<Joint, usize> = HashMap::new();
let mut mem_cell: HashMap<DriverKey, u32> = HashMap::new();
for (c, made) in classes.into_iter().zip(made) {
if !made {
continue;
}
let mut group = Group {
kind: c.kind,
width: c.width,
nets: c.nets,
drivers: Vec::new(),
switched: c.switched,
user: c.user,
rows: c.rows,
cells: Vec::new(),
driver_rows: Vec::new(),
};
for cell in c.cells {
let slot = out.bank_widths.len() as u32;
out.bank_widths.push(1);
out.ic_elem.insert(cell, slot);
group.cells.push(slot);
}
if c.switched {
group_of_root.insert(c.root.clone(), out.groups.len());
}
for (d, row, owner) in c.ds {
let slot = match row {
Some(_) => *mem_cell.entry(d.key.clone()).or_insert_with(|| {
let slot = out.bank_widths.len() as u32;
let net_slot = signals[&owner.0][&owner.1];
out.bank_widths.push(net_slot.width);
out.bank_mem.insert(slot, net_slot);
out.contrib.insert(d.key.clone(), slot);
slot
}),
None => {
let slot = out.bank_widths.len() as u32;
out.bank_widths.push(c.width);
out.contrib.insert(d.key.clone(), slot);
slot
}
};
group.drivers.push((slot, d.strength));
group.driver_rows.push(row);
}
out.groups.push(group);
}
let mut switches = Vec::new();
for (a, b, conducts, resistive, (ra, rb)) in pending_switches {
let ga = group_of_root[&find.root(&end_joint((a.0, a.1), ra))];
let gb = group_of_root[&find.root(&end_joint((b.0, b.1), rb))];
switches.push(Switch {
a: (ga, a.2),
b: (gb, b.2),
conducts,
resistive,
});
}
let mut parent: Vec<usize> = (0..out.groups.len()).collect();
fn top(p: &mut [usize], mut x: usize) -> usize {
while p[x] != x {
p[x] = p[p[x]];
x = p[x];
}
x
}
for s in &switches {
let (x, y) = (top(&mut parent, s.a.0), top(&mut parent, s.b.0));
if x != y {
parent[x] = y;
}
}
let mut by_top: std::collections::BTreeMap<usize, Network> = std::collections::BTreeMap::new();
for g in 0..out.groups.len() {
if out.groups[g].switched {
let t = top(&mut parent, g);
by_top
.entry(t)
.or_insert_with(|| Network {
groups: Vec::new(),
switches: Vec::new(),
})
.groups
.push(g);
}
}
for s in switches {
let t = top(&mut parent, s.a.0);
by_top
.get_mut(&t)
.expect("a switch's group is switched")
.switches
.push(s);
}
out.networks = by_top.into_values().collect();
for net in &out.networks {
let ty = out.groups[net.groups[0]].user;
let any_user = net.groups.iter().any(|&g| out.groups[g].user.is_some());
let name = || {
net.groups
.iter()
.find_map(|&g| out.groups[g].nets.first())
.map(|n| n.1.path())
.unwrap_or_default()
};
if net.groups.iter().any(|&g| !out.groups[g].cells.is_empty()) {
return Err(format!(
"{}: a switch network that reaches an interconnect array element is not supported",
name()
));
}
if !any_user {
continue;
}
if net.groups.iter().any(|&g| out.groups[g].user != ty) || ty.is_none() {
return Err(format!(
"{}: a switch connects nets of different nettypes, or a net of a user-defined nettype and a built-in net (LRM 28.8)",
name()
));
}
if net.switches.iter().any(|s| s.resistive) {
return Err(format!(
"{}: an rtran, rtranif0 or rtranif1 switch on a net of a user-defined nettype (LRM 28.8: only scalar and bit-select built-in nets)",
name()
));
}
if let Some(t) = ty
&& user.types[t].func.is_none()
&& net
.groups
.iter()
.filter(|&&g| !out.groups[g].drivers.is_empty())
.count()
> 1
{
return Err(format!(
"{}: a switch can join two driven nets of nettype `{}`, which has no resolution function (LRM 6.6.7: at most one driver)",
name(),
user.types[t].name
));
}
}
Ok(out)
}
fn inout_element_target<'d>(
target: Expression<'d>,
signals: &HashMap<InstanceId, HashMap<SymbolId, SignalSlot>>,
parent: &InstanceId,
rows_of: &RowsOf,
) -> Option<(Symbol<'d>, u32)> {
if target.kind() != ExpressionKind::ElementSelect {
return None;
}
let path = element_path(target)?;
if path.bits.is_some() {
return None;
}
let mut base = target;
while base.kind() == ExpressionKind::ElementSelect {
base = base.select_value()?;
}
if !vj_sim::symid::is_value_name(base.kind()) {
return None;
}
let pn = base.referenced_symbol()?;
signals.get(parent)?.get(&pn.uid())?;
let rows = rows_of.get(&(*parent, pn.uid()))?.as_ref()?;
let (row, _) = rows.iter().find(|(_, idx)| *idx == path.idx)?;
Some((pn, *row))
}
fn inout_select_target<'d>(e: Expression<'d>) -> Option<(Expression<'d>, Expression<'d>)> {
let e = if e.kind() == ExpressionKind::Conversion {
e.operand()?
} else {
e
};
if !matches!(
e.kind(),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect
) {
return None;
}
let sel = e;
let mut base = e;
while matches!(
base.kind(),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect
) {
base = base.select_value()?;
}
vj_sim::symid::is_value_name(base.kind()).then_some((sel, base))
}
fn inout_concat_leaves<'d>(
e: Expression<'d>,
) -> Option<Vec<(Expression<'d>, Option<Expression<'d>>)>> {
let e = if e.kind() == ExpressionKind::Conversion {
e.operand()?
} else {
e
};
if e.kind() != ExpressionKind::Concatenation {
return None;
}
let mut out = Vec::new();
for o in e.operands() {
let o = if o.kind() == ExpressionKind::Conversion {
o.operand()?
} else {
o
};
if o.kind() == ExpressionKind::Concatenation {
out.extend(inout_concat_leaves(o)?);
} else if vj_sim::symid::is_value_name(o.kind()) {
out.push((o, None));
} else {
let (sel, base) = inout_select_target(o)?;
out.push((base, Some(sel)));
}
}
Some(out)
}
fn out_select_plan(
target: Expression<'_>,
width: u32,
child: NetId,
parent: InstanceId,
pscope: &HashMap<SymbolId, SignalSlot>,
by_net: &HashMap<NetId, usize>,
) -> Option<(Vec<PendingSwitch>, Vec<SymbolId>)> {
let leaves = if let Some((sel, base)) = inout_select_target(target) {
vec![(base, Some(sel))]
} else {
inout_concat_leaves(target)?
};
let mut planned: Vec<PendingSwitch> = Vec::new();
let mut bases: Vec<SymbolId> = Vec::new();
let mut top = width;
for (base, sel) in leaves {
let pn = base.referenced_symbol()?;
let ps = pscope.get(&pn.uid())?;
if pn.kind() != SymbolKind::Net || is_memory(ps) || by_net.contains_key(&(parent, pn.uid()))
{
return None;
}
let (lo, w) = match sel {
None => (0, ps.width),
Some(sel) => vj_codegen::select::constant_window(sel, ps.width).ok()?,
};
if w > top {
return None;
}
top -= w;
for k in 0..w {
planned.push((
(child.0, child.1.clone(), top + k),
(parent, pn.uid(), lo + k),
Conducts::Always,
false,
(None, None),
));
}
if !bases.contains(&pn.uid()) {
bases.push(pn.uid());
}
}
(top == 0).then_some((planned, bases))
}
fn switch_terminal(
e: Expression<'_>,
own: &HashMap<SymbolId, SignalSlot>,
rows_of: &RowsOf,
inst: InstanceId,
) -> Option<(SymbolId, u32, u32, Option<u32>)> {
let e = if e.kind() == ExpressionKind::Assignment {
e.left()?
} else {
e
};
let e = if e.kind() == ExpressionKind::Conversion {
e.operand()?
} else {
e
};
if let Some(t) = element_terminal(e, own, rows_of, inst) {
return Some(t);
}
if vj_sim::symid::is_value_name(e.kind()) {
let s = e.referenced_symbol()?;
let slot = own
.get(&s.uid())
.filter(|slot| s.kind() == SymbolKind::Net && !is_memory(slot))?;
return Some((s.uid(), 0, slot.width, None));
}
if e.kind() == ExpressionKind::RangeSelect {
let (s, slot, lo, w) = select_range(e, own)?;
return (s.kind() == SymbolKind::Net && !is_memory(&slot)).then_some((
s.uid(),
lo,
w,
None,
));
}
let (s, slot, bit) = select_bit(e, own)?;
(s.kind() == SymbolKind::Net && !is_memory(&slot)).then_some((
s.uid(),
bit,
slot.width.min(1),
None,
))
}
fn element_terminal(
e: Expression<'_>,
own: &HashMap<SymbolId, SignalSlot>,
rows_of: &RowsOf,
inst: InstanceId,
) -> Option<(SymbolId, u32, u32, Option<u32>)> {
let constant = |x: Option<Expression<'_>>| {
x.and_then(|s| s.constant_value())
.as_ref()
.and_then(sv_lang::ConstantValue::as_integer)
.and_then(sv_lang::SVInt::as_i64)
};
let mut elem = e;
let packed = match e.kind() {
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect => {
let base = e.select_value()?;
if base.expr_type().is_some_and(|t| t.is_unpacked_array()) {
None
} else {
elem = base;
Some(e)
}
}
_ => None,
};
let mut idx: Vec<i64> = Vec::new();
let mut cur = elem;
while cur.kind() == ExpressionKind::ElementSelect {
let base = cur.select_value()?;
if !base.expr_type().is_some_and(|t| t.is_unpacked_array()) {
return None;
}
idx.push(constant(cur.selector())?);
cur = base;
}
if idx.is_empty() || !vj_sim::symid::is_value_name(cur.kind()) {
return None;
}
idx.reverse();
let s = cur.referenced_symbol()?;
let slot = own.get(&s.uid())?;
if s.kind() != SymbolKind::Net || !is_memory(slot) {
return None;
}
let rows = rows_of.get(&(inst, s.uid()))?.as_ref()?;
let (row, _) = rows.iter().find(|(_, i)| *i == idx)?;
let width = slot.width;
let Some(sel) = packed else {
return Some((s.uid(), 0, width, Some(*row)));
};
let (left, right) = match elem.expr_type() {
Some(t) if t.is_integral() => {
let r = t.bit_vector_range();
(i64::from(r.left), i64::from(r.right))
}
_ => (i64::from(width) - 1, 0),
};
let off = |i: i64| if left >= right { i - right } else { right - i };
let (lo, w) = if sel.kind() == ExpressionKind::ElementSelect {
(off(constant(sel.selector())?), 1)
} else {
vj_codegen::expr::classify_range_selection_kind(sel.range_selection_kind()?)
.ok()
.filter(|k| *k == vj_codegen::expr::RangeSelectionKindClass::Simple)?;
let a = off(constant(sel.range_left())?);
let b = off(constant(sel.range_right())?);
(a.min(b), (a - b).abs() + 1)
};
(lo >= 0 && lo + w <= i64::from(width)).then_some((s.uid(), lo as u32, w as u32, Some(*row)))
}
fn select_range<'d>(
e: Expression<'d>,
own: &HashMap<SymbolId, SignalSlot>,
) -> Option<(Symbol<'d>, SignalSlot, u32, u32)> {
let base = e.select_value()?;
if !vj_sim::symid::is_value_name(base.kind()) {
return None;
}
let s = base.referenced_symbol()?;
let slot = *own.get(&s.uid())?;
e.range_selection_kind().filter(|k| {
vj_codegen::expr::classify_range_selection_kind(*k)
== Ok(vj_codegen::expr::RangeSelectionKindClass::Simple)
})?;
let (rl, rr) = match base.expr_type() {
Some(t) if t.is_integral() => {
let r = t.bit_vector_range();
(i64::from(r.left), i64::from(r.right))
}
_ => (i64::from(slot.width) - 1, 0),
};
let off = |idx: i64| if rl >= rr { idx - rr } else { rr - idx };
let a = off(e.range_left()?.constant_value()?.as_i64()?);
let b = off(e.range_right()?.constant_value()?.as_i64()?);
let (lo, hi) = (a.min(b), a.max(b));
(lo >= 0 && hi < i64::from(slot.width)).then_some((s, slot, lo as u32, (hi - lo + 1) as u32))
}
fn array_slice(lo: u32, w: u32, n: u32, pos: u32) -> Option<(u32, u32)> {
if n == 1 || w == 1 {
return Some((lo, w));
}
if !w.is_multiple_of(n) {
return None;
}
let k = w / n;
Some((lo + (n - 1 - pos) * k, k))
}
fn select_bit<'d>(
e: Expression<'d>,
own: &HashMap<SymbolId, SignalSlot>,
) -> Option<(Symbol<'d>, SignalSlot, u32)> {
if e.kind() != ExpressionKind::ElementSelect {
return None;
}
let base = e.select_value()?;
let s = base.referenced_symbol()?;
let slot = *own.get(&s.uid())?;
let idx = e.selector()?.constant_value()?.as_i64()?;
let (left, right) = match base.expr_type() {
Some(t) if t.is_integral() => {
let r = t.bit_vector_range();
(i64::from(r.left), i64::from(r.right))
}
_ => (i64::from(slot.width) - 1, 0),
};
let off = if left >= right {
idx - right
} else {
right - idx
};
(0..i64::from(slot.width))
.contains(&off)
.then_some((s, slot, off as u32))
}
fn switch_array_position(inst: &Symbol<'_>) -> Result<(u32, u32), String> {
let path: Vec<u32> = inst.instance_array_path().collect();
match path.as_slice() {
[] => Ok((1, 0)),
[pos] => {
let n = inst
.parent()
.map_or(1, |p| p.instance_array_elements().count() as u32);
Ok((n, *pos))
}
_ => Err("a multi-dimensional array of switches (LRM 28.3.5) is not supported".into()),
}
}
fn control_terminal(
e: Expression<'_>,
id: InstanceId,
own: &HashMap<SymbolId, SignalSlot>,
(arr_n, arr_pos): (u32, u32),
) -> Option<Control> {
if let Some(c) = e.constant_value() {
return Some(Control::Const(match c.as_i64() {
Some(v) => (v & 1) as u8,
None => 2,
}));
}
let e = if e.kind() == ExpressionKind::Conversion {
e.operand()?
} else {
e
};
let e = match e.select_value() {
Some(base)
if arr_n > 1
&& e.kind() == ExpressionKind::ElementSelect
&& e.source_range() == base.source_range() =>
{
base
}
_ => e,
};
if vj_sim::symid::is_value_name(e.kind()) {
let s = e.referenced_symbol()?;
let slot = *own.get(&s.uid())?;
let (lo, _) = array_slice(0, slot.width, arr_n, arr_pos)?;
return Some(Control::Bit(id, slot, lo));
}
if e.kind() == ExpressionKind::RangeSelect {
let (_, slot, lo, w) = select_range(e, own)?;
let (lo, _) = array_slice(lo, w, arr_n, arr_pos)?;
return Some(Control::Bit(id, slot, lo));
}
let (_, slot, bit) = select_bit(e, own)?;
Some(Control::Bit(id, slot, bit))
}
#[cfg(test)]
mod tests {
use sv_lang::{Compilation, Session};
use super::{DriverKey, ElemPath, Resolution, paths_overlap};
use vj_sim::netlist::InstanceId;
use vj_sim::symid::SymbolUid;
#[test]
fn element_paths_overlap_only_on_a_shared_prefix() {
let p = |idx: &[i64], bits: Option<(i64, i64)>| ElemPath {
idx: idx.to_vec(),
bits,
};
assert!(paths_overlap(&p(&[1], None), &p(&[1], None)));
assert!(paths_overlap(&p(&[1], None), &p(&[1, 0], None)));
assert!(paths_overlap(&p(&[], None), &p(&[2, 3], None)));
assert!(!paths_overlap(&p(&[0], None), &p(&[1], None)));
assert!(!paths_overlap(&p(&[1, 0], None), &p(&[1, 1], None)));
assert!(!paths_overlap(
&p(&[1], Some((0, 1))),
&p(&[1], Some((2, 5)))
));
assert!(paths_overlap(
&p(&[1], Some((0, 2))),
&p(&[1], Some((2, 5)))
));
assert!(paths_overlap(&p(&[1], Some((0, 1))), &p(&[1], None)));
assert!(!paths_overlap(
&p(&[0], Some((0, 1))),
&p(&[1], Some((0, 1)))
));
}
#[test]
fn specify_path_driver_key_round_trips_through_resolution_contrib() {
let session = Session::new();
let mut comp = Compilation::new(&session).expect("compilation created");
comp.add_source("module m(input a, output q);\n assign q = a;\nendmodule\n")
.expect("source added");
let design = comp.compile().expect("design compiled");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("top instance has a body");
let q_uid = body
.members()
.find(|s| s.name() == "q")
.expect("net q")
.uid();
let mut res = Resolution::default();
res.bank_widths.push(1);
let key = DriverKey::SpecifyPath(InstanceId(0), q_uid.clone());
res.contrib.insert(key.clone(), 0);
assert_eq!(res.slot(&key), Some(0));
assert_eq!(
res.slot(&DriverKey::SpecifyPath(InstanceId(1), q_uid)),
None,
"a different instance's otherwise-identical key is a distinct entry"
);
}
}