From 2d85eacd0d062f6064beaaeadf4314449aeee84c Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Sun, 2 Aug 2026 10:56:33 -0700 Subject: [PATCH 1/2] Mark primitives OPAQUE on GHC >= 9.4 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. --- CHANGELOG.md | 5 +++++ src/Ice40/GB.hs | 5 +++++ src/Ice40/I2c.hs | 5 +++++ src/Ice40/IO.hs | 5 +++++ src/Ice40/Led.hs | 5 +++++ src/Ice40/Mac/Prim.hs | 5 +++++ src/Ice40/Osc.hs | 9 +++++++++ src/Ice40/Pll/Core.hs | 5 +++++ src/Ice40/Pll/Pad.hs | 5 +++++ src/Ice40/Rgb.hs | 5 +++++ src/Ice40/Spi.hs | 5 +++++ src/Ice40/Spram.hs | 5 +++++ 12 files changed, 64 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a5258c..743875d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Revision history for ice40-prim +## Unreleased +* Mark primitives `OPAQUE` rather than `NOINLINE` on GHC >= 9.4. Clash 1.10 + warns that a primitive marked `NOINLINE` "might make Clash ignore this + primitive". Guarded by CPP, so GHC 9.0 keeps `NOINLINE`. + ## 0.3.1.4 -- 2024-01-15 * Update dependency bounds: clash-prelude-1.8 diff --git a/src/Ice40/GB.hs b/src/Ice40/GB.hs index bb4d1f1..ff7029c 100644 --- a/src/Ice40/GB.hs +++ b/src/Ice40/GB.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-| Module : Ice40.GB Description : Ice40 Global Buffer IP primtive @@ -36,6 +37,10 @@ import Data.String.Interpolate.Util (unindent) |]) #-} -- | Global buffer primitive +#if __GLASGOW_HASKELL__ >= 904 +{-# OPAQUE gbPrim #-} +#else {-# NOINLINE gbPrim #-} +#endif gbPrim :: Clock dom -> Clock dom gbPrim !clk = clk diff --git a/src/Ice40/I2c.hs b/src/Ice40/I2c.hs index 6f6d4cc..0d5bdd9 100644 --- a/src/Ice40/I2c.hs +++ b/src/Ice40/I2c.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-| Module : Ice40.I2c Description : Ice40 I2C hard IP primitive @@ -135,7 +136,11 @@ import Data.String.Interpolate.Util (unindent) ] |]) #-} +#if __GLASGOW_HASKELL__ >= 904 +{-# OPAQUE i2cPrim #-} +#else {-# NOINLINE i2cPrim #-} +#endif i2cPrim :: String -- ARG[0] initAddr -> String -- ARG[1] busAddr diff --git a/src/Ice40/IO.hs b/src/Ice40/IO.hs index b3cea01..494f311 100644 --- a/src/Ice40/IO.hs +++ b/src/Ice40/IO.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-| Module : Ice40.IO Description : Ice40 IO hard IP primitives @@ -67,7 +68,11 @@ import Data.String.Interpolate.Util (unindent) |]) #-} -- | IO primitive, see io for wrapper +#if __GLASGOW_HASKELL__ >= 904 +{-# OPAQUE ioPrim #-} +#else {-# NOINLINE ioPrim #-} +#endif ioPrim :: BitVector 6 -- ^ pinType -> Bit -- ^ pullup diff --git a/src/Ice40/Led.hs b/src/Ice40/Led.hs index 55f9de9..c204322 100644 --- a/src/Ice40/Led.hs +++ b/src/Ice40/Led.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-| Module : Ice40.Led Description : LED Ice40 hard IP primitive @@ -79,7 +80,11 @@ import Data.String.Interpolate.Util (unindent) ] |]) #-} +#if __GLASGOW_HASKELL__ >= 904 +{-# OPAQUE ledPrim #-} +#else {-# NOINLINE ledPrim #-} +#endif ledPrim :: Signal dom Bit -- ARG[0] leddcs - CS to write LEDD IP registers -> Clock dom -- ARG[1] leddclk - Clock to write LEDD IP registers diff --git a/src/Ice40/Mac/Prim.hs b/src/Ice40/Mac/Prim.hs index 3f1903b..eba6897 100644 --- a/src/Ice40/Mac/Prim.hs +++ b/src/Ice40/Mac/Prim.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-| Module : Ice40.Mac.Prim Description : Ice40 Multiply-Accumulate (DSP) hard IP primitive @@ -134,7 +135,11 @@ import Data.String.Interpolate.Util (unindent) |]) #-} -- | Multiply-Accumulate primitive +#if __GLASGOW_HASKELL__ >= 904 +{-# OPAQUE macPrim #-} +#else {-# NOINLINE macPrim #-} +#endif macPrim :: Bit -- ^ negTrigger -> Bit -- ^ aReg diff --git a/src/Ice40/Osc.hs b/src/Ice40/Osc.hs index 373a7ee..4ad1e64 100644 --- a/src/Ice40/Osc.hs +++ b/src/Ice40/Osc.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-| Module : Ice40.Osc Description : Ice40 oscillator hard IP primitives @@ -49,7 +50,11 @@ import Ice40.Clock |]) #-} -- | Low frequency oscillator 10 kHz +#if __GLASGOW_HASKELL__ >= 904 +{-# OPAQUE lf10kHz #-} +#else {-# NOINLINE lf10kHz #-} +#endif lf10kHz :: Signal dom Bool -- ^ CLKLFPU - Power up the LFOSC circuit. After power up, oscillator output will be stable after 100us. Active High. -> Signal dom Bool -- ^ CLKLFEN - Enable the clock output. Enable should be low for the 100us power up period. Active High. @@ -86,7 +91,11 @@ lf10kHz !_ !_ = Clock SSymbol Nothing ] |]) #-} +#if __GLASGOW_HASKELL__ >= 904 +{-# OPAQUE hfPrim #-} +#else {-# NOINLINE hfPrim #-} +#endif hfPrim :: KnownDomain dom -- ARG[0] => KnownDomain dom' -- ARG[1] diff --git a/src/Ice40/Pll/Core.hs b/src/Ice40/Pll/Core.hs index e45240b..be62790 100644 --- a/src/Ice40/Pll/Core.hs +++ b/src/Ice40/Pll/Core.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-| Module : Ice40.Pll.Core Description : Ice40 PLL hard IP primitive @@ -76,7 +77,11 @@ import Data.String.Interpolate.Util (unindent) |]) #-} -- | PLL Core primitive +#if __GLASGOW_HASKELL__ >= 904 +{-# OPAQUE pllCorePrim #-} +#else {-# NOINLINE pllCorePrim #-} +#endif pllCorePrim :: KnownDomain dom' -- ARG[0] => BitVector 7 -- ^ divf diff --git a/src/Ice40/Pll/Pad.hs b/src/Ice40/Pll/Pad.hs index b66380f..1b00351 100644 --- a/src/Ice40/Pll/Pad.hs +++ b/src/Ice40/Pll/Pad.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-| Module : Ice40.Pll.Pad Description : Ice40 PLL Pad hard IP primitive @@ -77,7 +78,11 @@ import Data.String.Interpolate.Util (unindent) |]) #-} -- | PLL Pad primitive +#if __GLASGOW_HASKELL__ >= 904 +{-# OPAQUE pllPadPrim #-} +#else {-# NOINLINE pllPadPrim #-} +#endif pllPadPrim :: KnownDomain dom' -- ARG[0] => BitVector 7 -- ^ divf diff --git a/src/Ice40/Rgb.hs b/src/Ice40/Rgb.hs index cd1b62b..de5a66f 100644 --- a/src/Ice40/Rgb.hs +++ b/src/Ice40/Rgb.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-| Module : Ice40.Rgb Description : Ice40 RGB hard IP primitive @@ -88,7 +89,11 @@ import Data.String.Interpolate.Util (unindent) -- +-----------------------+-------------------+-------------------+ -- | "0b111111" | 24mA | 12mA | -- +-----------------------+-------------------+-------------------+ +#if __GLASGOW_HASKELL__ >= 904 +{-# OPAQUE rgbPrim #-} +#else {-# NOINLINE rgbPrim #-} +#endif rgbPrim :: String -- ^ currentMode - Parameter values: "0b0" = Full Current Mode (Default), "0b1" = Half Current Mode. -> String -- ^ rgb0Current diff --git a/src/Ice40/Spi.hs b/src/Ice40/Spi.hs index aaa544f..cdc5bb3 100644 --- a/src/Ice40/Spi.hs +++ b/src/Ice40/Spi.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-| Module : Ice40.Spi Description : Ice40 SPI hard IP primitive @@ -227,7 +228,11 @@ bitAt n = fmap (! n) ] |]) #-} +#if __GLASGOW_HASKELL__ >= 904 +{-# OPAQUE spiPrim #-} +#else {-# NOINLINE spiPrim #-} +#endif spiPrim :: String -- ARG[0] busAddr -> Clock dom -- ARG[1] sbclki diff --git a/src/Ice40/Spram.hs b/src/Ice40/Spram.hs index 6986b14..70e2741 100644 --- a/src/Ice40/Spram.hs +++ b/src/Ice40/Spram.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-| Module : Ice40.Spram Description : Ice40 single-port RAM hard IP primitive @@ -54,7 +55,11 @@ import Data.String.Interpolate.Util (unindent) data Nyb = Nyb3 | Nyb2 | Nyb1 | Nyb0 -- | Single port RAM primitive +#if __GLASGOW_HASKELL__ >= 904 +{-# OPAQUE spramPrim #-} +#else {-# NOINLINE spramPrim #-} +#endif spramPrim :: KnownDomain dom -- ARG[0] => Clock dom -- ^ clock From 6cdec1714f65b00f91dc05e7add2b5285a63d306 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Sun, 2 Aug 2026 10:57:48 -0700 Subject: [PATCH 2/2] Update dependency bounds: clash-prelude-1.10, GHC 9.10 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. --- .github/workflows/haskell.yml | 2 +- CHANGELOG.md | 4 ++++ ice40-prim.cabal | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/haskell.yml b/.github/workflows/haskell.yml index 49efa69..12a3122 100644 --- a/.github/workflows/haskell.yml +++ b/.github/workflows/haskell.yml @@ -14,7 +14,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - ghc: ['9.0', '9.4', '9.6'] + ghc: ['9.0', '9.4', '9.6', '9.8', '9.10'] os: [ubuntu-20.04] name: ${{ matrix.os }} GHC ${{ matrix.ghc }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 743875d..680d6d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ * Mark primitives `OPAQUE` rather than `NOINLINE` on GHC >= 9.4. Clash 1.10 warns that a primitive marked `NOINLINE` "might make Clash ignore this primitive". Guarded by CPP, so GHC 9.0 keeps `NOINLINE`. +* Update dependency bounds: clash-prelude-1.10, GHC 9.10 + * `clash-prelude >= 1.2.5 && < 1.11` + * `base >= 4.12 && < 4.21` +* Test GHC 9.8 and 9.10 in CI ## 0.3.1.4 -- 2024-01-15 * Update dependency bounds: clash-prelude-1.8 diff --git a/ice40-prim.cabal b/ice40-prim.cabal index d60d055..a2af1c5 100644 --- a/ice40-prim.cabal +++ b/ice40-prim.cabal @@ -32,9 +32,9 @@ library Ice40.Spi, Ice40.Spram hs-source-dirs: src - build-depends: base >= 4.12 && < 4.20, + build-depends: base >= 4.12 && < 4.21, interpolate >= 0.2 && < 0.3, - clash-prelude >= 1.2.5 && < 1.9, + clash-prelude >= 1.2.5 && < 1.11, ghc-typelits-natnormalise, ghc-typelits-extra, ghc-typelits-knownnat