Skip to content

[RFC][Sim] Add declarations and reads for externally controlled configuration bits#10552

Open
nanjo712 wants to merge 1 commit into
llvm:mainfrom
nanjo712:feat/sim-config
Open

[RFC][Sim] Add declarations and reads for externally controlled configuration bits#10552
nanjo712 wants to merge 1 commit into
llvm:mainfrom
nanjo712:feat/sim-config

Conversation

@nanjo712
Copy link
Copy Markdown
Contributor

I think we need a mechanism to replace some of the functionality of the SV macros in FIRRTLToHW. From my observation, STOP_COND and PRINTF_COND behave like configurations that allow external injection, and I want to introduce this abstraction into the Sim dialect.

I've implemented a preliminary approach to naming and designing this Op.

@circt-bot
Copy link
Copy Markdown

circt-bot Bot commented May 27, 2026

Results of circt-tests run for 88f6df3 compared to results for e2cbd2d: no change to test results.

@uenoku
Copy link
Copy Markdown
Member

uenoku commented May 28, 2026

These STOP_COND/PRINTF_COND macros were poor replacements for $asserton/$assertoff. I would love to remove them entirely today, but I cannot because our internal setup depends on them.

However, we should avoid introducing this technical debt into the Sim dialect. I see two better alternatives:

  • Introduce meta printf/assertion control operations (e.g., assertionon/off). As a compromise, I think we could also add printfoff/on even though it's not defined in SV spec

  • Add proper probe support, since these macros typically point to testbench registers.

@nanjo712
Copy link
Copy Markdown
Contributor Author

I think I understand the assertion-control direction better now, but I want to clarify one point.

In the current FIRRTLToHW lowering, STOP_COND_ is not just a static switch. It is materialized as an expression and combined with the lowered stop condition:

  %exitCond = STOP_COND_ & %cond
  sim.clocked_terminate %clock, %exitCond

So the existing mechanism allows the macro to expand to a runtime testbench signal, e.g. tb.stop_enable.
My understanding is that $asserton/$assertoff would not be a drop-in replacement for this lowering. It would only work if we changed the lowering model and represented stops as assertion-like checks, for example by lowering a non-zero-exit stop to something like:

  assert (!cond) else $fatal;

Then the external runtime control would come from the simulator's assertion control state, not from an injected i1 value.
Is that the direction you have in mind? If so, should this only apply to failure stops, or do you also expect zero-exit stops currently lowered to $finish to be modeled through assertion control?

@nanjo712
Copy link
Copy Markdown
Contributor Author

nanjo712 commented May 28, 2026

I think there are three separate concepts here:

  • assertion on/off, for assertion checking;
  • printf on/off, for simulation printing;
  • terminate/stop on/off, for simulation termination.

For STOP_COND_, a semantic terminate/stop control operation, possibly driven by a proper probe/testbench-value mechanism, seems closer than assertion control. Does that match what you had in mind, or do you expect STOP_COND_ to only apply to assertion-like failure stops?

Supplement: I am not sure printf on/off or terminate on/off captures the current behavior better. The existing mechanism is not a stateful operation that changes simulator mode. It is an expression-level predicate that is combined with each individual print/terminate condition.

@nanjo712 nanjo712 changed the title [Sim] Add declarations and reads for externally controlled configuration bits [RFC][Sim] Add declarations and reads for externally controlled configuration bits May 28, 2026
@nanjo712
Copy link
Copy Markdown
Contributor Author

Hi @fzi-hielscher, I'd be interested to hear your thoughts on modeling runtime configuration within the context of Sim dialects. I would greatly appreciate seeing your perspective on this matter.

@uenoku
Copy link
Copy Markdown
Member

uenoku commented May 31, 2026

Sorry for the late reply. I might be more comfortable if this was model was global variable (c.f. llhd.global_signal), not as config. Lowering them to some arc runtime function (for arcilator) or some ABI-defined macro (SV) is certainly ok. WDYT?

@fzi-hielscher
Copy link
Copy Markdown
Contributor

I like your idea of providing global registers/variables for simulation that are not tied to a specific simulator mechanism. But the first thing we need to determine is when and how we want to be able to change their value. I can think of three variants:

  1. The values are provided from outside of the simulation and never change
  2. The values can be set from within the simulation (either directly in the MLIR model or by a testbench) but changes only apply at the next time step.
  3. The values can be set from within the model and change immediately.
    Each of these variants may merit its own set of operations in the core dialects. SV macros can be used for all of that, but that's also what makes them such a pain to deal with.

