diff --git a/changelog/2023-03-01T12_18_28+01_00_xilinx_clocking_wizard b/changelog/2023-03-01T12_18_28+01_00_xilinx_clocking_wizard new file mode 100644 index 0000000000..02d4cb674c --- /dev/null +++ b/changelog/2023-03-01T12_18_28+01_00_xilinx_clocking_wizard @@ -0,0 +1,3 @@ +CHANGED: Remove `Asynchronous` constraint from Xilinx `clockWizard` and `clockWizardDifferential`. Originally intended to signal that these functions react asynchronously to the incoming reset and that the outgoing lock signal is an asynchronous signal. Since synchronous resets are a subset of synchronous resets this constraint on the input is vacuous. The constraint on the lock output does not convey this information at all and is wrong. Note that it is still necessary to synchronize the lock output in your design. +FIXED: Fix Xilinx ClockGen primitives. The port names of the Xilinx ClockGen primitives are now lowercase. The type of the lock output of Xilinx `clockWizard` and `clockWizardDifferential` is now `Signal Bool` instead of `Enable`, which was not the correct type here as a circuit should be kept in reset while the clock is stabilizing. This also fixes a polarity mismatch between hardware (and HDL simulation) and Haskell simulation. Also loosens the domain on the outgoing `Signal Bool`. +ADD: Add Tcl generation of Xilinx `clockWizard` and `clockWizardDifferential`. This moves the responsibility of MMCM component generation from the user to `clashConnector.tcl`. diff --git a/clash-lib/clash-lib.cabal b/clash-lib/clash-lib.cabal index c3a5e6f5e6..9a1639a641 100644 --- a/clash-lib/clash-lib.cabal +++ b/clash-lib/clash-lib.cabal @@ -279,6 +279,7 @@ Library Clash.Primitives.Sized.Signed Clash.Primitives.Sized.Vector Clash.Primitives.Verification + Clash.Primitives.Xilinx.ClockGen Clash.Rewrite.Combinators Clash.Rewrite.Types diff --git a/clash-lib/prims/commonverilog/Clash_Xilinx_ClockGen.primitives.yaml b/clash-lib/prims/commonverilog/Clash_Xilinx_ClockGen.primitives.yaml index 8b07a868ed..fe7461b821 100644 --- a/clash-lib/prims/commonverilog/Clash_Xilinx_ClockGen.primitives.yaml +++ b/clash-lib/prims/commonverilog/Clash_Xilinx_ClockGen.primitives.yaml @@ -3,40 +3,52 @@ kind: Declaration type: |- clockWizard - :: ( KnownDomain domIn confIn -- ARG[0] - , KnownDomain domOut confOut ) -- ARG[1] - => SSymbol name -- ARG[2] - -> Clock pllIn -- ARG[3] - -> Reset pllIn -- ARG[4] - -> (Clock pllOut, Enable pllOut) + :: ( KnownDomain domIn -- ARG[0] + , KnownDomain domOut -- ARG[1] + , KnownDomain domPllLock ) -- ARG[2] + => SSymbol name -- ARG[3] + -> Clock pllIn -- ARG[4] + -> Reset pllIn -- ARG[5] + -> (Clock pllOut, Signal domPllLock Bool) template: |- // clockWizard begin - ~NAME[2] ~GENSYM[clockWizard_inst][2] - (.CLK_IN1 (~ARG[3]) - ,.RESET (~IF ~ISACTIVEHIGH[0] ~THEN ~ELSE ! ~FI ~ARG[4]) - ,.CLK_OUT1 (~RESULT[1]) - ,.LOCKED (~RESULT[0])); + ~NAME[3] ~GENSYM[clockWizard_inst][2] + (.clk_in1 (~ARG[4]) + ,.reset (~IF ~ISACTIVEHIGH[0] ~THEN ~ELSE ! ~FI ~ARG[5]) + ,.clk_out1 (~RESULT[1]) + ,.locked (~RESULT[0])); // clockWizard end + includes: + - name: clk_wiz + extension: clash.tcl + format: Haskell + templateFunction: Clash.Primitives.Xilinx.ClockGen.clockWizardTclTF workInfo: Always - BlackBox: name: Clash.Xilinx.ClockGen.clockWizardDifferential kind: Declaration type: |- clockWizardDifferential - :: ( KnownDomain domIn confIn -- ARG[0] - , KnownDomain domOut confOut ) -- ARG[1] - :: SSymbol name -- ARG[2] - -> Clock pllIn -- ARG[3] - -> Clock pllIn -- ARG[4] - -> Reset pllIn -- ARG[5] - -> (Clock pllOut, Enable pllOut) + :: ( KnownDomain domIn -- ARG[0] + , KnownDomain domOut -- ARG[1] + , KnownDomain domPllLock ) -- ARG[2] + :: SSymbol name -- ARG[3] + -> Clock pllIn -- ARG[4] + -> Clock pllIn -- ARG[5] + -> Reset pllIn -- ARG[6] + -> (Clock pllOut, Signal domPllLock Bool) template: |- // clockWizardDifferential begin - ~NAME[2] ~GENSYM[clockWizardDifferential_inst][2] - (.CLK_IN1_D_clk_n (~ARG[3]) - ,.CLK_IN1_D_clk_n (~ARG[4]) - ,.RESET (~IF ~ISACTIVEHIGH[0] ~THEN ~ELSE ! ~FI ~ARG[5]) - ,.CLK_OUT1 (~RESULT[1]) - ,.LOCKED (~RESULT[0])); + ~NAME[3] ~GENSYM[clockWizardDifferential_inst][2] + (.clk_in1_n (~ARG[4]) + ,.clk_in1_p (~ARG[5]) + ,.reset (~IF ~ISACTIVEHIGH[0] ~THEN ~ELSE ! ~FI ~ARG[6]) + ,.clk_out1 (~RESULT[1]) + ,.locked (~RESULT[0])); // clockWizardDifferential end + includes: + - name: clk_wiz + extension: clash.tcl + format: Haskell + templateFunction: Clash.Primitives.Xilinx.ClockGen.clockWizardDifferentialTclTF workInfo: Always diff --git a/clash-lib/prims/vhdl/Clash_Xilinx_ClockGen.primitives.yaml b/clash-lib/prims/vhdl/Clash_Xilinx_ClockGen.primitives.yaml index c3181e0759..465712cf65 100644 --- a/clash-lib/prims/vhdl/Clash_Xilinx_ClockGen.primitives.yaml +++ b/clash-lib/prims/vhdl/Clash_Xilinx_ClockGen.primitives.yaml @@ -3,12 +3,13 @@ kind: Declaration type: |- clockWizard - :: ( KnownDomain domIn confIn -- ARG[0] - , KnownDomain domOut confOut ) -- ARG[1] - => SSymbol name -- ARG[2] - -> Clock pllIn -- ARG[3] - -> Reset pllIn -- ARG[4] - -> (Clock pllOut, Enable pllOut) + :: ( KnownDomain domIn -- ARG[0] + , KnownDomain domOut -- ARG[1] + , KnownDomain domPllLock ) -- ARG[2] + => SSymbol name -- ARG[3] + -> Clock pllIn -- ARG[4] + -> Reset pllIn -- ARG[5] + -> (Clock pllOut, Signal domPllLock Bool) template: |- -- clockWizard begin ~GENSYM[clockWizard][0] : block @@ -16,50 +17,60 @@ signal ~GENSYM[locked][2] : std_logic; signal ~GENSYM[pllLock][3] : boolean; - component ~NAME[2] - port (CLK_IN1 : in std_logic; - RESET : in std_logic; - CLK_OUT1 : out std_logic; - LOCKED : out std_logic); + component ~NAME[3] + port (clk_in1 : in std_logic; + reset : in std_logic; + clk_out1 : out std_logic; + locked : out std_logic); end component; begin - ~GENSYM[clockWizard_inst][4] : component ~NAME[2] port map (~ARG[3],~IF ~ISACTIVEHIGH[0] ~THEN ~ARG[4] ~ELSE NOT(~ARG[4]) ~FI,~SYM[1],~SYM[2]); + ~GENSYM[clockWizard_inst][4] : component ~NAME[3] port map (~ARG[4],~IF ~ISACTIVEHIGH[0] ~THEN ~ARG[5] ~ELSE NOT(~ARG[5]) ~FI,~SYM[1],~SYM[2]); ~SYM[3] <= true when ~SYM[2] = '1' else false; ~RESULT <= (~SYM[1],~SYM[3]); end block; -- clockWizard end + includes: + - name: clk_wiz + extension: clash.tcl + format: Haskell + templateFunction: Clash.Primitives.Xilinx.ClockGen.clockWizardTclTF workInfo: Always - BlackBox: name: Clash.Xilinx.ClockGen.clockWizardDifferential kind: Declaration type: |- - clockWizardDifferential - :: ( KnownDomain domIn confIn -- ARG[0] - , KnownDomain domOut confOut ) -- ARG[1] - => SSymbol name -- ARG[2] - -> Clock pllIn -- ARG[3] - -> Clock pllIn -- ARG[4] - -> Reset pllIn -- ARG[5] - -> (Clock pllOut, Enable pllOut) + clockWizard + :: ( KnownDomain domIn -- ARG[0] + , KnownDomain domOut -- ARG[1] + , KnownDomain domPllLock ) -- ARG[2] + => SSymbol name -- ARG[3] + -> Clock pllIn -- ARG[4] + -> Clock pllIn -- ARG[5] + -> Reset pllIn -- ARG[6] + -> (Clock pllOut, Signal domPllLock Bool) template: |- -- clockWizardDifferential begin ~GENSYM[clockWizardDifferential][0] : block signal ~GENSYM[pllOut][1] : std_logic; signal ~GENSYM[locked][2] : std_logic; signal ~GENSYM[pllLock][3] : boolean; - - component ~NAME[2] - port (CLK_IN1_D_clk_n : in std_logic; - CLK_IN1_D_clk_p : in std_logic; - RESET : in std_logic; - CLK_OUT1 : out std_logic; - LOCKED : out std_logic); + component ~NAME[3] + port (clk_in1_n : in std_logic; + clk_in1_p : in std_logic; + reset : in std_logic; + clk_out1 : out std_logic; + locked : out std_logic); end component; begin - ~GENSYM[clockWizardDifferential_inst][4] : component ~NAME[2] - port map (~ARG[3],~ARG[4],~IF ~ISACTIVEHIGH[0] ~THEN ~ARG[5] ~ELSE NOT(~ARG[5]) ~FI,~SYM[1],~SYM[2]); + ~GENSYM[clockWizardDifferential_inst][4] : component ~NAME[3] + port map (~ARG[4],~ARG[5],~IF ~ISACTIVEHIGH[0] ~THEN ~ARG[6] ~ELSE NOT(~ARG[6]) ~FI,~SYM[1],~SYM[2]); ~SYM[3] <= true when ~SYM[2] = '1' else false; ~RESULT <= (~SYM[1],~SYM[3]); end block; -- clockWizardDifferential end + includes: + - name: clk_wiz + extension: clash.tcl + format: Haskell + templateFunction: Clash.Primitives.Xilinx.ClockGen.clockWizardDifferentialTclTF workInfo: Always diff --git a/clash-lib/src/Clash/Driver.hs b/clash-lib/src/Clash/Driver.hs index dbc61132e7..c4af596484 100644 --- a/clash-lib/src/Clash/Driver.hs +++ b/clash-lib/src/Clash/Driver.hs @@ -141,6 +141,7 @@ import qualified Clash.Primitives.GHC.Word as P import qualified Clash.Primitives.Intel.ClockGen as P import qualified Clash.Primitives.Prelude as P import qualified Clash.Primitives.Verification as P +import qualified Clash.Primitives.Xilinx.ClockGen as P import Clash.Primitives.Types import Clash.Signal.Internal import Clash.Unique (Unique, getUnique) @@ -602,6 +603,8 @@ knownTemplateFunctions = , ('P.alteraPllTF, P.alteraPllTF) , ('P.altpllTF, P.altpllTF) , ('P.fromIntegerTFvhdl, P.fromIntegerTFvhdl) + , ('P.clockWizardTclTF, P.clockWizardTclTF) + , ('P.clockWizardDifferentialTclTF, P.clockWizardDifferentialTclTF) ] -- | Compiles blackbox functions and parses blackbox templates. diff --git a/clash-lib/src/Clash/Primitives/Xilinx/ClockGen.hs b/clash-lib/src/Clash/Primitives/Xilinx/ClockGen.hs new file mode 100644 index 0000000000..319a9307e2 --- /dev/null +++ b/clash-lib/src/Clash/Primitives/Xilinx/ClockGen.hs @@ -0,0 +1,102 @@ +{-| + Copyright : (C) 2023, QBayLogic B.V. + License : BSD2 (see the file LICENSE) + Maintainer : QBayLogic B.V. + + Blackbox template functions for + Clash.Xilinx.ClockGen.{clockWizard,clockWizardDifferential} +-} + +{-# LANGUAGE QuasiQuotes #-} + +module Clash.Primitives.Xilinx.ClockGen where + +import Control.Monad.State (State) +import qualified Data.String.Interpolate as I + +import Clash.Signal (periodToHz) + +import Clash.Backend (Backend) +import Clash.Netlist.BlackBox.Util (exprToString) +import Clash.Netlist.Types +import Clash.Netlist.Util (stripVoid) +import Data.Text.Prettyprint.Doc.Extra (Doc) + + +clockWizardTclTF :: TemplateFunction +clockWizardTclTF = + TemplateFunction used valid (clockWizardTclTemplate False) + where + knownDomIn = 0 + knownDomOut = 1 + -- knownDomPllLock = 2 + name = 3 + -- clk = 4 + -- rst = 5 + used = [knownDomIn, knownDomOut, name] + valid = const True + +clockWizardDifferentialTclTF :: TemplateFunction +clockWizardDifferentialTclTF = + TemplateFunction used valid (clockWizardTclTemplate True) + where + knownDomIn = 0 + knownDomOut = 1 + -- knownDomPllLock = 2 + name = 3 + -- clkN = 4 + -- clkP = 5 + -- rst = 6 + used = [knownDomIn, knownDomOut, name] + valid = const True + + +clockWizardTclTemplate + :: Backend s + => Bool + -> BlackBoxContext + -> State s Doc +clockWizardTclTemplate isDifferential bbCtx + | (_,stripVoid -> (KnownDomain _ clkInPeriod _ _ _ _),_) <- bbInputs bbCtx !! 0 + , (_,stripVoid -> (KnownDomain _ clkOutPeriod _ _ _ _),_) <- bbInputs bbCtx !! 1 + , (nm,_,_) <- bbInputs bbCtx !! 3 + , [(Identifier _ Nothing,Product {})] <- bbResults bbCtx + , Just compName <- exprToString nm + = + let + clkInFreq :: Double + clkInFreq = periodToHz (fromInteger clkInPeriod) / 1e6 + clkOutFreq :: Double + clkOutFreq = periodToHz (fromInteger clkOutPeriod) / 1e6 + + differentialPinString = if isDifferential + then "Differential_clock_capable_pin" + else "Single_ended_clock_capable_pin" + + bbText = [I.__i| + namespace eval $tclIface { + variable api 1 + variable scriptPurpose createIp + variable ipName {#{compName}} + + proc createIp {ipName0 args} { + create_ip \\ + -name clk_wiz \\ + -vendor xilinx.com \\ + -library ip \\ + -version 6.0 \\ + -module_name $ipName0 \\ + {*}$args + + set_property \\ + -dict [list \\ + CONFIG.PRIM_SOURCE #{differentialPinString} \\ + CONFIG.PRIM_IN_FREQ #{clkInFreq} \\ + CONFIG.CLKOUT1_REQUESTED_OUT_FREQ #{clkOutFreq} \\ + ] [get_ips $ipName0] + return + } + }|] + in pure bbText + | otherwise + = error ("clockWizardTclTemplate: bad bbContext: " <> show bbCtx) diff --git a/clash-prelude/src/Clash/Clocks/Deriving.hs b/clash-prelude/src/Clash/Clocks/Deriving.hs index 8553e8f69a..0939ea5ff6 100644 --- a/clash-prelude/src/Clash/Clocks/Deriving.hs +++ b/clash-prelude/src/Clash/Clocks/Deriving.hs @@ -13,12 +13,12 @@ Maintainer : QBayLogic B.V. module Clash.Clocks.Deriving (deriveClocksInstances) where import Control.Monad (foldM) +import Clash.Promoted.Symbol (SSymbol(..)) import Clash.Explicit.Signal (unsafeSynchronizer) import Clash.Signal.Internal import Language.Haskell.TH.Compat import Language.Haskell.TH.Syntax import Language.Haskell.TH.Lib -import Unsafe.Coerce (unsafeCoerce) conPatternNoTypes :: Name -> [Pat] -> Pat #if MIN_VERSION_template_haskell(2,18,0) @@ -46,24 +46,22 @@ derive' n = do #endif -- Function definition of 'clocks' - let clk = mkName "clk" let rst = mkName "rst" -- Implementation of 'clocks' + clkImpl <- [| Clock SSymbol Nothing |] lockImpl <- [| unsafeSynchronizer clockGen clockGen (unsafeToLowPolarity $(varE rst)) |] let noInline = PragmaD $ InlineP (mkName "clocks") NoInline FunLike AllPhases - clkImpls = replicate n (clkImpl clk) + clkImpls = replicate n clkImpl instTuple = mkTupE $ clkImpls ++ [lockImpl] funcBody = NormalB instTuple errMsg = "clocks: dynamic clocks unsupported" errBody = NormalB ((VarE 'error) `AppE` (LitE (StringL errMsg))) instFunc = FunD (mkName "clocks") [ Clause - [ AsP - clk - (conPatternNoTypes 'Clock [WildP, conPatternNoTypes 'Nothing []]) + [ (conPatternNoTypes 'Clock [WildP, conPatternNoTypes 'Nothing []]) , VarP rst] funcBody [] @@ -92,8 +90,6 @@ derive' n = do [t| KnownDomain $p |] - clkImpl clk = AppE (VarE 'unsafeCoerce) (VarE clk) - -- Derive instances for up to and including to /n/ clocks deriveClocksInstances :: Int -> Q [Dec] deriveClocksInstances n = mapM derive' [1..n] diff --git a/clash-prelude/src/Clash/Xilinx/ClockGen.hs b/clash-prelude/src/Clash/Xilinx/ClockGen.hs index 3e4d87a1fa..3fbe712ea6 100644 --- a/clash-prelude/src/Clash/Xilinx/ClockGen.hs +++ b/clash-prelude/src/Clash/Xilinx/ClockGen.hs @@ -1,7 +1,8 @@ {-| -Copyright : (C) 2017, Google Inc +Copyright : (C) 2017, Google Inc, + 2023, QBayLogic B.V. License : BSD2 (see the file LICENSE) -Maintainer : Christiaan Baaij +Maintainer : QBayLogic B.V. PLL and other clock-related components for Xilinx FPGAs -} @@ -11,95 +12,75 @@ PLL and other clock-related components for Xilinx FPGAs module Clash.Xilinx.ClockGen where -import Clash.Annotations.Primitive (hasBlackBox) -import Clash.Promoted.Symbol +import Clash.Annotations.Primitive (hasBlackBox) +import Clash.Clocks (clocks) +import Clash.Promoted.Symbol (SSymbol) import Clash.Signal.Internal -import Unsafe.Coerce --- | A clock source that corresponds to the Xilinx PLL/MMCM component created + +-- | A clock source that corresponds to the Xilinx MMCM component created -- with the \"Clock Wizard\" with settings to provide a stable 'Clock' from --- a single free-running input --- --- Only works when configured with: +-- a single free-running clock input. -- --- * 1 reference clock --- * 1 output clock --- * a reset port --- * a locked port --- --- You must use type applications to specify the output clock domain, e.g.: +-- You can use type applications to specify the output clock domain, e.g.: -- -- @ --- type Dom100MHz = Dom \"A\" 10000 +-- createDomain vXilinxSystem{vName=\"Dom100MHz\", vPeriod=10000} -- --- -- outputs a clock running at 100 MHz --- clockWizard @@Dom100MHz (SSymbol @@"clkWizard50to100") clk50 rst +-- -- Outputs a clock running at 100 MHz +-- clockWizard \@_ \@Dom100MHz (SSymbol \@\"clkWizard50to100\") clk50 rst -- @ clockWizard - :: forall domIn domOut periodIn periodOut edge init polarity name - . ( KnownConfiguration domIn ('DomainConfiguration domIn periodIn edge 'Asynchronous init polarity) - , KnownConfiguration domOut ('DomainConfiguration domOut periodOut edge 'Asynchronous init polarity) ) + :: forall domIn domOut domPllLock name + . ( KnownDomain domIn + , KnownDomain domOut + , KnownDomain domPllLock ) => SSymbol name - -- ^ Name of the component, must correspond to the name entered in the - -- \"Clock Wizard\" dialog. - -- - -- For example, when you entered \"clockWizard50\", instantiate as follows: + -- ^ Name of the component instance -- - -- > SSymbol @ "clockWizard50" + -- Instantiate as follows: @(SSymbol \@\"clockWizard50\")@ -> Clock domIn -- ^ Free running clock (i.e. a clock pin connected to a crystal) -> Reset domIn -- ^ Reset for the PLL - -> (Clock domOut, Enable domOut) + -> (Clock domOut, Signal domPllLock Bool) -- ^ (Stable PLL clock, PLL lock) -clockWizard !_ clk@(Clock _ Nothing) rst = - (unsafeCoerce clk, unsafeCoerce (toEnable (unsafeToHighPolarity rst))) -clockWizard _ _ _ = - error "clockWizard: no support for dynamic clocks" +clockWizard !_ = clocks {-# NOINLINE clockWizard #-} {-# ANN clockWizard hasBlackBox #-} --- | A clock source that corresponds to the Xilinx PLL/MMCM component created +-- | A clock source that corresponds to the Xilinx MMCM component created -- with the \"Clock Wizard\", with settings to provide a stable 'Clock' --- from differential free-running inputs. +-- from a free-running differential clock input. -- --- Only works when configured with: --- --- * 1 differential reference pair --- * 1 output clock --- * a reset port --- * a locked port --- --- You must use type applications to specify the output clock domain, e.g.: +-- You can use type applications to specify the output clock domain, e.g.: -- -- @ --- type Dom100MHz = Dom \"A\" 10000 +-- createDomain vXilinxSystem{vName=\"Dom100MHz\", vPeriod=10000} -- --- -- outputs a clock running at 100 MHz --- clockWizardDifferential @@Dom100MHz (SSymbol @@"clkWizardD50to100") clk50N clk50P rst +-- -- Outputs a clock running at 100 MHz +-- clockWizardDifferential \@_ \@Dom100MHz (SSymbol \@\"clkWizard50to100\") clk50N clk50P rst -- @ clockWizardDifferential - :: forall domIn domOut periodIn periodOut edge init polarity name - . ( KnownConfiguration domIn ('DomainConfiguration domIn periodIn edge 'Asynchronous init polarity) - , KnownConfiguration domOut ('DomainConfiguration domOut periodOut edge 'Asynchronous init polarity) ) + :: forall domIn domOut domPllLock name + . ( KnownDomain domIn + , KnownDomain domOut + , KnownDomain domPllLock ) => SSymbol name - -- ^ Name of the component, must correspond to the name entered in the - -- \"Clock Wizard\" dialog. - -- - -- For example, when you entered \"clockWizardD50\", instantiate as follows: + -- ^ Name of the component instance -- - -- > SSymbol @ "clockWizardD50" + -- Instantiate as follows: @(SSymbol \@\"clockWizardD50\")@ -> Clock domIn -- ^ Free running clock, negative phase -> Clock domIn -- ^ Free running clock, positive phase -> Reset domIn -- ^ Reset for the PLL - -> (Clock domOut, Enable domOut) + -> (Clock domOut, Signal domPllLock Bool) -- ^ (Stable PLL clock, PLL lock) -clockWizardDifferential !_name (Clock _ Nothing) (Clock _ Nothing) rst = - (Clock SSymbol Nothing, unsafeCoerce (toEnable (unsafeToHighPolarity rst))) -clockWizardDifferential !_name _ _ _ = - error "clockWizardDifferential: no support for dynamic clocks" +clockWizardDifferential !_ clk@(Clock _ Nothing) (Clock _ Nothing) = + clocks clk +clockWizardDifferential !_ _ _ = + error "clockWizardDifferential: dynamic clocks not supported" {-# NOINLINE clockWizardDifferential #-} {-# ANN clockWizardDifferential hasBlackBox #-}