clash-lang / clash-compiler

Haskell to VHDL/Verilog/SystemVerilog compiler
https://clash-lang.org/
Other
1.44k stars 154 forks source link

AutoReg Convenience Functions #1870

Open Jhana1 opened 3 years ago

Jhana1 commented 3 years ago

Some additional AutoReg convenience functions would be really nice, replicating the delay, delayN, delayI interface would be great. I've tried to do the same, but the following (a blatant copy paste of delayN) blows out compile times (so I imagine some internal special casing must happen on these functions to prevent that)

 dAutoRegN
   :: (HiddenClockResetEnable App, AutoReg a, Default a, KnownNat delay)
   => SNat n
   -> DSignal App delay a
   -> DSignal App (delay + n) a
 dAutoRegN s = unsafeFromSignal . go (snatToNum s) . toSignal
   where
     go 0 = id
     go n = autoReg def . go (n - 1)
alex-mckenna commented 3 years ago

We do have a branch from a while ago that @christiaanb was working on, but I'm not sure what state it's in. I'm not that familiar with the AutoReg stuff though, maybe it needs more adaptation to work with DSignals

christiaanb commented 3 years ago

@Jhana1 Interesting, I'll post a rewrite of what you wanted that shouldn't blow up compile times; after I've had my breakfast.

christiaanb commented 3 years ago

@Jhana1 The following should compile relatively quickly:

module DAutoReg where

import Clash.Prelude

dAutoRegN ::
  forall dom a delay n .
  (HiddenClockResetEnable dom, AutoReg a, Default a, KnownNat delay) =>
  SNat n ->
  DSignal dom delay a ->
  DSignal dom (delay + n) a
dAutoRegN s@SNat = unsafeFromSignal . go (toUNat s) . toSignal
  where
    go :: UNat n -> Signal dom a -> Signal dom a
    go UZero     inp = inp
    go (USucc _) inp =
      let shIn  = init (inp `Cons` shOut)
          shOut = map (autoReg def) (lazyV @n shIn)
       in last shOut

topEntity xs = dAutoRegN @System @(Vec 10 (Maybe Int)) @0 (SNat @200) xs

So the 200 stage deep pipeline of type Vec 10 (Maybe Int) for me gives:

$ time cabal run clash -- --vhdl DAutoReg.hs
Up to date
Loaded package environment from /home/christiaan/clash-compiler/.ghc.environment.x86_64-linux-8.10.4
GHC: Parsing and optimising modules took: 1.413s
GHC: Loading external modules from interface files took: 0.000s
GHC: Parsing annotations took: 0.001s
Clash: Parsing and compiling primitives took 0.142s
GHC+Clash: Loading modules cumulatively took 2.793s
Clash: Compiling DAutoReg.topEntity
Clash: Normalization took 0.291s
Clash: Netlist generation took 0.024s
Clash: Total compilation took 3.130s

real    0m3.743s
user    0m3.378s
sys     0m0.176s

This is on HEAD of master, but I don't expect much difference for the 1.4.2 release.

christiaanb commented 3 years ago

dAugoRegN can actually be simplified to:

dAutoRegN ::
  forall dom a delay n .
  (HiddenClockResetEnable dom, AutoReg a, Default a, KnownNat delay) =>
  SNat n ->
  DSignal dom delay a ->
  DSignal dom (delay + n) a
dAutoRegN SNat = unsafeFromSignal . go . toSignal
  where
    go inp =
      let shIn  = init (inp :> shOut)
          shOut = map (autoReg def) (lazyV @n shIn)
       in last (inp :> shOut)
christiaanb commented 3 years ago

The reason this doesn't blow up is because it allows the Clash compiler to take hard-coded "short-cuts" where it doesn't have to unroll the definitions for init, map, lazyV and last, but can use their "blackbox" implementations instead:

Another thing to note: lazyV is there to ensure we have a "spine" of Cons / (:>) constructors to map over.

Jhana1 commented 3 years ago

Thanks, I'm trying it now. Much appreciated!