diff --git a/compiler/Cargo.lock b/compiler/Cargo.lock index e754530..c697645 100644 --- a/compiler/Cargo.lock +++ b/compiler/Cargo.lock @@ -597,18 +597,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", @@ -682,6 +682,7 @@ dependencies = [ "serde", "serde_json", "serde_with", + "thiserror", ] [[package]] diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml index 494af97..3771915 100644 --- a/compiler/Cargo.toml +++ b/compiler/Cargo.toml @@ -14,6 +14,7 @@ regex = "1.10.4" serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.115" serde_with = { version = "3.7.0", features = ["json"] } +thiserror = "1.0.59" [lib] diff --git a/compiler/src/fitter.rs b/compiler/src/fitter.rs new file mode 100644 index 0000000..9c041b6 --- /dev/null +++ b/compiler/src/fitter.rs @@ -0,0 +1,108 @@ +use crate::pcf::PcfFile; +use crate::yosys_parser::{GalOLMC, Graph, NamedPort, Net}; +use galette::blueprint::Blueprint; +use galette::chips::Chip; +use log::info; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum MappingError { + #[error("OLMC missing output: {0}")] + OLMCMissingOutput(String), + + #[error("Could not find constraint for port {}", .0.name)] + MissingConstraint(NamedPort), + + #[error("Could not find the SOP input")] + MissingSOP, + + #[error("Unknown error")] + Unknown, +} + +// attempt to map graph into blueprint + +#[derive(Debug, Clone)] +struct GALMapEntry(GalOLMC, usize); // data and the entry in the graph + +pub fn graph_convert(graph: &Graph, pcf: PcfFile, chip: Chip) -> anyhow::Result { + let mut bp = Blueprint::new(chip); + + // phase one: OLMC mapping + // start by finding the constraints. + // + let mut olmcmap: Vec> = vec![None; chip.num_olmcs()]; + + let mut deferrals: Vec = Vec::new(); + + for o in graph.get_olmc_idx() { + // find all the nodes named + let others: Vec = graph + .get_node_port_conns(o, "Y") + .iter() + .map(|adj| adj.net.clone()) + .collect(); + + // if it's got a port we map it now else we defer it. + + let port = graph.find_port(&others[0]); + + match port { + Some(port) => { + info!("Found a port, performing port lookup"); + let pin = port + .lookup(&pcf) + .ok_or(MappingError::MissingConstraint(port.clone()))?; + let olmc_row = chip + .pin_to_olmc(pin.try_into()?) + .ok_or(MappingError::Unknown)?; + info!("Found a real pin to map: Mapping node {o} onto row {olmc_row}"); + olmcmap[olmc_row] = Some(o); + } + None => { + info!("No port found, deferring placement for {o}"); + deferrals.push(o) + } + } + } + // at this point, we should have mapped + let num_mapped = olmcmap.iter().filter(|x| x.is_some()).count(); + info!("Mapped {num_mapped} OLMCS, {} deferred", deferrals.len()); + + // to map remainders, we need to find the smallest SOP. + // + // Vec<(olmc_index,size)> + let unused_rows = + olmcmap + .iter() + .enumerate() + .filter_map(|(i, x)| if !x.is_some() { Some(i) } else { None }) + .map(|i| (i, chip.num_rows_for_olmc(i))); + // find the smallest row that fits. + + let olmc = deferrals[0]; + let mut chosen_row: Option<(usize, usize)> = None; + // FIXME: implement. + let sopsize = 0; + + for (olmc_idx, size) in unused_rows { + + match chosen_row { + None => { + if size > sopsize { + chosen_row = Some((olmc_idx, size)); + } + }, + Some(r) => { + // we do the comparison (size > SOP Size) + if size < r.1 && size > sopsize { + chosen_row = Some((olmc_idx, size)); + } + } + } + } + + + + Ok(bp) +} diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs index de7eb9a..b9fc494 100644 --- a/compiler/src/lib.rs +++ b/compiler/src/lib.rs @@ -1,2 +1,3 @@ pub mod yosys_parser; pub mod pcf; +pub mod fitter; diff --git a/compiler/src/yosys_parser.rs b/compiler/src/yosys_parser.rs index 78c7f26..08cf415 100644 --- a/compiler/src/yosys_parser.rs +++ b/compiler/src/yosys_parser.rs @@ -188,7 +188,7 @@ pub enum PortDirection { /// NamedPort is our internal representation of a port. #[derive(Debug, Clone, PartialEq, PartialOrd)] pub struct NamedPort { - name: String, + pub name: String, net: Net, direction: PortDirection, } @@ -221,16 +221,16 @@ impl NamedPort { } } /// Retrieves the port mapping for this port, given a PCF file. - pub fn lookup(&self, pcf: &PcfFile) -> u32 { + pub fn lookup(&self, pcf: &PcfFile) -> Option { //NOTE: since NamedPort is exactly (1) pin, we always use the pin case. // When constructing, if we have a port with multiple bits, we split it (see `new_split`) - pcf.pin(&self.name).expect("missing constraint") + pcf.pin(&self.name) } } #[derive(Debug, Clone)] pub struct NetAdjPair { - net: Net, + pub net: Net, idx1: usize, port1: String, port2: String, @@ -267,11 +267,20 @@ impl PartialEq for NetAdjPair { impl Eq for NetAdjPair {} impl NetAdjPair { - fn uses_net(&self, net: &Net) -> bool { + pub fn uses_net(&self, net: &Net) -> bool { net == &self.net } - fn uses_nodeport(&self, idx: usize, port: &str) -> bool { - self.idx1 == idx && self.port1 == port || self.idx2 == idx && self.port2 == port + pub fn uses_nodeport(&self, idx: usize, port: &str) -> bool { + (self.idx1 == idx && self.port1 == port) || (self.idx2 == idx && self.port2 == port) + } + pub fn get_other(&self, my_idx: usize) -> (usize, &str) { + if my_idx == self.idx1 { + (self.idx2, &self.port2) + } else if my_idx == self.idx2 { + (self.idx1, &self.port1) + } else { + (usize::MAX, "") + } } } @@ -303,14 +312,14 @@ impl Node { // Self::Olmc(go) => go.connections.input.to_vec(), // } // } - fn get_ports(&self) -> Iter<'_, String, Vec> { + pub fn get_ports(&self) -> HashMap> { match self { - Self::Olmc(ol) => ol.connections.iter(), - Self::Input(i) => i.connections.iter(), - Self::Sop(s) => s.connections.iter(), + Self::Olmc(ol) => ol.connections.clone(), + Self::Input(i) => i.connections.clone(), + Self::Sop(s) => s.connections.clone(), } } - fn port_for_net(&self, net: &Net) -> Option { + pub fn port_for_net(&self, net: &Net) -> Option { for (port, nets) in self.get_ports() { if nets.contains(net) { return Some(port.to_string()); @@ -318,8 +327,9 @@ impl Node { } None } - fn get_nets(&self) -> Vec { + pub fn get_nets(&self) -> Vec { self.get_ports() + .iter() .flat_map(|(_, nets)| nets.clone()) .collect() } @@ -387,9 +397,51 @@ impl Graph { } res } - /// find the port that uses the current net, if any - fn find_port(&self, net: &Net) -> Option<&NamedPort> { - self.ports.iter().find(|p| p.net == *net) + + /// Retrieve a node from the node index + /// TODO: make a newtype for the index. + pub fn get_node(&self, idx: usize) -> Option<&Node> { + self.nodelist.get(idx) + } + + // find the connections from the given node/port + pub fn get_node_port_conns(&self, nodeidx: usize, port: &str) -> Vec<&NetAdjPair> { + self.adjlist + .iter() + .filter(|adj| adj.uses_nodeport(nodeidx, port)) + .collect() + } + + // TODO: get rid of this or refactor somehow????? + // VERY BAD + pub fn get_olmc_idx(&self) -> Vec { + self.nodelist + .iter() + .enumerate() + .filter_map(|(idx, node)| match node { + Node::Olmc(_) => Some(idx), + _ => None, + }) + .collect() + } + + /// find the port that uses the current net, if any. + /// Ports are the input/output of a module. They are handled separately. + pub fn find_port(&self, net: &Net) -> Option<&NamedPort> { + match net { + Net::N(_) => self.ports.iter().find(|p| p.net == *net), + _ => None, + } + } + + pub fn get_olmc(&self) -> Vec<&Node> { + self.nodelist + .iter() + .filter_map(|node| match node { + Node::Olmc(_) => Some(node), + _ => None, + }) + .collect() } /// Validate that the graph has valid invariants. @@ -397,12 +449,11 @@ impl Graph { /// by the yosys script is what we expected. Mainly a tool for debugging the Yosys outputs. pub fn validate(&self) -> Result<(), &str> { info!("Checking OLMC blocks"); - let all_olmc = self.nodelist.iter().filter_map(|node| match node { + let olmc = self.nodelist.iter().filter_map(|node| match node { Node::Olmc(o) => Some(o), _ => None, }); - - let olmc_clock = all_olmc.filter_map(|o| o.connections.get("C")); + let olmc_clock = olmc.filter_map(|o| o.connections.get("C")); let test = olmc_clock.clone().all(|v| v.len() == 1); if !test { return Err("OLMC has more than one clock input!"); @@ -415,7 +466,8 @@ impl Graph { if !test { return Err("invalid clock pin"); } - // for the ones connected to a net, extract the net nubmer and Vec it. + // for the ones connected to a net, extract the net number so we can make sure they're all + // the same clock. let olmc_clocked: Vec = olmc_clock .clone() .flatten()