<Psentee>
(would m.d.comb += connect() feel less wrong? This is close to result of my random meddling, and it reflects a "just connect the wires" idea)
<whitequark>
the problem with that is that if it's legal, m.d.sync += connect() is necessarily legal too, and it really mustn't be
<cr1901>
What does "ClassName[foo]" do? Call the __dict__ method on ClassName's metaclass?
<WilliamDJones[m]>
What does "ClassName[foo]" do? Call the dict method on ClassName's metaclass? (Sorry for doublepost, thought the bridge was fixed)
<WilliamDJones[m]>
It was replaced with "ClassName(foo)" in the current draft of the RFC, but still curious
<whitequark>
In isn't a class, actually
<whitequark>
so In[x] calls In.__getitem__(x)
<whitequark>
for a class, it would normally call __getitem__ on the metaclass
<WilliamDJones[m]>
yea, __getitem__ is what I meant, thanks.
<whitequark>
however they have added some special method, with a name I don't remember, to handle the now-common case of cls[x] for type annotations
<whitequark>
that lives on the class itself, like __new__ for example
<WilliamDJones[m]>
Oh cool... is the type annotation use case why you changed the [] to () in the RFC (which I assume calls __call__)?
<whitequark>
no, it's to make Out(1, reset=1) possible
<whitequark>
x[1, kw=2] is a syntax error
<WilliamDJones[m]>
Ahhhh
<whitequark>
stares at an implementation
<whitequark>
signature.members.__setitem__ can emit five distinct diagnostics
<charlottia>
<whitequark> "ah yes, __class_getitem__" <- (! TIL, thanks!)
<WilliamDJones[m]>
I guess my only other q about the RFC right now is about this line:
<WilliamDJones[m]>
> While connecting an elaboratable as a whole (as opposed to its sub-interfaces) will rarely, if ever, happen ...
<WilliamDJones[m]>
While you have an example of using connect with sub-interfaces with connect(m, forward(self.bus), self.inner.bus), the guide-level explanation uses whole Elaboratables: connect(m, sink, source). Connecting a sink and source Elaborable directly seems like it'll happen more than rarely to me?
<whitequark>
yea I should fix that
<WilliamDJones[m]>
To be clear: the BusSignature in the forward examples is an interface object (as defined in the RFC), and not just an anonymous or named Signature
<charlottia>
The bus member of the components that's created as a result of the Component wizardry is an interface object, and so can be passed to forward() etc.
<WilliamDJones[m]>
<charlottia> "The bus member of the componen..." <- Okay I see it now, thanks. The `Component` wizardry is adding a layer of `Signature` before returning a `Signature`, at which point the `Component` constructor sees a `Signature` with a lone `Member` `bus`, which is `BusSignature`
<WilliamDJones[m]>
> <@charlotte:hrzn.ee> The `bus` member of the components that's created as a result of the `Component` wizardry _is_ an interface object, and so can be passed to `forward()` etc.
<WilliamDJones[m]>
* Okay I see it now, thanks. The `Component` wizardry is adding a layer of `Signature` before returning a the `signature` attribute (via a `@property`?), at which point the `Component` constructor sees a `Signature` with a lone `Member` `bus`, which is `BusSignature`
<charlottia>
I think that's close! It's important to remember that the variable annotations don't actually do or create anything: that `bus: BusSignature()` is very different from `bus = BusSignature()`.
<WilliamDJones[m]>
* Okay I see it now, thanks. The Component wizardry is adding a layer of Signature before returning a the signature attribute (via a @property?), at which point the Component constructor sees a Signature with a lone Member bus, which has type BusSignature.
<charlottia>
`Component` has (what might be) a `@property` `signature` that its subclasses inherit which enumerates the variable annotations and collects them into a `signature`. So in this case it'd create for you a signature that looks like `signature = Signature({ "bus": BusSignature() })`.
<WilliamDJones[m]>
Yea I don't really "get" PEP 526. Pretty sure you don't have to initialize variables that are initialized using PEP 526 in __init__ and you get objects with the relevant attributes tho.
<charlottia>
I don't think that's true! But let's test.
<charlottia>
The difference between class and instance variable is a bit :/ in Python to begin with, imo.
<charlottia>
Like, imagine the assignment isn't there. What is captain?
<charlottia>
PEP 526 says you're annotating an instance variable.
<charlottia>
You use ClassVar[T] to annotate something as a class variable — iow, in the context of a class, annotations are talking about instance variables by default.
<WilliamDJones[m]>
charlottia: A syntax error :)
<charlottia>
Lol :p
<charlottia>
Not so.
<charlottia>
Variables initialised in the class level like that are put on the __dict__ of the class object itself.
<charlottia>
The only reason that actually remains the case is because they never do e.g. self.stats = .... That would create an entry in that instance's __dict__. They could safely and happily always say self.stats.get(...) or self.stats[...], though they choose not to for clarity's sake, I suppose, and always access it via the class itself.
<charlottia>
Whereas they actually initialise the instance dict entries in __init__. You don't have to, but until you do actually set the attribute on the instance itself, when you do self.blah, it'll be fetching the value from the class's __dict__ instead.
<charlottia>
This is fine for things that can't change in and of themselves, like an int, but if you do this with a list or dict, you might accidentally end up mutating the class's copy that you thought was just a 'default' value, if that makes sense.
<charlottia>
Yep! I can see how it can confuse. Once you build your mental model of the objects and classes from the bottom up, it gets easier to reason about things.
Degi has quit [Ping timeout: 260 seconds]
Degi has joined #amaranth-lang
Bluefoxicy has quit [Ping timeout: 252 seconds]
Bluefoxicy has joined #amaranth-lang
cr1901_ has joined #amaranth-lang
cr1901 has quit [Ping timeout: 260 seconds]
tpw_rules has quit [Ping timeout: 250 seconds]
tpw_rules has joined #amaranth-lang
FFY00_ has joined #amaranth-lang
FFY00 has quit [Ping timeout: 250 seconds]
<fl4shk[m]>
is it possible with this new interfaces thing to connect a parent to a child? as in, `In` of the child is driven by `In` of the parent, and `Out` of the parent is driven by `Out` of the child?
<fl4shk[m]>
<josuah> "FL4SHK: I think that this is..." <- sounds good
<galibert[m]>
yeah, that's forwarding
<fl4shk[m]>
gotcha
<fl4shk[m]>
okay, so
<fl4shk[m]>
> A connection is only made from an output to a matching input, and any other combination is rejected with a diagnostic. This way, connecting a pair of interfaces always leads to the same outcome, regardless of their order.
<fl4shk[m]>
this is in the RFC
<fl4shk[m]>
reading the RFC on my laptop is so much easier than trying to read it on my phone
<fl4shk[m]>
now I'm able to read it better
<fl4shk[m]>
anyway, this all seems good the way it is right now
<fl4shk[m]>
markdown test
<fl4shk[m]>
okay, so that works with the desktop client, but not the Android client for some reason?
<fl4shk[m]>
no idea how that ended up being the case
<galibert[m]>
In this case, amaranth.lib.component.connect(...) won't help, since an output needs to be connected to an output, and an input to an input.
<galibert[m]>
An additional function amaranth.lib.component.forward(obj) is added to assist in this case. It returns a proxy object obj_forward where obj_forward.signature equals obj.signature.flip(), and everything else is forwarded identically otherwise.
<galibert[m]>
*
<galibert[m]>
In this case, amaranth.lib.component.connect(...) won't help, since an output needs to be connected to an output, and an input to an input.
<galibert[m]>
An additional function amaranth.lib.component.forward(obj) is added to assist in this case. It returns a proxy object obj_forward where obj_forward.signature equals obj.signature.flip(), and everything else is forwarded identically otherwise.
<galibert[m]>
* From the forward part of the rfc, that's exactly that, you can't use connect to directly forward stuff:
<galibert[m]>
In this case, amaranth.lib.component.connect(...) won't help, since an output needs to be connected to an output, and an input to an input.
<galibert[m]>
An additional function amaranth.lib.component.forward(obj) is added to assist in this case. It returns a proxy object obj_forward where obj_forward.signature equals obj.signature.flip(), and everything else is forwarded identically otherwise.
<fl4shk[m]>
ah
<fl4shk[m]>
the way I handled that in my version was with an enum as an argument to connect()
<fl4shk[m]>
but a separate function works too
<galibert[m]>
yeah, keeps connect focused
<fl4shk[m]>
I wanted to share logic between the two kinds of connection
<galibert[m]>
you're still using connect, the logic is shared at some level
<fl4shk[m]>
ah
<galibert[m]>
forward is a "magic inverter" that does exactly what you need to do a connect
<fl4shk[m]>
gotcha
<fl4shk[m]>
That's cool too
<galibert[m]>
self.bus and self.inner.bus have identical signatures
<galibert[m]>
yeah, it's a nice design
<fl4shk[m]>
this is actually very similar in functionality to what I implemented
<fl4shk[m]>
so I think great minds think alike
<galibert[m]>
there are not that many possibilities when you're trying to do non-stupid things :-)
<galibert[m]>
not putting it in connect (at least visibly) means you're not going to trigger it by mistake in areas that have nothing to do with forwarding
<fl4shk[m]>
I mean that's true for my way as well I think
<fl4shk[m]>
because you explicitly have to pick whether you want to forward or not :)
<fl4shk[m]>
I do think I'll switch to Amaranth's built-in thing though
<fl4shk[m]>
maybe
<galibert[m]>
I'd recommend switching, if only to bitch on the parts that don't work as well than your stuff so that they can be fixed :-)
<fl4shk[m]>
I could do that
<galibert[m]>
with all respect and politeness of course
<fl4shk[m]>
right
<fl4shk[m]>
I know the answer to this is probably "yes", but can you call connect() on a sub-interface of a higher level interface?
<fl4shk[m]>
my guess is that it should just work
<galibert[m]>
yes
<fl4shk[m]>
great
<galibert[m]>
it's necessary actually, most Components have more than one interface, or have other i/o in addition to the interface
<fl4shk[m]>
yeah
<fl4shk[m]>
though this is actually what my tag system solves
<fl4shk[m]>
you only need one connect() call per pair of interfaces
<galibert[m]>
that... the same here I think
<fl4shk[m]>
no I mean
<fl4shk[m]>
top level interfaces
<fl4shk[m]>
not sub-interfaces
<galibert[m]>
not sure what a top-level interface is
<fl4shk[m]>
like
<fl4shk[m]>
for example
<fl4shk[m]>
say you had a full adder
<fl4shk[m]>
inputs and outputs would both be in the same "top level" interface
<galibert[m]>
my goto example is a i2c engine on wishbone
<galibert[m]>
with a wishbone bus and a (scl,data) pin pair that's probably an interface too
<fl4shk[m]>
okay so what I'm saying is
<fl4shk[m]>
the top-level interface consists of both the i2c engine and the wishbone bus
<galibert[m]>
yep
<galibert[m]>
well, the i2c interface and the wishbone bus interface
<fl4shk[m]>
i2c would have a different tag from wishbone
<galibert[m]>
here is has a different name, like i2c and bus
<galibert[m]>
s/is/it/
<fl4shk[m]>
...that's true
<fl4shk[m]>
I suppose tags are mainly useful for when names in two different interfaces match
<galibert[m]>
they can't there
<fl4shk[m]>
not in that use case
<galibert[m]>
since it's going to be i2c.data and i2c.scl, and bus.data, bus.addr, etc
<galibert[m]>
the data fields, even with the same name, and in different sub-objects
<galibert[m]>
so no collision
<fl4shk[m]>
okay let me give a more concrete example
<galibert[m]>
okay
<fl4shk[m]>
I have a pipelined integer divider
<fl4shk[m]>
each pipeline stage has input data and output data
<fl4shk[m]>
you would want to connect inp.data of the next stage to outp.data of the previous stage
<galibert[m]>
yeah
<fl4shk[m]>
inp is a different name from outp
<fl4shk[m]>
so you could just do two separate connect() calls, sure
<_whitenotifier-6>
[amaranth-lang/amaranth] whitequark b77e33f - Drop support for Python 3.7.
<fl4shk[m]>
I think Python is a really great choice for DSLs honestly
<galibert[m]>
josuah: it's very much a question of what you want to present to the world as "the core language" vs. "add-ons". In my POV, lib.data and lib.component should be considered core language
<galibert[m]>
Catherine is not as convinced, and she's the one who decides
<fl4shk[m]>
I mean I think the fact that they're part of lib is already an indicator of them being implemented on top of the core language
<fl4shk[m]>
not necessarily a 100% indicator
<fl4shk[m]>
If C++ is anything to go by, where there are pieces of the standard library that are really compiler magic...
<galibert[m]>
std::move and std::forward being in std:: is IMNSHO deeply stupid
<fl4shk[m]>
C++ is my "native" programming language
<fl4shk[m]>
I agree with this
<fl4shk[m]>
I've been writing C++ my whole programming life
<galibert[m]>
C++ I started in... 1992 I think
<galibert[m]>
never stopped since
<fl4shk[m]>
haha
<fl4shk[m]>
I haven't been programming for that long
<fl4shk[m]>
but I'm younger than you are
<fl4shk[m]>
I wasn't even alive in 1992
<galibert[m]>
almost everybody is, it's kinda terrifying tbh
<galibert[m]>
(born in '71)
<fl4shk[m]>
(born in '94)
<whitequark>
std::move is a normal function innit?
<galibert[m]>
Catherine: yeah
<fl4shk[m]>
Yeah, you can actually implement std::move manually
<fl4shk[m]>
which is true for the majority of things in C++ stdlib I think
<_whitenotifier-6>
[amaranth-lang/amaranth-lang.github.io] github-merge-queue[bot] 0ab7c21 - Deploying to main from @ amaranth-lang/amaranth@b77e33f16a9c922810e84cf61c4c2cb641a95a2d 🚀
<fl4shk[m]>
didn't you just drop support for Python 3.6?
<fl4shk[m]>
dropping support for Python 3.7 already
<fl4shk[m]>
not that I have an issue with that
<fl4shk[m]>
it just kind of surprised me
<galibert[m]>
well, we're at 3.11 so meh
<whitequark>
Amaranth doesn't support Python versions that aren't supported by PSF
<fl4shk[m]>
fair enough
<fl4shk[m]>
oh
<fl4shk[m]>
I see then
<fl4shk[m]>
is there any estimation on how soon the interfaces RFC can be used?
<whitequark>
"soon"
<fl4shk[m]>
soon™️
<galibert[m]>
In five hours (now who is going to get that reference?)
<whitequark>
I have an implementation, though as I've just realized it misses a connect function... anyway, I'm writing tests for it right now
<galibert[m]>
yay
<whitequark>
so it turns out object() doesn't have a __dict__ property
<galibert[m]>
yay python is so nice and regular
<whitequark>
I think it's to save memory
<whitequark>
it's an equivalent of having __slots__ = () in the class object:
FFY00 has joined #amaranth-lang
<mwk>
object cannot have a __dict__, since it's the base for everything, including dict itself; if every dict had a __dict__, the whole object model would instantly explode
<galibert[m]>
dictception
FFY00_ has quit [Ping timeout: 250 seconds]
<whitequark>
oh heh
<galibert[m]>
isn't a type an object?
<mwk>
sure is
<galibert[m]>
and object() has a type, so it could have a dict too, probably None until needed
<mwk>
that doesn't follow
<whitequark>
that's not how this works; you can't even assign __dict__ to it
<mwk>
the point of __dict__ is that every __dict__ful object has its own dict, that's kinda the point of it
<mwk>
while multiple objects can have the same type
<mwk>
lazy-spawning __dict__ would work, I guess, though it does complicate the language
<mwk>
in any case, the secondary point is indeed memory savings, of two pointers per every non-user-defined object in the language (including stuff like ints)
<mwk>
(the two pointers being __dict__ and __weakref__)
<galibert[m]>
oh yeah, value boxing
<galibert[m]>
didn't think of that one
<mwk>
(but the real answer to why it ended up like this involves lots of Python 2 era history and old style / new style classes)
cr1901_ is now known as cr1901
<cr1901>
>in any case, the secondary point is indeed memory savings <-- the secondary point of what? object not having a __dict__?
<whitequark>
yes
<whitequark>
look up the docs for __slots__ for details
<cr1901>
Coincidentally, while looking up something unrelated yesterday, I saw a SO answer that says if you add __slots__ to your class, you automatically lose out on __dict__: https://stackoverflow.com/a/14361362
<cr1901>
Filed under "look up __slots__ later". Which I guess is "now"
jjsuperpower has joined #amaranth-lang
gatecat has quit [Server closed connection]
gatecat has joined #amaranth-lang
crzwdjk has joined #amaranth-lang
<crzwdjk>
Having finally caught up on the discussion around the interfaces RFC it's good to see that it's moving along.
<whitequark>
yeah it's pretty close to the finish line I think
<crzwdjk>
I guess one can debate whether Component should be the core component but I kind of like it being just a library feature, it shows that there's not really any magic behind it. It just does all the tedious wiring-up of connections for you.
<josuah>
and a start line for building modules
<whitequark>
modules?
<josuah>
I meant it without thinking about Amaranth's constructis: pieces of reusable code on top of that incoming interface specification
<josuah>
s/constructis/constructs/
<whitequark>
so the thing is that people already write modules. they're in standard library too! e.g. lib.fifo
<crzwdjk>
This just makes writing them somewhat nicer. Well, really it makes using them a lot nicer.
<josuah>
whitequark: very good point!
<josuah>
it just happen to correlate with the moment I get time to write anything outside work (at least I wish!)
<josuah>
happens*
<crzwdjk>
Oh also for me personally arrays in interfaces are going to be useful. Not, like, most of the time, but there are definitely categories of components that would want multiple of the same kind of interface.
<whitequark>
where do you plan to use them?
<crzwdjk>
An arbiter, arbitrating access to some component from N consumers
<galibert[m]>
crzwdjk: the connected components are probably not going to end up in the arbiter signature but the connection objects are going to be created as-needed (and plonked in a stardard python list)
<crzwdjk>
I should read the RFC more, I suppose
<whitequark>
it's not something explained in the RFC
<whitequark>
it's more that the existing practice is that
<galibert[m]>
crzwdjk: but my argument about lib vs. not lib is that I see adding the signatures a lot like adding prototypes to C did, and I tend to see them pretty much as fundamental. So I'd eventually like them as something mandatory, and in fact I'd have plonked it directly on Elaboratable. I'm radical that way
<galibert[m]>
* lot like what adding prototypes
<whitequark>
it's not really equivalent because you can still connect signals bypassing the signature
<whitequark>
for example, the arbiter we're discussing here is doing exactly that! the arbiter.add() function bypasses the signature of the arbiter entirely
<galibert[m]>
of course, and it's important to be able to do that
<galibert[m]>
it's more about the implicit contract about how the module is supposed to be talked to
<crzwdjk>
It's more like perl adding signatures I suppose
<whitequark>
yea
<galibert[m]>
sure
<galibert[m]>
between lib.data and lib.components I see a big push towards grouping by semantics and structural matching which I think will be an immense help
<whitequark>
yeah
<whitequark>
the coupling to the core language is a purely technical issue that's not really linked to the social one of the recommended approach of structuring designs
<galibert[m]>
it is a little though, but it's not critical
<galibert[m]>
I mean, std::struct? ;-)
<whitequark>
python does this?
<whitequark>
enum in python is a purely library thing
<whitequark>
dataclasses in python are a library thing
<galibert[m]>
no, I'm just imagining a world where C++ would do that
<galibert[m]>
yeah, python is a mess, news at 11
<zyp[m]>
there's std::array
<galibert[m]>
zyp: with the side effect that it's not at all used often
<whitequark>
if you look at Rust, Box<> used to be a language thing but they made it a library thing instead
<galibert[m]>
I haven't managed to do enough with Rust to have an opinion at this point
<whitequark>
considering how low-level Amaranth is, I think a better comparison is a macro-assembler
<whitequark>
where structures are emphemeral constructs on top of the "base" assembly language
<galibert[m]>
nah, verilog is the macro-assembler
<crzwdjk>
And there is probably less reason to dip into no_std Amaranth than into no_std Rust
<whitequark>
verilog is a higher-level language than amaranth
<galibert[m]>
verilog is a mess anyway
<whitequark>
I want amaranth.hdl.dsl.Module to be essentially a library construct, at some point
<whitequark>
but that's a tale for another day
<galibert[m]>
heh
<josuah>
<whitequark> verilog is a higher-level language than amaranth
<josuah>
interesting! I'll need to dive down the implementation some day...
<whitequark>
it boils down to verilog having "inference"
<galibert[m]>
I don't really see am as a macro-assembler. It's more in the deceptively simple language, like, say, lisp
<galibert[m]>
Emphasis on deceptively
<galibert[m]>
s/in/a/, s/the//
<whitequark>
so now that you mention lisp, racket has structs as a library thing
<josuah>
building-up text all the way :)
<whitequark>
not sure about scheme
<galibert[m]>
in lisp almost everything is a library, it's rather similar to forth in that aspect
<crzwdjk>
In any case, the standard library is part of the package, and presumably there will be standard Components in there as well, and that will steer people to using Component. Also, documentation and tutorials and the fact that it's easier at least for the common cases
<whitequark>
yeah, everything streams-related will be components, FIFOs as well
<galibert[m]>
yeah
<whitequark>
lib.scheduler should be just deprecated (I don't think it's actually used anywhere and it was added due to pressure from some external org IIRC)
<whitequark>
lib.coding is an interesting case because really all of those should be DSL-level functions, but we don't have DSL-level functions
<galibert[m]>
what's a dsl-level function?
<whitequark>
so you have python-level functions, which are def foo(...):. you can put python statements there. you cannot put Amaranth statements there because it causes confusing scoping bugs
<galibert[m]>
really? I do that on a regular basis
<whitequark>
i.e. once you do `m = Module()` somewhere, all `m.d.<domain> +=` and `with m.If` statements should live in the same function
<galibert[m]>
okay. I don't respect that, like, at all :/
<whitequark>
putting stuff into a function works some of the time but at other time it creates extremely confusing bugs where it silently does the wrong thing
<galibert[m]>
interesting, it has always done the right thing for me at this point
<crzwdjk>
Whoops, I have at least one example of that in my codebase too
<whitequark>
have you ever done with m.Elif(self.fn_call(...)):?
<galibert[m]>
No
<whitequark>
if you have m.d.comb += inside fn_call then this will put it in the wrong block
<whitequark>
which will silently do the wrong thing
<whitequark>
issues start once you start using control flow and functions together
<galibert[m]>
It actually doesn't work fine but that's for reasons that have nothing to do with amaranth :-)
<whitequark>
since it's not reasonable to expect amaranth programmers to know the exact internal causes of the bugs, my current position is "don't do it
<whitequark>
* do it"
<galibert[m]>
'kay
<zyp[m]>
having a function both do its own expressions and return something sounds pretty evil
<crzwdjk>
Whoops, I already did that too, control flow and functions, I guess I will need to rewrite that bit as well
<whitequark>
zyp: experienced designers do this all the time, eg ktemkin wrote this in LUNA, i think it was a posedge detector or something like that
<galibert[m]>
but the deep issue is with doing m.d.whatever += .... within the parameter of a with m.Something(...) right?
<whitequark>
galibert: that is *one instance* of the underlying issue
<galibert[m]>
not something I'd ever try indeed
<whitequark>
another instance is adding a state register in a function and then putting the function inside of a control flow construct
<whitequark>
like for an edge detector
* josuah
reads again: no problem with using functions taking m as argument and "m.d.comb +=" to it... phew! I liked that thing.
<whitequark>
the edge detector then gets an enable input it may not be expecting
<josuah>
only a problem when combining it with other control statements...
<galibert[m]>
Yeah, my personal coding style is "no creating signals in elaborate"
<whitequark>
if you are only using functions to factor out a bunch of statements, without creating new signals or using them in a condition of a control flow construct, it works fine
<galibert[m]>
so I'm safe there too
<galibert[m]>
that's exactly what I do
<whitequark>
you probably have suffered enough under other HDLs that you know the underwater hazards to avoid while sailing
<galibert[m]>
indeed
<whitequark>
however this is empirically not true in general, as in, I see people try to do those things that do not work frequently, including both beginners and experienced designers
<galibert[m]>
KISS is my way of life
<galibert[m]>
if it's not obvious what it's going to do, then just don't do it
<whitequark>
thing is, for many people it is obvious that a function can be used in a conditional
<whitequark>
because python places no restrictions on it and theyre thinking in terms of python
<whitequark>
"obvious" is a poor yardstick when there's multiple people involved, with different backgrounds
<galibert[m]>
for sure
<galibert[m]>
30 years of mucking into various open-source code, I'm quite opiniated about unrequired complexity
<whitequark>
first-class support for functions is actually kind of annoying, because there are two different things people expect
<whitequark>
one is what you're doing
<whitequark>
another is that a function would act like an anonymous submodule
<WilliamDJones[m]>
(I do not promise I understand what I wrote back then right this instant, but I welcome corrections :P)
<galibert[m]>
yeah, I see an edge detector as m.d.sync += h.eq(a) ; m.d.comb += edge.eq(a & ~h), for Rise at least
<galibert[m]>
there's no past, only future :-)
<WilliamDJones[m]>
Before I completely lose my train of thought...
<whitequark>
h is the past value
<WilliamDJones[m]>
<WilliamDJones[m]> "I think the anonymous submodule..." <- Does this code I linked pass the litmus test for "creating DSL-level functions"? This would've been A LOT to write out if I wasn't generating FSM states.
<whitequark>
this isn't directly answering your question but it's a really inefficient way to implement an UART
<whitequark>
and a more efficient implementation wouldn't require generating so many states
<WilliamDJones[m]>
I don't want commentary on that
<whitequark>
that said, any function that's not the function where you've originally created the Module needs to be a "DSL-level function" of some kind
<whitequark>
since only then can it have the relevant checks done that ensures its contents is using the Module in a way that won't cause it to silently produce unexpected input
<whitequark>
s//`/, s//`/, s/input/output/
<whitequark>
re: efficiency question, I think it's relevant because the tools and affordances provided by the language affect how designers split their modules into smaller portions
<whitequark>
in practical terms: maybe the right approach here is to make it easier to write interlocking or composable state machines more so than functions?
<whitequark>
or maybe it's more of an issue that we don't teach anywhere how to implement I/O interfaces
<WilliamDJones[m]>
It was simpler/quicker for me to generate a larger state machine than to interlock composable ones
<whitequark>
exactly
<whitequark>
I'm not commenting on you, I'm commenting on the language that made it simpler/quicker to do this
<WilliamDJones[m]>
Anyways, sorry, I took that critique a bit personally, when I really shouldn't have.
<whitequark>
I think a language that makes it too complicated to write a nice UART is not a very good one!
<galibert[m]>
yep, it's important to ensure that the best way to do things is also the simplest one
<whitequark>
a lot of the time 'the best' (and take those words with a grain of salt) implementation ends up being a sort of a puzzle you need to solve instead of getting on with your work
<WilliamDJones[m]>
Well, the receiver's done except for a single test (and adding the FIFO bullshit which I am deliberately procrastinating on). Now would be a good time to rearrange it
<whitequark>
this is especially prevalent in functional languages that use point-free style
<whitequark>
and forth
<whitequark>
(for similar reasons)
<WilliamDJones[m]>
point-free style?
<galibert[m]>
yeah, best is extremely contextual and relative
<galibert[m]>
forth is amusing... but also an atrocity
<whitequark>
William D. Jones: composing higher order functions without ever making a lambda and giving the intermediate variables names
<galibert[m]>
I really never want to have to program in that language
<galibert[m]>
it's a syntax error to write an assignment in a lambda
<whitequark>
ironically, using the point-free style in haskell results in writing a lot of the point ('.') character, making it all that more confusing
<WilliamDJones[m]>
Yea, it'll take me a minute to get through that.
<whitequark>
Finally, to see a complex example imagine a map filter program which takes a list, applies a function to it, and then filters the elements based on a criterion
<whitequark>
point-free style has its share of adherents that swear by it, but it tends to be impenetrable for anyone who isn't extremely plugged into that specific codebase
<whitequark>
anyway, figuring out where to put the .s is a sort of a puzzle that's intellectually satisfying to do instead of, like, your actual job
<whitequark>
which is what i was referring to earlier
<WilliamDJones[m]>
Yes, I can see the puzzle aspect. I can also see how one would find this more fun that doing one's actual job.
<WilliamDJones[m]>
<whitequark> "in practical terms: maybe the..." <- I think past-me prioritized "I want the implicit inner FSM that drives the receipt of each bit to be very close in source code proximity to where I used it, so I could tweak it quickly without having to scroll far". Not saying it justifies having a 43-state FSM, but it was my mindset at the time.
<whitequark>
the thing is that writing an efficient UART in Amaranth (with a single shift register that has a few muxed taps for setting the data width) is a bit of a puzzle
<whitequark>
crzwdjk: this was one of the motivations behind Amaranth!
<whitequark>
unfortunately I think it's also one on which I failed to deliver :(
<crzwdjk>
Btw, the naughty helper method that does m.d.sync += whatever that I wrote is to help deal with the FSM. Basically check if the FSM should move to the next state.
<WilliamDJones[m]>
I think lots of FSM states is defensible in the Verilog world b/c you can write Moore state machines (where the comb outputs of your FSM only depend on the current state). Sometimes synchronous outputs can be turned into comb outputs by adding more states. I think Moore FSMs w/ lots of states are easier to reason about than nested FSMs
<WilliamDJones[m]>
But I can see that composable FSMs are nicer to read at the cost of making it more difficult to reason about "which pair/triple/quads/etc of states set which outputs
<crzwdjk>
I don't really know any other HDL, but amaranth has been delivering a lot of value to me so far. By which I mean it's been easy enough to get something working and build it up without getting my ADHD brain distracted and discouraged.
<WilliamDJones[m]>
But you can't really write a Moore-style FSM in Amaranth...
<fl4shk[m]>
WilliamDJones[m]: can't you do combinatorial outputs from a module?
<whitequark>
William D. Jones: when I implemented an UART, I think I had a state per, well, state (start/data/stop)
<fl4shk[m]>
WilliamDJones[m]: isn't the current state just another signal that you drive?
<WilliamDJones[m]>
I don't want to write-out a one-hot signal for every single state the FSM can be in
<WilliamDJones[m]>
that's the optimizer's job
<fl4shk[m]>
Oh
<fl4shk[m]>
I see
<fl4shk[m]>
personally, I don't find that to be that hard
<whitequark>
you can do fsm.ongoing("STATE")
<whitequark>
that's your one-hot signal
<WilliamDJones[m]>
Yes, that's more work than just putting all the comb outputs into their relevant State context
<fl4shk[m]>
You could just use an `enum.Enum`
<fl4shk[m]>
and make the state machine that way
<WilliamDJones[m]>
I hope the optimizer knows to merge my state enum with the internal state signal used for the FSM state
<fl4shk[m]>
... the idea is that you'd just have one `enum.Enum` for the whole state machine
<fl4shk[m]>
i.e. like in other HDLs
<whitequark>
generally state registers are not merged by yosys
<fl4shk[m]>
hm
<whitequark>
anyway, the with m.FSM() state should be an enum internally, but it predates enum support in Amaranth
<whitequark>
actually, now that I think about that, it won't work because you can't add an enum member dynamically :(
<WilliamDJones[m]>
Just to make my high-level idea clear- I think being able to immediately figure out which states are setting which comb output is valuable. And sometimes it's worth adding more FSMs states to turn sync FSM outputs into comb ones. If composable FSMs are preferred in Amaranth, I would still want a way to be able to easily tell which state tuples (one for each FSM composed) are triggering each output
<whitequark>
I think we're using different definitions of "composable" here
<WilliamDJones[m]>
I'm using FSM-drives-one-or-more-FSMs
<whitequark>
that sounds about the same, which means my confusion lies elsewhere
<whitequark>
so to illustrate my thought process here, let's take an UART transmitter (an UAT?)
* WilliamDJones[m]
nods is listening
<whitequark>
I think of it as two processors, combined end to end: the first one takes a stream of bytes (from the input FIFO, usually) and turns it into a stream of bits plus an "are we outputting a bit right now?" signal, the second takes a stream of bits and changes the output of a pin each time a timer expires
<whitequark>
the second one would also reset/start/stop the timer and signal readiness to the first one
<whitequark>
they don't actually need to use the stream abstraction, it's more readable if they both live in the same elaborate function I found, but conceptually it's two stream processors on a string
<WilliamDJones[m]>
Indeed
<zyp[m]>
I like using stream abstractions even for simple stuff like that
<whitequark>
in the former FSM you'll only have a state per start/stop/data/parity bit then, where you advance to the next state only if the bit has been output
<whitequark>
and in the latter FSM you'd mostly manage the timer
<whitequark>
the output is changed solely by the latter one
<galibert[m]>
interesting, I wouldn't do the second fsm, just plonked a pulse generator as the first fsm enable
<whitequark>
that changes semantics
<whitequark>
you start to output the data at some point in the future, vs just as the byte arrives in the FIFO
<WilliamDJones[m]>
whitequark: Meaning the output of the bit only depends on the current state of the latter FSM. If you did a big single FSM, the output of the bit would depend on being in one of 8 states
<whitequark>
isn't it 10/11 (depending on whether you have parity)?
<WilliamDJones[m]>
yes, sorry, was excluding the non data bits :P
<galibert[m]>
also it suspect it's far more efficient to have a 9-bit shift register and use a 1-bit sentinel to know when you're done
<galibert[m]>
that's what I did for i2c in any case, it ends up very tight
<whitequark>
yes
<whitequark>
chipflow uses a similar i2c implementation for a resource constrained process
<WilliamDJones[m]>
"which means my confusion lies elsewhere" what were you confused about? The tuple remark?
<whitequark>
ye
<galibert[m]>
you could even have a 1+8+1+2+1 bits shift register and drop the fsm entirely
<WilliamDJones[m]>
An FSM-driving-another-FSM can be represented as a "super FSM". As a coarse approximation to optimal, I concatenate the two FSM's state vectors in my head to represent "enough state for the super FSM", and then split them into "outer FSM state vector" and "inner FSM state vector" to fully describe the super FSM
<galibert[m]>
(the biggest part is going to be the bresenham counter for the frequency generator)
<whitequark>
yeah but... that's undoing the benefits of composing two of them in first place?
<WilliamDJones[m]>
It does let me get back to the state == (FSM_1_STATE_1, FSM_2_STATE_1) ||
<WilliamDJones[m]>
s//`/, s//` idiom/
<whitequark>
I mean you can view any Amaranth design with any number of modules as one super-FSM
<WilliamDJones[m]>
s//`/, s//` idiom in Verilog that I find useful./
<WilliamDJones[m]>
damn I really screwed up that code block
<WilliamDJones[m]>
hold on please
<whitequark>
i.e. my point is that composing FSMs lets you reformulate the places where you'd use that idiom to no longer require it
<WilliamDJones[m]>
* It does let me get back to the my_output = ({fsm_1_state, fsm_2_state} == {FSM_1_STATE_1, FSM_2_STATE_1}) || idiom in Verilog that I find useful.
<WilliamDJones[m]>
Ahhhh, and my point ends up being "I'd still like to access the info that that idiom gets me easily, just in case". I can see how your UAT solution is simpler/don't require needing an state-to-output mapping.
<WilliamDJones[m]>
* Ahhhh, and my point ends up being "I'd still like to access the info that that idiom gets me easily, just in case". I can see how your UAT solution is simpler/don't require needing an state-to-output mapping though. Basically, I don't know if you need the idiom with your approach ever.
<WilliamDJones[m]>
* Ahhhh, and my point ends up being "I'd still like to access the info that that idiom gets me easily, just in case". I can see how your UAT solution is simpler/don't require needing an state-to-output mapping though. Basically, I don't know if you need the idiom with your approach, ever. :P
<whitequark>
I don't like thinking about large state spaces so I build my designs in ways that don't require it
<WilliamDJones[m]>
I'm open to the idea that "no, having a state-to-output mapping is not actually all that useful in your composable approach"
<galibert[m]>
pfff, copout :-P
<whitequark>
galibert: why 1+8+1+2+1 btw?
<whitequark>
I think you need only 1+8(+1)+1?
<galibert[m]>
start data parity stop sentinel
<whitequark>
sentinel?
<galibert[m]>
stop bits can be 1 or 2 in the cases I've seen
<galibert[m]>
yeah, you add a 1 at the correct msb start and you stop when register == 1
<galibert[m]>
no counter that way
<whitequark>
oh neat, and it works for capture too
<galibert[m]>
or mayb in that case you can use the fact that stop=1 so you stop on all zero
<WilliamDJones[m]>
whitequark: Fair, I don't think adding states bothers me if many of those states I'm adding aren't doing much, such as elapsing time. But your UAT approach where you compose a "data-to-send" FSM with a "time elapser" (is elapser a word?) FSM is fine too. _I see it both ways_.
<whitequark>
yeah I think you can use stop bit for TX and start bit for RX
<whitequark>
if you're doing FV you'll have a hell of a time doing induction with that many states
<whitequark>
so that's one direct benefit of having a more compact FSM (or no FSM)
<galibert[m]>
the only annoyance is if you want to handle 1.5 stop bits
<whitequark>
1.5 stop bits is something that only old physical teletypes need, right?
<whitequark>
I think we can safely chuck that in the dustbin of history
<galibert[m]>
You can usually chuck parity and non-8-bit frames too
<WilliamDJones[m]>
1.5 stop bits is a transmitter only thing
<WilliamDJones[m]>
at least for 8250
<galibert[m]>
we were talking about transmit though
<whitequark>
parity is still used, e.g. the STM32 bootloader requires even parity
<whitequark>
I think the M16C one too?
<galibert[m]>
sure, but if you're on a fpga and want to talk to some uart-over-usb, it's usually 112000 8N1
<WilliamDJones[m]>
My UART was intended to be an 16450 compatible eventually, so I plan to implement everything the UART has to the best of my ability
<galibert[m]>
really depends on your use case
<whitequark>
-stdio should have two UARTs, one that's just 8N1, and another that implements a full 16550 one
<WilliamDJones[m]>
(the datasheet kinda is "meh" on how the FIFO and non-FIFO modes interact)
<galibert[m]>
agreed
<whitequark>
although there could be some value in having a 'middle ground' type of UART
<WilliamDJones[m]>
I'm still doing my UART even if you're build another one from scratch :P
<whitequark>
it's on the chipflow roadmap for -stdio so yeah
<galibert[m]>
there could be a "compile-time configurable" one
<WilliamDJones[m]>
That was what I intended for mine
<whitequark>
whitequark: something you could easily build is a "UART" where the word size is configurable from 1 to 32 bits and you can stuff whatever you want inside
<whitequark>
parity, two stop bits, weird frame sizes, are all handled by this
<galibert[m]>
configurable it would be easy and nice, programmable it wouldn't be :-)
<jfng[m]>
whitequark: isn't that what -stdio already has ?
<whitequark>
I don't remember
<whitequark>
but I was thinking at runtime
<jfng[m]>
you can configure word size and parity
<jfng[m]>
oh right, no
<jfng[m]>
statically, only
<whitequark>
tbf I think we'll need to rethink that implementation
<whitequark>
but that's for later
<zyp[m]>
orbtrace has a stream-pipeline-based UART receiver as well for capturing UART-encoded SWO; one module that captures pulse lengths, one module that decodes those into a bitstream, and one module that finds start/stop bits, collects the data in between and outputs bytes
<whitequark>
interesting
<zyp[m]>
the first step doesn't entirely make sense for a regular UART, but in this case it's shared with the SWO manchester mode decoder
<zyp[m]>
UART specific modules are the last two in that file
<zyp[m]>
it's using a DDR register at 250MHz to oversample at 500MSPS, UART mode capture is reliable up to 70-something Mbaud or so
<WilliamDJones[m]>
Anyways, once interfaces impl gets pushed, I'll rewrite the RX FSM to compose two FSMs. Maybe that'll make my desire to do functions inline to generate FSM states go away.
<crzwdjk>
I have a stream pipeline based PS/2 keyboard controller. Well, not much of a pipeline but one module collects the serial input bits into bytes (and vice versa) and the other one provides a stream of keycodes and handles initialization.
<WilliamDJones[m]>
0xEE for echo :D
<crzwdjk>
Yep
<crzwdjk>
Also m.d.sync += yellow_led.eq(fsm.ongoing("SOME_STATE"))
<crzwdjk>
Er, m.d.comb
<crzwdjk>
Printf debugging for FPGA
<WilliamDJones[m]>
<whitequark> "you can do fsm.ongoing("STATE")..." <- Yea, I made a mistake during the discussion. This _does_ allow me to do the Verilog idiom I want. But still will change to a composable FSM approach. I'm curious if it can be made even more ergonomic besides interfaces. But that's a future discussion
<WilliamDJones[m]>
wq: any estimate on when the interfaces RFC will be deployed (directly influences what I do tonight. Yes I live an exciting life)?
<whitequark>
define "deployed"?
<WilliamDJones[m]>
"In a public git branch and you won't get upset if I start writing code against it (with the expectation I will be making changes to said code as you push to that branch)"
<whitequark>
it would be silly to get upset at people who use publicly available code (unless they're like, violating license terms or something)
<WilliamDJones[m]>
Fair, it's hyperbole :).
<whitequark>
I don't know. I realized yesterday that the implementation I wrote then doesn't actually include .connect()
<whitequark>
so depending on whether I finish that in a hour or two, "today" or "on Friday"
<WilliamDJones[m]>
Okay, cool. Don't let me get in your way :D
crzwdjk has quit [Quit: Client closed]
<whitequark>
yeah I think Friday is more realistic
<cr1901>
I can keep myself occupied till then. If my UART is good enough can it still be put on your amaranth code showcase that you want to do (even if the stdio one exists) :P?
<WilliamDJones[m]>
(Repost b/c bridge is screwy again) I can keep myself occupied till then. Btw, while it's on my mind: 1. Do you still want to have amaranth projects you can point to as "examples"? and 2. If my UART eventually meets a "good application", would the existence of an stdio UART preclude mine being added to that list.
<whitequark>
we probably want to have the stdio UART be representative of every 'best practice' we want to encourage since it's such a basic thing and also one a lot of people immediately jump to, so yeah having another one in the list would be weird
<WilliamDJones[m]>
(How was I supposed to know you were already contracted to build one? :/)
<WilliamDJones[m]>
Anyways, I said "I'm still going to do my UART", but who am I kidding? All I buy is a few weeks/months before yours is done
<whitequark>
I mean... so there are two things here
<whitequark>
first, contracts aside, an UART is such an obvious thing to have in -stdio/-soc that it would have been built regardless. we have one right now! it was literally the first thing I built, before a GPIO peripheral even. it's just not quite at the quality level I want it to be
<whitequark>
second, we (ChipFlow) have been considering contracting people from the community to build high-quality peripherals that would then go into -stdio/-soc
<whitequark>
however any concrete plans on that would have to wait until interfaces, streams, CSRs, and a bunch of other things are done
<WilliamDJones[m]>
Hmmmm
<WilliamDJones[m]>
Actually, the truth is embarrassing... but I'll wait till your done typing
<whitequark>
I'm also not really sure why you seem to be upset over a high quality UART implementation being available in the standard libraries at some later point?
<WilliamDJones[m]>
Oh, I'm upset (for want of a better term) b/c I wasted time making a UART that is a subset of what's going to be available later (or is a non-proper subset in ways that don't really matter).
<whitequark>
but you made it because you wanted it now, right?
<WilliamDJones[m]>
Yes. And then I also forgot about how featureful -stdio's UART was. That's the embarrassing part. It's more featureful than I remember (data bits, parity).
<WilliamDJones[m]>
Only thing mine has that yours doesn't, afaict, is "recovery from a bad data frame". But that's an 16450-ism.
<WilliamDJones[m]>
So right now I'm like: "okay, so I learned some Amaranth writing this UART RX. That's cool. But... I could've used this time more productively." ._.
<WilliamDJones[m]>
* Yes. And then I also forgot about how featureful -stdio's UART was. That's the embarrassing part. It's more featureful than I remember (data bits, parity, frame error detection).
<whitequark>
ah I see
<whitequark>
opportunity cost can be a lot yeah
<WilliamDJones[m]>
Oh, and mine has BRK*. Which unfortunately I need, but... what are your thoughts of me adding BRK support to the current UART and getting rid of Record/using lib.data as a stopgap?
<WilliamDJones[m]>
* Oh, and mine has BRK*. Which unfortunately I need, but... what are your thoughts of me adding BRK support to the current stdio UART and getting rid of Record/using lib.data as a stopgap?
<whitequark>
I'd say it makes more sense to wait, both my and JF's plates are pretty full at the moment
* WilliamDJones[m]
nods will do
<WilliamDJones[m]>
I only need BRK receive, so pretty sure I can just attach a BRK detector externally. Anyways, that's completely on me for not keeping up w/ -stdio.
<crzwdjk>
Seems like UART is almost the "Hello World" of HDL. Or maybe the equivalent of writing a linked list implementation in C.
<WilliamDJones[m]>
Latter; blinky is the hello world of HDL. UART is a good exercise to do once in a while IMO (my "I just wasted so much time" rant notwithstanding)
jjsuperpower has quit [Ping timeout: 258 seconds]
<WilliamDJones[m]>
adamgreig: Have you made a pysim pytest fixture?
<WilliamDJones[m]>
Seem to recall you have, but don't remember where
<adamgreig[m]>
I have a few but I think they're all for (non-public) work code
<adamgreig[m]>
rebase onto b1cce87630e917dfe09546cd78c73c7a310c6b97 passes too, what gives
<whitequark>
very weird
<adamgreig[m]>
either d218273b9b2c6e65b7d92eb0f280306ea9c07ea3is the culprit or it's a spurious gha thing. rebased onto actual main and re-pushed to try again
<whitequark>
do you use .repl?
<adamgreig[m]>
could also remove some of the tests but none should be that bad really...
<adamgreig[m]>
not explicitly, no
<whitequark>
I'd rather run the tests and get an error if the simulator gets too memory-intensive if anything
<adamgreig[m]>
aaand memoryerror again
<adamgreig[m]>
at least it's consistent so far
<whitequark>
it doesn't look like replication is used anywhere in the arch-independent code
<adamgreig[m]>
time says almost the same maxresident for the passing and the failing rebase options
<adamgreig[m]>
(53676k on pass, 50576k on fail, so...)
<whitequark>
and 50M isn't even a lot by any standard
<whitequark>
wtf is going on
<charlottia>
Runner image version bumped from 20230611.1 (last succeeding CI run for that PR) to 20230619.1.0?
<mwk>
are you testing locally with `python -m coverage`?
<adamgreig[m]>
it keeps consistently failing if 99417d6499b006a172d5b8cba413fd6181737374 is included and passing when it's not, but it still might be spurious