use crate::symid::SymbolUid;
use std::collections::{HashMap, HashSet};
use crate::netlist::{
DriverEntry, InstanceId, InstantiatedSignalId, Netlist, NetlistView, StaticSensitivity,
SymbolKey, TemplateId,
};
use super::{ClockDomainId, RegionId};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum NodeKind {
Net,
Process,
}
#[derive(Clone, Debug)]
pub struct Partition {
pub id: RegionId,
pub processes: Vec<InstantiatedSignalId>,
pub nets: Vec<InstantiatedSignalId>,
pub clock_domain: Option<ClockDomainId>,
}
#[derive(Default)]
pub struct DriverGraph {
write_edges: Vec<(InstantiatedSignalId, InstantiatedSignalId)>,
read_edges: Vec<(InstantiatedSignalId, InstantiatedSignalId)>,
node_kinds: HashMap<InstantiatedSignalId, NodeKind>,
all_nodes: Vec<InstantiatedSignalId>,
process_domain: HashMap<InstantiatedSignalId, ClockDomainId>,
domain_registry: Vec<(InstantiatedSignalId, crate::state::EdgeKind)>,
}
impl DriverGraph {
pub fn write_edges(&self) -> &[(InstantiatedSignalId, InstantiatedSignalId)] {
&self.write_edges
}
pub fn read_edges(&self) -> &[(InstantiatedSignalId, InstantiatedSignalId)] {
&self.read_edges
}
pub(crate) fn clock_domain_registry(
&self,
) -> &[(InstantiatedSignalId, crate::state::EdgeKind)] {
&self.domain_registry
}
}
fn member_position_key(
members: &[sv_lang::Symbol<'_>],
target: sv_lang::Symbol<'_>,
) -> Option<SymbolKey> {
members
.iter()
.position(|&m| m == target)
.map(|i| SymbolKey(i as u32))
}
fn resolve_net_drivers<'d>(
slang_analysis: &sv_lang::Analysis<'d>,
net_symbol: sv_lang::Symbol<'d>,
members: &[sv_lang::Symbol<'d>],
) -> Vec<(SymbolKey, sv_lang::DriverKind)> {
slang_analysis
.drivers(net_symbol)
.filter(|d| !d.is_input_port())
.filter_map(|d| {
member_position_key(members, d.containing_symbol()).map(|key| (key, d.kind()))
})
.collect()
}
pub fn build_driver_graph<'d>(
netlist: &Netlist<'d>,
slang_analysis: &sv_lang::Analysis<'d>,
) -> DriverGraph {
let mut graph = DriverGraph::default();
let mut domain_ids: HashMap<InstantiatedSignalId, ClockDomainId> = HashMap::new();
for template_index in 0..netlist.templates.len() {
let tmpl_id = TemplateId(template_index as u32);
let template = netlist.template(tmpl_id);
let net_key_order: Vec<SymbolKey> = template.signals.iter().map(|s| s.key).collect();
let net_key_set: HashSet<SymbolKey> = net_key_order.iter().copied().collect();
let Some(rep_instance) = representative_instance_of(netlist, slang_analysis, tmpl_id)
else {
continue;
};
let Some(body) = netlist.instance_symbol(rep_instance).instance_body() else {
continue;
};
let members: Vec<sv_lang::Symbol<'_>> = crate::netlist::module_items(&body);
let scope_keys = crate::netlist::symbol_key_map(body);
let mut write_pattern: Vec<(SymbolKey, SymbolKey)> = Vec::new();
let mut has_internal_driver: HashSet<SymbolKey> = HashSet::new();
for &net_key in &net_key_order {
let net_symbol = netlist.member_symbol(rep_instance, net_key);
let drivers = resolve_net_drivers(slang_analysis, net_symbol, &members);
if !drivers.is_empty() {
has_internal_driver.insert(net_key);
}
for (proc_key, _kind) in drivers {
write_pattern.push((proc_key, net_key));
}
}
let mut read_pattern: Vec<(SymbolKey, SymbolKey)> = Vec::new();
for proc in slang_analysis.procedures(body) {
let Some(proc_key) = member_position_key(&members, proc.symbol()) else {
continue;
};
for r in proc.read_set() {
let Some(&net_key) = scope_keys.get(&r.symbol().uid()) else {
continue;
};
if !net_key_set.contains(&net_key) || !has_internal_driver.contains(&net_key) {
continue;
}
read_pattern.push((net_key, proc_key));
}
}
for instance_id in netlist.registration_order() {
if netlist.instance(instance_id).template != tmpl_id {
continue;
}
for &net_key in &net_key_order {
let net_sig = InstantiatedSignalId {
instance: instance_id,
member: net_key,
};
graph.node_kinds.insert(net_sig, NodeKind::Net);
graph.all_nodes.push(net_sig);
}
for proc in &template.processes {
let proc_sig = InstantiatedSignalId {
instance: instance_id,
member: proc.key,
};
graph
.node_kinds
.entry(proc_sig)
.or_insert(NodeKind::Process);
graph.all_nodes.push(proc_sig);
}
for &(proc_key, net_key) in &write_pattern {
let proc_sig = InstantiatedSignalId {
instance: instance_id,
member: proc_key,
};
let net_sig = InstantiatedSignalId {
instance: instance_id,
member: net_key,
};
graph
.node_kinds
.entry(proc_sig)
.or_insert(NodeKind::Process);
graph.write_edges.push((proc_sig, net_sig));
}
for &(net_key, proc_key) in &read_pattern {
let proc_sig = InstantiatedSignalId {
instance: instance_id,
member: proc_key,
};
let net_sig = InstantiatedSignalId {
instance: instance_id,
member: net_key,
};
graph
.node_kinds
.entry(proc_sig)
.or_insert(NodeKind::Process);
graph.read_edges.push((net_sig, proc_sig));
}
for proc in &template.processes {
if let StaticSensitivity::Explicit { edges } = &proc.sensitivity
&& let Some(&(clk_key, edge_kind)) = edges.first()
{
let proc_sig = InstantiatedSignalId {
instance: instance_id,
member: proc.key,
};
let clk_sig = InstantiatedSignalId {
instance: instance_id,
member: clk_key,
};
let domain_id = *domain_ids.entry(clk_sig).or_insert_with(|| {
let id = ClockDomainId(graph.domain_registry.len() as u32);
graph.domain_registry.push((clk_sig, edge_kind));
id
});
graph.process_domain.insert(proc_sig, domain_id);
}
}
}
}
graph
}
pub(crate) fn representative_instance_of<'d>(
netlist: &Netlist<'d>,
slang_analysis: &sv_lang::Analysis<'d>,
tmpl: TemplateId,
) -> Option<InstanceId> {
let template = netlist.template(tmpl);
let template_has_analyzable_members =
!template.processes.is_empty() || !template.continuous_assigns.is_empty();
let mut fallback = None;
for instance_id in netlist.registration_order() {
if netlist.instance(instance_id).template != tmpl {
continue;
}
if fallback.is_none() {
fallback = Some(instance_id);
}
if !template_has_analyzable_members {
return Some(instance_id);
}
let Some(body) = netlist.instance_symbol(instance_id).instance_body() else {
continue;
};
if slang_analysis.procedures(body).count() > 0 {
return Some(instance_id);
}
}
fallback
}
pub(crate) fn populate_driver_kinds<'d>(
netlist: &mut Netlist<'d>,
slang_analysis: &sv_lang::Analysis<'d>,
) {
let mut updates: Vec<(usize, usize, Vec<DriverEntry>)> = Vec::new();
for template_index in 0..netlist.templates.len() {
let tmpl_id = TemplateId(template_index as u32);
let Some(rep_instance) = representative_instance_of(netlist, slang_analysis, tmpl_id)
else {
continue;
};
let Some(body) = netlist.instance_symbol(rep_instance).instance_body() else {
continue;
};
let members: Vec<sv_lang::Symbol<'_>> = crate::netlist::module_items(&body);
for (signal_index, signal) in netlist.templates[template_index].signals.iter().enumerate() {
let net_symbol = netlist.member_symbol(rep_instance, signal.key);
let entries: Vec<DriverEntry> =
resolve_net_drivers(slang_analysis, net_symbol, &members)
.into_iter()
.map(|(containing, driver_kind)| DriverEntry {
driver_kind,
containing,
})
.collect();
if !entries.is_empty() {
updates.push((template_index, signal_index, entries));
}
}
}
for (template_index, signal_index, entries) in updates {
netlist.templates[template_index].signals[signal_index].driver_set = entries;
}
}
pub fn partition(graph: &DriverGraph, max_passes: u32) -> Vec<Partition> {
let mut nodes: Vec<InstantiatedSignalId> = Vec::new();
let mut seen: HashSet<InstantiatedSignalId> = HashSet::new();
for &n in &graph.all_nodes {
if seen.insert(n) {
nodes.push(n);
}
}
for &(a, b) in graph.write_edges.iter().chain(graph.read_edges.iter()) {
for n in [a, b] {
if seen.insert(n) {
nodes.push(n);
}
}
}
let index_of: HashMap<InstantiatedSignalId, usize> =
nodes.iter().enumerate().map(|(i, &n)| (n, i)).collect();
let mut parent: Vec<usize> = (0..nodes.len()).collect();
let mut domain: Vec<Option<ClockDomainId>> = nodes
.iter()
.map(|n| graph.process_domain.get(n).copied())
.collect();
fn find(parent: &mut [usize], x: usize) -> usize {
if parent[x] != x {
parent[x] = find(parent, parent[x]);
}
parent[x]
}
fn union(parent: &mut [usize], domain: &mut [Option<ClockDomainId>], a: usize, b: usize) {
let ra = find(parent, a);
let rb = find(parent, b);
if ra == rb {
return;
}
parent[rb] = ra;
if domain[ra].is_none() {
domain[ra] = domain[rb];
}
}
for &(proc_sig, net_sig) in &graph.write_edges {
let (Some(&pi), Some(&ni)) = (index_of.get(&proc_sig), index_of.get(&net_sig)) else {
continue;
};
union(&mut parent, &mut domain, pi, ni);
}
for _pass in 0..max_passes.max(1) {
let mut changed = false;
for &(net_sig, proc_sig) in &graph.read_edges {
let (Some(&ni), Some(&pi)) = (index_of.get(&net_sig), index_of.get(&proc_sig)) else {
continue;
};
let rn = find(&mut parent, ni);
let rp = find(&mut parent, pi);
if rn == rp {
continue;
}
if domain[rn].is_none() || domain[rp].is_none() {
union(&mut parent, &mut domain, rn, rp);
changed = true;
}
}
if !changed {
break;
}
}
let mut region_of_root: HashMap<usize, RegionId> = HashMap::new();
let mut partitions: Vec<Partition> = Vec::new();
for (i, &sig) in nodes.iter().enumerate() {
let root = find(&mut parent, i);
let region_id = *region_of_root.entry(root).or_insert_with(|| {
let id = RegionId(partitions.len() as u32);
partitions.push(Partition {
id,
processes: Vec::new(),
nets: Vec::new(),
clock_domain: domain[root],
});
id
});
let kind = graph.node_kinds.get(&sig).copied().unwrap_or(NodeKind::Net);
let p = &mut partitions[region_id.0 as usize];
match kind {
NodeKind::Net => p.nets.push(sig),
NodeKind::Process => p.processes.push(sig),
}
}
partitions
}