libreda-pnr 0.0.4

Algorithm interface definitions of the LibrEDA place-and-route framework.
Documentation
// Copyright (c) 2020-2021 Thomas Kramer.
// SPDX-FileCopyrightText: 2022 Thomas Kramer <code@tkramer.ch>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

//! Traits for simple placement engines that care about standard-cells only and cannot handle obstructions.

pub use libreda_db::prelude::{SInt, UInt, WindingNumber, TryCastCoord};
pub use libreda_db::prelude::traits::*;
pub use libreda_db::prelude as db;
pub use libreda_db::iron_shapes::prelude::{Point, SimplePolygon};
use std::collections::{HashMap, HashSet};

use log;
use crate::db::{L2NBase, SimpleTransform};
use crate::place::mixed_size_placer::{MixedSizePlacer, PlacementError};
use crate::place::placement_problem::PlacementProblem;

/// Traits for simple placement engines that care about standard-cells only and  cannot handle obstructions.
pub trait SimpleStdCellPlacer<N: NetlistBase> {
    /// Get the name of the placement engine.
    fn name(&self) -> &str;

    /// Find the rough positions of all cell instances inside `cell`.
    ///
    /// # Parameters
    ///
    /// * `netlist`: The netlist holding the connectivity information.
    /// * `cell_id`: The ID of the cell containing all the standard-cell instances to be placed.
    /// * `core_area`: The region where the cells are allowed to be placed.
    /// * `initial_positions`: Initial positions of the cells. For movable instances this can serve as a hint for the optimization, fixed instances must have a position defined.
    /// * `fixed_instances`: A set of instances that have fixed positions and cannot be moved.
    /// * `cell_outlines`: Rectangular outline shapes of the cells.
    ///
    /// # Returns
    ///
    /// Returns a `HashMap` which maps cell instances to positions.
    fn find_cell_positions_impl(
        &self,
        netlist: &N,
        top_cell: &N::CellId,
        core_area: &SimplePolygon<db::Coord>,
        initial_positions: &HashMap<N::CellInstId, Point<db::Coord>>,
        fixed_instances: &HashSet<N::CellInstId>,
        cell_outlines: &HashMap<N::CellId, db::Rect<db::Coord>>,
        net_weights: &HashMap<N::NetId, f64>,
    ) -> HashMap<N::CellInstId, Point<db::Coord>>;

    /// Calls `find_cell_positions_impl()` before and after doing some sanity checks.
    fn find_cell_positions(&self,
                           netlist: &N,
                           cell_id: &N::CellId,
                           core_area: &SimplePolygon<db::Coord>,
                           initial_positions: &HashMap<N::CellInstId, Point<db::Coord>>,
                           fixed_instances: &HashSet<N::CellInstId>,
                           cell_outlines: &HashMap<N::CellId, db::Rect<db::Coord>>,
                           net_weights: &HashMap<N::NetId, f64>,
    ) -> HashMap<N::CellInstId, Point<SInt>> {

        // Debug output.
        log::debug!("Running placer '{}'.", self.name());
        log::debug!("Number of cells: {}", netlist.num_child_instances(cell_id));
        log::debug!("Number of initial positions: {}", initial_positions.len());
        log::debug!("Number of fixed cells: {}", fixed_instances.len());

        // TODO: Sanity checks.

        // Assert that all fixed instances are given a position.
        let num_without_position = fixed_instances.iter()
            .filter(|i| !initial_positions.contains_key(i))
            .count();
        assert_eq!(num_without_position, 0, "Some fixed instances have no position.");

        log::info!("Run placement engine.");
        let positions = self.find_cell_positions_impl(
            netlist,
            cell_id,
            core_area,
            initial_positions,
            fixed_instances,
            cell_outlines,
            net_weights
        );
        log::debug!("Placement engine done.");

        // Check that the positions actually lie in the core area.
        let core_area: SimplePolygon<i64> = core_area.cast(); // Convert to i64 to avoid overflows.
        let num_outliers = positions.iter()
            .filter(|(idx, p)| !fixed_instances.contains(idx) && !core_area.contains_point(p.cast()))
            .count();
        if num_outliers > 0 {
            log::warn!("{} cells were placed outside of the core area.", num_outliers);
        }

        positions
    }
}

/// Adapt a [`SimpleStdCellPlacer`] to expose the [`MixedSizePlacer`] API.
pub struct PlacerAdapter<P> {
    placer: P
}

impl<P> PlacerAdapter<P> {
    /// Wrap a [`SimpleStdCellPlacer`] to expose the [`MixedSizePlacer`] API.
    pub fn new(simple_stcell_placer: P) -> Self {
        PlacerAdapter {
            placer: simple_stcell_placer
        }
    }

}

impl<P: SimpleStdCellPlacer<C>, C: L2NBase<Coord=db::Coord>> MixedSizePlacer<C> for PlacerAdapter<P> {
    fn name(&self) -> &str {
        self.placer.name()
    }

    fn find_cell_positions_impl(&self, placement_problem: &dyn PlacementProblem<C>)
                                -> Result<HashMap<C::CellInstId, SimpleTransform<C::Coord>>, PlacementError> {

        // Extract arguments from `placement_problem` such that they can be passed to
        // `SimpleStdCellPlacer`.

        let netlist = placement_problem.fused_layout_netlist();
        let top_cell = placement_problem.top_cell();

        // Extract the core area.
        let core_area = {
            let placement_region = placement_problem.placement_region();
            if placement_region.len() != 1 {
                return Err(PlacementError::Other(
                    format!("Expect exactly one polygon marking the placement region. Found {}.", placement_region.len())
                ));
            }
            let placement_region = &placement_region[0];
            placement_region.to_simple_polygon()
        };

        let all_instances = netlist.each_cell_instance_vec(&top_cell);

        // Extract initial positions.
        let initial_positions = all_instances.iter()
            .map(|inst| {
                let pos = placement_problem.initial_position(inst);
                (inst.clone(), pos.transform_point(Point::zero()))
            })
            .collect();
        // Get set of fixed instances.
        let fixed_instances = placement_problem.get_fixed_instances();

        // Extract cell outlines.
        let cell_outlines = netlist.each_cell_dependency(&top_cell)
            .filter_map(|cell| {
                placement_problem.cell_outline(&cell)
                    .map(|outline| (cell, outline))
            })
            .collect();

        // Fetch all net weights.
        let net_weights: HashMap<C::NetId, f64> = netlist.each_internal_net(&top_cell)
            .map(|net| (net.clone(), placement_problem.net_weight(&net)))
            .collect();

        // Call the placer.
        let displacements = self.placer.find_cell_positions(
            netlist,
            &top_cell,
            &core_area,
            &initial_positions,
            &fixed_instances,
            &cell_outlines,
            &net_weights,
        );

        // Convert point locations into transforms (translations).
        let transforms = displacements.into_iter()
            .map(|(inst, displacement)| {
                (inst, SimpleTransform::translate(displacement))
            })
            .collect();

        Ok(transforms)
    }
}