mirror of
https://github.com/alex-s168/turbo-blif.git
synced 2025-09-10 01:55:08 +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