bugfixes, working on SOP stuff

This commit is contained in:
saji 2024-05-04 14:35:33 -05:00
parent 5ac484b1a8
commit a550d84750
5 changed files with 303 additions and 39 deletions

30
compiler/Cargo.lock generated
View file

@ -267,6 +267,29 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" 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]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.1" version = "1.0.1"
@ -323,6 +346,12 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.60" version = "0.1.60"
@ -676,6 +705,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap 4.5.4", "clap 4.5.4",
"env_logger",
"galette", "galette",
"log", "log",
"regex", "regex",

View file

@ -8,6 +8,7 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.81" anyhow = "1.0.81"
clap = { version = "4.5.4", features = ["derive"] } clap = { version = "4.5.4", features = ["derive"] }
env_logger = "0.11.3"
galette = "0.3.0" galette = "0.3.0"
log = "0.4.21" log = "0.4.21"
regex = "1.10.4" regex = "1.10.4"

View file

@ -1,10 +1,14 @@
use std::str::from_utf8;
use crate::pcf::PcfFile; 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::blueprint::Blueprint;
use galette::chips::Chip; use galette::chips::Chip;
use log::info; use log::{debug, info, warn};
use thiserror::Error; use thiserror::Error;
use galette::gal::{Pin, Term};
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum MappingError { pub enum MappingError {
#[error("OLMC missing output: {0}")] #[error("OLMC missing output: {0}")]
@ -26,18 +30,33 @@ pub enum MappingError {
// attempt to map graph into blueprint // attempt to map graph into blueprint
/// Acquire the SOP associated with the OLMC. If it's /// Acquire the SOP associated with the OLMC. If it's
fn get_sop_for_olmc(graph: &Graph, olmc_idx: NodeIdx) -> Result<GalSop, MappingError> { fn get_sop_for_olmc(graph: &Graph, olmc_idx: &NodeIdx) -> Result<GalSop, MappingError> {
let input = graph.get_node_port_conns(olmc_idx, "A"); let input = graph.get_node_port_conns(olmc_idx, "A");
assert_eq!(input.len(), 1, "OLMC input should have one netadjpair"); let sops_on_net: Vec<_> = input
let other_node = input[0].get_other(olmc_idx).ok_or(MappingError::Unknown)?; .iter()
let sop = graph .filter_map(|i| {
.get_node(other_node.0) let sop = i.get_other(olmc_idx)?;
.ok_or(MappingError::MissingSOP)?; if sop.1 != "Y" {
if let Node::Sop(s) = sop { return None;
Ok(s.clone()) };
} else { let node = graph.get_node(&sop.0)?;
Err(MappingError::MissingSOP) 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( fn map_remaining_olmc(
@ -48,7 +67,7 @@ fn map_remaining_olmc(
// (index, size) // (index, size)
let mut chosen_row: Option<(usize, usize)> = None; let mut chosen_row: Option<(usize, usize)> = None;
// FIXME: implement. // 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; let sopsize: usize = sop.parameters.depth as usize;
for (olmc_idx, size) in unused { 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<u32, MappingError> {
// 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<Vec<Pin>> = 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<Pin> = 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<u32> { fn valid_inputs(chip: Chip) -> Vec<u32> {
match chip { match chip {
Chip::GAL16V8 => vec![ Chip::GAL16V8 => vec![
@ -94,9 +195,6 @@ fn valid_inputs(chip: Chip) -> Vec<u32> {
pub fn graph_convert(graph: &Graph, pcf: PcfFile, chip: Chip) -> anyhow::Result<Blueprint> { pub fn graph_convert(graph: &Graph, pcf: PcfFile, chip: Chip) -> anyhow::Result<Blueprint> {
let mut bp = Blueprint::new(chip); let mut bp = Blueprint::new(chip);
// phase zero: input mapping.
let mut pinmap: Vec<Option<String>> = vec![None; chip.num_pins()];
let valid_inp = valid_inputs(chip); let valid_inp = valid_inputs(chip);
let mut olmcmap: Vec<Option<NodeIdx>> = vec![None; chip.num_olmcs()]; let mut olmcmap: Vec<Option<NodeIdx>> = 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 let Some(olmcrow) = chip.pin_to_olmc(pin as usize) {
if port.direction == PortDirection::Input { if port.direction == PortDirection::Input {
olmcmap[olmcrow] = Some(NodeIdx(usize::MAX)); olmcmap[olmcrow] = Some(NodeIdx(usize::MAX));
} // otherwise we do not care! } // otherwise we do not care at this point!
} }
} else { } 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 // phase one: OLMC mapping
// start by finding the constraints. // 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, // 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. // or we defer it to later.
for o in graph.get_olmc_idx() { for o in graph.get_olmc_idx() {
debug!("Processing OLMC {o}");
debug!("Value = {:?}", graph.get_node(&o));
// find all the // find all the
let others: Vec<Net> = graph
.get_node_port_conns(o, "Y") let n: &Net;
.iter() if let Some(Node::Olmc(olmc)) = graph.get_node(&o) {
.map(|adj| adj.net.clone()) n = &olmc.connections.get("Y").ok_or(MappingError::Unknown)?[0];
.collect(); } else {
warn!("Could not find output net! Silently skipping");
continue;
}
// if it's got a port we map it now else we defer it. // 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 { match port {
Some(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(); let num_mapped = olmcmap.iter().filter(|x| x.is_some()).count();
info!("Mapped {num_mapped} OLMCS, {} deferred", deferrals.len()); 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)> // Vec<(olmc_index,size)>
let mut unused_rows = olmcmap let mut unused_rows = olmcmap
.iter() .iter()
@ -175,8 +281,10 @@ pub fn graph_convert(graph: &Graph, pcf: PcfFile, chip: Chip) -> anyhow::Result<
.collect(); .collect();
// find the smallest row that fits. // find the smallest row that fits.
info!("Starting deferred mapping process");
for olmc in deferrals { for olmc in deferrals {
let row = map_remaining_olmc(graph, olmc, &unused_rows)?; 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 // insert into the mapping
olmcmap[row.0] = Some(olmc); olmcmap[row.0] = Some(olmc);
// remove this row from the available rows // 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. // 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) 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(())
}
}

View file

@ -1,12 +1,17 @@
pub mod pcf; pub mod pcf;
pub mod yosys_parser; pub mod yosys_parser;
mod fitter;
use clap::{Parser, Subcommand, Args}; use clap::{Parser, Subcommand, Args};
use crate::pcf::parse_pcf;
use crate::yosys_parser::{YosysDoc, Graph}; use crate::yosys_parser::{YosysDoc, Graph};
use crate::fitter::graph_convert;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use serde_json::from_slice; use serde_json::from_slice;
use std::path::PathBuf; use std::path::PathBuf;
use galette::chips::Chip;
use std::fs; use std::fs;
use env_logger;
#[derive(Parser)] #[derive(Parser)]
struct Cli { struct Cli {
@ -18,6 +23,7 @@ struct Cli {
enum Commands { enum Commands {
/// Validate a yosys netlist JSON file. /// Validate a yosys netlist JSON file.
Validate(ValidateArgs), Validate(ValidateArgs),
Synth(SynthArgs),
} }
#[derive(Args)] #[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<()>{ fn validate(v: ValidateArgs) -> Result<()>{
let f = fs::read(v.file)?; let f = fs::read(v.file)?;
@ -44,9 +59,38 @@ fn validate(v: ValidateArgs) -> Result<()>{
Ok(()) 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<()>{ fn main() -> Result<()>{
let args = Cli::parse(); let args = Cli::parse();
env_logger::init();
match args.command { match args.command {
Commands::Validate(v) => validate(v), Commands::Validate(v) => validate(v),
Commands::Synth(s) => synth(s),
} }
} }

View file

@ -4,6 +4,7 @@ use log::info;
use serde::{de::Error, Deserialize, Deserializer, Serialize}; use serde::{de::Error, Deserialize, Deserializer, Serialize};
use serde_with::{serde_as, BoolFromInt}; use serde_with::{serde_as, BoolFromInt};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt;
use std::str; use std::str;
#[derive(Debug, Serialize, Clone, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Serialize, Clone, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
@ -230,6 +231,12 @@ impl NamedPort {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct NodeIdx(pub usize); 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)] #[derive(Debug, Clone)]
pub struct NetAdjPair { pub struct NetAdjPair {
@ -273,13 +280,13 @@ impl NetAdjPair {
pub fn uses_net(&self, net: &Net) -> bool { pub fn uses_net(&self, net: &Net) -> bool {
net == &self.net net == &self.net
} }
pub fn uses_nodeport(&self, idx: NodeIdx, port: &str) -> bool { pub fn uses_nodeport(&self, idx: &NodeIdx, port: &str) -> bool {
(self.idx1 == idx && self.port1 == port) || (self.idx2 == idx && self.port2 == port) (&self.idx1 == idx && self.port1 == port) || (&self.idx2 == idx && self.port2 == port)
} }
pub fn get_other(&self, my_idx: NodeIdx) -> Option<(NodeIdx, &str)> { pub fn get_other(&self, my_idx: &NodeIdx) -> Option<(NodeIdx, &str)> {
if my_idx == self.idx1 { if my_idx == &self.idx1 {
Some((self.idx2, &self.port2)) Some((self.idx2, &self.port2))
} else if my_idx == self.idx2 { } else if my_idx == &self.idx2 {
Some((self.idx1, &self.port1)) Some((self.idx1, &self.port1))
} else { } else {
None None
@ -396,12 +403,12 @@ impl Graph {
/// Retrieve a node from the node index /// Retrieve a node from the node index
/// TODO: make a newtype for the 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) self.nodelist.get(idx.0)
} }
// find the connections from the given node/port // 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> { pub fn get_node_port_conns(&self, nodeidx: &NodeIdx, port: &str) -> Vec<&NetAdjPair> {
self.adjlist self.adjlist
.iter() .iter()
.filter(|adj| adj.uses_nodeport(nodeidx, port)) .filter(|adj| adj.uses_nodeport(nodeidx, port))
@ -527,12 +534,61 @@ type Parameters = HashMap<String, String>;
pub trait Cell { pub trait Cell {
fn name(&self) -> &str; fn name(&self) -> &str;
fn connections(&self) -> &Connections;
fn params(&self) -> &Parameters;
fn ctype(&self) -> CellType; fn ctype(&self) -> CellType;
fn get_connection(&self, conn: &str) -> Option<&Vec<Net>>;
fn get_param(&self, param: &str) -> Option<&String>;
fn nets(&self) -> Vec<Net>; fn nets(&self) -> Vec<Net>;
fn uses_net(&self, net: Net) -> bool; fn uses_net(&self, net: Net) -> bool;
fn net_on_port(&self, net: Net) -> Option<String>; }
pub struct GalCell {
name: Option<String>,
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<Net>> {
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
}
} }