xlsynth-driver 0.61.0

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

use clap::ArgMatches;
use std::path::Path;
use xlsynth_g8r::netlist::io::{load_liberty_with_timing_data_from_path, parse_netlist_from_path};
use xlsynth_g8r::netlist::report::{NetlistReport, build_netlist_report, select_module};
use xlsynth_g8r::netlist::sta::StaOptions;
use xlsynth_g8r::netlist::stages::StagePartitionStatus;

const SUBCOMMAND: &str = "gv-stats";

fn shown_time_unit(time_unit: &str) -> &str {
    if time_unit.is_empty() {
        "<unspecified>"
    } else {
        time_unit
    }
}

fn shown_optional_metric(value: Option<f64>) -> String {
    value
        .map(|value| format!("{value:.6}"))
        .unwrap_or_else(|| "n/a".to_string())
}

fn push_input_to_register_delay_breakdown(
    out: &mut String,
    value: Option<xlsynth_g8r::netlist::sta::RegisterPathDelayBreakdown>,
) {
    if let Some(value) = value {
        out.push_str(&format!(
            "  combinational_delay: {:.6}\n",
            value.combinational_delay
        ));
        out.push_str(&format!("  setup_delay: {:.6}\n", value.setup_delay));
    }
}

fn push_register_to_register_delay_breakdown(
    out: &mut String,
    value: Option<xlsynth_g8r::netlist::sta::RegisterPathDelayBreakdown>,
) {
    if let Some(value) = value {
        out.push_str(&format!(
            "  clock_to_output_delay: {:.6}\n",
            value.clock_to_output_delay
        ));
        out.push_str(&format!(
            "  combinational_delay: {:.6}\n",
            value.combinational_delay
        ));
        out.push_str(&format!("  setup_delay: {:.6}\n", value.setup_delay));
    }
}

fn push_register_to_output_delay_breakdown(
    out: &mut String,
    value: Option<xlsynth_g8r::netlist::sta::RegisterPathDelayBreakdown>,
) {
    if let Some(value) = value {
        out.push_str(&format!(
            "  clock_to_output_delay: {:.6}\n",
            value.clock_to_output_delay
        ));
        out.push_str(&format!(
            "  combinational_delay: {:.6}\n",
            value.combinational_delay
        ));
    }
}

fn shown_stage_status(status: StagePartitionStatus) -> &'static str {
    match status {
        StagePartitionStatus::NoRegisters => "no_registers",
        StagePartitionStatus::Partitioned => "partitioned",
        StagePartitionStatus::NotPartitionable => "not_partitionable",
        StagePartitionStatus::Ambiguous => "ambiguous",
    }
}

