Open NikLeberg opened 3 weeks ago
Same behaviour with just a simple reset:
module foo(
input wire reset,
input wire clk
);
initial begin
assume(reset);
end
always @(posedge clk) begin
assert(reset);
end
endmodule
This is the intended behavior for an assert in an always @(posedge clk)
block as the effects of such a block affect the following cycle. You can use asserts in an always @*
block if you want an assertion to trigger immediately and not with the next clock edge.
You can also use chformal -assert -early
to move assertions that are on the output of an FF like this early.
Having said this it has never been clear to me why Yosys does add a flop to the assertion for this case, it seems to me it should act as a blocking assignment in a clocked block does and trigger at the point that it is reached.
16.3 in the LRM (1800-2023) says
The immediate assertion statement is a test of an expression performed when the statement is executed in the procedural code. The expression is nontemporal and is interpreted the same way as an expression in the condition of a procedural if statement. In other words, if the expression evaluates to x, z, or 0, then it is interpreted as being false, and the assertion statement is said to fail. Otherwise, the expression is interpreted as being true, and the assertion statement is said to pass or, equivalently, to succeed.
There are two modes of immediate assertions, simple immediate assertions and deferred immediate assertions. In a simple immediate assertion, pass and fail actions take place immediately upon assertion evaluation.
Which to me suggests that really it should be evaluated at that point rather than flopped. This would bring the semantics in line with simulation where the assertion would be evaluated at the earlier point too
But it does trigger at the point it is reached, if you remove one full time step from the traces in this issue report, there would be no rising clock edge left for which the assertion fails. The reason the trace looks different from a simulation trace is that a single FV step corresponds to a full cycle consisting of the clock's rising edge, it being high for half a period, a falling edge and it being low for half a period. In particular a FV step does not include a final rising edge.
If the assertion wouldn't be flopped, including when running chformal -assert -early
, you can get incorrect results, as the assertion then triggers even when there is no clock edge for the duration of which the assert condition is false. The easiest way to observe this is with the multiclock on
SBY option corresponding to the clk2fflogic
yosys pass, which changes a FV step to consist of optional arbitrary clock edges followed by being stable for one period. With this you can have FV steps without clock edges but where the condition of the assertion changes multiple times in the middle of a clock cycle. (In multiclock mode the emitted trace would look slightly different, but still be longer than for an event based simulation but only by half a clock cycle, i.e. you would have the rising clock edge that triggers the assertion followed by the high period, but no falling clock edge.)
I think the main UX issue is that event based simulations exit as soon as the assertion is violated and with that they can end on a clock edge while FV traces always show full steps with signals changing only at the start of the step (with exception of the global clock in single clock mode which falls in the middle of the step to be able to rise again at the start of the next step).
One solution would be to always shorten the last emitted FV step in traces to only include any signal edges at the start, but not any following stable period. This would match what you get from event based simulation, that aborts during an assertion, but also makes it harder to see those final signal edges.
I think a better approach would be to partially shorten the last emitted FV step in the trace, so it doesn't include the implicit falling global clock edge, but still the period where the global clock is high making it easy to read the post-rising-clock-edge signal values, and then also include an event for each assertion in the trace that shows the time when an assertion is violated. This would also be useful if you use the option to append further steps after a failing assertion or for cover traces where multiple covers might be reached at different times.
Ahh yes I see your point about multiclock. It seems like this is an issue because the $assert
etc cells don't store a trigger event so by adding a flop you are synchronising them to the flop's clock I guess. I don't see why this couldn't be avoided in the general case but I do get why it isn't feasible with these current cells (I had also thought that clk2fflogic
worked by feeding FFs the global clock gated by free-running enables for each real clock, rather than by detecting rising edges in free-running clocks).
I would agree about those UX solutions (having an event for assertion failure in particular is nice I think), although perhaps even just having clearer documentation on this being a thing would be enough.
With the given rationale, this if fine for me. I'd like it to be documented somewhere. Albeit having this issue here makes it discoverable for others with the same observation.
For me this could be closed. Feel free to do so.
Even with this working as currently intended, I think this behavior is confusing enough for new users that I'll keep this issue open until we addressed this in some way. This might end up being better documentation, but I think there is likely more that can be done to improve the overall user experience.
Version
Yosys 0.41 (git sha1 c1ad37779, g++ 13.2.0-23ubuntu4 -fPIC -Os)
On which OS did this happen?
Linux
Reproduction Steps
Have SystemVerilog module
foo.sv
:and try to run formal verification with sby script
foo.sby
:Run the script with
sby -f foo.sby bmc2
Expected Behavior
The unconstrained internal signal
a
is in the first clock (initial
block) assumed to be1
. In this initial clock 0, theassume
holds. In subsequent steps, theassume
is no longer valid (as it's only an initial). The solver can choose a diverent value fora
because it is unconstrained withanyseq
.So I expect the formal verification to fail at clock step 1 where the solver could choose the value
0
.Actual Behavior
The verification (when only ran for two steps
bmc depth 2
does not fail.`sby -f foo.sby bmc2` output:
``` sby -f foo.sby bmc2 SBY 11:33:33 [foo_bmc2] Copy '/foo.sv' to '/foo_bmc2/src/foo.sv'. SBY 11:33:33 [foo_bmc2] engine_0: smtbmc --progress yices SBY 11:33:33 [foo_bmc2] base: starting process "cd foo_bmc2/src; yosys -ql ../model/design.log ../model/design.ys" SBY 11:33:33 [foo_bmc2] base: finished (returncode=0) SBY 11:33:33 [foo_bmc2] prep: starting process "cd foo_bmc2/model; yosys -ql design_prep.log design_prep.ys" SBY 11:33:33 [foo_bmc2] prep: finished (returncode=0) SBY 11:33:33 [foo_bmc2] smt2: starting process "cd foo_bmc2/model; yosys -ql design_smt2.log design_smt2.ys" SBY 11:33:33 [foo_bmc2] smt2: finished (returncode=0) SBY 11:33:33 [foo_bmc2] engine_0: starting process "cd foo_bmc2; yosys-smtbmc -s yices --presat --unroll -t 2 --append 0 --dump-vcd engine_0/trace.vcd --dump-yw engine_0/trace.yw --dump-vlogtb engine_0/trace_tb.v --dump-smtc engine_0/trace.smtc model/design_smt2.smt2" SBY 11:33:33 [foo_bmc2] engine_0: ## 0:00:00 Solver: yices SBY 11:33:33 [foo_bmc2] engine_0: ## 0:00:00 Checking assumptions in step 0.. SBY 11:33:33 [foo_bmc2] engine_0: ## 0:00:00 Checking assertions in step 0.. SBY 11:33:33 [foo_bmc2] engine_0: ## 0:00:00 Checking assumptions in step 1.. SBY 11:33:33 [foo_bmc2] engine_0: ## 0:00:00 Checking assertions in step 1.. SBY 11:33:33 [foo_bmc2] engine_0: ## 0:00:00 Status: passed SBY 11:33:33 [foo_bmc2] engine_0: finished (returncode=0) SBY 11:33:33 [foo_bmc2] engine_0: Status returned by engine: pass SBY 11:33:33 [foo_bmc2] summary: Elapsed clock time [H:MM:SS (secs)]: 0:00:00 (0) SBY 11:33:33 [foo_bmc2] summary: Elapsed process time [H:MM:SS (secs)]: 0:00:00 (0) SBY 11:33:33 [foo_bmc2] summary: engine_0 (smtbmc --progress yices) returned pass SBY 11:33:33 [foo_bmc2] summary: engine_0 did not produce any traces SBY 11:33:33 [foo_bmc2] DONE (PASS, rc=0) ```When running for three steps, it fails as expected.
`sby -f foo.sby bmc3` output:
``` SBY 11:38:45 [foo_bmc3] Copy '/foo.sv' to '/foo_bmc3/src/foo.sv'. SBY 11:38:46 [foo_bmc3] engine_0: smtbmc --progress yices SBY 11:38:46 [foo_bmc3] base: starting process "cd foo_bmc3/src; yosys -ql ../model/design.log ../model/design.ys" SBY 11:38:46 [foo_bmc3] base: finished (returncode=0) SBY 11:38:46 [foo_bmc3] prep: starting process "cd foo_bmc3/model; yosys -ql design_prep.log design_prep.ys" SBY 11:38:46 [foo_bmc3] prep: finished (returncode=0) SBY 11:38:46 [foo_bmc3] smt2: starting process "cd foo_bmc3/model; yosys -ql design_smt2.log design_smt2.ys" SBY 11:38:46 [foo_bmc3] smt2: finished (returncode=0) SBY 11:38:46 [foo_bmc3] engine_0: starting process "cd foo_bmc3; yosys-smtbmc -s yices --presat --unroll -t 3 --append 0 --dump-vcd engine_0/trace.vcd --dump-yw engine_0/trace.yw --dump-vlogtb engine_0/trace_tb.v --dump-smtc engine_0/trace.smtc model/design_smt2.smt2" SBY 11:38:47 [foo_bmc3] engine_0: ## 0:00:00 Solver: yices SBY 11:38:47 [foo_bmc3] engine_0: ## 0:00:00 Checking assumptions in step 0.. SBY 11:38:47 [foo_bmc3] engine_0: ## 0:00:00 Checking assertions in step 0.. SBY 11:38:47 [foo_bmc3] engine_0: ## 0:00:00 Checking assumptions in step 1.. SBY 11:38:47 [foo_bmc3] engine_0: ## 0:00:00 Checking assertions in step 1.. SBY 11:38:47 [foo_bmc3] engine_0: ## 0:00:00 Checking assumptions in step 2.. SBY 11:38:47 [foo_bmc3] engine_0: ## 0:00:00 Checking assertions in step 2.. SBY 11:38:47 [foo_bmc3] engine_0: ## 0:00:00 BMC failed! SBY 11:38:47 [foo_bmc3] engine_0: ## 0:00:00 Assert failed in foo: foo.sv:12.5-12.22 (_witness_.check_assert_foo_sv_12_3) SBY 11:38:47 [foo_bmc3] engine_0: ## 0:00:00 Writing trace to VCD file: engine_0/trace.vcd SBY 11:38:47 [foo_bmc3] engine_0: ## 0:00:00 Writing trace to Verilog testbench: engine_0/trace_tb.v SBY 11:38:47 [foo_bmc3] engine_0: ## 0:00:00 Writing trace to constraints file: engine_0/trace.smtc SBY 11:38:47 [foo_bmc3] engine_0: ## 0:00:00 Writing trace to Yosys witness file: engine_0/trace.yw SBY 11:38:47 [foo_bmc3] engine_0: ## 0:00:00 Status: failed SBY 11:38:47 [foo_bmc3] engine_0: finished (returncode=1) SBY 11:38:47 [foo_bmc3] engine_0: Status returned by engine: FAIL SBY 11:38:47 [foo_bmc3] summary: Elapsed clock time [H:MM:SS (secs)]: 0:00:01 (1) SBY 11:38:47 [foo_bmc3] summary: Elapsed process time [H:MM:SS (secs)]: 0:00:00 (0) SBY 11:38:47 [foo_bmc3] summary: engine_0 (smtbmc --progress yices) returned FAIL SBY 11:38:47 [foo_bmc3] summary: counterexample trace: foo_bmc3/engine_0/trace.vcd SBY 11:38:47 [foo_bmc3] summary: failed assertion foo._witness_.check_assert_foo_sv_12_3 at foo.sv:12.5-12.22 in step 2 SBY 11:38:47 [foo_bmc3] DONE (FAIL, rc=2) ```The produced trace![image](https://github.com/YosysHQ/yosys/assets/39563554/09e1b211-6cce-4999-87d9-f9765e7247bd)
trace.vcd
seems to hint IMHO that it could trigger the assert one clock earlier. But it doesnt.An equivalent testbench in VHDL+PSL, ran with the ghdl plugin does stop as expected at clock step 1.