xlsynth-driver 0.61.0

Binary that integrates XLS capabilities into a driver program
Documentation
// SPDX-License-Identifier: Apache-2.0

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));

    // Header row.
    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);

    // Build per-module port direction maps so that instances whose type is
    // another module in the netlist can be treated as typed "cell" instances
    // for connectivity and cone traversal purposes.
    let module_port_dirs =
        netlist::connectivity::build_module_port_directions(&parsed_netlist.modules);

    // Select the target module in entry-point code rather than in the cone
    // traversal library.
    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());
                // Identify modules that contain the requested instance name to
                // make the disambiguation error more actionable.
                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![],
        );
    }
}