Applets without CLI missing reset
When loading gateware onto Glasgow outside of the CLI, as in this example, a top-level clock domain does not get added to the gateware, and therefore the gateware does not get reset when you CTRL+C out of the script. This leads to unexpected behavior if another script is run and the bitstream is not reloaded, because the gateware is not returned to its default state.
In the current implementation, the top-level clock domain is instantiated from the CLI by assembly.add_applet().
A workaround is to force the gateware to be reset by reloading the bitstream: await assembly.start(reload_bitstream=True).
When I first noticed this issue I struggled to reproduce it reliably. Now that I better understand the causality, I came up with a compact example "applet" that makes this behavior obvious. I'm adding it here in case this can be at all useful for troubleshooting. Running this script twice in a row inevitably fails on the second runthrough because of the lack of reset.
import asyncio
import threading
import logging
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter(style="{",
fmt="{levelname[0]:s}: {name:s}: {message:s}"))
logging.getLogger().addHandler(handler)
logging.getLogger().setLevel(logging.DEBUG)
from amaranth import *
from amaranth.lib import wiring, stream
from amaranth.lib.wiring import In, Out
from glasgow.applet import GlasgowAppletV2
from glasgow.hardware.assembly import HardwareAssembly
class LoopbackComponent(wiring.Component):
i_stream: In(stream.Signature(8))
o_stream: Out(stream.Signature(8))
def elaborate(self, platform):
m = Module()
count = Signal(4)
led = platform.request("led",0)
with m.FSM(init="Reading"):
with m.State("Reading"):
m.d.comb += [
self.o_stream.payload.eq(count),
self.o_stream.valid.eq(self.i_stream.valid),
self.i_stream.ready.eq(self.o_stream.ready),
led.o.eq(1)
]
with m.If(self.i_stream.ready & self.i_stream.valid):
m.d.sync += count.eq(count + 1)
with m.If(count==15):
m.next = "Stopped"
with m.State("Stopped"):
m.d.comb += led.o.eq(0)
return m
async def main():
assembly = HardwareAssembly()
component = assembly.add_submodule(LoopbackComponent())
pipe = assembly.add_inout_pipe(component.o_stream, component.i_stream)
await assembly.start()
for _ in range(4):
await pipe.send(b'\xff\xff\xff\xff')
await pipe.flush()
data = await pipe.recv(4)
print(f"{data.tobytes()}")
if __name__ == "__main__":
asyncio.run(main())