use crate::symid::SymbolUid;
use std::collections::HashSet;
use crate::symid::SymbolId;
use sv_lang::kinds::{ExpressionKind, SymbolKind};
use sv_lang::{Expression, Statement, Symbol};
use super::FrameSlot;
use super::cfg::{
ArgBinding, LoweringError, ProcessCfg, RepeatCount, RepeatCounterId, SuspendPoint, Terminator,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FrameSlotDesc {
pub offset: u32,
pub width_bits: u32,
pub repr: FrameSlot,
pub source: FrameSlotSource,
pub cell: bool,
pub captured: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FrameSlotSource {
DeclaredVar(SymbolId),
Temporary(u32),
SyntheticCounter(RepeatCounterId),
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct FrameLayout {
pub slots: Vec<FrameSlotDesc>,
}
impl FrameLayout {
pub fn total_words(&self) -> u32 {
self.slots
.iter()
.map(|s| match s.repr {
_ if s.cell => 1,
FrameSlot::Narrow4State { word_count } => u32::from(word_count),
FrameSlot::Wide4State {
aval_word_count,
bval_word_count,
} => aval_word_count + bval_word_count,
FrameSlot::Array { rows, row_words } => rows * u32::from(row_words),
})
.sum()
}
}
fn storage_width(ty: &sv_lang::Type) -> u64 {
crate::state::storage::storage_width(ty).unwrap_or(0)
}
pub const EVENT_HANDLE_WIDTH: u64 = 64;
fn local_frame_slot(ty: &sv_lang::Type) -> Option<(u32, FrameSlot)> {
let words_for = |w: u64| (2 * w).div_ceil(64).max(1);
let is_collection = crate::state::storage::is_collection;
if ty.is_unpacked_array() && !is_collection(ty) {
let mut rows: u64 = 1;
let mut cur = *ty;
while cur.is_unpacked_array() && !is_collection(&cur) {
rows *= u64::from(cur.fixed_unpacked_array_range().width());
cur = cur.element_type()?;
}
let w = if cur.is_event() {
EVENT_HANDLE_WIDTH
} else {
storage_width(&cur)
};
if w == 0 || w > u64::from(u16::MAX) {
return None;
}
return Some((
w as u32,
FrameSlot::Array {
rows: u32::try_from(rows).ok()?,
row_words: words_for(w) as u16,
},
));
}
let w = if ty.is_event() {
EVENT_HANDLE_WIDTH
} else {
storage_width(ty)
};
if w == 0 || w > u64::from(u16::MAX) {
return None;
}
Some((
w as u32,
FrameSlot::Narrow4State {
word_count: words_for(w) as u16,
},
))
}
pub fn compute_liveness<'d>(cfg: &ProcessCfg<'d>) -> Result<FrameLayout, LoweringError> {
let read_set = aggregate_all_uses(cfg);
compute_liveness_with_oracle(cfg, &read_set)
}
pub fn compute_liveness_with_oracle<'d>(
cfg: &ProcessCfg<'d>,
read_set_oracle: &HashSet<SymbolId>,
) -> Result<FrameLayout, LoweringError> {
compute_liveness_impl(cfg, read_set_oracle, None)
}
pub fn compute_liveness_fault_injected<'d>(
cfg: &ProcessCfg<'d>,
read_set_oracle: &HashSet<SymbolId>,
bogus: SymbolId,
) -> Result<FrameLayout, LoweringError> {
compute_liveness_impl(cfg, read_set_oracle, Some(bogus))
}
fn compute_liveness_impl<'d>(
cfg: &ProcessCfg<'d>,
read_set_oracle: &HashSet<SymbolId>,
inject_bogus: Option<SymbolId>,
) -> Result<FrameLayout, LoweringError> {
let declared = collect_declared_locals(cfg)?;
let declared_ids: HashSet<SymbolId> = declared.iter().map(|(id, _)| id.clone()).collect();
let mut live_in = backward_fixpoint(cfg);
if let Some(bogus) = inject_bogus {
if let Some(first) = live_in.first_mut() {
first.insert(bogus);
}
}
for set in &live_in {
for sym in set {
if !read_set_oracle.contains(sym) {
return Err(LoweringError::liveness_oracle_mismatch(sym.to_string()));
}
}
}
let mut shared: HashSet<SymbolId> = HashSet::new();
for b in &cfg.blocks {
if let Terminator::ForkJoin { branches, .. } = &b.terminator {
for br in branches {
shared.extend(br.captured.iter().map(|v| v.uid()));
}
}
}
let mut declared = declared;
let mut from_parent: HashSet<SymbolId> = HashSet::new();
for v in &cfg.captured {
let id = v.uid();
if !declared.iter().any(|(d, _)| *d == id) {
shared.insert(id.clone());
from_parent.insert(id.clone());
declared.push((id, *v));
}
}
let mut slots = Vec::with_capacity(declared.len());
let mut next_offset: u32 = 0;
for (id, sym) in &declared {
let ty = sym.value_type().ok_or_else(|| {
LoweringError::malformed(format!("declared local {sym:?} has no value_type()"))
})?;
let (width_bits, repr) = local_frame_slot(&ty).ok_or_else(|| {
LoweringError::unsupported(format!(
"declared local {} of type {} has no frame representation (a value wider \
than 128 bits, a dynamic array, a class handle, ...)",
sym.name(),
ty.to_sv_string()
))
})?;
let words = match repr {
FrameSlot::Narrow4State { word_count } => u32::from(word_count),
FrameSlot::Array { rows, row_words } => rows * u32::from(row_words),
FrameSlot::Wide4State {
aval_word_count,
bval_word_count,
} => aval_word_count + bval_word_count,
};
let cell = shared.contains(id);
slots.push(FrameSlotDesc {
offset: next_offset,
width_bits,
repr,
source: FrameSlotSource::DeclaredVar(id.clone()),
cell,
captured: from_parent.contains(id),
});
next_offset += if cell { 1 } else { words };
}
let _ = &declared_ids;
for (counter, count) in collect_repeat_counters(cfg) {
let bit_width_u64 = match count {
RepeatCount::Const(_) | RepeatCount::Size(_) | RepeatCount::NestedSize { .. } => 64,
RepeatCount::Expr(count_expr) | RepeatCount::Inherited(count_expr) => count_expr
.expr_type()
.ok_or_else(|| {
LoweringError::malformed(format!(
"repeat({counter:?}) count expression has no expr_type()"
))
})?
.bit_width(),
};
if bit_width_u64 == 0 || bit_width_u64 > 64 {
return Err(LoweringError::unsupported(format!(
"repeat({counter:?}) count expression has bit_width {bit_width_u64} (0 or \
>64) -- wide/memory repeat counts are out of the A2 slice's scope \
(PR-025/CG-100, extended to R-run Task 8's synthetic counters)"
)));
}
let width_bits = bit_width_u64 as u32;
let word_count_u64 = (2u64 * bit_width_u64).div_ceil(64).max(1);
let word_count = word_count_u64 as u16;
slots.push(FrameSlotDesc {
offset: next_offset,
width_bits,
repr: FrameSlot::Narrow4State { word_count },
source: FrameSlotSource::SyntheticCounter(counter),
cell: false,
captured: false,
});
next_offset += u32::from(word_count);
}
let ref_temps: HashSet<u32> = cfg
.blocks
.iter()
.flat_map(|b| b.bindings.iter())
.filter_map(|b| match b {
ArgBinding::RefOpen { temp, .. } => Some(*temp),
_ => None,
})
.collect();
for (temp, target) in cfg.captures.iter().enumerate() {
if ref_temps.contains(&(temp as u32)) {
slots.push(FrameSlotDesc {
offset: next_offset,
width_bits: 64,
repr: FrameSlot::Narrow4State { word_count: 2 },
source: FrameSlotSource::Temporary(temp as u32),
cell: false,
captured: false,
});
next_offset += 2;
continue;
}
let ty = match cfg.capture_types.get(temp).copied().flatten() {
Some(formal) => formal.value_type(),
None => target.expr_type(),
}
.ok_or_else(|| {
LoweringError::malformed("intra-assignment timing target has no expr_type()")
})?;
let (width_bits, repr) = local_frame_slot(&ty).ok_or_else(|| {
LoweringError::unsupported(format!(
"an intra-assignment-timed assignment to a {} (no frame representation)",
ty.to_sv_string()
))
})?;
let words = match repr {
FrameSlot::Narrow4State { word_count } => u32::from(word_count),
FrameSlot::Array { rows, row_words } => rows * u32::from(row_words),
FrameSlot::Wide4State {
aval_word_count,
bval_word_count,
} => aval_word_count + bval_word_count,
};
slots.push(FrameSlotDesc {
offset: next_offset,
width_bits,
repr,
source: FrameSlotSource::Temporary(temp as u32),
cell: false,
captured: false,
});
next_offset += words;
}
Ok(FrameLayout { slots })
}
fn collect_repeat_counters<'d>(
cfg: &ProcessCfg<'d>,
) -> Vec<(RepeatCounterId, crate::process::cfg::RepeatCount<'d>)> {
cfg.blocks
.iter()
.filter_map(|b| b.repeat_init.clone())
.collect()
}
fn collect_declared_locals<'d>(
cfg: &ProcessCfg<'d>,
) -> Result<Vec<(SymbolId, Symbol<'d>)>, LoweringError> {
let mut out: Vec<(SymbolId, Symbol<'d>)> = Vec::new();
let mut seen: HashSet<SymbolId> = HashSet::new();
let mut stack = vec![cfg.body.as_sem_node()];
while let Some(node) = stack.pop() {
if let Some(stmt) = node.as_statement() {
for block_sym in super::cfg::block_scopes(&stmt) {
for member in block_sym.members() {
if member.kind() == SymbolKind::Variable
&& !crate::netlist::is_static_procedural_var(&member)
{
let id = member.uid();
if seen.insert(id.clone()) {
out.push((id, member));
}
}
}
}
}
stack.extend(super::cfg::live_children(&node));
}
for v in crate::patterns::pattern_vars(cfg.body.as_sem_node()) {
let id = v.uid();
if seen.insert(id.clone()) {
out.push((id, v));
}
}
for sym in &cfg.inlined_locals {
let id = sym.uid();
if seen.insert(id.clone()) {
out.push((id, *sym));
}
}
Ok(out)
}
pub(crate) fn declared_local_ids(cfg: &ProcessCfg<'_>) -> HashSet<SymbolId> {
collect_declared_locals(cfg)
.map(|v| v.into_iter().map(|(id, _)| id).collect())
.unwrap_or_default()
}
fn binding_use_def<'d>(b: &ArgBinding<'d>) -> (Vec<Symbol<'d>>, Option<Symbol<'d>>) {
let mut uses = Vec::new();
match b {
ArgBinding::In { formal, actual } => {
collect_named_values(*actual, &mut uses);
(uses, Some(*formal))
}
ArgBinding::Init { var } => {
if let Some(init) = var.initializer() {
collect_named_values(init, &mut uses);
}
(uses, Some(*var))
}
ArgBinding::Out { actual, formal } => {
collect_named_values(*actual, &mut uses);
uses.push(*formal);
(uses, None)
}
ArgBinding::Const { var, .. } | ArgBinding::ThisOf { var } => (uses, Some(*var)),
ArgBinding::Release { var } => {
uses.push(*var);
(uses, None)
}
ArgBinding::Capture { value, .. } => {
collect_named_values(*value, &mut uses);
(uses, None)
}
ArgBinding::AssignCaptured { target, .. } => {
collect_named_values(*target, &mut uses);
(uses, None)
}
ArgBinding::FromCaptured { formal, .. } => (uses, Some(*formal)),
ArgBinding::ToCaptured { formal, .. } => {
uses.push(*formal);
(uses, None)
}
ArgBinding::CallResult { .. } | ArgBinding::ClassEventLeave => (uses, None),
ArgBinding::CaptureThis { .. } => (uses, None),
ArgBinding::RefOpen { formal, actual, .. } => {
collect_named_values(*actual, &mut uses);
(uses, Some(*formal))
}
ArgBinding::RefClose { formal, actual, .. } => {
collect_named_values(*actual, &mut uses);
uses.push(*formal);
(uses, None)
}
ArgBinding::Step { var, .. } => {
uses.push(*var);
(uses, None)
}
ArgBinding::AssocKey {
var,
arr,
path,
next,
} => {
collect_named_values(*arr, &mut uses);
uses.extend(path.iter().map(|s| s.var));
if *next {
uses.push(*var);
}
(uses, Some(*var))
}
}
}
fn collect_named_values<'d>(expr: Expression<'d>, out: &mut Vec<Symbol<'d>>) {
let mut stack = vec![expr.as_sem_node()];
while let Some(node) = stack.pop() {
if let Some(e) = node.as_expression()
&& crate::symid::is_value_name(e.kind())
&& let Some(sym) = e.referenced_symbol()
{
out.push(sym);
}
if let Some(e) = node.as_expression()
&& e.kind() == ExpressionKind::Call
&& e.is_system_call() != Some(true)
&& let Some(callee) = e.call_subroutine()
&& callee.subroutine_kind() == Some(sv_lang::SubroutineKind::Function)
&& let Some(rv) = callee.subroutine_return_val_var()
{
out.push(rv);
}
stack.extend(node.children());
}
}
fn event_index_reads<'d>(e: Expression<'d>, out: &mut Vec<Symbol<'d>>) {
let mut cur = e;
while cur.kind() == ExpressionKind::ElementSelect {
if let Some(sel) = cur.selector() {
collect_named_values(sel, out);
}
match cur.select_value() {
Some(base) => cur = base,
None => break,
}
}
}
fn statement_use_def<'d>(stmt: &Statement<'d>) -> (Vec<Symbol<'d>>, Option<Symbol<'d>>) {
if let Some(top) = stmt.expressions().into_iter().next()
&& top.kind() == ExpressionKind::Assignment
{
let mut uses = Vec::new();
let mut def = None;
if let Some(lhs) = top.left() {
if crate::symid::is_value_name(lhs.kind()) {
def = lhs.referenced_symbol();
} else {
collect_named_values(lhs, &mut uses);
}
}
if let Some(rhs) = top.right() {
collect_named_values(rhs, &mut uses);
}
return (uses, def);
}
let mut uses = Vec::new();
for e in stmt.expressions() {
collect_named_values(e, &mut uses);
}
(uses, None)
}
fn successors(t: &Terminator<'_>) -> Vec<u32> {
match t {
Terminator::Fallthrough(b) => vec![b.0],
Terminator::Branch {
then_blk, else_blk, ..
} => vec![then_blk.0, else_blk.0],
Terminator::Assertion {
pass_blk,
fail_blk,
done_blk,
..
} => vec![pass_blk.0, fail_blk.0, done_blk.0],
Terminator::Suspend { resume_blk, .. } => vec![resume_blk.0],
Terminator::End { loop_back } => loop_back.iter().map(|b| b.0).collect(),
Terminator::Loop { entry, .. } => vec![entry.0],
Terminator::ForkJoin { join_blk, .. } => vec![join_blk.0],
Terminator::RepeatCheck {
then_blk, else_blk, ..
} => vec![then_blk.0, else_blk.0],
Terminator::Case {
arms, default_blk, ..
}
| Terminator::Switch {
arms, default_blk, ..
} => arms
.iter()
.chain(std::iter::once(default_blk))
.map(|b| b.0)
.collect(),
}
}
fn terminator_uses<'d>(t: &Terminator<'d>, out: &mut Vec<Symbol<'d>>) {
match t {
Terminator::Branch { cond, .. } => collect_named_values(*cond, out),
Terminator::Assertion { stmt, .. } => {
if let Some(c) = crate::process::cfg::assertion_cond(stmt) {
collect_named_values(c, out);
}
}
Terminator::Switch { on, .. } => {
for e in on.reads() {
collect_named_values(e, out);
}
}
Terminator::Case { stmt, .. } => {
for e in case_reads(stmt) {
collect_named_values(e, out);
}
}
Terminator::Suspend {
point: SuspendPoint::Delay(e, _),
..
} => collect_named_values(*e, out),
Terminator::Suspend {
point: SuspendPoint::Level(e),
..
} => collect_named_values(*e, out),
Terminator::Suspend {
point: SuspendPoint::ImplicitComb(exprs),
..
} => {
for e in exprs {
collect_named_values(*e, out);
}
}
Terminator::Suspend {
point: SuspendPoint::Edge { signal, iff, .. },
..
} => {
event_index_reads(*signal, out);
if let Some(g) = iff {
collect_named_values(*g, out);
}
}
Terminator::Suspend {
point: SuspendPoint::EdgeList(entries, iffs),
..
} => {
for (e, _) in entries {
event_index_reads(*e, out);
}
for g in iffs.iter().flatten() {
collect_named_values(*g, out);
}
}
Terminator::Suspend {
point:
SuspendPoint::CycleDelay { .. }
| SuspendPoint::OneStep
| SuspendPoint::WaitFork
| SuspendPoint::ByRequest,
..
}
| Terminator::Fallthrough(_)
| Terminator::End { .. }
| Terminator::Loop { .. }
| Terminator::ForkJoin { .. }
| Terminator::RepeatCheck { .. } => {}
}
}
fn backward_fixpoint<'d>(cfg: &ProcessCfg<'d>) -> Vec<HashSet<SymbolId>> {
let n = cfg.blocks.len();
let mut live_in: Vec<HashSet<SymbolId>> = vec![HashSet::new(); n];
let mut changed = true;
while changed {
changed = false;
for i in (0..n).rev() {
let block = &cfg.blocks[i];
let mut live: HashSet<SymbolId> = HashSet::new();
for succ in successors(&block.terminator) {
live.extend(live_in[succ as usize].iter().cloned());
}
let mut term_uses = Vec::new();
terminator_uses(&block.terminator, &mut term_uses);
for sym in term_uses {
live.insert(sym.uid());
}
for stmt in block.stmts.iter().rev() {
let (uses, def) = statement_use_def(stmt);
if let Some(d) = def {
live.remove(&d.uid());
}
for u in uses {
live.insert(u.uid());
}
}
for b in block.bindings.iter().rev() {
let (uses, def) = binding_use_def(b);
if let Some(d) = def {
live.remove(&d.uid());
}
for u in uses {
live.insert(u.uid());
}
}
if live != live_in[i] {
live_in[i] = live;
changed = true;
}
}
}
live_in
}
fn aggregate_all_uses<'d>(cfg: &ProcessCfg<'d>) -> HashSet<SymbolId> {
let mut out = HashSet::new();
for block in &cfg.blocks {
for b in &block.bindings {
let (uses, _def) = binding_use_def(b);
for u in uses {
out.insert(u.uid());
}
}
for stmt in &block.stmts {
let (uses, _def) = statement_use_def(stmt);
for u in uses {
out.insert(u.uid());
}
}
let mut term_uses = Vec::new();
terminator_uses(&block.terminator, &mut term_uses);
for u in term_uses {
out.insert(u.uid());
}
}
out
}
pub fn case_reads<'d>(stmt: &sv_lang::Statement<'d>) -> Vec<sv_lang::Expression<'d>> {
use sv_lang::kinds::StatementKind;
if stmt.kind() == StatementKind::Conditional {
return crate::process::cfg::if_series(*stmt)
.map(|c| {
c.links
.iter()
.flat_map(|l| l.conditions().into_iter().map(|c| c.expr()))
.collect()
})
.unwrap_or_default();
}
if stmt.kind() == StatementKind::PatternCase {
return crate::patterns::pattern_case_expr(*stmt)
.into_iter()
.chain(stmt.pattern_case_items().iter().filter_map(|i| i.filter()))
.collect();
}
stmt.expr()
.into_iter()
.chain(stmt.case_items().iter().flat_map(|i| i.expressions()))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn declared_local_width_is_never_truncated_even_at_the_64_bit_boundary() {
let design = crate::state::compile_source(
"module m; initial begin automatic logic [63:0] w; w = 0; end endmodule\n",
)
.unwrap();
let body = design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap();
let block = body
.members()
.find(|s| s.kind() == sv_lang::kinds::SymbolKind::ProceduralBlock)
.unwrap();
let root = block.body().unwrap();
let cfg = super::super::cfg::build_cfg(&root, super::super::cfg::ProcessLoopKind::OneShot)
.unwrap();
let frame = compute_liveness(&cfg).unwrap();
assert_eq!(frame.slots.len(), 1);
assert_eq!(frame.slots[0].width_bits, 64, "width must not be truncated");
match frame.slots[0].repr {
FrameSlot::Narrow4State { word_count } => {
assert_eq!(
word_count, 2,
"a 64-bit value's val+unk packing needs 2 u64 words, not 1"
);
}
other => panic!("expected Narrow4State for a 64-bit local, got {other:?}"),
}
}
#[test]
fn self_referencing_statement_keeps_its_own_rhs_use_live_into_the_block() {
let design = crate::state::compile_source(
"module m; initial begin int a; a = a + 1; end endmodule\n",
)
.unwrap();
let body = design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap();
let block = body
.members()
.find(|s| s.kind() == sv_lang::kinds::SymbolKind::ProceduralBlock)
.unwrap();
let root = block.body().unwrap();
let cfg = super::super::cfg::build_cfg(&root, super::super::cfg::ProcessLoopKind::OneShot)
.unwrap();
let read_set = aggregate_all_uses(&cfg);
let a_id = read_set
.iter()
.next()
.expect(
"`a` is read on the RHS of `a = a + 1`, so the whole-procedure read-set \
oracle must contain exactly its SymbolId",
)
.clone();
assert_eq!(
read_set.len(),
1,
"the only symbol ever read in this fixture is `a`"
);
let live_in = backward_fixpoint(&cfg);
assert!(
live_in.iter().any(|set| set.contains(&a_id)),
"`a` is used on the RHS of the very statement that defines it -- it must be live \
going into that block, but backward_fixpoint's computed live-in sets {live_in:?} \
contain no block with `a`"
);
}
#[test]
fn a_symbol_read_only_in_an_iff_guard_is_live_at_the_suspend() {
let design = crate::state::compile_source(
"module m; logic c, d; initial begin automatic int g; automatic int h; \
@(posedge d); @(posedge c iff g); @(posedge c iff h or posedge d); end endmodule\n",
)
.unwrap();
let body = design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap();
let block = body
.members()
.find(|s| s.kind() == sv_lang::kinds::SymbolKind::ProceduralBlock)
.unwrap();
let root = block.body().unwrap();
let cfg = super::super::cfg::build_cfg(&root, super::super::cfg::ProcessLoopKind::OneShot)
.unwrap();
let live_in = backward_fixpoint(&cfg);
let all: HashSet<SymbolId> = live_in.iter().flatten().cloned().collect();
let names: Vec<String> = all.iter().map(|id| format!("{id:?}")).collect();
for local in ["g", "h"] {
assert!(
names
.iter()
.any(|n| n.ends_with(local) || n.contains(&format!(".{local}"))),
"`{local}` is read only by an iff guard but is not live: {names:?}"
);
}
}
}