Open christiaanb opened 4 years ago
Hmm.. I think this does two things:
I think we could benefit from splitting this up. If we have (1) I don't think we need extra "magical" syntax. Something like this might work:
pipeline valid $ do
(aSq, bSq) <- stage d1 $ do
pure (a * a, b * b)
cSq <- stage d2 $ do
pure (aSq + bSq)
stage d3 $
sqrtS valid cSq
As a bonus, (1) could be reused in non-piplelined code too.
We need the magical syntax because the amount of delay is calculated as the difference between declaration and use site. In the example so far, the difference has always been one, but it can be arbitrary.
In the example I posted I imagined the idiom notation to take care of auto-delaying signals where necessary, and the 'stage' function to force a specific delay for their "return values".
It has to be taken care of at the use site, because a value might be used at 2 places, where you want to share pipelining registers. e.g. in
pipeline \a -> do
@1
let z = f a
@3
let y = g a z
@4
let x = h z y
you want:
a
, where the use in f a
branches off from the first stage. and the use in g a z
uses the last stage.z
, where the use in g a z
branches off from the first stage, and the use in h z y
uses the last stage.h z y
The way I imagined the algorithm, is that for every stage you get a delayed versions of the values declared in the previous stage: this includes delayed values in the stage before that. Then using some tying-the-knot recursion, you can prune the unused delayed values in the same traversal.
It has to be taken care of at the use site, because a value might be used at 2 places, where you want to share pipelining registers
Got it, but I feel like we could solve this on a more fundamental level by improving CSE.
At any rate, I'd be hesitant to put this in clash-prelude as it feels both highly specific and subject to change on future insights / compiler improvements. Did you imagine putting this in a separate package?
Also, I'm not a big fan of idiom brackets, because it changes the meaning of parenthesis inside the bracket. e.g. ([(f a b) c d])
means something else than ([f a b c d])
because the the former desugars to fmap (f a b) <*> c <*> d
and the latter to fmap f <*> a <*> b <*> c <*> d
I imaged it living in a separate package, definitely not in clash-prelude
. Also, the proposal is far from finished.
Ah ok, in that case I'm perfectly fine with the proposal :+1:
Hi Christiaan,
Thanks for proposing this extension, which I think is needed.
I don't understand the full ins and outs yet because it's not clear which notation is standard non-standard Haskell (a.k.a ghc extensons) and which notation is non-standard non-standard notation.
You're using @x
to indicate levels in the pipeline. This seems too complex to me. Can't we use types to denote the values at the different levels? E.g. (d0,x)
would be an input value, (d1,y)
a value at level 1, and so on. We could then define operations on such values, such that e.g. f <$> (i1,a) (i2,b)
evaluates to (d1 + max 1i i2,f a b)
.
You could then use these operations as building blocks to define the pipeline. Just a thought.
Regards,
Paddy
annotating every binder seems quite cumbersome, I think linking our approaches: My:
pipeline \a b p q r s -> do
@3
let x = f a b
let y = g p q
let z = h r s
bundle (x,y,z)
would I think translate to your:
\(d0,a) (d0,b) (d0,p) (d0,q) (d0,r) (d0,s) ->
let (d3,x) = f a b
(d3,y) = g p q
(d3,z) = h r s
in (d3,(x,y,z))
or annotating at the type-level:
\(a :: d0) (b :: d0) (p :: d0) (q :: d0) (r :: d0) (s :: d0) ->
let (x :: d3) = f a b
(y :: d3) = g p q
(z :: d3) = h r s
in (x,y,z) :: d3
The thing is, if you do annotation at the type-level, then you need to annotate every binder. Whereas "my" approach using scoping to "annotate" all the relevant bindings. Also, the point of my proposal was not to calculate the delay of a computation, it is to automatically add pipeline registers to make sure everything is synchronised.
It has to be taken care of at the use site, because a value might be used at 2 places, where you want to share pipelining registers
I'm wondering if the QualifiedDo notation for an Indexed Monad could help achieve the same thing, and thereby alleviating @martijnbastiaan concerns?
NB: this text is work in progress Inspired by TL-Verilog (https://www.redwoodeda.com/tl-verilog, https://www.makerchip.com/, http://www.tl-x.org/), I want to propose an extension to Clash for pipelined designs. It will need GHC source plugins (perhaps combined with contraint-solver plugins) to work.
Inital example
The following code:
which would desugar to:
where
Using a pipelined circuit
Assume that:
we would then write:
which would desugar do:
Using values from a particular pipeline stage
looking back (relative offset)
would desugar to:
where
looking ahead (relative offset)
would desugar to:
where
looking at an exact stage
would desugar to:
and
would desugar to:
Using choice constructs (case-expressions, guards, etc.)
desugars to:
e.g. we abstract over all pipelined values to create a pure function, and then apply that function applicatively over the pipelined values.
Using non-pipelined arguments
desugars to:
i.e. non-pipelined arguments are to be single values, not streams of values. So you get a type error if a non-pipelined arguments is of type
Signal
orDSignal