Verilog: fix the constant-expression interpreter for the conditional operator and zero-width replication - #2090
Draft
kroening wants to merge 2 commits into
Draft
Verilog: fix the constant-expression interpreter for the conditional operator and zero-width replication#2090kroening wants to merge 2 commits into
kroening wants to merge 2 commits into
Conversation
Per 1800-2017 11.4.11, the conditional operator evaluates the first expression when the condition is true, and the second expression when the condition is false. Both operands are evaluated, and their results merged, only when the condition is ambiguous. The constant-expression interpreter evaluated both operands unconditionally, and hence rejected the usual idiom for guarding a width computation against a degenerate parameter value: wire [(0 ? 1/0 : 3)-1:0] x; This now gives "expected constant expression, but got `(0 ? 1 / 0 : 3) - 1'" no longer. Note that the type checker rewrites the four-valued case, i.e., the case of an ambiguous condition, before the interpreter is given the expression, and hence only the two-valued case needs to be short-circuited here. This fixes two of the KNOWNBUG tests added in #2086.
Per 1800-2017 11.4.12.1, a replication with a zero replication constant is
legal inside a concatenation that has at least one operand of nonzero size;
its size is zero, and it contributes nothing to the result. The net
declaration
wire [3:0] x = {2'b10, {0{1'b1}}, 2'b01};
was accepted, but the very same expression in a constant context
localparam [3:0] p = {2'b10, {0{1'b1}}, 2'b01};
gave "expected constant expression". The constant-expression interpreter
lowered the zero replication to an empty concatenation, which is neither a
constant nor something the simplifier can fold; the enclosing concatenation
was hence given up on as well.
Zero-width operands of a concatenation are now removed before the operands
are simplified, and a zero replication constant yields a zero-width
constant rather than an empty concatenation.
Furthermore, a replication with a zero replication constant is now rejected
unless it appears directly within a concatenation, and a concatenation is
required to have at least one operand of nonzero size, both as mandated by
11.4.12.1. Previously, a standalone zero replication was accepted in
non-constant contexts, and gave a confusing "expected constant expression"
in constant contexts.
This fixes the remaining KNOWNBUG test added in #2086.
tautschnig
approved these changes
Aug 12, 2026
Comment on lines
+213
to
+217
| exprt::operandst ops; | ||
| ops.reserve(times); | ||
| for(std::size_t i = 0; i < times; i++) | ||
| ops.push_back(replication.op()); | ||
| expr = concatenation_exprt{ops, expr.type()}; |
Collaborator
There was a problem hiding this comment.
Suggested change
| exprt::operandst ops; | |
| ops.reserve(times); | |
| for(std::size_t i = 0; i < times; i++) | |
| ops.push_back(replication.op()); | |
| expr = concatenation_exprt{ops, expr.type()}; | |
| exprt::operandst ops{times, replication.op()}; | |
| expr = concatenation_exprt{std::move(ops), expr.type()}; |
kroening
marked this pull request as draft
August 12, 2026 14:50
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the three KNOWNBUG tests added in #2086.
1. The conditional operator is not short-circuited (1800-2017 11.4.11)
Root cause.
verilog_simplifier_rec(src/verilog/verilog_simplifier.cpp), whichimplements the constant-expression interpreter, recursed into all operands of an
expression and gave up as soon as any operand failed to simplify to a constant. For
ID_if, that meant both arms were evaluated even when the condition was known. TheCBMC simplifier deliberately refuses to fold a division by zero, so
failed with
expected constant expression, but got `(0 ? 1 / 0 : 3) - 1'. This isthe usual idiom for guarding a width computation against a degenerate parameter value.
Fix.
verilog_simplifier_recnow simplifies the condition of anID_iffirst, and,if the condition is a definite
true/false, returns the simplified taken arm withouttouching the other one — exactly what 11.4.11 mandates.
Short-circuiting vs. x-propagation
#2086 notes that a fix could equally come from propagating
xout of a division byzero (11.4.4). I chose short-circuiting, for three reasons:
class of guarded width computations, not just the division-by-zero instance: e.g.
0 ? f(...) : 3or0 ? p[i] : 3with an out-of-rangeiare guarded too, and noamount of x-propagation would help there.
today — not just in the constant-expression interpreter.
wire [3:0] y = 4'd1/4'd0;currently synthesises to
0, not4'bxxxx. Making 11.4.4 work would mean touchingthe aval/bval encoding and synthesis as well, which is a separate, larger change,
and it would still leave 11.4.11 unimplemented.
what may be a two-state constant expression, which the conditional operator would
then have to merge back.
So the two are complementary rather than alternatives; this PR does the 11.4.11 half.
11.4.4 remains unimplemented and is called out as a limitation below.
2. Zero-width replication is not a constant expression (1800-2017 11.4.12.1)
Root cause.
verilog_simplifier_reclowered a replication to a concatenation oftimescopies of the operand. Fortimes == 0that produced an emptyconcatenation, which is neither a constant nor something the simplifier can fold, so
the enclosing concatenation was given up on as well. Hence
(As an aside,
simplify_concatenationcomputesoperands().size() - 1unsigned, sofeeding it an empty concatenation was undefined behaviour.)
Fix.
simplified, since per 11.4.12.1 they contribute nothing. (An empty concatenation is
never created.)
instead of an empty concatenation.
3. Zero replication outside a concatenation is now diagnosed properly
11.4.12.1 only permits a zero replication constant inside a concatenation that has
at least one operand of nonzero size. Before this PR, a standalone
wire [3:0] x = {0{1'b1}};was silently accepted, while the same in a constantcontext gave a confusing "expected constant expression" (with an ireprinted
zero-width concatenation in the message).
convert_replication_exprnow takes azero_allowedflag, set only for operands thatappear directly within a concatenation, and
convert_expr_concatenationrejects aconcatenation whose total width is zero. New tests
replication5andreplication6cover the two error cases.
Tests
regression/verilog/expressions/conditional_operator_short_circuit{1,2}.descandreplication4.descflipped fromKNOWNBUGtoCORE;replication4asserts theresulting value of both
pandx. Two new error tests added.Verified locally (Clang, macOS, MiniSat + Z3 4.16.0):
make -C regression/verilog test— All tests were successful, 220 tests skippedmake -C regression/verilog test-z3— All tests were successful, 227 tests skippedmake -C regression/ebmc test— All tests were successful, 9 tests skippedmake -C regression/ebmc test-z3— All tests were successful, 12 tests skippedmake -C regression/smv test— All tests were successful, 32 tests skippedmake -C regression/vlindex test— All tests were successfulmake -C unit— All tests passed (190 assertions in 45 test cases)Known limitations
x) is still not implemented, in neitherthe constant-expression interpreter nor synthesis. A bare
localparam p = 1/0;stillreports "expected constant expression" rather than elaborating to
x.operator with a four-valued result is rewritten into the aval/bval encoding by
verilog_loweringbefore the interpreter sees it, so both arms are still elaboratedin that case — which is what 11.4.11 asks for when the condition is ambiguous, but
is more conservative than necessary when a four-valued condition happens to be
unambiguous.