<_whitenotifier-9>
[amaranth-lang/amaranth-lang.github.io] github-merge-queue[bot] 584b6b8 - Deploying to main from @ amaranth-lang/amaranth@028d5d80736151dbe6f9d35d35544cbfff6bef87 🚀
<tpw_rules>
whitequark[cis]: this is very nice, thank you. i have some questions i think would be answered by the reference. i think also it would be good to talk a bit about multiple testbenches above the "advanced" section
<whitequark[cis]>
oh, yes, good catch; I was planning that initially and then it slipped my mind
<whitequark[cis]>
which questions?
<tpw_rules>
do you get a different context type depending on if you are a process or a testbench? the guide names the same type but the methods seem to have different capabilities?
<whitequark[cis]>
haha
<whitequark[cis]>
me and Wanda have actually discussed that earlier in a call
<whitequark[cis]>
the actual underlying type is different but I plan to document them as the same thing and not export the subtypes
<tpw_rules>
why? can you ctx.tick().sample(x) in a testbench or ctx.delay/ctx.repeat in a process?
<whitequark[cis]>
yes and yes
<whitequark[cis]>
the sole thing you cannot do is ctx.get in a process
<tpw_rules>
ok i see
<whitequark[cis]>
this was a major developmental milestone and means that we no longer need any sort of branching or annotation in helpers
<whitequark[cis]>
as the semantics is the same except for one case where it's a hard error instead
<tpw_rules>
so helpers can work in either type
<whitequark[cis]>
if you don't use get in helper functions, which you probably just shouldn't, your helpers are portable
<tpw_rules>
assuming they are written to avoid get
<whitequark[cis]>
if you write out the type annotations, which is the only context where I expect people to ever name the type (I want to actively discourage instanceof(ProcessContext)) having to write out both, or trying to write out the exact context it's supposed to run in, is mostly noise
<whitequark[cis]>
yes, there is a minute amount of value in being that precise, but it would be better served by a phantom type parameter than subtyping or having an explicit union
<tpw_rules>
ok, i inquired under the impression there were more differences. thanks for explaining
<whitequark[cis]>
an earlier design had more differences
<whitequark[cis]>
this is what we implemented in the end
<whitequark[cis]>
mainly because the earlier design was a lot more fragile and I couldn't in good faith ship it
<tpw_rules>
i don't think i tracked that closely, just went by the samples. that's why i wanted to check the references
<tpw_rules>
s/references/reference section/
<tpw_rules>
so also ctx.tick().sample(x) gives the relevant domain's clock and reset too for convenience. i'm assuming it can take multiple arguments and you specify the domain to tick()
<whitequark[cis]>
(I'm trialing actually using type annotations in amaranth.sim)
<whitequark[cis]>
s//`/, s//`, solely for the benefit of downstream users and without any expectation that type checkers would or should run on Amaranth itself/
<tpw_rules>
i have not made that leap yet, but neat. do share how it goes
<whitequark[cis]>
I think I have a reasonable mental model of their strengths and weaknesses at this point
<whitequark[cis]>
and there are many weaknesses
<whitequark[cis]>
I outright exclude mypy from the list of software which I aim to support, and focus exclusively on pylance/pyright
<tpw_rules>
(mine is mostly the code density fetish)
<whitequark[cis]>
I really don't like how they bloat function signatures, yes
<tpw_rules>
last thing, i am supposing that in a testbench, .set or .get must have to re-enter the simulator to handle the zero-delay combinational aspect. is it documented which? that could be a performance problem maybe
<whitequark[cis]>
it has to be .set or you get weird behavior with certain asynchronous circuits
<tpw_rules>
really just trying to update the model, i don't plan to micro-optimize that much
<tpw_rules>
mental model*
<whitequark[cis]>
like... a process can have an immediately observable effect even before you run .get, namely it can crash
<tpw_rules>
that problem would also exclude a multi-set sort of thing?
<whitequark[cis]>
no, you can .set(Cat(...))
<tpw_rules>
oh, good to know
<whitequark[cis]>
this is intentionally difficult because if you set two async signals simultaneously you create a race
<whitequark[cis]>
e.g. if you toggle two clocks simultaneously, or assert clock and reset at once
<whitequark[cis]>
most of the time it doesn't matter, but a small subset of the time it introduces nondeterminism
<tpw_rules>
do the intermediate values get recorded? e.g. in the adder example, can you see 1719 on the output when you've updated a but not b? or does the delay sort of do a snapshot?
<whitequark[cis]>
they get recorded if you pass the argument fs_per_delta to write_vcd
<tpw_rules>
(i assume you could see 1719 on the output if you put a .get there but wondering about the files)
<tpw_rules>
does it default to 0?
<whitequark[cis]>
yes
<whitequark[cis]>
this is because it crashes if you have a sufficiently fast clock otherwise
<tpw_rules>
crashes? not OOM?
<whitequark[cis]>
it crashes if it runs out of femtoseconds
<whitequark[cis]>
to borrow from the next event
<tpw_rules>
oh i see. so a 2fs clock would only have one cycle to settle
<whitequark[cis]>
it doesn't shift the absolute positions of events that occur on a delay; it just sort of smoothes deltas
<whitequark[cis]>
yes
<tpw_rules>
and you crash rather than lying about times
<whitequark[cis]>
yes
<whitequark[cis]>
shifting absolute positions seems like an absurd solution
<tpw_rules>
yeah
<tpw_rules>
okay that all makes sense, thanks again for your time. hope to take it for a spin soon
<whitequark[cis]>
nice~
<whitequark[cis]>
have fun
<whitequark[cis]>
re: multiple testbenches, they just get run sequentially
<whitequark[cis]>
testbenches used to preempt on each get/set in an earlier design but that has resulted in an API that is completely impossible to write sound code against
<whitequark[cis]>
it was like, the most designer-hostile option
<whitequark[cis]>
so now on each timeline advancement (by any amount, including 0, because you can still do await sim.delay(0)... please don't though) the testbenches are run sequentially from the first added to the last added, and they only preempt when they wait for an event, which now coincides with an await point
<whitequark[cis]>
ctx.set is not an await point because a testbench doesn't get preempted: it reenters the simulator core and then continues to execute
<tpw_rules>
yeah i saw the description in the advanced section. do they still pre-empt if you do a sample? is there an await until time?
<whitequark[cis]>
what do you mean by the former?
<whitequark[cis]>
and no there isn't
<tpw_rules>
clk, rst, en_ = await ctx.tick().sample(en). i guess that could be an implied delay until a clock edge?
<whitequark[cis]>
await ctx.tick() waits for an event
<tpw_rules>
so await always enters the scheduler, and runs the next simulator in order that has an event this time, or advances to the next time and lowest numbered simulator which has an event
<whitequark[cis]>
no
<whitequark[cis]>
all await ... operations suspend the current testbench
<tpw_rules>
why did i say simulator there, i meant testbench
<whitequark[cis]>
actually, let me rephrase that
<whitequark[cis]>
what you said is true if you use sim.run()
<whitequark[cis]>
if you use sim.run_until() or sim.advance() it may not be true because it returns after running some other testbenches or not
<tpw_rules>
my confusion is what happens if testbench 1 does await sim.delay(1) and testbench 2 does await sim.delay(2). testbenches can't all run in order every time. anyway i am running late ofr something so i gotta run, we can converse more later if you like
<whitequark[cis]>
only the testbenches that were waiting on a triggered event run in their addition order
<whitequark[cis]>
also, another wrinkle: async for has performance implications (it's better)
<whitequark[cis]>
this is because the trigger doesn't get added and removed from the simulator core endlessly, which can impose high FFI costs