xlsynth-driver 0.61.0

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

use std::process;

use clap::ArgMatches;

use crate::common::{
    CodegenFlags, DEFAULT_WARNINGS_AS_ERRORS, PipelineSpec, enforce_extern_verilog_codegen_policy,
    extract_codegen_flags, extract_pipeline_spec, parse_bool_flag, parse_bool_flag_or,
    pipeline_codegen_flags_proto, resolve_type_inference_v2, scheduling_options_proto,
};
use crate::report_cli_error::report_cli_error_and_exit;
use crate::toolchain_config::ToolchainConfig;
use crate::tools::{run_codegen_pipeline, run_ir_converter_main, run_opt_main};

/// Converts the DSLX source in `input_file` using the top level entry point
/// named `top` into a Verilog pipeline and prints that to stdout.
fn dslx2pipeline(
    input_file: &std::path::Path,
    dslx_top: &str,
    pipeline_spec: &PipelineSpec,
    codegen_flags: &CodegenFlags,
    delay_model: &str,
    keep_temps: &Option<bool>,
    type_inference_v2: Option<bool>,
    allow_extern_verilog: bool,
    output_unopt_ir: &Option<&std::path::Path>,
    output_opt_ir: &Option<&std::path::Path>,
    config: &Option<ToolchainConfig>,
) {
    log::info!("dslx2pipeline; config: {:?}", config);
    let module_name = xlsynth::dslx_path_to_module_name(input_file).unwrap();
    let ir_top = xlsynth::mangle_dslx_name(module_name, dslx_top).unwrap();
    log::info!("dslx2pipeline; dslx_top: {}; ir_top: {}", dslx_top, ir_top);

    if let Some(tool_path) = config.as_ref().and_then(|c| c.tool_path.as_deref()) {
        log::info!("dslx2pipeline using tool path: {}", tool_path);
        let mut temp_dir = tempfile::Builder::new()
            .prefix("dslx2pipeline.")
            .tempdir()
            .unwrap();

        let dslx_stdlib_path = config
            .as_ref()
            .and_then(|c| c.dslx.as_ref()?.dslx_stdlib_path.as_deref());
        let dslx_path_slice = config
            .as_ref()
            .and_then(|c| c.dslx.as_ref()?.dslx_path.as_deref());
        let dslx_path = dslx_path_slice.map(|s| s.join(":"));
        let dslx_path_ref = dslx_path.as_ref().map(|s| s.as_str());

        let enable_warnings = config
            .as_ref()
            .and_then(|c| c.dslx.as_ref()?.enable_warnings.as_deref());
        let disable_warnings = config
            .as_ref()
            .and_then(|c| c.dslx.as_ref()?.disable_warnings.as_deref());
        let unopt_ir = run_ir_converter_main(
            input_file,
            Some(dslx_top),
            dslx_stdlib_path,
            dslx_path_ref,
            tool_path,
            enable_warnings,
            disable_warnings,
            type_inference_v2,
            /* convert_tests= */ false,
        );
        let unopt_ir_path = temp_dir.path().join("unopt.ir");
        std::fs::write(&unopt_ir_path, unopt_ir).unwrap();

        let opt_ir = run_opt_main(&unopt_ir_path, Some(&ir_top), tool_path);
        enforce_extern_verilog_codegen_policy(&opt_ir, allow_extern_verilog)
            .unwrap_or_else(|err| report_cli_error_and_exit(&err, Some("dslx2pipeline"), vec![]));
        let opt_ir_path = temp_dir.path().join("opt.ir");
        std::fs::write(&opt_ir_path, opt_ir.clone()).unwrap();

        // If user requested, persist the IR artifacts to the provided paths.
        if let Some(path) = output_unopt_ir {
            std::fs::write(path, &std::fs::read_to_string(&unopt_ir_path).unwrap())
                .expect("write output_unopt_ir");
        }
        if let Some(path) = output_opt_ir {
            std::fs::write(path, &std::fs::read_to_string(&opt_ir_path).unwrap())
                .expect("write output_opt_ir");
        }

        let sv = run_codegen_pipeline(
            &opt_ir_path,
            delay_model,
            pipeline_spec,
            codegen_flags,
            tool_path,
        );
        let sv_path = temp_dir.path().join("output.sv");
        std::fs::write(&sv_path, &sv).unwrap();

        if let Some(_) = keep_temps {
            temp_dir.disable_cleanup(true);
            eprintln!(
                "Pipeline generation successful. Output written to: {}",
                temp_dir.path().to_str().unwrap()
            );
        }
        println!("{}", sv);
    } else {
        if type_inference_v2 == Some(true) {
            eprintln!(
                "error: --type_inference_v2 is only supported when using --toolchain (external tool path)"
            );
            std::process::exit(1);
        }
        log::info!("dslx2pipeline using runtime APIs");
        let dslx = std::fs::read_to_string(input_file).unwrap();

        let dslx_path = config
            .as_ref()
            .and_then(|c| c.dslx.as_ref()?.dslx_path.as_deref());
        let dslx_path_vec = match dslx_path {
            Some(entries) => entries
                .iter()
                .map(|p| std::path::Path::new(p))
                .collect::<Vec<_>>(),
            None => vec![],
        };
        let dslx_stdlib_path = config
            .as_ref()
            .and_then(|c| c.dslx.as_ref()?.dslx_stdlib_path.as_deref());
        let enable_warnings = config
            .as_ref()
            .and_then(|c| c.dslx.as_ref()?.enable_warnings.as_deref());
        let disable_warnings = config
            .as_ref()
            .and_then(|c| c.dslx.as_ref()?.disable_warnings.as_deref());
        let warnings_as_errors = config
            .as_ref()
            .and_then(|c| c.dslx.as_ref()?.warnings_as_errors)
            .unwrap_or(DEFAULT_WARNINGS_AS_ERRORS);
        let convert_options = xlsynth::DslxConvertOptions {
            dslx_stdlib_path: dslx_stdlib_path.map(|p| std::path::Path::new(p)),
            additional_search_paths: dslx_path_vec,
            enable_warnings: enable_warnings,
            disable_warnings: disable_warnings,
            force_implicit_token_calling_convention: false,
        };
        let convert_result: xlsynth::DslxToIrPackageResult =
            xlsynth::convert_dslx_to_ir(&dslx, input_file, &convert_options)
                .expect("DSLX to IR conversion failed");
        if warnings_as_errors && !convert_result.warnings.is_empty() {
            for warning in convert_result.warnings {
                eprintln!(
                    "DSLX warning for {}: {}",
                    input_file.to_str().unwrap(),
                    warning
                );
            }
            eprintln!("DSLX warnings found with warnings-as-errors enabled; exiting.");
            process::exit(1);
        }

        for warning in convert_result.warnings {
            log::warn!(
                "DSLX warning for {}: {}",
                input_file.to_str().unwrap(),
                warning
            );
        }

        let opt_ir = xlsynth::optimize_ir(&convert_result.ir, &ir_top).unwrap();
        let opt_ir_text = opt_ir.to_string();
        enforce_extern_verilog_codegen_policy(&opt_ir_text, allow_extern_verilog)
            .unwrap_or_else(|err| report_cli_error_and_exit(&err, Some("dslx2pipeline"), vec![]));

        // If user requested, persist the IR artifacts to the provided paths.
        if let Some(path) = output_unopt_ir {
            std::fs::write(path, &convert_result.ir.to_string()).expect("write output_unopt_ir");
        }
        if let Some(path) = output_opt_ir {
            std::fs::write(path, &opt_ir_text).expect("write output_opt_ir");
        }

        let scheduling_options_flags_proto = scheduling_options_proto(delay_model, pipeline_spec);
        let codegen_flags_proto = pipeline_codegen_flags_proto(codegen_flags);
        let codegen_result = xlsynth::schedule_and_codegen(
            &opt_ir,
            &scheduling_options_flags_proto,
            &codegen_flags_proto,
        )
        .unwrap();
        let sv = codegen_result.get_verilog_text().unwrap();
        println!("{}", sv);
    }
}

pub fn handle_dslx2pipeline(matches: &ArgMatches, config: &Option<ToolchainConfig>) {
    log::info!("handle_dslx2pipeline");
    let input_file = matches.get_one::<String>("dslx_input_file").unwrap();
    let input_path = std::path::Path::new(input_file);
    let top = matches.get_one::<String>("dslx_top").unwrap();
    let pipeline_spec = extract_pipeline_spec(matches);
    let delay_model = matches.get_one::<String>("DELAY_MODEL").unwrap();
    let keep_temps = parse_bool_flag(matches, "keep_temps");
    let allow_extern_verilog = parse_bool_flag_or(matches, "allow_extern_verilog", true);
    let codegen_flags = extract_codegen_flags(matches, config.as_ref());

    // New optional output paths for capturing IR artifacts
    let output_unopt_ir: Option<std::path::PathBuf> = matches
        .get_one::<String>("output_unopt_ir")
        .map(|s| std::path::PathBuf::from(s));
    let output_opt_ir: Option<std::path::PathBuf> = matches
        .get_one::<String>("output_opt_ir")
        .map(|s| std::path::PathBuf::from(s));

    let type_inference_v2 = resolve_type_inference_v2(matches, config);

    dslx2pipeline(
        input_path,
        top,
        &pipeline_spec,
        &codegen_flags,
        delay_model,
        &keep_temps,
        type_inference_v2,
        allow_extern_verilog,
        &output_unopt_ir.as_deref(),
        &output_opt_ir.as_deref(),
        config,
    );
}