use crate::report_cli_error::report_cli_error_and_exit;
use clap::ArgMatches;
use csv::WriterBuilder;
use std::collections::HashSet;
use std::io::BufWriter;
use std::path::Path;
use xlsynth_g8r::liberty::IndexedLibrary;
use xlsynth_g8r::netlist;
use xlsynth_g8r::netlist::cone::{ConeError, ConeVisit, StopCondition, TraversalDirection};
fn parse_traversal_direction(dir: &str) -> Result<TraversalDirection, String> {
match dir {
"fanin" => Ok(TraversalDirection::Fanin),
"fanout" => Ok(TraversalDirection::Fanout),
other => Err(format!(
"invalid --traverse value '{}'; expected 'fanin' or 'fanout'",
other
)),
}
}
fn stop_condition_from_matches(matches: &ArgMatches) -> Result<StopCondition, String> {
let levels = matches.get_one::<String>("stop-at-levels");
let at_dff = matches.get_flag("stop-at-dff");
let at_block_port = matches.get_flag("stop-at-block-port");
let mut count = 0;
if levels.is_some() {
count += 1;
}
if at_dff {
count += 1;
}
if at_block_port {
count += 1;
}
if count != 1 {
return Err(
"exactly one of --stop-at-levels, --stop-at-dff, or --stop-at-block-port must be specified"
.to_string(),
);
}
if let Some(s) = levels {
let n: u32 = s
.parse()
.map_err(|e| format!("invalid --stop-at-levels value '{}': {}", s, e))?;
return Ok(StopCondition::Levels(n));
}
if at_dff {
return Ok(StopCondition::AtDff);
}
if at_block_port {
return Ok(StopCondition::AtBlockPort);
}
Err("internal error: no stop condition selected".to_string())
}
fn cone_error_to_report_message(err: ConeError) -> (String, Vec<(&'static str, String)>) {
match err {
ConeError::InvalidConnectivity { reason } => (
"gv-dump-cone could not normalize module connectivity".to_string(),
vec![("reason", reason)],
),
ConeError::MissingInstance { name } => (
"start instance not found in module".to_string(),
vec![("instance", name)],
),
ConeError::UnknownCellType {
cell,
instance,
lineno,
colno,
} => (
"cell type from netlist is missing in Liberty library".to_string(),
vec![
("cell_type", cell),
("instance", instance),
("inst_lineno", format!("{}", lineno)),
("inst_colno", format!("{}", colno)),
],
),
ConeError::InvalidStartPin {
instance,
pin,
reason,
} => (
"invalid start pin for cone traversal".to_string(),
vec![("instance", instance), ("pin", pin), ("reason", reason)],
),
ConeError::UnknownCellPin { cell, pin } => (
"cell pin from netlist is missing in Liberty library".to_string(),
vec![("cell_type", cell), ("pin", pin)],
),
}
}
pub fn handle_gv_dump_cone(matches: &ArgMatches) {
let netlist_path = matches
.get_one::<String>("netlist")
.expect("netlist path is required");
let liberty_proto_path = matches
.get_one::<String>("liberty_proto")
.expect("liberty_proto is required");
let instance_name = matches
.get_one::<String>("instance")
.expect("instance name is required");
let module_name = matches.get_one::<String>("module_name").map(|s| s.as_str());
let traverse_str = matches
.get_one::<String>("traverse")
.expect("--traverse is required");
let direction = match parse_traversal_direction(traverse_str) {
Ok(d) => d,
Err(msg) => {
report_cli_error_and_exit(&msg, None, vec![]);
}
};
let stop = match stop_condition_from_matches(matches) {
Ok(s) => s,
Err(msg) => {
report_cli_error_and_exit(&msg, None, vec![]);
}
};
let dff_cells: HashSet<String> = matches
.get_one::<String>("dff_cells")
.map(|s| {
s.split(',')
.filter(|p| !p.is_empty())
.map(|p| p.to_string())
.collect()
})
.unwrap_or_default();
if matches!(stop, StopCondition::AtDff) && dff_cells.is_empty() {
report_cli_error_and_exit(
"when using --stop-at-dff you must also provide --dff_cells with one or more DFF cell names",
None,
vec![],
);
}
let start_pins: Option<Vec<String>> = matches
.get_one::<String>("start-pins")
.map(|s| s.split(',').map(|p| p.to_string()).collect());
let stdout = std::io::stdout();
let handle = stdout.lock();
let mut wtr = WriterBuilder::new()
.has_headers(false)
.from_writer(BufWriter::new(handle));
if let Err(e) = wtr.write_record(["instance_type", "instance_name", "traversal_pin", "levels"])
{
report_cli_error_and_exit(
"failed to write CSV header to stdout",
Some(&format!("{}", e)),
vec![],
);
}
let parsed_netlist = match netlist::io::parse_netlist_from_path(Path::new(netlist_path)) {
Ok(p) => p,
Err(e) => {
report_cli_error_and_exit(
"failed to parse gate-level netlist",
Some(&format!("{}", e)),
vec![("netlist", netlist_path.as_str())],
);
}
};
let liberty_lib = match netlist::io::load_liberty_from_path(Path::new(liberty_proto_path)) {
Ok(l) => l,
Err(e) => {
report_cli_error_and_exit(
"failed to parse Liberty proto",
Some(&format!("{}", e)),
vec![("liberty_proto", liberty_proto_path.as_str())],
);
}
};
let indexed_lib = IndexedLibrary::new(liberty_lib);
let module_port_dirs =
netlist::connectivity::build_module_port_directions(&parsed_netlist.modules);
let module: &netlist::parse::NetlistModule = match module_name {
Some(name) => {
let mut found: Option<&netlist::parse::NetlistModule> = None;
for m in &parsed_netlist.modules {
let m_name = parsed_netlist
.interner
.resolve(m.name)
.expect("module name symbol should resolve");
if m_name == name {
found = Some(m);
break;
}
}
match found {
Some(m) => m,
None => {
report_cli_error_and_exit(
"requested module name was not found in netlist",
None,
vec![("module_name", name)],
);
}
}
}
None => {
if parsed_netlist.modules.len() != 1 {
let count_str = format!("{}", parsed_netlist.modules.len());
let mut modules_with_instance: Vec<String> = Vec::new();
for m in &parsed_netlist.modules {
let m_name = parsed_netlist
.interner
.resolve(m.name)
.expect("module name symbol should resolve")
.to_string();
let mut found = false;
for inst in &m.instances {
let inst_name = parsed_netlist
.interner
.resolve(inst.instance_name)
.expect("instance name symbol should resolve");
if inst_name == instance_name.as_str() {
found = true;
break;
}
}
if found {
modules_with_instance.push(m_name);
}
}
let modules_str = if modules_with_instance.is_empty() {
"<none>".to_string()
} else {
modules_with_instance.join(",")
};
report_cli_error_and_exit(
"netlist contains multiple modules; specify --module_name to disambiguate",
None,
vec![
("module_count", count_str.as_str()),
("modules_with_instance", modules_str.as_str()),
],
);
}
&parsed_netlist.modules[0]
}
};
let visit_result = netlist::cone::visit_module_cone(
module,
&parsed_netlist.nets,
&parsed_netlist.interner,
&indexed_lib,
&dff_cells,
Some(&module_port_dirs),
instance_name.as_str(),
start_pins.as_ref().map(|v| v.as_slice()),
direction,
stop,
|ConeVisit {
instance_type,
instance_name,
traversal_pin,
level,
}| {
let level_str = format!("{}", level);
if let Err(e) =
wtr.write_record([instance_type, instance_name, traversal_pin, &level_str])
{
report_cli_error_and_exit(
"failed to write CSV record",
Some(&format!("{}", e)),
vec![],
);
}
Ok(())
},
);
if let Err(err) = visit_result {
let (msg, details) = cone_error_to_report_message(err);
let mut kvs: Vec<(&str, &str)> = Vec::new();
for (k, v) in &details {
kvs.push((*k, v.as_str()));
}
report_cli_error_and_exit(&msg, None, kvs);
}
if let Err(e) = wtr.flush() {
report_cli_error_and_exit(
"failed to flush CSV output to stdout",
Some(&format!("{}", e)),
vec![],
);
}
}