diff --git a/cider/src/errors.rs b/cider/src/errors.rs index 297155e243..c2ffbd66c5 100644 --- a/cider/src/errors.rs +++ b/cider/src/errors.rs @@ -236,6 +236,9 @@ pub enum RuntimeError { idx: GlobalCellIdx, }, + #[error("Assertion has been tripped")] + AssertionError(GlobalCellIdx), + // TODO (Griffin): Make this error message better please #[error("Computation has under/overflowed its bounds")] OverflowError, @@ -474,6 +477,9 @@ impl RuntimeError { dims.as_string() )) } + RuntimeError::AssertionError(cell) => CiderError::GenericError( + format!("Assert '{}' failed", env.get_full_name(cell)), + ), RuntimeError::OverflowError => todo!(), RuntimeError::StalledExecution => { CiderError::GenericError(format!( diff --git a/cider/src/flatten/flat_ir/cell_prototype.rs b/cider/src/flatten/flat_ir/cell_prototype.rs index 9a17ffa9e7..f02669de8b 100644 --- a/cider/src/flatten/flat_ir/cell_prototype.rs +++ b/cider/src/flatten/flat_ir/cell_prototype.rs @@ -95,6 +95,8 @@ pub enum SingleWidthType { UnsynSDiv, /// Unsynthesizeable signed mod (`std_unsyn_smod`) UnsynSMod, + /// Unsynthesizable assertion (`assert`) + UnsynAssert, /// Represents the `undef` primitive. Not to be confused with undefined /// port values during simulation. Undef, @@ -807,7 +809,7 @@ impl CellPrototype { }) } n @ ("std_unsyn_mult" | "std_unsyn_div" | "std_unsyn_smult" - | "std_unsyn_sdiv" | "std_unsyn_mod" + | "std_unsyn_sdiv" | "std_unsyn_mod" | "std_assert" | "std_unsyn_smod") => { get_params![params; width: "WIDTH"]; Self::SingleWidth { @@ -817,6 +819,7 @@ impl CellPrototype { "std_unsyn_smult" => SingleWidthType::UnsynSMult, "std_unsyn_sdiv" => SingleWidthType::UnsynSDiv, "std_unsyn_mod" => SingleWidthType::UnsynMod, + "std_assert" => SingleWidthType::UnsynAssert, _ => SingleWidthType::UnsynSMod, }, width: width.try_into().unwrap(), diff --git a/cider/src/flatten/primitives/builder.rs b/cider/src/flatten/primitives/builder.rs index 7df8a6fdbd..e2cfc40c6f 100644 --- a/cider/src/flatten/primitives/builder.rs +++ b/cider/src/flatten/primitives/builder.rs @@ -135,6 +135,9 @@ pub fn build_primitive( SingleWidthType::Undef => { Box::new(StdUndef::new(base_port, *width)) } + SingleWidthType::UnsynAssert => { + Box::new(UnsynAssert::new(base_port, cell_idx)) + } } } CellPrototype::FixedPoint { diff --git a/cider/src/flatten/primitives/stateful/mod.rs b/cider/src/flatten/primitives/stateful/mod.rs index 5ab4be6196..5cbe51fc42 100644 --- a/cider/src/flatten/primitives/stateful/mod.rs +++ b/cider/src/flatten/primitives/stateful/mod.rs @@ -1,5 +1,7 @@ pub mod math; pub mod memories; +pub mod other; pub use math::*; pub use memories::*; +pub use other::*; diff --git a/cider/src/flatten/primitives/stateful/other.rs b/cider/src/flatten/primitives/stateful/other.rs new file mode 100644 index 0000000000..1fe6531e80 --- /dev/null +++ b/cider/src/flatten/primitives/stateful/other.rs @@ -0,0 +1,73 @@ +use cider_idx::iter::SplitIndexRange; + +use crate::{ + errors::RuntimeError, + flatten::{ + flat_ir::indexes::{GlobalCellIdx, GlobalPortIdx, PortValue}, + primitives::{ + Primitive, + macros::declare_ports, + prim_trait::{UpdateResult, UpdateStatus}, + }, + structures::environment::{MemoryMap, PortMap}, + }, +}; + +#[derive(Clone)] +pub struct UnsynAssert { + base_port: GlobalPortIdx, + cell_id: GlobalCellIdx, + output: PortValue, +} + +impl UnsynAssert { + declare_ports![IN: 0, EN: 1, _CLK:2, _RESET: 3 | OUT: 4]; + pub fn new(base_port: GlobalPortIdx, cell_id: GlobalCellIdx) -> Self { + Self { + base_port, + output: PortValue::new_undef(), + cell_id, + } + } +} + +impl Primitive for UnsynAssert { + fn exec_comb( + &self, + port_map: &mut PortMap, + _state_map: &MemoryMap, + ) -> crate::flatten::primitives::prim_trait::UpdateResult { + crate::flatten::primitives::macros::ports![&self.base_port; + out: Self::OUT + ]; + + Ok(port_map.write_exact_unchecked(out, self.output.clone())) + } + + fn exec_cycle( + &mut self, + port_map: &mut PortMap, + _: &mut MemoryMap, + ) -> UpdateResult { + crate::flatten::primitives::macros::ports![&self.base_port; + in_: Self::IN, + en: Self::EN + ]; + if port_map[en].as_bool().unwrap_or_default() + && !port_map[in_].as_bool().unwrap_or_default() + { + Err(RuntimeError::AssertionError(self.cell_id).into()) + } else { + self.output = port_map[in_].clone(); + Ok(UpdateStatus::Unchanged) + } + } + + fn clone_boxed(&self) -> Box { + Box::new(self.clone()) + } + + fn get_ports(&self) -> SplitIndexRange { + self.get_signature() + } +} diff --git a/primitives/assert.futil b/primitives/assert.futil new file mode 100644 index 0000000000..9342745e53 --- /dev/null +++ b/primitives/assert.futil @@ -0,0 +1,5 @@ + +extern "assert.sv" { + primitive std_assert(@data in: 1, en: 1, @clk clk:1, @reset reset: 1) -> (out: 1); + // WARNING: the assert will be optimized out unless it is `@protected`, or `out` is referenced +} diff --git a/primitives/assert.sv b/primitives/assert.sv new file mode 100644 index 0000000000..ae579fe23a --- /dev/null +++ b/primitives/assert.sv @@ -0,0 +1,14 @@ +module std_assert ( + input wire logic clk, + input wire logic reset, + input logic in, + input logic en, + output logic out +); + always_ff @(posedge clk) begin + out <= in; + if (in == '0 & en) begin + $fatal(1, "std_assert tripped!"); + end + end +endmodule diff --git a/tests/correctness/assert.expect b/tests/correctness/assert.expect new file mode 100644 index 0000000000..b7e86d3c2a --- /dev/null +++ b/tests/correctness/assert.expect @@ -0,0 +1,4 @@ +---CODE--- +1 +---STDERR--- +Error: ninja exited with exit status: 1 diff --git a/tests/correctness/assert.futil b/tests/correctness/assert.futil new file mode 100644 index 0000000000..e2f8c99b40 --- /dev/null +++ b/tests/correctness/assert.futil @@ -0,0 +1,49 @@ +import "primitives/core.futil"; +import "primitives/binary_operators.futil"; +import "primitives/assert.futil"; + + +// TODO(elamdf): this test currently expects verilator (-> ninja) to crash. +// A better solution would be to extend runt to parse the verilator sim log, +// looking for the assertion as the cause of the crash. + +component main() -> () { + cells { + a = std_const(32, 42); + b = std_const(32, 41); + eq = std_eq(32); + tmp_reg = std_reg(1); + // the assert will be optimized out unless it is `@protected`, or `out` is referenced + @protected my_assert = std_assert(); + } + + wires { + eq.left = a.out; + eq.right = b.out; + + + my_assert.in = eq.out; + group check_assert { + tmp_reg.in = 1'b1; + tmp_reg.write_en = 1'b1; + // assert that `a` and `b` are equal (they aren't!) + my_assert.en = 1'b1; + check_assert[done] = tmp_reg.done; + + } + + group check_assert2 { + tmp_reg.in = eq.out; + tmp_reg.write_en = 1'b1; + check_assert2[done] = tmp_reg.done; + + } + } + + control { + check_assert; + check_assert2; + + + } +} diff --git a/tests/correctness/assert.futil.data b/tests/correctness/assert.futil.data new file mode 100644 index 0000000000..c97d2cfe57 --- /dev/null +++ b/tests/correctness/assert.futil.data @@ -0,0 +1 @@ +we should never get here \ No newline at end of file