-
Notifications
You must be signed in to change notification settings - Fork 154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Configurable initial register values #527
Conversation
Could the domain also encompass the reset behaviour? I've always felt uneasy about the ability to connect up logic on the same clock, with different resets, which are nominally in the same domain. I think would would make reasoning about reset behaviour over any domain easier. |
I think so. If I understand you correctly, this would induce the following change: data Domain name period initKind edgeKind
= Domain { domainName :: SSymbol name
, domainPeriod :: Period name period
, domainInit :: Init name initKind
, domainEdge :: Edge name edgeKind
+ , domainReset :: ResetNess name resetKind
} As a bonus, we could freely add reset polarity too: data ResetPolarity
= ActiveHigh
| ActiveLow -data Reset (domain :: Symbol) (synchronous :: ResetKind) where
- Sync :: Signal domain Bool -> Reset domain 'Synchronous
- Async :: Signal domain Bool -> Reset domain 'Asynchronous
+data Reset (domain :: Symbol) (polarity :: ResetPolarity) where
+ Reset :: Signal domain Bool -> Reset domain 'ActiveHigh
+ InvReset :: Signal domain Bool -> Reset domain 'ActiveLow
|
Where data ResetKind
= Synchronous
| Asynchronous |
data ResetKind
= Synchronous
| Asynchronous
| None Although i don't know how that works out in practice. Maybe just the 2 is saner |
3 options is hard, because you can't (easily?) write a function which "can deal with Synchronous and Asynchronous, but not with None". I'm having the same implementation problem with: data EdgeKind
= Rising
-- ^ Registers are rising edge (low-to-high) sensitive
| Falling
-- ^ Registers are falling edge (high-to-low) sensitive
| Double
-- ^ Registers are sensitive to both a rising edge (low-to-high) and a
-- falling edge (high-to-low). [Not supported yet.] I have to think about it some more. |
Double is a bit weird. |
We know at least some interest exists to use Clash for ASICs so I don't think we should only focus on FPGAs :). Anyway, I mainly added it to see how much sense it makes to introduce 3 options in some domain property. And so far I still don't know, so I'll keep experimenting. |
We have some DDR primitives, their DDR signals might be annotated with edgekind Double. |
After an offline discussion with @oliverbunting, we concluded that we just need a |
If you want to describe a function that could be active on either edge, but not both, maybe something like: data ActiveEdge
= OneEdge Edge
| BothEdges
data Edge = Rising | Falling
f :: KnownEdge dom ('OneEdge edge)
=> Signal dom a -> Signal dom a |
I've confirmed something like that works @leonschoorl. The jury is out on its prettiness :P. |
My initial thoughts: I'm really not liking even more |
But I'd rather discuss this when I'm back in the office to understand the rational behind the current design. |
I'm open to suggestions! Although I don't think the |
Alright, didn't see your second comment just now. I'm fairly convinced that phantom types can't really work (conveniently), so I'll still try to write up the process tomorrow. If it fails we can try it face to face when you're back (and I'm back from the UK too, so probably in 2.5 weeks). |
@martijnbastiaan my apologies, I should've waited with reacting before reading the entire PR properly. When I saw all the new
The same can be done for the
|
Another issue really needs solving properly is: old
vs new:
where in the old/current API, when you use this function in a monomorphic context, and the periods do not exactly differ by with the new proposed API you simply propagate the constraint:
and what's now stopping me from assigning period 4 to "DomA" in one part of my design, and period 5 in another part? Using This could never happen in the old/current design, because the period was an integral part of the domain annotation on Signals. I don't think I can accept an API where the above descirbed situation can legally happen. |
Right, I had considered putting them in as "normal" values, but -ironically- thought that wouldn't have a chance of passing the Christiaan test :P. I'm slightly hesitant of it too, considering that you would store period/init/edge-iness in various places without type guarantees.
The direct answer to this question is: we can reliably track what domains we've seen in the Clash compiler, and generate an error if we see any differences between domains with the same name. As opposed to The indirect answer is that using f :: Clock (domain :: NameAndPeriod) 'RisingEdge (gated :: ClockKind)
-> Signal domain Int
g :: Clock (domain :: NameAndPeriod) 'FallingEdge (gated :: ClockKind)
-> Signal domain Int Now, you'd like to write: h :: Clock (domain :: NameAndPeriod) edge (gated :: ClockKind)
-> Signal domain Int I don't think you can do it without resorting to Last but not least, and I don't want to drag oude koeien uit de sloot, I think the current design avoids the conceptual problem of "where to put the To complete my sales pitch: I think the current PR proposes a method that's safe, easy to understand, easy to extend and implement. It only needs a single modification to Clash itself (that is, domain tracking). If a user manages to do something wrong, they'll get a clear and easy to understand error. |
I thought of a solution yesterday, but was too tired to go back on the laptop again.
Now there can be only 1 period per domain; and we could do the same for the active clock, etc. Also, the only safe way to construct a |
That's an option @leonschoorl and I discussed, but for some reason we decided not to go with that one. I can't remember if we had a good reason or not. I'll see if it come back to me in the following days. Have a nice weekend in SF :)! |
I did manage to think of the reasons we shot it down, but after rereading your comment I realize the designs are slightly different, so they don't really apply. I do think the
A minor complaint is that I don't think using instances (with domains leaking into a global namespace, even if you don't end up using it in your design) is the most elegant. It's not something I really care about though, as I doubt it would be a problem in practice. Hopefully we can resolve this before Monday so I can freely work on it :) |
clock period and active flank really need to be globally unique for a domain, so a normal type-class (constraint) is really the proper solution is that part of the design space. |
This is too big of a change to be resolved before monday, as I really want to have a proper in-person discussion; and I won't be in until tuesday morning. Changing the core API simply requires more consideration and design iterations than other features and shouldn’t be rushed. |
Here's some of my initial test code, but requires more experimentation: https://gist.github.com/christiaanb/8eac9bc72fc2bf44e9cce2a8499a4db8 I has:
Still needs:
|
Put it in
Move As far as I can tell, your proposal is what I've proposed here, with the Known-constraints consolidated in a single construct *and implemented with instances. Could you confirm this? Either way, it's fine by me, but like I said earlier, you'll lose:
This could be solved by reintroducing And you'll also lose the ability to have dynamic subdomains (that is, part of your design with an "generated" modification to the current configuration), because you can't do hidden class instances. This can be solved, if you wanted to, by using a |
One more thing
I've experimented with this and it doesn't seem an issue in practice if GHC can't. You almost always pass a "real" argument anyway (mostly |
b0beed2
to
01169c4
Compare
01169c4
to
757019c
Compare
Reconfigurable powerup values warrent a new version number
Co-Authored-By: Leon Schoorl <[email protected]>
Haddock will turn "Foo" into a link to the module Foo, even if such a module doesn't exist. [skip ci]
de84402
to
512b776
Compare
Enable doesn't have it, so Reset should need it either. Clock also contains a singleton, but it uses it for its Show instance.
512b776
to
c7d2dc2
Compare
Before this commit, 'withReset' would behave very surprisingly in combination with 'simulate' or 'sample': >>> rst0 = fromList [True, True, False, False, True, True] >>> rst1 = unsafeFromHighPolarity @System rst0 >>> sig = withReset rst (register 'a' (pure 'b')) >>> sampleN @System 6 sig "aabbbb" Even though `rst1` is asserted on the 5th and 6th clock cycle, we never see the reset value, `a`, after the initial two. But even the first two `a`s are suspicious. We'd expect to see three `a`s (one powerup, two reset) instead! It seems that 'sampleN' is generating a default reset value and using that one. Indeed, when we inspect the type of `sig` we see it is as we never applied the reset at all: >>> :t sig (HiddenReset dom, ..) => Signal dom Char To figure out what's going on, let's inspect the type signature of 'withReset': withReset :: KnownDomain dom => Reset dom -> (HiddenReset dom => r) -> r It turns out that GHC is perfectly happy to /only/ resolve `r` when given the following as its second argument: reg :: (HiddenReset dom, ..) => Signal dom Char reg = register 'a' (pure 'b') i.e., `withReset` would actually resolve to something like: withReset* :: forall dom r . KnownDomain dom => Reset dom -> (HiddenReset dom => (HiddenReset dom0 => Signal dom0 Char)) -> (HiddenReset dom => Signal dom0 Char) thus completely discarding its given reset, and leaving it up to `sample` to actually generate the reset. The reason GHC is free to do this, is that nothing in `r` ties `dom` to the "dom inside `r`" simply because it's not mentioned in the type signature. To solve this, this commit introduces a typeclass `HasDomain` that forces a link between the given `dom` and the "dom inside `r`". The type signature of 'withReset' changes only slightly: withReset :: (KnownDomain dom, HasDomain r, GetDomain r ~ dom) => Reset dom -> (HiddenReset dom => r) -> r With this patch, 'withReset' behaves exactly like we expect it would: >>> rst0 = fromList [True, True, False, False, True, True] >>> rst1 = unsafeFromHighPolarity rst0 >>> sig = withReset rst1 (register 'a' (pure 'b')) >>> sampleN @System 6 sig "aaabaa" We haven't got a good answer for bundled types yet. _Ideally_ we'd be able to write something like instance HasDomain (Unbundled dom a) where type GetDomain (Unbundled dom a) = dom but we can't, as _Unbundled_ in itself is an associated type. To those wondering why this issue didn't rear its head the moment we introduced hidden clocks: this issue has only been introduced a few days ago after merging PR #527 (configurable initial register values). Before that PR, only a single hidden clock/register could be used - a fact that GHC allowed to resolve the constraint early. Co-Authored-By: Leon Schoorl <[email protected]>
Before this commit, 'withReset' would behave very surprisingly in combination with 'simulate' or 'sample': >>> rst0 = fromList [True, True, False, False, True, True] >>> rst1 = unsafeFromHighPolarity @System rst0 >>> sig = withReset rst (register 'a' (pure 'b')) >>> sampleN @System 6 sig "aabbbb" Even though `rst1` is asserted on the 5th and 6th clock cycle, we never see the reset value, `a`, after the initial two. But even the first two `a`s are suspicious. We'd expect to see three `a`s (one powerup, two reset) instead! It seems that 'sampleN' is generating a default reset value and using that one. Indeed, when we inspect the type of `sig` we see it is as we never applied the reset at all: >>> :t sig (HiddenReset dom, ..) => Signal dom Char To figure out what's going on, let's inspect the type signature of 'withReset': withReset :: KnownDomain dom => Reset dom -> (HiddenReset dom => r) -> r It turns out that GHC is perfectly happy to /only/ resolve `r` when given the following as its second argument: reg :: (HiddenReset dom, ..) => Signal dom Char reg = register 'a' (pure 'b') i.e., `withReset` would actually resolve to something like: withReset* :: forall dom r . KnownDomain dom => Reset dom -> (HiddenReset dom => (HiddenReset dom0 => Signal dom0 Char)) -> (HiddenReset dom => Signal dom0 Char) thus completely discarding its given reset, and leaving it up to `sample` to actually generate the reset. The reason GHC is free to do this, is that nothing in `r` ties `dom` to the "dom inside `r`" simply because it's not mentioned in the type signature. To solve this, this commit introduces a typeclass `HasDomain` that forces a link between the given `dom` and the "dom inside `r`". The type signature of 'withReset' changes only slightly: withReset :: (KnownDomain dom, HasDomain r, GetDomain r ~ dom) => Reset dom -> (HiddenReset dom => r) -> r With this patch, 'withReset' behaves exactly like we expect it would: >>> rst0 = fromList [True, True, False, False, True, True] >>> rst1 = unsafeFromHighPolarity rst0 >>> sig = withReset rst1 (register 'a' (pure 'b')) >>> sampleN @System 6 sig "aaabaa" We haven't got a good answer for bundled types yet. _Ideally_ we'd be able to write something like instance HasDomain (Unbundled dom a) where type GetDomain (Unbundled dom a) = dom but we can't, as _Unbundled_ in itself is an associated type. To those wondering why this issue didn't rear its head the moment we introduced hidden clocks: this issue has only been introduced a few days ago after merging PR #527 (configurable initial register values). Before that PR, only a single hidden clock/register could be used - a fact that GHC allowed to resolve the constraint early. Co-Authored-By: Leon Schoorl <[email protected]>
Before this commit, 'withReset' would behave very surprisingly in combination with 'simulate' or 'sample': >>> rst0 = fromList [True, True, False, False, True, True] >>> rst1 = unsafeFromHighPolarity @System rst0 >>> sig = withReset rst1 (register 'a' (pure 'b')) >>> sampleN @System 6 sig "aabbbb" Even though `rst1` is asserted on the 5th and 6th clock cycle, we never see the reset value, `a`, after the initial two. But even the first two `a`s are suspicious. We'd expect to see three `a`s (one powerup, two reset) instead! It seems that 'sampleN' is generating a default reset value and using that one. Indeed, when we inspect the type of `sig` we see it is as we never applied the reset at all: >>> :t sig (HiddenReset dom, ..) => Signal dom Char To figure out what's going on, let's inspect the type signature of 'withReset': withReset :: KnownDomain dom => Reset dom -> (HiddenReset dom => r) -> r It turns out that GHC is perfectly happy to /only/ resolve `r` when given the following as its second argument: reg :: (HiddenReset dom, ..) => Signal dom Char reg = register 'a' (pure 'b') i.e., `withReset` would actually resolve to something like: withReset* :: forall dom r . KnownDomain dom => Reset dom -> (HiddenReset dom => ((HiddenReset dom0, ..) => Signal dom0 Char)) -> ((HiddenReset dom0, ..) => Signal dom0 Char) thus completely discarding its given reset, and leaving it up to `sample` to actually generate the reset. The reason GHC is free to do this, is that nothing in `r` ties `dom` to the "dom inside `r`" simply because it's not mentioned in the type signature. To solve this, this commit introduces a typeclass `HasDomain` that forces a link between the given `dom` and the "dom inside `r`". The type signature of 'withReset' changes only slightly: withReset :: (KnownDomain dom, HasDomain r, GetDomain r ~ dom) => Reset dom -> (HiddenReset dom => r) -> r With this patch, 'withReset' behaves exactly like we expect it would: >>> rst0 = fromList [True, True, False, False, True, True] >>> rst1 = unsafeFromHighPolarity rst0 >>> sig = withReset rst1 (register 'a' (pure 'b')) >>> sampleN @System 6 sig "aaabaa" We haven't got a good answer for bundled types yet. _Ideally_ we'd be able to write something like instance HasDomain (Unbundled dom a) where type GetDomain (Unbundled dom a) = dom but we can't, as _Unbundled_ in itself is an associated type. To those wondering why this issue didn't rear its head the moment we introduced hidden clocks: this issue has only been introduced a few days ago after merging PR #527 (configurable initial register values). Before that PR, only a single hidden clock/register could be used - a fact that GHC allowed to resolve the constraint early. Co-Authored-By: Leon Schoorl <[email protected]>
Updated: 2019 June 14
This is an implementation of configurable initial register values, discussed in #516 and in the comments below. We've settled on the following design:
Domain
, now take a tag of kindSymbol
denoting the domain name. For example,Clock
is now:Clock (dom :: Symbol)
.Domain
is no more, butDomainConfiguration
now exists specified by:KnownDomain
a typeclass with a functional dependency on its name/configuration. Users should create their own domain by using a template functioncreateDomain
:clash-compiler/clash-prelude/src/Clash/Signal/Internal.hs
Lines 492 to 512 in 05d3fa8
A function in need of one of these fields should indicate so by using a constraint
KnownDomain
. AHiddenReset
orHiddenClock
automatically introduces this constraint. Simple one-clock designs can continue to useSystemClockReset
without making any changes.Reset polarity is carried in the domain, as well as its synchronicity. A
Reset
itself is now a very simple datatype.Clock gatedness as a concept does not exist anymore in Clash. Instead, users should use a separate
Enable
line to achieve the same. Almost all components in Clash have an Enable line.To summarize the design's features:
KnownNat
constraints, the newKnownDomain
constraint should feel familiar.AppendSymbol
, these constraints are usable in a multi-clock designs. Coincidentally, we can use this strategy to support hidden clocks / resets in multi-clock designs too!For reviewers:
Roadmap
clash-lib
clash-ghc
clash-cosim
clash-prelude
, so figured I'd get it consistent with our style guide), or revert themHOClock
test to fail. Will be fixed in another PR.createDomain
to create type aliases