use std::collections::{HashMap, HashSet};
use std::sync::{Arc, Mutex};
use sv_lang::kinds::{ExpressionKind, SymbolKind, SyntaxKind};
use sv_lang::{EvalSession, Expression, SemNode, Symbol, SyntaxTree};
use vj_abi::bit4::Bit4;
use vj_codegen::process_lower::{SignalSlot, SignalSlotClass};
use vj_codegen::types::TypeWidth;
use vj_exec::timing::dyndelay::{ConvTo, DelayExpr, DelayOp, DelayTerm, DynDelay};
use vj_exec::timing::prim::{Field, Gate, LiveDelays, Row, TermLoc, Udp, can_be_ambiguous};
pub(crate) use vj_exec::timing::{Delays, Loc, LocClass, SlotRef};
pub(crate) use vj_exec::timing::{LinePlan, PrimPlan};
use vj_sim::netlist::InstanceId;
use vj_sim::symid::{SymbolId, SymbolUid};
use crate::nets::{DriverKey, Resolution};
pub(crate) fn slot_ref(slot: SignalSlot) -> SlotRef {
SlotRef {
slot: slot.slot,
width: slot.width,
signed: slot.signed,
class: match slot.class {
SignalSlotClass::Inline => LocClass::Inline,
SignalSlotClass::Wide => LocClass::Wide,
SignalSlotClass::Memory {
num_rows,
lower_index,
} => LocClass::Memory {
num_rows,
lower_index,
},
},
}
}
pub(crate) fn bank_slot(slot: u32, width: u32) -> SignalSlot {
SignalSlot {
slot,
width,
signed: false,
class: if width > 64 {
SignalSlotClass::Wide
} else {
SignalSlotClass::Inline
},
}
}
pub(crate) fn loc_of(inst: InstanceId, slot: SignalSlot) -> Loc {
Loc::new(inst, slot_ref(slot))
}
fn builtin(name: &str) -> Option<(Gate, bool)> {
Some(match name {
"and" => (Gate::And, false),
"nand" => (Gate::Nand, false),
"or" => (Gate::Or, false),
"nor" => (Gate::Nor, false),
"xor" => (Gate::Xor, false),
"xnor" => (Gate::Xnor, false),
"buf" => (Gate::Buf, false),
"not" => (Gate::Not, false),
"bufif0" => (Gate::Bufif0, false),
"bufif1" => (Gate::Bufif1, false),
"notif0" => (Gate::Notif0, false),
"notif1" => (Gate::Notif1, false),
"nmos" => (Gate::Nmos, false),
"pmos" => (Gate::Pmos, false),
"cmos" => (Gate::Cmos, false),
"rnmos" => (Gate::Nmos, true),
"rpmos" => (Gate::Pmos, true),
"rcmos" => (Gate::Cmos, true),
"pullup" => (Gate::Pullup, false),
"pulldown" => (Gate::Pulldown, false),
_ => return None,
})
}
pub(crate) fn instances<'d>(body: &Symbol<'d>) -> Vec<Symbol<'d>> {
fn walk<'d>(s: Symbol<'d>, out: &mut Vec<Symbol<'d>>) {
if s.kind() == SymbolKind::PrimitiveInstance {
out.push(s);
} else if s.kind() == SymbolKind::InstanceArray {
for e in s.instance_array_elements() {
walk(e, out);
}
}
}
let mut out = Vec::new();
for m in vj_sim::netlist::module_items(body) {
walk(m, &mut out);
}
out
}
pub(crate) fn type_name(inst: &Symbol<'_>, trees: &[SyntaxTree]) -> Option<String> {
let mut sym = *inst;
loop {
for tree in trees {
let Some(mut node) = sym.syntax(tree) else {
continue;
};
loop {
if node.kind() == SyntaxKind::PrimitiveInstantiation {
return <sv_lang::nodes::PrimitiveInstantiationSyntax as sv_lang::AstNode>::cast(
node,
)
.and_then(|n| n.r#type())
.map(|t| t.text().to_string());
}
if node.kind() == SyntaxKind::HierarchyInstantiation {
return <sv_lang::nodes::HierarchyInstantiationSyntax as sv_lang::AstNode>::cast(
node,
)
.and_then(|n| n.r#type())
.map(|t| t.text().to_string());
}
match node.parent() {
Some(p) => node = p,
None => break,
}
}
}
match sym.parent() {
Some(p) if p.kind() == SymbolKind::InstanceArray => sym = p,
_ => return None,
}
}
}
pub(crate) fn is_switch(inst: &Symbol<'_>, trees: &[SyntaxTree]) -> bool {
type_name(inst, trees).is_some_and(|n| n.starts_with("tran") || n.starts_with("rtran"))
}
fn output_count(gate: Gate, n: usize) -> usize {
match gate {
Gate::Buf | Gate::Not => n.saturating_sub(1),
Gate::Pullup | Gate::Pulldown => n,
_ => 1,
}
}
fn udps_of<'d>(inst: &Symbol<'d>) -> HashMap<String, Symbol<'d>> {
let mut root = *inst;
while let Some(p) = root.parent() {
root = p;
}
let mut out = HashMap::new();
for unit in root.members() {
for m in unit.members() {
if m.kind() == SymbolKind::Primitive {
out.insert(m.name().to_string(), m);
}
}
}
out
}
pub(crate) struct Shape<'d> {
pub gate: Gate,
pub resistive: bool,
pub udp: Option<Udp>,
pub outputs: Vec<Expression<'d>>,
pub inputs: Vec<Expression<'d>>,
}
pub(crate) fn shape<'d>(inst: &Symbol<'d>, trees: &[SyntaxTree]) -> Result<Shape<'d>, String> {
let name = type_name(inst, trees).ok_or_else(|| {
format!(
"primitive instance {}: no instantiation syntax",
inst.name()
)
})?;
let (gate, resistive, udp) = match builtin(&name) {
Some((g, r)) => (g, r, None),
None => {
let def = udps_of(inst).remove(&name).ok_or_else(|| {
format!("`{name}` is not a supported primitive (tran switches are not)")
})?;
(Gate::Udp, false, Some(udp_of(&def)?))
}
};
let conns: Vec<Expression<'d>> = inst.primitive_instance_port_connections().collect();
let n_out = output_count(gate, conns.len());
let mut outputs = Vec::new();
for c in &conns[..n_out] {
let lhs = if c.kind() == ExpressionKind::Assignment {
c.left()
} else {
Some(*c)
};
outputs.push(
lhs.ok_or_else(|| format!("primitive {}: an output with no target", inst.name()))?,
);
}
Ok(Shape {
gate,
resistive,
udp,
outputs,
inputs: conns[n_out..].to_vec(),
})
}
pub(crate) fn strength(
inst: &Symbol<'_>,
gate: Gate,
resistive: bool,
) -> vj_sim::state::DriveStrengthPair {
use vj_sim::state::DriveStrength;
let given = inst.primitive_instance_drive_strength();
let default = if resistive || matches!(gate, Gate::Pullup | Gate::Pulldown) {
DriveStrength::Pull
} else {
DriveStrength::Strong
};
let one = |s: Option<sv_lang::DriveStrength>| match s {
None => default,
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,
};
vj_sim::state::DriveStrengthPair {
s0: one(given.strength0),
s1: one(given.strength1),
}
}
fn level_char(c: char) -> Option<u8> {
Some(match c {
'0' => 1,
'1' => 2,
'x' | 'X' => 4,
'?' => 7,
'b' | 'B' => 3,
_ => return None,
})
}
fn parse_fields(s: &str) -> Result<Vec<Field>, String> {
let bad = || format!("unrecognized UDP table entry `{s}`");
let mut out = Vec::new();
let mut chars = s.chars().filter(|c| !c.is_whitespace());
while let Some(c) = chars.next() {
let field = match c {
'(' => {
let from = chars.next().and_then(level_char).ok_or_else(bad)?;
let to = chars.next().and_then(level_char).ok_or_else(bad)?;
if chars.next() != Some(')') {
return Err(bad());
}
Field::Edge(vec![(from, to)])
}
'r' | 'R' => Field::Edge(vec![(1, 2)]),
'f' | 'F' => Field::Edge(vec![(2, 1)]),
'p' | 'P' => Field::Edge(vec![(1, 2), (1, 4), (4, 2)]),
'n' | 'N' => Field::Edge(vec![(2, 1), (2, 4), (4, 1)]),
'*' => Field::Edge(vec![(7, 7)]),
c => Field::Level(level_char(c).ok_or_else(bad)?),
};
out.push(field);
}
Ok(out)
}
fn udp_of(def: &Symbol<'_>) -> Result<Udp, String> {
let mut rows = Vec::new();
for (inputs, out, state) in def.primitive_table() {
let out = match out.trim() {
"0" => Some(Bit4::Zero),
"1" => Some(Bit4::One),
"x" | "X" => Some(Bit4::X),
"-" => None,
other => return Err(format!("unrecognized UDP output `{other}`")),
};
let state = if state.trim().is_empty() {
7
} else {
level_char(state.trim().chars().next().unwrap_or('?'))
.ok_or_else(|| format!("unrecognized UDP state `{state}`"))?
};
rows.push(Row {
inputs: parse_fields(inputs)?,
state,
out,
});
}
let init = match def.primitive_init_val().and_then(|v| v.as_i64()) {
Some(0) => Bit4::Zero,
Some(1) => Bit4::One,
_ => Bit4::X,
};
Ok(Udp {
sequential: def.primitive_is_sequential(),
init,
rows,
})
}
fn constant_of(eval: &EvalSession<'_>, e: Expression<'_>) -> Option<sv_lang::ConstantValue> {
e.constant_value().or_else(|| eval.eval_constant(e))
}
fn specparam_source_value(e: Expression<'_>) -> Option<sv_lang::ConstantValue> {
let mut bare = e;
while bare.kind() == ExpressionKind::Conversion
&& let Some(inner) = bare.conversion_operand()
{
bare = inner;
}
bare.referenced_symbol()
.filter(|s| s.kind() == SymbolKind::Specparam)
.and_then(|s| s.constant_value())
}
fn select_offset(
eval: &EvalSession<'_>,
base: Expression<'_>,
index: Expression<'_>,
width: u32,
) -> Option<u32> {
let idx = constant_of(eval, index)?.as_i64()?;
let (left, right) = match base.expr_type() {
Some(t) if t.is_integral() && !t.is_struct() && !t.is_union() => {
let r = t.bit_vector_range();
(i64::from(r.left), i64::from(r.right))
}
_ => (i64::from(width) - 1, 0),
};
let off = if left >= right {
idx - right
} else {
right - idx
};
(0..i64::from(width)).contains(&off).then_some(off as u32)
}
fn signal_range<'d>(
eval: &EvalSession<'_>,
e: Expression<'d>,
signals: &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 sym = base.referenced_symbol()?;
let slot = *signals.get(&sym.uid())?;
if matches!(slot.class, SignalSlotClass::Memory { .. }) {
return None;
}
let a = select_offset(eval, base, e.range_left()?, slot.width)?;
let b = select_offset(eval, base, e.range_right()?, slot.width)?;
e.range_selection_kind().filter(|k| {
vj_codegen::expr::classify_range_selection_kind(*k)
== Ok(vj_codegen::expr::RangeSelectionKindClass::Simple)
})?;
Some((sym, slot, (a.min(b), a.abs_diff(b) + 1)))
}
fn signal_bit<'d>(
eval: &EvalSession<'_>,
e: Expression<'d>,
signals: &HashMap<SymbolId, SignalSlot>,
) -> Option<(Symbol<'d>, SignalSlot, u32)> {
let e = if e.kind() == ExpressionKind::Conversion {
e.operand()?
} else {
e
};
if vj_sim::symid::is_value_name(e.kind()) {
let sym = e.referenced_symbol()?;
let slot = *signals.get(&sym.uid())?;
return (!matches!(slot.class, SignalSlotClass::Memory { .. })).then_some((sym, slot, 0));
}
if e.kind() == ExpressionKind::ElementSelect {
let base = e.select_value()?;
if base.kind() == ExpressionKind::RangeSelect {
let (sym, slot, lo) = signal_range(eval, base, signals)?;
let width = lo.1;
let off = select_offset(eval, base, e.selector()?, width)?;
return Some((sym, slot, lo.0 + off));
}
if !vj_sim::symid::is_value_name(base.kind()) {
return None;
}
let sym = base.referenced_symbol()?;
let slot = *signals.get(&sym.uid())?;
if matches!(slot.class, SignalSlotClass::Memory { .. }) {
return None;
}
let lo = select_offset(eval, base, e.selector()?, slot.width)?;
return Some((sym, slot, lo));
}
None
}
pub(crate) fn delay_values_of<'d>(
eval: &EvalSession<'_>,
exprs: impl Iterator<Item = Expression<'d>>,
scope: &Symbol<'_>,
) -> Result<Vec<u64>, String> {
let tick = vj_codegen::timescale::design_tick_exp(scope);
let mut out = Vec::new();
for e in exprs {
let e = if e.kind() == ExpressionKind::MinTypMax {
e.children()
.into_iter()
.filter_map(|n| n.as_expression())
.nth(1)
.ok_or_else(|| "a min:typ:max delay with no typical value".to_string())?
} else {
e
};
let v = match constant_of(eval, e).or_else(|| specparam_source_value(e)) {
Some(sv_lang::ConstantValue::Real(f)) => f,
Some(c) => c
.as_i64()
.ok_or_else(|| "a delay that is not a number".to_string())?
as f64,
None => return Err("a delay that is not a constant".to_string()),
};
out.push(vj_codegen::timescale::delay_ticks(v, scope, tick).map_err(|e| e.message)?);
}
Ok(out)
}
pub(crate) fn delays_of(
eval: &EvalSession<'_>,
node: SemNode<'_>,
scope: &Symbol<'_>,
) -> Result<Delays, String> {
let tc = node
.as_timing_control()
.ok_or_else(|| "a delay that is not a timing control".to_string())?;
let out = delay_values_of(
eval,
tc.children().into_iter().filter_map(|n| n.as_expression()),
scope,
)?;
Ok(Delays::from_list(&out))
}
fn delays_or_dynamic(
eval: &EvalSession<'_>,
node: SemNode<'_>,
scope: &Symbol<'_>,
inst: InstanceId,
signals: &HashMap<SymbolId, SignalSlot>,
) -> Result<(Delays, Option<DynDelay<Loc>>), String> {
match delays_of(eval, node, scope) {
Err(e) if e == "a delay that is not a constant" => {}
r => return r.map(|d| (d, None)),
}
let tc = node
.as_timing_control()
.ok_or_else(|| "a delay that is not a timing control".to_string())?;
let (unit, precision) = vj_codegen::timescale::unit_and_precision(scope);
let tick = vj_codegen::timescale::design_tick_exp(scope);
let mut terms = Vec::new();
for e in tc.children().into_iter().filter_map(|n| n.as_expression()) {
let e = if e.kind() == ExpressionKind::MinTypMax {
e.children()
.into_iter()
.filter_map(|n| n.as_expression())
.nth(1)
.ok_or_else(|| "a min:typ:max delay with no typical value".to_string())?
} else {
e
};
terms.push(DelayTerm {
expr: delay_expr(eval, e, inst, signals)?,
unit_to_precision: unit - precision,
precision_to_tick: precision - tick,
});
}
Ok((Delays::default(), Some(DynDelay { terms })))
}
fn delay_expr(
eval: &EvalSession<'_>,
e: Expression<'_>,
inst: InstanceId,
signals: &HashMap<SymbolId, SignalSlot>,
) -> Result<DelayExpr<Loc>, String> {
let refuse = |what: &str| format!("a delay expression with {what}");
if let Some(c) = constant_of(eval, e).or_else(|| specparam_source_value(e)) {
return match c {
sv_lang::ConstantValue::Real(f) => Ok(DelayExpr::Real(f.to_bits())),
c => c
.as_i64()
.map(DelayExpr::Int)
.ok_or_else(|| "a delay that is not a number".to_string()),
};
}
let ty = e.expr_type();
let real = ty.as_ref().is_some_and(|t| t.is_floating());
let width = ty.as_ref().map_or(64, |t| t.bit_width() as u32);
let signed = ty.as_ref().is_some_and(|t| t.is_signed());
match e.kind() {
ExpressionKind::Conversion => {
let inner = e
.conversion_operand()
.ok_or_else(|| refuse("a conversion with no operand"))?;
let to = if real {
ConvTo::Real
} else {
ConvTo::Int { width, signed }
};
Ok(DelayExpr::Conv(
to,
Box::new(delay_expr(eval, inner, inst, signals)?),
))
}
k if vj_sim::symid::is_value_name(k) => {
let sym = e
.referenced_symbol()
.ok_or_else(|| refuse("an unresolved name"))?;
let slot = *signals
.get(&sym.uid())
.ok_or_else(|| refuse(&format!("`{}`, which is not a signal", sym.name())))?;
if matches!(slot.class, SignalSlotClass::Memory { .. }) || slot.width > 64 {
return Err(refuse(&format!(
"`{}`, an array or a signal wider than 64 bits",
sym.name()
)));
}
Ok(DelayExpr::Sig {
loc: loc_of(inst, slot),
width,
signed,
real,
})
}
ExpressionKind::UnaryOp => match e.unary_op() {
Some(sv_lang::UnaryOp::Plus) => delay_expr(
eval,
e.operand()
.ok_or_else(|| refuse("a unary operator with no operand"))?,
inst,
signals,
),
Some(sv_lang::UnaryOp::Minus) => Ok(DelayExpr::Neg(Box::new(delay_expr(
eval,
e.operand()
.ok_or_else(|| refuse("a unary operator with no operand"))?,
inst,
signals,
)?))),
op => Err(refuse(&format!("the unary operator {op:?}"))),
},
ExpressionKind::BinaryOp => {
use sv_lang::BinaryOp as B;
let op = match e.binary_op() {
Some(B::Add) => DelayOp::Add,
Some(B::Subtract) => DelayOp::Sub,
Some(B::Multiply) => DelayOp::Mul,
Some(B::Divide) => DelayOp::Div,
Some(B::Mod) => DelayOp::Mod,
Some(B::BinaryAnd) => DelayOp::And,
Some(B::BinaryOr) => DelayOp::Or,
Some(B::BinaryXor) => DelayOp::Xor,
Some(B::LogicalShiftLeft | B::ArithmeticShiftLeft) => DelayOp::Shl,
Some(B::LogicalShiftRight) => DelayOp::Shr,
Some(B::ArithmeticShiftRight) => DelayOp::Ashr,
op => return Err(refuse(&format!("the binary operator {op:?}"))),
};
let (Some(l), Some(r)) = (e.left(), e.right()) else {
return Err(refuse("a binary operator with a missing operand"));
};
Ok(DelayExpr::Bin {
op,
width,
signed,
real,
a: Box::new(delay_expr(eval, l, inst, signals)?),
b: Box::new(delay_expr(eval, r, inst, signals)?),
})
}
k => Err(refuse(&format!("a {k:?} operand"))),
}
}
fn net_delays(eval: &EvalSession<'_>, net: &Symbol<'_>) -> Result<Delays, String> {
match net.net_delay() {
Some(d) if net.kind() == SymbolKind::Net => {
let mut delays = delays_of(eval, d, net)?;
if is_trireg(eval, net) {
delays.off = delays.rise.min(delays.fall);
}
Ok(delays)
}
_ => Ok(Delays::default()),
}
}
fn is_trireg(eval: &EvalSession<'_>, net: &Symbol<'_>) -> bool {
crate::nets::net_kind_of(net, &eval.design().syntax_trees()) == vj_sim::state::NetKind::TriReg
}
pub(crate) fn trireg_decays(
eval: &EvalSession<'_>,
bodies: &[(InstanceId, Symbol<'_>)],
) -> Result<HashMap<(InstanceId, SymbolId), u64>, String> {
let mut out = HashMap::new();
for (id, body) in bodies {
for m in vj_sim::netlist::module_items(body) {
if m.kind() != SymbolKind::Net || !is_trireg(eval, &m) {
continue;
}
if let Some(d) = m.net_delay() {
let delays = delays_of(eval, d, &m)?;
if delays.off > 0 {
out.insert((*id, m.uid()), delays.off);
}
}
}
}
Ok(out)
}
pub(crate) fn trireg_charges(
eval: &EvalSession<'_>,
bodies: &[(InstanceId, Symbol<'_>)],
) -> HashMap<(InstanceId, SymbolId), u8> {
let trees = eval.design().syntax_trees();
let mut out = HashMap::new();
for (id, body) in bodies {
for m in vj_sim::netlist::module_items(body) {
if m.kind() != SymbolKind::Net || !is_trireg(eval, &m) {
continue;
}
let code = crate::nets::charge_code_of(&m, &trees);
if code != 4 {
out.insert((*id, m.uid()), code);
}
}
}
out
}
pub(crate) type DeviceIndex = HashMap<(InstanceId, SymbolId), Vec<u32>>;
pub(crate) type DeviceDelays = HashMap<(InstanceId, SymbolId), Vec<Arc<Mutex<Delays>>>>;
pub(crate) type PrimIndex = HashMap<String, (String, Vec<u32>)>;
pub(crate) type PrimDelays = HashMap<String, (String, Vec<Arc<Mutex<Delays>>>)>;
pub(crate) fn prim_cells(index: &PrimIndex, live: &vj_exec::timing::TimingLive) -> PrimDelays {
index
.iter()
.map(|(k, (ty, v))| {
(
k.clone(),
(
ty.clone(),
v.iter()
.map(|&i| live.prim.device[i as usize].clone())
.collect(),
),
)
})
.collect()
}
pub(crate) fn device_cells(
index: &DeviceIndex,
live: &vj_exec::timing::TimingLive,
) -> DeviceDelays {
index
.iter()
.map(|(k, v)| {
(
k.clone(),
v.iter()
.map(|&i| live.prim.device[i as usize].clone())
.collect(),
)
})
.collect()
}
pub(crate) fn calls_sdf_annotate(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.kind() == sv_lang::kinds::TokenKind::SystemIdentifier
&& t.text() == "$sdf_annotate"
{
found = true;
return sv_lang::Walk::Break;
}
sv_lang::Walk::Continue
});
if found {
return true;
}
}
false
}
fn output_ports(body: &Symbol<'_>) -> HashSet<SymbolId> {
body.members()
.filter(|m| m.kind() == SymbolKind::Port)
.filter(|m| {
matches!(
m.port_direction(),
sv_lang::ArgumentDirection::Out | sv_lang::ArgumentDirection::InOut
)
})
.map(|m| m.port_internal_symbol().unwrap_or(m).uid())
.collect()
}
pub(crate) struct HiddenTerm<'d> {
pub(crate) inst: InstanceId,
pub(crate) prim: Symbol<'d>,
pub(crate) expr: Expression<'d>,
pub(crate) cell: u32,
}
#[derive(Default)]
pub(crate) struct Plans<'d> {
pub(crate) prims: Vec<PrimPlan>,
pub(crate) hidden: Vec<HiddenTerm<'d>>,
pub(crate) lines: Vec<LinePlan>,
pub staged: HashMap<DriverKey, u32>,
pub(crate) natural: HashMap<(InstanceId, SymbolId), Delays>,
pub(crate) specify_staging: HashMap<(InstanceId, SymbolId), Loc>,
pub(crate) device: DeviceIndex,
pub(crate) device_init: Vec<Delays>,
pub(crate) by_path: PrimIndex,
}
pub(crate) fn plan<'d>(
eval: &EvalSession<'_>,
insts: &[(InstanceId, Symbol<'d>)],
signals: &HashMap<InstanceId, HashMap<SymbolId, SignalSlot>>,
trees: &[SyntaxTree],
res: &mut Resolution,
bank: InstanceId,
specify_dests: &HashSet<(InstanceId, SymbolId)>,
) -> Result<Plans<'d>, String> {
let device_lines = calls_sdf_annotate(trees);
let shows_strength = crate::nets::shows_strength(trees);
let mut plans = Plans::default();
let mut staged_bits: HashSet<(InstanceId, SymbolId, u32)> = HashSet::new();
let alloc = |res: &mut Resolution, width: u32| {
let slot = res.bank_widths.len() as u32;
res.bank_widths.push(width);
loc_of(bank, bank_slot(slot, width))
};
for (id, body) in insts {
let own = &signals[id];
let ports = if device_lines {
output_ports(body)
} else {
HashSet::new()
};
for inst in instances(body) {
if is_switch(&inst, trees) {
let uid = inst.uid();
let Some(dc) = res
.delayed_controls
.iter()
.find(|c| c.inst == *id && c.prim == uid)
.cloned()
else {
continue;
};
let d = inst
.primitive_instance_delay()
.ok_or_else(|| "a delayed switch control with no delay".to_string())?;
let (mut delays, mut dynamic) = delays_or_dynamic(eval, d, &inst, *id, own)?;
if !dc.on_is_one {
delays = Delays {
rise: delays.fall,
fall: delays.rise,
off: delays.off,
};
if let Some(dd) = dynamic.as_mut()
&& dd.terms.len() >= 2
{
dd.terms.swap(0, 1);
}
}
let staging = loc_of(dc.source.0, dc.source.1);
let kick = alloc(res, 32);
plans.lines.push(LinePlan {
staging,
range: (dc.source.2, 1),
dst: loc_of(bank, bank_slot(dc.cell, 1)),
dst_lo: 0,
delays,
kick,
live: None,
dynamic,
});
continue;
}
let sh = shape(&inst, trees)?;
let prim_name = type_name(&inst, trees).unwrap_or_default();
let at = |what: &str| format!("primitive {}: {what}", inst.hierarchical_path());
let mut inputs = Vec::new();
for e in &sh.inputs {
if let Some((_, elem)) = crate::nets::ic_element(*e, *id)? {
let cell = *res
.ic_elem
.get(&elem)
.ok_or_else(|| at("an interconnect array element terminal with no wire"))?;
inputs.push(TermLoc::Bit(loc_of(bank, bank_slot(cell, 1)), 0));
} else if let Some((_, slot, lo)) = signal_bit(eval, *e, own) {
inputs.push(TermLoc::Bit(loc_of(*id, slot), lo));
} else if let Some((_, slot, (lo, _))) = signal_range(
eval,
if e.kind() == ExpressionKind::Conversion {
e.operand().unwrap_or(*e)
} else {
*e
},
own,
) {
inputs.push(TermLoc::Bit(loc_of(*id, slot), lo));
} else if let Some(c) = constant_of(eval, *e) {
let b = match c.as_i64() {
Some(v) if v & 1 == 1 => Bit4::One,
Some(_) => Bit4::Zero,
None => Bit4::X,
};
inputs.push(TermLoc::Const(b));
} else {
let ty = e.expr_type();
let width = ty.as_ref().map_or(0, |t| t.value_width());
if width == 0
|| ty.as_ref().is_some_and(|t| t.is_floating())
|| vj_codegen::huge::is_huge_width(width)
{
return Err(at(
"an input terminal expression that is real or wider than the \
inline value path",
));
}
let cell = res.bank_widths.len() as u32;
res.bank_widths.push(width);
plans.hidden.push(HiddenTerm {
inst: *id,
prim: inst,
expr: *e,
cell,
});
inputs.push(TermLoc::Bit(loc_of(bank, bank_slot(cell, width)), 0));
}
}
let (gate_delays, gate_dyn) = match inst.primitive_instance_delay() {
Some(d) => delays_or_dynamic(eval, d, &inst, *id, own)?,
None => (Delays::default(), None),
};
let mut outputs = Vec::new();
for e in &sh.outputs {
let ic = crate::nets::ic_element(*e, *id)?;
let (net, slot, lo) = match &ic {
Some((root, _)) => (*root, bank_slot(0, 1), 0),
None => signal_bit(eval, *e, own).ok_or_else(|| {
at("an output terminal that is not a net or a constant bit-select of one")
})?,
};
let net_extra = net_delays(eval, &net)?;
let delays = gate_delays.plus(&net_extra);
let live = device_lines.then(|| {
let own = plans.device_init.len() as u32;
plans.device_init.push(gate_delays);
if ports.contains(&net.uid()) && !specify_dests.contains(&(*id, net.uid())) {
plans.device.entry((*id, net.uid())).or_default().push(own);
}
plans
.by_path
.entry(inst.hierarchical_path())
.or_insert_with(|| (prim_name.clone(), Vec::new()))
.1
.push(own);
LiveDelays {
own,
net: net_extra,
}
});
let mut route = |res: &mut Resolution, dst: Loc, lo: u32| {
if delays.is_zero() && live.is_none() && gate_dyn.is_none() {
return (dst, lo);
}
let staging = alloc(res, 1);
let kick = alloc(res, 32);
plans.lines.push(LinePlan {
staging,
range: (0, 1),
dst,
dst_lo: lo,
delays,
kick,
live,
dynamic: gate_dyn.clone(),
});
(staging, 0)
};
let key = match &ic {
Some((_, elem)) => {
DriverKey::PrimElem(*id, inst.uid(), net.uid(), elem.2.clone())
}
None => DriverKey::Prim(*id, inst.uid(), net.uid()),
};
if specify_dests.contains(&(*id, net.uid())) && res.slot(&key).is_none() {
if !staged_bits.insert((*id, net.uid(), lo)) {
return Err(at("a specify path destination driven by two primitives"));
}
let staging = match plans.specify_staging.get(&(*id, net.uid())) {
Some(s) => *s,
None => {
let s = alloc(res, slot.width);
plans.specify_staging.insert((*id, net.uid()), s);
s
}
};
if gate_dyn.is_some() {
return Err(at("a variable delay on a specify path destination"));
}
let prior = plans
.natural
.get(&(*id, net.uid()))
.copied()
.unwrap_or_default();
let first = staged_bits
.iter()
.filter(|(i, n, _)| (*i, n) == (*id, &net.uid()))
.count()
== 1;
if !first && prior != delays {
return Err(at(
"primitives of one specify path destination with different delays",
));
}
if !delays.is_zero() {
plans.natural.insert((*id, net.uid()), delays);
}
outputs.push((staging, lo, None));
continue;
}
let out = match res.slot(&key) {
Some(s) => {
let dst = loc_of(bank, bank_slot(s, slot.width));
if shows_strength
&& matches!(sh.gate, Gate::Nmos | Gate::Pmos | Gate::Cmos)
&& let Some((isym, _, ilo)) = signal_bit(eval, sh.inputs[0], own)
&& let Some(gi) = res.groups.iter().position(|g| {
g.nets.contains(&(*id, isym.uid()))
&& g.user.is_none()
&& g.rows.is_empty()
&& g.cells.is_empty()
})
{
let tick = match res.strength_ticks.get(&gi) {
Some(&t) => t,
None => {
let t = alloc(res, 32).slot.slot;
res.strength_ticks.insert(gi, t);
t
}
};
res.strength_src.insert(
s,
vj_sim::image::StrengthSrc {
key: vj_runtime::fmt::strength_key(&isym.uid().key_string()),
bit: ilo,
out_bit: lo,
resistive: sh.resistive,
tick,
},
);
}
let amb = can_be_ambiguous(sh.gate).then(|| {
let a = alloc(res, slot.width);
res.ambiguous.insert(s, a.slot.slot);
route(res, a, lo)
});
let (d, l) = route(res, dst, lo);
(d, l, amb)
}
None => {
let (d, l) = route(res, loc_of(*id, slot), lo);
(d, l, None)
}
};
outputs.push(out);
}
plans.prims.push(PrimPlan {
gate: sh.gate,
udp: sh.udp,
inputs,
outputs,
path: inst.hierarchical_path(),
});
}
for (pid, port, net, one) in std::mem::take(&mut res.open_pulls) {
let slot = signals[&pid][&net];
let key = DriverKey::Prim(pid, port, net);
let mut outputs = Vec::new();
for bit in 0..slot.width {
let dst = match res.slot(&key) {
Some(s) => loc_of(bank, bank_slot(s, slot.width)),
None => loc_of(pid, slot),
};
outputs.push((dst, bit, None));
}
plans.prims.push(PrimPlan {
gate: if one { Gate::Pullup } else { Gate::Pulldown },
udp: None,
inputs: Vec::new(),
outputs,
path: String::new(),
});
}
for ca in vj_codegen::module::discover_cont_assigns(body).map_err(|e| e.message)? {
let (own_delay, ca_dyn) = match ca.delay {
Some(d) => delays_or_dynamic(eval, d, &ca.sym, *id, own)?,
None => (Delays::default(), None),
};
let (root, lo, width) = match ca.net_target {
Some(net) => {
let slot = *own
.get(&net.uid())
.ok_or_else(|| format!("net {} has no slot", net.name()))?;
(net, 0, slot.width)
}
None => {
let lhs = ca.assign_expr.left().ok_or("an assign with no target")?;
let roots = crate::nets::lvalue_roots(lhs);
let [root] = roots[..] else {
if own_delay.is_zero() && ca_dyn.is_none() {
continue;
}
return Err(format!(
"{}: a delayed continuous assignment to a concatenation",
ca.sym.hierarchical_path()
));
};
let Some(slot) = own.get(&root.uid()).copied() else {
continue;
};
if vj_sim::symid::is_value_name(lhs.kind()) {
(root, 0, slot.width)
} else if let Some((_, _, bit)) = signal_bit(eval, lhs, own) {
(root, bit, 1)
} else if lhs.kind() == ExpressionKind::RangeSelect
&& let Some(base) = lhs.select_value()
&& let (Some(l), Some(r)) = (lhs.range_left(), lhs.range_right())
&& let (Some(a), Some(b)) = (
select_offset(eval, base, l, slot.width),
select_offset(eval, base, r, slot.width),
)
&& lhs.range_selection_kind().is_some_and(|k| {
vj_codegen::expr::classify_range_selection_kind(k)
== Ok(vj_codegen::expr::RangeSelectionKindClass::Simple)
})
{
(root, a.min(b), a.abs_diff(b) + 1)
} else {
if own_delay.is_zero()
&& ca_dyn.is_none()
&& net_delays(eval, &root)?.is_zero()
{
continue;
}
return Err(format!(
"{}: a delayed continuous assignment whose target is not a whole \
signal or a constant select of one",
ca.sym.hierarchical_path()
));
}
}
};
let delays = if ca.net_target.is_some() {
own_delay
} else {
own_delay.plus(&net_delays(eval, &root)?)
};
if delays.is_zero() && ca_dyn.is_none() {
continue;
}
if specify_dests.contains(&(*id, root.uid())) {
if ca_dyn.is_some() {
return Err(format!(
"{}: a variable delay on a specify path destination",
ca.sym.hierarchical_path()
));
}
plans.natural.insert((*id, root.uid()), delays);
continue;
}
let slot = own[&root.uid()];
let is_mem = matches!(slot.class, SignalSlotClass::Memory { .. });
if is_mem && (lo != 0 || width != slot.width) {
return Err(format!(
"{}: a delayed continuous assignment to part of an unpacked array (LRM 10.3.3)",
ca.sym.hierarchical_path()
));
}
let key = DriverKey::Assign(*id, ca.sym.uid(), root.uid());
let dst = match res.slot(&key) {
Some(s) if is_mem => loc_of(bank, SignalSlot { slot: s, ..slot }),
Some(s) => loc_of(bank, bank_slot(s, slot.width)),
None => loc_of(*id, slot),
};
let staging = alloc(res, slot.width);
let staging = if is_mem {
res.bank_mem.insert(staging.slot.slot, slot);
loc_of(
bank,
SignalSlot {
slot: staging.slot.slot,
..slot
},
)
} else {
staging
};
let kick = alloc(res, 32);
plans.staged.insert(key, staging.slot.slot);
plans.lines.push(LinePlan {
staging,
range: (lo, width),
dst,
dst_lo: lo,
delays,
kick,
live: None,
dynamic: ca_dyn,
});
}
}
Ok(plans)
}