<charlottia>
Got there! It took just a bit of lining everything up, and one change in Amaranth. For anyone who's interested: (1) the $meminit cell generation in amaranth.back.rtlil._convert_fragment needs to be conditional on memory.init being non-empty ("if memory.init:" around line 880 until the end of the module.cell() call); (2) specify attrs={"ram_style": "huge"} and no init when constructing the Memory(); (3) read port must be
<charlottia>
transparent; (4) write port's and read port's address lines must be driven identically.
<charlottia>
Can also use synth_opts="-spram" when calling platform.build(...) to enable inference of SB_SPRAM256KA, and then if it doesn't fit the SB_RAM40_4K nicely it'll happen automatically. \o/
Degi has quit [Ping timeout: 260 seconds]
Degi has joined #amaranth-lang
anuejn has quit [Server closed connection]
anuejn has joined #amaranth-lang
<charlottia>
One thing that tripped me up a bit was assuming I needed to match the granularity at first. It seems like it will want to use as many SPRAM blocks as there are width//granularity chunks (modulo optimisation passes etc.; the lines all need to get used); i.e. width=16, depth=16384 and a write port with granularity=4 (which matches the SPRAM256KA's 4-bit MASKWREN) will use all 4 blocks. My assumption right now is each chunk of a
<charlottia>
write is getting split out per-block in a width-cascading fashion, since width=64, depth=16384 and granularity=16 works fine too.
<charlottia>
(tl;dr I was hoping to be able to use width=16 with a smaller granularity to write byte-wise, but it ends up multiplexing over multiple SPRAM blocks instead)
<galibert[m]>
Oh my, that rfc is confusing me so hard it's not funny
<whitequark>
does it?
<galibert[m]>
Yeah. I'm lost between what's typing and what's behaviour
<charlottia>
I like the Signature(...).apply(self) shorthand, though as written in the example it does mean we lose the classvar signature to reflect on. (Nothing stopping someone from still defining signature as a classvar and then just doing self.signature.apply(self) in __init__, though.)
<whitequark>
charlotte: yep, that's the idea
<whitequark>
and maybe that should be in the RFC
<charlottia>
Right right
<charlottia>
galibert[m]: Do you mean "typing" in the Python type annotation sense? There's none, really
<galibert[m]>
No, I mean typing in the general sense
<galibert[m]>
For instance, let's take a i2c support peripheral on a wishbone bus. The Module would have two interfaces, one for its wishbone input port, and one for the pair of i/o pins (let's assume there's somehow in the resources a I2C interface with the input/output/enable on the pins as needed)
<galibert[m]>
how would it looks like? This mixing between signal/port/connections definitions and the actual actions on the data makes me utterly confused
<galibert[m]>
in other words, for me SequenceGenerator should own an interface, but it seems to be an interface
<whitequark>
have you read the entire section?
<galibert[m]>
yeah, probably not correctly given how lost I am
<galibert[m]>
SequenceSource sorry
<whitequark>
hm, maybe that was a poor choice of way to introduce interfaces
<whitequark>
your module would be closer to AbsoluteProcessor as introduced
<whitequark>
since yourmodule.wishbone also has its own .signature
<charlottia>
(because applying the signature will instantiate each sub-signature as its own "interface" as attributes on the module)
<whitequark>
yep!
<galibert[m]>
Does the signature = [...] create the self.wishbone and self.i2c sub-objects?
<charlottia>
Applying it does, so it'd be:
<charlottia>
class WBI2C(Elaboratable):
<charlottia>
self.signature.apply(self)
<charlottia>
def __init__(self, ...):
<charlottia>
signature = ...
<whitequark>
to add to that, signature = [...] declares the self.wishbone and self.i2c objects to be present
<whitequark>
and when you pass WBI2C to verilog.convert, it'll use those instead of requiring you to write out ports=[...]
<whitequark>
so now we finally have a solution for that annoying tidbit
<charlottia>
! Neat!
<galibert[m]>
Yeah, I like to pretend verilog doesn't exist :-)
<whitequark>
or rtlil.convert, it's the same issue
<whitequark>
you have to specify top-level ports for a module unless you use the Amaranth build harness
<galibert[m]>
I guess I don't have top-level ports, ever, except in simulation
<charlottia>
Not to encourage further bikeshedding, but I think one reason this RFC seems particularly prone to inspiring confusion is because we're operating at the intersection of three things:
<charlottia>
2. the software-level instantiation of such a thing, which we're calling an "interface object" to begin with, but later then just call them "interfaces" (as in "Interfaces may be connected to each other using the `connect` free function"); and,
<charlottia>
1. a concept-level interface, as in "the shape of something that can be conformed to, and which objects can be validated for conformity with (or instantiated from)", which is a `Signature` instance here;
<charlottia>
3. a hardware-level _interface_, as in "a connector between two things", which is always going to be in mind given the word "interface" features in the names of half the kinds of hardware one ends up implementing.
<whitequark>
mm, yeah
<whitequark>
I'll be honest, at 2+ years, I'm very much at the "I just need to get something out the door so I can move on to other important things" mode
<whitequark>
2+ years for just this RFC
<charlottia>
Yeah, I completely understand u_u
<whitequark>
so I don't particularly care that it's somewhat confusing at first as long as using it isn't especially error-prone
<charlottia>
It's just been interesting to see where the sticking points have been.
<whitequark>
yeah
<charlottia>
whitequark: Yes, fair. Honestly, putting code examples up front might fix half the problem? Because it is so succinct.
<whitequark>
I... do? I put the guide-level explanation before the reference-level one
<galibert[m]>
charlotte: for mostly software people like me you can add the separation-or-not between what in a function would be the prototype (which is usually called signature) and what is the implementation
<galibert[m]>
Catherine: what could help me is to have the examples slightly less reduced, in particular still have an object instantiation and a connect in there (which I suspect changes from one example to the other, but not at all certain)
<whitequark>
no, it's the same for every case
<charlottia>
whitequark: Yep, which is definitely correct! I was imagining putting the final `AbsoluteProcessor` example much higher up, for people to have to sit in their minds, but I'm not convinced any more; would probably confuse more.
<galibert[m]>
oh, apply() is in fact an instantiation
<galibert[m]>
ohhh-kay, I'm starting to be less confused
<galibert[m]>
so, eventually, should every module have a signature and bare self.i_whatever = [...] be deprecated?
<whitequark>
yep
<galibert[m]>
Any ShapeCastable is supposed to be accepted in there?
<whitequark>
by "in there" do you mean inside "In[...]" / "Out[...]"?
<galibert[m]>
yep
<whitequark>
yes.
<galibert[m]>
and for .apply(), now that you can Signal(any shape-castable)
<whitequark>
I don't quite understand?
<galibert[m]>
You need apply() to create the correct types of objects, if you put say a StructLayout in there you need to get a Struct as a sub-object when you call apply
<whitequark>
a View of a StructLayout but yeah
<whitequark>
if you pass a Struct there you get a Struct instance
<galibert[m]>
ok, in fact that's a massive language overhaul.... and I think a rather good one
<whitequark>
lib.data and lib.component are made to work together
<whitequark>
and yes
<whitequark>
it's been a long time coming and it's absolutely essential for long-term evolution
<galibert[m]>
well, you just created module prototypes (in the method-prototype sense of the word)
<whitequark>
there method signatures too!
<galibert[m]>
you're essentially going from K&R C to ANSI C there
<whitequark>
* there are method signatures
<whitequark>
it's just that in C... yeh
<whitequark>
a prototype is a declaration of the signature
<galibert[m]>
yeah, I'm going with high-level concepts, not with the exact implementation
<galibert[m]>
bikeshedding: eventually .apply() will happen systematically afaict, so do you want it to be explicit rather than automatically done somehow?
<whitequark>
we can extend Elaboratable to do that in the base __init__
<whitequark>
but right now this will probably break anyone who calls super().__init__(), which is not a lot but probably nonzero amount of code
<whitequark>
really what we can add is a warning when instantiating an Elaboratable without a signature that suggests a signature to add
<whitequark>
like the whole method
<galibert[m]>
their code will break only if they have a signature attribute, no?
<whitequark>
ah hm, no we can't, no way to suggest In/Out
<whitequark>
galibert: well the idea is to make `signature` required
<whitequark>
since it will be made mandatory eventually
<galibert[m]>
Sure, but that means the code that calls super().init() is going to have to be changed anyway
<charlottia>
whitequark: ... unless you wait until the module is elaborated and look at where in the hierarchy the drivers are ...
<galibert[m]>
I thought your idea is not to break it immediatly
<whitequark>
charlotte: yes but that code is *evil* and it *hates you*
<charlottia>
Yes ^_^
<whitequark>
galibert: if we introduce conditional instantiation then people will come to rely on conditional instantiation
<whitequark>
I'd like to add a warning alone, have everyone add signature properties, and then both make them mandatory and add the constructor behavir
<whitequark>
* I'd like to add a warning alone, have everyone add signature properties, and then both make them mandatory and add the constructor behavior
<galibert[m]>
So everybody who added signatures have to go back and remove the apply calls?
<whitequark>
this is a good point
<charlottia>
You could detect when it was being re-called like that and just silently admit it, with a note in docs that doing so is idempotent and the call now safe to remove. (So you don't suddenly give Everyone warnings.)
<galibert[m]>
charlotte: Honestly I'd like to be able to write the final form of the code immediatly
<charlottia>
Sure, that's always nice if possible!
<charlottia>
Just depends on the project's tolerance for making breaking changes.
<galibert[m]>
it's not breaking if you don't do a thing if you don't have a .signature
<galibert[m]>
I mean, I find that not trivial to reason with:
<galibert[m]>
(enter validating immediatly instead of adding a cr is killing me :-) )
<charlottia>
galibert[m]: If you're using Element, "All settings -> Preferences -> Composer -> Use Ctrl + Enter to send a message" might help.
<charlottia>
So what about:
<charlottia>
1. conditional instantiation based on if the `signature` classvar is defined, _and_
<charlottia>
2. a warning if it's not.
<charlottia>
And then at some point you just change that warning to an error and call it a day.
<galibert[m]>
changed, thanks
<galibert[m]>
I would agree with that, Catherine seemed afraid people would rely on the conditional aspect of the conditional instantiation, but the presence of the warning would change that
<galibert[m]>
(I think)
<galibert[m]>
that means every existing code I going to start to warn but keep working
<galibert[m]>
s/I/is/
<charlottia>
I would hope so! If the goal is to avoid people having to change their code twice (but necessitating them changing it once), then I think something like this — introducing the new feature in its final form and doing something isomorphic to "deprecating" its non-use, with a grace period — is the only way to do it.
<galibert[m]>
I agree. Catherine is the final word in any case though :-)
<galibert[m]>
(damn, it was not just a bikeshed after all)
<whitequark>
I've also added a component.forward method to address zyp's comments
<galibert[m]>
Can I be a little more annoying? :-)
<whitequark>
sure
<galibert[m]>
Isn't connect going to be a little annoying for tightly coupled internal submodules?
<galibert[m]>
Imagine you implement a cpu, and you say have a submodule for instruction decoding, has say ir as input and a bunch of output signals derived from the opcodes
<galibert[m]>
You're going to end up having to create an interface for those signals, use it in the top and the bottom module, and connect between the two? I'm not even sure how you create, well, the inner interface in the top module to be connected to the decoding submodule
<galibert[m]>
probably can wrap the signals on a Struct, it even makes sense, so there's that
<galibert[m]>
but I'm not sure of what it's going to look like in the top cpu module
<galibert[m]>
s/on/in/
<whitequark>
you can define the interface once in the inner module and in the top you'd have self._decoder = self.decoder.signature.apply() or something
<whitequark>
you dont need to define any new entities
<galibert[m]>
yeah, now that I think about it the top module doesn't have to connect itself, if can hit the interface directly with eq, and it can in addition sanely connect submodules together when it's useful.
<galibert[m]>
it's going to work nicely
<galibert[m]>
in combination with struct it will even make decomposition in sub-modules easier
<galibert[m]>
and yeah, forward is needed
<galibert[m]>
Could signature = StreamSignature(16).flip() be signature = In[StreamSignature(16)]
<galibert[m]>
and signature = StreamSignature(16) equivalent to signature = Out[StreamSignature(16)]
<galibert[m]>
(now that's bikeshedding)
antoinevg[m] has quit [Remote host closed the connection]
cesar has quit [Remote host closed the connection]
<zyp[m]>
So if I'm interpreting the constructor discussion correctly, it implies that once an elaboratable is constructed, its signature will be frozen, since apply() calls freeze()
<zyp[m]>
which means that it won't be possible to have methods adding things to the interface
<galibert[m]>
zyp: probably yes. It's kind of a contract though, not sure you want to have the interface build outside of the constructor
<galibert[m]>
s/build/built/
<zyp[m]>
it's a question of how to handle optional features
Psentee has quit [Quit: Off]
<zyp[m]>
for something like a bus interconnect it wouldn't be a problem, because a method like get_port() would just return a new port interface, not add it as an attribute on itself
<galibert[m]>
there may be an issue of when the specialized object constructor runs vs. the Elaboratable one
Psentee has joined #amaranth-lang
<galibert[m]>
but if there's no issue there, I think it's better to require information about options chosen at construction time, instead of having that chosen dynamically
<zyp[m]>
but for a module meant to be a top level module instanced as a verilog core or something, you might want to have methods to add features to it
<galibert[m]>
hmmm yeah
<galibert[m]>
having a method called between construction and elaborate called on all elaboratables?
<galibert[m]>
* having a method called on all elaboratables between construction and elaborate?
<josuah>
the recent work around interfaces is inspiring!
<crzwdjk>
It's a huge improvement to the language, I can't wait to rewrite my stuff to use it.
<josuah>
that's good news if the effort of rewriting is continuously rewarded by something that feels better coming out of it
<josuah>
that will help with converting existing codebases
<crzwdjk>
I'm just excited to finally get rid of an opportunity to make stupid typos while wiring up signals one by one and then spending half an hour wondering why it doesn't work
<josuah>
amen
<crzwdjk>
I assume streams are coming after interfaces and will be built on top of them?
crzwdjk has quit [Quit: Client closed]
crzwdjk has joined #amaranth-lang
crzwdjk has quit [Client Quit]
<whitequark>
yes
lipolp[m] has joined #amaranth-lang
lipolp[m] has left #amaranth-lang [#amaranth-lang]
SpaceCoaster has joined #amaranth-lang
SpaceCoaster has quit [Quit: Bye]
SpaceCoaster has joined #amaranth-lang
SpaceCoaster has quit [Quit: Bye]
SpaceCoaster has joined #amaranth-lang
<robtaylor>
It’s great to see everyone so positive on the interface rfc!