mirror of
https://github.com/alex-s168/turbo-blif.git
synced 2025-09-09 17:45:09 +02:00
init
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
26
Cargo.lock
generated
Normal file
26
Cargo.lock
generated
Normal 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
8
Cargo.toml
Normal 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
13
README.md
Normal 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
183
src/ast.rs
Normal 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
595
src/lib.rs
Normal 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
450
src/tests.rs
Normal 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 r’clock1
|
||||
.clock_event 50.0 (f’clock2 2.0 5.0)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// TODO: assert_eq
|
||||
}
|
Reference in New Issue
Block a user