New feature: configurable initial values

Page content

TL;DR: PR #527 will soon be merged. Signal’s Domain will now be a simple tag (type level string), uniquely referring to a datatype DomainConfiguration that holds the properties of the synthesis domain. Properties include the clock period, reset synchronisity, whether registers have initial values, and more. GatedClock is no more, but Enable now exists. Clock is now a singleton value. Reset simply carries a signal of Bools.

Initial values: why do we need them?

To properly simulate what happens in synthesized circuits, the development version of Clash has been simulating what happens before a clock starts running. For a while, Clash implemented the following behavior:

$ clashi
>>> let counter = register 0 (counter + 1)
>>> printX (sampleN 5 counter)
[X,0,1,2,3]

The first value, X, signifies an undefined value with similar meaning to its Verilog, SystemVerilog, and VHDL counterpart. Whenever you have to deal with undefined values, you have to be careful not to evaluate them during simulation at the risk of stopping the simulation entirely. This change in Clash meant that circuits previously not having to deal with undefined values, suddenly had to. This was especially annoying for users that used strict data structures in order to increase simulation performance - for no good reason their simulations wouldn’t run anymore.

Undefined initial values had never been intented to stay around for long though, as almost all mainstream FPGAs support defined register initial values. With PR #498 merged, Clash would now assume the reset value as initial value too:

$ clashi
>>> let counter = register 0 (counter + 1)
>>> printX (sampleN 5 counter)
[0,0,1,2,3]

Great! Case closed?

No, not really. While this strategy covers a good chunk of use cases, it doesn’t account for them all. For example, reconfigurable regions of many FPGAs do not support defined initial values, and neither do ASICs. What we actually need is the ability to configure whether a specific circuit has defined or undefined initial values.

Synthesis domains

