diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h index 1ff2ad9da9cd..c9fbdeeddd4d 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h @@ -58,6 +58,13 @@ constexpr uint32_t ManifestPtrOffset = 0x10; constexpr uint32_t CycleCountOffset = 0x20; constexpr uint32_t CoreFreqOffset = 0x28; +/// Magic value which, when written to MMIO offset `ResetRequestOffset`, +/// requests a design reset. Keep in sync with 'ResetMagicNumber' in the PyCDE +/// BSP (python/esiaccel/bsp/common.py). +constexpr uint64_t ResetMagicNumber = 0x00000E510000B007; +/// Offset into the (global) MMIO space at which to request a design reset. +constexpr uint32_t ResetRequestOffset = 0x38; + //===----------------------------------------------------------------------===// // Accelerator design hierarchy root. //===----------------------------------------------------------------------===// @@ -96,6 +103,11 @@ class AcceleratorConnection { /// Disconnect from the accelerator cleanly. virtual void disconnect(); + /// Request a reset of the accelerator design. Returns true if the reset was + /// successfully requested, false if it could not be performed for any reason + /// -- most commonly because the backend (BSP) does not support resets. + virtual bool reset(); + /// Return a pointer to the accelerator 'service' thread (or threads). If the /// thread(s) are not running, they will be started when this method is /// called. `std::thread` is used. If users don't want the runtime to spin up diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h index 0c6e23dc8daa..7cac1cf8c366 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h @@ -63,6 +63,9 @@ class TraceAccelerator : public esi::AcceleratorConnection { static std::unique_ptr connect(Context &, std::string connectionString); + /// Resets are not supported. + bool reset() override { return false; } + /// Internal implementation. struct Impl; Impl &getImpl(); diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Xrt.h b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Xrt.h index 3c0efef2a564..35afd8e3f29d 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Xrt.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Xrt.h @@ -37,6 +37,9 @@ class XrtAccelerator : public esi::AcceleratorConnection { static std::unique_ptr connect(Context &, std::string connectionString); + /// Resets are not supported. + bool reset() override { return false; } + protected: virtual Service *createService(Service::Type service, AppIDPath path, std::string implName, diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp index 09d4b24fa9cb..5b6c913c7e5b 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp @@ -40,6 +40,23 @@ AcceleratorConnection::AcceleratorConnection(Context &ctxt) : ctxt(ctxt), serviceThread(nullptr) {} AcceleratorConnection::~AcceleratorConnection() { disconnect(); } +// Request a design reset by writing the reset magic number to a particular +// MMIO offset. +bool AcceleratorConnection::reset() { + services::MMIO *mmio = getService(); + if (!mmio) + return false; + // The MMIO write is a virtual backend interface which may throw. + try { + mmio->write(ResetRequestOffset, ResetMagicNumber); + } catch (const std::exception &e) { + getLogger().error("reset", + std::string("failed to request reset: ") + e.what()); + return false; + } + return true; +} + AcceleratorServiceThread *AcceleratorConnection::getServiceThread() { if (!serviceThread) serviceThread = std::make_unique(); diff --git a/lib/Dialect/ESI/runtime/cpp/tools/esiquery.cpp b/lib/Dialect/ESI/runtime/cpp/tools/esiquery.cpp index 920a75c4dbbd..8bf127c74ccc 100644 --- a/lib/Dialect/ESI/runtime/cpp/tools/esiquery.cpp +++ b/lib/Dialect/ESI/runtime/cpp/tools/esiquery.cpp @@ -38,6 +38,7 @@ void printInfo(std::ostream &os, AcceleratorConnection &acc, bool details); void printHier(std::ostream &os, AcceleratorConnection &acc, bool details); void printTelemetry(std::ostream &os, AcceleratorConnection &acc); void printTelemetryJson(std::ostream &os, AcceleratorConnection &acc); +void resetDesign(std::ostream &os, AcceleratorConnection &acc); int main(int argc, const char *argv[]) { CliParser cli("esiquery"); @@ -59,6 +60,8 @@ int main(int argc, const char *argv[]) { cli.add_subcommand("telemetry", "Print ESI system telemetry information"); telemetrySub->add_flag("--json", telemetryJson, "Dump telemetry information as JSON"); + CLI::App *resetSub = + cli.add_subcommand("reset", "Reset the ESI system design"); if (int rc = cli.esiParse(argc, argv)) return rc; @@ -81,6 +84,8 @@ int main(int argc, const char *argv[]) { printTelemetryJson(std::cout, *acc); else printTelemetry(std::cout, *acc); + } else if (*resetSub) { + resetDesign(std::cout, *acc); } return 0; } catch (std::exception &e) { @@ -240,3 +245,8 @@ void printTelemetry(std::ostream &os, AcceleratorConnection &acc) { os << value << std::endl; } } + +void resetDesign(std::ostream &os, AcceleratorConnection &acc) { + os << "Resetting design..." << std::endl; + acc.reset(); +} diff --git a/lib/Dialect/ESI/runtime/cpp/tools/esitester.cpp b/lib/Dialect/ESI/runtime/cpp/tools/esitester.cpp index b790b48922f8..cedfe8a0f7cd 100644 --- a/lib/Dialect/ESI/runtime/cpp/tools/esitester.cpp +++ b/lib/Dialect/ESI/runtime/cpp/tools/esitester.cpp @@ -78,6 +78,7 @@ static void autoSerialCoordTranslateTest(AcceleratorConnection *, Accelerator *, uint32_t numCoords); static void channelTest(AcceleratorConnection *, Accelerator *, uint32_t iterations); +static void resetTest(AcceleratorConnection *, Accelerator *); // Default widths and default widths string for CLI help text. constexpr std::array defaultWidths = {32, 64, 128, 256, 512}; @@ -323,6 +324,9 @@ int main(int argc, const char *argv[]) { channelTestSub->add_option("-i,--iters", channelIters, "Number of loopback iterations (default 10)"); + CLI::App *resetSub = cli.add_subcommand( + "reset", "Test the design reset feature (telemetry clears after reset)"); + if (int rc = cli.esiParse(argc, argv)) return rc; if (!cli.get_help_ptr()->empty()) @@ -371,6 +375,8 @@ int main(int argc, const char *argv[]) { autoCoordNumItems); } else if (*channelTestSub) { channelTest(acc, accel, channelIters); + } else if (*resetSub) { + resetTest(acc, accel); } acc->disconnect(); @@ -1143,6 +1149,67 @@ static void loopbackAddTest(AcceleratorConnection *conn, Accelerator *accel, } } +// Exercise the design reset feature using existing telemetry. Run a hostmem +// write operation on the 'writemem' module, which increments its +// 'addrCmdResponses' telemetry counter. Confirm the telemetry advanced, +// request a design reset, then confirm the telemetry has been cleared back to +// zero (the counters live in the user design which the reset clears). +static void resetTest(AcceleratorConnection *conn, Accelerator *accel) { + Logger &logger = conn->getLogger(); + constexpr uint32_t width = 64; + + // Run an existing test that increments telemetry. The hostmem write test + // bumps the writemem module's 'addrCmdResponses' counter. + hostmemTest(conn, accel, {width}, /*write=*/true, /*read=*/false); + + // Grab the writemem module's response telemetry counter to observe the reset. + auto writeMemChildIter = accel->getChildren().find(AppID("writemem", width)); + if (writeMemChildIter == accel->getChildren().end()) + throw std::runtime_error("Reset test: no 'writemem' child"); + auto &ports = writeMemChildIter->second->getPorts(); + auto respIter = ports.find(AppID("addrCmdResponses")); + if (respIter == ports.end()) + throw std::runtime_error( + "Reset test: no 'addrCmdResponses' telemetry port"); + auto *respMetric = + respIter->second.getAs(); + if (!respMetric) + throw std::runtime_error("Reset test: 'addrCmdResponses' not telemetry"); + respMetric->connect(); + + uint64_t before = respMetric->readInt(); + std::cout << "[reset] telemetry addrCmdResponses before reset = " << before + << std::endl; + if (before == 0) + throw std::runtime_error( + "Reset test: telemetry was not incremented by the hostmem write"); + + // Request a design reset. + logger.info("esitester", "Requesting design reset"); + if (!conn->reset()) + throw std::runtime_error("Reset test: reset() reported failure"); + std::cout << "[reset] reset requested" << std::endl; + + // The reset is asserted a fixed number of cycles after the request (to let + // in-flight transactions drain), so poll the telemetry until it clears. + uint64_t after = before; + constexpr int maxPolls = 1000000; + for (int polls = 0; polls < maxPolls; ++polls) { + after = respMetric->readInt(); + if (after == 0) + break; + std::this_thread::sleep_for(std::chrono::microseconds(1)); + } + std::cout << "[reset] telemetry addrCmdResponses after reset = " << after + << std::endl; + if (after != 0) + throw std::runtime_error( + "Reset test: telemetry was not cleared by the reset (got " + + std::to_string(after) + ")"); + + std::cout << "Reset test passed" << std::endl; +} + static void aggregateHostmemBandwidthTest(AcceleratorConnection *conn, Accelerator *acc, uint32_t width, uint32_t xferCount, bool read, diff --git a/lib/Dialect/ESI/runtime/python/esiaccel/bsp/common.py b/lib/Dialect/ESI/runtime/python/esiaccel/bsp/common.py index ca64070fa260..fab7092c4291 100644 --- a/lib/Dialect/ESI/runtime/python/esiaccel/bsp/common.py +++ b/lib/Dialect/ESI/runtime/python/esiaccel/bsp/common.py @@ -25,6 +25,15 @@ IndirectionMagicNumber = 0x312bf0cc_E5100E51 # random + ESI__ESI IndirectionVersionNumber = 0 # Version 0: format subject to change +# Magic value which, when written by the host to header slot 7, requests a +# design reset. Keep in sync with 'ResetMagicNumber' in the runtime +# (cpp/include/esi/Accelerator.h). This magic number guards against "write +# spraying" which other devices have been know to do on boot. +ResetMagicNumber = 0x00000E510000B007 +# Number of cycles to wait after a reset is requested before asserting it. This +# gives in-flight transactions time to drain. +ResetCycles = 8192 + class ESI_Manifest_ROM(Module): """Module which will be created later by CIRCT which will contain the @@ -66,17 +75,39 @@ class HeaderMMIO(Module): clk = Clock() rst = Reset() - read = Input(esi.MMIO.read.type) + read = Input(esi.MMIO.read_write.type) + # Asserted for one cycle when the host writes the reset magic number to + # header slot 7. Propagates up to the BSP which performs the actual reset. + reset_request = Output(Bits(1)) @generator def build(ports): + clk = ports.clk + rst = ports.rst data_chan_wire = Wire(Channel(esi.MMIODataType)) input_bundles = ports.read.unpack(data=data_chan_wire) - address_chan = input_bundles['offset'] - - address_ready = Wire(Bits(1)) - address, address_valid = address_chan.unwrap(address_ready) - address_words = address.as_bits()[3:] # Lop off the lower three bits. + cmd_chan = input_bundles['cmd'] + + # Two-stage half-throughput pipeline: stage 1 captures the incoming + # command, stage 2 holds the looked-up response. Each stage carries its + # own occupancy bit. + cmd_ready = Wire(Bits(1)) + s1_to_s2_xact = Wire(Bits(1)) + cmd_raw, cmd_valid = cmd_chan.unwrap(cmd_ready) + + # Stage 1: command capture register and occupancy bit. + s1_load = cmd_valid & cmd_ready + cmd = cmd_raw.reg(clk, rst, ce=s1_load, name="cmd") + s1_valid = ControlReg(clk, + rst, + asserts=[s1_load], + resets=[s1_to_s2_xact], + name="s1_valid") + # Accept a new command when stage 1 is empty. + cmd_ready.assign(~s1_valid) + + address_words = cmd.offset.as_bits()[3:] # Lop off the lower three bits. + slot = address_words[:3] cycles = Counter(64)(clk=ports.clk, rst=ports.rst, @@ -96,18 +127,40 @@ def build(ports): 0, # Reserved for future use. cycles.out.as_bits(), # Cycle counter. core_freq, # Core frequency, if known. - 0, + 0, # Slot 7: write the reset magic number here to request a reset. ]) header.name = "header" - header_response_valid = address_valid # Zero latency read. - # Select the appropriate header index. - header_out = header[address_words[:3]] - header_out.name = "header_out" + + # Stage 2: registered response value and its occupancy bit. + s2_valid = Wire(Bits(1)) + data_chan_ready = Wire(Bits(1)) + s2_xact = s2_valid & data_chan_ready + # Stage 1 advances into stage 2 only when stage 2 is empty. + s1_to_s2_xact.assign(s1_valid & ~s2_valid) + + header_out = header[slot].reg(clk=clk, + rst=rst, + ce=s1_to_s2_xact, + name="header_out") + s2_valid.assign( + ControlReg(clk, + rst, + asserts=[s1_to_s2_xact], + resets=[s2_xact], + name="header_out_valid")) # Wrap the response. - data_chan, data_chan_ready = Channel(esi.MMIODataType).wrap( - header_out, header_response_valid) + data_chan, data_chan_ready_sig = Channel(esi.MMIODataType).wrap( + header_out, s2_valid) data_chan_wire.assign(data_chan) - address_ready.assign(data_chan_ready) + data_chan_ready.assign(data_chan_ready_sig) + + # Detect a write of the reset magic number to slot 7. Register the request + # so it is a clean one-cycle pulse, asserted as the command advances into + # the response stage. 'DesignResetController' latches it, so a single-cycle + # pulse is sufficient to trigger the reset. + reset_detect = (cmd.write & (slot == Bits(3)(7)) & + (cmd.data == Bits(64)(ResetMagicNumber))).as_bits() + ports.reset_request = (reset_detect & s1_to_s2_xact).as_bits() return HeaderMMIO @@ -316,6 +369,56 @@ def get_out(self, index: int) -> ChannelSignal: return ChannelDemuxTree +@modparams +def DesignResetController( + delay_cycles: int) -> type["DesignResetControllerImpl"]: + """Counts `delay_cycles` clock cycles after a reset request is observed, then + asserts `design_reset` for one cycle. This module must be driven by the + *external* reset only (not the reset it generates) so that the countdown is + not disturbed by the reset it produces. + + `reset_pending` is asserted from the moment a reset is requested until it + fires. It is intended to be used to quiesce the design (e.g. stop accepting + new transactions) so that nothing is in flight when the reset is asserted.""" + + if delay_cycles < 1: + raise ValueError("'delay_cycles' must be at least 1.") + + counter_width = max(clog2(delay_cycles), 1) + + class DesignResetControllerImpl(Module): + clk = Clock() + rst = Reset() + reset_request = Input(Bits(1)) + design_reset = Output(Bits(1)) + # High from the cycle a reset is requested until it fires. Use this to stop + # accepting new work so in-flight transactions can drain before the reset. + reset_pending = Output(Bits(1)) + + @generator + def build(ports): + fire = Wire(Bits(1)) + # Latch that a reset has been requested until we fire the reset. + pending = ControlReg(clk=ports.clk, + rst=ports.rst, + asserts=[ports.reset_request], + resets=[fire], + name="reset_pending") + # Count cycles while a reset is pending. + count = Counter(counter_width)(clk=ports.clk, + rst=ports.rst, + clear=(fire | ~pending).as_bits(), + increment=pending, + instance_name="reset_delay_counter") + fire.assign( + (pending & + (count.out == UInt(counter_width)(delay_cycles - 1))).as_bits()) + ports.design_reset = fire + ports.reset_pending = pending + + return DesignResetControllerImpl + + class ChannelMMIO(esi.ServiceImplementation): """MMIO service implementation with MMIO bundle interfaces. Should be relatively easy to adapt to physical interfaces by wrapping the wires to @@ -351,6 +454,10 @@ class ChannelMMIO(esi.ServiceImplementation): cmd = Input(esi.MMIO.read_write.type) + # Asserted for one cycle when the host requests a design reset via an MMIO + # write to the header. Propagates up to the BSP which performs the reset. + reset_request = Output(Bits(1)) + # Amount of register space each client gets. This is a GIANT HACK and needs to # be replaced by parameterizable services. # TODO: make the amount of register space each client gets a parameter. @@ -406,11 +513,11 @@ def build_read(ports, manifest_loc: int, table: Dict[int, AssignableSignal]): # Instantiate the header and manifest ROM. Fill in the read_table with # bundle wires to be assigned identically to the other MMIO clients. - header_bundle_wire = Wire(esi.MMIO.read.type) + header_bundle_wire = Wire(esi.MMIO.read_write.type) table[0] = header_bundle_wire - HeaderMMIO(manifest_loc)(clk=ports.clk, - rst=ports.rst, - read=header_bundle_wire) + header = HeaderMMIO(manifest_loc)(clk=ports.clk, + rst=ports.rst, + read=header_bundle_wire) mani_bundle_wire = Wire(esi.MMIO.read.type) table[manifest_loc] = mani_bundle_wire @@ -464,6 +571,10 @@ def build_read(ports, manifest_loc: int, table: Dict[int, AssignableSignal]): resp_channel = esi.ChannelMux(client_data_channels) data_resp_channel.assign(resp_channel) + # The header surfaces a reset request when the host writes the reset magic + # number to slot 7. Propagate it up to the caller (the BSP). + ports.reset_request = header.reset_request + @staticmethod def build_addr_read(read_addr_chan: ChannelSignal, num_clients: int, manifest_loc: int) -> Tuple[BitsSignal, ChannelSignal]: diff --git a/lib/Dialect/ESI/runtime/python/esiaccel/bsp/cosim.py b/lib/Dialect/ESI/runtime/python/esiaccel/bsp/cosim.py index 3b9af23f0878..196ba2d7fc4f 100644 --- a/lib/Dialect/ESI/runtime/python/esiaccel/bsp/cosim.py +++ b/lib/Dialect/ESI/runtime/python/esiaccel/bsp/cosim.py @@ -4,15 +4,17 @@ from typing import Callable, Optional, Tuple, Type -from pycde.common import AppID, Clock, Input +from pycde.common import AppID, Clock, Input, Output from pycde.module import Module, generator from pycde.support import get_user_loc from pycde.system import System +from pycde.signals import ChannelSignal from pycde.types import (Bits, Channel, StructType, UInt) from pycde.constructs import Wire from pycde import esi -from .common import (ChannelEngineService, ChannelHostMem, ChannelMMIO) +from .common import (ChannelEngineService, ChannelHostMem, ChannelMMIO, + DesignResetController, ResetCycles) from .dma import OneItemBuffersFromHost, OneItemBuffersToHost from pycde.circt import ir @@ -49,6 +51,13 @@ class ESI_Cosim_UserTopWrapper(Module): hostmem_read = ChannelHostMemModule.read hostmem_write = ChannelHostMemModule.write + # Asserted for one cycle when the design requests a reset (via an MMIO write + # to the header). Consumed by the top-level module which performs the reset. + # NOTE: must be declared after 'hostmem_read'/'hostmem_write' so it does not + # perturb their output port indices (they alias the sub-module's shared + # Output objects, whose '.idx' is reused for the instance output lookup). + reset_request = Output(Bits(1)) + @generator def build(ports): user_module(clk=ports.clk, rst=ports.rst) @@ -64,11 +73,12 @@ def build(ports): clk=ports.clk, rst=ports.rst) - ChannelMMIO(esi.MMIO, - appid=esi.AppID("__cosim_mmio"), - clk=ports.clk, - rst=ports.rst, - cmd=ports.mmio) + mmio = ChannelMMIO(esi.MMIO, + appid=esi.AppID("__cosim_mmio"), + clk=ports.clk, + rst=ports.rst, + cmd=ports.mmio) + ports.reset_request = mmio.reset_request # Instantiate a hostmem service generator which multiplexes requests to a # either a single read or write channel. Get those channels and transform @@ -91,9 +101,58 @@ def build(ports): mmio_read_write = esi.FuncService.get( esi.AppID("__cosim_mmio_read_write"), esi.MMIO.read_write.type) + + # The design can request a reset via an MMIO write to the header. Once + # requested, reset the entire design (but not the cosim link) after a + # fixed number of cycles. The reset controller is driven by the external + # reset only so its countdown is not disturbed by the reset it generates. + design_reset = Wire(Bits(1)) + reset_pending = Wire(Bits(1)) + combined_rst = ports.rst | design_reset + + # Once a reset has been requested, stop accepting *new* MMIO transactions + # so that none is in flight when the design (including the MMIO plane) is + # actually reset 'ResetCycles' cycles later. Any already-accepted + # transaction has that window to drain; a command held here is released + # once the reset completes and the MMIO plane is back up. The gate is + # purely combinational off the (registered) 'reset_pending', so it blocks + # at a clean cycle boundary: when pending is high the command is not + # presented downstream and the host sees back-pressure (so it retains the + # command), and neither side completes a handshake. This logic lives in + # the external-reset domain so it is not cleared by the reset it guards. + mmio_resp = Wire(Channel(esi.MMIODataType)) + host_cmd = mmio_read_write.unpack(data=mmio_resp)['cmd'] + gate_ready = Wire(Bits(1)) + cmd_payload, cmd_valid = host_cmd.unwrap(gate_ready) + fwd_valid = (cmd_valid & ~reset_pending).as_bits() + gated_cmd, fwd_ready = host_cmd.type.wrap(cmd_payload, fwd_valid) + gate_ready.assign((fwd_ready & ~reset_pending).as_bits()) + gated_mmio, gated_froms = esi.MMIO.read_write.type.pack(cmd=gated_cmd) + mmio_resp.assign(gated_froms['data']) + wrapper = ESI_Cosim_UserTopWrapper(clk=ports.clk, - rst=ports.rst, - mmio=mmio_read_write) + rst=combined_rst, + mmio=gated_mmio) + reset_controller = DesignResetController(ResetCycles)( + clk=ports.clk, rst=ports.rst, reset_request=wrapper.reset_request) + design_reset.assign(reset_controller.design_reset) + reset_pending.assign(reset_controller.reset_pending) + + # While a reset is pending, drop host-driven responses to the design's + # hostmem service. In-flight requests issued before the reset will have + # their responses arrive after the design (and its hostmem tag/demux + # state) has been reset; delivering them would corrupt the fresh state or + # hang on a response the reset design never consumes. Draining them here + # (assert ready toward the host, never present a valid downstream) keeps + # the host response channels flowing. This lives in the external-reset + # domain so it is not cleared by the reset it guards. + def drop_while_resetting(chan: ChannelSignal) -> ChannelSignal: + src_ready = Wire(Bits(1)) + payload, valid = chan.unwrap(src_ready) + fwd_valid = (valid & ~reset_pending).as_bits() + gated, dn_ready = chan.type.wrap(payload, fwd_valid) + src_ready.assign((dn_ready | reset_pending).as_bits()) + return gated resp_channel = esi.ChannelService.from_host( esi.AppID("__cosim_hostmem_read_resp"), @@ -101,14 +160,15 @@ def build(ports): ("tag", UInt(8)), ("data", Bits(ESI_Cosim_UserTopWrapper.HostMemWidth)), ])) - req = wrapper.hostmem_read.unpack(resp=resp_channel)['req'] + req = wrapper.hostmem_read.unpack( + resp=drop_while_resetting(resp_channel))['req'] esi.ChannelService.to_host(esi.AppID("__cosim_hostmem_read_req"), req) ack_wire = Wire(Channel(UInt(8))) write_req = wrapper.hostmem_write.unpack(ackTag=ack_wire)['req'] ack_tag = esi.CallService.call(esi.AppID("__cosim_hostmem_write"), write_req, UInt(8)) - ack_wire.assign(ack_tag) + ack_wire.assign(drop_while_resetting(ack_tag)) cosim_svc = raw_esi.ServiceInstanceOp( result=[], diff --git a/lib/Dialect/ESI/runtime/tests/integration/test_esitester.py b/lib/Dialect/ESI/runtime/tests/integration/test_esitester.py index 4dd4e538e277..9d23038e0542 100644 --- a/lib/Dialect/ESI/runtime/tests/integration/test_esitester.py +++ b/lib/Dialect/ESI/runtime/tests/integration/test_esitester.py @@ -115,6 +115,15 @@ def test_telemetry(self, host: str, port: int) -> None: "writemem[32].addrCmdResponses: 0", ]) + def test_reset(self, host: str, port: int) -> None: + conn = f"{host}:{port}" + stdout = run_cmd(["esitester", "cosim", conn, "reset"]) + check_lines(stdout, [ + "[reset] reset requested", + "[reset] telemetry addrCmdResponses after reset = 0", + "Reset test passed", + ]) + def test_channel_python(self, conn: AcceleratorConnection) -> None: """Test ChannelService ToHost and FromHost ports from Python.""" acc = conn.build_accelerator()