miek__[m] has quit [Quit: Idle timeout reached: 172800s]
jer_emy[m] has quit [Quit: Idle timeout reached: 172800s]
nelgau_ has quit [Ping timeout: 255 seconds]
pie_ has quit [Server closed connection]
pie_ has joined #amaranth-lang
zyp[m] has joined #amaranth-lang
<zyp[m]>
it'll break in unintuitive ways as soon as you try using it in a different clock domain
frgo has quit [Quit: Leaving...]
<key2>
@ktemkin: just read. this is a good news.
<key2>
I hacked something equivalent just for my own need that was not PRable, and I always postponed cleaning it. But yours is done the right way
nelgau has joined #amaranth-lang
<whitequark[cis]>
<tpw_rules> "will i get arrested for this..." <- yeah
<ktemkin>
tpw_rules: if you want a plea bargain on that code, pass in the module and domain name explicitly, and either use m.d[name] or put a domain transformer on the end
<ktemkin>
(and maybe don’t use names that overlap with the formal verification equivalents)
notgull has quit [Ping timeout: 255 seconds]
<whitequark[cis]>
something I want to eventually have is a way to make "functions", kind of like Verilog functions but better
<whitequark[cis]>
where it would be a decorator that basically transparently adds a submodule
<whitequark[cis]>
(potentially a submodule only for clock domain reasons, not visible in the hierarchy; so more like a toplevel scopr)
<whitequark[cis]>
s/scopr/scope/
notgull has joined #amaranth-lang
nelgau has quit [Read error: Connection reset by peer]
<tpw_rules>
ktemkin: the thing is those are being killed and i miss them :(
<tpw_rules>
i never got proficient enough in verilog to use functions but that does sound nice
<whitequark[cis]>
they're being killed because they're too difficult to use correctly
nelgau has joined #amaranth-lang
<whitequark[cis]>
the exact behavior of an edge detector, including all the edge cases like power-on reset, domain reset, and so on, is specific to a particular call site, and in formal context you want something slightly different than in general context
<tpw_rules>
i skimmed the issue thread and it sounded like the problem was specifically about formal and interaction with yosys? like if it's just the same name but it resolves to actual logic then that seems better
<whitequark[cis]>
also Past slows down elaboration for everyone by some tens of % probably
<tpw_rules>
i think i did discover that site specificity with my reset=1 though...
<whitequark[cis]>
yes
<whitequark[cis]>
also, Past doesn't work for expressions because of the way it's implemented
<whitequark[cis]>
it only works for a signal, and it does weird clock domain sniffing that isn't used anywhere in the language
<whitequark[cis]>
it's what Yosys does for $past but it just doesn't fit Amaranth
<tpw_rules>
i understand why it's all leaving amaranth
<whitequark[cis]>
how about something like this...
nelgau has quit [Read error: Connection reset by peer]
<whitequark[cis]>
this should give you all of the power you want, without any of the weird stack stuff
<tpw_rules>
i hadn't thought about setting up a module in __init__, thanks
<tpw_rules>
(why might one not just pass m to EdgeDetector?)
<tpw_rules>
and not have it be an Elaboratable
<whitequark[cis]>
because that breaks if you use it within an with m.If block
<whitequark[cis]>
people try to do things like def rose(m, value): m.d.sync += but these are subtly wrong in a very difficult to find way
nelgau has quit [Ping timeout: 240 seconds]
<tpw_rules>
why is that?
<whitequark[cis]>
the register won't be updated unless the m.If condition holds
<tpw_rules>
(because i definitely tried that earlier)
<whitequark[cis]>
so it's not "the value has rose on this cycle" but rather "the value has rose on this cycle, if you ignore every intervening cycle where the condition doesn't hold"
<tpw_rules>
oh, that makes total sense
<tpw_rules>
so the detector needs its own module to, if you will, be in a separate flow control stack
<whitequark[cis]>
the trickery that Past/Rose/Fell use in formal avoids that, but it introduces its own issues, like the reset complications
<whitequark[cis]>
tpw_rules: yes. if Amaranth had first-class scopes (something I've considered) it wouldn't have to be an entire module
nelgau has joined #amaranth-lang
nelgau has quit [Ping timeout: 258 seconds]
nelgau has joined #amaranth-lang
nelgau_ has joined #amaranth-lang
nelgau has quit [Read error: Connection reset by peer]
ravenslofty[m] has quit [Quit: Idle timeout reached: 172800s]
<whitequark[cis]>
good evening everyone, it is time for our regularly scheduled Amaranth meeting
<whitequark[cis]>
it does not appear that we have anything on the agenda
<whitequark[cis]>
I am happy to spend this hour answering general questions in that case
<galibert[m]>
Should we have a past/etc that works only for formal (like Assert & co) and has no impact on the netlist?
<galibert[m]>
General question: what's missing for 0.4?
<galibert[m]>
amaranth really deserves a release :-)
<whitequark[cis]>
<galibert[m]> "Should we have a past/etc that..." <- I don't know how would that work, considering that formal is done via netlist conversion
<whitequark[cis]>
galibert[m]: yes
<whitequark[cis]>
I think Wanda has been working on #778
<whitequark[cis]>
#931 is a straightforward version bump
<galibert[m]>
#790 doesn't look easy
<whitequark[cis]>
it's not
<whitequark[cis]>
but it's quite essential
<galibert[m]>
Yeah, I hit it too iirc
<galibert[m]>
We talked about it a bit, it kinda goes with the ability of a decoder to produce multiple flows, but that doesn't go down nicely into cxxrtl
<galibert[m]>
it's probably not possible to use the same code for both anyway
<galibert[m]>
Would optional methods on the ValueCastable a sane path?
<galibert[m]>
* the ValueCastable be a sane
<whitequark[cis]>
it should be determined by the shape
<whitequark[cis]>
signals have shapes and are what go into the vcd; not values
<galibert[m]>
Optional methods on the ShapeCastable then?
<whitequark[cis]>
yeah. I'm not sure how optional
<whitequark[cis]>
but it would be a method on ShapeCastable
<galibert[m]>
There are two things I think for a "decoder" method
<galibert[m]>
There's slicing into multiple signals/flows/streams/whatever name is used, and there's interpretation (to turn it into a string, a floating point value, etc)
<whitequark[cis]>
yeah
<galibert[m]>
Slicing could probably be called only once and go down to cxxrtl, interpretation needs to be called on value change and won't go down easily
<galibert[m]>
Perhaps it should be two different things
<zyp[m]>
sorry, I forgot the meeting, I'm here
nelgau_ has quit [Read error: Connection reset by peer]
<Wanda[cis]>
<whitequark[cis]> "I think Wanda has been working..." <- I ... looked at it and had some thoughts about it, but I can't say I worked on it
nelgau has joined #amaranth-lang
<whitequark[cis]>
<galibert[m]> "Slicing could probably be called..." <- hmm, I'm not sure if I see it the same way
<Wanda[cis]>
I may work on this when I feel a little more uh coherent
<whitequark[cis]>
I think "slicing" is a pretty narrow way to view it, and it's more that presentation of a single signal can imply having multiple rows in the vcd or what you have
<galibert[m]>
Slicing is not a good term
<galibert[m]>
I mean going from a shaped object to a list of named traces with the associated signal (which could be a Cat of multiple things)
<galibert[m]>
IOW the part one could generate c++ for
<zyp[m]>
maybe ShapeCastable needs a method to declare what rows to have in the vcd, and ValueCastable needs a method to format the individual values
<galibert[m]>
zyp: that wouldn’t work out well for cxxrtl
<whitequark[cis]>
galibert[m]: I'm not sure if this helps a lot?
<whitequark[cis]>
maybe I don't entirely understand
<galibert[m]>
well, to do a very simple example, a struct { u8 r, g, b} ends up as a Signal(24). The "slicing" method could return something like { "r": view.s[16:24], "g": view.s[8:16], "b": view.s[0:8] }
<galibert[m]>
that could be used to generate c++ code to get the fields or a struct, and on the python side to get 3 traces
<galibert[m]>
Between Cat and slices you can build quite a lot of stuff while staying into something you can interpret in both the python and c++ sides
<galibert[m]>
not sure how clear I am
<whitequark[cis]>
hmm
<whitequark[cis]>
no, this makes sense. I'll need to think about it. I think this idea has good potential
<zyp[m]>
sounds reasonable to me
<galibert[m]>
Cool. Probably needs more thinking about, but I think you got the gist
<whitequark[cis]>
yeah
chaoticryptidz has quit [Ping timeout: 252 seconds]
chaoticryptidz has joined #amaranth-lang
<galibert[m]>
For the interpretation part, I'm not sure one can avoid having specific independant python and c++-code-generation paths
<galibert[m]>
(turning an Enum into a c++ enum, a fp value into a float, etc)
<whitequark[cis]>
cxxrtl doesn't generate c++ enums for HDL enums, or floats
<whitequark[cis]>
it just generates appropriate metadata which can then be read by the VCD writer
<whitequark[cis]>
(floats in particular would be pretty cursed)
<galibert[m]>
Ah, I tend not to use cxxrtl for generating vcds
<galibert[m]>
But do generate more interesting things like images
<galibert[m]>
btw, can cxxrtl do multi-domain?
<whitequark[cis]>
sure
<galibert[m]>
if I have a 135Mhz domain (hdmi generation) and a 12Mhz one (emulation) with a memory block in the middle I can run both in alternance?
<whitequark[cis]>
sure
<whitequark[cis]>
as long as the domains are asynchronous everything just works
<galibert[m]>
This is relevant to my interests
<whitequark[cis]>
things start to get problematic if you have two domains that are phase-related
<galibert[m]>
I don't see how different it is?
<whitequark[cis]>
specifically if (a) domain A's frequency is a multiple of domain B's, and (b) there is no CDC between them
<whitequark[cis]>
s/CDC/synchronizers/
<galibert[m]>
ah, when they coincide?
<whitequark[cis]>
the simulation becomes unphysical in a way real circuitry is not in this case, and some uses break
<whitequark[cis]>
yes
vegard_e[m] has joined #amaranth-lang
<vegard_e[m]>
being a simulator, wouldn't they stay perfectly in phase?
<whitequark[cis]>
vegard_e: they do... sort of
<whitequark[cis]>
consider that in a simulator, time is not real, and what is real is ticks (which can be zero time too)
<galibert[m]>
vegard_e: the generated code which considers domains independant can't handle having two domains tick at the exact same time
<whitequark[cis]>
and the number of ticks isn't guaranteed, nor is exactly when logic is updated on a tick
<whitequark[cis]>
(only that all flops wired to the same clock wire tick simultaneously)
<whitequark[cis]>
* (only that all flops wired to the same clock wire change values in the same tick)
<whitequark[cis]>
so you can have race conditions within zero physical time, which is annoying
<zyp[m]>
ah, right, so you get like a Settle() between the two clocks, even though they happen at the same time
<whitequark[cis]>
you can get like a Settle(), yes
<whitequark[cis]>
whether you will or not depends on the netlist in a subtle way
<galibert[m]>
And yes, you can build those kind of domains with perfectly synchronous different frequencies on say a Cyclone V pll, which has 9 counters with independant warp and phase connected to the same pll loop
<whitequark[cis]>
and also on pass options
<zyp[m]>
does cxxrtl handle logic driven clocks?
<whitequark[cis]>
it does not reject logic driven clocks
<zyp[m]>
e.g. if I have a module implementing a SPI controller, can I clock another right off sclk?
<whitequark[cis]>
the behavior you get is currently unspecified
<whitequark[cis]>
if you do not make assumptions about the phase of the clocks relative to another clock, everything should just work
<zyp[m]>
it's a bit moot, I imagine a simulated SPI device would more likely be a python testbench function anyway
<galibert[m]>
If you do make assumptions and avoid CDCs otoh... poof
<whitequark[cis]>
for SPI, you should also note that the "setup requirement" of the COPI flop is zero physical time but nonzero ticks
<whitequark[cis]>
so you probably want to capture on the other edge or something
<zyp[m]>
yeah
<whitequark[cis]>
well, you want to output and capture on different edges
mcc111[m] has quit [Quit: Idle timeout reached: 172800s]
<zyp[m]>
I need to find time to play a bit more with the async simulator interface, the `await trigger & condition` stuff have been bouncing around in the back of my head for a while
<ktemkin>
tpw_rules: as a bit of meta-commentary on that discussion -- I actually find there's value (for your use case) in creating little "macro"-style helpers to make code clearer in the local scope using nested functions
<ktemkin>
they provide something akin to the convenience you want, but they're closed over the right scope, and it tends to make it clear that the clock domain assumptions that are encapsulated into the relevant module also apply to your helper
<ktemkin>
I think of Amaranth as more or less a netlist manipulation toolkit that exists to let you build larger abstractions (and has a "standard library" of abstractions like m.If() that you can take advantage of); and there's definitely value in creating your own python-based-abstractions, like you're trying to do
<tpw_rules>
wouldn't you have to redefine them in every module you use them tehn?
<ktemkin>
yes; but often the abstractions are themselves an encapsulation of a bunch of assumptions about the environment
<tpw_rules>
that's fair
<ktemkin>
it's really about finding the right place to put your abstraction
<whitequark[cis]>
I agree with Kate; this is the style I've gravitated towards as well
<tpw_rules>
so you just have your nested functions in elaborate?
<tpw_rules>
(then they close over m even)
<whitequark[cis]>
often there is a feeling that an abstraction should "obviously" be in the stdlib, but it doesn't really hurt to copy the code around a few times, and it can be clearer and more convenient even
<ktemkin>
and if you wind up finding that you're using it all over the place in a project, I don't think it's taboo to create functions that accept m and then do manipulations on it, and which live on a project-level -- you just want them to match your project-wide assumptions (which are ideally documented)
<ktemkin>
things like Rose/Fell just tend to be encumbered with a bunch of assumptions that you should really think about, and that makes them prime candidates for being written per-elaboratable
<tpw_rules>
so you wouldn't even bother with that EdgeDetector concept?
<tpw_rules>
question i was thinking about wrt expanding it: is there a simple Signal.named_like?
<ktemkin>
it really depends on the scope of what you're doing
<ktemkin>
I wouldn't argue against that EdgeDetector concept per se; but I would stop and think what it's buying you
<ktemkin>
there are a bunch of local-scope things that wind up being assumed in there; like whether you care about a stray detection on the first cycle
<ktemkin>
and a bunch of local-scope things that have to be hoisted to arguments (like the domain you're working with)
<ktemkin>
in that case, the assumptions you're building in or working around are large enough that it makes more sense to me to not try and be general-case
<ktemkin>
don't get me wrong -- when I'm writing code that's just for me for quick prototyping, or where I'm targeting a function for a very specific set of clock domains in a very specific project, I'll do things like this: https://usercontent.irccloud-cdn.com/file/YhyxeAEy/image.png
<ktemkin>
code truncated, but the point is that I'm doing something that'd be general-case horrible but which is fine when carefully scoped
<ktemkin>
[also, as a rule: if the code is just for you, and it sparks joy to do things a particular way, ignore me and do that =P]
nelgau has quit [Read error: Connection reset by peer]
nelgau has joined #amaranth-lang
<whitequark[cis]>
personally I don't view functions on m as OK because calling one from within an m.If or m.Case block can easily have unintended, hard to understand effects
<whitequark[cis]>
in some cases, for example factoring out common behavior in FSM states, they are clearly and unambiguously acceptable (i.e. where `m.d.<domain> +=` statements are the specific desirable outcome of running it)
<whitequark[cis]>
but in cases where the `m.d.<domain> +=` statements are coincidental to the purpose of the function, I see them as a "trip hazard"
<whitequark[cis]>
i.e. as long as you are the only one working on the code and you know what you're doing and you test it, it's fine
<whitequark[cis]>
however whenever others (which could include you years later) rearrange that code in ways that seemingly do not change functionality, but neglect to respect the invariant that the function must be called from the toplevel scope, they could trip and fall and that would be not great
<galibert[m]>
Yeah, the common behaviour case has no subtlety whatsoever
<whitequark[cis]>
I'm thinking of adding two decorators
<whitequark[cis]>
one blesses the common behavior case
<whitequark[cis]>
another makes it so that everything that happens within a function gets added to the toplevel of the module instead of in the current scope
<whitequark[cis]>
and then prohibit (with a warning, not error) accessing m except from the function where it was created and the functions that are decorated
<whitequark[cis]>
what do yall think?
<galibert[m]>
I have no idea how decorators work
<tpw_rules>
that sounds nice
<tpw_rules>
is the common behavior case the one where the scope is not reet?
<tpw_rules>
reset*
<zyp[m]>
galibert[m]: `@foo; def bar(): …` is effectively just a shorthand for `def bar(): …; bar = foo(bar)`
<zyp[m]>
i.e. it's a way to put various wrappers around functions
<crzwdjk>
Seems like a good idea to me, I've felt the need for helper methods once in a while and it's good to have officially sanctioned suggestions for how they should be used
<crzwdjk>
E.g. I definitely have an FSM helper in my codebase somewhere, it's very helpful for parsers.
<zyp[m]>
<whitequark[cis]> "what do yall think?" <- where does methods on an elaboratable that's only called from `elaborate()` fit?
<whitequark[cis]>
that's orthogonal
smkz has quit [Quit: smkz]
<zyp[m]>
e.g. like how LUNA control handlers tend to put each request handler in a method taking m
<whitequark[cis]>
the important bit is how they relate to the Module, not how they relate to elaborate
<whitequark[cis]>
because Module controls scoping and elaborate is incidental to the whole thing
smkz has joined #amaranth-lang
jjsuperpower has joined #amaranth-lang
<crzwdjk>
How does connect(m, ...) fit into all of this btw, if at all?
<galibert[m]>
Well, if your function doesn’t return anything there’s a fair chance you can’t fuck it up
<galibert[m]>
Because you can’t really use it in a If, a Switch or that kind of stuff, and that’s where the problem lies
<galibert[m]>
connect doesn’t return anything
<crzwdjk>
I think the issue with the m.If is putting things in the body of the "with m.If()" block. Then it ends up enabled conditionally where maybe that is not what the user is expecting.
<galibert[m]>
Don’t think so. I expect it to be conditional, as anything that’s in an if
<crzwdjk>
Right, but if you put an edge_detect(m, sig) in there, that's not going to work right
<galibert[m]>
It’s with m.Switch(obj.next(m)): where next chsnges obj that’s unpredictable (for instance)
<zyp[m]>
for the LUNA usage I mentioned above, using it conditionally is the point, each request handler function is conditional on that request currently being handled
<galibert[m]>
Well m.d.comb += s.eq(edge_detect(…)) is going to work
<galibert[m]>
with m.If(edge_detect(…)) is what breaks
<galibert[m]>
No assignments within the expression of a control statement
<tpw_rules>
that does (seem to) work fine
<tpw_rules>
i think it's about being within the body
<galibert[m]>
Well, ask Cath when she’s around
<galibert[m]>
(We already had that discussion her and I)
<whitequark[cis]>
<crzwdjk> "I think the issue with the m...." <- yes
<whitequark[cis]>
depending on the type of the thing, enabling it conditionally will be expected or not expected
<whitequark[cis]>
<crzwdjk> "How does connect(m, ...) fit..." <- `connect` forces `m.comb` (this is important for connections to actually work; something like `m.d.comb += connect(...)`, like with records, is not a good idea because `m.d.sync += connect(...)` makes no sense usually), but other than that, you can just make conditional connections
<whitequark[cis]>
such as in bus decoders
<crzwdjk>
Yes, that is how I expect to use connect
<ravenslofty[m]>
It's official: iCE40, ECP5, and Gowin all use ABC9 by default. So, I guess, if your designs are all suddenly worse, feel free to file bugs or ping me.
<crzwdjk>
I tried building my iCE40 design with the new ABC9 mode and the final result was marginally worse. But only slightly, so I don't think it matters much.
<whitequark[cis]>
fmax or area?
adamgreig[m] has joined #amaranth-lang
<adamgreig[m]>
woo, thanks ravenslofty!
* adamgreig[m]
deletes "-abc9" from all my YOSYS_OPTS
<ravenslofty[m]>
As wq asks: Fmax or area. It's entirely possible that area is going up because ABC9 has found a higher-Fmax mapping of your design, which translates to there being less slack available for area recovery. There's a workaround, although it's clumsy
<crzwdjk>
Both, but very slightly. Before: 1046 LUTs, fmax = 58.62, after (ABC9): 1079 LUTs, fmax = 58.54
<whitequark[cis]>
that fmax difference is within noise
<adamgreig[m]>
yea, try with a handful of different seeds too
<adamgreig[m]>
I usually run a couple hundred rounds and pick the "best", there's often a significant spread
<adamgreig[m]>
also I have a lot of cores and nextpnr is not very parallel so...
<crzwdjk>
That's good to know
<zyp[m]>
adamgreig[m]: do you have a good way to automate that?
<adamgreig[m]>
well, I have a way to automate it, idk about good, it hasn't been touched in 2y and isn't exactly well documented, but it sure beats manually running a bunch of nextpnr commands
<adamgreig[m]>
I wanted to build it into nextpnr instead, but it was a bit tricky
<crzwdjk>
Handy because I keep bumping up against the point where my design just barely fails timing because the iCE40 UP is slow
<whitequark[cis]>
it could probably be another binary invoking nextpnr
<whitequark[cis]>
ice40up is just really bad
<adamgreig[m]>
yea, but if you can buy yourself 10MHz more timing by running a hundred seeds, it's a decent win
<adamgreig[m]>
Catherine: I mean, that's basically what mine is, it's a python script that wraps nextpnr
<adamgreig[m]>
I just put it in my path and run it as "multipnr"
<zyp[m]>
I feel like given enough feature creep, any design will grow to the point where it struggles to meet timing or fit
<adamgreig[m]>
but there's no reason nextpnr couldn't take --n-seeds=100 and split into threads itself
<adamgreig[m]>
conceivably it could then even share the common work like parsing inputs and whatever
<adamgreig[m]>
it's not clear what it should do with the outputs though... I generally just want to keep the best one, but be told what the timing spread was
<zyp[m]>
best is a bit ambiguous when you've got multiple domains
<adamgreig[m]>
yea, that too
<adamgreig[m]>
so, it got a bit complicated and I stuck to my fairly dumb python script (which does report all domains, and iirc says which seed was best for each domain)
<whitequark[cis]>
I think it makes no sense to do n runs and pick best