Skip to content

Beginning of timing aware optimization using comb-depth and node count#228

Open
danielpenas42 wants to merge 2 commits intocornell-zhang:mainfrom
danielpenas42:main
Open

Beginning of timing aware optimization using comb-depth and node count#228
danielpenas42 wants to merge 2 commits intocornell-zhang:mainfrom
danielpenas42:main

Conversation

@danielpenas42
Copy link
Copy Markdown
Contributor

No description provided.

@matth2k matth2k self-requested a review April 27, 2026 15:57
Comment thread src/bin/timing.rs
updated_region_mapping
.rewrite(&netlist)
.map_err(std::io::Error::other)?;

Copy link
Copy Markdown
Collaborator

@matth2k matth2k Apr 27, 2026

Choose a reason for hiding this comment

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

This time.rs file looks fine. But I think I want to take the time to untangle all the CLI flags for eqmap_asic and eqmap_fpga. I think using clap ValueEnum could cleanup most of eqmap_fpga, then we can just move the critical path partitioning code into the same file.

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.

I will work on #226 eventually, then we can merge the code from here into eqmap_fpga.

Comment thread src/timing.rs
/// Gets one critical path from the combinational-depth analysis.
pub fn get_critical_path(
netlist: &Netlist<PrimitiveCell>,
) -> Result<Vec<NetRef<PrimitiveCell>>, std::io::Error> {
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.

This function is ultimately a wrapper around build_critical_path. So this function should return the same type: Option<Vec<NetRef>>

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.

Maybe this function should just take the analysis as an argument. Then Option<Vec<NetRef>>> might be possible. Remember the ? operator works for Option types too.

Comment thread src/timing.rs
/// A representative critical path ending at a timing endpoint.
pub struct CriticalPath {
/// The output or register-input driver where this path ends.
pub endpoint: NetRef<PrimitiveCell>,
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.

If this struct must be public, then the fields should probably be private. Have to keep some of the state encapsulated in this struct, or else people could set the depth, path, and endpoint to mismatched values.

Copy link
Copy Markdown
Collaborator

@matth2k matth2k Apr 27, 2026

Choose a reason for hiding this comment

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

For example, I don't think endpoint and depth are necessary separate fields. Is this what you want to do?

pub struct CriticalPath {
    /// The path through the critical fan-in
    path:  Vec<NetRef<PrimitiveCell>>,
}

impl CriticalPath {
    pub fn new(...) {
        // Check path isn't empty and make new struct
        ...
    }
    
    pub fn depth(&self) -> usize {
        self.path.len()
    }

    pub fn endpoint(&self) -> NetRef<PrimitiveCell> {
        self.path[0].clone()
    }

    pub fn path(&self) -> &[NetRef<PrimitiveCell>] {
        // expose the vec as a slice
    }
}

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.

Comment thread src/timing.rs
) -> Result<HashSet<NetRef<PrimitiveCell>>, std::io::Error> {
let mut expanded_nodes: HashSet<NetRef<PrimitiveCell>> =
critical_path.iter().cloned().collect();
let mut frontier: Vec<NetRef<PrimitiveCell>> = critical_path.to_owned();
Copy link
Copy Markdown
Collaborator

@matth2k matth2k Apr 27, 2026

Choose a reason for hiding this comment

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

After the refactor, you should be able to do something like let frontier = critical_path.path().to_vec().

Comment thread src/timing.rs

#[derive(Debug, Clone, PartialEq, Eq)]
/// A representative critical path ending at a timing endpoint.
pub struct CriticalPath {
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.

Should this struct be renamed to DelayPath since it is not necessarily the most critical?

Comment thread src/timing.rs
for driver in node.drivers().flatten() {
if expanded_nodes.insert(driver.clone()) {
next_frontier.push(driver);
}
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.

Cool

Comment thread src/timing.rs
}

fn build_path_from_endpoint(
analysis: &CombDepthInfo<'_, PrimitiveCell>,
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.

I like that this function takes the analysis directly as the argument, because then we know that the netlist is in a verified state.

Copy link
Copy Markdown
Collaborator

@matth2k matth2k Apr 27, 2026

Choose a reason for hiding this comment

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

Maybe the other functions should use the same pattern instead of building their own CombDepthInfo analysis. It will also speed up the code, because we will be running the timing pass once and reusing the result.

Comment thread src/timing.rs
current = crit
.get_driver()
.ok_or_else(|| std::io::Error::other("Circuit is ill-formed"))?
.unwrap();
Copy link
Copy Markdown
Collaborator

@matth2k matth2k Apr 27, 2026

Choose a reason for hiding this comment

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

I think this looks prettier as:

if let Some(c) = crit.get_driver() {
    current = c.unwrap();
} else {
    // Do the early exit thing
    return Err(...);
}

Because currently we create a result type, just to call unwrap() on it. But honestly, it is a tie. Both aren't super pretty.

Comment thread src/netlist.rs
impl<'a, L: CircuitLang, I: Instantiable + LogicFunc<L>> 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>(
pub fn insert_filtered<F, G>(
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.

If we make this method public, we should improve the docline for it. Like how filter_netref and filter_inst change how errors are thrown by this func. Something like the graph induced by filter_netref and filter_inst must be a DAG.

Comment thread src/timing.rs
let mut endpoints: Vec<(NetRef<PrimitiveCell>, usize)> = endpoints
.into_iter()
.filter_map(|endpoint| match analysis.get_comb_depth(&endpoint) {
Some(CombDepthResult::Depth(depth)) => Some((endpoint, depth)),
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.

Do we need to separately retrieve the path depth? We know that re-building the path should give a vec that is the same length.

Comment thread src/timing.rs
.into_iter()
.take(n)
.map(|(endpoint, depth)| {
let path = build_path_from_endpoint(&analysis, endpoint.clone())?;
Copy link
Copy Markdown
Collaborator

@matth2k matth2k Apr 27, 2026

Choose a reason for hiding this comment

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

If a path fails to build, should we abort the function completely? Or just skip and try the next path? I think the latter is better.

Comment thread src/timing.rs
endpoints.insert(driver.unwrap());
}

for reg in netlist.matches(|inst| inst.is_seq()) {
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.

Should we not be populating our endpoints with get_critical_points?

Should be able to do

// Take only the top-n most critical endpoints
for node in analysis.get_critical_points().take(n) {
    ...
}

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.

Here is take() if you never used it before: https://doc.rust-lang.org/std/iter/struct.Take.html

Comment thread src/timing.rs

/// Expands a critical path backward through fan-in for `n` frontier steps.
pub fn expand_n_nodes(
critical_path: &[NetRef<PrimitiveCell>],
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.

Think this function is fine. But shouldn't it take in as an input your critical path struct? Otherwise, you're not really using the abstraction you've built.

Copy link
Copy Markdown
Collaborator

@matth2k matth2k left a comment

Choose a reason for hiding this comment

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

Summary

  • Use analysis as a func argument, so we can reuse the verified and analyzed state of the netlist
  • Use your struct for encapsulation (no pub fields)
  • Try to avoid remapping None to errors unless we really need to. Just have the wrapping function return None
  • You should populate your endpoints using get_critical_points() not just any register.

Comment thread src/timing.rs
})
.collect::<Vec<_>>();

endpoints.sort_by(|(a_endpoint, a_depth), (b_endpoint, b_depth)| {
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.

Once you refactor to use get_critical_points(), this sorting can be deleted. The iterator is designed to be sorted in order from most to least critical.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants