[LLHD] Deseq: handle signals with multiple drivers soundly#10793
Open
AmurG wants to merge 1 commit into
Open
Conversation
Deseq replaces each fed conditional drive with an unconditional (continuous) drive of a seq.firreg result. That is only sound when the analyzed process is the signal's sole driver: a variable written by several processes takes the last write per write event in the source semantics, and the continuous register drive clobbers every other process's write on the next delta. The ubiquitous casualty is the bench idiom initial begin read = 1; ... end // one-shot t=0 init always @(posedge clk) read <= ...; // edge updates where the initial block's t=0 value is silently lost: on current main, --llhd-deseq promotes the edge-driven process to a preset-less `seq.firreg` plus an unconditional epsilon-delay drive while the init process's t=0 constant drive is left in place, so the register drive clobbers the init on the next delta and the signal reads the power-on zero until the first clock edge. Classify the extra drivers instead of promoting blindly: - A drive that provably executes exactly once at t=0 with a constant value -- unconditional, zero-time delay, located in the entry block of another process (runs in that process's first activation before any wait, has no predecessors, cannot re-execute) -- folds into the promoted register's preset: the continuous register drive then reproduces the constant from t=0 onward, equivalent to the initial block's write for every t >= 0 observation. - Any other extra driver (non-constant value, conditional, delayed, repeated, module-level continuous, or in llhd.final) keeps the genuinely-correct process form. Folding the one-shot init rather than also bailing on it matters beyond the lost init itself: in mixed form, sibling registers on the same clock that do promote commit their state at clock evaluation, before kept processes resume, so a kept process observes post-edge values of same-clock registers -- a one-delta lead against preponed sampling that breaks lockstep pipelines. The preset fold keeps the promoted form for the common idiom and reserves the process form for genuine multi-driver interleavings. deseq.mlir gains @MultiDrivenInitFoldsToPreset (checks the folded preset value), @MultiDrivenSignalKeepsProcess (non-constant init value), and @MultiDrivenDelayedInitKeepsProcess (delayed init write). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
30877eb to
f12929b
Compare
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.
llhd-deseqpromotes a fed conditional drive to aseq.firregplus an unconditional continuous drive even when the analyzed process is not the signal's only driver. A signal written by several processes takes the last write per write event, so the register drive clobbers every other process's write on the next delta. The common casualty is the bench idiomwhere the initial block's value is silently lost: the register holds its power-on zero until the first clock edge.
Reproducer on current main (circt-opt --llhd-deseq)
Output on current main: the init process survives, but the promoted register has no preset and its unconditional drive clobbers the init on the next delta.
The fix classifies extra drivers instead of promoting blindly. A drive that provably executes exactly once at t=0 with a constant value (unconditional, zero delay, in the entry block of another process, no predecessors, cannot re-execute) folds into the promoted register's preset, which reproduces the constant from t=0 onward. Any other extra driver (non-constant, conditional, delayed, repeated, module-level continuous, or in
llhd.final) keeps the process form.Folding rather than always bailing matters: in mixed form, promoted registers on the same clock commit at clock evaluation before kept processes resume, so a kept process observes post-edge values of same-clock registers, a one-delta lead that breaks lockstep pipelines. We measured exactly that regression on two SVA local-variable benches under the bare guard, before adding the preset fold.
Tested:
deseq.mlirgains@MultiDrivenInitFoldsToPreset,@MultiDrivenSignalKeepsProcess, and@MultiDrivenDelayedInitKeepsProcess.Written with AI assistance; I've reviewed the diff and tests.