After a few design iterations (see PR #527 for a technical discussion), we settled on the introduction of synthesis domains. Because any feasible design would break backwards compatibility in a serious way anyway, we decided to solve some long-standing annoyances of ours simultaneously. Let’s look at an example and go over it line by line:

register
  :: ( KnownDomain dom conf
     , Undefined a )
  => Clock dom
  -> Reset dom
  -> Enable dom
  -> a
  -> Signal dom a
  -> Signal dom a
register clk rst gen initial i = ..

First off:

  :: ( KnownDomain dom conf

^ This is the core of the change: functions that need to know anything about the synthesis domain need a KnownDomain constraint. It’s made up of two parts. The first, dom :: Symbol, is the name for the domain. This dom uniquely refers to the second part, conf :: DomainConfiguration. “Uniquely” in this context means that there can never be two intances where dom1 == dom2, but conf1 /= conf2. We’ll later see how to actually use this contraint, and what properties it carries.

     , Undefined a )

^ For users of the development version of Clash this should be familiar. It’s a constraint that considerably improves dealing with undefined values. For more information, see the blogpost Undefined values: how do they work?.

  => Clock dom
  -> Reset dom
  -> Enable dom

^ Both Clock and Reset have been simplified. Clock used to carry and extra type argument, indicating whether it was a gated clock or not. Because gated clocks don’t really exist on FPGAs, we decided to remove this altoghether. Instead, what we actually implemented was some sort of “enabled” clock: a simple wire indicating whether a component is active or not. This has been moved to a separate argument Enable. Reset used to carry an extra type argument too, indicating whether it was a synchronous or asynchronous reset. This is now part of the synthesis domain. For all arguments, dom indicates what domain they belong to.

Finally, not much has changed for Signal. dom is of kind Symbol (a type level string), pointing to the domain carried by the KnownDomain constraint. The obvious next question is, what does a domain configuration actually look like?

data DomainConfiguration
  = DomainConfiguration
  { _dom :: Domain
  -- ^ Domain name
  , _period :: Nat
  -- ^ Period of clock in /ps/
  , _edge :: ActiveEdge
  -- ^ Active edge of the clock (not yet implemented)
  , _reset :: ResetKind
  -- ^ Whether resets are synchronous (edge-sensitive) or 
  -- ^ asynchronous (level-sensitive)
  , _init :: InitBehavior
  -- ^ Whether the initial (or "power up") value of memory elements is
  -- unknown/undefined, or configurable to a specific value
  , _polarity :: ResetPolarity
  -- ^ Whether resets are active high or active low
  }

Quite a few options!

How to use: synthesis domains

Because it’s quite a tedious task to actually write instances for KnownDomain due to its use of GADTs and promoted data kinds, we’ve written a convenience Template Haskell function called createDomain. It takes a VDomainConfiguration: it’s similar to DomainConfiguration, but with Domain and Nat replaced with String and Integer respectively, such that it can be represented on Haskell runtime. To create a synthesis domain based on the System domain, but with synchronous resets and undefined initial values use:

createDomain vSystem{vTag="SyncUndefined", vReset=Synchronous, vInit=Undefined}

After doing this, you can use SyncUndefined in your type signatures:

f :: Signal SyncUndefined Bool

and vSyncUndefined to create an other domain:

createDomain vSyncUndefined{vTag="SyncUndefined_2300", vPeriod=2300}

How to use: initial values

Create a domain using the instructions above, setting Defined or Undefined however you wish. Given the simple counter we’ve seen before:

import Clash.Prelude

createDomain vSystem{vTag="SyncUndefined",  vReset=Synchronous,  vInit=Undefined}
createDomain vSystem{vTag="SyncDefined",    vReset=Synchronous,  vInit=Defined}
createDomain vSystem{vTag="AsyncUndefined", vReset=Asynchronous, vInit=Undefined}
createDomain vSystem{vTag="AsyncDefined",   vReset=Asynchronous, vInit=Defined}

counter
  :: HiddenClockResetEnable dom conf
  => Signal dom Int
counter = register 0 (counter + 1)

main = do
  putStrLn "Sync:"
  putStr "  Undefined: "
  printX (sampleN @SyncUndefined 5 counter)

  putStr "  Defined:   "
  printX (sampleN @SyncDefined 5 counter)

  putStrLn "Async:"
  putStr "  Undefined: "
  printX (sampleN @AsyncUndefined 5 counter)

  putStr "  Defined:   "
  printX (sampleN @AsyncDefined 5 counter)

you can expect the output to be:

Sync:
  Undefined: [X,0,1,2,3]
  Defined:   [0,0,1,2,3]
Async:
  Undefined: [0,0,1,2,3]
  Defined:   [0,0,1,2,3]

How to use: hidden clocks, resets, and enables

Syntax has changed slightly, with some type variables having moved to the synthesis domain. The explicitness of Enable slightly changes names of some constructs. To sum it up:

  • SystemClockReset is now called SystemClockResetEnable, as it now includes the enable signal too.
  • HiddenClockReset is now called HiddenClockResetEnable, as it now includes the enable signal too.
  • HiddenClock conf gated is now HiddenClock dom conf.
  • HiddenReset conf sync is now HiddenReset dom conf.
  • HiddenEnable dom conf has been added.

All of the Hidden* constructs carry a KnownDomain constraint.

Bonus: multiple hidden clocks

Thanks to the API changes, you can now use multiple hidden clocks, resets, and enables. It basically works as you’d expect it would:

delay2
  :: ( HiddenClockResetEnable domA confA
     , HiddenClockResetEnable domB confB )
  => Signal domA Int
  -> Signal domB Int
  -> (Signal domA Int, Signal domB Int)
delay2 a b = (register 0 a, register 0 b)

That’s all, thanks for reading!

Martijn Bastiaan avatar
About Martijn Bastiaan
Martijn Bastiaan has been a software engineer at QBayLogic since 2017. He is an active contributer to the Clash compiler itself.