Skip to content

[LSQ] Add VHDL emitter structure - #1051

Open
JasmijnB wants to merge 19 commits into
EPFL-LAP:mainfrom
JasmijnB:emitter-structure
Open

JasmijnB wants to merge 19 commits into
EPFL-LAP:mainfrom
JasmijnB:emitter-structure

Conversation

@JasmijnB

Copy link
Copy Markdown
Collaborator

Instead of directly pasting strings into the Op command, go through an Emitter class. This adds a layer of abstraction when adding LSQ code, allows for adding new emitters to add a new output language. In preparation of the full #914

The structure works as follows:

  • There is a superclass Emitter implementing functions such as add_assignment, int_to_bit etc. this superclass is then implemented by the VHDLEmitter
  • add_assignment takes a a Signal and a Statement, this statement is the superclass of a simple IR which contains objects such as a Bin, Val etc.
  • Now statements can be constructed using this IR, and the Statement class overrides some operators to make the code look nice. E.g. you can do Val(1) + Val(2) which evaluates to Bin(Val(1), BinOp.ADD, Val(2)
  • Then the emitter implements functions to transform these statements to actual strings. E.g. get_binop_str in VHDLEmitter then generates the actual string

This change might seem huge, this is because every single Op call has been rewritten, but looking at the actual code you will see that there are no structural code changes, just the change from Op to em.add_assignment(...). I've used claude to perform part of the rewrite against the current main as I couldn't cherry pick, and then checked this and the VHDL output.

Don't merge this branch yet before merging #1048 which it's based on, I'm just setting up the pull request ahead of time

JasmijnB and others added 7 commits September 12, 2026 14:36
The previous commit converted genWrapper to the shared signal classes but
left genWrapperSlave referencing an undefined `ctx`, and dropped the
explicit tab levels that OpTab used to take, so assignments inside the
clocked processes were emitted at the wrong indentation.

- Give genWrapperSlave its own VHDLContext, and flush portInitString
  after the IO declarations and signalInitString before "begin", matching
  genWrapper (both buffers are filled lazily by signalInit()).
- Raise the context indent around the Op() loops so the generated VHDL
  keeps the indentation OpTab produced.

Generated VHDL is byte-identical to before for master/slave wrappers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
VHDLLogicType, VHDLLogicVecType, VHDLLogicTypeArray and VHDLLogicVecTypeArray
were a second copy of Logic, LogicVec, LogicArray and LogicVecArray from
core_gen/signals.py, differing only in returning their declarations as strings
instead of buffering them in a VHDLContext, and in omitting the _i/_o port
suffixes. Now that the LSQ wrapper builds its signals through signals.py with
dyn_comp=True, nothing uses them.

Drop the four classes and OpTab (superseded by operators.assign.Op), along with
the now-unused `re` import and the stale re-exports in core_gen/__init__.py.
The remaining helper functions in utils.py are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
getNameWrite computed the dyn_comp-aware suffix and then passed the raw
f'_{i}' to Logic.getNameWrite anyway, so a dyn_comp LogicArray would have
written to <name>_0 where it reads from <name>. LogicVecArray.getNameWrite
already used the computed suffix; make LogicArray match.

Not reachable from the current wrapper code, which indexes these arrays
through __getitem__ rather than calling getNameWrite(i) directly, so the
generated VHDL is unchanged.

Also reformat the touched files with autopep8 --max-line-length 200, which
CI enforces on modified Python files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@JasmijnB JasmijnB changed the title Add VHDL emitter structure [LSQ] Add VHDL emitter structure Sep 15, 2026
@JasmijnB
JasmijnB force-pushed the emitter-structure branch 2 times, most recently from 8664d6a to d27b093 Compare October 15, 2026 09:35
JasmijnB and others added 8 commits October 15, 2026 11:36
Introduces the two pieces the generators will be rewritten against:

  - core_gen/ir.py: a small expression IR (Statement, Val, Bin, Un, Bit,
    WhenElse, CustomStatement) with Python operator overloads, so the
    generators can describe logic instead of formatting HDL text.

  - core_gen/emitters/: the abstract Emitter, which replaces VHDLContext
    as the code-generation context, plus VHDLEmitter, which renders the
    IR as VHDL. A second language only needs another Emitter subclass.

Both are unused at this point; the next commit ports the generators onto
them.

Taken from the lsq-verilog branch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRJyvC1t9ivZeRVp35SAn3
Rewrites the generators to build the IR and hand it to an Emitter,
rather than formatting VHDL strings inline. VHDLContext, its Op helper
and the unused cli.py go away; signals.py, the operators, the
generators, codegen.py and lsq-generator.py all now take an `em`.
Formatting of literals moves to the emitter too, so MaskLess, IntToBits
and Zero leave utils.py (mask_less / int_to_str on VHDLEmitter).

The generated VHDL is no longer byte-identical to what main emits. It is
structurally the same design, but the emitter drops the pass-through
wires that main emitted for disabled pipeline stages, names temporaries
differently, and formats literals, comments and instantiations its own
way.

Taken from the lsq-verilog branch, minus VerilogEmitter and the --hdl
flag, which follow separately. On top of that:

  - Carried forward the four main-side LSQ commits that lsq-verilog
    predates: the ldq_head_oh_pcomp pipeline register and the stricter
    IntToBits (EPFL-LAP#993), the single-group-allocation assertion (EPFL-LAP#1011), and
    the master-interface stall handling in the slave wrapper (EPFL-LAP#903).

  - Fixed a net -4 indent imbalance in genWrapperSlave that pinned every
    line after the first process to column 0.

  - add_comment and add_custom_statement now emit a blank line rather
    than a bare "-- " for an empty line, which is what the block
    separators in the operators were asking for.

  - Dropped LSQWrapper.lsq_wrapper_str, which nothing reads now that the
    emitter writes the file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRJyvC1t9ivZeRVp35SAn3
The three bugs all sat on paths the lsq-verilog branch does not exercise
(the slave wrapper and single-bit register init); each aborted generation
outright. Across a 7168-config sweep they accounted for every failure,
and with them fixed the sweep is clean on both sides.

  - VHDLEmitter.logic_reg_init called self.in_to_bits, which does not
    exist. Every scalar register initialiser hit it: 5376/7168 configs.

  - lsq.py passed a bare 0 to add_assignment where a Statement is
    expected, in the non-master write-request path.

  - The same path built when/else with misplaced parentheses, calling
    .else_() on the condition rather than on the value.

Emitter also only declared about half the methods the generators call on
it, so a new subclass would fail at run time rather than at the contract.
Added stubs for the remaining thirteen, and implemented print_custom_str
on VHDLEmitter, which CustomStatement._to_str had always called and no
emitter defined.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRJyvC1t9ivZeRVp35SAn3

This branch has not been deployed

No deployments
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