Skip to content

[Sim] Add sim.assoc_array.clear op - #10996

Open
okekayode wants to merge 1 commit into
mainfrom
dev/add-sim-clear-op
Open

[Sim] Add sim.assoc_array.clear op#10996
okekayode wants to merge 1 commit into
mainfrom
dev/add-sim-clear-op

Conversation

@okekayode

Copy link
Copy Markdown
Contributor

Adds sim.assoc_array.clear, which returns and empty associative array of the same type as its input. This work is part of ongoing work to add simulation hashtable support within CIRCT. An explicit operation is the cleaner choice here than pattern-matching a whole array store in the emitter because it gives the lowering a single anchor for the places a table gets emptied from (e.g. reset, a clear port).

@okekayode
okekayode requested a review from youngar August 15, 2026 00:27
@okekayode okekayode added the Simulator Involving the Sim dialect or other simulation concerns label Aug 15, 2026
@circt-bot

circt-bot Bot commented Aug 15, 2026

Copy link
Copy Markdown

Results of circt-tests run for 0ec9427 compared to results for 6a42084: no change to test results.

@fzi-hielscher

Copy link
Copy Markdown
Contributor

I'm afraid I don't quite understand the purpose of this operation. We're treating associative arrays (and other dynamic containers) as immutable values in the Sim dialect. So, what would be the benefit of
%0 = sim.assoc_array.clear %array : !sim.assoc_array<i64, i32>
over just
%0 = sim.assoc_array.empty : !sim.assoc_array<i64, i32>?

@okekayode

Copy link
Copy Markdown
Contributor Author

I'm afraid I don't quite understand the purpose of this operation. We're treating associative arrays (and other dynamic containers) as immutable values in the Sim dialect. So, what would be the benefit of %0 = sim.assoc_array.clear %array : !sim.assoc_array<i64, i32> over just %0 = sim.assoc_array.empty : !sim.assoc_array<i64, i32>?

sim.assoc_array.clear is effectively syntactic sugar instead of sim.assoc_array.empty at the right type.

I have listed a couple reasons for the addition (mainly for lowering and the convenience it will provide for later work):

  1. There is literally zero overhead, a trivial folder of clear(a) -> empty erases the distinction during the canonicalisation wherever its not needed, costing us pretty much nothing in optimisation passes.
  2. clear allows us to infer key and value types directly from its operands via AllTypesMatch, avoiding the need to specify the type parameters explicitly.
  3. Also I would like this intent to be legible in the IR, that clear provides a clear message that the table was emptied which gives the lowering ExportVerilog one anchor for places a table is emptied at (e.g, resets, clear ports etc) to lower to race-free non-blocking assignments. (MooreToCore drives empty into the signal so I feel via clear is just a tad bit more convenient here).

@fabianschuiki fabianschuiki left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @fzi-hielscher: this feels like it's just empty with an unused operand. I think what you're going for is the ability to emit a table.clear() in Verilog. If that's the case, you might be able to just emit table.clear() when you see an assignment of sim.assoc_array.empty to an assoc array sv.reg. The conversion from sim.assoc_array.* ops to the in-place mutation that SV provides feels like a pattern similar to MLIR's Bufferization pass. Not that we need to use that pass, but it might contain a few ideas worth stealing. They probably also have a pattern of turning an empty buffer into a memset on the allocated storage 🤔

@uenoku

uenoku commented Aug 20, 2026

Copy link
Copy Markdown
Member

I agree with @fzi-hielscher and @fabianschuiki. sim.assoc_array.clear doesn't look compose well in core dialect representation. I think it's reasonable addition for SV dialect though.

@okekayode

Copy link
Copy Markdown
Contributor Author

Thank you @fzi-hielscher @fabianschuiki @uenoku for the feedback on this. I'll abandon this PR/change. I think i'll branch off on @uenoku suggestion to have a dedicatd sv.clear/sv.assoc_clear op during the lowering. Which should give a clear synchronous anchor for whole table clearing behaviour desired.

So something like:

sv.always posedge %clk {
  sv.if %clear {
      sv.assoc_array_clear %tbl
}
}

emits lke:

always @(posedge clk) begin
 if (clear) 
  table <= my_table_empties
//deletes 
table.delete(...). // (when delete enabled)
//writes.  
table[key] <= valaue // (when write_enabled)

@uenoku

uenoku commented Aug 21, 2026

Copy link
Copy Markdown
Member

We might prefer straightforward representation for SV based on SV spec (sv.assoc_array.delete %array, (optional operand)) instead of clear.

@okekayode

Copy link
Copy Markdown
Contributor Author

We might prefer straightforward representation for SV based on SV spec (sv.assoc_array.delete %array, (optional operand)) instead of clear.

Thanks, i didn't catch that indexes were optional in the spec for that. That gives us a nice two-in-one operation!

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

Labels

Simulator Involving the Sim dialect or other simulation concerns

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants