600 lines
20 KiB
Rust
600 lines
20 KiB
Rust
// SPDX-FileCopyrightText: 2022 Thomas Kramer <code@tkramer.ch>
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
#![deny(missing_docs)]
|
|
|
|
//! Bindings to the 'TritonRoute' detail router and 'TritonRoute-WXL' global+detail router.
|
|
//! TritonRouter is used as an external executable. Communication happens over the file system.
|
|
//!
|
|
//! # Source code of routers
|
|
//! * <https://github.com/The-OpenROAD-Project/TritonRoute/>
|
|
//! * <https://github.com/ABKGroup/TritonRoute-WXL>
|
|
|
|
extern crate num_traits;
|
|
extern crate libreda_lefdef;
|
|
extern crate libreda_pnr;
|
|
|
|
use log;
|
|
|
|
use num_traits::{PrimInt, FromPrimitive};
|
|
|
|
use std::path;
|
|
use std::process::{Command, Stdio};
|
|
use std::env;
|
|
use std::collections::{HashMap, HashSet};
|
|
use std::io::{BufRead, BufReader, Write, BufWriter};
|
|
use std::thread;
|
|
|
|
use libreda_pnr::libreda_db::prelude as db;
|
|
use libreda_pnr::db::*;
|
|
use libreda_pnr::route::prelude::*;
|
|
|
|
use libreda_lefdef::{DEF, DEFReaderConfig, LEF};
|
|
use libreda_lefdef::export::{export_db_to_def, DEFExportOptions};
|
|
use libreda_lefdef::import::DEFImportOptions;
|
|
|
|
|
|
/// Configuration for TritonRouter.
|
|
#[derive(Debug, Clone)]
|
|
pub struct TritonRoute<LN: LayoutBase> {
|
|
/// Path to the binary of TritonRouter.
|
|
executable: path::PathBuf,
|
|
/// Path to the LEF file which contains the technology information and cell library.
|
|
lef_file: path::PathBuf,
|
|
/// Path to the working directory. This directory will be used to write result files or temporary files.
|
|
/// If nothing is specified, a temporary directory will be created when needed.
|
|
working_dir: path::PathBuf,
|
|
/// Enable verbose mode of TritonRouter.
|
|
verbose: bool,
|
|
/// Specify number of threads.
|
|
threads: Option<usize>,
|
|
/// Template DEF for writing the routing problem into. This can be used to specify routing tracks.
|
|
template_def: DEF,
|
|
/// Layer which contains cell outlines.
|
|
outline_layer: LN::LayerId,
|
|
/// Enable usage of TritonRoute-WXL. This router does not need guides, it computes the global
|
|
/// routes itself.
|
|
is_triton_wxl: bool,
|
|
/// Path to FLUTE 'potentially optimal steiner tree' lookup table. Needed by TritonRoute-WXL.
|
|
flute_post9_path: path::PathBuf,
|
|
/// Path to FLUTE 'potentially optimal wire-length vector' lookup table. Needed by TritonRoute-WXL.
|
|
flute_powv9_path: path::PathBuf,
|
|
}
|
|
|
|
impl<LN> TritonRoute<LN>
|
|
where LN: L2NBase,
|
|
LN::Coord: PrimInt + FromPrimitive {
|
|
/// Specify the location of the `TritonRoute` executable and the location
|
|
/// of the library and technology LEF file.
|
|
pub fn new(executable: path::PathBuf, lef_file: path::PathBuf, outline_layer: LN::LayerId) -> Self {
|
|
Self {
|
|
executable,
|
|
lef_file,
|
|
working_dir: env::temp_dir(),
|
|
verbose: false,
|
|
threads: std::thread::available_parallelism().ok().map(|n| n.get()),
|
|
template_def: Default::default(),
|
|
outline_layer,
|
|
is_triton_wxl: false,
|
|
flute_post9_path: Default::default(),
|
|
flute_powv9_path: Default::default(),
|
|
}
|
|
}
|
|
|
|
/// Specify the location of the `TritonRouter-WXL` executable and the location
|
|
/// of the library and technology LEF file.
|
|
pub fn new_wxl(executable: path::PathBuf, lef_file: path::PathBuf, outline_layer: LN::LayerId) -> Self {
|
|
Self {
|
|
executable,
|
|
lef_file,
|
|
working_dir: env::temp_dir(),
|
|
verbose: false,
|
|
threads: std::thread::available_parallelism().ok().map(|n| n.get()),
|
|
template_def: Default::default(),
|
|
outline_layer,
|
|
is_triton_wxl: true,
|
|
flute_post9_path: "POST9.dat".into(),
|
|
flute_powv9_path: "POWV9.dat".into(),
|
|
}
|
|
}
|
|
|
|
/// Set the '-threads' argument of TritonRoute.
|
|
pub fn set_num_threads(&mut self, num_threads: usize) {
|
|
self.threads = Some(num_threads);
|
|
}
|
|
|
|
/// Access the template for the output DEF file which will be presented to the router.
|
|
pub fn template_def(&mut self) -> &mut DEF {
|
|
&mut self.template_def
|
|
}
|
|
|
|
/// Check if the executable and the LEF file can be found.
|
|
/// Detailed error messages will be written to the log.
|
|
pub fn check_configuration(&self) -> bool {
|
|
log::debug!("Checking configuration.");
|
|
|
|
let mut success = true;
|
|
|
|
if !self.executable.exists() {
|
|
success = false;
|
|
log::error!("Path to executable is not valid: {}", self.executable.display());
|
|
} else if !self.executable.is_file() {
|
|
success = false;
|
|
log::error!("Path to executable is not a file: {}", self.executable.display());
|
|
}
|
|
|
|
if !self.lef_file.exists() {
|
|
success = false;
|
|
log::error!("Path to LEF file is not valid: {}", self.lef_file.display());
|
|
} else if !self.lef_file.is_file() {
|
|
success = false;
|
|
log::error!("Path to LEF is not a file: {}", self.lef_file.display());
|
|
}
|
|
|
|
if !self.working_dir.exists() {
|
|
success = false;
|
|
log::error!("Working dir does not exist: {}", self.working_dir.display());
|
|
}
|
|
|
|
if self.is_triton_wxl {
|
|
// Check lookup-table files for FLUTE.
|
|
if !self.flute_post9_path.exists() {
|
|
success = false;
|
|
log::error!("Lookup-table file for FLUTE cannot be found: {}", self.flute_post9_path.display());
|
|
}
|
|
if !self.flute_powv9_path.exists() {
|
|
success = false;
|
|
log::error!("Lookup-table file for FLUTE cannot be found: {}", self.flute_powv9_path.display());
|
|
}
|
|
}
|
|
|
|
|
|
success
|
|
}
|
|
|
|
/// Specify a working directory where temporary files or results will be stored.
|
|
/// If nothing is specified, a temporary directory will be created when needed.
|
|
pub fn with_working_dir(mut self, working_dir: path::PathBuf) -> Self {
|
|
self.working_dir = working_dir;
|
|
self
|
|
}
|
|
|
|
/// Run the triton router with a placed design (in DEF format) and a file with the routing guides
|
|
/// as determined by the global router.
|
|
/// On success returns the path to the output file.
|
|
fn run(&self, def_input: path::PathBuf, guide_file: Option<path::PathBuf>) -> Result<path::PathBuf, ()> {
|
|
if !self.check_configuration() {
|
|
log::error!("Bad configuration.");
|
|
return Err(());
|
|
}
|
|
|
|
if !def_input.is_file() {
|
|
log::error!("DEF input file does not exist: {}", def_input.display());
|
|
return Err(());
|
|
}
|
|
if let Some(guide_file) = &guide_file {
|
|
if !guide_file.is_file() {
|
|
log::error!("Guide input file does not exist: {}", guide_file.display());
|
|
return Err(());
|
|
}
|
|
}
|
|
|
|
let working_dir = self.working_dir.clone();
|
|
|
|
let mut output_file = working_dir.clone();
|
|
output_file.push("output.def");
|
|
|
|
log::info!("Run TritonRoute. Executable: {}", self.executable.display());
|
|
log::info!("Routing input file: {}", def_input.display());
|
|
if let Some(guide_file) = &guide_file {
|
|
log::info!("Routing guide input file: {}", guide_file.display());
|
|
}
|
|
log::info!("Routing output file: {}", output_file.display());
|
|
|
|
let mut command = Command::new(&self.executable);
|
|
command
|
|
.stdin(Stdio::piped())
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped())
|
|
.arg("-lef").arg(&self.lef_file)
|
|
.arg("-def").arg(&def_input)
|
|
.arg("-output").arg(&output_file);
|
|
|
|
if let Some(guide_file) = &guide_file {
|
|
command.arg("-guide").arg(&guide_file);
|
|
}
|
|
|
|
if self.verbose {
|
|
command.arg("-verbose");
|
|
}
|
|
|
|
if let Some(threads) = self.threads {
|
|
command.arg("-threads").arg(format!("{}", threads));
|
|
}
|
|
|
|
log::debug!("Running: {:?}", &command);
|
|
|
|
let mut handle = command.spawn()
|
|
.map_err(|err| {
|
|
log::error!("Failed to start TritonRoute: {}", err);
|
|
()
|
|
})?;
|
|
|
|
let mut stdout = handle.stdout.take().unwrap();
|
|
let mut stderr = handle.stderr.take().unwrap();
|
|
|
|
// Reader thread for stdout.
|
|
thread::spawn(move || {
|
|
|
|
let mut buf = String::new();
|
|
let mut buf_reader = BufReader::with_capacity(128, &mut stdout);
|
|
|
|
loop {
|
|
let read_result = buf_reader.read_line(&mut buf);
|
|
match read_result {
|
|
Ok(num_bytes) => {
|
|
if num_bytes == 0 {
|
|
break
|
|
}
|
|
log::info!("TritonRoute: {}", buf);
|
|
}
|
|
Err(err) => {
|
|
log::error!("{}", err);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Reader thread for stderr.
|
|
thread::spawn(move || {
|
|
|
|
let mut buf = String::new();
|
|
let mut buf_reader = BufReader::new(&mut stderr);
|
|
|
|
loop {
|
|
let read_result = buf_reader.read_line(&mut buf);
|
|
match read_result {
|
|
Ok(num_bytes) => {
|
|
if num_bytes == 0 {
|
|
break
|
|
}
|
|
log::error!("TritonRoute: {}", buf);
|
|
}
|
|
Err(err) => {
|
|
log::error!("{}", err);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
log::debug!("Wait for sub-process to finish.");
|
|
let exit_status = handle.wait();
|
|
|
|
if let Err(e) = &exit_status {
|
|
log::error!("Running TritonRouter failed: {}", e);
|
|
return Err(());
|
|
}
|
|
|
|
if let Ok(status) = exit_status {
|
|
if !status.success() {
|
|
log::error!("TritonRouter failed with status: {}", status);
|
|
|
|
// log::info!("stdout: {}", String::from_utf8_lossy(&output.stdout));
|
|
// log::error!("stderr: {}", String::from_utf8_lossy(&output.stderr));
|
|
return Err(());
|
|
}
|
|
log::info!("TritonRouter exited successfully.")
|
|
}
|
|
|
|
Ok(output_file)
|
|
}
|
|
|
|
/// Perform detail routing on all nets in the specified top cell.
|
|
/// On success: returns the routes. Internally the routes are stored in DEF format.
|
|
/// On error: returns partial (or empty) routes and a list of failed nets.
|
|
fn route_all_nets(
|
|
&self,
|
|
chip: &LN,
|
|
top: LN::CellId,
|
|
routing_guides: &Vec<(LN::NetId, Vec<(db::Rect<LN::Coord>, LN::LayerId)>)>,
|
|
) -> Result<TritonRoutingResult, ()>
|
|
{
|
|
|
|
// Check that all nets have a name.
|
|
{
|
|
let top = chip.cell_ref(&top);
|
|
let num_nets_without_name = top.each_net()
|
|
.filter(|net| net.name().is_none())
|
|
.count();
|
|
if num_nets_without_name > 0 {
|
|
log::error!("All nets need to be named. Number of nets without name: {}", num_nets_without_name);
|
|
return Err(());
|
|
}
|
|
}
|
|
|
|
// Export the top cell to a DEF file.
|
|
let def_input_file = {
|
|
let mut def_input_file = self.working_dir.clone();
|
|
def_input_file.push("input.def");
|
|
|
|
// Populate a DEF structure with the data from the `chip`.
|
|
let mut def = self.template_def.clone();
|
|
let mut options = DEFExportOptions::default();
|
|
options.outline_layer = Some(self.outline_layer.clone());
|
|
|
|
// Convert to DEF.
|
|
let result = export_db_to_def(&options, chip, &top, &mut def);
|
|
result.map_err(|err| {
|
|
log::error!("Failed to export design to DEF: {}", err);
|
|
()
|
|
})?;
|
|
|
|
// Save DEF to file.
|
|
def.store_file(&def_input_file)
|
|
.map_err(|err| {
|
|
log::error!("Failed to write DEF file: {}", err);
|
|
()
|
|
})?;
|
|
|
|
def_input_file
|
|
};
|
|
|
|
// Export the routing guides to a file.
|
|
let guide_file = if self.is_triton_wxl {
|
|
None
|
|
} else {
|
|
let mut guide_file = self.working_dir.clone();
|
|
guide_file.push("guides");
|
|
log::info!("Write guide file: {}", guide_file.display());
|
|
|
|
let f = std::fs::File::create(&guide_file)
|
|
.map_err(|err| {
|
|
log::error!("Failed to create guide file: {}", err);
|
|
()
|
|
})?;
|
|
let mut buf = BufWriter::new(f);
|
|
|
|
write_routing_guides(
|
|
&mut buf,
|
|
chip,
|
|
&top,
|
|
routing_guides,
|
|
)
|
|
.map_err(|err| {
|
|
log::error!("Error writing guide file: {}", err);
|
|
()
|
|
})?;
|
|
|
|
Some(guide_file)
|
|
};
|
|
|
|
// Run the router.
|
|
let routing_result_file = self.run(def_input_file, guide_file)?;
|
|
|
|
// Read back the produced DEF file.
|
|
let result_def = {
|
|
let reader_config = DEFReaderConfig::default();
|
|
let result_def = DEF::load_file(&reader_config, &routing_result_file)
|
|
.map_err(|err| {
|
|
log::error!("Failed to read DEF with routing results: {}", err);
|
|
()
|
|
})?;
|
|
result_def
|
|
};
|
|
|
|
|
|
let result = TritonRoutingResult {
|
|
initialized: true,
|
|
lef_file: self.lef_file.clone(),
|
|
result_def,
|
|
};
|
|
|
|
Ok(result)
|
|
}
|
|
}
|
|
|
|
/// Output of TritonRoute.
|
|
#[derive(Clone, Default)]
|
|
pub struct TritonRoutingResult {
|
|
/// When false, this represents a failed routing result.
|
|
initialized: bool,
|
|
/// Path the the LEF library.
|
|
lef_file: path::PathBuf,
|
|
/// DEF data structure which contains the routed design.
|
|
result_def: DEF,
|
|
}
|
|
|
|
impl<LN> DrawDetailRoutes<LN> for TritonRoutingResult
|
|
where LN: db::L2NEdit,
|
|
LN::Coord: FromPrimitive + PrimInt {
|
|
type Error = ();
|
|
|
|
fn draw_detail_routes(&self, chip: &mut LN, top: &LN::CellId) -> Result<(), Self::Error> {
|
|
|
|
// Abort if routing result is not initialized (for example when routing failed).
|
|
if !self.initialized {
|
|
log::warn!("Failed routing result.");
|
|
return Err(());
|
|
}
|
|
|
|
// Draw the routes into the layout.
|
|
log::info!("Import routes from DEF.");
|
|
|
|
// Read the LEF first. This is needed for via definitions, default wiring widths, etc.
|
|
let lef = LEF::load_file(&self.lef_file)
|
|
.map_err(|err| {
|
|
log::error!("Failed to load LEF file: {}", err);
|
|
()
|
|
})?;
|
|
|
|
// Sanity check: Data-base unit should be the same as used in LEF.
|
|
{
|
|
let lef_dbu = LN::Coord::from_u64(lef.technology.units.database_microns)
|
|
.expect("Failed to cast DATABASE MICRONS to {}");
|
|
let db_dbu = chip.dbu();
|
|
if lef_dbu != db_dbu {
|
|
log::warn!("Mismatch of distance units. Current design: {} [1/um], LEF: {} [1/um]", db_dbu, lef_dbu);
|
|
}
|
|
}
|
|
|
|
let mut def_import_options = DEFImportOptions::default();
|
|
def_import_options.lef_import_options.create_missing_layers = true;
|
|
def_import_options.lef_import_options.skip_existing_vias = true;
|
|
def_import_options.skip_existing_vias = true;
|
|
|
|
libreda_lefdef::import::import_lef_vias(
|
|
&def_import_options.lef_import_options,
|
|
&lef,
|
|
chip,
|
|
).map_err(|err| {
|
|
log::error!("Failed to import via definitions from LEF: {}", err);
|
|
()
|
|
})?;
|
|
|
|
libreda_lefdef::import::import_def_vias(
|
|
&def_import_options,
|
|
&self.result_def,
|
|
chip,
|
|
).map_err(|err| {
|
|
log::error!("Failed to import via definitions from DEF: {}", err);
|
|
()
|
|
})?;
|
|
|
|
libreda_lefdef::import::import_def_regular_wiring(
|
|
&def_import_options,
|
|
&lef,
|
|
&self.result_def,
|
|
top,
|
|
chip,
|
|
).map_err(|err| {
|
|
log::error!("Failed to import routes from DEF: {}", err);
|
|
()
|
|
})?;
|
|
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<LN> DetailRouter<LN> for TritonRoute<LN>
|
|
where LN: db::L2NEdit,
|
|
LN::Coord: FromPrimitive + PrimInt {
|
|
type RoutingResult = TritonRoutingResult;
|
|
/// On error: return partial result and list of failed nets.
|
|
type Error = (TritonRoutingResult, Vec<LN::NetId>);
|
|
|
|
fn name(&self) -> String {
|
|
match self.is_triton_wxl {
|
|
false => "TritonRoute",
|
|
true => "TritonRoute-WXL"
|
|
}.into()
|
|
}
|
|
|
|
fn route<RP>(&self, routing_problem: RP) -> Result<Self::RoutingResult, Self::Error>
|
|
where RP: DetailRoutingProblem<LN> {
|
|
let chip = routing_problem.fused_layout_netlist();
|
|
let top = routing_problem.top_cell();
|
|
|
|
let all_nets = chip.each_internal_net(&top);
|
|
|
|
// Keep track of nets which don't have a routing guide.
|
|
let mut nets_without_guides = vec![];
|
|
|
|
let guides = if self.is_triton_wxl {
|
|
// No need for routing guides.
|
|
vec![]
|
|
} else {
|
|
// Convert the routing guides into sets of rectangles.
|
|
let guides: Vec<(LN::NetId, Vec<(db::Rect<LN::Coord>, LN::LayerId)>)> =
|
|
all_nets.into_iter()
|
|
.filter_map(|net| {
|
|
if let Some(guide) = routing_problem.routing_guide(&net) {
|
|
let guide_rectangles = guide.to_rectangles();
|
|
Some((net, guide_rectangles))
|
|
} else {
|
|
nets_without_guides.push(net);
|
|
None
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
if !nets_without_guides.is_empty() {
|
|
log::warn!("Number of nets without routing guide (net names are listed in debug log): {}", nets_without_guides.len());
|
|
|
|
for net in &nets_without_guides {
|
|
if let Some(net_name) = chip.net_name(net) {
|
|
log::debug!("Net has no routing guide: {}", net_name);
|
|
}
|
|
}
|
|
}
|
|
guides
|
|
};
|
|
|
|
// Call TritonRoute.
|
|
self.route_all_nets(
|
|
chip,
|
|
top.clone(),
|
|
&guides,
|
|
)
|
|
// Convert the error such that it contains information about which nets have failed to route (all nets in this case).
|
|
.map_err(|_err| {
|
|
let all_nets: Vec<_> = chip.each_internal_net(&top).collect();
|
|
(Default::default(), all_nets)
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Write the global routes (routing guides) to a 'guide' file which is understood by `TritonRoute`.
|
|
///
|
|
/// The format is simple: Each net has a set of rectangle shapes associated with a layer.
|
|
/// Here `net1` has a rectangular guide on the layer `metal1`. The axis-aligned rectangle is spanned
|
|
/// from point `(1, 2)` to point `(100, 200)`.
|
|
/// ```guide
|
|
/// net1
|
|
/// (
|
|
/// 1 2 100 200 metal1
|
|
/// )
|
|
/// net2
|
|
/// (
|
|
///
|
|
/// )
|
|
/// ```
|
|
fn write_routing_guides<LN: db::L2NBase, W: Write>(
|
|
writer: &mut W,
|
|
chip: &LN,
|
|
top_cell: &LN::CellId,
|
|
routing_guides: &Vec<(LN::NetId, Vec<(db::Rect<LN::Coord>, LN::LayerId)>)>) -> std::io::Result<()> {
|
|
|
|
// Create lookup-table for layer names.
|
|
let layer_names: HashMap<LN::LayerId, LN::NameType> = chip.each_layer()
|
|
.filter_map(|l| {
|
|
let name = chip.layer_info(&l).name;
|
|
name.map(|n| (l, n))
|
|
})
|
|
.collect();
|
|
|
|
dbg!(&layer_names);
|
|
|
|
// Write all the guides.
|
|
let all_nets: HashSet<_> = chip.each_internal_net(top_cell).collect();
|
|
for (net, guide) in routing_guides {
|
|
if all_nets.contains(net) {
|
|
let net_name = chip.net_name(net)
|
|
.expect("Net has no name.");
|
|
writeln!(writer, "{}", net_name)?;
|
|
writeln!(writer, "(")?;
|
|
for (rectangle, layer) in guide {
|
|
let (p1, p2) = (rectangle.lower_left(), rectangle.upper_right());
|
|
let layer_name = layer_names.get(layer).expect("Layer has no name.");
|
|
writeln!(writer, "{} {} {} {} {}", p1.x, p1.y, p2.x, p2.y, layer_name)?;
|
|
}
|
|
writeln!(writer, ")")?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|