Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/bin/eqmap_asic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,11 @@ fn main() -> std::io::Result<()> {
PartitionMethod::ArcSet => {
mapper.insert_partitioned().map_err(std::io::Error::other)?;
}
PartitionMethod::DelayPaths => todo!("Implement delay-based partitioning"),
PartitionMethod::DelayPaths => {
mapper
.insert_delay_paths(1, 2)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a later PR, we could make these arguments to insert_delay_paths() top-level command line flags. Leave as is for now.

.map_err(std::io::Error::other)?;
}
}

let mut mapping = mapper.mappings();
Expand Down
6 changes: 5 additions & 1 deletion src/bin/eqmap_fpga.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,11 @@ fn main() -> std::io::Result<()> {
PartitionMethod::ArcSet => {
mapper.insert_partitioned().map_err(std::io::Error::other)?;
}
PartitionMethod::DelayPaths => todo!("Implement delay-based partitioning"),
PartitionMethod::DelayPaths => {
mapper
.insert_delay_paths(1, 2)
.map_err(std::io::Error::other)?;
}
}

let mut mapping = mapper.mappings();
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ pub mod pass;
pub mod rewrite;
#[cfg(feature = "graph_dumps")]
pub mod serialize;
pub mod timing;
pub mod verilog;

#[cfg(test)]
Expand Down
32 changes: 29 additions & 3 deletions src/netlist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
use crate::asic::CellLang;
use crate::driver::CircuitLang;
use crate::lut::LutLang;
use crate::timing::get_critical_paths;
use bitvec::field::BitField;
use egg::{Id, RecExpr, Symbol};
use nl_compiler::FromId;
use safety_net::graph::MultiDiGraph;
use safety_net::graph::{CombDepthInfo, MultiDiGraph};
use safety_net::{
Analysis, DrivenNet, Error, Identifier, Instantiable, Logic, Net, Netlist, Parameter,
format_id, iter::NetDFSIterator,
Expand Down Expand Up @@ -129,8 +130,7 @@ where
})
}
}

impl<'a, L: CircuitLang, I: Instantiable + LogicFunc<L>> LogicMapper<'a, L, I> {
impl<'a, L: CircuitLang, I: Instantiable + LogicFunc<L> + 'static> LogicMapper<'a, L, I> {
/// Map `nets` to [CircuitLang] nodes. `nets` that do not pass `filter_netref` *and* `filter_inst` become leaves.
fn insert_filtered<F, G>(
&mut self,
Expand Down Expand Up @@ -345,6 +345,32 @@ impl<'a, L: CircuitLang, I: Instantiable + LogicFunc<L>> LogicMapper<'a, L, I> {
pub fn mappings(self) -> Vec<LogicMapping<L, I>> {
self.mappings
}

/// Maps a critical delay path plus a bounded amount of surrounding fan-in logic.
pub fn insert_delay_paths(
&mut self,
topk: usize,
branch_factor: usize,
) -> Result<RecExpr<L>, String> {
let analysis = self
._netlist
.get_analysis::<CombDepthInfo<_>>()
.map_err(|e| e.to_string())?;
let critical_paths = get_critical_paths(&analysis, topk);
if critical_paths.is_empty() {
return Err("Critical paths are empty".to_string());
}
let mut expanded_nodes = HashSet::new();
let mut roots = Vec::new();

for critical_path in &critical_paths {
let endpoint = critical_path.endpoint();
expanded_nodes.extend(critical_path.expand_n_nodes(branch_factor));
Comment thread
danielpenas42 marked this conversation as resolved.
roots.push(endpoint);
}

self.insert_filtered(roots, move |d| expanded_nodes.contains(d), |_| true)
}
}

/// Create an instantiable cell out of the [CellType]
Expand Down
108 changes: 108 additions & 0 deletions src/timing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*!

Timing analysis helpers for timing-aware optimization flows.

*/

use safety_net::DrivenNet;
use safety_net::Instantiable;
use safety_net::graph::CombDepthInfo;
use std::collections::HashSet;

#[derive(Debug)]
/// A representative critical path ending at a timing endpoint.
pub struct DelayPath<I: Instantiable> {
/// The path from endpoint backward through critical fan-in.
path: Vec<DrivenNet<I>>,
}

impl<I: Instantiable> DelayPath<I> {
/// Returns the depth/length of the delay path.
pub fn depth(&self) -> usize {
self.path.len()
}

/// The signal being driven by this path
pub fn endpoint(&self) -> DrivenNet<I> {
self.path[0].clone()
}

/// The nodes along the delay path as a slice
pub fn path(&self) -> &[DrivenNet<I>] {
&self.path
}
/// Expands and collects the transitive fan-in along the critical path provided by a branch factor of n.
pub fn expand_n_nodes(&self, n: usize) -> HashSet<DrivenNet<I>> {
let mut frontier: Vec<DrivenNet<I>> = self.path().to_vec();
let mut expanded_nodes: HashSet<DrivenNet<I>> = frontier.iter().cloned().collect();

for _ in 0..n {
let mut next_frontier = Vec::new();

for net in frontier {
let node = net.unwrap();

for input in node.inputs() {
if let Some(driver) = input.get_driver()
&& expanded_nodes.insert(driver.clone())
{
next_frontier.push(driver);
}
}
}

frontier = next_frontier;
}

expanded_nodes
}
}

impl<I: Instantiable> IntoIterator for DelayPath<I> {
type Item = DrivenNet<I>;
type IntoIter = std::vec::IntoIter<DrivenNet<I>>;

fn into_iter(self) -> Self::IntoIter {
self.path.into_iter()
}
}

fn build_path_from_endpoint<I: Instantiable>(
analysis: &CombDepthInfo<'_, I>,
endpoint: DrivenNet<I>,
) -> Option<DelayPath<I>> {
let mut path = Vec::new();
let mut current = endpoint;

while let Some(crit) = analysis.get_crit_input(&current.clone().unwrap()) {
path.push(current.clone());
if let Some(c) = crit.get_driver() {
current = c;
} else {
return None;
}
}

path.push(current);
Some(DelayPath { path })
}

/// Gets up to `n` most critical paths.
pub fn get_critical_paths<I: Instantiable>(
analysis: &CombDepthInfo<'_, I>,
n: usize,
) -> Vec<DelayPath<I>> {

@matth2k matth2k Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this function lazy? Return a Iterator<Item =DelayPath<I>>?
Would be nice because build_critical_path is a heavy function.

Something like

pub fn get_critical_paths<I: Instantiable>(
    analysis: &CombDepthInfo<'_, I>,
    n: usize,
) -> impl Iterator<Item =DelayPath<I>> {
    if analysis.get_max_depth().is_none() {
        return std::iter::empty();
    }

    analysis.get_critical_points().into_iter().take(n).flat_map(|p| build_path_from_endpoint(analysis, p))
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a big deal if it doesn't work. Let me know.

if analysis.get_max_depth().is_none() {
return Vec::new();
}

let mut vec = Vec::new();

for p in analysis.get_critical_points().into_iter().take(n) {
if let Some(path) = build_path_from_endpoint(analysis, p.clone()) {
vec.push(path);
}
}

vec
}
Loading
Loading