diff --git a/compiler/Cargo.lock b/compiler/Cargo.lock index c697645..532439b 100644 --- a/compiler/Cargo.lock +++ b/compiler/Cargo.lock @@ -267,6 +267,29 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -323,6 +346,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -676,6 +705,7 @@ version = "0.1.0" dependencies = [ "anyhow", "clap 4.5.4", + "env_logger", "galette", "log", "regex", diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml index 3771915..eac2512 100644 --- a/compiler/Cargo.toml +++ b/compiler/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] anyhow = "1.0.81" clap = { version = "4.5.4", features = ["derive"] } +env_logger = "0.11.3" galette = "0.3.0" log = "0.4.21" regex = "1.10.4" diff --git a/compiler/src/fitter.rs b/compiler/src/fitter.rs index 7ddf0f6..8617346 100644 --- a/compiler/src/fitter.rs +++ b/compiler/src/fitter.rs @@ -1,10 +1,14 @@ +use std::str::from_utf8; + use crate::pcf::PcfFile; -use crate::yosys_parser::{GalOLMC, GalSop, Graph, NamedPort, Net, Node, NodeIdx, PortDirection}; +use crate::yosys_parser::{GalInput, GalSop, Graph, NamedPort, Net, Node, NodeIdx, PortDirection}; use galette::blueprint::Blueprint; use galette::chips::Chip; -use log::info; +use log::{debug, info, warn}; use thiserror::Error; +use galette::gal::{Pin, Term}; + #[derive(Debug, Error)] pub enum MappingError { #[error("OLMC missing output: {0}")] @@ -26,18 +30,33 @@ pub enum MappingError { // attempt to map graph into blueprint /// Acquire the SOP associated with the OLMC. If it's -fn get_sop_for_olmc(graph: &Graph, olmc_idx: NodeIdx) -> Result { +fn get_sop_for_olmc(graph: &Graph, olmc_idx: &NodeIdx) -> Result { let input = graph.get_node_port_conns(olmc_idx, "A"); - assert_eq!(input.len(), 1, "OLMC input should have one netadjpair"); - let other_node = input[0].get_other(olmc_idx).ok_or(MappingError::Unknown)?; - let sop = graph - .get_node(other_node.0) - .ok_or(MappingError::MissingSOP)?; - if let Node::Sop(s) = sop { - Ok(s.clone()) - } else { - Err(MappingError::MissingSOP) - } + let sops_on_net: Vec<_> = input + .iter() + .filter_map(|i| { + let sop = i.get_other(olmc_idx)?; + if sop.1 != "Y" { + return None; + }; + let node = graph.get_node(&sop.0)?; + match node { + Node::Sop(s) => Some(s), + _ => None, + } + }) + .collect(); + assert_eq!(sops_on_net.len(), 1, "Should only be one sop driving a net"); + Ok(sops_on_net[0].clone()) + // let other_node = input[0].get_other(olmc_idx).ok_or(MappingError::Unknown)?; + // let sop = graph + // .get_node(&other_node.0) + // .ok_or(MappingError::MissingSOP)?; + // if let Node::Sop(s) = sop { + // Ok(s.clone()) + // } else { + // Err(MappingError::MissingSOP) + // } } fn map_remaining_olmc( @@ -48,7 +67,7 @@ fn map_remaining_olmc( // (index, size) let mut chosen_row: Option<(usize, usize)> = None; // FIXME: implement. - let sop = get_sop_for_olmc(graph, olmc)?; + let sop = get_sop_for_olmc(graph, &olmc)?; let sopsize: usize = sop.parameters.depth as usize; for (olmc_idx, size) in unused { @@ -79,6 +98,88 @@ fn map_remaining_olmc( } } +fn find_hwpin_for_net(graph: &Graph, pcf: &PcfFile, net: &Net) -> Result { + // this does a double lookup. first it finds the Input on the net, + // then it finds the port on the input of the GAL_INPUT. + // find the input on the net. + let inputs: Vec<&GalInput> = graph + .find_nodes_on_net(net) + .iter() + .filter_map(|n| match graph.get_node(n) { + Some(Node::Input(i)) => Some(i), + _ => None, + }) + .collect(); + + // now we have an array of inputs, this should be one elemnt. + if inputs.len() != 1 { + return Err(MappingError::Unknown); + } + + let port_nets = inputs[0] + .connections + .get("A") + .ok_or(MappingError::Unknown)?; + assert_eq!(port_nets.len(), 1, "should only be one input to GAL_INPUT"); + let pnet = &port_nets[0]; + + if let Some(p) = graph.find_port(&pnet) { + info!("Found a port after traversing inputs"); + // look up the pin. + p.lookup(pcf) + .ok_or(MappingError::MissingConstraint(p.clone())) + } else { + Err(MappingError::Unknown) + } +} +/// Takes a gal sop, and turns it into a vec of mapped pins. +fn make_term_from_sop(graph: &Graph, sop: GalSop, pcf: &PcfFile) -> Term { + let table = sop.parameters.table.as_bytes(); + + let n_products = sop.parameters.depth; + let product_size = sop.parameters.width; + let chunksize = product_size * 2; // 00 for dontcare, 01 for negation, 10 for positive i think + + let input_nets = sop.connections.get("A").unwrap(); + + let terms: Vec> = table + .chunks(chunksize as usize) + .map(|chunk| { + // chunk is now a block of terms. + let terms: Vec<&str> = chunk.chunks(2).map(|c| from_utf8(c).unwrap()).collect(); + // create our term + let pins: Vec = terms + .iter() + .enumerate() + .filter_map(|(idx, p)| { + let net_for_pin = input_nets.get(idx).unwrap(); + // now use the helper to find the true hardware pin + let hwpin: usize = + find_hwpin_for_net(graph, pcf, net_for_pin).unwrap() as usize; + // we now have our hardware pin number! + match *p { + "01" => Some(Pin { + pin: hwpin, + neg: true, + }), + "10" => Some(Pin { + pin: hwpin, + neg: false, + }), + _ => None, + } + }) + .collect(); + pins + }) + .collect(); + assert_eq!(n_products as usize, terms.len()); + Term { + line_num: 0, + pins: terms, + } +} + fn valid_inputs(chip: Chip) -> Vec { match chip { Chip::GAL16V8 => vec![ @@ -94,9 +195,6 @@ fn valid_inputs(chip: Chip) -> Vec { pub fn graph_convert(graph: &Graph, pcf: PcfFile, chip: Chip) -> anyhow::Result { let mut bp = Blueprint::new(chip); - // phase zero: input mapping. - let mut pinmap: Vec> = vec![None; chip.num_pins()]; - let valid_inp = valid_inputs(chip); let mut olmcmap: Vec> = vec![None; chip.num_olmcs()]; @@ -108,13 +206,16 @@ pub fn graph_convert(graph: &Graph, pcf: PcfFile, chip: Chip) -> anyhow::Result< if let Some(olmcrow) = chip.pin_to_olmc(pin as usize) { if port.direction == PortDirection::Input { olmcmap[olmcrow] = Some(NodeIdx(usize::MAX)); - } // otherwise we do not care! + } // otherwise we do not care at this point! } } else { - return Err(MappingError::Unknown.into()); + // we don't have a constraint for this port + return Err(MappingError::MissingConstraint(port.clone()).into()); } } + debug!("Graph adj list is {:?}", graph.adjlist); + // phase one: OLMC mapping // start by finding the constraints. @@ -123,16 +224,21 @@ pub fn graph_convert(graph: &Graph, pcf: PcfFile, chip: Chip) -> anyhow::Result< // For all the OLMCs in the graph, we either map it directly since it's constrained to a pin, // or we defer it to later. for o in graph.get_olmc_idx() { + debug!("Processing OLMC {o}"); + debug!("Value = {:?}", graph.get_node(&o)); // find all the - let others: Vec = graph - .get_node_port_conns(o, "Y") - .iter() - .map(|adj| adj.net.clone()) - .collect(); + + let n: &Net; + if let Some(Node::Olmc(olmc)) = graph.get_node(&o) { + n = &olmc.connections.get("Y").ok_or(MappingError::Unknown)?[0]; + } else { + warn!("Could not find output net! Silently skipping"); + continue; + } // if it's got a port we map it now else we defer it. - let port = graph.find_port(&others[0]); + let port = graph.find_port(n); match port { Some(port) => { @@ -164,8 +270,8 @@ pub fn graph_convert(graph: &Graph, pcf: PcfFile, chip: Chip) -> anyhow::Result< 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. - // + // to map the deferred ones, we need to find the smallest SOP that is still large enough for + // it. // Vec<(olmc_index,size)> let mut unused_rows = olmcmap .iter() @@ -175,8 +281,10 @@ pub fn graph_convert(graph: &Graph, pcf: PcfFile, chip: Chip) -> anyhow::Result< .collect(); // find the smallest row that fits. + info!("Starting deferred mapping process"); for olmc in deferrals { let row = map_remaining_olmc(graph, olmc, &unused_rows)?; + debug!("Found a mapping for {olmc} in row {} size {}", row.0, row.1); // insert into the mapping olmcmap[row.0] = Some(olmc); // remove this row from the available rows @@ -185,7 +293,32 @@ pub fn graph_convert(graph: &Graph, pcf: PcfFile, chip: Chip) -> anyhow::Result< } // at this point, we have mapped every OLMC. - // now use the blueprint to set the settings. + // find the SOPs and for each sop, find + info!("Deferred mapping complete, starting SOP mapping"); + for (idx, olmc) in olmcmap.iter().enumerate() { + match olmc { + Some(node) => { + debug!("Mapping node {node} at row {idx}"); + let sop = get_sop_for_olmc(graph, node)?; + debug!("Got SOP {:?} attached to node", sop); + let term = make_term_from_sop(graph, sop, &pcf); + debug!("Got term {:?}", term); + } + None => {} + } + } Ok(bp) } + +#[cfg(test)] +mod tests { + use super::*; + use anyhow::Result; + + #[test] + fn test_sop_to_term() -> Result<()> { + let pct = "set_io pinName 1"; + Ok(()) + } +} diff --git a/compiler/src/main.rs b/compiler/src/main.rs index d75f627..262975c 100644 --- a/compiler/src/main.rs +++ b/compiler/src/main.rs @@ -1,12 +1,17 @@ pub mod pcf; pub mod yosys_parser; +mod fitter; use clap::{Parser, Subcommand, Args}; +use crate::pcf::parse_pcf; use crate::yosys_parser::{YosysDoc, Graph}; +use crate::fitter::graph_convert; use anyhow::{bail, Result}; use serde_json::from_slice; use std::path::PathBuf; +use galette::chips::Chip; use std::fs; +use env_logger; #[derive(Parser)] struct Cli { @@ -18,6 +23,7 @@ struct Cli { enum Commands { /// Validate a yosys netlist JSON file. Validate(ValidateArgs), + Synth(SynthArgs), } #[derive(Args)] @@ -27,6 +33,15 @@ struct ValidateArgs { } +#[derive(Args)] +struct SynthArgs { + #[arg(required = true, value_hint = clap::ValueHint::DirPath)] + netlist: PathBuf, + #[arg(required = true, value_hint = clap::ValueHint::DirPath)] + constraints: PathBuf, +} + + fn validate(v: ValidateArgs) -> Result<()>{ let f = fs::read(v.file)?; @@ -44,9 +59,38 @@ fn validate(v: ValidateArgs) -> Result<()>{ Ok(()) } + +fn synth(s: SynthArgs) -> Result<()> { + let f = fs::read(s.netlist)?; + + let data: YosysDoc = from_slice(f.as_slice())?; + + let g = Graph::from(data); + let res = g.validate().map_err(|x| x.to_string()); + if let Err(e) = res { + bail!(e); + } + println!("Validation Complete!"); + println!("Stats:"); + println!("Nodes: {}", g.nodelist.len()); + println!("Edges: {}", g.adjlist.len()); + + // load the pcf + let pcf_file = &fs::read(s.constraints)?; + let pcf_string = std::str::from_utf8(pcf_file)?; + let pcf = parse_pcf(pcf_string); + + let res = graph_convert(&g, pcf, Chip::GAL16V8); + + + Ok(()) +} + fn main() -> Result<()>{ let args = Cli::parse(); + env_logger::init(); match args.command { Commands::Validate(v) => validate(v), + Commands::Synth(s) => synth(s), } } diff --git a/compiler/src/yosys_parser.rs b/compiler/src/yosys_parser.rs index e8ee2c5..723df2d 100644 --- a/compiler/src/yosys_parser.rs +++ b/compiler/src/yosys_parser.rs @@ -4,6 +4,7 @@ use log::info; use serde::{de::Error, Deserialize, Deserializer, Serialize}; use serde_with::{serde_as, BoolFromInt}; use std::collections::HashMap; +use std::fmt; use std::str; #[derive(Debug, Serialize, Clone, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)] @@ -230,6 +231,12 @@ impl NamedPort { #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct NodeIdx(pub usize); +impl fmt::Display for NodeIdx { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "(Nodeindex: {})", self.0) + } +} + #[derive(Debug, Clone)] pub struct NetAdjPair { @@ -273,13 +280,13 @@ impl NetAdjPair { pub fn uses_net(&self, net: &Net) -> bool { net == &self.net } - pub fn uses_nodeport(&self, idx: NodeIdx, port: &str) -> bool { - (self.idx1 == idx && self.port1 == port) || (self.idx2 == idx && self.port2 == port) + pub fn uses_nodeport(&self, idx: &NodeIdx, port: &str) -> bool { + (&self.idx1 == idx && self.port1 == port) || (&self.idx2 == idx && self.port2 == port) } - pub fn get_other(&self, my_idx: NodeIdx) -> Option<(NodeIdx, &str)> { - if my_idx == self.idx1 { + pub fn get_other(&self, my_idx: &NodeIdx) -> Option<(NodeIdx, &str)> { + if my_idx == &self.idx1 { Some((self.idx2, &self.port2)) - } else if my_idx == self.idx2 { + } else if my_idx == &self.idx2 { Some((self.idx1, &self.port1)) } else { None @@ -396,12 +403,12 @@ impl Graph { /// Retrieve a node from the node index /// TODO: make a newtype for the index. - pub fn get_node(&self, idx: NodeIdx) -> Option<&Node> { + pub fn get_node(&self, idx: &NodeIdx) -> Option<&Node> { self.nodelist.get(idx.0) } - // find the connections from the given node/port - pub fn get_node_port_conns(&self, nodeidx: NodeIdx, port: &str) -> Vec<&NetAdjPair> { + // find the connections from the given node/port ONLY WORKS FOR NON_PORT DEVICES. + pub fn get_node_port_conns(&self, nodeidx: &NodeIdx, port: &str) -> Vec<&NetAdjPair> { self.adjlist .iter() .filter(|adj| adj.uses_nodeport(nodeidx, port)) @@ -527,12 +534,61 @@ type Parameters = HashMap; pub trait Cell { fn name(&self) -> &str; - fn connections(&self) -> &Connections; - fn params(&self) -> &Parameters; fn ctype(&self) -> CellType; + + fn get_connection(&self, conn: &str) -> Option<&Vec>; + fn get_param(&self, param: &str) -> Option<&String>; fn nets(&self) -> Vec; fn uses_net(&self, net: Net) -> bool; - fn net_on_port(&self, net: Net) -> Option; +} + +pub struct GalCell { + name: Option, + ctype: CellType, + connections: Connections, + params: Parameters, +} + +impl GalCell { + pub fn name(&self) -> String { + self.name.clone().unwrap_or("unnamed".to_string()) + } + + pub fn get_connection(&self, conn: &str) -> Option<&Vec> { + self.connections.get(conn) + } + + /// Access the parameter from the cell. Note that this does not + /// have. + pub fn get_param(&self, param: &str) -> Option<&String> { + self.params.get(param) + } + + /// Get all of the nets that this cell uses + pub fn nets(&self) -> Vec<&Net> { + self.connections.iter().flat_map(|x| x.1).collect() + } + + /// Returns true if the net is used at all by the cell + pub fn uses_net(&self, net: &Net) -> bool { + self.nets().contains(&net) + } + + /// Returns the port that uses the given net, if any. + pub fn get_port_for_net(&self, net: &Net) -> Option<&str> { + for (port, nets) in self.connections.iter() { + if nets.contains(net) { + return Some(port) + } + } + None + } + + /// Returns the underlying Cell type, used to differentiate available + /// connections + pub fn ctype(&self) -> &CellType { + &self.ctype + } }