From 33aefdde9ab5d2acb736567aa81058df20593da4 Mon Sep 17 00:00:00 2001 From: Peter Lebbing Date: Thu, 2 Mar 2023 12:46:46 +0100 Subject: [PATCH 1/2] New: `seClockToDiffClock` To create a differential clock signal in a test bench. It is not suitable for synthesising a differential output in hardware. --- changelog/2023-03-17T14_40_13+01_00_diff_clk | 2 ++ .../Clash_Explicit_Testbench.primitives.yaml | 4 ++++ .../Clash_Explicit_Testbench.primitives.yaml | 4 ++++ clash-prelude/src/Clash/Explicit/Testbench.hs | 24 +++++++++++++++++++ clash-prelude/src/Clash/Prelude/Testbench.hs | 1 + 5 files changed, 35 insertions(+) create mode 100644 changelog/2023-03-17T14_40_13+01_00_diff_clk diff --git a/changelog/2023-03-17T14_40_13+01_00_diff_clk b/changelog/2023-03-17T14_40_13+01_00_diff_clk new file mode 100644 index 0000000000..a9af0b6499 --- /dev/null +++ b/changelog/2023-03-17T14_40_13+01_00_diff_clk @@ -0,0 +1,2 @@ +NEW: `seClockToDiffClock`, to create a differential clock signal in a test +bench. It is not suitable for synthesising a differential output in hardware. diff --git a/clash-lib/prims/commonverilog/Clash_Explicit_Testbench.primitives.yaml b/clash-lib/prims/commonverilog/Clash_Explicit_Testbench.primitives.yaml index 338e5a9b4c..e7f1af3d1e 100644 --- a/clash-lib/prims/commonverilog/Clash_Explicit_Testbench.primitives.yaml +++ b/clash-lib/prims/commonverilog/Clash_Explicit_Testbench.primitives.yaml @@ -5,3 +5,7 @@ Enable dom' template: assign ~RESULT = 1'b1; workInfo: Always +- BlackBox: + name: Clash.Explicit.Testbench.seClockToDiffClock + kind: Expression + template: '{~ARG[0], ~ ~ARG[0]}' diff --git a/clash-lib/prims/vhdl/Clash_Explicit_Testbench.primitives.yaml b/clash-lib/prims/vhdl/Clash_Explicit_Testbench.primitives.yaml index ee177a462c..a4e6544e88 100644 --- a/clash-lib/prims/vhdl/Clash_Explicit_Testbench.primitives.yaml +++ b/clash-lib/prims/vhdl/Clash_Explicit_Testbench.primitives.yaml @@ -155,3 +155,7 @@ Enable dom' template: ~RESULT <= true; workInfo: Always +- BlackBox: + name: Clash.Explicit.Testbench.seClockToDiffClock + kind: Expression + template: (~ARG[0], not ~ARG[0]) diff --git a/clash-prelude/src/Clash/Explicit/Testbench.hs b/clash-prelude/src/Clash/Explicit/Testbench.hs index eb52ffff8b..b2c108adf3 100644 --- a/clash-prelude/src/Clash/Explicit/Testbench.hs +++ b/clash-prelude/src/Clash/Explicit/Testbench.hs @@ -26,6 +26,7 @@ module Clash.Explicit.Testbench , tbClockGen , tbEnableGen , tbSystemClockGen + , seClockToDiffClock , outputVerifier , outputVerifier' @@ -451,6 +452,29 @@ tbSystemClockGen -> Clock System tbSystemClockGen = tbClockGen +-- | Convert a single-ended clock to a differential clock +-- +-- The 'tbClockGen' function generates a single-ended clock. This function will +-- output the two phases of a differential clock corresponding to that +-- single-ended clock. +-- +-- This function is only meant to be used in the /testBench/ function, not to +-- create a differential output in hardware. +-- +-- Example: +-- +-- @ +-- (clkP, clkN) = seClockToDiffClock $ tbClockGen (not \<\$\> done) +-- @ +seClockToDiffClock :: + -- | Single-ended input + Clock dom -> + -- | (Positive phase, negative phase) + (Clock dom, Clock dom) +seClockToDiffClock clk = (clk, clk) +{-# NOINLINE seClockToDiffClock #-} +{-# ANN seClockToDiffClock hasBlackBox #-} + -- | Cross clock domains in a way that is unsuitable for hardware but good -- enough for simulation. -- diff --git a/clash-prelude/src/Clash/Prelude/Testbench.hs b/clash-prelude/src/Clash/Prelude/Testbench.hs index 8565c5f428..2b867e9dfa 100644 --- a/clash-prelude/src/Clash/Prelude/Testbench.hs +++ b/clash-prelude/src/Clash/Prelude/Testbench.hs @@ -26,6 +26,7 @@ module Clash.Prelude.Testbench , E.tbClockGen , E.tbEnableGen , E.tbSystemClockGen + , E.seClockToDiffClock ) where From 95a9df0a23728f0481a36184ef84221e373f074e Mon Sep 17 00:00:00 2001 From: Hidde Moll Date: Wed, 22 Feb 2023 09:05:43 +0100 Subject: [PATCH 2/2] Fix Xilinx clock wizard, add Tcl IP generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the Xilinx clock generation primitives and adds support for clock primitive instantiation on Xilinx without manually going through the Vivado Clocking Wizard. The updated black boxes generate Tcl code to instantiate an MMCM through the Clocking Wizard. The clock primitives are limited to 1 output clock, and either a single-ended or a differential input clock. The generated Tcl script conforms with the Clash<->Tcl API. ​ Fixes the following: - Broken Haskell simulation The Haskell simulation of the Xilinx clock primitives was incorrect due to a polarity mismatch in the lock output of Xilinx `clockWizard` and `clockWizardDifferential`: Haskell simulation was the inverse of HDL simulation and hardware. In Haskell simulation, the primitives had an `Enable` as their output, which was _asserted_ when the PLL lock was _deasserted_. This corresponds to an _asserted_ reset input to the primitive in the simulation model, meaning the `Enable` output was asserted iff the PLL reset input was asserted. HDL simulation and hardware meanwhile had an active high lock signal output (which is usually used as an active low reset signal to the circuit). Finally, the simulation model did not account for different frequencies of the input and output domains, instead treating individual input samples as individual output samples without resampling. This has been fixed by changing the type to `Signal Bool` and asserting it when the PLL has locked. - Wrong port names of blackbox primitives The blackbox primitives for `clockWizard` and `clockWizardDifferential` had capitalized port names. Also, the `clockWizardDifferential` primitive had 2 identical port names for the differential input clock signals. ​- Remove `Asynchronous` constraint from Xilinx `clockWizard` and `clockWizardDifferential`. Originally intended to signal that these functions react synchronously to the incoming reset and that the outgoing lock signal is an asynchronous signal. Since synchronous reset signals are a subset of asynchronous reset signals 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. Known problems: - The `locked` signal is asynchronous. Therefore, this function is unsafe. - The Clocking Wizard uses input and output frequencies to determine the multiplier and divider for the MMCM, but Clash uses periods instead. The conversion from period (picoseconds, Natural) to frequency (MHz, Double) is not exact. This is already not exact when using `hzToPeriod`, e.g. `hzToPeriod 300e6` translates to a period of `3333` ps, which becomes `300.03000300030004` in Tcl. --- ...3-01T12_18_28+01_00_xilinx_clocking_wizard | 3 + clash-lib/clash-lib.cabal | 1 + .../Clash_Xilinx_ClockGen.primitives.yaml | 54 ++++++---- .../Clash_Xilinx_ClockGen.primitives.yaml | 57 +++++----- clash-lib/src/Clash/Driver.hs | 3 + .../src/Clash/Primitives/Xilinx/ClockGen.hs | 100 ++++++++++++++++++ clash-prelude/src/Clash/Clocks/Deriving.hs | 12 +-- clash-prelude/src/Clash/Xilinx/ClockGen.hs | 93 +++++++--------- tests/Main.hs | 7 ++ tests/shouldwork/Xilinx/ClockWizard.hs | 84 +++++++++++++++ 10 files changed, 303 insertions(+), 111 deletions(-) create mode 100644 changelog/2023-03-01T12_18_28+01_00_xilinx_clocking_wizard create mode 100644 clash-lib/src/Clash/Primitives/Xilinx/ClockGen.hs create mode 100644 tests/shouldwork/Xilinx/ClockWizard.hs 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..0a31496c1a --- /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 synchronously to the incoming reset and that the outgoing lock signal is an asynchronous signal. Since synchronous reset signals are a subset of asynchronous reset signals 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. The lock signal is now also correctly resampled to the output domain. +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..cc46efadfe 100644 --- a/clash-lib/prims/commonverilog/Clash_Xilinx_ClockGen.primitives.yaml +++ b/clash-lib/prims/commonverilog/Clash_Xilinx_ClockGen.primitives.yaml @@ -3,40 +3,50 @@ 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] + => SSymbol name -- ARG[2] + -> Clock domIn -- ARG[3] + -> Reset domIn -- ARG[4] + -> (Clock domOut, Signal domOut 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])); + (.clk_in1 (~ARG[3]) + ,.reset (~IF ~ISACTIVEHIGH[0] ~THEN ~ELSE ! ~FI ~ARG[4]) + ,.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] + :: SSymbol name -- ARG[2] + -> Clock domIn -- ARG[3] + -> Clock domIn -- ARG[4] + -> Reset domIn -- ARG[5] + -> (Clock domOut, Signal domOut 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])); + (.clk_in1_n (~ARG[3]) + ,.clk_in1_p (~ARG[4]) + ,.reset (~IF ~ISACTIVEHIGH[0] ~THEN ~ELSE ! ~FI ~ARG[5]) + ,.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..c60ad96866 100644 --- a/clash-lib/prims/vhdl/Clash_Xilinx_ClockGen.primitives.yaml +++ b/clash-lib/prims/vhdl/Clash_Xilinx_ClockGen.primitives.yaml @@ -3,12 +3,12 @@ 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] + => SSymbol name -- ARG[2] + -> Clock domIn -- ARG[3] + -> Reset domIn -- ARG[4] + -> (Clock domIn, Signal domOut Bool) template: |- -- clockWizard begin ~GENSYM[clockWizard][0] : block @@ -17,10 +17,10 @@ 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); + 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]); @@ -28,32 +28,36 @@ ~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] + => SSymbol name -- ARG[2] + -> Clock domIn -- ARG[3] + -> Clock domIn -- ARG[4] + -> Reset domIn -- ARG[5] + -> (Clock domOut, Signal domOut 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); + 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] @@ -62,4 +66,9 @@ ~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..96c38378b6 --- /dev/null +++ b/clash-lib/src/Clash/Primitives/Xilinx/ClockGen.hs @@ -0,0 +1,100 @@ +{-| + 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 + name = 2 + -- clk = 3 + -- rst = 4 + used = [knownDomIn, knownDomOut, name] + valid = const True + +clockWizardDifferentialTclTF :: TemplateFunction +clockWizardDifferentialTclTF = + TemplateFunction used valid (clockWizardTclTemplate True) + where + knownDomIn = 0 + knownDomOut = 1 + name = 2 + -- clkN = 3 + -- clkP = 4 + -- rst = 5 + 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 !! 2 + , [(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..4e6006b0d8 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..89fc0e7fd5 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,73 @@ 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 name + . ( KnownDomain domIn + , KnownDomain domOut ) => 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 domOut 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 name + . ( KnownDomain domIn + , KnownDomain domOut ) => 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 domOut 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 #-} diff --git a/tests/Main.hs b/tests/Main.hs index 9c0faf7d16..c0ec632c05 100755 --- a/tests/Main.hs +++ b/tests/Main.hs @@ -1019,6 +1019,13 @@ runClashTest = defaultMain $ clashTestRoot , verificationTool=Just SymbiYosys } ] + , clashTestGroup "Xilinx" + [ let _opts = def{ hdlTargets=[VHDL, Verilog] + , hdlLoad=[Vivado] + , hdlSim=[Vivado] + } + in runTest "ClockWizard" _opts + ] , clashTestGroup "XOptimization" [ outputTest "Conjunction" def , outputTest "Disjunction" def diff --git a/tests/shouldwork/Xilinx/ClockWizard.hs b/tests/shouldwork/Xilinx/ClockWizard.hs new file mode 100644 index 0000000000..0892920f07 --- /dev/null +++ b/tests/shouldwork/Xilinx/ClockWizard.hs @@ -0,0 +1,84 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE TemplateHaskell #-} + +{-# OPTIONS_GHC -Wno-orphans #-} + +module ClockWizard where + +import Data.String.Interpolate (__i) + +import Clash.Annotations.Primitive +import Clash.Explicit.Prelude +import Clash.Explicit.Testbench +import Clash.Xilinx.ClockGen + +createDomain vXilinxSystem{vName="DomIn", vPeriod=hzToPeriod 24_000_000} +createDomain vXilinxSystem{vName="DomOut", vPeriod=hzToPeriod 300_000_000} + +topEntity :: + Clock DomIn -> + Clock DomIn -> + Reset DomIn -> + Signal DomOut (Index 10, Index 10) +topEntity clkInN clkInP rstIn = + let f clk rst = register clk rst enableGen 0 . fmap (satSucc SatBound) + (clkA, stableA) = clockWizard (SSymbol @"clk_wiz_se") clkInP rstIn + rstA = unsafeFromLowPolarity stableA + (clkB, stableB) = clockWizardDifferential (SSymbol @"clk_wiz_diff") clkInN + clkInP rstIn + rstB = unsafeFromLowPolarity stableB + o1 = f clkA rstA o1 + o2 = f clkB rstB o2 + in bundle (o1, o2) +{-# NOINLINE topEntity #-} + +testBench :: + Signal DomIn Bool +testBench = done + where + (o1, o2) = unbundle $ topEntity clkP clkN rst + done1 = o1 .==. pure maxBound + done2 = o2 .==. pure maxBound + done = unsafeSynchronizer clockGen clkP $ fmap endVhdlSim $ + strictAnd <$> done1 <*> done2 + strictAnd !a !b = a && b + (clkP, clkN) = seClockToDiffClock $ tbClockGen (not <$> done) + rst = resetGen +{-# NOINLINE testBench #-} + +-- Normally we end VHDL sim by stopping the clocks; usually simulation will +-- notice nothing can ever change anymore and end. The @clockWizard@ simulation +-- models keep running even after we stopped the input clocks. In VHDL-93, the +-- best we can do is throw an assertion. Since our CI greps for assertions of +-- severity @error@ to indicate problems, we can assert severity @failure@ and +-- CI will consider it a success. +-- +-- NB: The assertion triggers as soon as it observes @done@ is asserted, which +-- is earlier than we normally end simulation. It might miss an error assertion +-- in the final sample. +endVhdlSim :: + Bool -> + Bool +endVhdlSim = id +{-# NOINLINE endVhdlSim #-} +{-# ANN endVhdlSim ( + let primName = 'endVhdlSim + in InlineYamlPrimitive [VHDL] [__i| + BlackBox: + name: #{primName} + kind: Declaration + template: |- + -- endVhdlSim begin + assert (not ~ARG[0]) report "Simulation finished" severity failure; + ~RESULT <= ~ARG[0]; + -- endVhdlSim end + |]) #-} +{-# ANN endVhdlSim ( + let primName = 'endVhdlSim + in InlineYamlPrimitive [Verilog, SystemVerilog] [__i| + BlackBox: + name: #{primName} + kind: Expression + template: ~ARG[0] + |]) #-}