The way I'm interpreting the operations you have described here, you have been targeting variant (1), right? But apparently the SiFive use-case for the STOP_COND/PRINTF_COND macros requires (2). (3) would require us to make both the get (load) and set (store) operations procedural, otherwise we couldn't reason about their order.

In the case of (2), I agree with @uenoku that it would be even more useful if we had something like probes, allowing us to manipulate generic signals and not only simulation specific ones.

@uenoku
Copy link
Copy Markdown
Member

uenoku commented Jun 1, 2026

But apparently the SiFive use-case for the STOP_COND/PRINTF_COND macros requires (2).

Yes, basically SiFive uses something like STOP_COND=!TestBench.reset_phase which basically disables assertion/logging during top reset phase. This is something we can replace with top-level probe ports, which currently doesn't exist in core dialect.

(3) would require us to make both the get (load) and set (store) operations procedural, otherwise we couldn't reason about their order.

Yeah I generally oppose something like global variable but I also wonder we need something that can be lowered from llhd.global_signal, that certainly have clock/enable operands, maybe ..?

@nanjo712
Copy link
Copy Markdown
Contributor Author

nanjo712 commented Jun 2, 2026

Thanks, this discussion makes me realize that my initial model was too narrow.

My original mental model was that STOP_COND / PRINTF_COND were externally provided values that stay unchanged during the simulation. But the current macro mechanism can clearly do more than that: the macro may expand to an expression over testbench/harness signals, e.g. something like !TestBench.reset_phase && TestBench.done_reset.

With that in mind, I agree that a probe-like mechanism seems to be the better lower-level abstraction. It makes the problem somewhat larger, but I think it is probably the right layer to solve this.

One possible direction could be something like:

hw.hierpath @path_to_reset_phase ...
hw.hierpath @path_to_done_reset ...

sim.global_signal @STOP_COND : i1 {
  %reset_phase = hw.probe.read @path_to_reset_phase : i1
  %done_reset  = hw.probe.read @path_to_done_reset  : i1

  %true = hw.constant true
  %not_reset_phase = comb.xor %reset_phase, %true : i1
  %stop_cond = comb.and %not_reset_phase, %done_reset : i1

  sim.yield %stop_cond : i1
}

%stop_cond = sim.global_signal.read @STOP_COND : i1
%exit_cond = comb.and %stop_cond, %local_cond : i1
sim.clocked_terminate %clock, %exit_cond {success = ...}

Here, hw.probe.read would reference a signal through a hw.hierpath and materialize the current value of that signal as an SSA value in the current context. This is similar in spirit to what sv.xmr.ref provides for the SV backend, but the intent would be to keep the Core-level abstraction backend-independent. For SV, one possible lowering would be to lower hw.probe.read to sv.xmr.ref.

I would initially keep this read-only. Extending this to writes / force / release seems to introduce significantly more complicated same-time-step ordering and mutation semantics, and I do not think that is necessary for the STOP_COND / PRINTF_COND use case.

For sim.global_signal, my current thought is that it should not be a mutable storage object. It should be closer to a named, globally visible runtime expression. A sim.global_signal.read would evaluate/materialize that expression at each read site. The expression body should be side-effect-free and should not introduce sequential/procedural semantics; it would be built from probe reads, constants, and pure combinational operations.

This would structurally model the current macro behavior when the macro expands to an expression over probe-able testbench/harness signals. For compatibility, the SV backend could still lower unresolved or ABI-defined hooks to the existing macro path where needed, but the structured probe/global-expression form seems like the cleaner Core-level abstraction.

Look forward to your feedback, thanks!

@fzi-hielscher
Copy link
Copy Markdown
Contributor

That makes sense to me. I suppose sim.global_signal would then be conceptionally similar to a llvm.func with LinkOnce linkage, i.e., its body can be externally overridden.

It is probably not what @uenoku had in mind, though? I've never really looked into how FIRRTL probes work, so I don't know if they can provide a better solution here.

@uenoku
Copy link
Copy Markdown
Member

uenoku commented Jun 5, 2026

I think sim.global_signal looks good for representing global variables in Sim (as long as @fzi-hielscher agrees). However, I still think FIRRTL's SSA-based representation works better for Arc probes. I'd prefer to stick with sim.global_signal for now and avoid widening the scope to include hierarchical path-based probe operations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants