From 0fafa4c95b0f6c8d8f04e84ba547843a93d0673e Mon Sep 17 00:00:00 2001 From: Alexander Nutz Date: Wed, 20 Aug 2025 23:50:14 +0200 Subject: [PATCH] init --- .gitignore | 1 + Cargo.lock | 26 +++ Cargo.toml | 8 + README.md | 13 ++ src/ast.rs | 183 ++++++++++++++++ src/lib.rs | 595 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/tests.rs | 450 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 1276 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/ast.rs create mode 100644 src/lib.rs create mode 100644 src/tests.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..0f2066e --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,26 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "smallstr" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862077b1e764f04c251fe82a2ef562fd78d7cadaeb072ca7c2bcaf7217b1ff3b" +dependencies = [ + "smallvec", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "turbo-blif" +version = "0.1.0" +dependencies = [ + "smallstr", + "smallvec", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..eaf12e4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "turbo-blif" +version = "0.1.0" +edition = "2024" + +[dependencies] +smallstr = "0.3.1" +smallvec = "1.15.1" diff --git a/README.md b/README.md new file mode 100644 index 0000000..ae09ed1 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# turbo-blif + +low-memory-usage BLIF (berkeley logic interchange format) and KISS (finite state-machine description format) parser and writer. + +supports: +- the latest BLIF specification (dated July 28, 1992) +- all yosys BLIF extensions + (supports reading of BLIF files generated with `write_blif -iname -iattr -param -cname -blackbox -attr -conn -icells`) +- KISS state machines (which yosys doesn't even support) +- clock and delay constraints (yosys just ignores those) + +If you found a program that generates non-standard BLIF attributes or keywords, please open a GitHub issue. +We want to support all non-standard extensions. diff --git a/src/ast.rs b/src/ast.rs new file mode 100644 index 0000000..3b7324a --- /dev/null +++ b/src/ast.rs @@ -0,0 +1,183 @@ +use std::path::Path; + +use super::*; + +#[derive(Clone, Hash, PartialEq, PartialOrd)] +pub struct LUT(pub Vec<(SmallVec<[Tristate; 8]>, bool)>); + +impl std::fmt::Display for LUT { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for ent in self.0.iter() { + write!( + f, + "{} {}\n", + ent.0.iter().fold(String::new(), |acc, x| { + let mut acc = acc; + acc.push_str(x.to_string().as_str()); + acc + }), + if ent.1 { "1" } else { "0" } + )?; + } + Ok(()) + } +} + +impl std::fmt::Debug for LUT { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", format!("{}", self)) + } +} + +#[derive(Debug, Clone, Hash, PartialEq, PartialOrd)] +pub struct Gate { + pub meta: GateMeta, + pub lut: LUT, +} + +impl From for Gate { + fn from(value: GateMeta) -> Self { + Self { + meta: value, + lut: LUT(vec![]), + } + } +} + +impl GateLutConsumer for Gate { + fn entry(&mut self, ins: SmallVec<[Tristate; 8]>, out: bool) { + self.lut.0.push((ins, out)); + } +} + +#[derive(Debug, Clone, Hash, PartialEq, PartialOrd)] +pub enum ModelCmd { + Gate(Gate), + FF(FlipFlop), + LibGate(LibGate), + LibFF(LibFlipFlop), + SubModel { + name: Str<32>, + map: Vec<(Str<16>, Str<16>)>, + }, +} + +#[derive(Debug, Clone, Hash, PartialEq, PartialOrd)] +pub struct Model { + pub meta: ModelMeta, + pub commands: Vec, +} + +impl CommandConsumer for Model { + type Gate = Gate; + + fn gate(&self, gate: GateMeta) -> Self::Gate { + gate.into() + } + + fn gate_done(&mut self, gate: Self::Gate) { + self.commands.push(ModelCmd::Gate(gate)); + } + + fn ff(&mut self, ff: FlipFlop) { + self.commands.push(ModelCmd::FF(ff)); + } + + fn lib_gate(&mut self, gate: LibGate) { + self.commands.push(ModelCmd::LibGate(gate)); + } + + fn lib_ff(&mut self, ff: LibFlipFlop) { + self.commands.push(ModelCmd::LibFF(ff)); + } + + fn sub_model(&mut self, model: &str, map: Vec<(Str<16>, Str<16>)>) { + self.commands.push(ModelCmd::SubModel { + name: model.into(), + map, + }); + } +} + +#[derive(Debug)] +pub enum FullBlifErr { + Blif(BlifParserError), + File(E), + FileNoName, + /// only caused when parsing single blif file + SearchPathsNotSupported, +} + +#[derive(Debug, PartialEq, Hash)] +pub enum BlifEntry { + Model(Model), +} + +#[derive(Debug, PartialEq, Hash)] +pub struct Blif { + pub entries: Vec, + to_search: Vec, +} + +impl ModelConsumer for Blif { + type Inner = Model; + + fn model(&self, meta: ModelMeta) -> Self::Inner { + Model { + meta, + commands: vec![], + } + } + + fn model_done(&mut self, model: Self::Inner) { + self.entries.push(BlifEntry::Model(model)); + } + + fn search(&mut self, path: &str) { + self.to_search.push(path.to_string()); + } +} + +pub fn parse_many_blif_to_ast>>( + path: &str, + lut: impl Fn(&str) -> Result, +) -> Result> { + let mut blif = Blif { + entries: vec![], + to_search: vec![path.to_string()], + }; + + while !blif.to_search.is_empty() { + let p = blif.to_search.pop().unwrap(); + let filnam = Path::new(p.as_str()) + .file_name() + .ok_or(FullBlifErr::FileNoName)?; + let filnam = filnam.to_string_lossy(); + let p = lut(p.as_str()).map_err(FullBlifErr::File)?; + parse_blif(filnam.as_ref(), &mut blif, p).map_err(FullBlifErr::Blif)?; + } + + Ok(blif) +} + +pub fn parse_blif_to_ast( + filename: &str, + lines: impl IntoIterator>, +) -> Result> { + let mut blif = Blif { + entries: vec![], + to_search: vec![], + }; + + parse_blif(filename, &mut blif, lines).map_err(FullBlifErr::Blif)?; + + if !blif.to_search.is_empty() { + Err(FullBlifErr::SearchPathsNotSupported)?; + } + + Ok(blif) +} + +pub fn parse_str_blif_to_ast(filename: &str, source: &str) -> Result> { + parse_blif_to_ast(filename, source.split('\n')) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..807a588 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,595 @@ +use smallvec::SmallVec; +use std::{iter::Peekable, str::FromStr}; + +pub mod ast; + +pub type Str = smallstr::SmallString<[u8; N]>; + +#[derive(Debug, Clone, Hash, PartialEq, PartialOrd)] +pub struct GateMeta { + pub inputs: Vec>, + pub output: Str<16>, + /// external don't care + pub external_dc: bool, +} + +/// truth table consumer +pub trait GateLutConsumer { + /// according to the spec, out is only ever binary + fn entry(&mut self, ins: SmallVec<[Tristate; 8]>, out: bool); +} + +#[derive(Debug, Clone, Hash, PartialEq, PartialOrd)] +pub struct FlipFlop { + /// if not set, is a generic DFF + pub ty: Option, + pub input: Str<16>, + pub output: Str<16>, + /// If a latch does not have a controlling clock specified, it is assumed that it is actually controlled by a single + /// global clock. The behavior of this global clock may be interpreted differently by the various algorithms that may + /// manipulate the model after the model has been read in. + pub clock: Option>, + pub init: FlipFlopInit, +} + +#[derive(Debug, Clone, Hash, PartialEq, PartialOrd)] +pub struct LibGate { + /// name of the gate in the technology library + pub name: Str<16>, + /// mappings of wires from technology library gate -> rest of the circuit + /// + /// from the spec: + /// > All of the formal parameters of name must be specified in the formal-actual-list and the single output of [name] must be the last one in the list + pub maps: Vec<(Str<16>, Str<16>)>, +} + +#[derive(Debug, Clone, Hash, PartialEq, PartialOrd)] +pub struct LibFlipFlop { + /// name of the gate in the technology library + pub name: Str<16>, + /// mappings of wires from technology library gate -> rest of the circuit + /// + /// from the spec: + /// > All of the formal parameters of name must be specified in the formal-actual-list and the single output of [name] must be the last one in the list + pub maps: Vec<(Str<16>, Str<16>)>, + /// If a latch does not have a controlling clock specified, it is assumed that it is actually controlled by a single + /// global clock. The behavior of this global clock may be interpreted differently by the various algorithms that may + /// manipulate the model after the model has been read in. + pub clock: Option>, + pub init: FlipFlopInit, +} + +pub trait CommandConsumer { + type Gate: GateLutConsumer; + + fn gate(&self, gate: GateMeta) -> Self::Gate; + fn gate_done(&mut self, gate: Self::Gate); + fn ff(&mut self, ff: FlipFlop); + fn lib_gate(&mut self, gate: LibGate); + fn lib_ff(&mut self, ff: LibFlipFlop); + /// copies the whole circuit of the referenced model, and maps the ins/outs/clocks according to + /// [map] + fn sub_model(&mut self, model: &str, map: Vec<(Str<16>, Str<16>)>); +} + +#[derive(Debug, Clone, Hash, PartialEq, PartialOrd)] +pub struct ModelMeta { + pub name: Str<32>, + /// if [None], HAS TO be inferred from netlist (signals which are not outputs from other blocks) + pub inputs: Option>>, + /// if [None], HAS TO be inferred from netlist (signals which are not inputs to other blocks) + pub outputs: Option>>, + pub clocks: Vec>, +} + +pub trait ModelConsumer { + type Inner: CommandConsumer; + + fn model(&self, meta: ModelMeta) -> Self::Inner; + fn model_done(&mut self, model: Self::Inner); + + // search the given BLIF file for additional model declarations + fn search(&mut self, path: &str); +} + +fn parse_padding(lines: &mut Peekable>>) { + while lines + .peek() + .is_some_and(|x| x.as_ref().trim().is_empty() || x.as_ref().trim().starts_with('#')) + { + let _ = lines.next(); + } +} + +#[derive(Debug)] +pub enum BlifParserError { + UnknownKw(String), + MissingArgs, + TooManyArgs, + Invalid, + UnexpectedEnd, +} + +enum AsRefOrString { + AsRef(T), + String(String), +} + +impl> AsRef for AsRefOrString { + fn as_ref(&self) -> &str { + match self { + AsRefOrString::AsRef(a) => a.as_ref(), + AsRefOrString::String(s) => s.as_ref(), + } + } +} + +fn before_cmt(s: &str) -> &str { + s.split('#').next().unwrap() +} + +#[test] +fn test_before_cmt() { + assert_eq!(before_cmt("aa asa as a # comment"), "aa asa as a "); +} + +fn next_stmt>( + lines: &mut Peekable>, +) -> Result>, BlifParserError> { + let s_orig = match lines.next() { + Some(x) => x, + None => { + return Ok(None); + } + }; + let s = before_cmt(s_orig.as_ref()).trim_end(); + Ok(Some(if s.ends_with('\\') { + let mut s = s_orig; + let mut whole = String::new(); + while before_cmt(s.as_ref()).trim_end().ends_with('\\') { + whole.push_str(before_cmt(s.as_ref()).trim_end().trim_end_matches('\\')); + s = lines.next().ok_or(BlifParserError::UnexpectedEnd)?; + } + whole.push_str(before_cmt(s.as_ref()).trim_end()); + + AsRefOrString::String(whole) + } else { + if s_orig.as_ref().contains('#') { + AsRefOrString::String(s.to_string()) + } else { + AsRefOrString::AsRef(s_orig) + } + })) +} + +#[test] +fn test_nextstmt_simple() { + let mut lines = ["this is line 0"].into_iter().peekable(); + assert_eq!( + next_stmt(&mut lines).unwrap().unwrap().as_ref(), + "this is line 0" + ); + assert!(lines.peek().is_none()); +} + +#[test] +fn test_nextstmt_cmt() { + let mut lines = ["this is line 0 # but this is a comment"] + .into_iter() + .peekable(); + assert_eq!( + next_stmt(&mut lines).unwrap().unwrap().as_ref(), + "this is line 0" + ); + assert!(lines.peek().is_none()); +} + +#[test] +fn test_nextstmt_simple_one_line() { + let mut lines = ["this is line 0", "this is line 1"].into_iter().peekable(); + assert_eq!( + next_stmt(&mut lines).unwrap().unwrap().as_ref(), + "this is line 0" + ); + assert!(lines.next().unwrap() == "this is line 1"); +} + +#[test] +fn test_nextstmt_simple_multiline() { + let mut lines = ["this is line 0 \\", "this is line 1", "this is line 2"] + .into_iter() + .peekable(); + assert_eq!( + next_stmt(&mut lines).unwrap().unwrap().as_ref(), + "this is line 0 this is line 1" + ); + assert!(lines.next().unwrap() == "this is line 2"); +} + +#[test] +fn test_nextstmt_simple_multiline_cmt() { + let mut lines = [ + "this is line 0 \\ # comment", + "this is line 1 # comment", + "this is line 2", + ] + .into_iter() + .peekable(); + assert_eq!( + next_stmt(&mut lines).unwrap().unwrap().as_ref(), + "this is line 0 this is line 1" + ); + assert!(lines.next().unwrap() == "this is line 2"); +} + +fn is_kw(lines: &mut Peekable>>, kw: &str) -> bool { + lines + .peek() + .is_some_and(|x| x.as_ref().split(' ').next().is_some_and(|y| y == kw)) +} + +#[repr(u8)] +#[derive(Clone, Hash, PartialEq, PartialOrd)] +pub enum Tristate { + False = 0, + True = 1, + Ignored, +} + +impl std::fmt::Display for Tristate { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Tristate::False => write!(f, "0"), + Tristate::True => write!(f, "1"), + Tristate::Ignored => write!(f, "-"), + } + } +} + +impl std::fmt::Debug for Tristate { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self) + } +} + +impl FromStr for Tristate { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "0" => Ok(Self::False), + "1" => Ok(Self::True), + "-" => Ok(Self::Ignored), + _ => Err(()), + } + } +} + +#[derive(Debug, Clone, Hash, PartialEq, PartialOrd)] +pub enum FlipFlopType { + FallingEdge, + RisingEdge, + ActiveHigh, + ActiveLow, + Asynchronous, +} + +#[derive(Debug, Clone, Hash, PartialEq, PartialOrd)] +pub enum FlipFlopInit { + Const(bool), + DontCare, + Unknown, +} + +fn parse_mod( + name: &str, + consumer: &mut impl ModelConsumer, + lines: &mut Peekable>>, +) -> Result<(), BlifParserError> { + let inputs = { + parse_padding(lines); + if is_kw(lines, ".inputs") || is_kw(lines, ".input") { + let line = next_stmt(lines)?.unwrap(); + let line = line.as_ref(); + let mut args = line.split(' '); + let cmd = args.next().ok_or(BlifParserError::Invalid)?; + + Some(args.map(|x| x.into()).collect()) + } else { + None + } + }; + + let outputs = { + parse_padding(lines); + if is_kw(lines, ".outputs") || is_kw(lines, ".output") { + let line = next_stmt(lines)?.unwrap(); + let line = line.as_ref(); + let mut args = line.split(' '); + let cmd = args.next().ok_or(BlifParserError::Invalid)?; + + Some(args.map(|x| x.into()).collect()) + } else { + None + } + }; + + let clocks = { + parse_padding(lines); + if is_kw(lines, ".clock") { + let line = next_stmt(lines)?.unwrap(); + let line = line.as_ref(); + let mut args = line.split(' '); + let cmd = args.next().ok_or(BlifParserError::Invalid)?; + + args.map(|x| x.into()).collect() + } else { + vec![] + } + }; + + let main_consumer = consumer; + let mut consumer = main_consumer.model(ModelMeta { + name: name.into(), + inputs, + outputs, + clocks, + }); + + while { + parse_padding(lines); + lines.peek().is_some() + } { + let mut line = next_stmt(lines)?.unwrap(); + let extdc = if line.as_ref() == ".exdc" { + line = next_stmt(lines)?.unwrap(); + true + } else { + false + }; + + let line = line.as_ref().trim(); + if line == ".end" { + break; + } + + let mut args = line.split(' '); + let cmd = args.next().ok_or(BlifParserError::Invalid)?; + + match cmd { + ".names" => { + let mut inputs: Vec<_> = args.map(|x| x.into()).collect(); + let output = inputs.pop().ok_or(BlifParserError::MissingArgs)?; + + let mut gate = consumer.gate(GateMeta { + inputs, + output, + external_dc: extdc, + }); + + while { + parse_padding(lines); + lines.peek().is_some_and(|x| !x.as_ref().starts_with(".")) + } { + let l = next_stmt(lines)?.unwrap(); + let (l, r) = l.as_ref().split_once(' ').ok_or(BlifParserError::Invalid)?; + + let invs = l + .bytes() + .map(|x| { + let x = [x]; + let x = unsafe { str::from_utf8_unchecked(&x) }; + x.parse() + }) + .collect::>() + .map_err(|_| BlifParserError::Invalid)?; + let outvs = match r { + "0" => false, + "1" => true, + _ => Err(BlifParserError::Invalid)?, + }; + + gate.entry(invs, outvs); + } + + consumer.gate_done(gate); + } + + ".latch" => { + let inp = args.next().ok_or(BlifParserError::MissingArgs)?; + let out = args.next().ok_or(BlifParserError::MissingArgs)?; + let mut init_val = FlipFlopInit::Unknown; + let ty = args + .next() + .map(|t| match t { + "fe" => Ok(Some(FlipFlopType::FallingEdge)), + "re" => Ok(Some(FlipFlopType::RisingEdge)), + "ah" => Ok(Some(FlipFlopType::ActiveHigh)), + "al" => Ok(Some(FlipFlopType::ActiveLow)), + "as" => Ok(Some(FlipFlopType::Asynchronous)), + "0" => { + init_val = FlipFlopInit::Const(false); + Ok(None) + } + "1" => { + init_val = FlipFlopInit::Const(true); + Ok(None) + } + "2" => { + init_val = FlipFlopInit::DontCare; + Ok(None) + } + "3" => { + init_val = FlipFlopInit::Unknown; + Ok(None) + } + _ => Err(BlifParserError::Invalid), + }) + .unwrap_or(Ok(None))?; + let mut ctrl = args.next().map(|x| x.into()); + if ctrl.as_ref().is_some_and(|x| x == "NIL") { + ctrl = None; + } + if let Some(x) = args.next() { + init_val = match x { + "0" => FlipFlopInit::Const(false), + "1" => FlipFlopInit::Const(true), + "2" => FlipFlopInit::DontCare, + "3" => FlipFlopInit::Unknown, + _ => Err(BlifParserError::Invalid)?, + }; + } + + if args.next().is_some() { + Err(BlifParserError::TooManyArgs)? + } + + consumer.ff(FlipFlop { + ty, + input: inp.into(), + output: out.into(), + clock: ctrl, + init: init_val, + }); + } + + ".gate" => { + let name = args.next().ok_or(BlifParserError::MissingArgs)?.into(); + let maps = args + .map(|x| { + x.split_once('=') + .ok_or(BlifParserError::Invalid) + .map(|(k, v)| (k.into(), v.into())) + }) + .collect::>()?; + + consumer.lib_gate(LibGate { name, maps }); + } + + ".mlatch" => { + let name = args.next().ok_or(BlifParserError::MissingArgs)?.into(); + let mut args = args.peekable(); + let mut maps = vec![]; + while args.peek().is_some_and(|x| x.contains("=")) { + let (k, v) = args + .next() + .unwrap() + .split_once("=") + .ok_or(BlifParserError::Invalid)?; + maps.push((k.into(), v.into())); + } + let mut control = args.next().map(|x| x.into()); + if control.as_ref().is_some_and(|x| x == "NIL") { + control = None; + } + let init_val = if let Some(x) = args.next() { + Some(match x { + "0" => FlipFlopInit::Const(false), + "1" => FlipFlopInit::Const(true), + "2" => FlipFlopInit::DontCare, + "3" => FlipFlopInit::Unknown, + _ => Err(BlifParserError::Invalid)?, + }) + } else { + None + }; + + if args.next().is_some() { + Err(BlifParserError::TooManyArgs)? + } + + consumer.lib_ff(LibFlipFlop { + name, + maps, + clock: control, + init: init_val.unwrap_or(FlipFlopInit::Unknown), + }); + } + + ".subckt" => { + let name = args.next().ok_or(BlifParserError::MissingArgs)?.into(); + let maps = args + .map(|x| { + x.split_once('=') + .ok_or(BlifParserError::Invalid) + .map(|(k, v)| (k.into(), v.into())) + }) + .collect::>()?; + + consumer.sub_model(name, maps); + } + + ".search" => { + let path = args.next().ok_or(BlifParserError::MissingArgs)?.into(); + + if args.next().is_some() { + Err(BlifParserError::TooManyArgs)? + } + + main_consumer.search(path); + } + + // TODO: .start_kiss + // TODO: clock & delay cst + _ => Err(BlifParserError::UnknownKw(cmd.to_string()))?, + }; + } + + main_consumer.model_done(consumer); + + Ok(()) +} + +/// # Arguments +/// +/// * `file_name` - when the model name is not declared explicitly in the BLIF file, this is used as model name (compliant with specification) +pub fn parse_blif( + file_name: &str, + consumer: &mut impl ModelConsumer, + lines: impl IntoIterator>, +) -> Result<(), BlifParserError> { + let mut lines = lines.into_iter().peekable(); + let mut first = true; + + while { + parse_padding(&mut lines); + lines.peek().is_some() + } { + if is_kw(&mut lines, ".model") || !first { + let line = next_stmt(&mut lines)?.unwrap(); + let line = line.as_ref(); + let mut args = line.split(' '); + let cmd = args.next().ok_or(BlifParserError::Invalid)?; + + match cmd { + ".search" => { + let path = args.next().ok_or(BlifParserError::MissingArgs)?.into(); + + if args.next().is_some() { + Err(BlifParserError::TooManyArgs)? + } + + consumer.search(path); + } + + ".model" => { + let mod_name = args.next().ok_or(BlifParserError::MissingArgs)?; + if args.next().is_some() { + Err(BlifParserError::TooManyArgs)?; + } + parse_mod(mod_name, consumer, &mut lines)?; + } + _ => Err(BlifParserError::UnknownKw(cmd.to_string()))?, + } + } else { + parse_mod(file_name, consumer, &mut lines)?; + } + + first = false; + } + + Ok(()) +} + +#[cfg(test)] +mod tests; diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..2bfbca5 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,450 @@ +use super::ast::*; +use super::*; + +#[test] +fn simple_named() { + let ast = parse_str_blif_to_ast( + "top.blif", + r#" +.model simple +.inputs a b +.outputs c +.names a b c # .names described later +11 1 +.end +"#, + ) + .unwrap(); + assert_eq!( + ast.entries, + vec![BlifEntry::Model(Model { + meta: ModelMeta { + name: "simple".into(), + inputs: Some(vec!["a".into(), "b".into()]), + outputs: Some(vec!["c".into()]), + clocks: vec![], + }, + commands: vec![ModelCmd::Gate(Gate { + meta: GateMeta { + inputs: vec!["a".into(), "b".into(),], + output: "c".into(), + external_dc: false, + }, + lut: LUT(vec![( + [Tristate::True, Tristate::True].into_iter().collect(), + true + )]), + })], + })], + ); +} + +#[test] +fn simple_unnamed() { + let ast = parse_str_blif_to_ast( + "top.blif", + r#" +.inputs a b +.outputs c +.names a b c # .names described later +11 1 +"#, + ) + .unwrap(); + assert_eq!( + ast.entries, + vec![BlifEntry::Model(Model { + meta: ModelMeta { + name: "top.blif".into(), + inputs: Some(vec!["a".into(), "b".into()]), + outputs: Some(vec!["c".into()]), + clocks: vec![], + }, + commands: vec![ModelCmd::Gate(Gate { + meta: GateMeta { + inputs: vec!["a".into(), "b".into(),], + output: "c".into(), + external_dc: false, + }, + lut: LUT(vec![( + [Tristate::True, Tristate::True].into_iter().collect(), + true + )]), + })], + })], + ); +} + +#[test] +fn simple_unnamed_infer() { + let ast = parse_str_blif_to_ast( + "top.blif", + r#" +.names a b \ +c +11 1 +"#, + ) + .unwrap(); + assert_eq!( + ast.entries, + vec![BlifEntry::Model(Model { + meta: ModelMeta { + name: "top.blif".into(), + inputs: None, + outputs: None, + clocks: vec![], + }, + commands: vec![ModelCmd::Gate(Gate { + meta: GateMeta { + inputs: vec!["a".into(), "b".into(),], + output: "c".into(), + external_dc: false, + }, + lut: LUT(vec![( + [Tristate::True, Tristate::True].into_iter().collect(), + true + )]), + })], + })], + ); +} + +#[test] +fn simple_unnamed_infer_extdc() { + let ast = parse_str_blif_to_ast( + "top.blif", + r#" +.exdc +.names a b \ +c +11 1 +"#, + ) + .unwrap(); + assert_eq!( + ast.entries, + vec![BlifEntry::Model(Model { + meta: ModelMeta { + name: "top.blif".into(), + inputs: None, + outputs: None, + clocks: vec![], + }, + commands: vec![ModelCmd::Gate(Gate { + meta: GateMeta { + inputs: vec!["a".into(), "b".into(),], + output: "c".into(), + external_dc: true, + }, + lut: LUT(vec![( + [Tristate::True, Tristate::True].into_iter().collect(), + true + )]), + })], + })], + ); +} + +#[test] +fn lut() { + let ast = parse_str_blif_to_ast( + "top.blif", + r#" +.names v3 v6 j u78 v13.15 +1--0 1 +-1-1 1 +0-11 1 +"#, + ) + .unwrap(); + assert_eq!( + ast.entries, + vec![BlifEntry::Model(Model { + meta: ModelMeta { + name: "top.blif".into(), + inputs: None, + outputs: None, + clocks: vec![], + }, + commands: vec![ModelCmd::Gate(Gate { + meta: GateMeta { + inputs: vec!["v3".into(), "v6".into(), "j".into(), "u78".into()], + output: "v13.15".into(), + external_dc: false, + }, + lut: LUT(vec![ + ( + [ + Tristate::True, + Tristate::Ignored, + Tristate::Ignored, + Tristate::False + ] + .into_iter() + .collect(), + true + ), + ( + [ + Tristate::Ignored, + Tristate::True, + Tristate::Ignored, + Tristate::True + ] + .into_iter() + .collect(), + true + ), + ( + [ + Tristate::False, + Tristate::Ignored, + Tristate::True, + Tristate::True + ] + .into_iter() + .collect(), + true + ) + ]), + })], + })], + ); +} + +#[test] +fn submod() { + let ast = parse_str_blif_to_ast( + "top.blif", + r#" +.model a +.inputs x y +.outputs j +.subckt b x=x y=y j=j +.exdc +.names x j +1 1 +.end + + +.model b +.inputs x y +.outputs j +.names x y j +11 1 +.end +"#, + ) + .unwrap(); + assert_eq!( + ast.entries, + vec![ + BlifEntry::Model(Model { + meta: ModelMeta { + name: "a".into(), + inputs: Some(vec!["x".into(), "y".into()]), + outputs: Some(vec!["j".into()]), + clocks: vec![] + }, + commands: vec![ + ModelCmd::SubModel { + name: "b".into(), + map: vec![ + ("x".into(), "x".into()), + ("y".into(), "y".into()), + ("j".into(), "j".into()) + ] + }, + ModelCmd::Gate(Gate { + meta: GateMeta { + inputs: vec!["x".into()], + output: "j".into(), + external_dc: true + }, + lut: LUT(vec![([Tristate::True].into_iter().collect(), true)]) + }) + ] + }), + BlifEntry::Model(Model { + meta: ModelMeta { + name: "b".into(), + inputs: Some(vec!["x".into(), "y".into()]), + outputs: Some(vec!["j".into()]), + clocks: vec![] + }, + commands: vec![ModelCmd::Gate(Gate { + meta: GateMeta { + inputs: vec!["x".into(), "y".into()], + output: "j".into(), + external_dc: false + }, + lut: LUT(vec![( + [Tristate::True, Tristate::True].into_iter().collect(), + true + )]) + })] + }) + ] + ); +} + +#[test] +fn simple_ff() { + let ast = parse_str_blif_to_ast( + "top.blif", + r#" +.inputs in # a very simple sequential circuit +.outputs out +.latch out in 0 +.names in out +0 1 +.end +"#, + ) + .unwrap(); + + // TODO: assert_eq +} + +#[test] +fn simple_ff_clocked() { + let ast = parse_str_blif_to_ast( + "top.blif", + r#" +.inputs d # a clocked flip-flop +.output q +.clock c +.latch d q re c 0 +.end +"#, + ) + .unwrap(); + + // TODO: assert_eq +} + +#[test] +fn tech_gate() { + let ast = parse_str_blif_to_ast( + "top.blif", + r#" +.inputs v1 v2 +.outputs j +.gate nand2 A=v1 B=v2 O=x # given: formals of this gate are A, B, O +.gate inv A=x O=j # given: formals of this gate are A & O +.end +"#, + ) + .unwrap(); + + // TODO: assert_eq +} + +#[test] +fn tech_ff() { + let ast = parse_str_blif_to_ast( + "top.blif", + r#" +.inputs j kbar +.outputs out +.clock clk +.mlatch jk_rising_edge J=j K=k Q=q clk 1 # given: formals are J, K, Q +.names q out +0 1 +.names kbar k +0 1 +.end +"#, + ) + .unwrap(); + + // TODO: assert_eq +} + +#[test] +fn tech_kiss_fsm() { + let ast = parse_str_blif_to_ast( + "top.blif", + r#" +.model 101 # outputs 1 whenever last 3 inputs were 1, 0, 1 +.start_kiss +.i 1 +.o 1 +0 st0 st0 0 +1 st0 st1 0 +0 st1 st2 0 +1 st1 st1 0 +0 st2 st0 0 +8 +1 st2 st3 1 +0 st3 st2 0 +1 st3 st1 0 +.end_kiss +.end +"#, + ) + .unwrap(); + + // TODO: assert_eq +} + +#[test] +fn tech_kiss_fsm2() { + let ast = parse_str_blif_to_ast( + "top.blif", + r#" +.model +.inputs v0 +.outputs v3.2 +.latch [6] v1 0 +.latch [7] v2 0 +.start_kiss +.i 1 +.o 1 +.p 8 +.s 4 +.r st0 +0 st0 st0 0 +1 st0 st1 0 +0 st1 st2 0 +1 st1 st1 0 +0 st2 st0 0 +1 st2 st3 1 +0 st3 st2 0 +1 st3 st1 0 +.end_kiss +.latch_order v1 v2 +.code st0 00 +.code st1 11 +.code st2 01 +.code st3 10 +.names v0 [6] +1 1 +.names v0 v1 v2 [7] +-1- 1 +1-0 1 +.names v0 v1 v2 v3.2 +101 1 +.end +"#, + ) + .unwrap(); + + // TODO: assert_eq +} + +#[test] +fn tech_clock_cst() { + let ast = parse_str_blif_to_ast( + "top.blif", + r#" +.clock clock1 clock2 +.clock_event 50.0 r’clock1 +.clock_event 50.0 (f’clock2 2.0 5.0) +"#, + ) + .unwrap(); + + // TODO: assert_eq +}