use sv_lang::kinds::{ExpressionKind, SymbolKind};
use sv_lang::{ConstantValue, EvalSession, Expression, SemNode};
use vj_sim::netlist::InstanceId;
use vj_sim::netlist::specify::TimingModel;
use vj_sim::symid::{SymbolUid, is_value_name};
fn constant_of(eval: &EvalSession<'_>, e: Expression<'_>) -> Option<ConstantValue> {
e.constant_value().or_else(|| eval.eval_constant(e))
}
#[allow(dead_code)]
pub(crate) fn eval_specparam_aware(
eval: &EvalSession<'_>,
model: &TimingModel,
instance: InstanceId,
node: SemNode<'_>,
) -> Result<ConstantValue, String> {
let e = node.as_expression().ok_or_else(|| {
"a specparam-aware evaluation of a node that is not an expression".to_string()
})?;
let e = if e.kind() == ExpressionKind::Conversion {
e.operand().unwrap_or(e)
} else {
e
};
if is_value_name(e.kind())
&& let Some(sym) = e.referenced_symbol()
&& sym.kind() == SymbolKind::Specparam
{
return model
.specparam(instance, &sym.uid())
.cloned()
.ok_or_else(|| {
format!(
"specparam `{}` has no value in this design's TimingModel (not seeded by \
TimingModel::seed_specparams)",
sym.name()
)
});
}
constant_of(eval, e)
.ok_or_else(|| "a specparam-aware expression that did not fold to a constant".to_string())
}
#[cfg(test)]
mod tests {
use sv_lang::kinds::SymbolKind;
use sv_lang::{Compilation, Design, Session, Symbol};
use vj_sim::netlist::InstanceId;
use vj_sim::netlist::specify::TimingModel;
use vj_sim::symid::SymbolUid;
use super::eval_specparam_aware;
fn compile(src: &str) -> Design {
let session = Session::new();
let mut comp = Compilation::new(&session).expect("compilation created");
comp.add_source(src).expect("source added");
comp.compile().expect("design compiled")
}
fn top_body(design: &Design) -> Symbol<'_> {
design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("top instance has a body")
}
fn path_delay_expr<'d>(body: &Symbol<'d>) -> sv_lang::Expression<'d> {
let specify = body
.members()
.find(|s| s.kind() == SymbolKind::SpecifyBlock)
.expect("specify block member");
let path = specify
.members()
.find(|s| s.kind() == SymbolKind::TimingPath)
.expect("TimingPath member");
path.timing_path_delays()
.next()
.expect("one delay expression")
}
#[test]
fn matches_constant_of_before_any_mutation() {
let mut design = compile(
"module m(input a, output q);\n specparam tRise = 10;\n assign q = a;\n \
specify\n (a => q) = tRise;\n endspecify\nendmodule\n",
);
let eval = design.eval_session();
let body = top_body(eval.design());
let mut model = TimingModel::new();
model.seed_specparams(&[(InstanceId(0), body)]);
let delay = path_delay_expr(&body);
let via_model = eval_specparam_aware(&eval, &model, InstanceId(0), delay.as_sem_node())
.unwrap_or_else(|e| panic!("eval_specparam_aware failed: {e}"));
let via_constant_value = delay
.constant_value()
.expect("tRise reference folds to a constant");
assert_eq!(via_model.as_i64(), via_constant_value.as_i64());
assert_eq!(via_model.as_i64(), Some(10));
}
#[test]
fn reflects_a_direct_mutation_of_the_table() {
let mut design = compile(
"module m(input a, output q);\n specparam tRise = 10;\n specparam tFall = 22;\n \
assign q = a;\n specify\n (a => q) = tRise;\n endspecify\nendmodule\n",
);
let eval = design.eval_session();
let body = top_body(eval.design());
let mut model = TimingModel::new();
model.seed_specparams(&[(InstanceId(0), body)]);
let delay = path_delay_expr(&body);
let rise_uid = body
.members()
.find(|s| s.name() == "tRise")
.expect("tRise member")
.uid();
let new_value = body
.members()
.find(|s| s.name() == "tFall")
.expect("tFall member")
.constant_value()
.expect("tFall has a constant value");
model.set_specparam(InstanceId(0), rise_uid, new_value);
let via_model = eval_specparam_aware(&eval, &model, InstanceId(0), delay.as_sem_node())
.unwrap_or_else(|e| panic!("eval_specparam_aware failed: {e}"));
assert_eq!(
via_model.as_i64(),
Some(22),
"mutating the table must change what the evaluator reports, with no re-plan"
);
}
#[test]
fn non_specparam_constant_expressions_still_fold() {
let mut design = compile(
"module m(input a, output q);\n assign q = a;\n specify\n (a => q) = 5;\n endspecify\nendmodule\n",
);
let eval = design.eval_session();
let body = top_body(eval.design());
let model = TimingModel::new();
let delay = path_delay_expr(&body);
let via_model = eval_specparam_aware(&eval, &model, InstanceId(0), delay.as_sem_node())
.unwrap_or_else(|e| panic!("eval_specparam_aware failed: {e}"));
assert_eq!(via_model.as_i64(), Some(5));
}
#[test]
fn resolves_a_specparam_declared_inside_the_specify_block() {
let mut design = compile(
"module m(input a, output q);\n assign q = a;\n specify\n (a => q) = \
tRise;\n specparam tRise = 10;\n endspecify\nendmodule\n",
);
let eval = design.eval_session();
let body = top_body(eval.design());
let mut model = TimingModel::new();
model.seed_specparams(&[(InstanceId(0), body)]);
let delay = path_delay_expr(&body);
let via_model = eval_specparam_aware(&eval, &model, InstanceId(0), delay.as_sem_node())
.unwrap_or_else(|e| panic!("eval_specparam_aware failed: {e}"));
assert_eq!(via_model.as_i64(), Some(10));
}
#[test]
fn an_unseeded_specparam_is_a_named_error_not_a_silent_default() {
let mut design = compile(
"module m(input a, output q);\n specparam tRise = 10;\n assign q = a;\n \
specify\n (a => q) = tRise;\n endspecify\nendmodule\n",
);
let eval = design.eval_session();
let body = top_body(eval.design());
let model = TimingModel::new();
let delay = path_delay_expr(&body);
let err = eval_specparam_aware(&eval, &model, InstanceId(0), delay.as_sem_node())
.expect_err("an unseeded specparam reference must be a named error");
assert!(err.contains("tRise"), "{err}");
}
}