Beginning of timing aware optimization using comb-depth and node count#228
Beginning of timing aware optimization using comb-depth and node count#228danielpenas42 wants to merge 2 commits intocornell-zhang:mainfrom
Conversation
| updated_region_mapping | ||
| .rewrite(&netlist) | ||
| .map_err(std::io::Error::other)?; | ||
|
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
I will work on #226 eventually, then we can merge the code from here into eqmap_fpga.
| /// Gets one critical path from the combinational-depth analysis. | ||
| pub fn get_critical_path( | ||
| netlist: &Netlist<PrimitiveCell>, | ||
| ) -> Result<Vec<NetRef<PrimitiveCell>>, std::io::Error> { |
There was a problem hiding this comment.
This function is ultimately a wrapper around build_critical_path. So this function should return the same type: Option<Vec<NetRef>>
There was a problem hiding this comment.
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.
| /// 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>, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
}
}There was a problem hiding this comment.
| ) -> 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(); |
There was a problem hiding this comment.
After the refactor, you should be able to do something like let frontier = critical_path.path().to_vec().
|
|
||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||
| /// A representative critical path ending at a timing endpoint. | ||
| pub struct CriticalPath { |
There was a problem hiding this comment.
Should this struct be renamed to DelayPath since it is not necessarily the most critical?
| for driver in node.drivers().flatten() { | ||
| if expanded_nodes.insert(driver.clone()) { | ||
| next_frontier.push(driver); | ||
| } |
| } | ||
|
|
||
| fn build_path_from_endpoint( | ||
| analysis: &CombDepthInfo<'_, PrimitiveCell>, |
There was a problem hiding this comment.
I like that this function takes the analysis directly as the argument, because then we know that the netlist is in a verified state.
There was a problem hiding this comment.
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.
| current = crit | ||
| .get_driver() | ||
| .ok_or_else(|| std::io::Error::other("Circuit is ill-formed"))? | ||
| .unwrap(); |
There was a problem hiding this comment.
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.
| 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>( |
There was a problem hiding this comment.
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.
| 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)), |
There was a problem hiding this comment.
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.
| .into_iter() | ||
| .take(n) | ||
| .map(|(endpoint, depth)| { | ||
| let path = build_path_from_endpoint(&analysis, endpoint.clone())?; |
There was a problem hiding this comment.
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.
| endpoints.insert(driver.unwrap()); | ||
| } | ||
|
|
||
| for reg in netlist.matches(|inst| inst.is_seq()) { |
There was a problem hiding this comment.
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) {
...
}There was a problem hiding this comment.
Here is take() if you never used it before: https://doc.rust-lang.org/std/iter/struct.Take.html
|
|
||
| /// Expands a critical path backward through fan-in for `n` frontier steps. | ||
| pub fn expand_n_nodes( | ||
| critical_path: &[NetRef<PrimitiveCell>], |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
Noneto errors unless we really need to. Just have the wrapping function returnNone - You should populate your endpoints using
get_critical_points()not just any register.
| }) | ||
| .collect::<Vec<_>>(); | ||
|
|
||
| endpoints.sort_by(|(a_endpoint, a_depth), (b_endpoint, b_depth)| { |
There was a problem hiding this comment.
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.
No description provided.