whitequark[cis] changed the topic of #amaranth-lang to: Amaranth hardware definition language · weekly meetings: Amaranth each Mon 1700 UTC, Amaranth SoC each Fri 1700 UTC · play https://amaranth-lang.org/play/ · code https://github.com/amaranth-lang · logs https://libera.catirclogs.org/amaranth-lang · Matrix #amaranth-lang:matrix.org
frgo__ has quit [Read error: Connection reset by peer]
frgo has joined #amaranth-lang
Degi has quit [Ping timeout: 276 seconds]
Degi has joined #amaranth-lang
<whitequark[cis]> zyp: so I've realized that there is actually no benefit from using a size queue on iCE40
<whitequark[cis]> or, rather, no benefit to doing so if you have more than, say, 4 in-flight packets
<whitequark[cis]> for some reason I thought that 16 element FIFOs would be mapped without BRAMs, but no, of course they aren't
zyp[m] has joined #amaranth-lang
<zyp[m]> yeah, that's pretty much what I figured
<zyp[m]> could still be useful for something like HS USB if you know most packets would be 512B and are okay with it choking on a run of short packets
<zyp[m]> the question is just whether it's useful enough to maintain both that implementation and a delimited one
<whitequark[cis]> exactly, yes
<whitequark[cis]> I'm starting to feel that it isn't
<whitequark[cis]> btw, implementing an RMII/RGMII Ethernet MAC for Glasgow has been a very good testbed for various modes of packet streams
<whitequark[cis]> I am starting to wonder whether the enum is really a good idea, or whether we should agree to have all of the fields present on all packet streams, and burden the gateware with needing to accept all of the flag combinations
<whitequark[cis]> possible compromises: (1) add a packet-specific connect function that can smooth over the differences provided it can be done combinationally, (2) extend wiring.connect to be able to look into concatenations and accept connecting non-fully-constant interface members with constant bits
<whitequark[cis]> I do not know the solution, but I do know that (a) it is fairly error-prone to have to fall back to manual m.d.comb += blocks, and (b) the Ethernet MAC has a dizzying array of gateware, much of which uses the packet flags in unique ways
<whitequark[cis]> for the RX path i have: {end}->{first,last,end} (unused last)->{first,last}-(adaptor)->{first,last,end}->{first-last}->{end}
<whitequark[cis]> and end simplifies things often enough that i'm convinced that it should be broadly supported, on par with last ideally
<whitequark[cis]> actually, I think there's another important point here, which is a deviation from eg LiteX streams (as far as I understand them). we use last and end to signal packet end (combined or not combined with data), and first as a within-payload reset signal
<zyp[m]> I would rather not have to support every mode in every component, and we really don't want to repeat the litex situation where all signals are present and you don't really know which are used until it blows up on you
<whitequark[cis]> I'm starting to think that mandating support for every mode in every component is a lesser evil than branching on the modes
<whitequark[cis]> consider PacketQueue, which is something that would support almost all modes
<whitequark[cis]> I think that's at least 6 permutations that generate distinct netlists. that's a testing nightmare more so than supporting all the modes
<zyp[m]> IME most components only need last, so I would only implement that, and use gasket components wherever I need to interface with components needing different semantics
<whitequark[cis]> IME end is more useful than last and if I could only have one it'd be end
<whitequark[cis]> and the amount of gaskets required for the Ethernet controllers would double my submodule count, so I'm not fond of that either
<zyp[m]> end adds throughput overhead, last doesn't
<whitequark[cis]> s/controllers/controller/
<whitequark[cis]> so?
<whitequark[cis]> having both is ideal, but end simplifies implementation of many important things (the COBS encoder, the CRC/framing handling) so much that if I can't have both I'd rather give up a few cycles of throughput
<whitequark[cis]> I also don't think that adding support for end is burdensome when you already support last. you enable your control path for last but not the data path
<whitequark[cis]> (separately from this, I think that adding support for first with the "processing reset" semantics is essentially free and should be done unconditionally; this hinges upon agreeing that first should have this semantic)
<whitequark[cis]> (first is different in that you cannot make a gasket that adds support for the "processing reset" semantic where there was previously none)
<whitequark[cis]> (and I think that the "processing reset" semantic is significantly more useful than the "yet another way to end a packet" semantic)
<zyp[m]> you mean that every component should have to implement and test reset logic on first even if it'll only ever be used in a stream where it'll follow last/end?
<whitequark[cis]> zyp[m]: correct
<whitequark[cis]> I think this is a lesser evil than having to add multiple mode arguments to every interesting piece of middleware and then having to test all of their combinations
<whitequark[cis]> but as a compromise, I think we can also explore the idea of tying the control signals of the payload to constants, the same way we have managed to ship streams that do not have any modes or exceptions to the stream rules while still supporting always-ready/always-valid edge cases
<whitequark[cis]> then you could implement your component that will never be used with first by tying it to 0 at both input and output; then its output is pluggable into e.g. a generic PacketQueue, but the input can only be used with other components which don't use first
<zyp[m]> I have a few components where I'm only using first for its reset handling, e.g. a bitstream where first indicates «this is definitely the first bit of a byte», and a separate component that converts it into a plain bytestream
<whitequark[cis]> in my proposal, first has exclusively reset semantics
<whitequark[cis]> ie there is no way to get a last at the output of anything by only setting first
<whitequark[cis]> * ie there is no way to get a last at the output of anything by only setting first at the input
<whitequark[cis]> (whether the component produces anything at all on the output before you feed it end or last is component-specific)
<zyp[m]> in my case I want to keep the whole bit counting logic out of the manchester decoder, I just want it to output a bitstream with sync, and let the downstream component turn that into bytes
<whitequark[cis]> sure. in this case they don't actually need the packet abstraction at all
<zyp[m]> indeed, but it matches first semantics, so I might as well reuse that part of it
<whitequark[cis]> as a general matter, whenever you have two components that are always used with each other, I do not think we should consider this pairing at all for the purposes of the design of the packet abstraction; it is a distraction from what is actually important
<whitequark[cis]> if you were not able to reuse the Packet class for it, there would be no downside at all. therefore we shouldn't look at it
<whitequark[cis]> the packet abstraction is useful because it allows components from different authors and libraries to be used together without having to individually check whether they all have the same interpretation of the control flags. otherwise it is not an abstraction but a shortcut to avoid writing two lines of cde
<whitequark[cis]> and I think that adding modes to the packet abstraction makes it immediately less useful for its only purpose (interoperation) because you can't connect streams with different modes together
<whitequark[cis]> therefore we should either get rid of modes, or make our connect operation more powerful
<whitequark[cis]> you could convince me that both of these options are worse than modes, but that would be a high bar
<whitequark[cis]> s/cde/code/
<zyp[m]> I'm planning to make a stream_connect() or similar that takes a number of components and connects them in series; having it transparently (or explicitly) insert gaskets between different semantics would be trivial
<zyp[m]> that function is part of the motivation for standardizing input/output names
<whitequark[cis]> that's one option, but I don't think we should abandon wiring.connect so easily
<zyp[m]> but we'll have to continue this later, I have to go :)
<whitequark[cis]> (I also think that this "connect pipeline in series" operation can make your code worse by prioritizing more complicated straight-line solutions over more appropriate branched ones that don't fit into its primitive input language)
<whitequark[cis]> zyp: btw I don't think it is trivial since you cannot insert `first` semantics post-factum (you can reset the entire component but there's no guarantee that it doesn't have important state that should be kept around, so this should not be something that's inserted automatically)
<whitequark[cis]> * is trivial to insert gaskets in `stream_connect()` since you
<whitequark[cis]> * zyp: btw I don't think it is trivial to insert gaskets in `stream_connect()` since you cannot add `first` semantics post-factum (you can reset the entire component but there's no guarantee that it doesn't have important state that should be kept around, so this should not be something that's inserted automatically)
<zyp[m]> fair, yeah, it should be explicit if anything
<whitequark[cis]> I think a reasonable way to resolve this would be to implement a nontrivial amount of gateware using both styles (modeful vs modeless) and decide based on the results
<whitequark[cis]> aside from that, I would need some really convincing argument to go for modes again, vs something like extending wiring.connect to handle concatenations
d_olex has quit [Ping timeout: 252 seconds]
d_olex has joined #amaranth-lang
balrog has quit [Ping timeout: 265 seconds]
balrog_ has joined #amaranth-lang
phire has quit [Ping timeout: 260 seconds]
mindw0rk has quit [Read error: Connection reset by peer]
mindw0rk has joined #amaranth-lang
mindw0rk has quit [Read error: Connection reset by peer]
mindw0rk has joined #amaranth-lang
<whitequark[cis]> zyp: so after sleeping on it, i came up with another possibility: each component chooses between "first+last" or "first+end" semantics for each of its streams, the connect function figures it out. this avoids having to test multiple permutations of modes while also not requiring implementing both `last` and `end`
<whitequark[cis]> the downside is that converting end to last semantics has a cost: namely, it causes a byte to be stuck in a buffer until end comes. this could be undesirable
<zyp[m]> yeah, I've implemented that a couple of times
<zyp[m]> e.g. when packetizing a bytestream (think a UART receiver feeding a CDC ACM endpoint) with last semantics, I have a timeout to assert last to force a short packet
<whitequark[cis]> i don't particularly like this downside and i think that is another argument in favor of implementing both
<whitequark[cis]> what i think we need is a way to automatically test your packet-based component for compliance by exercising it with a variety of combinations of flags automatically (adding backpressure to the mix as well)
<whitequark[cis]> if you only need to call a single function to test your stream transformer component that randomly calls it with first, last, and end asserted at various times, with this function verifying that it turns packet sequence A to packet sequence B regardless of the flags, the burden of supporting all of these flags becomes minimal
<whitequark[cis]> (it would have to take, as an option, whether the component cuts the packet through--where asserting first prematurely gives you a prefix of the expected packet--or whether it buffers it)