Infix Operator Interface
April 5, 2021 ยท View on GitHub
NOTE Property support is experimental, please submit bug reports and feature requests via GitHub Issues.
Fault provides two interfaces for defining temporal assertions. The firt uses
a magic implementation of the __or__ and __ror__ methods to define custom
"infix" operators. The second provides an interface to provide strings
containing SVA operator syntax.
Infix Operator Interface
This interface overloads the use of the | operator to define a "custom infix"
operator. Here's an example of a property where io.I being high implies that
the value (driver) of io.O will be high one cycle later.
f.assert_(io.I | f.implies | f.delay[1] | (io.O.value() == 0),
on=f.posedge(io.CLK))
Operators
f.impliescorresponds to SVA|->f.delay[N]corresponds to SVA##Nf.delay[M:N]corresponds to SVA##[M:N]f.delay[0:]corresponds to SVA##[*]or##[*0:$]f.delay[1:]corresponds to SVA##[+]or##[*1:$]f.repeat[N]corresponds to SVA[*N]f.repeat[0:]corresponds to SVA[*]f.repeat[1:]corresponds to SVA[+]f.goto[N]corresponds to SVA[-> N]f.goto[M:N]corresponds to SVA[-> M:N]f.eventuallycorresponds to SVAs_eventuallyf.throughoutcorresponds to SVAthroughoutf.untilcorresponds to SVAuntilf.until_withcorresponds to SVAuntil_withf.insidecorresponds to SVinside
SVA
Use the f.sva function to construct sva properties by interleaving magma
signal/expressions and SVA operators. Here's a simple example:
f.assert_(f.sva(io.I, "|-> ##1", io.O.value() == 0),
on=f.posedge(io.CLK))
System and Sampled Value Functions
f.onehot0f.onehotf.countonesf.isunknownf.pastf.rosef.fellf.stable
Other
f.not_negates property
Compile Guards (ifdef)
Use the compile_guard parameter and pass a string for the variable to guard
the property.
f.assert_(..., compile_guard="ASSERT_ON", ...)
This will wrap the generated property inside a macro.
`ifdef ASSERT_ON
...
`endif
For multiple guards, you can pass a list of strings, for example:
f.assert_(..., compile_guard=["ASSERT_ON", "FORMAL_ON"], ...)
will produce
`ifdef ASSERT_ON
`ifdef FORMAL_ON
...
`endif
`endif
NOTE: One issue with using a compile guard is that when the macro is disabled
magma/fault will produce dangling wires (these are used inside the body
of the macro which has been disabled). As a temporary measure, fault will generate
these wires with the prefix _FAULT_ASSERT_WIRE_ so you can direct your lint
tool to ignore these dangling wires (e.g. when you disable macros for synthesis).
The prefix can be chagned by setting the parameter inline_wire_prefix when
invoking f.assert_.
Disable If
Use the disable_iff parameter and pass a signal or expression, for example
with a negedge RESET:
f.assert_(..., on=f.posedge(io.CLK), disable_iff=f.not_(io.RESETN), ...)
Name
Use the name parameter to give a property a name in the generated code.
For example,
f.assert_(..., name="my_property")
will insert my_property: before the generated property code.
Helper function for default clock/reset
A useful pattern is leveraging magma's automatic clock wiring logic to handle default clock and reset values so the user doesn't have to provide them for most assertions where the default clock is obvious and will be wired up automatically.
To do this, create a temporary value of the approriate type and use that for the on/disable_iff parameter. This undriven temporary value will be automatically wired up by magma. We leave this to the user rather than having it built-in so the user can control the Clock type or the reset type/semantics.
Here's an example:
def my_assert(property, on=None, disable_iff=None):
# If needed, create undriven clock/reset temporaries, will be driven by
# automatic clock wiring logic
if on is None:
on = f.posedge(m.Clock())
if disable_iff is None:
disable_iff = f.not_(m.ResetN())
f.assert_(property, on=on, disable_iff=disable_iff)
class Main(m.Circuit):
io = m.IO(I=m.In(m.Bits[8]), O=m.Out(m.Bits[8]))
io += m.ClockIO(has_resetn=True)
io.O @= m.Register(T=m.Bits[8], reset_type=m.ResetN)()(io.I)
my_assert(io.I | f.implies | f.delay[1] | io.O)
Examples
Here are some representative examples, for a complete set of examples and corresponding test cases, see tests/test_property.py.
Basic assertion
Here's an example of a basic implication/delay assertion on a register.
class Main(m.Circuit):
io = m.IO(I=m.In(m.Bits[8]), O=m.Out(m.Bits[8])) + m.ClockIO()
io.O @= m.Register(T=m.Bits[8])()(io.I)
# SVA
f.assert_(f.sva(io.I, "|-> ##1", io.O.value() == 0),
on=f.posedge(io.CLK))
# Infix
f.assert_(io.I | f.implies | f.delay[1] | (io.O.value() == 0),
on=f.posedge(io.CLK))
Variable Delay
Here's an example of three properties using variable delay.
class Main(m.Circuit):
io = m.IO(write=m.In(m.Bit), read=m.In(m.Bit)) + m.ClockIO()
# SVA
f.assert_(f.sva(io.write, "|-> ##[1:2]", io.read),
on=f.posedge(io.CLK))
f.assert_(f.sva(io.write, "|-> ##[*]", io.read),
on=f.posedge(io.CLK))
f.assert_(f.sva(io.write, "|-> ##[+]", io.read),
on=f.posedge(io.CLK))
# Infix
f.assert_(io.write | f.implies | f.delay[1:2] | io.read,
on=f.posedge(io.CLK))
f.assert_(io.write | f.implies | f.delay[0:] | io.read,
on=f.posedge(io.CLK))
f.assert_(io.write | f.implies | f.delay[1:] | io.read,
on=f.posedge(io.CLK))
Repetition and Sequences
Here's an example of using repetition and sequences. Note the use of
f.sequence in order to group sequences together for the repeition operator.
This property waits for read and write to be low for 2 cycles, followed by seq0 (read low, then write high) repeated twice followed by seq1 (read high, write high).
class Main(m.Circuit):
io = m.IO(write=m.In(m.Bit), read=m.In(m.Bit)) + m.ClockIO()
# SVA
seq0 = f.sequence(f.sva(~io.read, "##1", io.write))
seq1 = f.sequence(f.sva(io.read, "##1", io.write))
f.assert_(f.sva(~io.read & ~io.write, "[*2] |->", seq0,
f"[*2] ##1", seq1), on=f.posedge(io.CLK))
# Infix
seq0 = f.sequence(~io.read | f.delay[1] | io.write)
seq1 = f.sequence(io.read | f.delay[1] | io.write)
f.assert_(~io.read & ~io.write | f.repeat[2] | f.implies | seq0 |
f.repeat[2] | f.delay[1] | seq1, on=f.posedge(io.CLK))
Cover
Fault provides a function f.cover that uses the same interface as
f.assert_. This will generate a system verilog coverage statement.
In order to use this, you'll need to compile_and_run your circuit with
coverage=True (NOTE: vcs is currently unsupported, please open an issue to
request support).
Here's an example:
def test_coverage(capsys):
"""
NOTE: Uses pytest's capsys feature to check the output of stdout
"""
class Main(m.Circuit):
io = m.IO(I=m.In(m.Bit), O=m.Out(m.Bit)) + m.ClockIO()
io.O @= m.Register(T=m.Bit)()(io.I)
f.cover(io.I | f.delay[1] | ~io.I, on=f.posedge(io.CLK))
tester = f.SynchronousTester(Main, Main.CLK)
tester.circuit.I = 1
tester.advance_cycle()
tester.circuit.I = 1
tester.advance_cycle()
tester.compile_and_run("system-verilog", simulator="ncsim",
flags=["-sv"], magma_opts={"inline": True},
disp_type="realtime", coverage=True)
out, _ = capsys.readouterr()
# not covered
assert """\
Disabled Finish Failed Assertion Name
0 0 0 Main_tb.dut.__cover1
Total Assertions = 1, Failing Assertions = 0, Unchecked Assertions = 1\
""" in out
tester.circuit.I = 1
tester.advance_cycle()
tester.circuit.I = 0
tester.compile_and_run("system-verilog", simulator="ncsim",
flags=["-sv"], magma_opts={"inline": True},
disp_type="realtime", coverage=True)
out, _ = capsys.readouterr()
# covered
assert """\
Disabled Finish Failed Assertion Name
0 1 0 Main_tb.dut.__cover1
Total Assertions = 1, Failing Assertions = 0, Unchecked Assertions = 0\
""" in out
Assume
Fault provides the function f.assume that uses the same interface as
f.assert_ and will generate a system verilog assume statement
Advanced Examples
Example 1
Verilog
input [7:0] a, b, c;
output [7:0] x, y;
`ASSERT(name_A,
!$onehot(a) && |b && x[0] |=> y != $past(y, 2),
clk, resetn
)
Python
class Foo(m.Circuit):
io = m.IO(a=m.In(m.Bits[8]), b=m.In(m.Bits[8]), c=m.In(m.Bits[8]),
x=m.Out(m.Bits[8]), y=m.Out(m.Bits[8]))
io += m.ClockIO(has_resetn=True)
# NOTE: Circuit code should appear above or else referring to output values
# (e.g. `io.y.value()` won't work)
# sva syntax
f.assert_(
f.sva(f.not_(f.onehot(io.a)), "&&",
io.b.reduce_or(), "&&",
io.x[0].value(), "|=>",
io.y.value() != f.past(io.y.value(), 2)),
name="name_A",
on=f.posedge(io.CLK),
disable_iff=f.not_(io.RESETN)
)
# infix syntax
f.assert_(
# Note parens matter!
(f.not_(f.onehot(io.a)) & io.b.reduce_or() & io.x[0].value())
| f.implies | f.delay[1] |
(io.y != f.past(io.y.value(), 2)),
name="name_A",
on=f.posedge(io.CLK),
disable_iff=f.not_(io.RESETN)
)
Example 2
Verilog
input valid, sop, eop;
output ready;
`ASSERT(eop_must_happen_btn_two_sop_A,
not(!(valid && ready && eop) throughout ((valid && ready && sop)[->2])),
clk, resetn
)
`ASSERT(first_valid_after_eop_must_have_sop_A,
(valid && ready && eop) ##1 (!valid)[*0:$] ##1 valid |-> sop,
clk, resetn
)
Python
class Foo(m.Circuit):
io = m.IO(valid=m.In(m.Bit), sop=m.In(m.Bit), eop=m.In(m.Bit),
ready=m.Out(m.Bit)) + m.ClockIO(has_resetn=True)
# NOTE: Circuit code should appear above or else referring to output values
# (e.g. `io.ready.value()` won't work)
# sva syntax
f.assert_(
f.sva(f.not_(~(io.valid & io.ready.value() & io.eop)),
"throughout",
# Note: need sequence here to wrap parens
f.sequence(f.sva((io.valid & io.ready.value() & io.sop),
"[-> 2]"))),
name="eop_must_happen_btn_two_sop_A",
on=f.posedge(io.CLK),
disable_iff=f.not_(io.RESETN)
)
f.assert_(
f.sva(io.valid & io.ready.value() & io.eop, "##1",
~io.valid, "[*0:$] ##1", io.valid, "|->", io.sop),
name="first_valid_after_eop_must_have_sop_A",
on=f.posedge(io.CLK),
disable_iff=f.not_(io.RESETN)
)
# infix syntax (note parens matter based on python precedence)
f.assert_(
f.not_(~(io.valid & io.ready.value() & io.eop))
| f.throughout |
((io.valid & io.ready.value() & io.sop) | f.goto[2]),
name="eop_must_happen_btn_two_sop_A",
on=f.posedge(io.CLK),
disable_iff=f.not_(io.RESETN)
)
f.assert_(
(io.valid & io.ready.value() & io.eop) | f.delay[1] |
(~io.valid) | f.repeat[0:] | f.delay[1] |
(io.valid | f.implies | io.sop),
name="first_valid_after_eop_must_have_sop_A",
on=f.posedge(io.CLK),
disable_iff=f.not_(io.RESETN)
)
Countones Example
Verilog
`COVER(cover_name_C, read_valid && ($countones(read_array) == iter_req),
clk, rstn)
Python
class Foo(m.Circuit):
io = m.IO(
read_valid=m.In(m.Bit),
read_array=m.In(m.Bits[8]),
iter_req=m.In(m.Bits[2])) + m.ClockIO(has_resetn=True)
f.cover(io.read_valid & (f.countones(io.read_array) == io.iter_req),
on=f.posedge(io.CLK), disable_iff=f.not_(io.RESETN),
name="cover_name_C")