Skip to content

[topgen] Add support for split IPs - #31220

Open
etterli wants to merge 14 commits into
lowRISC:masterfrom
etterli:topgen-split-ip
Open

etterli wants to merge 14 commits into
lowRISC:masterfrom
etterli:topgen-split-ip

Conversation

@etterli

@etterli etterli commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

This builds on #31219 and adds the required topgen extensions.

@etterli
etterli force-pushed the topgen-split-ip branch 7 times, most recently from 3d62602 to dd6dd0c Compare September 8, 2026 12:23

@andreaskurth andreaskurth 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.

Thanks @etterli and @glaserf, this is a nicely structured extension! 👍

Minor changes requested; nothing fundamental.

I checked this two ways. All three in-tree tops regenerate identically with this branch, so there is no regression on the non-split path. Then I made aon_timer a synthetic split IP (partitioned clocking, one interrupt and one extra alert in the secondary partition, an intra_p2s req/rcv pair, and secondary- and both-scoped exposed parameters) and instantiated it in top_earlgrey with domain: Main / domain_secondary: Aon. That works impressively well end to end: the alert slices and LpgMap entries land in the right per-domain buses and LPGs (LPG 5 = io_div4_timers + lc_io_div4[Main] for the primary, LPG 14 = aon_timers + lc_aon[Aon] for the secondary), the intra-IP signal is routed out of earlgrey_pd_main into earlgrey_pd_aon over a top-level net, TL/RACL/scan stay primary-only, and the domain == domain_secondary variant collapses to a local wire as documented. The comments below all come out of that experiment.

