diff --git a/README.md b/README.md index 68c3286..932eee3 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # turbo-blif -low-memory-usage BLIF (berkeley logic interchange format) parser and writer. +low-memory-usage BLIF (berkeley logic interchange format) parser. 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`) + (supports reading of BLIF files generated with `write_blif -iname -iattr -param -cname -blackbox -attr -conn`) - KISS state machines (which yosys doesn't even support) - clock and delay constraints (yosys just ignores those) diff --git a/src/ast.rs b/src/ast.rs index e4ade96..a31f33b 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -80,7 +80,23 @@ impl FSMConsumer for FSM { } #[derive(Debug, Clone, Hash, PartialEq, PartialOrd)] -pub enum ModelCmd { +pub enum CellAttrAst { + /// non-standard; emitted by: yosys + CellName(String), + + /// non-standard; possibly emitted by: yosys + /// + /// example: Attr { key: "src", val: "\"some/file.v:320.20-320.28\"" } + Attr { key: Str<8>, val: String }, + + /// non-standard; emitted by: yosys + /// + /// example: Param { key: "A_WIDTH", val: "00000000000000000000000000000001" } + Param { key: Str<16>, val: String }, +} + +#[derive(Debug, Clone, Hash, PartialEq, PartialOrd)] +pub enum ModelCmdKind { Gate(Gate), FF(FlipFlop), LibGate(LibGate), @@ -90,6 +106,25 @@ pub enum ModelCmd { name: Str<32>, map: Vec<(Str<16>, Str<16>)>, }, + Connect { + from: Str<16>, + to: Str<16>, + }, +} + +#[derive(Debug, Clone, Hash, PartialEq, PartialOrd)] +pub struct ModelCmd { + pub kind: ModelCmdKind, + pub attrs: Vec, +} + +impl From for ModelCmd { + fn from(value: ModelCmdKind) -> Self { + Self { + kind: value, + attrs: vec![], + } + } } #[derive(Debug, Clone, Hash, PartialEq, PartialOrd)] @@ -106,26 +141,29 @@ impl CommandConsumer for Model { } fn gate_done(&mut self, gate: Self::Gate) { - self.commands.push(ModelCmd::Gate(gate)); + self.commands.push(ModelCmdKind::Gate(gate).into()); } fn ff(&mut self, ff: FlipFlop) { - self.commands.push(ModelCmd::FF(ff)); + self.commands.push(ModelCmdKind::FF(ff).into()); } fn lib_gate(&mut self, gate: LibGate) { - self.commands.push(ModelCmd::LibGate(gate)); + self.commands.push(ModelCmdKind::LibGate(gate).into()); } fn lib_ff(&mut self, ff: LibFlipFlop) { - self.commands.push(ModelCmd::LibFF(ff)); + self.commands.push(ModelCmdKind::LibFF(ff).into()); } fn sub_model(&mut self, model: &str, map: Vec<(Str<16>, Str<16>)>) { - self.commands.push(ModelCmd::SubModel { - name: model.into(), - map, - }); + self.commands.push( + ModelCmdKind::SubModel { + name: model.into(), + map, + } + .into(), + ); } type FSM = FSM; @@ -150,7 +188,31 @@ impl CommandConsumer for Model { let mut fsm = fsm; fsm.physical_latch_order = physical_latch_order; fsm.state_assignments = state_assignments; - self.commands.push(ModelCmd::FSM(fsm)); + self.commands.push(ModelCmdKind::FSM(fsm).into()); + } + + fn attr(&mut self, attr: CellAttr) { + self.commands.last_mut().unwrap().attrs.push(match attr { + CellAttr::CellName(n) => CellAttrAst::CellName(n.into()), + CellAttr::Attr { key, val } => CellAttrAst::Attr { + key: key.into(), + val: val.into(), + }, + CellAttr::Param { key, val } => CellAttrAst::Param { + key: key.into(), + val: val.into(), + }, + }); + } + + fn connect(&mut self, from: &str, to: &str) { + self.commands.push( + ModelCmdKind::Connect { + from: from.into(), + to: to.into(), + } + .into(), + ) } } diff --git a/src/lib.rs b/src/lib.rs index 906d065..19151d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,6 +63,22 @@ pub trait FSMConsumer { fn add_transition(&mut self, transition: FSMTransition); } +#[derive(Debug, Clone, Hash, PartialEq, PartialOrd)] +pub enum CellAttr<'a> { + /// non-standard; emitted by: yosys + CellName(&'a str), + + /// non-standard; possibly emitted by: yosys + /// + /// example: Attr { key: "src", val: "\"some/file.v:320.20-320.28\"" } + Attr { key: &'a str, val: &'a str }, + + /// non-standard; emitted by: yosys + /// + /// example: Param { key: "A_WIDTH", val: "00000000000000000000000000000001" } + Param { key: &'a str, val: &'a str }, +} + pub trait CommandConsumer { type Gate: GateLutConsumer; type FSM: FSMConsumer; @@ -84,6 +100,12 @@ pub trait CommandConsumer { /// 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>)>); + + /// attach attr to last gate / fsm / ff / libgate / libff / sub_model + fn attr(&mut self, attr: CellAttr); + + /// non-standard + fn connect(&mut self, from: &str, to: &str); } #[derive(Debug, Clone, Hash, PartialEq, PartialOrd)] @@ -404,7 +426,13 @@ fn parse_mod( 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 l = l.as_ref(); + + let (l, r) = if l.contains(' ') { + l.split_once(' ').unwrap() + } else { + ("", l) + }; let invs = str_to_tristates(l).map_err(|_| BlifParserError::Invalid)?; let outvs = match r { @@ -765,7 +793,59 @@ fn parse_mod( ); } + ".cname" => { + let arg = args.next().ok_or(BlifParserError::MissingArgs)?.into(); + + if args.next().is_some() { + Err(BlifParserError::TooManyArgs)? + } + + consumer.attr(CellAttr::CellName(arg)); + } + + ".attr" => { + let key = args.next().ok_or(BlifParserError::MissingArgs)?.into(); + + let val = args.fold(String::new(), |acc, x| { + let mut acc = acc; + acc.push_str(x); + acc + }); + + consumer.attr(CellAttr::Attr { + key, + val: val.as_str(), + }); + } + + ".param" => { + let key = args.next().ok_or(BlifParserError::MissingArgs)?.into(); + + let val = args.fold(String::new(), |acc, x| { + let mut acc = acc; + acc.push_str(x); + acc + }); + + consumer.attr(CellAttr::Param { + key, + val: val.as_str(), + }); + } + + ".conn" => { + let from = args.next().ok_or(BlifParserError::MissingArgs)?.into(); + let to = args.next().ok_or(BlifParserError::MissingArgs)?.into(); + + if args.next().is_some() { + Err(BlifParserError::TooManyArgs)? + } + + consumer.connect(from, to); + } + // TODO: clock & delay cst + // TODO: .blackblox _ => Err(BlifParserError::UnknownKw(cmd.to_string()))?, }; } diff --git a/src/tests.rs b/src/tests.rs index 14c5545..b78381d 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -24,17 +24,20 @@ fn simple_named() { 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 - )]), - })], + commands: vec![ + ModelCmdKind::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 + )]), + }) + .into() + ], })], ); } @@ -60,17 +63,20 @@ fn simple_unnamed() { 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 - )]), - })], + commands: vec![ + ModelCmdKind::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 + )]), + }) + .into() + ], })], ); } @@ -95,17 +101,20 @@ c 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 - )]), - })], + commands: vec![ + ModelCmdKind::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 + )]), + }) + .into() + ], })], ); } @@ -131,17 +140,20 @@ c 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 - )]), - })], + commands: vec![ + ModelCmdKind::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 + )]), + }) + .into() + ], })], ); } @@ -167,48 +179,51 @@ fn lut() { 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 - ) - ]), - })], + commands: vec![ + ModelCmdKind::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 + ) + ]), + }) + .into() + ], })], ); } @@ -248,15 +263,16 @@ fn submod() { clocks: vec![] }, commands: vec![ - ModelCmd::SubModel { + ModelCmdKind::SubModel { name: "b".into(), map: vec![ ("x".into(), "x".into()), ("y".into(), "y".into()), ("j".into(), "j".into()) ] - }, - ModelCmd::Gate(Gate { + } + .into(), + ModelCmdKind::Gate(Gate { meta: GateMeta { inputs: vec!["x".into()], output: "j".into(), @@ -264,6 +280,7 @@ fn submod() { }, lut: LUT(vec![([Tristate::True].into_iter().collect(), true)]) }) + .into() ] }), BlifEntry::Model(Model { @@ -273,17 +290,20 @@ fn submod() { 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 - )]) - })] + commands: vec![ + ModelCmdKind::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 + )]) + }) + .into() + ] }) ] ); @@ -434,7 +454,7 @@ fn kiss_fsm2() { } #[test] -fn tech_clock_cst() { +fn clock_cst() { let ast = parse_str_blif_to_ast( "top.blif", r#" @@ -447,3 +467,43 @@ fn tech_clock_cst() { // TODO: assert_eq } + +#[test] +fn yosys_attrs() { + let ast = parse_str_blif_to_ast( + "top.blif", + r#" + +.model MAC +.inputs clock reset io_rmii_r_RXD[0] io_rmii_r_RXD[1] io_rmii_r_RXER io_rmii_r_CRS_DV io_rmii_clk io_is10mbits io_full_duplex xmit_io_transmit xmit_io_byte[0] xmit_io_byte[1] xmit_io_byte[2] xmit_io_byte[3] xmit_io_byte[4] xmit_io_byte[5] xmit_io_byte[6] xmit_io_byte[7] +.outputs io_rmii_t_TXD[0] io_rmii_t_TXD[1] io_rmii_t_TXEN xmit_io_byte_sent recv_io_frame_start recv_io_abort_frame recv_io_valid_byte recv_io_byte[0] recv_io_byte[1] recv_io_byte[2] recv_io_byte[3] recv_io_byte[4] recv_io_byte[5] recv_io_byte[6] recv_io_byte[7] recv_io_info_carrier_lost_during_packet +.names $false +.names $true +1 +.names $undef + +.subckt $add A[0]=xmit_preamble_counter[0] A[1]=xmit_preamble_counter[1] A[2]=xmit_preamble_counter[2] B[0]=$true B[1]=$false B[2]=$false Y[0]=$add$build/out.v:213$277_Y[0] Y[1]=$add$build/out.v:213$277_Y[1] Y[2]=$add$build/out.v:213$277_Y[2] +.cname $add$build/out.v:213$277 +.attr src "build/out.v:213.31-213.59" +.param A_SIGNED 00000000000000000000000000000000 +.param A_WIDTH 00000000000000000000000000000011 +.param B_SIGNED 00000000000000000000000000000000 +.param B_WIDTH 00000000000000000000000000000011 +.param Y_WIDTH 00000000000000000000000000000011 + +.subckt $and A=$not$build/out.v:170$224_Y B=$eq$build/out.v:170$225_Y Y=$and$build/out.v:170$226_Y +.cname $and$build/out.v:170$226 +.attr src "build/out.v:170.31-170.103" +.param A_SIGNED 00000000000000000000000000000000 +.param A_WIDTH 00000000000000000000000000000001 +.param B_SIGNED 00000000000000000000000000000000 +.param B_WIDTH 00000000000000000000000000000001 +.param Y_WIDTH 00000000000000000000000000000001 + + +"#, + ) + .unwrap(); + + // TODO: assert_eq +}