This commit is contained in:
2025-08-20 23:50:14 +02:00
commit 0fafa4c95b
7 changed files with 1276 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

26
Cargo.lock generated Normal file
View File

@@ -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",
]

8
Cargo.toml Normal file
View File

@@ -0,0 +1,8 @@
[package]
name = "turbo-blif"
version = "0.1.0"
edition = "2024"
[dependencies]
smallstr = "0.3.1"
smallvec = "1.15.1"

13
README.md Normal file
View File

@@ -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.

183
src/ast.rs Normal file
View File

@@ -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<GateMeta> 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<ModelCmd>,
}
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<E: std::fmt::Debug> {
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<BlifEntry>,
to_search: Vec<String>,
}
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<E: std::fmt::Debug, L: IntoIterator<Item = impl AsRef<str>>>(
path: &str,
lut: impl Fn(&str) -> Result<L, E>,
) -> Result<Blif, FullBlifErr<E>> {
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<Item = impl AsRef<str>>,
) -> Result<Blif, FullBlifErr<()>> {
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<Blif, FullBlifErr<()>> {
parse_blif_to_ast(filename, source.split('\n'))
}

595
src/lib.rs Normal file
View File

@@ -0,0 +1,595 @@
use smallvec::SmallVec;
use std::{iter::Peekable, str::FromStr};
pub mod ast;
pub type Str<const N: usize> = smallstr::SmallString<[u8; N]>;
#[derive(Debug, Clone, Hash, PartialEq, PartialOrd)]
pub struct GateMeta {
pub inputs: Vec<Str<16>>,
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<FlipFlopType>,
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<Str<16>>,
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<Str<16>>,
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<Vec<Str<16>>>,
/// if [None], HAS TO be inferred from netlist (signals which are not inputs to other blocks)
pub outputs: Option<Vec<Str<16>>>,
pub clocks: Vec<Str<16>>,
}
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<impl Iterator<Item = impl AsRef<str>>>) {
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<T> {
AsRef(T),
String(String),
}
impl<T: AsRef<str>> AsRef<str> for AsRefOrString<T> {
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<S: AsRef<str>>(
lines: &mut Peekable<impl Iterator<Item = S>>,
) -> Result<Option<AsRefOrString<S>>, 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<impl Iterator<Item = impl AsRef<str>>>, 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<Self, Self::Err> {
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<impl Iterator<Item = impl AsRef<str>>>,
) -> 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::<Result<_, _>>()
.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::<Result<_, _>>()?;
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::<Result<_, _>>()?;
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<Item = impl AsRef<str>>,
) -> 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;

450
src/tests.rs Normal file
View File

@@ -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 rclock1
.clock_event 50.0 (fclock2 2.0 5.0)
"#,
)
.unwrap();
// TODO: assert_eq
}