Comment thread util/topgen/templates/tb__xbar_connect.sv.tpl
Comment thread util/topgen/merge.py Outdated
# its own clock group and power domain. It need not be clocked at all,
# in which case there is nothing to elaborate for it.
if lib.secondary_key('clock_srcs') in ep:
ep[lib.secondary_key('clock_group')] = ep.get(

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.

Defaulting clock_group_secondary here happens before validate_top runs, which I think makes the matching check in check_power_domains unreachable: by the time validate.py:1354 asks whether clock_group_secondary exists, this line has already created it.

The consequence is that a split instance written as clock_group: "timers" rather than clock_group: {primary: ..., secondary: ...} does not get the intended error; it silently generates the secondary partition into the secure group. I reproduced this: .clk_aon_i(clkmgr_clocks.clk_aon_secure) instead of clk_aon_timers, exit code 0.

Would it be better to default this only for a non-split instance, and let validation reject a missing clock_group_secondary?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point. I've now added a proper validation of the secondary clocking information that it is consistent. If secondary clock_srcs is defined, the secondary clock_group and domain keys must also exist.

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.

The upfront consistency check does the job: the config that previously generated clk_aon_secure silently is now rejected.

Small wording point on one of the messages: for a split instance that writes clock_group: "timers" (a plain string rather than a per-partition dict) the error is "defines a secondary domain but no primary information is specified for clock_group". That reads as if the primary entry were missing, when the real problem is that the key is not partitioned at all. Would something like "clock_group is not specified per partition" be clearer?

Comment thread util/topgen/validate.py
Comment thread util/topgen/lib.py
Comment thread util/topgen/README.md Outdated
Comment thread util/topgen/intermodule.py Outdated
Comment thread util/topgen/intermodule.py

@etterli etterli left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks @andreaskurth for the detailed review. Good catches. I've now addressed the first comments. Some fixes required also one additional reggen commit. Will continue tomorrow.

I refrained from rebasing on the merged reggen commits so the diff is simpler to compare.

Comment thread util/topgen/README.md Outdated
Comment thread util/topgen/intermodule.py Outdated
Comment thread util/topgen/templates/tb__xbar_connect.sv.tpl
Comment thread util/topgen/intermodule.py
Comment thread util/topgen/lib.py
@etterli
etterli force-pushed the topgen-split-ip branch 2 times, most recently from a9cadbd to 0d58118 Compare September 9, 2026 15:31

@etterli etterli left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've now also addressed the remaining feedback. These points gave raise to some additional improvements/changes.

  • Power domains are now validated before any of its information is used.
    • This resulted in autogen changes. The domain attribute is now emitted earlier. Otherwise there are no functional changes to the autogen files.
  • A split IP's secondary partition must always be clocked.
  • Intra-IP connections support req_rsp.
  • CIO signals are emitted per partition for split IPs instead of for all partitions.
  • Fixed a trailing comma issue in module instantiation for a split IP's secondary partition

The first push updates the changes. The 2nd one is a rebase on the current master.

Comment thread util/topgen/merge.py Outdated
# its own clock group and power domain. It need not be clocked at all,
# in which case there is nothing to elaborate for it.
if lib.secondary_key('clock_srcs') in ep:
ep[lib.secondary_key('clock_group')] = ep.get(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point. I've now added a proper validation of the secondary clocking information that it is consistent. If secondary clock_srcs is defined, the secondary clock_group and domain keys must also exist.

Comment thread util/topgen/validate.py
andreaskurth
andreaskurth previously approved these changes Sep 10, 2026

@andreaskurth andreaskurth 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.

Thanks @etterli! Every point from the last review is addressed, and you found and fixed two things I had missed (the CIO declarations and the clkmgr idle hints). Making the secondary partition always clocked is a good simplification.

Two small blockers, both CI lint failures on the current head, plus one doc slip and two nits. Everything else LGTM, so approving because CI will tell when lint passes.

Comment thread util/reggen/ip_block.py Outdated
Comment thread util/reggen/README.md
Comment thread util/topgen/validate.py
Comment thread util/topgen/merge.py Outdated
# its own clock group and power domain. It need not be clocked at all,
# in which case there is nothing to elaborate for it.
if lib.secondary_key('clock_srcs') in ep:
ep[lib.secondary_key('clock_group')] = ep.get(

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.

The upfront consistency check does the job: the config that previously generated clk_aon_secure silently is now rejected.

Small wording point on one of the messages: for a split instance that writes clock_group: "timers" (a plain string rather than a per-partition dict) the error is "defines a secondary domain but no primary information is specified for clock_group". That reads as if the primary entry were missing, when the real problem is that the key is not partitioned at all. Would something like "clock_group is not specified per partition" be clearer?

Comment thread util/topgen/README.md Outdated
Comment thread util/topgen/intermodule.py Outdated

@etterli etterli left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks again for the review. I've now fixed it and I think this is ready to merge.

However, as the AST split already surfaced some required changes I leave this PR as draft for now. I will merge it once the AST split draft is more mature.

Comment thread util/reggen/ip_block.py Outdated
Comment thread util/reggen/README.md
Comment thread util/topgen/intermodule.py Outdated
Comment thread util/topgen/validate.py
@etterli
etterli marked this pull request as ready for review September 14, 2026 14:39
@etterli etterli added the CI:Rerun Rerun failed CI jobs label Sep 14, 2026
@github-actions github-actions Bot removed the CI:Rerun Rerun failed CI jobs label Sep 14, 2026
@etterli etterli added the CI:Rerun Rerun failed CI jobs label Sep 14, 2026
@github-actions github-actions Bot removed the CI:Rerun Rerun failed CI jobs label Sep 14, 2026
@etterli etterli added the CI:Rerun Rerun failed CI jobs label Sep 14, 2026
@github-actions github-actions Bot removed the CI:Rerun Rerun failed CI jobs label Sep 14, 2026
@etterli etterli added the CI:Rerun Rerun failed CI jobs label Sep 15, 2026
@github-actions github-actions Bot removed the CI:Rerun Rerun failed CI jobs label Sep 15, 2026
@etterli
etterli marked this pull request as draft September 22, 2026 14:47
@etterli
etterli force-pushed the topgen-split-ip branch 2 times, most recently from 1693eb1 to 84246e8 Compare September 23, 2026 08:52
@etterli

etterli commented Sep 23, 2026

Copy link
Copy Markdown
Contributor Author

The last pushes contains the new topgen changes implemented during the AST split integration work. I don't expect further changes.

The main new changes are (last 4 commits):

  • Support internal clock sources
  • Extend external clock handling such that these are properly wired down to the receiving power domains.
  • Add a template to support the memory configuration struct assignments inside the power domains instead of at chip level
  • Make xbar connections clock aware.

@etterli

etterli commented Sep 23, 2026

Copy link
Copy Markdown
Contributor Author

@andreaskurth PTAL

@etterli
etterli marked this pull request as ready for review September 23, 2026 08:54
@etterli
etterli requested a review from a team as a code owner September 23, 2026 08:54
@etterli
etterli requested review from rswarbrick and removed request for a team September 23, 2026 08:54
@etterli
etterli removed the request for review from rswarbrick September 23, 2026 09:47
etterli and others added 14 commits September 24, 2026 11:25
A wakeup or reset-request is wired to pwrmgr through an inter-module signal of the same name. For a
split IP this reference is resolved per partition, so reggen now requires the wakeup or
reset-request to be declared in the same partition as its inter-module signal. This rejects a
mismatch early instead of letting it surface as an unresolved inter-module reference in topgen.

Also require a split IP to always declare clockings for its secondary partition.

Signed-off-by: Pascal Etterli <pascal.etterli@lowrisc.org>
… connections

This was not possible with the planned topgen changes. But the new implementation will allow this.

Signed-off-by: Pascal Etterli <pascal.etterli@lowrisc.org>
The inter-module array-width check compared req/rsp widths that may be Parameter objects rather than
plain ints. This factors the width resolution into a helper and resolves the requester width as
well, such that the req/rsp array comparison always operates on integers.

Signed-off-by: Pascal Etterli <pascal.etterli@lowrisc.org>
A split IP can have intra-IP connections which directly connect the two partitions. However, in the
config a split IP is a single instance. So any regular inter-module signal would have the same start
and end point, which is not supported by the current topgen.

This commit thus introduces an intra-IP signal handling which uniquifies inter-module signals per
partition. It does so by adding a partition qualifier to each inter-module signal of a split IP.
This allows the regular inter-module signal handling to connect the two partitions properly.

It also enables the partitions to use the same signal name, otherwise these would have to be unique.

Non-split IPs use no qualifier.

Co-authored-by: Florian Glaser <glaserf@lowrisc.org>
Signed-off-by: Pascal Etterli <pascal.etterli@lowrisc.org>
Wakeup, reset-request and idle-hint connections to pwrmgr/clkmgr were currently generated as
<module>.<signal> references. For a split IP, Inter-module names are only unique per partition. This
would give an ambigious signal choice. This commit adds logic to qualify these references with the
signal's partition. Non-split IPs stay unqualified.

Signed-off-by: Pascal Etterli <pascal.etterli@lowrisc.org>
Extends the hjson validator to handle the additional keys for split IPs.

This prepares topgen to handle split IPs. topgen will emit additional keys for partitioned / split
IPs. This commits adds the required code to validate such a configuration. It checks the secondary
keys as well as that the split is configured properly.

Co-authored-by: Florian Glaser <glaserf@lowrisc.org>
Signed-off-by: Pascal Etterli <pascal.etterli@lowrisc.org>
A split IP has nested dictionaries for certain keys. This flattens such a configuration by emitting
keys for the secondary partition. It also adapts all the elaboration logic such that it can handle
multiple partitions. It also validates that any secondary partition has proper clocking information.

Co-authored-by: Florian Glaser <glaserf@lowrisc.org>
Signed-off-by: Pascal Etterli <pascal.etterli@lowrisc.org>
topgen uses certain power domain names before the actual validation code is run. This can lead to
unexpected crashes especially when handling split IPs.

This commit moves the domain validation before any of the domain information is used. This is
possible as it is completely top dependend and does not require knowledge about the actual IPs.

A consequence of this is that the domain key is emitted earlier as before in the autogen hjson
files. Otherwise there is no functional change.

Signed-off-by: Pascal Etterli <pascal.etterli@lowrisc.org>
This commit finally adds the full support for split IPs.

A split IP consists of two partitions which can be instantiated in different power domains. topgen
then automatically resolves the inter-module connections as well as intra-IP connections.

All registers (TL-UL) stay in the primary partition. So do RACL and scan/DFT ports.

Co-authored-by: Florian Glaser <glaserf@lowrisc.org>
Signed-off-by: Pascal Etterli <pascal.etterli@lowrisc.org>
Adds some basic documentation how to integrate a split IP into a top.

Signed-off-by: Pascal Etterli <pascal.etterli@lowrisc.org>
This extends the templates such that external clocks are properly wired down to the receiving power
domains.

Signed-off-by: Pascal Etterli <pascal.etterli@lowrisc.org>
This extends topgen such that clocks can be sourced inside a partition/domain. Previously, clocks
would have to be provided from outside a top. This now allows a clock to be inside a partition which
then can be distributed to all domains/partitons.

It has also implications on DFT signals. These are originating inside the top instead of being
provided externally.

Signed-off-by: Pascal Etterli <pascal.etterli@lowrisc.org>
This adds a small template which connects memory configurations from a unified struct to signals
connecting to the consumers. It can be used inside a power domain to generate the local memory
configuration wiring required to connect to the AST partitions.

Signed-off-by: Pascal Etterli <pascal.etterli@lowrisc.org>
Previously, the xbar drivers assumed that each IP drives it xbar logic with clk_i. This commit
generalized this such that the IP's primary clock is used.

This is required for the AST split.

Signed-off-by: Pascal Etterli <pascal.etterli@lowrisc.org>

@andreaskurth andreaskurth 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.

Thanks @etterli. The split-IP part stays in good shape. This review is for the four commits added after my previous review & approval.

Major: [topgen] Support clock sources inside the top crashes for what looks like its main use case; see the comment on merge.py. An alert-bearing module whose primary clock is sourced inside the top hits AttributeError: 'ModuleClockRef' object has no attribute 'split'. CI does not catch it because no in-tree top uses an internal clock source, so every new path here is dormant.

Couple of other comments below.

Two process points, both worth your judgement rather than mine:

  • The four new commits are not split-IP work. [topgen] Support clock sources inside the top in particular is a feature of its own, with a new top-hjson schema and its own failure mode. Splitting it into a separate PR would make it easier to review now and easier to bisect later; it would also let the split-IP work land on its own.
  • One nit from round 2 is still open: the "no primary information is specified for clock_group" message in _pre_validate_split_ip.

Comment thread util/topgen/merge.py
clock_group = None
else:
# Discover what clock group we are related to.
clock_group = clock_groups[clk.split(".")[-1]]

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.

This dereferences the raw clock_connections value, which may still be a ModuleClockRef placeholder rather than a net-name string.

create_alert_lpgs runs at topgen.py:1890, but the placeholders are only replaced by resolve_module_clocks at topgen.py:1904. So for any alert-bearing module whose primary clock is sourced inside the top, _make_lpg_entry reaches this line with a ModuleClockRef and topgen dies:

File "util/topgen/merge.py", line 1122, in _make_lpg_entry
    clock_group = clock_groups[clk.split(".")[-1]]
AttributeError: 'ModuleClockRef' object has no attribute 'split'

I reproduced this on top_earlgrey by giving io_div4 module: "rv_dm", signal: "ndmreset_req" and putting aon_timer into the ast group (src: "ext"). The unmanaged-clock loop just above is also comparing a string against the placeholder, which silently never matches.

Since alert senders sit on the block's primary clock, I think this hits any AST-domain IP with alerts that takes an internally sourced clock. Could resolve_module_clocks run before create_alert_lpgs, or could the LPG code resolve the reference itself? If neither is easy, would it be worth raising a clear error here when the value is still a ModuleClockRef, so it fails with an explanation rather than an AttributeError?

<%
# cfg type kind -> (req struct type, rsp struct type)
mem_cfg_types = {
'1p': ('prim_ram_1p_pkg::ram_1p_cfg_req_t', 'prim_ram_1p_pkg::ram_1p_cfg_rsp_t'),

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.

Nothing in the repo includes this template. So it is never rendered, and neither the Python nor the generated SystemVerilog gets any coverage from CI. Would it be better to land it together with the AST-split change that uses it?

Two things inside it while it is unused, in case they get missed later:

  • mem_cfg_types is defined but never referenced, and the loop unpacks kind and width without using either; only field and wire are used.
  • mem_cfg_lhs_pad calls max() over mem_cfg_consumers, which raises ValueError on an empty list. A guard or a default= would make an empty consumer list a no-op instead.

Comment thread util/topgen/clocks.py

# Capture the producing module and its inter-signal, in case this clock is generated by an
# IP instance inside the top.
self.module = raw.get('module', None)

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.

These three keys (module, signal, partition on a clock source) are new top-hjson schema, but I could not find them documented anywhere; util/topgen/README.md gained a "Split IPs" section for that feature, and there is no equivalent for this one.

Could the README get a short section covering how a clock source is declared as internally sourced, the constraint that the referenced signal must be a uni/req/logic inter-signal (which check_module_sourced_clocks enforces), and the requirement that a same-power-domain producer be listed in inter_module.top (which _resolve_module_clock raises on)? Those last two are discoverable only by hitting the errors.

module = lib.find_module_by_name(top["module"] + top["xbar"], inst_name)
power_domain = module.get("domain").lower()
## Extract the port name of the clock driving the xbar of the module. This is not available for
## xbar nodes.These nodes also never reach the DRIVE_CHIP_TL_{HOST,DEVICE}_IF branches below.

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.

Missing space after the period: "for xbar nodes.These nodes also never reach ..."

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.

2 participants