nl_opt 0.4.5

Verilog compiler driver for running safety-net passes
use clap::Parser;
use log::{error, info, warn};
use nl_compiler::{from_vast, from_vast_overrides};
use safety_net::Identifier;
use safety_net::emitter::VerilogEmitter;
use safety_pass::passes::BasicPasses;
use safety_pass::{Cell, Pipeline};
use simplelog::{ColorChoice, ConfigBuilder, TermLogger, TerminalMode};
use std::collections::HashMap;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};

/// Netlist optimization debugging tool
#[derive(Parser, Debug)]
#[command(version, long_about = None)]
struct Args {
    /// Verilog file to read from (or use stdin)
    input: Option<PathBuf>,

    /// Optional Verilog file to output to
    output: Option<PathBuf>,

    /// Do not parse with Xilinx-specific port names
    #[arg(short = 'x', long, default_value_t = false)]
    no_xilinx: bool,

    /// Verify after every pass (not just the last)
    #[arg(long, default_value_t = false)]
    verify_each: bool,

    /// Verbose logging
    #[arg(short, long, default_value_t = false)]
    verbose: bool,

    /// A list of passes to run in order
    #[arg(value_delimiter = ',', short = 'p', long, value_enum)]
    passes: Vec<BasicPasses>,

    /// Emit the verilog in non-ANSI style
    #[arg(long, default_value_t = false)]
    non_ansi: bool,
}

fn xilinx_overrides(id: &Identifier, cell: &Cell) -> Option<Cell> {
    if id.to_string() == "INV" {
        Some(
            cell.clone()
                .remap_input(0, "I".into())
                .remap_output(0, "O".into()),
        )
    } else {
        None
    }
}

/// Initializes the logger
fn logger_init(verbose: bool) {
    let level = if verbose {
        log::LevelFilter::Debug
    } else {
        log::LevelFilter::Info
    };
    let config = ConfigBuilder::new()
        .add_filter_ignore_str("egg")
        .set_thread_level(log::LevelFilter::Off)
        .build();
    TermLogger::init(level, config, TerminalMode::Stderr, ColorChoice::Auto).unwrap();
}

/// A wrapper for parsing verilog at file `path` with content `s`
fn sv_parse_wrapper(
    s: &str,
    path: Option<PathBuf>,
) -> Result<sv_parser::SyntaxTree, sv_parser::Error> {
    let incl: Vec<std::path::PathBuf> = vec![];
    let path = path.unwrap_or(Path::new("top.v").to_path_buf());
    match sv_parser::parse_sv_str(s, path, &HashMap::new(), &incl, true, false) {
        Ok((ast, _defs)) => Ok(ast),
        Err(e) => Err(e),
    }
}

fn main() -> std::io::Result<()> {
    let args = Args::parse();
    logger_init(args.verbose);

    if cfg!(debug_assertions) {
        warn!("Debug assertions are enabled");
    }

    info!("Netlist optimization debugging tool");

    let mut buf = String::new();

    let path: Option<PathBuf> = match args.input {
        Some(p) => {
            std::fs::File::open(&p)?.read_to_string(&mut buf)?;
            Some(p)
        }
        None => {
            info!("Reading from stdin...");
            std::io::stdin().read_to_string(&mut buf)?;
            None
        }
    };

    info!("Parsing Verilog...");
    let ast = sv_parse_wrapper(&buf, path).map_err(std::io::Error::other)?;

    info!("Compiling Verilog...");
    let f = if !args.no_xilinx {
        from_vast_overrides(&ast, xilinx_overrides)
    } else {
        from_vast(&ast)
    };

    let f = match f {
        Ok(f) => f,
        Err(e) => {
            error!("{e}");
            return Err(std::io::Error::other(e));
        }
    };

    let mut pipeline = Pipeline::default();

    for pass in args.passes {
        pipeline.insert_dyn(pass.get_pass());
    }

    let output = match pipeline.execute(&f, args.verify_each) {
        Ok(output) => output,
        Err(e) => {
            error!("Error during pass execution: {}", e);
            return Err(std::io::Error::other(e.to_string()));
        }
    };

    if let Some(p) = args.output {
        info!("Writing output netlist to Verilog...");
        let emitter = VerilogEmitter::new_default(&f);
        let emitter = if args.non_ansi {
            emitter.with_nonansi_style()
        } else {
            emitter.with_ansi_style()
        };
        let mut file = std::fs::File::create(p)?;
        write!(
            file,
            "/* Generated by {} {} */\n\n{}",
            env!("CARGO_PKG_NAME"),
            env!("CARGO_PKG_VERSION"),
            emitter
        )?;
    }

    println!("{output}");

    Ok(())
}