Skip to content

[FIRRTL] Add RWProbe force/release synthesis to ProbesToSignals#10803

Open
prithayan wants to merge 1 commit into
dev/pbarua/gatedClockConversionfrom
dev/pbarua/forceReleaseProbesToSignals
Open

[FIRRTL] Add RWProbe force/release synthesis to ProbesToSignals#10803
prithayan wants to merge 1 commit into
dev/pbarua/gatedClockConversionfrom
dev/pbarua/forceReleaseProbesToSignals

Conversation

@prithayan

Copy link
Copy Markdown
Contributor

This PR extends the ProbesToSignals pass to support RWProbes with force/release operations.
ProbesToSignals now supports force/release synthesis by creating state machines that track when a signal is being overridden and what value it should hold.

High-Level Flow

Phase 0: Gated Clock Handling

If the force/release or the target register is running under a gated clock, perform gated clock conversion. It folds the clock gate enable into the operation and ensures force/release and its target register use the same free running clock.

Phase 1: Discovery and Type Conversion

Convert all probe types to their underlying hardware types and record the corresponding RWProbe ports and operations. Maintain the original program order for all force/release operations, because this defines priority (earlier operations have higher priority)

Phase 2: Access Reduction

For each target signal that has force/release operations:

  1. Build priority arbitration logic:

    • Create a "force wins" signal that encodes whether force or release should take effect
    • Walk through accesses in priority order, using muxes to decide force vs release
    • Higher priority accesses override lower priority ones
  2. Compute reduced control signals:

    • forceActive: OR together all force predicates, gated by "force wins"
    • releaseActive: OR together all release predicates, gated by "NOT force wins"
    • forcedValue: mux chain selecting the value from the highest-priority active force
    • clock: pick any available clock from clocked accesses (null if only initial accesses exist)

Phase 3: Categorization

The following 3 kinds of uses exist:

  1. Local targets: Force/release only happens within the same module where the target is declared
  2. Exported targets: The target is sent out through a module output port, so force control must merge local and external sources
  3. Instance-forwarded targets: The target comes from a child instance, so the parent needs to forward control signals down

Phase 4: State Machine Materialization

For Local Targets:

Input:

reg r;
rwprobe r_ref = rwprobe(r);
when (force_en)  { force(r_ref, value); }
when (release_en) { release(r_ref); }

Generated State Machine:

reg forced;        // 1-bit flag: is target currently forced?
reg forcedValue;   // captured forced value

// Priority arbitration: force wins over release
forceWins = force_en ? 1 : (release_en ? 0 : forceWins);
forcedValueMux = force_en ? value : forcedValue;

// Next-state logic
when (forceActive) {
  forced := 1;
  forcedValue := value;
} elsewhen (releaseActive) {
  forced := 0;
}

// Override mux at target
r := forced ? forcedValue : normalValue;

For Exported Targets:

  1. Create the state machine registers and control bundle wire locally (same as local targets)
  2. Add a new input port to the module named <portName>_force_ctrl carrying a bundle:
    • forceActive: combined force enables from parent and local
    • releaseActive: combined release enables from parent and local
    • forcedValue: the value to force (local wins over parent in priority)
    • clock: the clock to use for the state machine
  3. Merge local control with incoming parent control (local has higher priority)
  4. Drive the control bundle wire from this merged control
  5. The wire feeds the state machine registers
  6. For every instantiation of this module, add the matching input port and connect it

For Instance-Forwarded Targets:

  1. Create a control bundle wire in the parent module (no registers here - they're in the child)
  2. Drive this wire from the reduced local control signals computed in Phase 2
  3. Connect this wire to the child instance's force control input port
  4. The child module handles the actual state machine synthesis

Phase 5: Cross-Module Port Insertion

Mutation of module signatures and instance ports is done sequentially, after the previous in-parallel steps are over:

  1. For each module with exported forceable ports:

    • Insert the _force_ctrl input ports at the end of the port list
    • Connect these ports to the control bundle wires created earlier
  2. For each instance of such modules:

    • Clone the instance with additional ports matching the new module signature
    • Connect the new _force_ctrl input ports to the parent's forwarding wires
    • Replace the old instance with the new one

This PR is stacked on #10660, for the gated clock conversion utility.

Assisted-by: OpenCode:Opus-4.8

@prithayan prithayan requested review from dtzSiFive and removed request for darthscsi and seldridge July 9, 2026 15:32
@circt-bot

circt-bot Bot commented Jul 9, 2026

Copy link
Copy Markdown

Results of circt-tests run for 5c41521 compared to results for 7ce21f0: no change to test results.

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.

1 participant