fn render_netlist_report(report: &NetlistReport) -> String {
    let mut out = String::new();
    out.push_str(&format!("module: {}\n", report.module));
    out.push_str(&format!(
        "time_unit: {}\n",
        shown_time_unit(&report.time_unit)
    ));
    out.push_str(&format!(
        "primary_input_transition: {:.6}\n",
        report.primary_input_transition
    ));
    out.push_str(&format!(
        "module_output_load: {:.6}\n",
        report.module_output_load
    ));
    out.push_str(&format!("cell_area: {:.6}\n", report.cell_area));
    out.push_str(&format!(
        "max_delay: {}\n",
        shown_optional_metric(report.max_delay)
    ));
    out.push_str(&format!(
        "max_input_to_register_delay: {}\n",
        shown_optional_metric(report.max_input_to_register_delay)
    ));
    push_input_to_register_delay_breakdown(&mut out, report.max_input_to_register_delay_breakdown);
    out.push_str(&format!(
        "max_register_to_register_delay: {}\n",
        shown_optional_metric(report.max_register_to_register_delay)
    ));
    push_register_to_register_delay_breakdown(
        &mut out,
        report.max_register_to_register_delay_breakdown,
    );
    out.push_str(&format!(
        "max_register_to_output_delay: {}\n",
        shown_optional_metric(report.max_register_to_output_delay)
    ));
    push_register_to_output_delay_breakdown(
        &mut out,
        report.max_register_to_output_delay_breakdown,
    );
    out.push_str("timing_query_diagnostic_counts:\n");
    out.push_str(&format!(
        "  delay_slew_below_min_clamp_count: {}\n",
        report
            .timing_query_diagnostic_counts
            .delay_slew_below_min_clamp_count
    ));
    out.push_str(&format!(
        "  delay_slew_single_above_max_extrapolation_count: {}\n",
        report
            .timing_query_diagnostic_counts
            .delay_slew_single_above_max_extrapolation_count
    ));
    out.push_str(&format!(
        "  delay_slew_multiple_above_max_clamp_count: {}\n",
        report
            .timing_query_diagnostic_counts
            .delay_slew_multiple_above_max_clamp_count
    ));
    out.push_str(&format!(
        "  setup_below_min_clamp_count: {}\n",
        report
            .timing_query_diagnostic_counts
            .setup_below_min_clamp_count
    ));
    out.push_str(&format!(
        "  setup_above_max_clamp_count: {}\n",
        report
            .timing_query_diagnostic_counts
            .setup_above_max_clamp_count
    ));
    out.push_str(&format!("cell_count: {}\n", report.cell_count));
    out.push_str(&format!("cell_levels: {}\n", report.cell_levels));
    out.push_str(&format!(
        "sequential_cell_area: {:.6}\n",
        report.sequential_cell_area
    ));
    out.push_str(&format!(
        "non_stage_combinational_cell_area: {:.6}\n",
        report.non_stage_combinational_cell_area
    ));
    out.push_str(&format!(
        "stage_partition_status: {}\n",
        shown_stage_status(report.stage_partition_status)
    ));
    for stage in &report.stages {
        out.push_str(&format!(
            "stage {} max_delay={:.6} combinational_cell_area={:.6}\n",
            stage.stage, stage.max_delay, stage.combinational_cell_area
        ));
        push_register_to_register_delay_breakdown(&mut out, stage.max_delay_breakdown);
    }
    out.push_str("cell_counts:\n");
    for cell in &report.cells {
        out.push_str(&format!(
            "  {} count={} cell_area={:.6} total_area={:.6}\n",
            cell.cell, cell.count, cell.cell_area, cell.total_area
        ));
    }
    for output in &report.outputs {
        out.push_str(&format!(
            "output {} rise_arrival={:.6} fall_arrival={:.6} rise_transition={:.6} fall_transition={:.6} worst_arrival={:.6}\n",
            output.output,
            output.rise_arrival,
            output.fall_arrival,
            output.rise_transition,
            output.fall_transition,
            output.worst_arrival
        ));
    }
    out
}

pub fn handle_gv_stats(matches: &ArgMatches) {
    let netlist_path = Path::new(matches.get_one::<String>("netlist").unwrap());
    let liberty_proto_path = Path::new(matches.get_one::<String>("liberty_proto").unwrap());
    let module_name = matches.get_one::<String>("module_name").map(|s| s.as_str());
    let primary_input_transition = *matches
        .get_one::<f64>("primary_input_transition")
        .expect("primary_input_transition has default");
    let module_output_load = *matches
        .get_one::<f64>("module_output_load")
        .expect("module_output_load has default");
    let json_out = matches.get_one::<String>("json_out");

    let parsed = parse_netlist_from_path(netlist_path).unwrap_or_else(|e| {
        eprintln!(
            "{} error: failed to parse netlist '{}': {:#}",
            SUBCOMMAND,
            netlist_path.display(),
            e
        );
        std::process::exit(1)
    });
    let module = select_module(&parsed, module_name).unwrap_or_else(|e| {
        eprintln!("{} error: {}", SUBCOMMAND, e);
        std::process::exit(1)
    });
    let liberty = load_liberty_with_timing_data_from_path(liberty_proto_path).unwrap_or_else(|e| {
        eprintln!(
            "{} error: failed to load timing-enabled Liberty proto '{}': {:#}",
            SUBCOMMAND,
            liberty_proto_path.display(),
            e
        );
        std::process::exit(1)
    });
    let report = build_netlist_report(
        module,
        parsed.nets.as_slice(),
        &parsed.interner,
        &liberty,
        StaOptions {
            primary_input_transition,
            module_output_load,
        },
    )
    .unwrap_or_else(|e| {
        eprintln!("{} error: failed to compute report: {:#}", SUBCOMMAND, e);
        std::process::exit(1)
    });

    print!("{}", render_netlist_report(&report));
    if let Some(json_path) = json_out {
        std::fs::File::create(json_path)
            .map_err(|e| e.to_string())
            .and_then(|file| serde_json::to_writer_pretty(file, &report).map_err(|e| e.to_string()))
            .unwrap_or_else(|e| {
                eprintln!(
                    "{} error: failed writing JSON summary to '{}': {}",
                    SUBCOMMAND, json_path, e
                );
                std::process::exit(1)
            });
    }
}

#[cfg(test)]
mod tests {
    use super::render_netlist_report;
    use xlsynth_g8r::netlist::report::{
        CellAreaRow, NetlistReport, OutputTimingRow, StageReportRow,
    };
    use xlsynth_g8r::netlist::{
        sta::{RegisterPathDelayBreakdown, TimingQueryDiagnosticCounts},
        stages::StagePartitionStatus,
    };

    #[test]
    fn render_netlist_report_is_stable() {
        let report = NetlistReport {
            module: "top".to_string(),
            time_unit: "1ps".to_string(),
            primary_input_transition: 0.01,
            module_output_load: 0.0,
            cell_area: 4.0,
            max_delay: Some(3.0),
            max_input_to_register_delay: Some(1.0),
            max_input_to_register_delay_breakdown: Some(RegisterPathDelayBreakdown {
                clock_to_output_delay: 0.0,
                combinational_delay: 0.75,
                setup_delay: 0.25,
            }),
            max_register_to_register_delay: Some(2.0),
            max_register_to_register_delay_breakdown: Some(RegisterPathDelayBreakdown {
                clock_to_output_delay: 0.5,
                combinational_delay: 1.25,
                setup_delay: 0.25,
            }),
            max_register_to_output_delay: Some(3.0),
            max_register_to_output_delay_breakdown: Some(RegisterPathDelayBreakdown {
                clock_to_output_delay: 0.5,
                combinational_delay: 2.5,
                setup_delay: 0.0,
            }),
            timing_query_diagnostic_counts: TimingQueryDiagnosticCounts {
                delay_slew_below_min_clamp_count: 1,
                delay_slew_single_above_max_extrapolation_count: 2,
                delay_slew_multiple_above_max_clamp_count: 3,
                setup_below_min_clamp_count: 4,
                setup_above_max_clamp_count: 5,
            },
            cell_count: 3,
            cell_levels: 2,
            sequential_cell_area: 1.0,
            non_stage_combinational_cell_area: 1.0,
            stage_partition_status: StagePartitionStatus::Partitioned,
            stages: vec![StageReportRow {
                stage: 0,
                max_delay: 2.0,
                max_delay_breakdown: Some(RegisterPathDelayBreakdown {
                    clock_to_output_delay: 0.5,
                    combinational_delay: 1.25,
                    setup_delay: 0.25,
                }),
                combinational_cell_area: 2.0,
            }],
            cells: vec![CellAreaRow {
                cell: "INV".to_string(),
                count: 2,
                cell_area: 1.0,
                total_area: 2.0,
            }],
            outputs: vec![OutputTimingRow {
                output: "y".to_string(),
                rise_arrival: 2.0,
                fall_arrival: 3.0,
                rise_transition: 0.2,
                fall_transition: 0.3,
                worst_arrival: 3.0,
            }],
        };
        assert!(render_netlist_report(&report).contains("cell_levels: 2\n"));
        assert!(render_netlist_report(&report).contains("max_delay: 3.000000\n"));
        assert!(render_netlist_report(&report).contains(
            "max_input_to_register_delay: 1.000000\n  combinational_delay: 0.750000\n  setup_delay: 0.250000\n"
        ));
        assert!(render_netlist_report(&report).contains(
            "max_register_to_register_delay: 2.000000\n  clock_to_output_delay: 0.500000\n  combinational_delay: 1.250000\n  setup_delay: 0.250000\n"
        ));
        assert!(render_netlist_report(&report).contains(
            "max_register_to_output_delay: 3.000000\n  clock_to_output_delay: 0.500000\n  combinational_delay: 2.500000\n"
        ));
        assert!(render_netlist_report(&report).contains(
            "timing_query_diagnostic_counts:\n  delay_slew_below_min_clamp_count: 1\n  delay_slew_single_above_max_extrapolation_count: 2\n  delay_slew_multiple_above_max_clamp_count: 3\n  setup_below_min_clamp_count: 4\n  setup_above_max_clamp_count: 5\n"
        ));
        assert!(
            render_netlist_report(&report)
                .contains("stage 0 max_delay=2.000000 combinational_cell_area=2.000000\n")
        );
        assert!(render_netlist_report(&report).contains(
            "stage 0 max_delay=2.000000 combinational_cell_area=2.000000\n  clock_to_output_delay: 0.500000\n  combinational_delay: 1.250000\n  setup_delay: 0.250000\n"
        ));
    }
}