Skip to content

Mark primitives OPAQUE and update to clash-prelude-1.10 - #14

Open
felipebalbi wants to merge 2 commits into
standardsemiconductor:mainfrom
felipebalbi:opaque-primitives-and-newer-bounds
Open

Mark primitives OPAQUE and update to clash-prelude-1.10#14
felipebalbi wants to merge 2 commits into
standardsemiconductor:mainfrom
felipebalbi:opaque-primitives-and-newer-bounds

Conversation

@felipebalbi

Copy link
Copy Markdown

Mark primitives OPAQUE on GHC ≥ 9.4, and support clash-prelude 1.10 / GHC 9.10

Two independent changes, one commit each, so you can take either on its own.

1. NOINLINEOPAQUE (2d85eac)

Clash 1.10 warns on every primitive this package exports:

Warning: primitive Ice40.Rgb.rgbPrim isn't marked OPAQUE.
This might make Clash ignore this primitive.

clash-lib's isOpaque accepts only Opaque, so all twelve primitives trip it —
Spram, Spi, Rgb, Pll.Pad, Pll.Core, Osc (×2), Mac.Prim, Led, IO,
I2c, GB — not just the one a given design happens to use.

GHC 9.4 added OPAQUE for exactly this case. NOINLINE prevents inlining but still
lets the simplifier rewrite a binding in ways that can break blackbox matching;
OPAQUE also disables those transformations. For a primitive whose whole contract is
"match this name and emit the template", that is the stronger and more accurate
statement of intent.

It can't be silenced downstream. -fclash-no-prim-warn doesn't cover it —
clash-ghc emits this one through an unguarded warnIf in GenerateBindings.hs,
while opt_primWarn only guards "Dubious primitive instantiation" in clash-lib's
Netlist/BlackBox.hs. Confirmed by reading both sources and by running with the flag.

Why CPP. CI builds GHC 9.0, which has no OPAQUE, so each pragma is guarded and
9.0 keeps NOINLINE. That isn't a downgrade — on a GHC without OPAQUE there's
nothing stricter available.

2. Dependency bounds (6cdec17)

base < 4.20< 4.21, clash-prelude < 1.9< 1.11. Nothing in the package
actually breaks on the newer versions; the bounds were simply the binding constraint.

Granularity follows this package's own precedent: 1.6.1 was admitted with < 1.7 and
1.8 with < 1.9, so 1.10 gets < 1.11.

CI gains GHC 9.8 and 9.10 so the new bounds are tested rather than asserted — an
untested upper bound is how the previous one went stale, and I'd rather not repeat
that. 9.0 and 9.4 still resolve older clash-prelude, so the lower bounds are
untouched.

Verification

On GHC 9.10.3 / clash-prelude 1.10.0:

Check Result
Library build, -Wall -Wcompat, all 13 modules clean, zero warnings
Resolves without allow-newer yes, after commit 2
Warning on a design instantiating SB_RGBA_DRV gone
Generated Verilog, before vs after byte-identical, md5 dc4bb4b391125dda03c4f6097b670c85

That last row is the important one: this changes what GHC is permitted to do, not
what Clash emits.

Notes

  • Version left at 0.3.1.4, with the CHANGELOG entry under ## Unreleased — release
    timing is yours to decide. Happy to fold it into a 0.3.1.5 heading instead.
  • CI runner is ubuntu-20.04, which GitHub has retired, so the workflow may not be
    starting at all today regardless of this PR. I left it alone as out of scope, but say
    the word and I'll bump it to ubuntu-latest here.
  • cabal check reports missing upper bounds on ghc-typelits-{natnormalise,extra,knownnat}.
    Pre-existing, untouched.

Clash 1.10 warns on every primitive it loads from this package:

    Warning: primitive Ice40.Rgb.rgbPrim isn't marked OPAQUE.
    This might make Clash ignore this primitive.

GHC 9.4 introduced OPAQUE precisely for this case: NOINLINE stops inlining
but still permits the simplifier to rewrite a binding in ways that can break
blackbox matching, whereas OPAQUE also disables those transformations.
clash-lib's isOpaque accepts only Opaque, so NOINLINE trips the warning for
all twelve primitives here, not just the RGB one.

The warning cannot be silenced from the user's side. -fclash-no-prim-warn
does not cover it: clash-ghc emits it via an unguarded warnIf in
GenerateBindings.hs, and opt_primWarn only guards "Dubious primitive
instantiation" in clash-lib's Netlist/BlackBox.hs.

CI builds GHC 9.0, which has no OPAQUE, so each pragma is CPP-guarded and
9.0 keeps NOINLINE. That is not a downgrade: on a GHC without OPAQUE there
is nothing stricter to ask for.

Verified on GHC 9.10.3 / Clash 1.10.0 against a design that instantiates
SB_RGBA_DRV: the warning is gone and the generated Verilog is byte-identical
before and after (md5 dc4bb4b391125dda03c4f6097b670c85), so this changes
what GHC is permitted to do, not what Clash emits.
The upper bounds stopped at clash-prelude < 1.9 and base < 4.20, so the
package would not resolve against Clash 1.10 on GHC 9.10. Nothing in it
actually breaks: with the bounds relaxed the library builds clean under
-Wall -Wcompat with no warnings, and the emitted Verilog is unchanged.

Bounds follow the granularity this package already uses -- 1.6.1 was
admitted with < 1.7 and 1.8 with < 1.9, so 1.10 gets < 1.11. base < 4.21
covers GHC 9.10.

CI gains 9.8 and 9.10 so these bounds are tested rather than asserted;
claiming an untested upper bound is how the previous one went stale. 9.0
and 9.4 still resolve older clash-prelude, which is why the lower bounds
are untouched.

Verified on GHC 9.10.3 / clash-prelude 1.10.0.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant