diff --git a/default.nix b/default.nix index cb28ac1..2826ce1 100644 --- a/default.nix +++ b/default.nix @@ -22,7 +22,7 @@ let name = "firmware"; src = ./src; - cargoSha256 = "0p9d2j7qp00wpxm48phl5rq26simzry6w0m673lyhrlbzqdz4frb"; + cargoSha256 = "sha256-uiwESZNwPdVnDkA1n0v1DQHp3rTazDkgIYscVTpgNq0="; nativeBuildInputs = [ pkgs.gnumake @@ -35,7 +35,7 @@ let export XARGO_RUST_SRC="${rustPlatform.rust.rustc}/lib/rustlib/src/rust/library" export CLANG_EXTRA_INCLUDE_DIR="${pkgs.llvmPackages_9.clang-unwrapped.lib}/lib/clang/9.0.1/include" export CARGO_HOME=$(mktemp -d cargo-home.XXX) - make TARGET=${target} GWARGS="${if json == null then "-V ${variant}" else json}" ${fwtype} + make TARGET=${target} GWARGS="${if json == null then "-V ${variant}" else json}" ../build/${fwtype}.bin ''; installPhase = '' diff --git a/src/Makefile b/src/Makefile index 5eca52f..a6a15e0 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,18 +1,15 @@ TARGET := zc706 GWARGS := -V simple -all: runtime - -runtime: ../build/runtime.bin +all: ../build/runtime.bin .PHONY: all - -../build/pl.rs ../build/rustc-cfg: gateware/* +../build/pl.rs ../build/rustc-cfg ../build/mem.rs: gateware/* mkdir -p ../build python gateware/$(TARGET).py -r ../build/pl.rs -c ../build/rustc-cfg -m ../build/mem.rs $(GWARGS) -../build/firmware/armv7-none-eabihf/release/runtime: ../build/pl.rs ../build/rustc-cfg +../build/firmware/armv7-none-eabihf/release/runtime: ../build/pl.rs ../build/rustc-cfg ../build/mem.rs $(shell find . -print) cd runtime && \ XBUILD_SYSROOT_PATH=`pwd`/../../build/sysroot \ cargo xbuild --release \ @@ -22,12 +19,12 @@ runtime: ../build/runtime.bin ../build/runtime.bin: ../build/firmware/armv7-none-eabihf/release/runtime llvm-objcopy -O binary ../build/firmware/armv7-none-eabihf/release/runtime ../build/runtime.bin -satmanout: ../build/pl.rs ../build/rustc-cfg +../build/firmware/armv7-none-eabihf/release/satman: ../build/pl.rs ../build/rustc-cfg ../build/mem.rs $(shell find . -print) cd satman && \ XBUILD_SYSROOT_PATH=`pwd`/../../build/sysroot \ cargo xbuild --release \ --target-dir ../../build/firmware \ --no-default-features --features=target_$(TARGET) -satman: satmanout +../build/satman.bin: ../build/firmware/armv7-none-eabihf/release/satman llvm-objcopy -O binary ../build/firmware/armv7-none-eabihf/release/satman ../build/satman.bin \ No newline at end of file diff --git a/src/gateware/drtio_aux_controller.py b/src/gateware/drtio_aux_controller.py new file mode 100644 index 0000000..fc667b7 --- /dev/null +++ b/src/gateware/drtio_aux_controller.py @@ -0,0 +1,85 @@ +"""Auxiliary controller, common to satellite and master""" + +from artiq.gateware.drtio.aux_controller import Transmitter, Receiver +from migen.fhdl.simplify import FullMemoryWE +from misoc.interconnect.csr import * +from migen_axi.interconnect.sram import SRAM +from migen_axi.interconnect import axi + +max_packet = 1024 + +class _DRTIOAuxControllerBase(Module): + def __init__(self, link_layer): + self.bus = axi.Interface() + self.submodules.transmitter = Transmitter(link_layer, len(self.bus.w.data)) + self.submodules.receiver = Receiver(link_layer, len(self.bus.w.data)) + + def get_csrs(self): + return self.transmitter.get_csrs() + self.receiver.get_csrs() + + +# TODO: FullMemoryWE should be applied by migen.build +@FullMemoryWE() +class DRTIOAuxControllerAxi(_DRTIOAuxControllerBase): + def __init__(self, link_layer): + _DRTIOAuxControllerBase.__init__(self, link_layer) + + tx_sdram_if = SRAM(self.transmitter.mem, read_only=False) + rx_sdram_if = SRAM(self.receiver.mem, read_only=True) + aw_decoder = axi.AddressDecoder(self.bus.aw, + [(lambda a: a[log2_int(max_packet)] == 0, tx_sdram_if.bus.aw), + (lambda a: a[log2_int(max_packet)] == 1, rx_sdram_if.bus.aw)], + register=True) + ar_decoder = axi.AddressDecoder(self.bus.ar, + [(lambda a: a[log2_int(max_packet)] == 0, tx_sdram_if.bus.ar), + (lambda a: a[log2_int(max_packet)] == 1, rx_sdram_if.bus.ar)], + register=True) + # unlike wb, axi address decoder only connects ar/aw lanes, + # the rest must also be connected! + # not quite unlike an address decoder itself. + + # connect bus.b with tx.b + self.comb += [tx_sdram_if.bus.b.ready.eq(self.bus.b.ready), + self.bus.b.id.eq(tx_sdram_if.bus.b.id), + self.bus.b.resp.eq(tx_sdram_if.bus.b.resp), + self.bus.b.valid.eq(tx_sdram_if.bus.b.valid)] + # connect bus.w with tx.w + # no worries about w.valid and slave sel here, only tx will be written to + self.comb += [tx_sdram_if.bus.w.id.eq(self.bus.w.id), + tx_sdram_if.bus.w.data.eq(self.bus.w.data), + tx_sdram_if.bus.w.strb.eq(self.bus.w.strb), + tx_sdram_if.bus.w.last.eq(self.bus.w.last), + tx_sdram_if.bus.w.valid.eq(self.bus.w.valid), + self.bus.w.ready.eq(tx_sdram_if.bus.w.ready)] + # connect bus.r with rx.r and tx.r w/o data + self.comb += [self.bus.r.id.eq(rx_sdram_if.bus.r.id | tx_sdram_if.bus.r.id), + #self.bus.r.data.eq(rx_sdram_if.bus.r.data | tx_sdram_if.bus.r.data), + self.bus.r.resp.eq(rx_sdram_if.bus.r.resp | tx_sdram_if.bus.r.resp), + self.bus.r.last.eq(rx_sdram_if.bus.r.last | tx_sdram_if.bus.r.last), + self.bus.r.valid.eq(rx_sdram_if.bus.r.valid | tx_sdram_if.bus.r.valid), + rx_sdram_if.bus.r.ready.eq(self.bus.r.ready), + tx_sdram_if.bus.r.ready.eq(self.bus.r.ready)] + # connect read data after being masked + masked = [Replicate(rx_sdram_if.bus.r.valid, + len(self.bus.r.data) + ) & rx_sdram_if.bus.r.data, + Replicate(tx_sdram_if.bus.r.valid, + len(self.bus.r.data) + ) & tx_sdram_if.bus.r.data] + self.comb += self.bus.r.data.eq(reduce(or_, masked)) + + self.submodules += tx_sdram_if, rx_sdram_if, aw_decoder, ar_decoder + + +@FullMemoryWE() +class DRTIOAuxControllerBare(_DRTIOAuxControllerBase): + # Barebones version of the AuxController. No SRAM, no decoders. + # add memories manually from tx and rx in target code. + def get_tx_port(self): + return self.transmitter.mem.get_port(write_capable=True) + + def get_rx_port(self): + return self.receiver.mem.get_port(write_capable=False) + + def get_mem_size(self): + return max_packet diff --git a/src/gateware/kasli_soc.py b/src/gateware/kasli_soc.py index 5226501..fa45fe8 100755 --- a/src/gateware/kasli_soc.py +++ b/src/gateware/kasli_soc.py @@ -15,11 +15,16 @@ from misoc.integration import cpu_interface from artiq.coredevice import jsondesc from artiq.gateware import rtio, eem_7series from artiq.gateware.rtio.phy import ttl_simple +from artiq.gateware.rtio.xilinx_clocking import RTIOClockMultiplier +from artiq.gateware.drtio.transceiver import gtx_7series +from artiq.gateware.drtio.siphaser import SiPhaser7Series +from artiq.gateware.drtio.rx_synchronizer import XilinxRXSynchronizer +from artiq.gateware.drtio import * import dma import analyzer import acpki - +import drtio_aux_controller class RTIOCRG(Module, AutoCSR): def __init__(self, platform): @@ -70,7 +75,7 @@ class RTIOCRG(Module, AutoCSR): MultiReg(pll_locked, self.pll_locked.status) ] - + eem_iostandard_dict = { 0: "LVDS_25", 1: "LVDS_25", @@ -173,13 +178,287 @@ class GenericStandalone(SoCCore): class GenericMaster(SoCCore): - def __init__(self, description, **kwargs): - raise NotImplementedError + def __init__(self, description, acpki=False): + sys_clk_freq = 125e6 + rtio_clk_freq = 125e6 + + self.acpki = acpki + self.rustc_cfg = dict() + + platform = kasli_soc.Platform() + platform.toolchain.bitstream_commands.extend([ + "set_property BITSTREAM.GENERAL.COMPRESS True [current_design]", + ]) + ident = self.__class__.__name__ + if self.acpki: + ident = "acpki_" + ident + SoCCore.__init__(self, platform=platform, csr_data_width=32, ident=ident) + + platform.add_platform_command("create_clock -name clk_fpga_0 -period 8 [get_pins \"PS7/FCLKCLK[0]\"]") + platform.add_platform_command("set_input_jitter clk_fpga_0 0.24") + + data_pads = [platform.request("sfp", i) for i in range(4)] + + self.submodules.drtio_transceiver = gtx_7series.GTX( + clock_pads=platform.request("clk125_gtp"), + pads=data_pads, + sys_clk_freq=sys_clk_freq) + self.csr_devices.append("drtio_transceiver") + + self.crg = self.ps7 # HACK for eem_7series to find the clock + self.submodules.rtio_crg = RTIOClockMultiplier(rtio_clk_freq) + self.csr_devices.append("rtio_crg") + + self.rtio_channels = [] + has_grabber = any(peripheral["type"] == "grabber" for peripheral in description["peripherals"]) + if has_grabber: + self.grabber_csr_group = [] + eem_7series.add_peripherals(self, description["peripherals"], iostandard=eem_iostandard) + for i in (0, 1): + print("USER LED at RTIO channel 0x{:06x}".format(len(self.rtio_channels))) + user_led = self.platform.request("user_led", i) + phy = ttl_simple.Output(user_led) + self.submodules += phy + self.rtio_channels.append(rtio.Channel.from_phy(phy)) + self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels) + self.rtio_channels.append(rtio.LogChannel()) + + self.submodules.rtio_tsc = rtio.TSC("async", glbl_fine_ts_width=3) + + drtio_csr_group = [] + drtioaux_csr_group = [] + drtioaux_memory_group = [] + self.drtio_cri = [] + for i in range(len(self.drtio_transceiver.channels)): + core_name = "drtio" + str(i) + coreaux_name = "drtioaux" + str(i) + memory_name = "drtioaux" + str(i) + "_mem" + drtio_csr_group.append(core_name) + drtioaux_csr_group.append(coreaux_name) + drtioaux_memory_group.append(memory_name) + + cdr = ClockDomainsRenamer({"rtio_rx": "rtio_rx" + str(i)}) + + core = cdr(DRTIOMaster(self.rtio_tsc, self.drtio_transceiver.channels[i])) + setattr(self.submodules, core_name, core) + self.drtio_cri.append(core.cri) + self.csr_devices.append(core_name) + + coreaux = cdr(drtio_aux_controller.DRTIOAuxControllerBare(core.link_layer)) + setattr(self.submodules, coreaux_name, coreaux) + self.csr_devices.append(coreaux_name) + + size = coreaux.get_mem_size() + memory_address = self.axi2csr.register_port(coreaux.get_tx_port(), size) + self.axi2csr.register_port(coreaux.get_rx_port(), size) + self.add_memory_region(memory_name, self.mem_map["csr"] + memory_address, size * 2) + self.rustc_cfg["has_drtio"] = None + self.rustc_cfg["has_drtio_routing"] = None + self.add_csr_group("drtio", drtio_csr_group) + self.add_csr_group("drtioaux", drtioaux_csr_group) + self.add_memory_group("drtioaux_mem", drtioaux_memory_group) + + self.submodules.rtio_core = rtio.Core(self.rtio_tsc, self.rtio_channels) + self.csr_devices.append("rtio_core") + + if self.acpki: + self.rustc_cfg["ki_impl"] = "acp" + self.submodules.rtio = acpki.KernelInitiator(self.rtio_tsc, + bus=self.ps7.s_axi_acp, + user=self.ps7.s_axi_acp_user, + evento=self.ps7.event.o) + self.csr_devices.append("rtio") + else: + self.rustc_cfg["ki_impl"] = "csr" + self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc, now64=True) + self.csr_devices.append("rtio") + + self.submodules.rtio_dma = dma.DMA(self.ps7.s_axi_hp0) + self.csr_devices.append("rtio_dma") + + self.submodules.cri_con = rtio.CRIInterconnectShared( + [self.rtio.cri, self.rtio_dma.cri], + [self.rtio_core.cri] + self.drtio_cri, + enable_routing=True) + self.csr_devices.append("cri_con") + + self.submodules.rtio_moninj = rtio.MonInj(self.rtio_channels) + self.csr_devices.append("rtio_moninj") + + self.submodules.routing_table = rtio.RoutingTableAccess(self.cri_con) + self.csr_devices.append("routing_table") + + self.submodules.rtio_analyzer = analyzer.Analyzer(self.rtio_tsc, self.rtio_core.cri, + self.ps7.s_axi_hp1) + self.csr_devices.append("rtio_analyzer") + + if has_grabber: + self.rustc_cfg["has_grabber"] = None + self.add_csr_group("grabber", self.grabber_csr_group) class GenericSatellite(SoCCore): - def __init__(self, description, **kwargs): - raise NotImplementedError + def __init__(self, description, acpki=False): + sys_clk_freq = 125e6 + rtio_clk_freq = 125e6 + + self.acpki = acpki + self.rustc_cfg = dict() + + platform = kasli_soc.Platform() + platform.toolchain.bitstream_commands.extend([ + "set_property BITSTREAM.GENERAL.COMPRESS True [current_design]", + ]) + ident = self.__class__.__name__ + if self.acpki: + ident = "acpki_" + ident + SoCCore.__init__(self, platform=platform, csr_data_width=32, ident=ident) + + platform.add_platform_command("create_clock -name clk_fpga_0 -period 8 [get_pins \"PS7/FCLKCLK[0]\"]") + platform.add_platform_command("set_input_jitter clk_fpga_0 0.24") + + self.crg = self.ps7 # HACK for eem_7series to find the clock + self.submodules.rtio_crg = RTIOClockMultiplier(rtio_clk_freq) + self.csr_devices.append("rtio_crg") + + data_pads = [platform.request("sfp", i) for i in range(4)] + + self.submodules.drtio_transceiver = gtx_7series.GTX( + clock_pads=platform.request("clk125_gtp"), + pads=data_pads, + sys_clk_freq=sys_clk_freq) + self.csr_devices.append("drtio_transceiver") + + self.rtio_channels = [] + has_grabber = any(peripheral["type"] == "grabber" for peripheral in description["peripherals"]) + if has_grabber: + self.grabber_csr_group = [] + eem_7series.add_peripherals(self, description["peripherals"], iostandard=eem_iostandard) + for i in (0, 1): + print("USER LED at RTIO channel 0x{:06x}".format(len(self.rtio_channels))) + user_led = self.platform.request("user_led", i) + phy = ttl_simple.Output(user_led) + self.submodules += phy + self.rtio_channels.append(rtio.Channel.from_phy(phy)) + self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels) + self.rtio_channels.append(rtio.LogChannel()) + + self.submodules.rtio_tsc = rtio.TSC("sync", glbl_fine_ts_width=3) + + drtioaux_csr_group = [] + drtioaux_memory_group = [] + drtiorep_csr_group = [] + self.drtio_cri = [] + for i in range(len(self.drtio_transceiver.channels)): + coreaux_name = "drtioaux" + str(i) + memory_name = "drtioaux" + str(i) + "_mem" + drtioaux_csr_group.append(coreaux_name) + drtioaux_memory_group.append(memory_name) + + cdr = ClockDomainsRenamer({"rtio_rx": "rtio_rx" + str(i)}) + + if i == 0: + self.submodules.rx_synchronizer = cdr(XilinxRXSynchronizer()) + core = cdr(DRTIOSatellite( + self.rtio_tsc, self.drtio_transceiver.channels[i], + self.rx_synchronizer)) + self.submodules.drtiosat = core + self.csr_devices.append("drtiosat") + else: + corerep_name = "drtiorep" + str(i-1) + drtiorep_csr_group.append(corerep_name) + + core = cdr(DRTIORepeater( + self.rtio_tsc, self.drtio_transceiver.channels[i])) + setattr(self.submodules, corerep_name, core) + self.drtio_cri.append(core.cri) + self.csr_devices.append(corerep_name) + + coreaux = cdr(drtio_aux_controller.DRTIOAuxControllerBare(core.link_layer)) + setattr(self.submodules, coreaux_name, coreaux) + self.csr_devices.append(coreaux_name) + + mem_size = coreaux.get_mem_size() + tx_port = coreaux.get_tx_port() + rx_port = coreaux.get_rx_port() + memory_address = self.axi2csr.register_port(tx_port, mem_size) + # rcv in upper half of the memory, thus added second + self.axi2csr.register_port(rx_port, mem_size) + # and registered in PS interface + # manually, because software refers to rx/tx by halves of entire memory block, not names + self.add_memory_region(memory_name, self.mem_map["csr"] + memory_address, mem_size * 2) + self.rustc_cfg["has_drtio"] = None + self.rustc_cfg["has_drtio_routing"] = None + self.add_csr_group("drtioaux", drtioaux_csr_group) + self.add_memory_group("drtioaux_mem", drtioaux_memory_group) + self.add_csr_group("drtiorep", drtiorep_csr_group) + + if self.acpki: + self.rustc_cfg["ki_impl"] = "acp" + self.submodules.rtio = acpki.KernelInitiator(self.rtio_tsc, + bus=self.ps7.s_axi_acp, + user=self.ps7.s_axi_acp_user, + evento=self.ps7.event.o) + self.csr_devices.append("rtio") + else: + self.rustc_cfg["ki_impl"] = "csr" + self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc, now64=True) + self.csr_devices.append("rtio") + + self.submodules.rtio_dma = dma.DMA(self.ps7.s_axi_hp0) + self.csr_devices.append("rtio_dma") + + self.submodules.local_io = SyncRTIO(self.rtio_tsc, self.rtio_channels) + self.comb += self.drtiosat.async_errors.eq(self.local_io.async_errors) + + self.submodules.cri_con = rtio.CRIInterconnectShared( + [self.drtiosat.cri], + [self.local_io.cri] + self.drtio_cri, + mode="sync", enable_routing=True) + self.csr_devices.append("cri_con") + + self.submodules.routing_table = rtio.RoutingTableAccess(self.cri_con) + self.csr_devices.append("routing_table") + + self.submodules.rtio_moninj = rtio.MonInj(self.rtio_channels) + self.csr_devices.append("rtio_moninj") + + rtio_clk_period = 1e9/rtio_clk_freq + self.rustc_cfg["rtio_frequency"] = str(rtio_clk_freq/1e6) + + self.submodules.siphaser = SiPhaser7Series( + si5324_clkin=platform.request("cdr_clk"), + rx_synchronizer=self.rx_synchronizer, + ultrascale=False, + rtio_clk_freq=self.drtio_transceiver.rtio_clk_freq) + platform.add_false_path_constraints( + self.crg.cd_sys.clk, self.siphaser.mmcm_freerun_output) + self.csr_devices.append("siphaser") + self.rustc_cfg["has_si5324"] = None + self.rustc_cfg["has_siphaser"] = None + self.rustc_cfg["si5324_soft_reset"] = None + + gtx0 = self.drtio_transceiver.gtxs[0] + platform.add_period_constraint(gtx0.txoutclk, rtio_clk_period) + platform.add_period_constraint(gtx0.rxoutclk, rtio_clk_period) + platform.add_false_path_constraints( + self.crg.cd_sys.clk, + gtx0.txoutclk, gtx0.rxoutclk) + for gtx in self.drtio_transceiver.gtxs[1:]: + platform.add_period_constraint(gtx.rxoutclk, rtio_clk_period) + platform.add_false_path_constraints( + self.crg.cd_sys.clk, gtx.rxoutclk) + + if has_grabber: + self.rustc_cfg["has_grabber"] = None + self.add_csr_group("grabber", self.grabber_csr_group) + # no RTIO CRG here + + +def write_mem_file(soc, filename): + with open(filename, "w") as f: + f.write(cpu_interface.get_mem_rust( + soc.get_memory_regions(), soc.get_memory_groups(), None)) def write_csr_file(soc, filename): @@ -204,6 +483,8 @@ def main(): help="build Rust interface into the specified file") parser.add_argument("-c", default=None, help="build Rust compiler configuration into the specified file") + parser.add_argument("-m", default=None, + help="build Rust memory interface into the specified file") parser.add_argument("-g", default=None, help="build gateware into the specified directory") parser.add_argument("--acpki", default=False, action="store_true", @@ -230,6 +511,8 @@ def main(): if args.r is not None: write_csr_file(soc, args.r) + if args.m is not None: + write_mem_file(soc, args.m) if args.c is not None: write_rustc_cfg_file(soc, args.c) if args.g is not None: diff --git a/src/gateware/zc706.py b/src/gateware/zc706.py index a6cd746..45dabf8 100755 --- a/src/gateware/zc706.py +++ b/src/gateware/zc706.py @@ -11,13 +11,20 @@ from migen_axi.integration.soc_core import SoCCore from migen_axi.platforms import zc706 from misoc.interconnect.csr import * from misoc.integration import cpu_interface +from misoc.cores import gpio from artiq.gateware import rtio, nist_clock, nist_qc2 from artiq.gateware.rtio.phy import ttl_simple, ttl_serdes_7series, dds, spi2 +from artiq.gateware.rtio.xilinx_clocking import RTIOClockMultiplier, fix_serdes_timing_path +from artiq.gateware.drtio.transceiver import gtx_7series +from artiq.gateware.drtio.siphaser import SiPhaser7Series +from artiq.gateware.drtio.rx_synchronizer import XilinxRXSynchronizer +from artiq.gateware.drtio import * import dma import analyzer import acpki +import drtio_aux_controller class RTIOCRG(Module, AutoCSR): @@ -64,23 +71,45 @@ class RTIOCRG(Module, AutoCSR): ] +# The NIST backplanes require setting VADJ to 3.3V by reprogramming the power supply. +# This also changes the I/O standard for some on-board LEDs. +leds_fmc33 = [ + ("user_led_33", 0, Pins("Y21"), IOStandard("LVCMOS33")), + ("user_led_33", 1, Pins("G2"), IOStandard("LVCMOS15")), + ("user_led_33", 2, Pins("W21"), IOStandard("LVCMOS33")), + ("user_led_33", 3, Pins("A17"), IOStandard("LVCMOS15")), +] + +# same deal as with LEDs - changed I/O standard. +si5324_fmc33 = [ + ("si5324_33", 0, + Subsignal("rst_n", Pins("W23"), IOStandard("LVCMOS33")), + Subsignal("int", Pins("AJ25"), IOStandard("LVCMOS33")) + ), +] + + +def prepare_zc706_platform(platform): + platform.toolchain.bitstream_commands.extend([ + "set_property BITSTREAM.GENERAL.COMPRESS True [current_design]", + ]) + platform.add_platform_command("create_clock -name clk_fpga_0 -period 8 [get_pins \"PS7/FCLKCLK[0]\"]") + platform.add_platform_command("set_input_jitter clk_fpga_0 0.24") + + class ZC706(SoCCore): def __init__(self, acpki=False): self.acpki = acpki self.rustc_cfg = dict() platform = zc706.Platform() - platform.toolchain.bitstream_commands.extend([ - "set_property BITSTREAM.GENERAL.COMPRESS True [current_design]", - ]) + prepare_zc706_platform(platform) + ident = self.__class__.__name__ if self.acpki: ident = "acpki_" + ident SoCCore.__init__(self, platform=platform, csr_data_width=32, ident=ident) - platform.add_platform_command("create_clock -name clk_fpga_0 -period 8 [get_pins \"PS7/FCLKCLK[0]\"]") - platform.add_platform_command("set_input_jitter clk_fpga_0 0.24") - self.submodules.rtio_crg = RTIOCRG(self.platform, self.ps7.cd_sys.clk) self.csr_devices.append("rtio_crg") self.rustc_cfg["has_rtio_crg_clock_sel"] = None @@ -122,10 +151,284 @@ class ZC706(SoCCore): self.csr_devices.append("rtio_analyzer") -class Simple(ZC706): - def __init__(self, **kwargs): - ZC706.__init__(self, **kwargs) +class _MasterBase(SoCCore): + def __init__(self, acpki=False): + self.acpki = acpki + self.rustc_cfg = dict() + platform = zc706.Platform() + prepare_zc706_platform(platform) + ident = self.__class__.__name__ + if self.acpki: + ident = "acpki_" + ident + SoCCore.__init__(self, platform=platform, csr_data_width=32, ident=ident) + + platform.add_extension(si5324_fmc33) + + self.sys_clk_freq = 125e6 + + platform = self.platform + + self.comb += platform.request("sfp_tx_disable_n").eq(1) + data_pads = [ + platform.request("sfp"), + platform.request("user_sma_mgt") + ] + + # 1000BASE_BX10 Ethernet compatible, 125MHz RTIO clock + self.submodules.drtio_transceiver = gtx_7series.GTX( + clock_pads=platform.request("si5324_clkout"), + pads=data_pads, + sys_clk_freq=self.sys_clk_freq) + self.csr_devices.append("drtio_transceiver") + + self.submodules.rtio_tsc = rtio.TSC("async", glbl_fine_ts_width=3) + + drtio_csr_group = [] + drtioaux_csr_group = [] + drtioaux_memory_group = [] + self.drtio_cri = [] + for i in range(len(self.drtio_transceiver.channels)): + core_name = "drtio" + str(i) + coreaux_name = "drtioaux" + str(i) + memory_name = "drtioaux" + str(i) + "_mem" + drtio_csr_group.append(core_name) + drtioaux_csr_group.append(coreaux_name) + drtioaux_memory_group.append(memory_name) + + cdr = ClockDomainsRenamer({"rtio_rx": "rtio_rx" + str(i)}) + + core = cdr(DRTIOMaster( + self.rtio_tsc, self.drtio_transceiver.channels[i])) + setattr(self.submodules, core_name, core) + self.drtio_cri.append(core.cri) + self.csr_devices.append(core_name) + + coreaux = cdr(drtio_aux_controller.DRTIOAuxControllerBare(core.link_layer)) + setattr(self.submodules, coreaux_name, coreaux) + self.csr_devices.append(coreaux_name) + + mem_size = coreaux.get_mem_size() + memory_address = self.axi2csr.register_port(coreaux.get_tx_port(), mem_size) + self.axi2csr.register_port(coreaux.get_rx_port(), mem_size) + self.add_memory_region(memory_name, self.mem_map["csr"] + memory_address, mem_size * 2) + self.rustc_cfg["has_drtio"] = None + self.rustc_cfg["has_drtio_routing"] = None + self.add_csr_group("drtio", drtio_csr_group) + self.add_csr_group("drtioaux", drtioaux_csr_group) + self.add_memory_group("drtioaux_mem", drtioaux_memory_group) + + self.rustc_cfg["RTIO_FREQUENCY"] = str(self.drtio_transceiver.rtio_clk_freq/1e6) + + self.submodules.si5324_rst_n = gpio.GPIOOut(platform.request("si5324_33").rst_n) + self.csr_devices.append("si5324_rst_n") + self.rustc_cfg["has_si5324"] = None + self.rustc_cfg["si5324_as_synthesizer"] = None + + rtio_clk_period = 1e9/self.drtio_transceiver.rtio_clk_freq + # Constrain TX & RX timing for the first transceiver channel + # (First channel acts as master for phase alignment for all channels' TX) + gtx0 = self.drtio_transceiver.gtxs[0] + platform.add_period_constraint(gtx0.txoutclk, rtio_clk_period) + platform.add_period_constraint(gtx0.rxoutclk, rtio_clk_period) + platform.add_false_path_constraints( + self.ps7.cd_sys.clk, + gtx0.txoutclk, gtx0.rxoutclk) + # Constrain RX timing for the each transceiver channel + # (Each channel performs single-lane phase alignment for RX) + for gtx in self.drtio_transceiver.gtxs[1:]: + platform.add_period_constraint(gtx.rxoutclk, rtio_clk_period) + platform.add_false_path_constraints( + self.ps7.cd_sys.clk, gtx0.txoutclk, gtx.rxoutclk) + + self.submodules.rtio_crg = RTIOClockMultiplier(self.sys_clk_freq) + self.csr_devices.append("rtio_crg") + fix_serdes_timing_path(self.platform) + + def add_rtio(self, rtio_channels): + self.submodules.rtio_tsc = rtio.TSC("async", glbl_fine_ts_width=3) + self.submodules.rtio_core = rtio.Core(self.rtio_tsc, rtio_channels) + self.csr_devices.append("rtio_core") + + if self.acpki: + self.rustc_cfg["ki_impl"] = "acp" + self.submodules.rtio = acpki.KernelInitiator(self.rtio_tsc, + bus=self.ps7.s_axi_acp, + user=self.ps7.s_axi_acp_user, + evento=self.ps7.event.o) + self.csr_devices.append("rtio") + else: + self.rustc_cfg["ki_impl"] = "csr" + self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc, now64=True) + self.csr_devices.append("rtio") + + self.submodules.rtio_dma = dma.DMA(self.ps7.s_axi_hp0) + self.csr_devices.append("rtio_dma") + + self.submodules.local_io = SyncRTIO(self.rtio_tsc, rtio_channels) + self.submodules.cri_con = rtio.CRIInterconnectShared( + [self.rtio.cri, self.rtio_dma.cri], + [self.local_io.cri] + self.drtio_cri, + mode="sync", enable_routing=True) + self.csr_devices.append("cri_con") + + self.submodules.rtio_moninj = rtio.MonInj(rtio_channels) + self.csr_devices.append("rtio_moninj") + + self.submodules.rtio_analyzer = analyzer.Analyzer(self.rtio_tsc, self.rtio_core.cri, + self.ps7.s_axi_hp1) + self.csr_devices.append("rtio_analyzer") + + self.submodules.routing_table = rtio.RoutingTableAccess(self.cri_con) + self.csr_devices.append("routing_table") + + +class _SatelliteBase(SoCCore): + def __init__(self, acpki=False): + self.acpki = acpki + self.rustc_cfg = dict() + + platform = zc706.Platform() + prepare_zc706_platform(platform) + ident = self.__class__.__name__ + if self.acpki: + ident = "acpki_" + ident + SoCCore.__init__(self, platform=platform, csr_data_width=32, ident=ident) + + platform.add_extension(si5324_fmc33) + + self.sys_clk_freq = 125e6 + platform = self.platform + + # SFP + self.comb += platform.request("sfp_tx_disable_n").eq(0) + data_pads = [ + platform.request("sfp"), + platform.request("user_sma_mgt") + ] + + self.submodules.rtio_tsc = rtio.TSC("sync", glbl_fine_ts_width=3) + + # 1000BASE_BX10 Ethernet compatible, 125MHz RTIO clock + self.submodules.drtio_transceiver = gtx_7series.GTX( + clock_pads=platform.request("si5324_clkout"), + pads=data_pads, + sys_clk_freq=self.sys_clk_freq) + self.csr_devices.append("drtio_transceiver") + + drtioaux_csr_group = [] + drtioaux_memory_group = [] + drtiorep_csr_group = [] + self.drtio_cri = [] + for i in range(len(self.drtio_transceiver.channels)): + coreaux_name = "drtioaux" + str(i) + memory_name = "drtioaux" + str(i) + "_mem" + drtioaux_csr_group.append(coreaux_name) + drtioaux_memory_group.append(memory_name) + + cdr = ClockDomainsRenamer({"rtio_rx": "rtio_rx" + str(i)}) + + # Satellite + if i == 0: + self.submodules.rx_synchronizer = cdr(XilinxRXSynchronizer()) + core = cdr(DRTIOSatellite( + self.rtio_tsc, self.drtio_transceiver.channels[0], self.rx_synchronizer)) + self.submodules.drtiosat = core + self.csr_devices.append("drtiosat") + # Repeaters + else: + corerep_name = "drtiorep" + str(i-1) + drtiorep_csr_group.append(corerep_name) + core = cdr(DRTIORepeater( + self.rtio_tsc, self.drtio_transceiver.channels[i])) + setattr(self.submodules, corerep_name, core) + self.drtio_cri.append(core.cri) + self.csr_devices.append(corerep_name) + + coreaux = cdr(drtio_aux_controller.DRTIOAuxControllerBare(core.link_layer)) + setattr(self.submodules, coreaux_name, coreaux) + self.csr_devices.append(coreaux_name) + + mem_size = coreaux.get_mem_size() + tx_port = coreaux.get_tx_port() + rx_port = coreaux.get_rx_port() + memory_address = self.axi2csr.register_port(tx_port, mem_size) + # rcv in upper half of the memory, thus added second + self.axi2csr.register_port(rx_port, mem_size) + # and registered in PS interface + # manually, because software refers to rx/tx by halves of entire memory block, not names + self.add_memory_region(memory_name, self.mem_map["csr"] + memory_address, mem_size * 2) + self.rustc_cfg["has_drtio"] = None + self.rustc_cfg["has_drtio_routing"] = None + self.add_csr_group("drtioaux", drtioaux_csr_group) + self.add_csr_group("drtiorep", drtiorep_csr_group) + self.add_memory_group("drtioaux_mem", drtioaux_memory_group) + + self.rustc_cfg["rtio_frequency"] = str(self.drtio_transceiver.rtio_clk_freq/1e6) + + # Si5324 Phaser + self.submodules.siphaser = SiPhaser7Series( + si5324_clkin=platform.request("si5324_clkin"), + rx_synchronizer=self.rx_synchronizer, + ultrascale=False, + rtio_clk_freq=self.drtio_transceiver.rtio_clk_freq) + platform.add_false_path_constraints( + self.ps7.cd_sys.clk, self.siphaser.mmcm_freerun_output) + self.csr_devices.append("siphaser") + self.submodules.si5324_rst_n = gpio.GPIOOut(platform.request("si5324_33").rst_n) + self.csr_devices.append("si5324_rst_n") + self.rustc_cfg["has_si5324"] = None + self.rustc_cfg["has_siphaser"] = None + + rtio_clk_period = 1e9/self.drtio_transceiver.rtio_clk_freq + # Constrain TX & RX timing for the first transceiver channel + # (First channel acts as master for phase alignment for all channels' TX) + gtx0 = self.drtio_transceiver.gtxs[0] + platform.add_period_constraint(gtx0.txoutclk, rtio_clk_period) + platform.add_period_constraint(gtx0.rxoutclk, rtio_clk_period) + platform.add_false_path_constraints( + self.ps7.cd_sys.clk, + gtx0.txoutclk, gtx0.rxoutclk) + # Constrain RX timing for the each transceiver channel + # (Each channel performs single-lane phase alignment for RX) + for gtx in self.drtio_transceiver.gtxs[1:]: + platform.add_period_constraint(gtx.rxoutclk, rtio_clk_period) + platform.add_false_path_constraints( + self.ps7.cd_sys.clk, gtx.rxoutclk) + + self.submodules.rtio_crg = RTIOClockMultiplier(self.sys_clk_freq) + self.csr_devices.append("rtio_crg") + fix_serdes_timing_path(self.platform) + + def add_rtio(self, rtio_channels): + self.submodules.rtio_moninj = rtio.MonInj(rtio_channels) + self.csr_devices.append("rtio_moninj") + + if self.acpki: + self.rustc_cfg["ki_impl"] = "acp" + self.submodules.rtio = acpki.KernelInitiator(self.rtio_tsc, + bus=self.ps7.s_axi_acp, + user=self.ps7.s_axi_acp_user, + evento=self.ps7.event.o) + self.csr_devices.append("rtio") + else: + self.rustc_cfg["ki_impl"] = "csr" + self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc, now64=True) + self.csr_devices.append("rtio") + + self.submodules.local_io = SyncRTIO(self.rtio_tsc, rtio_channels) + self.submodules.cri_con = rtio.CRIInterconnectShared( + [self.drtiosat.cri], + [self.local_io.cri] + self.drtio_cri, + mode="sync", enable_routing=True) + self.csr_devices.append("cri_con") + + self.submodules.routing_table = rtio.RoutingTableAccess(self.cri_con) + self.csr_devices.append("routing_table") + + +class _Simple_RTIO: + def __init__(self): platform = self.platform rtio_channels = [] @@ -170,13 +473,11 @@ _sdcard_spi_33 = [ ) ] -class NIST_CLOCK(ZC706): +class _NIST_CLOCK_RTIO: """ NIST clock hardware, with old backplane and 11 DDS channels """ - def __init__(self, **kwargs): - ZC706.__init__(self, **kwargs) - + def __init__(self): platform = self.platform platform.add_extension(nist_clock.fmc_adapter_io) platform.add_extension(leds_fmc33) @@ -245,14 +546,12 @@ class NIST_CLOCK(ZC706): self.add_rtio(rtio_channels) -class NIST_QC2(ZC706): +class _NIST_QC2_RTIO: """ NIST QC2 hardware, as used in Quantum I and Quantum II, with new backplane and 24 DDS channels. Two backplanes are used. """ - def __init__(self, **kwargs): - ZC706.__init__(self, **kwargs) - + def __init__(self): platform = self.platform platform.add_extension(nist_qc2.fmc_adapter_io) platform.add_extension(leds_fmc33) @@ -309,7 +608,56 @@ class NIST_QC2(ZC706): self.add_rtio(rtio_channels) -VARIANTS = {cls.__name__.lower(): cls for cls in [Simple, NIST_CLOCK, NIST_QC2]} +class Simple(ZC706, _Simple_RTIO): + def __init__(self, acpki): + ZC706.__init__(self, acpki) + _Simple_RTIO.__init__(self) + +class Master(_MasterBase, _Simple_RTIO): + def __init__(self, acpki): + _MasterBase.__init__(self, acpki) + _Simple_RTIO.__init__(self) + +class Satellite(_SatelliteBase, _Simple_RTIO): + def __init__(self, acpki): + _SatelliteBase.__init__(self, acpki) + _Simple_RTIO.__init__(self) + +class NIST_CLOCK(ZC706, _NIST_CLOCK_RTIO): + def __init__(self, acpki): + ZC706.__init__(self, acpki) + _NIST_CLOCK_RTIO.__init__(self) + +class NIST_CLOCK_Master(_MasterBase, _NIST_CLOCK_RTIO): + def __init__(self, acpki): + _MasterBase.__init__(self, acpki) + + _NIST_CLOCK_RTIO.__init__(self) + +class NIST_CLOCK_Satellite(_SatelliteBase, _NIST_CLOCK_RTIO): + def __init__(self, acpki): + _SatelliteBase.__init__(self, acpki) + _NIST_CLOCK_RTIO.__init__(self) + +class NIST_QC2(ZC706, _NIST_QC2_RTIO): + def __init__(self, acpki): + ZC706.__init__(self, acpki) + _NIST_QC2_RTIO.__init__(self) + +class NIST_QC2_Master(_MasterBase, _NIST_QC2_RTIO): + def __init__(self, acpki): + _MasterBase.__init__(self, acpki) + _NIST_QC2_RTIO.__init__(self) + +class NIST_QC2_Satellite(_SatelliteBase, _NIST_QC2_RTIO): + def __init__(self, acpki): + _SatelliteBase.__init__(self, acpki) + _NIST_QC2_RTIO.__init__(self) + + +VARIANTS = {cls.__name__.lower(): cls for cls in [Simple, Master, Satellite, + NIST_CLOCK, NIST_CLOCK_Master, NIST_CLOCK_Satellite, + NIST_QC2, NIST_QC2_Master, NIST_QC2_Satellite]} def write_csr_file(soc, filename): @@ -317,6 +665,11 @@ def write_csr_file(soc, filename): f.write(cpu_interface.get_csr_rust( soc.get_csr_regions(), soc.get_csr_groups(), soc.get_constants())) +def write_mem_file(soc, filename): + with open(filename, "w") as f: + f.write(cpu_interface.get_mem_rust( + soc.get_memory_regions(), soc.get_memory_groups(), None)) + def write_rustc_cfg_file(soc, filename): with open(filename, "w") as f: @@ -332,6 +685,8 @@ def main(): description="ARTIQ port to the ZC706 Zynq development kit") parser.add_argument("-r", default=None, help="build Rust interface into the specified file") + parser.add_argument("-m", default=None, + help="build Rust memory interface into the specified file") parser.add_argument("-c", default=None, help="build Rust compiler configuration into the specified file") parser.add_argument("-g", default=None, @@ -356,11 +711,12 @@ def main(): if args.r is not None: write_csr_file(soc, args.r) + if args.m is not None: + write_mem_file(soc, args.m) if args.c is not None: write_rustc_cfg_file(soc, args.c) if args.g is not None: soc.build(build_dir=args.g) - if __name__ == "__main__": main() diff --git a/src/libboard_artiq/Cargo.toml b/src/libboard_artiq/Cargo.toml new file mode 100644 index 0000000..8b9f7b8 --- /dev/null +++ b/src/libboard_artiq/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "libboard_artiq" +version = "0.0.0" +authors = ["M-Labs"] +edition = "2018" + +[lib] +name = "libboard_artiq" + +[features] +target_zc706 = [] +target_kasli_soc = [] + +[build-dependencies] +build_zynq = { path = "../libbuild_zynq" } + +[dependencies] +log = "0.4" +log_buffer = { version = "1.2" } +crc = { version = "1.7", default-features = false } +core_io = { version = "0.1", features = ["collections"] } +embedded-hal = "0.2" +nb = "1.0" +void = { version = "1", default-features = false } + +io = { path = "../libio", features = ["byteorder"] } +libboard_zynq = { git = "https://git.m-labs.hk/M-Labs/zynq-rs.git"} +libregister = { git = "https://git.m-labs.hk/M-Labs/zynq-rs.git" } +libconfig = { git = "https://git.m-labs.hk/M-Labs/zynq-rs.git"} +libcortex_a9 = { git = "https://git.m-labs.hk/M-Labs/zynq-rs.git" } +libasync = { git = "https://git.m-labs.hk/M-Labs/zynq-rs.git" } \ No newline at end of file diff --git a/src/libboard_artiq/build.rs b/src/libboard_artiq/build.rs new file mode 100644 index 0000000..ba3b31b --- /dev/null +++ b/src/libboard_artiq/build.rs @@ -0,0 +1,5 @@ +extern crate build_zynq; + +fn main() { + build_zynq::cfg(); +} diff --git a/src/libboard_artiq/src/drtio_routing.rs b/src/libboard_artiq/src/drtio_routing.rs new file mode 100644 index 0000000..9688ee3 --- /dev/null +++ b/src/libboard_artiq/src/drtio_routing.rs @@ -0,0 +1,109 @@ +use libconfig::Config; +#[cfg(has_drtio_routing)] +use crate::pl::csr; +use core::fmt; + +use log::{warn, info}; + +#[cfg(has_drtio_routing)] +pub const DEST_COUNT: usize = 256; +#[cfg(not(has_drtio_routing))] +pub const DEST_COUNT: usize = 0; +pub const MAX_HOPS: usize = 32; +pub const INVALID_HOP: u8 = 0xff; + +pub struct RoutingTable(pub [[u8; MAX_HOPS]; DEST_COUNT]); + +impl RoutingTable { + // default routing table is for star topology with no repeaters + pub fn default_master(default_n_links: usize) -> RoutingTable { + let mut ret = RoutingTable([[INVALID_HOP; MAX_HOPS]; DEST_COUNT]); + let n_entries = default_n_links + 1; // include local RTIO + for i in 0..n_entries { + ret.0[i][0] = i as u8; + } + for i in 1..n_entries { + ret.0[i][1] = 0x00; + } + ret + } + + // use this by default on satellite, as they receive + // the routing table from the master + pub fn default_empty() -> RoutingTable { + RoutingTable([[INVALID_HOP; MAX_HOPS]; DEST_COUNT]) + } +} + +impl fmt::Display for RoutingTable { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "RoutingTable {{")?; + for i in 0..DEST_COUNT { + if self.0[i][0] != INVALID_HOP { + write!(f, " {}:", i)?; + for j in 0..MAX_HOPS { + if self.0[i][j] == INVALID_HOP { + break; + } + write!(f, " {}", self.0[i][j])?; + } + write!(f, ";")?; + } + } + write!(f, " }}")?; + Ok(()) + } +} + +pub fn config_routing_table(default_n_links: usize, cfg: &Config) -> RoutingTable { + let mut ret = RoutingTable::default_master(default_n_links); + if let Ok(data) = cfg.read("routing_table") { + if data.len() == DEST_COUNT*MAX_HOPS + { + for i in 0..DEST_COUNT { + for j in 0..MAX_HOPS { + ret.0[i][j] = data[i*MAX_HOPS+j]; + } + } + } + else { + warn!("length of the routing table is incorrect, using default"); + } + } + else { + warn!("could not read routing table from configuration, using default"); + } + info!("routing table: {}", ret); + ret +} + +#[cfg(has_drtio_routing)] +pub fn interconnect_enable(routing_table: &RoutingTable, rank: u8, destination: u8) { + let hop = routing_table.0[destination as usize][rank as usize]; + unsafe { + csr::routing_table::destination_write(destination); + csr::routing_table::hop_write(hop); + } +} + +#[cfg(has_drtio_routing)] +pub fn interconnect_disable(destination: u8) { + unsafe { + csr::routing_table::destination_write(destination); + csr::routing_table::hop_write(INVALID_HOP); + } +} + +#[cfg(has_drtio_routing)] +pub fn interconnect_enable_all(routing_table: &RoutingTable, rank: u8) { + for i in 0..DEST_COUNT { + interconnect_enable(routing_table, rank, i as u8); + } +} + +#[cfg(has_drtio_routing)] +pub fn interconnect_disable_all() { + for i in 0..DEST_COUNT { + interconnect_disable(i as u8); + } +} diff --git a/src/libboard_artiq/src/drtioaux.rs b/src/libboard_artiq/src/drtioaux.rs new file mode 100644 index 0000000..8d3a7a9 --- /dev/null +++ b/src/libboard_artiq/src/drtioaux.rs @@ -0,0 +1,174 @@ +use crc; + +use core_io::{ErrorKind as IoErrorKind, Error as IoError}; + +use io::{proto::ProtoRead, proto::ProtoWrite, Cursor}; +use libboard_zynq::{timer::GlobalTimer, time::Milliseconds}; +use crate::mem::mem::DRTIOAUX_MEM; +use crate::pl::csr::DRTIOAUX; +use crate::drtioaux_proto::Error as ProtocolError; + +pub use crate::drtioaux_proto::Packet; + +#[derive(Debug)] +pub enum Error { + GatewareError, + CorruptedPacket, + + LinkDown, + TimedOut, + UnexpectedReply, + + RoutingError, + + Protocol(ProtocolError) +} + +impl From for Error { + fn from(value: ProtocolError) -> Error { + Error::Protocol(value) + } +} + +impl From for Error { + fn from(value: IoError) -> Error { + Error::Protocol(ProtocolError::Io(value)) + } +} + +pub fn reset(linkno: u8) { + let linkno = linkno as usize; + unsafe { + // clear buffer first to limit race window with buffer overflow + // error. We assume the CPU is fast enough so that no two packets + // will be received between the buffer and the error flag are cleared. + (DRTIOAUX[linkno].aux_rx_present_write)(1); + (DRTIOAUX[linkno].aux_rx_error_write)(1); + } +} + +pub fn has_rx_error(linkno: u8) -> bool { + let linkno = linkno as usize; + unsafe { + let error = (DRTIOAUX[linkno].aux_rx_error_read)() != 0; + if error { + (DRTIOAUX[linkno].aux_rx_error_write)(1) + } + error + } +} + +pub fn copy_with_swap(src: *mut u8, dst: *mut u8, len: isize) { + // for some reason, everything except checksum arrives + // with byte order swapped. and it must be sent as such too. + unsafe { + for i in (0..(len-4)).step_by(4) { + *dst.offset(i) = *src.offset(i+3); + *dst.offset(i+1) = *src.offset(i+2); + *dst.offset(i+2) = *src.offset(i+1); + *dst.offset(i+3) = *src.offset(i); + } + // checksum untouched + // unrolled for performance + *dst.offset(len-4) = *src.offset(len-4); + *dst.offset(len-3) = *src.offset(len-3); + *dst.offset(len-2) = *src.offset(len-2); + *dst.offset(len-1) = *src.offset(len-1); + } +} + +fn receive(linkno: u8, f: F) -> Result, Error> + where F: FnOnce(&[u8]) -> Result +{ + let linkidx = linkno as usize; + unsafe { + if (DRTIOAUX[linkidx].aux_rx_present_read)() == 1 { + let ptr = (DRTIOAUX_MEM[linkidx].base + DRTIOAUX_MEM[linkidx].size / 2) as *mut u8; + let len = (DRTIOAUX[linkidx].aux_rx_length_read)() as usize; + // work buffer, as byte order will need to be swapped, cannot be in place + let mut buf: [u8; 1024] = [0; 1024]; + copy_with_swap(ptr, buf.as_mut_ptr(), len as isize); + let result = f(&buf[0..len]); + (DRTIOAUX[linkidx].aux_rx_present_write)(1); + Ok(Some(result?)) + } else { + Ok(None) + } + } +} + +pub fn recv(linkno: u8) -> Result, Error> { + if has_rx_error(linkno) { + return Err(Error::GatewareError) + } + + receive(linkno, |buffer| { + if buffer.len() < 8 { + return Err(IoError::new(IoErrorKind::UnexpectedEof, "Unexpected end").into()) + } + + let mut reader = Cursor::new(buffer); + + let checksum_at = buffer.len() - 4; + let checksum = crc::crc32::checksum_ieee(&reader.get_ref()[0..checksum_at]); + reader.set_position(checksum_at); + if reader.read_u32()? != checksum { + return Err(Error::CorruptedPacket) + } + reader.set_position(0); + + Ok(Packet::read_from(&mut reader)?) + }) +} + +pub fn recv_timeout(linkno: u8, timeout_ms: Option, + timer: GlobalTimer) -> Result +{ + let timeout_ms = Milliseconds(timeout_ms.unwrap_or(10)); + let limit = timer.get_time() + timeout_ms; + while timer.get_time() < limit { + match recv(linkno)? { + None => (), + Some(packet) => return Ok(packet), + } + } + Err(Error::TimedOut) +} + +fn transmit(linkno: u8, f: F) -> Result<(), Error> + where F: FnOnce(&mut [u8]) -> Result +{ + let linkno = linkno as usize; + unsafe { + while (DRTIOAUX[linkno].aux_tx_read)() != 0 {} + let ptr = DRTIOAUX_MEM[linkno].base as *mut u8; + let len = DRTIOAUX_MEM[linkno].size / 2; + // work buffer, works with unaligned mem access + let mut buf: [u8; 1024] = [0; 1024]; + let len = f(&mut buf[0..len])?; + copy_with_swap(buf.as_mut_ptr(), ptr, len as isize); + (DRTIOAUX[linkno].aux_tx_length_write)(len as u16); + (DRTIOAUX[linkno].aux_tx_write)(1); + Ok(()) + } +} + +pub fn send(linkno: u8, packet: &Packet) -> Result<(), Error> { + transmit(linkno, |buffer| { + let mut writer = Cursor::new(buffer); + + packet.write_to(&mut writer)?; + + let padding = 4 - (writer.position() % 4); + if padding != 4 { + for _ in 0..padding { + writer.write_u8(0)?; + } + } + + let checksum = crc::crc32::checksum_ieee(&writer.get_ref()[0..writer.position()]); + writer.write_u32(checksum)?; + + Ok(writer.position()) + }) +} diff --git a/src/libboard_artiq/src/drtioaux_async.rs b/src/libboard_artiq/src/drtioaux_async.rs new file mode 100644 index 0000000..eb7f219 --- /dev/null +++ b/src/libboard_artiq/src/drtioaux_async.rs @@ -0,0 +1,139 @@ +use crc; + +use core_io::{ErrorKind as IoErrorKind, Error as IoError}; +use void::Void; +use nb; + +use libboard_zynq::{timer::GlobalTimer, time::Milliseconds}; +use libasync::{task, block_async}; + +use io::{proto::ProtoRead, proto::ProtoWrite, Cursor}; +use crate::mem::mem::DRTIOAUX_MEM; +use crate::pl::csr::DRTIOAUX; +use crate::drtioaux::{Error, has_rx_error, copy_with_swap}; + +pub use crate::drtioaux_proto::Packet; + +pub async fn reset(linkno: u8) { + let linkno = linkno as usize; + unsafe { + // clear buffer first to limit race window with buffer overflow + // error. We assume the CPU is fast enough so that no two packets + // will be received between the buffer and the error flag are cleared. + (DRTIOAUX[linkno].aux_rx_present_write)(1); + (DRTIOAUX[linkno].aux_rx_error_write)(1); + } +} + +fn tx_ready(linkno: usize) -> nb::Result<(), Void> { + unsafe { + if (DRTIOAUX[linkno].aux_tx_read)() != 0 { + Err(nb::Error::WouldBlock) + } + else { + Ok(()) + } + } +} + +async fn receive(linkno: u8, f: F) -> Result, Error> + where F: FnOnce(&[u8]) -> Result +{ + let linkidx = linkno as usize; + unsafe { + if (DRTIOAUX[linkidx].aux_rx_present_read)() == 1 { + let ptr = (DRTIOAUX_MEM[linkidx].base + DRTIOAUX_MEM[linkidx].size / 2) as *mut u8; + let len = (DRTIOAUX[linkidx].aux_rx_length_read)() as usize; + // work buffer, as byte order will need to be swapped, cannot be in place + let mut buf: [u8; 1024] = [0; 1024]; + copy_with_swap(ptr, buf.as_mut_ptr(), len as isize); + let result = f(&buf[0..len]); + (DRTIOAUX[linkidx].aux_rx_present_write)(1); + Ok(Some(result?)) + } else { + Ok(None) + } + } +} + +pub async fn recv(linkno: u8) -> Result, Error> { + if has_rx_error(linkno) { + return Err(Error::GatewareError) + } + + receive(linkno, |buffer| { + if buffer.len() < 8 { + return Err(IoError::new(IoErrorKind::UnexpectedEof, "Unexpected end").into()) + } + + let mut reader = Cursor::new(buffer); + + let checksum_at = buffer.len() - 4; + let checksum = crc::crc32::checksum_ieee(&reader.get_ref()[0..checksum_at]); + reader.set_position(checksum_at); + if reader.read_u32()? != checksum { + return Err(Error::CorruptedPacket) + } + reader.set_position(0); + + Ok(Packet::read_from(&mut reader)?) + }).await +} + +pub async fn recv_timeout(linkno: u8, timeout_ms: Option, + timer: GlobalTimer) -> Result +{ + let timeout_ms = Milliseconds(timeout_ms.unwrap_or(10)); + let limit = timer.get_time() + timeout_ms; + let mut would_block = false; + while timer.get_time() < limit { + // to ensure one last time recv would run one last time + // in case async would return after timeout + if would_block { + task::r#yield().await; + } + match recv(linkno).await? { + None => { would_block = true; }, + Some(packet) => return Ok(packet), + } + } + Err(Error::TimedOut) +} + +async fn transmit(linkno: u8, f: F) -> Result<(), Error> + where F: FnOnce(&mut [u8]) -> Result +{ + let linkno = linkno as usize; + unsafe { + let _ = block_async!(tx_ready(linkno)).await; + let ptr = DRTIOAUX_MEM[linkno].base as *mut u8; + let len = DRTIOAUX_MEM[linkno].size / 2; + // work buffer, works with unaligned mem access + let mut buf: [u8; 1024] = [0; 1024]; + let len = f(&mut buf[0..len])?; + copy_with_swap(buf.as_mut_ptr(), ptr, len as isize); + (DRTIOAUX[linkno].aux_tx_length_write)(len as u16); + (DRTIOAUX[linkno].aux_tx_write)(1); + Ok(()) + } +} + +pub async fn send(linkno: u8, packet: &Packet) -> Result<(), Error> { + transmit(linkno, |buffer| { + let mut writer = Cursor::new(buffer); + + packet.write_to(&mut writer)?; + + let padding = 4 - (writer.position() % 4); + if padding != 4 { + for _ in 0..padding { + writer.write_u8(0)?; + } + } + + let checksum = crc::crc32::checksum_ieee(&writer.get_ref()[0..writer.position()]); + writer.write_u32(checksum)?; + + Ok(writer.position()) + }).await +} diff --git a/src/libboard_artiq/src/drtioaux_proto.rs b/src/libboard_artiq/src/drtioaux_proto.rs new file mode 100644 index 0000000..62523d6 --- /dev/null +++ b/src/libboard_artiq/src/drtioaux_proto.rs @@ -0,0 +1,339 @@ +use core_io::{Write, Read, Error as IoError}; + +use io::proto::{ProtoWrite, ProtoRead}; + +#[derive(Debug)] +pub enum Error { + UnknownPacket(u8), + Io(IoError) +} + +impl From for Error { + fn from(value: IoError) -> Error { + Error::Io(value) + } +} + +#[derive(PartialEq, Debug)] +pub enum Packet { + EchoRequest, + EchoReply, + ResetRequest, + ResetAck, + TSCAck, + + DestinationStatusRequest { destination: u8 }, + DestinationDownReply, + DestinationOkReply, + DestinationSequenceErrorReply { channel: u16 }, + DestinationCollisionReply { channel: u16 }, + DestinationBusyReply { channel: u16 }, + + RoutingSetPath { destination: u8, hops: [u8; 32] }, + RoutingSetRank { rank: u8 }, + RoutingAck, + + MonitorRequest { destination: u8, channel: u16, probe: u8 }, + MonitorReply { value: u32 }, + InjectionRequest { destination: u8, channel: u16, overrd: u8, value: u8 }, + InjectionStatusRequest { destination: u8, channel: u16, overrd: u8 }, + InjectionStatusReply { value: u8 }, + + I2cStartRequest { destination: u8, busno: u8 }, + I2cRestartRequest { destination: u8, busno: u8 }, + I2cStopRequest { destination: u8, busno: u8 }, + I2cWriteRequest { destination: u8, busno: u8, data: u8 }, + I2cWriteReply { succeeded: bool, ack: bool }, + I2cReadRequest { destination: u8, busno: u8, ack: bool }, + I2cReadReply { succeeded: bool, data: u8 }, + I2cBasicReply { succeeded: bool }, + + SpiSetConfigRequest { destination: u8, busno: u8, flags: u8, length: u8, div: u8, cs: u8 }, + SpiWriteRequest { destination: u8, busno: u8, data: u32 }, + SpiReadRequest { destination: u8, busno: u8 }, + SpiReadReply { succeeded: bool, data: u32 }, + SpiBasicReply { succeeded: bool }, + +} + +impl Packet { + pub fn read_from(reader: &mut R) -> Result + where R: Read + ?Sized + { + Ok(match reader.read_u8()? { + 0x00 => Packet::EchoRequest, + 0x01 => Packet::EchoReply, + 0x02 => Packet::ResetRequest, + 0x03 => Packet::ResetAck, + 0x04 => Packet::TSCAck, + + 0x20 => Packet::DestinationStatusRequest { + destination: reader.read_u8()? + }, + 0x21 => Packet::DestinationDownReply, + 0x22 => Packet::DestinationOkReply, + 0x23 => Packet::DestinationSequenceErrorReply { + channel: reader.read_u16()? + }, + 0x24 => Packet::DestinationCollisionReply { + channel: reader.read_u16()? + }, + 0x25 => Packet::DestinationBusyReply { + channel: reader.read_u16()? + }, + + 0x30 => { + let destination = reader.read_u8()?; + let mut hops = [0; 32]; + reader.read_exact(&mut hops)?; + Packet::RoutingSetPath { + destination: destination, + hops: hops + } + }, + 0x31 => Packet::RoutingSetRank { + rank: reader.read_u8()? + }, + 0x32 => Packet::RoutingAck, + + 0x40 => Packet::MonitorRequest { + destination: reader.read_u8()?, + channel: reader.read_u16()?, + probe: reader.read_u8()? + }, + 0x41 => Packet::MonitorReply { + value: reader.read_u32()? + }, + 0x50 => Packet::InjectionRequest { + destination: reader.read_u8()?, + channel: reader.read_u16()?, + overrd: reader.read_u8()?, + value: reader.read_u8()? + }, + 0x51 => Packet::InjectionStatusRequest { + destination: reader.read_u8()?, + channel: reader.read_u16()?, + overrd: reader.read_u8()? + }, + 0x52 => Packet::InjectionStatusReply { + value: reader.read_u8()? + }, + + 0x80 => Packet::I2cStartRequest { + destination: reader.read_u8()?, + busno: reader.read_u8()? + }, + 0x81 => Packet::I2cRestartRequest { + destination: reader.read_u8()?, + busno: reader.read_u8()? + }, + 0x82 => Packet::I2cStopRequest { + destination: reader.read_u8()?, + busno: reader.read_u8()? + }, + 0x83 => Packet::I2cWriteRequest { + destination: reader.read_u8()?, + busno: reader.read_u8()?, + data: reader.read_u8()? + }, + 0x84 => Packet::I2cWriteReply { + succeeded: reader.read_bool()?, + ack: reader.read_bool()? + }, + 0x85 => Packet::I2cReadRequest { + destination: reader.read_u8()?, + busno: reader.read_u8()?, + ack: reader.read_bool()? + }, + 0x86 => Packet::I2cReadReply { + succeeded: reader.read_bool()?, + data: reader.read_u8()? + }, + 0x87 => Packet::I2cBasicReply { + succeeded: reader.read_bool()? + }, + + 0x90 => Packet::SpiSetConfigRequest { + destination: reader.read_u8()?, + busno: reader.read_u8()?, + flags: reader.read_u8()?, + length: reader.read_u8()?, + div: reader.read_u8()?, + cs: reader.read_u8()? + }, + /* 0x91: was Packet::SpiSetXferRequest */ + 0x92 => Packet::SpiWriteRequest { + destination: reader.read_u8()?, + busno: reader.read_u8()?, + data: reader.read_u32()? + }, + 0x93 => Packet::SpiReadRequest { + destination: reader.read_u8()?, + busno: reader.read_u8()? + }, + 0x94 => Packet::SpiReadReply { + succeeded: reader.read_bool()?, + data: reader.read_u32()? + }, + 0x95 => Packet::SpiBasicReply { + succeeded: reader.read_bool()? + }, + + ty => return Err(Error::UnknownPacket(ty)) + }) + } + + pub fn write_to(&self, writer: &mut W) -> Result<(), IoError> + where W: Write + ?Sized + { + + match *self { + Packet::EchoRequest => + writer.write_u8(0x00)?, + Packet::EchoReply => + writer.write_u8(0x01)?, + Packet::ResetRequest => + writer.write_u8(0x02)?, + Packet::ResetAck => + writer.write_u8(0x03)?, + Packet::TSCAck => + writer.write_u8(0x04)?, + + Packet::DestinationStatusRequest { destination } => { + writer.write_u8(0x20)?; + writer.write_u8(destination)?; + }, + Packet::DestinationDownReply => + writer.write_u8(0x21)?, + Packet::DestinationOkReply => + writer.write_u8(0x22)?, + Packet::DestinationSequenceErrorReply { channel } => { + writer.write_u8(0x23)?; + writer.write_u16(channel)?; + }, + Packet::DestinationCollisionReply { channel } => { + writer.write_u8(0x24)?; + writer.write_u16(channel)?; + }, + Packet::DestinationBusyReply { channel } => { + writer.write_u8(0x25)?; + writer.write_u16(channel)?; + }, + + Packet::RoutingSetPath { destination, hops } => { + writer.write_u8(0x30)?; + writer.write_u8(destination)?; + writer.write_all(&hops)?; + }, + Packet::RoutingSetRank { rank } => { + writer.write_u8(0x31)?; + writer.write_u8(rank)?; + }, + Packet::RoutingAck => + writer.write_u8(0x32)?, + + Packet::MonitorRequest { destination, channel, probe } => { + writer.write_u8(0x40)?; + writer.write_u8(destination)?; + writer.write_u16(channel)?; + writer.write_u8(probe)?; + }, + Packet::MonitorReply { value } => { + writer.write_u8(0x41)?; + writer.write_u32(value)?; + }, + Packet::InjectionRequest { destination, channel, overrd, value } => { + writer.write_u8(0x50)?; + writer.write_u8(destination)?; + writer.write_u16(channel)?; + writer.write_u8(overrd)?; + writer.write_u8(value)?; + }, + Packet::InjectionStatusRequest { destination, channel, overrd } => { + writer.write_u8(0x51)?; + writer.write_u8(destination)?; + writer.write_u16(channel)?; + writer.write_u8(overrd)?; + }, + Packet::InjectionStatusReply { value } => { + writer.write_u8(0x52)?; + writer.write_u8(value)?; + }, + + Packet::I2cStartRequest { destination, busno } => { + writer.write_u8(0x80)?; + writer.write_u8(destination)?; + writer.write_u8(busno)?; + }, + Packet::I2cRestartRequest { destination, busno } => { + writer.write_u8(0x81)?; + writer.write_u8(destination)?; + writer.write_u8(busno)?; + }, + Packet::I2cStopRequest { destination, busno } => { + writer.write_u8(0x82)?; + writer.write_u8(destination)?; + writer.write_u8(busno)?; + }, + Packet::I2cWriteRequest { destination, busno, data } => { + writer.write_u8(0x83)?; + writer.write_u8(destination)?; + writer.write_u8(busno)?; + writer.write_u8(data)?; + }, + Packet::I2cWriteReply { succeeded, ack } => { + writer.write_u8(0x84)?; + writer.write_bool(succeeded)?; + writer.write_bool(ack)?; + }, + Packet::I2cReadRequest { destination, busno, ack } => { + writer.write_u8(0x85)?; + writer.write_u8(destination)?; + writer.write_u8(busno)?; + writer.write_bool(ack)?; + }, + Packet::I2cReadReply { succeeded, data } => { + writer.write_u8(0x86)?; + writer.write_bool(succeeded)?; + writer.write_u8(data)?; + }, + Packet::I2cBasicReply { succeeded } => { + writer.write_u8(0x87)?; + writer.write_bool(succeeded)?; + }, + + Packet::SpiSetConfigRequest { destination, busno, flags, length, div, cs } => { + writer.write_u8(0x90)?; + writer.write_u8(destination)?; + writer.write_u8(busno)?; + writer.write_u8(flags)?; + writer.write_u8(length)?; + writer.write_u8(div)?; + writer.write_u8(cs)?; + }, + Packet::SpiWriteRequest { destination, busno, data } => { + writer.write_u8(0x92)?; + writer.write_u8(destination)?; + writer.write_u8(busno)?; + writer.write_u32(data)?; + }, + Packet::SpiReadRequest { destination, busno } => { + writer.write_u8(0x93)?; + writer.write_u8(destination)?; + writer.write_u8(busno)?; + }, + Packet::SpiReadReply { succeeded, data } => { + writer.write_u8(0x94)?; + writer.write_bool(succeeded)?; + writer.write_u32(data)?; + }, + Packet::SpiBasicReply { succeeded } => { + writer.write_u8(0x95)?; + writer.write_bool(succeeded)?; + }, + + } + Ok(()) + } + +} diff --git a/src/libboard_artiq/src/lib.rs b/src/libboard_artiq/src/lib.rs new file mode 100644 index 0000000..b85481c --- /dev/null +++ b/src/libboard_artiq/src/lib.rs @@ -0,0 +1,69 @@ +#![no_std] +#![feature(never_type)] + +extern crate log; +extern crate crc; +extern crate embedded_hal; +extern crate core_io; +extern crate io; +extern crate libboard_zynq; +extern crate libregister; +extern crate libconfig; +extern crate libcortex_a9; +extern crate libasync; +extern crate log_buffer; + +#[path = "../../../build/pl.rs"] +pub mod pl; +pub mod drtioaux_proto; +pub mod drtio_routing; +pub mod logger; +#[cfg(has_si5324)] +pub mod si5324; +#[cfg(has_drtio)] +pub mod drtioaux; +#[cfg(has_drtio)] +pub mod drtioaux_async; +#[cfg(has_drtio)] +#[path = "../../../build/mem.rs"] +pub mod mem; + +use core::{cmp, str}; +use libboard_zynq::slcr; +use libregister::RegisterW; + +pub fn identifier_read(buf: &mut [u8]) -> &str { + unsafe { + pl::csr::identifier::address_write(0); + let len = pl::csr::identifier::data_read(); + let len = cmp::min(len, buf.len() as u8); + for i in 0..len { + pl::csr::identifier::address_write(1 + i); + buf[i as usize] = pl::csr::identifier::data_read(); + } + str::from_utf8_unchecked(&buf[..len as usize]) + } +} + +pub fn init_gateware() { + // Set up PS->PL clocks + slcr::RegisterBlock::unlocked(|slcr| { + // As we are touching the mux, the clock may glitch, so reset the PL. + slcr.fpga_rst_ctrl.write( + slcr::FpgaRstCtrl::zeroed() + .fpga0_out_rst(true) + .fpga1_out_rst(true) + .fpga2_out_rst(true) + .fpga3_out_rst(true) + ); + slcr.fpga0_clk_ctrl.write( + slcr::Fpga0ClkCtrl::zeroed() + .src_sel(slcr::PllSource::IoPll) + .divisor0(8) + .divisor1(1) + ); + slcr.fpga_rst_ctrl.write( + slcr::FpgaRstCtrl::zeroed() + ); + }); +} \ No newline at end of file diff --git a/src/runtime/src/logger.rs b/src/libboard_artiq/src/logger.rs similarity index 100% rename from src/runtime/src/logger.rs rename to src/libboard_artiq/src/logger.rs diff --git a/src/runtime/src/si5324.rs b/src/libboard_artiq/src/si5324.rs similarity index 72% rename from src/runtime/src/si5324.rs rename to src/libboard_artiq/src/si5324.rs index f981c00..c083fae 100644 --- a/src/runtime/src/si5324.rs +++ b/src/libboard_artiq/src/si5324.rs @@ -3,14 +3,14 @@ use log::info; use libboard_zynq::{i2c::I2c, timer::GlobalTimer, time::Milliseconds}; use embedded_hal::blocking::delay::DelayUs; #[cfg(not(si5324_soft_reset))] -use pl::csr; +use crate::pl::csr; type Result = result::Result; const ADDRESS: u8 = 0x68; #[cfg(not(si5324_soft_reset))] -fn hard_reset(timer: GlobalTimer) { +fn hard_reset(timer: &mut GlobalTimer) { unsafe { csr::si5324_rst_n::out_write(0); } timer.delay_us(1_000); unsafe { csr::si5324_rst_n::out_write(1); } @@ -104,6 +104,7 @@ fn write(i2c: &mut I2c, reg: u8, val: u8) -> Result<()> { Ok(()) } +#[allow(dead_code)] fn write_no_ack_value(i2c: &mut I2c, reg: u8, val: u8) -> Result<()> { i2c.start().unwrap(); if !i2c.write(ADDRESS << 1).unwrap() { @@ -146,8 +147,9 @@ fn ident(i2c: &mut I2c) -> Result { } #[cfg(si5324_soft_reset)] -fn soft_reset(i2c: &mut I2c, timer: GlobalTimer) -> Result<()> { - write_no_ack_value(i2c, 136, read(i2c, 136)? | 0x80)?; +fn soft_reset(i2c: &mut I2c, timer: &mut GlobalTimer) -> Result<()> { + let val = read(i2c, 136)?; + write_no_ack_value(i2c, 136, val | 0x80)?; timer.delay_us(10_000); Ok(()) } @@ -167,7 +169,7 @@ fn locked(i2c: &mut I2c) -> Result { Ok((read(i2c, 130)? & 0x01) == 0) // LOL_INT=0 } -fn monitor_lock(i2c: &mut I2c, timer: GlobalTimer) -> Result<()> { +fn monitor_lock(i2c: &mut I2c, timer: &mut GlobalTimer) -> Result<()> { info!("waiting for Si5324 lock..."); let timeout = timer.get_time() + Milliseconds(20_000); while !locked(i2c)? { @@ -180,7 +182,7 @@ fn monitor_lock(i2c: &mut I2c, timer: GlobalTimer) -> Result<()> { Ok(()) } -fn init(i2c: &mut I2c, timer: GlobalTimer) -> Result<()> { +fn init(i2c: &mut I2c, timer: &mut GlobalTimer) -> Result<()> { #[cfg(not(si5324_soft_reset))] hard_reset(timer); @@ -189,6 +191,10 @@ fn init(i2c: &mut I2c, timer: GlobalTimer) -> Result<()> { i2c.pca9548_select(0x70, 0)?; i2c.pca9548_select(0x71, 1 << 3)?; } + #[cfg(feature = "target_zc706")] + { + i2c.pca9548_select(0x74, 1 << 4)?; + } if ident(i2c)? != 0x0182 { return Err("Si5324 does not have expected product number"); @@ -199,7 +205,7 @@ fn init(i2c: &mut I2c, timer: GlobalTimer) -> Result<()> { Ok(()) } -pub fn bypass(i2c: &mut I2c, input: Input, timer: GlobalTimer) -> Result<()> { +pub fn bypass(i2c: &mut I2c, input: Input, timer: &mut GlobalTimer) -> Result<()> { let cksel_reg = match input { Input::Ckin1 => 0b00, Input::Ckin2 => 0b01, @@ -213,7 +219,7 @@ pub fn bypass(i2c: &mut I2c, input: Input, timer: GlobalTimer) -> Result<()> { Ok(()) } -pub fn setup(i2c: &mut I2c, settings: &FrequencySettings, input: Input, timer: GlobalTimer) -> Result<()> { +pub fn setup(i2c: &mut I2c, settings: &FrequencySettings, input: Input, timer: &mut GlobalTimer) -> Result<()> { let s = map_frequency_settings(settings)?; let cksel_reg = match input { Input::Ckin1 => 0b00, @@ -259,7 +265,7 @@ pub fn setup(i2c: &mut I2c, settings: &FrequencySettings, input: Input, timer: G Ok(()) } -pub fn select_input(i2c: &mut I2c, input: Input, timer: GlobalTimer) -> Result<()> { +pub fn select_input(i2c: &mut I2c, input: Input, timer: &mut GlobalTimer) -> Result<()> { let cksel_reg = match input { Input::Ckin1 => 0b00, Input::Ckin2 => 0b01, @@ -270,4 +276,78 @@ pub fn select_input(i2c: &mut I2c, input: Input, timer: GlobalTimer) -> Result<( } monitor_lock(i2c, timer)?; Ok(()) +} + +#[cfg(has_siphaser)] +pub mod siphaser { + use super::*; + use crate::pl::csr; + + pub fn select_recovered_clock(i2c: &mut I2c, rc: bool, timer: &mut GlobalTimer) -> Result<()> { + let val = read(i2c, 3)?; + write(i2c, 3, (val & 0xdf) | (1 << 5))?; // DHOLD=1 + unsafe { + csr::siphaser::switch_clocks_write(if rc { 1 } else { 0 }); + } + let val = read(i2c, 3)?; + write(i2c, 3, (val & 0xdf) | (0 << 5))?; // DHOLD=0 + monitor_lock(i2c, timer)?; + Ok(()) + } + + fn phase_shift(direction: u8, timer: &mut GlobalTimer) { + unsafe { + csr::siphaser::phase_shift_write(direction); + while csr::siphaser::phase_shift_done_read() == 0 {} + } + // wait for the Si5324 loop to stabilize + timer.delay_us(500); + } + + fn has_error(timer: &mut GlobalTimer) -> bool { + unsafe { + csr::siphaser::error_write(1); + } + timer.delay_us(5_000); + unsafe { + csr::siphaser::error_read() != 0 + } + } + + fn find_edge(target: bool, timer: &mut GlobalTimer) -> Result { + let mut nshifts = 0; + + let mut previous = has_error(timer); + loop { + phase_shift(1, timer); + nshifts += 1; + let current = has_error(timer); + if previous != target && current == target { + return Ok(nshifts); + } + if nshifts > 5000 { + return Err("failed to find timing error edge"); + } + previous = current; + } + } + + pub fn calibrate_skew(timer: &mut GlobalTimer) -> Result<()> { + let jitter_margin = 32; + let lead = find_edge(false, timer)?; + for _ in 0..jitter_margin { + phase_shift(1, timer); + } + let width = find_edge(true, timer)? + jitter_margin; + // width is 360 degrees (one full rotation of the phase between s/h limits) minus jitter + info!("calibration successful, lead: {}, width: {} ({}deg)", lead, width, width*360/(56*8)); + + // Apply reverse phase shift for half the width to get into the + // middle of the working region. + for _ in 0..width/2 { + phase_shift(0, timer); + } + + Ok(()) + } } \ No newline at end of file diff --git a/src/libbuild_zynq/Cargo.toml b/src/libbuild_zynq/Cargo.toml new file mode 100644 index 0000000..b5cc9e6 --- /dev/null +++ b/src/libbuild_zynq/Cargo.toml @@ -0,0 +1,8 @@ +[package] +authors = ["M-Labs"] +name = "build_zynq" +version = "0.0.0" + +[lib] +name = "build_zynq" +path = "lib.rs" diff --git a/src/libbuild_zynq/lib.rs b/src/libbuild_zynq/lib.rs new file mode 100644 index 0000000..7bb0aef --- /dev/null +++ b/src/libbuild_zynq/lib.rs @@ -0,0 +1,30 @@ +use std::env; +use std::fs::File; +use std::io::{BufRead, BufReader, Write}; +use std::path::PathBuf; + + +pub fn add_linker_script() { + // Put the linker script somewhere the linker can find it + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("link.x")) + .unwrap() + .write_all(include_bytes!("link.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // Only re-run the build script when link.x is changed, + // instead of when any part of the source code changes. + println!("cargo:rerun-if-changed=link.x"); +} + +pub fn cfg() { + // Handle rustc-cfg file + let cfg_path = "../../build/rustc-cfg"; + println!("cargo:rerun-if-changed={}", cfg_path); + + let f = BufReader::new(File::open(cfg_path).unwrap()); + for line in f.lines() { + println!("cargo:rustc-cfg={}", line.unwrap()); + } +} diff --git a/src/runtime/link.x b/src/libbuild_zynq/link.x similarity index 100% rename from src/runtime/link.x rename to src/libbuild_zynq/link.x diff --git a/src/libio/Cargo.toml b/src/libio/Cargo.toml new file mode 100644 index 0000000..c02f08a --- /dev/null +++ b/src/libio/Cargo.toml @@ -0,0 +1,17 @@ +[package] +authors = ["M-Labs"] +name = "io" +version = "0.0.0" + +[lib] +name = "io" +path = "lib.rs" + +[dependencies] +core_io = { version = "0.1", features = ["collections"] } +byteorder = { version = "1.0", default-features = false, optional = true } + +libsupport_zynq = { default-features = false, features = ["alloc_core"], git = "https://git.m-labs.hk/M-Labs/zynq-rs.git" } + +[features] +alloc = [] diff --git a/src/libio/cursor.rs b/src/libio/cursor.rs new file mode 100644 index 0000000..c31c8da --- /dev/null +++ b/src/libio/cursor.rs @@ -0,0 +1,81 @@ +use core_io::{Read, Write, Error as IoError}; + +#[derive(Debug, Clone)] +pub struct Cursor { + inner: T, + pos: usize +} + +impl Cursor { + #[inline] + pub fn new(inner: T) -> Cursor { + Cursor { inner, pos: 0 } + } + + #[inline] + pub fn into_inner(self) -> T { + self.inner + } + + #[inline] + pub fn get_ref(&self) -> &T { + &self.inner + } + + #[inline] + pub fn get_mut(&mut self) -> &mut T { + &mut self.inner + } + + #[inline] + pub fn position(&self) -> usize { + self.pos + } + + #[inline] + pub fn set_position(&mut self, pos: usize) { + self.pos = pos + } +} + +impl> Read for Cursor { + + fn read(&mut self, buf: &mut [u8]) -> Result { + let data = &self.inner.as_ref()[self.pos..]; + let len = buf.len().min(data.len()); + buf[..len].copy_from_slice(&data[..len]); + self.pos += len; + Ok(len) + } +} + +impl Write for Cursor<&mut [u8]> { + + fn write(&mut self, buf: &[u8]) -> Result { + let data = &mut self.inner[self.pos..]; + let len = buf.len().min(data.len()); + data[..len].copy_from_slice(&buf[..len]); + self.pos += len; + Ok(len) + } + + #[inline] + fn flush(&mut self) -> Result<(), IoError> { + Ok(()) + } +} + +#[cfg(feature = "alloc")] +impl Write for Cursor<::alloc::Vec> { + + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + self.inner.extend_from_slice(buf); + Ok(buf.len()) + } + + #[inline] + fn flush(&mut self) -> Result<(), IoError> { + Ok(()) + } +} diff --git a/src/libio/lib.rs b/src/libio/lib.rs new file mode 100644 index 0000000..e754be4 --- /dev/null +++ b/src/libio/lib.rs @@ -0,0 +1,22 @@ +#![no_std] +#![feature(never_type)] +#![cfg_attr(feature = "alloc", feature(alloc))] + +extern crate alloc; +extern crate core_io; + +#[cfg(feature = "alloc")] +#[macro_use] +use alloc; +#[cfg(feature = "byteorder")] +extern crate byteorder; + +pub mod cursor; +#[cfg(feature = "byteorder")] +pub mod proto; + +pub use cursor::Cursor; +#[cfg(feature = "byteorder")] +pub use proto::{ProtoRead, ProtoWrite}; +#[cfg(all(feature = "byteorder", feature = "alloc"))] +pub use proto::ReadStringError; \ No newline at end of file diff --git a/src/runtime/src/proto_core_io.rs b/src/libio/proto.rs similarity index 100% rename from src/runtime/src/proto_core_io.rs rename to src/libio/proto.rs diff --git a/src/runtime/Cargo.toml b/src/runtime/Cargo.toml index c813611..804e8ec 100644 --- a/src/runtime/Cargo.toml +++ b/src/runtime/Cargo.toml @@ -6,10 +6,13 @@ authors = ["M-Labs"] edition = "2018" [features] -target_zc706 = ["libboard_zynq/target_zc706", "libsupport_zynq/target_zc706", "libconfig/target_zc706"] -target_kasli_soc = ["libboard_zynq/target_kasli_soc", "libsupport_zynq/target_kasli_soc", "libconfig/target_kasli_soc"] +target_zc706 = ["libboard_zynq/target_zc706", "libsupport_zynq/target_zc706", "libconfig/target_zc706", "libboard_artiq/target_zc706"] +target_kasli_soc = ["libboard_zynq/target_kasli_soc", "libsupport_zynq/target_kasli_soc", "libconfig/target_kasli_soc", "libboard_artiq/target_kasli_soc"] default = ["target_zc706"] +[build-dependencies] +build_zynq = { path = "../libbuild_zynq" } + [dependencies] num-traits = { version = "0.2", default-features = false } num-derive = "0.3" @@ -37,3 +40,5 @@ dyld = { path = "../libdyld" } dwarf = { path = "../libdwarf" } unwind = { path = "../libunwind" } libc = { path = "../libc" } +io = { path = "../libio" } +libboard_artiq = { path = "../libboard_artiq" } \ No newline at end of file diff --git a/src/runtime/build.rs b/src/runtime/build.rs index e0fdb0c..aee092b 100644 --- a/src/runtime/build.rs +++ b/src/runtime/build.rs @@ -1,28 +1,6 @@ -use std::env; -use std::fs::File; -use std::io::Write; -use std::io::{BufRead, BufReader}; -use std::path::PathBuf; +extern crate build_zynq; fn main() { - // Put the linker script somewhere the linker can find it - let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); - File::create(out.join("link.x")) - .unwrap() - .write_all(include_bytes!("link.x")) - .unwrap(); - println!("cargo:rustc-link-search={}", out.display()); - - // Only re-run the build script when link.x is changed, - // instead of when any part of the source code changes. - println!("cargo:rerun-if-changed=link.x"); - - // Handle rustc-cfg file - let cfg_path = "../../build/rustc-cfg"; - println!("cargo:rerun-if-changed={}", cfg_path); - - let f = BufReader::new(File::open(cfg_path).unwrap()); - for line in f.lines() { - println!("cargo:rustc-cfg={}", line.unwrap()); - } + build_zynq::add_linker_script(); + build_zynq::cfg(); } diff --git a/src/runtime/src/comms.rs b/src/runtime/src/comms.rs index 06ff331..f90b6de 100644 --- a/src/runtime/src/comms.rs +++ b/src/runtime/src/comms.rs @@ -21,6 +21,7 @@ use libcortex_a9::{semaphore::Semaphore, mutex::Mutex, sync_channel::{Sender, Re use futures::{select_biased, future::FutureExt}; use libasync::{smoltcp::{Sockets, TcpStream}, task}; use libconfig::{Config, net_settings}; +use libboard_artiq::drtio_routing; use crate::proto_async::*; use crate::kernel; @@ -28,7 +29,9 @@ use crate::rpc; use crate::moninj; use crate::mgmt; use crate::analyzer; - +use crate::rtio_mgt; +#[cfg(has_drtio)] +use crate::pl; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Error { @@ -387,8 +390,21 @@ pub fn main(timer: GlobalTimer, cfg: Config) { Sockets::init(32); + // before, mutex was on io, but now that io isn't used...? + let aux_mutex: Rc> = Rc::new(Mutex::new(false)); + #[cfg(has_drtio)] + let drtio_routing_table = Rc::new(RefCell::new( + drtio_routing::config_routing_table(pl::csr::DRTIO.len(), &cfg))); + #[cfg(not(has_drtio))] + let drtio_routing_table = Rc::new(RefCell::new(drtio_routing::RoutingTable::default_empty())); + let up_destinations = Rc::new(RefCell::new([false; drtio_routing::DEST_COUNT])); + #[cfg(has_drtio_routing)] + drtio_routing::interconnect_disable_all(); + + rtio_mgt::startup(&aux_mutex, &drtio_routing_table, &up_destinations, timer); + analyzer::start(); - moninj::start(timer); + moninj::start(timer, aux_mutex, drtio_routing_table); let control: Rc> = Rc::new(RefCell::new(kernel::Control::start())); let idle_kernel = Rc::new(cfg.read("idle").ok()); diff --git a/src/runtime/src/main.rs b/src/runtime/src/main.rs index f59d2b9..973afbe 100644 --- a/src/runtime/src/main.rs +++ b/src/runtime/src/main.rs @@ -11,82 +11,43 @@ extern crate alloc; -use core::{cmp, str}; use log::{info, warn, error}; -use libboard_zynq::{timer::GlobalTimer, mpcore, gic, slcr}; +use libboard_zynq::{timer::GlobalTimer, mpcore, gic}; use libasync::{task, block_async}; use libsupport_zynq::ram; use nb; use void::Void; use embedded_hal::blocking::delay::DelayMs; use libconfig::Config; -use libregister::RegisterW; use libcortex_a9::l2c::enable_l2_cache; +use libboard_artiq::{logger, identifier_read, init_gateware, pl}; +#[cfg(has_si5324)] +use libboard_artiq::si5324; -mod proto_core_io; mod proto_async; mod comms; mod rpc; -#[path = "../../../build/pl.rs"] -mod pl; #[cfg(ki_impl = "csr")] #[path = "rtio_csr.rs"] mod rtio; #[cfg(ki_impl = "acp")] #[path = "rtio_acp.rs"] mod rtio; +mod rtio_mgt; mod kernel; mod moninj; mod eh_artiq; mod panic; -mod logger; mod mgmt; mod analyzer; mod irq; mod i2c; -#[cfg(has_si5324)] -mod si5324; -fn init_gateware() { - // Set up PS->PL clocks - slcr::RegisterBlock::unlocked(|slcr| { - // As we are touching the mux, the clock may glitch, so reset the PL. - slcr.fpga_rst_ctrl.write( - slcr::FpgaRstCtrl::zeroed() - .fpga0_out_rst(true) - .fpga1_out_rst(true) - .fpga2_out_rst(true) - .fpga3_out_rst(true) - ); - slcr.fpga0_clk_ctrl.write( - slcr::Fpga0ClkCtrl::zeroed() - .src_sel(slcr::PllSource::IoPll) - .divisor0(8) - .divisor1(1) - ); - slcr.fpga_rst_ctrl.write( - slcr::FpgaRstCtrl::zeroed() - ); - }); -} - -fn identifier_read(buf: &mut [u8]) -> &str { - unsafe { - pl::csr::identifier::address_write(0); - let len = pl::csr::identifier::data_read(); - let len = cmp::min(len, buf.len() as u8); - for i in 0..len { - pl::csr::identifier::address_write(1 + i); - buf[i as usize] = pl::csr::identifier::data_read(); - } - str::from_utf8_unchecked(&buf[..len as usize]) - } -} - -fn init_rtio(timer: &mut GlobalTimer, cfg: &Config) { +fn init_rtio(timer: &mut GlobalTimer, _cfg: &Config) { + #[cfg(has_rtio_crg_clock_sel)] let clock_sel = - if let Ok(rtioclk) = cfg.read_str("rtioclk") { + if let Ok(rtioclk) = _cfg.read_str("rtioclk") { match rtioclk.as_ref() { "internal" => { info!("using internal RTIO clock"); @@ -130,6 +91,19 @@ fn init_rtio(timer: &mut GlobalTimer, cfg: &Config) { } } +#[cfg(has_drtio)] +fn init_drtio(timer: &mut GlobalTimer) +{ + unsafe { + pl::csr::drtio_transceiver::stable_clkin_write(1); + } + timer.delay_ms(2); // wait for CPLL/QPLL lock + unsafe { + pl::csr::drtio_transceiver::txenable_write(0xffffffffu32 as _); + } +} + + fn wait_for_async_rtio_error() -> nb::Result<(), Void> { unsafe { if pl::csr::rtio_core::async_error_read() != 0 { @@ -201,7 +175,7 @@ pub fn main_core0() { i2c::init(); #[cfg(has_si5324)] si5324::setup(unsafe { (&mut i2c::I2C_BUS).as_mut().unwrap() }, - &SI5324_SETTINGS, si5324::Input::Ckin2, timer).expect("cannot initialize Si5324"); + &SI5324_SETTINGS, si5324::Input::Ckin2, &mut timer).expect("cannot initialize Si5324"); let cfg = match Config::new() { Ok(cfg) => cfg, @@ -211,6 +185,9 @@ pub fn main_core0() { } }; + #[cfg(has_drtio)] + init_drtio(&mut timer); + init_rtio(&mut timer, &cfg); task::spawn(report_async_rtio_errors()); diff --git a/src/runtime/src/mgmt.rs b/src/runtime/src/mgmt.rs index 06ed8d5..51adeeb 100644 --- a/src/runtime/src/mgmt.rs +++ b/src/runtime/src/mgmt.rs @@ -6,7 +6,7 @@ use core::cell::RefCell; use alloc::{rc::Rc, vec::Vec, string::String}; use log::{self, info, debug, warn, error, LevelFilter}; -use crate::logger::{BufferLogger, LogBufferRef}; +use libboard_artiq::logger::{BufferLogger, LogBufferRef}; use crate::proto_async::*; use num_derive::FromPrimitive; use num_traits::FromPrimitive; diff --git a/src/runtime/src/moninj.rs b/src/runtime/src/moninj.rs index d1124b4..77b7942 100644 --- a/src/runtime/src/moninj.rs +++ b/src/runtime/src/moninj.rs @@ -1,17 +1,19 @@ -use core::fmt; -use alloc::collections::BTreeMap; +use core::{fmt, cell::RefCell}; +use alloc::{collections::BTreeMap, rc::Rc}; use log::{debug, info, warn}; use void::Void; +use libboard_artiq::drtio_routing; + use libboard_zynq::{smoltcp, timer::GlobalTimer, time::Milliseconds}; use libasync::{task, smoltcp::TcpStream, block_async, nb}; +use libcortex_a9::mutex::Mutex; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; use futures::{pin_mut, select_biased, FutureExt}; use crate::proto_async::*; -use crate::pl::csr; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -19,7 +21,6 @@ pub enum Error { NetworkError(smoltcp::Error), UnexpectedPattern, UnrecognizedPacket, - } pub type Result = core::result::Result; @@ -54,32 +55,109 @@ enum DeviceMessage { InjectionStatus = 1 } -fn read_probe(channel: i32, probe: i8) -> i32 { - unsafe { - csr::rtio_moninj::mon_chan_sel_write(channel as _); - csr::rtio_moninj::mon_probe_sel_write(probe as _); - csr::rtio_moninj::mon_value_update_write(1); - csr::rtio_moninj::mon_value_read() as i32 +#[cfg(has_drtio)] +mod remote_moninj { + use super::*; + use libboard_artiq::drtioaux; + use crate::rtio_mgt::drtio; + use log::error; + + pub fn read_probe(aux_mutex: &Rc>, timer: GlobalTimer, linkno: u8, destination: u8, channel: i32, probe: i8) -> i32 { + let reply = task::block_on(drtio::aux_transact(aux_mutex, linkno, &drtioaux::Packet::MonitorRequest { + destination: destination, + channel: channel as _, + probe: probe as _}, + timer)); + match reply { + Ok(drtioaux::Packet::MonitorReply { value }) => return value as i32, + Ok(packet) => error!("received unexpected aux packet: {:?}", packet), + Err(e) => error!("aux packet error ({})", e) + } + 0 + } + + pub fn inject(aux_mutex: &Rc>, _timer: GlobalTimer, linkno: u8, destination: u8, channel: i32, overrd: i8, value: i8) { + let _lock = aux_mutex.lock(); + drtioaux::send(linkno, &drtioaux::Packet::InjectionRequest { + destination: destination, + channel: channel as _, + overrd: overrd as _, + value: value as _ + }).unwrap(); + } + + pub fn read_injection_status(aux_mutex: &Rc>, timer: GlobalTimer, linkno: u8, destination: u8, channel: i32, overrd: i8) -> i8 { + let reply = task::block_on(drtio::aux_transact(aux_mutex, + linkno, + &drtioaux::Packet::InjectionStatusRequest { + destination: destination, + channel: channel as _, + overrd: overrd as _}, + timer)); + match reply { + Ok(drtioaux::Packet::InjectionStatusReply { value }) => return value as i8, + Ok(packet) => error!("received unexpected aux packet: {:?}", packet), + Err(e) => error!("aux packet error ({})", e) + } + 0 } } -fn inject(channel: i32, overrd: i8, value: i8) { - unsafe { - csr::rtio_moninj::inj_chan_sel_write(channel as _); - csr::rtio_moninj::inj_override_sel_write(overrd as _); - csr::rtio_moninj::inj_value_write(value as _); +mod local_moninj { + use libboard_artiq::pl::csr; + + pub fn read_probe(channel: i32, probe: i8) -> i32 { + unsafe { + csr::rtio_moninj::mon_chan_sel_write(channel as _); + csr::rtio_moninj::mon_probe_sel_write(probe as _); + csr::rtio_moninj::mon_value_update_write(1); + csr::rtio_moninj::mon_value_read() as i32 + } + } + + pub fn inject(channel: i32, overrd: i8, value: i8) { + unsafe { + csr::rtio_moninj::inj_chan_sel_write(channel as _); + csr::rtio_moninj::inj_override_sel_write(overrd as _); + csr::rtio_moninj::inj_value_write(value as _); + } + } + + pub fn read_injection_status(channel: i32, overrd: i8) -> i8 { + unsafe { + csr::rtio_moninj::inj_chan_sel_write(channel as _); + csr::rtio_moninj::inj_override_sel_write(overrd as _); + csr::rtio_moninj::inj_value_read() as i8 + } } } -fn read_injection_status(channel: i32, overrd: i8) -> i8 { - unsafe { - csr::rtio_moninj::inj_chan_sel_write(channel as _); - csr::rtio_moninj::inj_override_sel_write(overrd as _); - csr::rtio_moninj::inj_value_read() as i8 - } +#[cfg(has_drtio)] +macro_rules! dispatch { + ($timer:ident, $aux_mutex:ident, $routing_table:ident, $channel:expr, $func:ident $(, $param:expr)*) => {{ + let destination = ($channel >> 16) as u8; + let channel = $channel; + let routing_table = $routing_table.borrow_mut(); + let hop = routing_table.0[destination as usize][0]; + if hop == 0 { + local_moninj::$func(channel.into(), $($param, )*) + } else { + let linkno = hop - 1 as u8; + remote_moninj::$func($aux_mutex, $timer, linkno, destination, channel, $($param, )*) + } + }} } -async fn handle_connection(stream: &TcpStream, timer: GlobalTimer) -> Result<()> { +#[cfg(not(has_drtio))] +macro_rules! dispatch { + ($timer:ident, $aux_mutex:ident, $routing_table:ident, $channel:expr, $func:ident $(, $param:expr)*) => {{ + let channel = $channel as u16; + local_moninj::$func(channel.into(), $($param, )*) + }} +} + +async fn handle_connection(stream: &TcpStream, timer: GlobalTimer, + _aux_mutex: &Rc>, _routing_table: &Rc>) -> Result<()> { if !expect(&stream, b"ARTIQ moninj\n").await? { return Err(Error::UnexpectedPattern); } @@ -135,13 +213,13 @@ async fn handle_connection(stream: &TcpStream, timer: GlobalTimer) -> Result<()> let channel = read_i32(&stream).await?; let overrd = read_i8(&stream).await?; let value = read_i8(&stream).await?; - inject(channel, overrd, value); + dispatch!(timer, _aux_mutex, _routing_table, channel, inject, overrd, value); debug!("INJECT channel {}, overrd {}, value {}", channel, overrd, value); }, HostMessage::GetInjectionStatus => { let channel = read_i32(&stream).await?; let overrd = read_i8(&stream).await?; - let value = read_injection_status(channel, overrd); + let value = dispatch!(timer, _aux_mutex, _routing_table, channel, read_injection_status, overrd); write_i8(&stream, DeviceMessage::InjectionStatus.to_i8().unwrap()).await?; write_i32(&stream, channel).await?; write_i8(&stream, overrd).await?; @@ -151,7 +229,7 @@ async fn handle_connection(stream: &TcpStream, timer: GlobalTimer) -> Result<()> }, _ = timeout_f => { for (&(channel, probe), previous) in probe_watch_list.iter_mut() { - let current = read_probe(channel, probe); + let current = dispatch!(timer, _aux_mutex, _routing_table, channel, read_probe, probe); if previous.is_none() || previous.unwrap() != current { write_i8(&stream, DeviceMessage::MonitorStatus.to_i8().unwrap()).await?; write_i32(&stream, channel).await?; @@ -161,7 +239,7 @@ async fn handle_connection(stream: &TcpStream, timer: GlobalTimer) -> Result<()> } } for (&(channel, overrd), previous) in inject_watch_list.iter_mut() { - let current = read_injection_status(channel, overrd); + let current = dispatch!(timer, _aux_mutex, _routing_table, channel, read_injection_status, overrd); if previous.is_none() || previous.unwrap() != current { write_i8(&stream, DeviceMessage::InjectionStatus.to_i8().unwrap()).await?; write_i32(&stream, channel).await?; @@ -176,13 +254,15 @@ async fn handle_connection(stream: &TcpStream, timer: GlobalTimer) -> Result<()> } } -pub fn start(timer: GlobalTimer) { +pub fn start(timer: GlobalTimer, aux_mutex: Rc>, routing_table: Rc>) { task::spawn(async move { loop { + let aux_mutex = aux_mutex.clone(); + let routing_table = routing_table.clone(); let stream = TcpStream::accept(1383, 2048, 2048).await.unwrap(); task::spawn(async move { info!("received connection"); - let result = handle_connection(&stream, timer).await; + let result = handle_connection(&stream, timer, &aux_mutex, &routing_table).await; match result { Err(Error::NetworkError(smoltcp::Error::Finished)) => info!("peer closed connection"), Err(error) => warn!("connection terminated: {}", error), diff --git a/src/runtime/src/rpc.rs b/src/runtime/src/rpc.rs index bb17f50..ef4e659 100644 --- a/src/runtime/src/rpc.rs +++ b/src/runtime/src/rpc.rs @@ -10,7 +10,7 @@ use libasync::smoltcp::TcpStream; use alloc::boxed::Box; use async_recursion::async_recursion; -use crate::proto_core_io::ProtoWrite; +use io::proto::ProtoWrite; use crate::proto_async; use self::tag::{Tag, TagIterator, split_tag}; diff --git a/src/runtime/src/rtio_mgt.rs b/src/runtime/src/rtio_mgt.rs new file mode 100644 index 0000000..d72a902 --- /dev/null +++ b/src/runtime/src/rtio_mgt.rs @@ -0,0 +1,352 @@ +use core::cell::RefCell; +use alloc::rc::Rc; +use libboard_zynq::timer::GlobalTimer; +use libboard_artiq::{pl::csr, drtio_routing}; +use libcortex_a9::mutex::Mutex; + + +#[cfg(has_drtio)] +pub mod drtio { + use super::*; + use libboard_artiq::drtioaux_async; + use libboard_artiq::drtioaux_async::Packet; + use libboard_artiq::drtioaux::Error; + use log::{warn, error, info}; + use embedded_hal::blocking::delay::DelayMs; + use libasync::{task, delay}; + use libboard_zynq::time::Milliseconds; + + pub fn startup(aux_mutex: &Rc>, + routing_table: &Rc>, + up_destinations: &Rc>, + timer: GlobalTimer) { + let aux_mutex = aux_mutex.clone(); + let routing_table = routing_table.clone(); + let up_destinations = up_destinations.clone(); + task::spawn(async move { + let routing_table = routing_table.borrow(); + link_task(&aux_mutex, &routing_table, &up_destinations, timer).await; + }); + } + + async fn link_rx_up(linkno: u8) -> bool { + let linkno = linkno as usize; + unsafe { + (csr::DRTIO[linkno].rx_up_read)() == 1 + } + } + + async fn recv_aux_timeout(linkno: u8, timeout: u64, timer: GlobalTimer) -> Result { + if !link_rx_up(linkno).await { + return Err("link went down"); + } + match drtioaux_async::recv_timeout(linkno, Some(timeout), timer).await { + Ok(packet) => return Ok(packet), + Err(Error::TimedOut) => return Err("timed out"), + Err(_) => return Err("aux packet error"), + } + } + + pub async fn aux_transact(aux_mutex: &Mutex, linkno: u8, request: &Packet, + timer: GlobalTimer) -> Result { + let _lock = aux_mutex.lock(); + drtioaux_async::send(linkno, request).await.unwrap(); + recv_aux_timeout(linkno, 200, timer).await + } + + async fn drain_buffer(linkno: u8, draining_time: Milliseconds, timer: GlobalTimer) { + let max_time = timer.get_time() + draining_time; + loop { + if timer.get_time() > max_time { + return; + } //could this be cut short? + let _ = drtioaux_async::recv(linkno).await; + } + } + + async fn ping_remote(aux_mutex: &Rc>, linkno: u8, timer: GlobalTimer) -> u32 { + let mut count = 0; + loop { + if !link_rx_up(linkno).await { + return 0 + } + count += 1; + if count > 100 { + return 0; + } + let reply = aux_transact(aux_mutex, linkno, &Packet::EchoRequest, timer).await; + match reply { + Ok(Packet::EchoReply) => { + // make sure receive buffer is drained + let draining_time = Milliseconds(200); + drain_buffer(linkno, draining_time, timer).await; + return count; + } + _ => {} + } + } + } + + async fn sync_tsc(aux_mutex: &Rc>, linkno: u8, timer: GlobalTimer) -> Result<(), &'static str> { + let _lock = aux_mutex.lock(); + + unsafe { + (csr::DRTIO[linkno as usize].set_time_write)(1); + while (csr::DRTIO[linkno as usize].set_time_read)() == 1 {} + } + // TSCAck is the only aux packet that is sent spontaneously + // by the satellite, in response to a TSC set on the RT link. + let reply = recv_aux_timeout(linkno, 10000, timer).await?; + if reply == Packet::TSCAck { + return Ok(()); + } else { + return Err("unexpected reply"); + } + } + + async fn load_routing_table(aux_mutex: &Rc>, linkno: u8, routing_table: &drtio_routing::RoutingTable, + timer: GlobalTimer) -> Result<(), &'static str> { + for i in 0..drtio_routing::DEST_COUNT { + let reply = aux_transact(aux_mutex, linkno, &Packet::RoutingSetPath { + destination: i as u8, + hops: routing_table.0[i] + }, timer).await?; + if reply != Packet::RoutingAck { + return Err("unexpected reply"); + } + } + Ok(()) + } + + async fn set_rank(aux_mutex: &Rc>, linkno: u8, rank: u8, timer: GlobalTimer) -> Result<(), &'static str> { + let reply = aux_transact(aux_mutex, linkno, &Packet::RoutingSetRank { + rank: rank + }, timer).await?; + if reply != Packet::RoutingAck { + return Err("unexpected reply"); + } + Ok(()) + } + + async fn init_buffer_space(destination: u8, linkno: u8) { + let linkno = linkno as usize; + unsafe { + (csr::DRTIO[linkno].destination_write)(destination); + (csr::DRTIO[linkno].force_destination_write)(1); + (csr::DRTIO[linkno].o_get_buffer_space_write)(1); + while (csr::DRTIO[linkno].o_wait_read)() == 1 {} + info!("[DEST#{}] buffer space is {}", + destination, (csr::DRTIO[linkno].o_dbg_buffer_space_read)()); + (csr::DRTIO[linkno].force_destination_write)(0); + } + } + + async fn process_unsolicited_aux(aux_mutex: &Rc>, linkno: u8) { + let _lock = aux_mutex.lock(); + match drtioaux_async::recv(linkno).await { + Ok(Some(packet)) => warn!("[LINK#{}] unsolicited aux packet: {:?}", linkno, packet), + Ok(None) => (), + Err(_) => warn!("[LINK#{}] aux packet error", linkno) + } + } + + async fn process_local_errors(linkno: u8) { + let errors; + let linkidx = linkno as usize; + unsafe { + errors = (csr::DRTIO[linkidx].protocol_error_read)(); + (csr::DRTIO[linkidx].protocol_error_write)(errors); + } + if errors != 0 { + error!("[LINK#{}] error(s) found (0x{:02x}):", linkno, errors); + if errors & 1 != 0 { + error!("[LINK#{}] received packet of an unknown type", linkno); + } + if errors & 2 != 0 { + error!("[LINK#{}] received truncated packet", linkno); + } + if errors & 4 != 0 { + error!("[LINK#{}] timeout attempting to get remote buffer space", linkno); + } + } + } + + async fn destination_set_up(routing_table: &drtio_routing::RoutingTable, + up_destinations: &Rc>, + destination: u8, up: bool) { + let mut up_destinations = up_destinations.borrow_mut(); + up_destinations[destination as usize] = up; + if up { + drtio_routing::interconnect_enable(routing_table, 0, destination); + info!("[DEST#{}] destination is up", destination); + } else { + drtio_routing::interconnect_disable(destination); + info!("[DEST#{}] destination is down", destination); + } + } + + async fn destination_up(up_destinations: &Rc>, destination: u8) -> bool { + let up_destinations = up_destinations.borrow(); + up_destinations[destination as usize] + } + + async fn destination_survey(aux_mutex: &Rc>, routing_table: &drtio_routing::RoutingTable, + up_links: &[bool], + up_destinations: &Rc>, + timer: GlobalTimer) { + for destination in 0..drtio_routing::DEST_COUNT { + let hop = routing_table.0[destination][0]; + let destination = destination as u8; + + if hop == 0 { + /* local RTIO */ + if !destination_up(up_destinations, destination).await { + destination_set_up(routing_table, up_destinations, destination, true).await; + } + } else if hop as usize <= csr::DRTIO.len() { + let linkno = hop - 1; + if destination_up(up_destinations, destination).await { + if up_links[linkno as usize] { + let reply = aux_transact(aux_mutex, linkno, &Packet::DestinationStatusRequest { + destination: destination + }, timer).await; + match reply { + Ok(Packet::DestinationDownReply) => + destination_set_up(routing_table, up_destinations, destination, false).await, + Ok(Packet::DestinationOkReply) => (), + Ok(Packet::DestinationSequenceErrorReply { channel }) => + error!("[DEST#{}] RTIO sequence error involving channel 0x{:04x}", destination, channel), + Ok(Packet::DestinationCollisionReply { channel }) => + error!("[DEST#{}] RTIO collision involving channel 0x{:04x}", destination, channel), + Ok(Packet::DestinationBusyReply { channel }) => + error!("[DEST#{}] RTIO busy error involving channel 0x{:04x}", destination, channel), + Ok(packet) => error!("[DEST#{}] received unexpected aux packet: {:?}", destination, packet), + Err(e) => error!("[DEST#{}] communication failed ({})", destination, e) + } + } else { + destination_set_up(routing_table, up_destinations, destination, false).await; + } + } else { + if up_links[linkno as usize] { + let reply = aux_transact(aux_mutex, linkno, &Packet::DestinationStatusRequest { + destination: destination + }, timer).await; + match reply { + Ok(Packet::DestinationDownReply) => (), + Ok(Packet::DestinationOkReply) => { + destination_set_up(routing_table, up_destinations, destination, true).await; + init_buffer_space(destination as u8, linkno).await; + }, + Ok(packet) => error!("[DEST#{}] received unexpected aux packet: {:?}", destination, packet), + Err(e) => error!("[DEST#{}] communication failed ({})", destination, e) + } + } + } + } + } + } + + pub async fn link_task(aux_mutex: &Rc>, + routing_table: &drtio_routing::RoutingTable, + up_destinations: &Rc>, + timer: GlobalTimer) { + let mut up_links = [false; csr::DRTIO.len()]; + loop { + for linkno in 0..csr::DRTIO.len() { + let linkno = linkno as u8; + if up_links[linkno as usize] { + /* link was previously up */ + if link_rx_up(linkno).await { + process_unsolicited_aux(aux_mutex, linkno).await; + process_local_errors(linkno).await; + } else { + info!("[LINK#{}] link is down", linkno); + up_links[linkno as usize] = false; + } + } else { + /* link was previously down */ + if link_rx_up(linkno).await { + info!("[LINK#{}] link RX became up, pinging", linkno); + let ping_count = ping_remote(aux_mutex, linkno, timer).await; + if ping_count > 0 { + info!("[LINK#{}] remote replied after {} packets", linkno, ping_count); + up_links[linkno as usize] = true; + if let Err(e) = sync_tsc(aux_mutex, linkno, timer).await { + error!("[LINK#{}] failed to sync TSC ({})", linkno, e); + } + if let Err(e) = load_routing_table(aux_mutex, linkno, routing_table, timer).await { + error!("[LINK#{}] failed to load routing table ({})", linkno, e); + } + if let Err(e) = set_rank(aux_mutex, linkno, 1 as u8, timer).await { + error!("[LINK#{}] failed to set rank ({})", linkno, e); + } + info!("[LINK#{}] link initialization completed", linkno); + } else { + error!("[LINK#{}] ping failed", linkno); + } + } + } + } + destination_survey(aux_mutex, routing_table, &up_links, up_destinations, timer).await; + let mut countdown = timer.countdown(); + delay(&mut countdown, Milliseconds(200)).await; + } + } + + #[allow(dead_code)] + pub fn reset(aux_mutex: Rc>, mut timer: GlobalTimer) { + for linkno in 0..csr::DRTIO.len() { + unsafe { + (csr::DRTIO[linkno].reset_write)(1); + } + } + timer.delay_ms(1); + for linkno in 0..csr::DRTIO.len() { + unsafe { + (csr::DRTIO[linkno].reset_write)(0); + } + } + + for linkno in 0..csr::DRTIO.len() { + let linkno = linkno as u8; + if task::block_on(link_rx_up(linkno)) { + let reply = task::block_on(aux_transact(&aux_mutex, linkno, + &Packet::ResetRequest, timer)); + match reply { + Ok(Packet::ResetAck) => (), + Ok(_) => error!("[LINK#{}] reset failed, received unexpected aux packet", linkno), + Err(e) => error!("[LINK#{}] reset failed, aux packet error ({})", linkno, e) + } + } + } + } +} + +#[cfg(not(has_drtio))] +pub mod drtio { + use super::*; + + pub fn startup(_aux_mutex: &Rc>, _routing_table: &Rc>, + _up_destinations: &Rc>, _timer: GlobalTimer) {} + + #[allow(dead_code)] + pub fn reset(_aux_mutex: Rc>, mut _timer: GlobalTimer) {} +} + +pub fn startup(aux_mutex: &Rc>, + routing_table: &Rc>, + up_destinations: &Rc>, + timer: GlobalTimer) { + drtio::startup(aux_mutex, routing_table, up_destinations, timer); + unsafe { + csr::rtio_core::reset_phy_write(1); + } +} + +#[allow(dead_code)] +pub fn reset(aux_mutex: Rc>, timer: GlobalTimer) { + unsafe { + csr::rtio_core::reset_write(1); + } + drtio::reset(aux_mutex, timer) +} diff --git a/src/satman/Cargo.toml b/src/satman/Cargo.toml new file mode 100644 index 0000000..6c44e5a --- /dev/null +++ b/src/satman/Cargo.toml @@ -0,0 +1,28 @@ +[package] +authors = ["M-Labs"] +name = "satman" +version = "0.0.0" +build = "build.rs" + +[features] +target_zc706 = ["libboard_zynq/target_zc706", "libsupport_zynq/target_zc706", "libconfig/target_zc706", "libboard_artiq/target_zc706"] +target_kasli_soc = ["libboard_zynq/target_kasli_soc", "libsupport_zynq/target_kasli_soc", "libconfig/target_kasli_soc", "libboard_artiq/target_kasli_soc"] +default = ["target_zc706", ] + +[build-dependencies] +build_zynq = { path = "../libbuild_zynq" } + +[dependencies] +log = { version = "0.4", default-features = false } +embedded-hal = "0.2" + +libboard_zynq = { git = "https://git.m-labs.hk/M-Labs/zynq-rs.git", features = ["ipv6"]} +libsupport_zynq = { default-features = false, features = ["alloc_core"], git = "https://git.m-labs.hk/M-Labs/zynq-rs.git" } +libcortex_a9 = { git = "https://git.m-labs.hk/M-Labs/zynq-rs.git" } +libasync = { git = "https://git.m-labs.hk/M-Labs/zynq-rs.git" } +libregister = { git = "https://git.m-labs.hk/M-Labs/zynq-rs.git" } +libconfig = { git = "https://git.m-labs.hk/M-Labs/zynq-rs.git", features = ["ipv6"] } + +libboard_artiq = { path = "../libboard_artiq" } +unwind = { path = "../libunwind" } +libc = { path = "../libc" } \ No newline at end of file diff --git a/src/satman/build.rs b/src/satman/build.rs new file mode 100644 index 0000000..aee092b --- /dev/null +++ b/src/satman/build.rs @@ -0,0 +1,6 @@ +extern crate build_zynq; + +fn main() { + build_zynq::add_linker_script(); + build_zynq::cfg(); +} diff --git a/src/satman/src/main.rs b/src/satman/src/main.rs new file mode 100644 index 0000000..5c05e25 --- /dev/null +++ b/src/satman/src/main.rs @@ -0,0 +1,626 @@ +#![no_std] +#![no_main] +#![feature(never_type, panic_info_message, asm, naked_functions)] +#![feature(alloc_error_handler)] + +#[macro_use] +extern crate log; + +extern crate embedded_hal; + +extern crate libboard_zynq; +extern crate libboard_artiq; +extern crate libsupport_zynq; +extern crate libcortex_a9; +extern crate libregister; + +extern crate unwind; + +extern crate alloc; + +use libboard_zynq::{i2c::I2c, timer::GlobalTimer, time::Milliseconds, print, println, mpcore, gic, stdio}; +use libsupport_zynq::ram; +#[cfg(has_si5324)] +use libboard_artiq::si5324; +use libboard_artiq::{pl::csr, drtio_routing, drtioaux, logger, identifier_read, init_gateware}; +use libcortex_a9::{spin_lock_yield, interrupt_handler, regs::{MPIDR, SP}, notify_spin_lock, asm, l2c::enable_l2_cache}; +use libregister::{RegisterW, RegisterR}; + +use embedded_hal::blocking::delay::DelayUs; +use core::sync::atomic::{AtomicBool, Ordering}; + +mod repeater; + +fn drtiosat_reset(reset: bool) { + unsafe { + csr::drtiosat::reset_write(if reset { 1 } else { 0 }); + } +} + +fn drtiosat_reset_phy(reset: bool) { + unsafe { + csr::drtiosat::reset_phy_write(if reset { 1 } else { 0 }); + } +} + +fn drtiosat_link_rx_up() -> bool { + unsafe { + csr::drtiosat::rx_up_read() == 1 + } +} + +fn drtiosat_tsc_loaded() -> bool { + unsafe { + let tsc_loaded = csr::drtiosat::tsc_loaded_read() == 1; + if tsc_loaded { + csr::drtiosat::tsc_loaded_write(1); + } + tsc_loaded + } +} + + +#[cfg(has_drtio_routing)] +macro_rules! forward { + ($routing_table:expr, $destination:expr, $rank:expr, $repeaters:expr, $packet:expr, $timer:expr) => {{ + let hop = $routing_table.0[$destination as usize][$rank as usize]; + if hop != 0 { + let repno = (hop - 1) as usize; + if repno < $repeaters.len() { + return $repeaters[repno].aux_forward($packet, $timer); + } else { + return Err(drtioaux::Error::RoutingError); + } + } + }} +} + +#[cfg(not(has_drtio_routing))] +macro_rules! forward { + ($routing_table:expr, $destination:expr, $rank:expr, $repeaters:expr, $packet:expr, $timer:expr) => {} +} + +fn process_aux_packet(_repeaters: &mut [repeater::Repeater], + _routing_table: &mut drtio_routing::RoutingTable, _rank: &mut u8, + packet: drtioaux::Packet, timer: &mut GlobalTimer, i2c: &mut I2c) -> Result<(), drtioaux::Error> { + // In the code below, *_chan_sel_write takes an u8 if there are fewer than 256 channels, + // and u16 otherwise; hence the `as _` conversion. + match packet { + drtioaux::Packet::EchoRequest => + drtioaux::send(0, &drtioaux::Packet::EchoReply), + drtioaux::Packet::ResetRequest => { + info!("resetting RTIO"); + drtiosat_reset(true); + timer.delay_us(100); + drtiosat_reset(false); + for rep in _repeaters.iter() { + if let Err(e) = rep.rtio_reset(timer) { + error!("failed to issue RTIO reset ({:?})", e); + } + } + drtioaux::send(0, &drtioaux::Packet::ResetAck) + }, + + drtioaux::Packet::DestinationStatusRequest { destination: _destination } => { + #[cfg(has_drtio_routing)] + let hop = _routing_table.0[_destination as usize][*_rank as usize]; + #[cfg(not(has_drtio_routing))] + let hop = 0; + + if hop == 0 { + let errors; + unsafe { + errors = csr::drtiosat::rtio_error_read(); + } + if errors & 1 != 0 { + let channel; + unsafe { + channel = csr::drtiosat::sequence_error_channel_read(); + csr::drtiosat::rtio_error_write(1); + } + drtioaux::send(0, + &drtioaux::Packet::DestinationSequenceErrorReply { channel })?; + } else if errors & 2 != 0 { + let channel; + unsafe { + channel = csr::drtiosat::collision_channel_read(); + csr::drtiosat::rtio_error_write(2); + } + drtioaux::send(0, + &drtioaux::Packet::DestinationCollisionReply { channel })?; + } else if errors & 4 != 0 { + let channel; + unsafe { + channel = csr::drtiosat::busy_channel_read(); + csr::drtiosat::rtio_error_write(4); + } + drtioaux::send(0, + &drtioaux::Packet::DestinationBusyReply { channel })?; + } + else { + drtioaux::send(0, &drtioaux::Packet::DestinationOkReply)?; + } + } + + #[cfg(has_drtio_routing)] + { + if hop != 0 { + let hop = hop as usize; + if hop <= csr::DRTIOREP.len() { + let repno = hop - 1; + match _repeaters[repno].aux_forward(&drtioaux::Packet::DestinationStatusRequest { + destination: _destination + }, timer) { + Ok(()) => (), + Err(drtioaux::Error::LinkDown) => drtioaux::send(0, &drtioaux::Packet::DestinationDownReply)?, + Err(e) => { + drtioaux::send(0, &drtioaux::Packet::DestinationDownReply)?; + error!("aux error when handling destination status request: {:?}", e); + }, + } + } else { + drtioaux::send(0, &drtioaux::Packet::DestinationDownReply)?; + } + } + } + + Ok(()) + } + + #[cfg(has_drtio_routing)] + drtioaux::Packet::RoutingSetPath { destination, hops } => { + _routing_table.0[destination as usize] = hops; + for rep in _repeaters.iter() { + if let Err(e) = rep.set_path(destination, &hops, timer) { + error!("failed to set path ({:?})", e); + } + } + drtioaux::send(0, &drtioaux::Packet::RoutingAck) + } + #[cfg(has_drtio_routing)] + drtioaux::Packet::RoutingSetRank { rank } => { + *_rank = rank; + drtio_routing::interconnect_enable_all(_routing_table, rank); + + let rep_rank = rank + 1; + for rep in _repeaters.iter() { + if let Err(e) = rep.set_rank(rep_rank, timer) { + error!("failed to set rank ({:?})", e); + } + } + + info!("rank: {}", rank); + info!("routing table: {}", _routing_table); + + drtioaux::send(0, &drtioaux::Packet::RoutingAck) + } + + #[cfg(not(has_drtio_routing))] + drtioaux::Packet::RoutingSetPath { destination: _, hops: _ } => { + drtioaux::send(0, &drtioaux::Packet::RoutingAck) + } + #[cfg(not(has_drtio_routing))] + drtioaux::Packet::RoutingSetRank { rank: _ } => { + drtioaux::send(0, &drtioaux::Packet::RoutingAck) + } + + drtioaux::Packet::MonitorRequest { destination: _destination, channel: _channel, probe: _probe } => { + forward!(_routing_table, _destination, *_rank, _repeaters, &packet, timer); + let value; + #[cfg(has_rtio_moninj)] + unsafe { + csr::rtio_moninj::mon_chan_sel_write(channel as _); + csr::rtio_moninj::mon_probe_sel_write(probe); + csr::rtio_moninj::mon_value_update_write(1); + value = csr::rtio_moninj::mon_value_read(); + } + #[cfg(not(has_rtio_moninj))] + { + value = 0; + } + let reply = drtioaux::Packet::MonitorReply { value: value as u32 }; + drtioaux::send(0, &reply) + }, + drtioaux::Packet::InjectionRequest { destination: _destination, channel: _channel, + overrd: _overrd, value: _value } => { + forward!(_routing_table, _destination, *_rank, _repeaters, &packet, timer); + #[cfg(has_rtio_moninj)] + unsafe { + csr::rtio_moninj::inj_chan_sel_write(channel as _); + csr::rtio_moninj::inj_override_sel_write(overrd); + csr::rtio_moninj::inj_value_write(value); + } + Ok(()) + }, + drtioaux::Packet::InjectionStatusRequest { destination: _destination, + channel: _channel, overrd: _overrd } => { + forward!(_routing_table, _destination, *_rank, _repeaters, &packet, timer); + let value; + #[cfg(has_rtio_moninj)] + unsafe { + csr::rtio_moninj::inj_chan_sel_write(channel as _); + csr::rtio_moninj::inj_override_sel_write(overrd); + value = csr::rtio_moninj::inj_value_read(); + } + #[cfg(not(has_rtio_moninj))] + { + value = 0; + } + drtioaux::send(0, &drtioaux::Packet::InjectionStatusReply { value: value }) + }, + + drtioaux::Packet::I2cStartRequest { destination: _destination, busno: _busno } => { + forward!(_routing_table, _destination, *_rank, _repeaters, &packet, timer); + let succeeded = i2c.start().is_ok(); + drtioaux::send(0, &drtioaux::Packet::I2cBasicReply { succeeded: succeeded }) + } + drtioaux::Packet::I2cRestartRequest { destination: _destination, busno: _busno } => { + forward!(_routing_table, _destination, *_rank, _repeaters, &packet, timer); + let succeeded = i2c.restart().is_ok(); + drtioaux::send(0, &drtioaux::Packet::I2cBasicReply { succeeded: succeeded }) + } + drtioaux::Packet::I2cStopRequest { destination: _destination, busno: _busno } => { + forward!(_routing_table, _destination, *_rank, _repeaters, &packet, timer); + let succeeded = i2c.stop().is_ok(); + drtioaux::send(0, &drtioaux::Packet::I2cBasicReply { succeeded: succeeded }) + } + drtioaux::Packet::I2cWriteRequest { destination: _destination, busno: _busno, data } => { + forward!(_routing_table, _destination, *_rank, _repeaters, &packet, timer); + match i2c.write(data) { + Ok(ack) => drtioaux::send(0, + &drtioaux::Packet::I2cWriteReply { succeeded: true, ack: ack }), + Err(_) => drtioaux::send(0, + &drtioaux::Packet::I2cWriteReply { succeeded: false, ack: false }) + } + } + drtioaux::Packet::I2cReadRequest { destination: _destination, busno: _busno, ack } => { + forward!(_routing_table, _destination, *_rank, _repeaters, &packet, timer); + match i2c.read(ack) { + Ok(data) => drtioaux::send(0, + &drtioaux::Packet::I2cReadReply { succeeded: true, data: data }), + Err(_) => drtioaux::send(0, + &drtioaux::Packet::I2cReadReply { succeeded: false, data: 0xff }) + } + } + + drtioaux::Packet::SpiSetConfigRequest { destination: _destination, busno: _busno, + flags: _flags, length: _length, div: _div, cs: _cs } => { + forward!(_routing_table, _destination, *_rank, _repeaters, &packet, timer); + // todo: reimplement when/if SPI is available + //let succeeded = spi::set_config(busno, flags, length, div, cs).is_ok(); + drtioaux::send(0, + &drtioaux::Packet::SpiBasicReply { succeeded: false }) + }, + drtioaux::Packet::SpiWriteRequest { destination: _destination, busno: _busno, data: _data } => { + forward!(_routing_table, _destination, *_rank, _repeaters, &packet, timer); + // todo: reimplement when/if SPI is available + //let succeeded = spi::write(busno, data).is_ok(); + drtioaux::send(0, + &drtioaux::Packet::SpiBasicReply { succeeded: false }) + } + drtioaux::Packet::SpiReadRequest { destination: _destination, busno: _busno } => { + forward!(_routing_table, _destination, *_rank, _repeaters, &packet, timer); + // todo: reimplement when/if SPI is available + // match spi::read(busno) { + // Ok(data) => drtioaux::send(0, + // &drtioaux::Packet::SpiReadReply { succeeded: true, data: data }), + // Err(_) => drtioaux::send(0, + // &drtioaux::Packet::SpiReadReply { succeeded: false, data: 0 }) + // } + drtioaux::send(0, + &drtioaux::Packet::SpiReadReply { succeeded: false, data: 0 }) + } + + _ => { + warn!("received unexpected aux packet"); + Ok(()) + } + } +} + +fn process_aux_packets(repeaters: &mut [repeater::Repeater], + routing_table: &mut drtio_routing::RoutingTable, rank: &mut u8, + timer: &mut GlobalTimer, i2c: &mut I2c) { + let result = + drtioaux::recv(0).and_then(|packet| { + if let Some(packet) = packet { + process_aux_packet(repeaters, routing_table, rank, packet, timer, i2c) + } else { + Ok(()) + } + }); + match result { + Ok(()) => (), + Err(e) => warn!("aux packet error ({:?})", e) + } +} + +fn drtiosat_process_errors() { + let errors; + unsafe { + errors = csr::drtiosat::protocol_error_read(); + } + if errors & 1 != 0 { + error!("received packet of an unknown type"); + } + if errors & 2 != 0 { + error!("received truncated packet"); + } + if errors & 4 != 0 { + let destination; + unsafe { + destination = csr::drtiosat::buffer_space_timeout_dest_read(); + } + error!("timeout attempting to get buffer space from CRI, destination=0x{:02x}", destination) + } + if errors & 8 != 0 { + let channel; + let timestamp_event; + let timestamp_counter; + unsafe { + channel = csr::drtiosat::underflow_channel_read(); + timestamp_event = csr::drtiosat::underflow_timestamp_event_read() as i64; + timestamp_counter = csr::drtiosat::underflow_timestamp_counter_read() as i64; + } + error!("write underflow, channel={}, timestamp={}, counter={}, slack={}", + channel, timestamp_event, timestamp_counter, timestamp_event-timestamp_counter); + } + if errors & 16 != 0 { + error!("write overflow"); + } + unsafe { + csr::drtiosat::protocol_error_write(errors); + } +} + + +#[cfg(has_rtio_crg)] +fn init_rtio_crg(timer: GlobalTimer) { + unsafe { + csr::rtio_crg::pll_reset_write(0); + } + timer.delay_us(150); + let locked = unsafe { csr::rtio_crg::pll_locked_read() != 0 }; + if !locked { + error!("RTIO clock failed"); + } +} + +#[cfg(not(has_rtio_crg))] +fn init_rtio_crg(_timer: GlobalTimer) { } + +fn hardware_tick(ts: &mut u64, timer: &mut GlobalTimer) { + let now = timer.get_time(); + let mut ts_ms = Milliseconds(*ts); + if now > ts_ms { + ts_ms = now + Milliseconds(200); + *ts = ts_ms.0; + } +} + +#[cfg(has_si5324)] +const SI5324_SETTINGS: si5324::FrequencySettings + = si5324::FrequencySettings { + n1_hs : 5, + nc1_ls : 8, + n2_hs : 7, + n2_ls : 360, + n31 : 63, + n32 : 63, + bwsel : 4, + crystal_ref: true +}; + +static mut LOG_BUFFER: [u8; 1<<17] = [0; 1<<17]; + +#[no_mangle] +pub extern fn main_core0() -> i32 { + enable_l2_cache(0x8); + + let mut timer = GlobalTimer::start(); + + let buffer_logger = unsafe { + logger::BufferLogger::new(&mut LOG_BUFFER[..]) + }; + buffer_logger.set_uart_log_level(log::LevelFilter::Info); + buffer_logger.register(); + log::set_max_level(log::LevelFilter::Info); + + init_gateware(); + + info!("ARTIQ satellite manager starting..."); + info!("gateware ident {}", identifier_read(&mut [0; 64])); + + ram::init_alloc_core0(); + + let mut i2c = I2c::i2c0(); + i2c.init().expect("I2C initialization failed"); + + #[cfg(has_si5324)] + si5324::setup(&mut i2c, &SI5324_SETTINGS, si5324::Input::Ckin1, &mut timer).expect("cannot initialize Si5324"); + + unsafe { + csr::drtio_transceiver::stable_clkin_write(1); + } + timer.delay_us(1500); // wait for CPLL/QPLL lock + + unsafe { + csr::drtio_transceiver::txenable_write(0xffffffffu32 as _); + } + init_rtio_crg(timer); + + #[cfg(has_drtio_routing)] + let mut repeaters = [repeater::Repeater::default(); csr::DRTIOREP.len()]; + #[cfg(not(has_drtio_routing))] + let mut repeaters = [repeater::Repeater::default(); 0]; + for i in 0..repeaters.len() { + repeaters[i] = repeater::Repeater::new(i as u8); + } + let mut routing_table = drtio_routing::RoutingTable::default_empty(); + let mut rank = 1; + + let mut hardware_tick_ts = 0; + + loop { + while !drtiosat_link_rx_up() { + drtiosat_process_errors(); + #[allow(unused_mut)] + for mut rep in repeaters.iter_mut() { + rep.service(&routing_table, rank, &mut timer); + } + hardware_tick(&mut hardware_tick_ts, &mut timer); + } + + info!("uplink is up, switching to recovered clock"); + #[cfg(has_siphaser)] + { + si5324::siphaser::select_recovered_clock(&mut i2c, true, &mut timer).expect("failed to switch clocks"); + si5324::siphaser::calibrate_skew(&mut timer).expect("failed to calibrate skew"); + } + + drtioaux::reset(0); + drtiosat_reset(false); + drtiosat_reset_phy(false); + + while drtiosat_link_rx_up() { + drtiosat_process_errors(); + process_aux_packets(&mut repeaters, &mut routing_table, &mut rank, &mut timer, &mut i2c); + #[allow(unused_mut)] + for mut rep in repeaters.iter_mut() { + rep.service(&routing_table, rank, &mut timer); + } + hardware_tick(&mut hardware_tick_ts, &mut timer); + if drtiosat_tsc_loaded() { + info!("TSC loaded from uplink"); + for rep in repeaters.iter() { + if let Err(e) = rep.sync_tsc(&mut timer) { + error!("failed to sync TSC ({:?})", e); + } + } + if let Err(e) = drtioaux::send(0, &drtioaux::Packet::TSCAck) { + error!("aux packet error: {:?}", e); + } + } + } + + drtiosat_reset_phy(true); + drtiosat_reset(true); + drtiosat_tsc_loaded(); + info!("uplink is down, switching to local oscillator clock"); + #[cfg(has_siphaser)] + si5324::siphaser::select_recovered_clock(&mut i2c, false, &mut timer).expect("failed to switch clocks"); + } +} + +extern "C" { + static mut __stack1_start: u32; +} + +interrupt_handler!(IRQ, irq, __irq_stack0_start, __irq_stack1_start, { + if MPIDR.read().cpu_id() == 1{ + let mpcore = mpcore::RegisterBlock::mpcore(); + let mut gic = gic::InterruptController::gic(mpcore); + let id = gic.get_interrupt_id(); + if id.0 == 0 { + gic.end_interrupt(id); + asm::exit_irq(); + SP.write(&mut __stack1_start as *mut _ as u32); + asm::enable_irq(); + CORE1_RESTART.store(false, Ordering::Relaxed); + notify_spin_lock(); + main_core1(); + } + stdio::drop_uart(); + } + loop {} +}); + +static mut PANICKED: [bool; 2] = [false; 2]; + +static CORE1_RESTART: AtomicBool = AtomicBool::new(false); + +pub fn restart_core1() { + let mut interrupt_controller = gic::InterruptController::gic(mpcore::RegisterBlock::mpcore()); + CORE1_RESTART.store(true, Ordering::Relaxed); + interrupt_controller.send_sgi(gic::InterruptId(0), gic::CPUCore::Core1.into()); + while CORE1_RESTART.load(Ordering::Relaxed) { + spin_lock_yield(); + } +} + +#[no_mangle] +pub fn main_core1() { + let mut interrupt_controller = gic::InterruptController::gic(mpcore::RegisterBlock::mpcore()); + interrupt_controller.enable_interrupts(); + + loop {} +} + +#[no_mangle] +pub extern fn exception(_vect: u32, _regs: *const u32, pc: u32, ea: u32) { + + fn hexdump(addr: u32) { + let addr = (addr - addr % 4) as *const u32; + let mut ptr = addr; + println!("@ {:08p}", ptr); + for _ in 0..4 { + print!("+{:04x}: ", ptr as usize - addr as usize); + print!("{:08x} ", unsafe { *ptr }); ptr = ptr.wrapping_offset(1); + print!("{:08x} ", unsafe { *ptr }); ptr = ptr.wrapping_offset(1); + print!("{:08x} ", unsafe { *ptr }); ptr = ptr.wrapping_offset(1); + print!("{:08x}\n", unsafe { *ptr }); ptr = ptr.wrapping_offset(1); + } + } + + hexdump(pc); + hexdump(ea); + panic!("exception at PC 0x{:x}, EA 0x{:x}", pc, ea) +} + +#[no_mangle] // https://github.com/rust-lang/rust/issues/{38281,51647} +#[panic_handler] +pub fn panic_fmt(info: &core::panic::PanicInfo) -> ! { + let id = MPIDR.read().cpu_id() as usize; + print!("Core {} ", id); + unsafe { + if PANICKED[id] { + println!("nested panic!"); + loop {} + } + PANICKED[id] = true; + } + print!("panic at "); + if let Some(location) = info.location() { + print!("{}:{}:{}", location.file(), location.line(), location.column()); + } else { + print!("unknown location"); + } + if let Some(message) = info.message() { + println!(": {}", message); + } else { + println!(""); + } + + + loop {} +} + +// linker symbols +extern "C" { + static __text_start: u32; + static __text_end: u32; + static __exidx_start: u32; + static __exidx_end: u32; +} + +#[no_mangle] +extern fn dl_unwind_find_exidx(_pc: *const u32, len_ptr: *mut u32) -> *const u32 { + let length; + let start: *const u32; + unsafe { + length = (&__exidx_end as *const u32).offset_from(&__exidx_start) as u32; + start = &__exidx_start; + *len_ptr = length; + } + start +} diff --git a/src/satman/src/repeater.rs b/src/satman/src/repeater.rs new file mode 100644 index 0000000..ed12365 --- /dev/null +++ b/src/satman/src/repeater.rs @@ -0,0 +1,290 @@ +use libboard_artiq::{drtioaux, drtio_routing}; +use libboard_zynq::timer::GlobalTimer; + +#[cfg(has_drtio_routing)] +use libboard_artiq::{pl::csr}; +#[cfg(has_drtio_routing)] +use libboard_zynq::time::Milliseconds; +#[cfg(has_drtio_routing)] +use embedded_hal::prelude::_embedded_hal_blocking_delay_DelayUs; + +#[cfg(has_drtio_routing)] +fn rep_link_rx_up(repno: u8) -> bool { + let repno = repno as usize; + unsafe { + (csr::DRTIOREP[repno].rx_up_read)() == 1 + } +} + +#[cfg(has_drtio_routing)] +#[derive(Clone, Copy, PartialEq)] +enum RepeaterState { + Down, + SendPing { ping_count: u16 }, + WaitPingReply { ping_count: u16, timeout: Milliseconds }, + Up, + Failed +} + +#[cfg(has_drtio_routing)] +impl Default for RepeaterState { + fn default() -> RepeaterState { RepeaterState::Down } +} + +#[cfg(has_drtio_routing)] +#[derive(Clone, Copy, Default)] +pub struct Repeater { + repno: u8, + auxno: u8, + state: RepeaterState +} + +#[cfg(has_drtio_routing)] +impl Repeater { + pub fn new(repno: u8) -> Repeater { + Repeater { + repno: repno, + auxno: repno + 1, + state: RepeaterState::Down + } + } + + #[allow(dead_code)] + pub fn is_up(&self) -> bool { + self.state == RepeaterState::Up + } + + pub fn service(&mut self, routing_table: &drtio_routing::RoutingTable, rank: u8, + timer: &mut GlobalTimer) { + self.process_local_errors(); + + match self.state { + RepeaterState::Down => { + if rep_link_rx_up(self.repno) { + info!("[REP#{}] link RX became up, pinging", self.repno); + self.state = RepeaterState::SendPing { ping_count: 0 }; + } + } + RepeaterState::SendPing { ping_count } => { + if rep_link_rx_up(self.repno) { + drtioaux::send(self.auxno, &drtioaux::Packet::EchoRequest).unwrap(); + self.state = RepeaterState::WaitPingReply { + ping_count: ping_count + 1, + timeout: timer.get_time() + Milliseconds(100) + } + } else { + error!("[REP#{}] link RX went down during ping", self.repno); + self.state = RepeaterState::Down; + } + } + RepeaterState::WaitPingReply { ping_count, timeout } => { + if rep_link_rx_up(self.repno) { + if let Ok(Some(drtioaux::Packet::EchoReply)) = drtioaux::recv(self.auxno) { + info!("[REP#{}] remote replied after {} packets", self.repno, ping_count); + self.state = RepeaterState::Up; + if let Err(e) = self.sync_tsc(timer) { + error!("[REP#{}] failed to sync TSC ({:?})", self.repno, e); + self.state = RepeaterState::Failed; + return; + } + if let Err(e) = self.load_routing_table(routing_table, timer) { + error!("[REP#{}] failed to load routing table ({:?})", self.repno, e); + self.state = RepeaterState::Failed; + return; + } + if let Err(e) = self.set_rank(rank + 1, timer) { + error!("[REP#{}] failed to set rank ({:?})", self.repno, e); + self.state = RepeaterState::Failed; + return; + } + } else { + if timer.get_time() > timeout { + if ping_count > 200 { + error!("[REP#{}] ping failed", self.repno); + self.state = RepeaterState::Failed; + } else { + self.state = RepeaterState::SendPing { ping_count: ping_count }; + } + } + } + } else { + error!("[REP#{}] link RX went down during ping", self.repno); + self.state = RepeaterState::Down; + } + } + RepeaterState::Up => { + self.process_unsolicited_aux(); + if !rep_link_rx_up(self.repno) { + info!("[REP#{}] link is down", self.repno); + self.state = RepeaterState::Down; + } + } + RepeaterState::Failed => { + if !rep_link_rx_up(self.repno) { + info!("[REP#{}] link is down", self.repno); + self.state = RepeaterState::Down; + } + } + } + } + + fn process_unsolicited_aux(&self) { + match drtioaux::recv(self.auxno) { + Ok(Some(packet)) => warn!("[REP#{}] unsolicited aux packet: {:?}", self.repno, packet), + Ok(None) => (), + Err(_) => warn!("[REP#{}] aux packet error", self.repno) + } + } + + fn process_local_errors(&self) { + let repno = self.repno as usize; + let errors; + unsafe { + errors = (csr::DRTIOREP[repno].protocol_error_read)(); + } + if errors & 1 != 0 { + error!("[REP#{}] received packet of an unknown type", repno); + } + if errors & 2 != 0 { + error!("[REP#{}] received truncated packet", repno); + } + if errors & 4 != 0 { + let cmd; + let chan_sel; + unsafe { + cmd = (csr::DRTIOREP[repno].command_missed_cmd_read)(); + chan_sel = (csr::DRTIOREP[repno].command_missed_chan_sel_read)(); + } + error!("[REP#{}] CRI command missed, cmd={}, chan_sel=0x{:06x}", repno, cmd, chan_sel) + } + if errors & 8 != 0 { + let destination; + unsafe { + destination = (csr::DRTIOREP[repno].buffer_space_timeout_dest_read)(); + } + error!("[REP#{}] timeout attempting to get remote buffer space, destination=0x{:02x}", repno, destination); + } + unsafe { + (csr::DRTIOREP[repno].protocol_error_write)(errors); + } + } + + fn recv_aux_timeout(&self, timeout: u32, timer: &mut GlobalTimer) -> Result { + let max_time = timer.get_time() + Milliseconds(timeout.into()); + loop { + if !rep_link_rx_up(self.repno) { + return Err(drtioaux::Error::LinkDown); + } + if timer.get_time() > max_time { + return Err(drtioaux::Error::TimedOut); + } + match drtioaux::recv(self.auxno) { + Ok(Some(packet)) => return Ok(packet), + Ok(None) => (), + Err(e) => return Err(e) + } + } + } + + pub fn aux_forward(&self, request: &drtioaux::Packet, timer: &mut GlobalTimer) -> Result<(), drtioaux::Error> { + if self.state != RepeaterState::Up { + return Err(drtioaux::Error::LinkDown); + } + drtioaux::send(self.auxno, request).unwrap(); + let reply = self.recv_aux_timeout(200, timer)?; + drtioaux::send(0, &reply).unwrap(); + Ok(()) + } + + pub fn sync_tsc(&self, timer: &mut GlobalTimer) -> Result<(), drtioaux::Error> { + if self.state != RepeaterState::Up { + return Ok(()); + } + + let repno = self.repno as usize; + unsafe { + (csr::DRTIOREP[repno].set_time_write)(1); + while (csr::DRTIOREP[repno].set_time_read)() == 1 {} + } + + // TSCAck is the only aux packet that is sent spontaneously + // by the satellite, in response to a TSC set on the RT link. + let reply = self.recv_aux_timeout(10000, timer)?; + if reply == drtioaux::Packet::TSCAck { + return Ok(()); + } else { + return Err(drtioaux::Error::UnexpectedReply); + } + } + + pub fn set_path(&self, destination: u8, hops: &[u8; drtio_routing::MAX_HOPS], timer: &mut GlobalTimer) -> Result<(), drtioaux::Error> { + if self.state != RepeaterState::Up { + return Ok(()); + } + + drtioaux::send(self.auxno, &drtioaux::Packet::RoutingSetPath { + destination: destination, + hops: *hops + }).unwrap(); + let reply = self.recv_aux_timeout(200, timer)?; + if reply != drtioaux::Packet::RoutingAck { + return Err(drtioaux::Error::UnexpectedReply); + } + Ok(()) + } + + pub fn load_routing_table(&self, routing_table: &drtio_routing::RoutingTable, timer: &mut GlobalTimer) -> Result<(), drtioaux::Error> { + for i in 0..drtio_routing::DEST_COUNT { + self.set_path(i as u8, &routing_table.0[i], timer)?; + } + Ok(()) + } + + pub fn set_rank(&self, rank: u8, timer: &mut GlobalTimer) -> Result<(), drtioaux::Error> { + if self.state != RepeaterState::Up { + return Ok(()); + } + drtioaux::send(self.auxno, &drtioaux::Packet::RoutingSetRank { + rank: rank + }).unwrap(); + let reply = self.recv_aux_timeout(200, timer)?; + if reply != drtioaux::Packet::RoutingAck { + return Err(drtioaux::Error::UnexpectedReply); + } + Ok(()) + } + + pub fn rtio_reset(&self, timer: &mut GlobalTimer) -> Result<(), drtioaux::Error> { + let repno = self.repno as usize; + unsafe { (csr::DRTIOREP[repno].reset_write)(1); } + timer.delay_us(100); + unsafe { (csr::DRTIOREP[repno].reset_write)(0); } + + if self.state != RepeaterState::Up { + return Ok(()); + } + + drtioaux::send(self.auxno, &drtioaux::Packet::ResetRequest).unwrap(); + let reply = self.recv_aux_timeout(200, timer)?; + if reply != drtioaux::Packet::ResetAck { + return Err(drtioaux::Error::UnexpectedReply); + } + Ok(()) + } +} + +#[cfg(not(has_drtio_routing))] +#[derive(Clone, Copy, Default)] +pub struct Repeater { +} + +#[cfg(not(has_drtio_routing))] +impl Repeater { + pub fn new(_repno: u8) -> Repeater { Repeater::default() } + + pub fn service(&self, _routing_table: &drtio_routing::RoutingTable, _rank: u8, _timer: &mut GlobalTimer) { } + + pub fn sync_tsc(&self, _timer: &mut GlobalTimer) -> Result<(), drtioaux::Error> { Ok(()) } + + pub fn rtio_reset(&self, _timer: &mut GlobalTimer) -> Result<(), drtioaux::Error> { Ok(()) } +}