forked from M-Labs/artiq-zynq
Compare commits
10 Commits
3f414278e2
...
e3d7c3bfe9
Author | SHA1 | Date |
---|---|---|
mwojcik | e3d7c3bfe9 | |
Egor Savkin | d3fbfd75b0 | |
Egor Savkin | b768d5648c | |
Sebastien Bourdeauducq | 812aea33b3 | |
linuswck | 136e24f597 | |
Sebastien Bourdeauducq | 0f050844cf | |
linuswck | a4d1be00c0 | |
linuswck | b15322b6ba | |
linuswck | 8fd1306145 | |
Sebastien Bourdeauducq | a28a819b18 |
14
flake.lock
14
flake.lock
|
@ -11,11 +11,11 @@
|
||||||
"src-pythonparser": "src-pythonparser"
|
"src-pythonparser": "src-pythonparser"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1696817638,
|
"lastModified": 1697013694,
|
||||||
"narHash": "sha256-rbIN4Ll1VX4RXxDBlLGgTiQC6L6jZE9q8HYBxPhwCpY=",
|
"narHash": "sha256-jmr+pFeyV0+nUbFY8/g2Aw4kU06i5CG0XhLobI8vUR8=",
|
||||||
"ref": "refs/heads/master",
|
"ref": "refs/heads/master",
|
||||||
"rev": "d070826911e86b5ec9de958c4bcd93ef0b7fddb2",
|
"rev": "f7abc156cbc0a371452148d11436e5f241798092",
|
||||||
"revCount": 8564,
|
"revCount": 8569,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/m-labs/artiq.git"
|
"url": "https://github.com/m-labs/artiq.git"
|
||||||
},
|
},
|
||||||
|
@ -163,11 +163,11 @@
|
||||||
"src-migen": {
|
"src-migen": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1693990700,
|
"lastModified": 1697013661,
|
||||||
"narHash": "sha256-qJLA03QcZ5S9DrqrseuzIQBTWS7rjAbYJxLYZEQ8rxA=",
|
"narHash": "sha256-qNCqgWyE4vTDmyjE2XMJqW1djuBxT25A36AzQfZqluU=",
|
||||||
"owner": "m-labs",
|
"owner": "m-labs",
|
||||||
"repo": "migen",
|
"repo": "migen",
|
||||||
"rev": "2cfee3e0db6fdca9b5918686ea77c93252e7cebd",
|
"rev": "aadc19df93b7aa9ca761aaebbb98a11e0cf2d7c7",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
@ -271,6 +271,7 @@ dependencies = [
|
||||||
"libconfig",
|
"libconfig",
|
||||||
"libcortex_a9",
|
"libcortex_a9",
|
||||||
"libregister",
|
"libregister",
|
||||||
|
"libsupport_zynq",
|
||||||
"log",
|
"log",
|
||||||
"log_buffer",
|
"log_buffer",
|
||||||
"nb 1.0.0",
|
"nb 1.0.0",
|
||||||
|
|
|
@ -7,7 +7,7 @@ runtime: ../build/runtime.bin
|
||||||
|
|
||||||
satman: ../build/satman.bin
|
satman: ../build/satman.bin
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all manifests
|
||||||
|
|
||||||
manifests = libboard_artiq/Cargo.toml libc/Cargo.toml libdyld/Cargo.toml libio/Cargo.toml libksupport/Cargo.toml runtime/Cargo.toml satman/Cargo.toml
|
manifests = libboard_artiq/Cargo.toml libc/Cargo.toml libdyld/Cargo.toml libio/Cargo.toml libksupport/Cargo.toml runtime/Cargo.toml satman/Cargo.toml
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ from artiq.coredevice import jsondesc
|
||||||
from artiq.gateware import rtio, eem_7series
|
from artiq.gateware import rtio, eem_7series
|
||||||
from artiq.gateware.rtio.xilinx_clocking import fix_serdes_timing_path
|
from artiq.gateware.rtio.xilinx_clocking import fix_serdes_timing_path
|
||||||
from artiq.gateware.rtio.phy import ttl_simple
|
from artiq.gateware.rtio.phy import ttl_simple
|
||||||
from artiq.gateware.drtio.transceiver import gtx_7series
|
from artiq.gateware.drtio.transceiver import gtx_7series, eem_serdes
|
||||||
from artiq.gateware.drtio.siphaser import SiPhaser7Series
|
from artiq.gateware.drtio.siphaser import SiPhaser7Series
|
||||||
from artiq.gateware.drtio.rx_synchronizer import XilinxRXSynchronizer
|
from artiq.gateware.drtio.rx_synchronizer import XilinxRXSynchronizer
|
||||||
from artiq.gateware.drtio import *
|
from artiq.gateware.drtio import *
|
||||||
|
@ -107,6 +107,7 @@ class GTPBootstrapClock(Module):
|
||||||
class GenericStandalone(SoCCore):
|
class GenericStandalone(SoCCore):
|
||||||
def __init__(self, description, acpki=False):
|
def __init__(self, description, acpki=False):
|
||||||
self.acpki = acpki
|
self.acpki = acpki
|
||||||
|
clk_freq = description["rtio_frequency"]
|
||||||
|
|
||||||
platform = kasli_soc.Platform()
|
platform = kasli_soc.Platform()
|
||||||
platform.toolchain.bitstream_commands.extend([
|
platform.toolchain.bitstream_commands.extend([
|
||||||
|
@ -127,15 +128,21 @@ class GenericStandalone(SoCCore):
|
||||||
|
|
||||||
clk_synth = platform.request("cdr_clk_clean_fabric")
|
clk_synth = platform.request("cdr_clk_clean_fabric")
|
||||||
clk_synth_se = Signal()
|
clk_synth_se = Signal()
|
||||||
|
clk_synth_se_buf = Signal()
|
||||||
platform.add_period_constraint(clk_synth.p, 8.0)
|
platform.add_period_constraint(clk_synth.p, 8.0)
|
||||||
|
|
||||||
self.specials += Instance("IBUFGDS",
|
self.specials += [
|
||||||
|
Instance("IBUFGDS",
|
||||||
p_DIFF_TERM="TRUE", p_IBUF_LOW_PWR="FALSE",
|
p_DIFF_TERM="TRUE", p_IBUF_LOW_PWR="FALSE",
|
||||||
i_I=clk_synth.p, i_IB=clk_synth.n, o_O=clk_synth_se)
|
i_I=clk_synth.p, i_IB=clk_synth.n, o_O=clk_synth_se
|
||||||
|
),
|
||||||
|
Instance("BUFG", i_I=clk_synth_se, o_O=clk_synth_se_buf),
|
||||||
|
]
|
||||||
fix_serdes_timing_path(platform)
|
fix_serdes_timing_path(platform)
|
||||||
self.submodules.bootstrap = GTPBootstrapClock(self.platform, description["rtio_frequency"])
|
self.submodules.bootstrap = GTPBootstrapClock(self.platform, clk_freq)
|
||||||
|
self.config["CLOCK_FREQUENCY"] = int(clk_freq)
|
||||||
|
|
||||||
self.submodules.sys_crg = zynq_clocking.SYSCRG(self.platform, self.ps7, clk_synth_se)
|
self.submodules.sys_crg = zynq_clocking.SYSCRG(self.platform, self.ps7, clk_synth_se_buf)
|
||||||
platform.add_false_path_constraints(
|
platform.add_false_path_constraints(
|
||||||
self.bootstrap.cd_bootstrap.clk, self.sys_crg.cd_sys.clk)
|
self.bootstrap.cd_bootstrap.clk, self.sys_crg.cd_sys.clk)
|
||||||
self.csr_devices.append("sys_crg")
|
self.csr_devices.append("sys_crg")
|
||||||
|
@ -201,6 +208,7 @@ class GenericMaster(SoCCore):
|
||||||
def __init__(self, description, acpki=False):
|
def __init__(self, description, acpki=False):
|
||||||
clk_freq = description["rtio_frequency"]
|
clk_freq = description["rtio_frequency"]
|
||||||
|
|
||||||
|
has_drtio_over_eem = any(peripheral["type"] == "shuttler" for peripheral in description["peripherals"])
|
||||||
self.acpki = acpki
|
self.acpki = acpki
|
||||||
|
|
||||||
platform = kasli_soc.Platform()
|
platform = kasli_soc.Platform()
|
||||||
|
@ -223,6 +231,7 @@ class GenericMaster(SoCCore):
|
||||||
pads=data_pads,
|
pads=data_pads,
|
||||||
clk_freq=clk_freq)
|
clk_freq=clk_freq)
|
||||||
self.csr_devices.append("gt_drtio")
|
self.csr_devices.append("gt_drtio")
|
||||||
|
self.config["CLOCK_FREQUENCY"] = int(clk_freq)
|
||||||
|
|
||||||
txout_buf = Signal()
|
txout_buf = Signal()
|
||||||
gtx0 = self.gt_drtio.gtxs[0]
|
gtx0 = self.gt_drtio.gtxs[0]
|
||||||
|
@ -246,6 +255,8 @@ class GenericMaster(SoCCore):
|
||||||
|
|
||||||
self.rtio_channels = []
|
self.rtio_channels = []
|
||||||
has_grabber = any(peripheral["type"] == "grabber" for peripheral in description["peripherals"])
|
has_grabber = any(peripheral["type"] == "grabber" for peripheral in description["peripherals"])
|
||||||
|
if has_drtio_over_eem:
|
||||||
|
self.eem_drtio_channels = []
|
||||||
if has_grabber:
|
if has_grabber:
|
||||||
self.grabber_csr_group = []
|
self.grabber_csr_group = []
|
||||||
eem_7series.add_peripherals(self, description["peripherals"], iostandard=eem_iostandard)
|
eem_7series.add_peripherals(self, description["peripherals"], iostandard=eem_iostandard)
|
||||||
|
@ -260,17 +271,17 @@ class GenericMaster(SoCCore):
|
||||||
|
|
||||||
self.submodules.rtio_tsc = rtio.TSC(glbl_fine_ts_width=3)
|
self.submodules.rtio_tsc = rtio.TSC(glbl_fine_ts_width=3)
|
||||||
|
|
||||||
drtio_csr_group = []
|
self.drtio_csr_group = []
|
||||||
drtioaux_csr_group = []
|
self.drtioaux_csr_group = []
|
||||||
drtioaux_memory_group = []
|
self.drtioaux_memory_group = []
|
||||||
self.drtio_cri = []
|
self.drtio_cri = []
|
||||||
for i in range(len(self.gt_drtio.channels)):
|
for i in range(len(self.gt_drtio.channels)):
|
||||||
core_name = "drtio" + str(i)
|
core_name = "drtio" + str(i)
|
||||||
coreaux_name = "drtioaux" + str(i)
|
coreaux_name = "drtioaux" + str(i)
|
||||||
memory_name = "drtioaux" + str(i) + "_mem"
|
memory_name = "drtioaux" + str(i) + "_mem"
|
||||||
drtio_csr_group.append(core_name)
|
self.drtio_csr_group.append(core_name)
|
||||||
drtioaux_csr_group.append(coreaux_name)
|
self.drtioaux_csr_group.append(coreaux_name)
|
||||||
drtioaux_memory_group.append(memory_name)
|
self.drtioaux_memory_group.append(memory_name)
|
||||||
|
|
||||||
cdr = ClockDomainsRenamer({"rtio_rx": "rtio_rx" + str(i)})
|
cdr = ClockDomainsRenamer({"rtio_rx": "rtio_rx" + str(i)})
|
||||||
|
|
||||||
|
@ -289,9 +300,10 @@ class GenericMaster(SoCCore):
|
||||||
self.add_memory_region(memory_name, self.mem_map["csr"] + memory_address, size * 2)
|
self.add_memory_region(memory_name, self.mem_map["csr"] + memory_address, size * 2)
|
||||||
self.config["HAS_DRTIO"] = None
|
self.config["HAS_DRTIO"] = None
|
||||||
self.config["HAS_DRTIO_ROUTING"] = None
|
self.config["HAS_DRTIO_ROUTING"] = None
|
||||||
self.add_csr_group("drtio", drtio_csr_group)
|
|
||||||
self.add_csr_group("drtioaux", drtioaux_csr_group)
|
if has_drtio_over_eem:
|
||||||
self.add_memory_group("drtioaux_mem", drtioaux_memory_group)
|
self.add_eem_drtio(self.eem_drtio_channels)
|
||||||
|
self.add_drtio_cpuif_groups()
|
||||||
|
|
||||||
self.submodules.rtio_core = rtio.Core(
|
self.submodules.rtio_core = rtio.Core(
|
||||||
self.rtio_tsc, self.rtio_channels, lane_count=description["sed_lanes"]
|
self.rtio_tsc, self.rtio_channels, lane_count=description["sed_lanes"]
|
||||||
|
@ -340,6 +352,42 @@ class GenericMaster(SoCCore):
|
||||||
self.comb += [self.virtual_leds.get(i).eq(channel.rx_ready)
|
self.comb += [self.virtual_leds.get(i).eq(channel.rx_ready)
|
||||||
for i, channel in enumerate(self.gt_drtio.channels)]
|
for i, channel in enumerate(self.gt_drtio.channels)]
|
||||||
|
|
||||||
|
def add_eem_drtio(self, eem_drtio_channels):
|
||||||
|
# Must be called before invoking add_rtio() to construct the CRI
|
||||||
|
# interconnect properly
|
||||||
|
self.submodules.eem_transceiver = eem_serdes.EEMSerdes(self.platform, eem_drtio_channels)
|
||||||
|
self.csr_devices.append("eem_transceiver")
|
||||||
|
self.config["HAS_DRTIO_EEM"] = None
|
||||||
|
self.config["EEM_DRTIO_COUNT"] = len(eem_drtio_channels)
|
||||||
|
|
||||||
|
cdr = ClockDomainsRenamer({"rtio_rx": "sys"})
|
||||||
|
for i in range(len(self.eem_transceiver.channels)):
|
||||||
|
channel = i + len(self.gt_drtio.channels)
|
||||||
|
core_name = "drtio" + str(channel)
|
||||||
|
coreaux_name = "drtioaux" + str(channel)
|
||||||
|
memory_name = "drtioaux" + str(channel) + "_mem"
|
||||||
|
self.drtio_csr_group.append(core_name)
|
||||||
|
self.drtioaux_csr_group.append(coreaux_name)
|
||||||
|
self.drtioaux_memory_group.append(memory_name)
|
||||||
|
|
||||||
|
core = cdr(DRTIOMaster(self.rtio_tsc, self.eem_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)
|
||||||
|
|
||||||
|
def add_drtio_cpuif_groups(self):
|
||||||
|
self.add_csr_group("drtio", self.drtio_csr_group)
|
||||||
|
self.add_csr_group("drtioaux", self.drtioaux_csr_group)
|
||||||
|
self.add_memory_group("drtioaux_mem", self.drtioaux_memory_group)
|
||||||
|
|
||||||
|
|
||||||
class GenericSatellite(SoCCore):
|
class GenericSatellite(SoCCore):
|
||||||
|
@ -487,6 +535,7 @@ class GenericSatellite(SoCCore):
|
||||||
|
|
||||||
rtio_clk_period = 1e9/clk_freq
|
rtio_clk_period = 1e9/clk_freq
|
||||||
self.config["RTIO_FREQUENCY"] = str(clk_freq/1e6)
|
self.config["RTIO_FREQUENCY"] = str(clk_freq/1e6)
|
||||||
|
self.config["CLOCK_FREQUENCY"] = int(clk_freq)
|
||||||
|
|
||||||
self.submodules.siphaser = SiPhaser7Series(
|
self.submodules.siphaser = SiPhaser7Series(
|
||||||
si5324_clkin=platform.request("cdr_clk"),
|
si5324_clkin=platform.request("cdr_clk"),
|
||||||
|
|
|
@ -69,6 +69,8 @@ class SYSCRG(Module, AutoCSR):
|
||||||
# assumes bootstrap clock is same freq as main and sys output
|
# assumes bootstrap clock is same freq as main and sys output
|
||||||
self.clock_domains.cd_sys = ClockDomain()
|
self.clock_domains.cd_sys = ClockDomain()
|
||||||
self.clock_domains.cd_sys4x = ClockDomain(reset_less=True)
|
self.clock_domains.cd_sys4x = ClockDomain(reset_less=True)
|
||||||
|
self.clock_domains.cd_sys5x = ClockDomain(reset_less=True)
|
||||||
|
self.clock_domains.cd_clk200 = ClockDomain()
|
||||||
|
|
||||||
self.current_clock = CSRStatus()
|
self.current_clock = CSRStatus()
|
||||||
|
|
||||||
|
@ -78,11 +80,6 @@ class SYSCRG(Module, AutoCSR):
|
||||||
|
|
||||||
period = 1e9/freq
|
period = 1e9/freq
|
||||||
|
|
||||||
pll_locked = Signal()
|
|
||||||
pll_sys = Signal()
|
|
||||||
pll_sys4x = Signal()
|
|
||||||
fb_clk = Signal()
|
|
||||||
|
|
||||||
self.submodules.clk_sw_fsm = ClockSwitchFSM()
|
self.submodules.clk_sw_fsm = ClockSwitchFSM()
|
||||||
|
|
||||||
if clk_sw is None:
|
if clk_sw is None:
|
||||||
|
@ -91,32 +88,55 @@ class SYSCRG(Module, AutoCSR):
|
||||||
else:
|
else:
|
||||||
self.comb += self.clk_sw_fsm.i_clk_sw.eq(clk_sw)
|
self.comb += self.clk_sw_fsm.i_clk_sw.eq(clk_sw)
|
||||||
|
|
||||||
|
mmcm_locked = Signal()
|
||||||
|
mmcm_sys = Signal()
|
||||||
|
mmcm_sys4x = Signal()
|
||||||
|
mmcm_sys5x = Signal()
|
||||||
|
mmcm_clk208 = Signal()
|
||||||
|
mmcm_fb_clk = Signal()
|
||||||
self.specials += [
|
self.specials += [
|
||||||
Instance("PLLE2_ADV",
|
Instance("MMCME2_ADV",
|
||||||
p_STARTUP_WAIT="FALSE", o_LOCKED=pll_locked,
|
p_STARTUP_WAIT="FALSE", o_LOCKED=mmcm_locked,
|
||||||
p_BANDWIDTH="HIGH",
|
p_BANDWIDTH="HIGH",
|
||||||
p_REF_JITTER1=0.001,
|
p_REF_JITTER1=0.001,
|
||||||
p_CLKIN1_PERIOD=period, i_CLKIN1=main_clk,
|
p_CLKIN1_PERIOD=period, i_CLKIN1=main_clk,
|
||||||
p_CLKIN2_PERIOD=period, i_CLKIN2=bootstrap_clk,
|
p_CLKIN2_PERIOD=period, i_CLKIN2=bootstrap_clk,
|
||||||
i_CLKINSEL=self.clk_sw_fsm.o_clk_sw,
|
i_CLKINSEL=self.clk_sw_fsm.o_clk_sw,
|
||||||
|
|
||||||
# VCO @ 1.5GHz when using 125MHz input
|
# VCO @ 1.25GHz
|
||||||
# 1.2GHz for 100MHz (zc706)
|
p_CLKFBOUT_MULT_F=10, p_DIVCLK_DIVIDE=1,
|
||||||
p_CLKFBOUT_MULT=12, p_DIVCLK_DIVIDE=1,
|
i_CLKFBIN=mmcm_fb_clk,
|
||||||
i_CLKFBIN=fb_clk,
|
i_RST=self.clk_sw_fsm.o_reset,
|
||||||
i_RST=self.clk_sw_fsm.o_reset,
|
|
||||||
|
|
||||||
o_CLKFBOUT=fb_clk,
|
o_CLKFBOUT=mmcm_fb_clk,
|
||||||
|
|
||||||
p_CLKOUT0_DIVIDE=3, p_CLKOUT0_PHASE=0.0,
|
p_CLKOUT0_DIVIDE_F=2.5, p_CLKOUT0_PHASE=0.0, o_CLKOUT0=mmcm_sys4x,
|
||||||
o_CLKOUT0=pll_sys4x,
|
|
||||||
|
|
||||||
p_CLKOUT1_DIVIDE=12, p_CLKOUT1_PHASE=0.0,
|
# 125MHz
|
||||||
o_CLKOUT1=pll_sys),
|
p_CLKOUT1_DIVIDE=10, p_CLKOUT1_PHASE=0.0, o_CLKOUT1=mmcm_sys,
|
||||||
Instance("BUFG", i_I=pll_sys, o_O=self.cd_sys.clk),
|
|
||||||
Instance("BUFG", i_I=pll_sys4x, o_O=self.cd_sys4x.clk),
|
|
||||||
|
|
||||||
AsyncResetSynchronizer(self.cd_sys, ~pll_locked),
|
# 625MHz
|
||||||
|
p_CLKOUT2_DIVIDE=2, p_CLKOUT2_PHASE=0.0, o_CLKOUT2=mmcm_sys5x,
|
||||||
|
|
||||||
|
# 208MHz
|
||||||
|
p_CLKOUT3_DIVIDE=6, p_CLKOUT3_PHASE=0.0, o_CLKOUT3=mmcm_clk208,
|
||||||
|
),
|
||||||
|
Instance("BUFG", i_I=mmcm_sys5x, o_O=self.cd_sys5x.clk),
|
||||||
|
Instance("BUFG", i_I=mmcm_sys, o_O=self.cd_sys.clk),
|
||||||
|
Instance("BUFG", i_I=mmcm_sys4x, o_O=self.cd_sys4x.clk),
|
||||||
|
Instance("BUFG", i_I=mmcm_clk208, o_O=self.cd_clk200.clk),
|
||||||
|
AsyncResetSynchronizer(self.cd_sys, ~mmcm_locked),
|
||||||
|
AsyncResetSynchronizer(self.cd_clk200, ~mmcm_locked),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
reset_counter = Signal(4, reset=15)
|
||||||
|
ic_reset = Signal(reset=1)
|
||||||
|
self.sync.clk200 += \
|
||||||
|
If(reset_counter != 0,
|
||||||
|
reset_counter.eq(reset_counter - 1)
|
||||||
|
).Else(
|
||||||
|
ic_reset.eq(0)
|
||||||
|
)
|
||||||
|
self.specials += Instance("IDELAYCTRL", i_REFCLK=ClockSignal("clk200"), i_RST=ic_reset)
|
||||||
|
|
||||||
self.comb += self.current_clock.status.eq(self.clk_sw_fsm.o_clk_sw)
|
self.comb += self.current_clock.status.eq(self.clk_sw_fsm.o_clk_sw)
|
||||||
|
|
|
@ -25,6 +25,7 @@ void = { version = "1", default-features = false }
|
||||||
|
|
||||||
io = { path = "../libio", features = ["byteorder"] }
|
io = { path = "../libio", features = ["byteorder"] }
|
||||||
libboard_zynq = { path = "@@ZYNQ_RS@@/libboard_zynq" }
|
libboard_zynq = { path = "@@ZYNQ_RS@@/libboard_zynq" }
|
||||||
|
libsupport_zynq = { path = "@@ZYNQ_RS@@/libsupport_zynq", default-features = false, features = ["alloc_core"] }
|
||||||
libregister = { path = "@@ZYNQ_RS@@/libregister" }
|
libregister = { path = "@@ZYNQ_RS@@/libregister" }
|
||||||
libconfig = { path = "@@ZYNQ_RS@@/libconfig", features = ["fat_lfn"] }
|
libconfig = { path = "@@ZYNQ_RS@@/libconfig", features = ["fat_lfn"] }
|
||||||
libcortex_a9 = { path = "@@ZYNQ_RS@@/libcortex_a9" }
|
libcortex_a9 = { path = "@@ZYNQ_RS@@/libcortex_a9" }
|
||||||
|
|
|
@ -0,0 +1,228 @@
|
||||||
|
use embedded_hal::prelude::_embedded_hal_blocking_delay_DelayUs;
|
||||||
|
use libboard_zynq::timer::GlobalTimer;
|
||||||
|
use libconfig::Config;
|
||||||
|
use libsupport_zynq::alloc::format;
|
||||||
|
use log::{debug, error, info};
|
||||||
|
|
||||||
|
use crate::pl;
|
||||||
|
|
||||||
|
struct SerdesConfig {
|
||||||
|
pub delay: [u8; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SerdesConfig {
|
||||||
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
|
unsafe {
|
||||||
|
core::slice::from_raw_parts(
|
||||||
|
(self as *const SerdesConfig) as *const u8,
|
||||||
|
core::mem::size_of::<SerdesConfig>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_lane(lane_no: u8) {
|
||||||
|
unsafe {
|
||||||
|
pl::csr::eem_transceiver::lane_sel_write(lane_no);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_delay(tap: u8, timer: &mut GlobalTimer) {
|
||||||
|
unsafe {
|
||||||
|
pl::csr::eem_transceiver::dly_cnt_in_write(tap);
|
||||||
|
pl::csr::eem_transceiver::dly_ld_write(1);
|
||||||
|
timer.delay_us(1);
|
||||||
|
assert!(tap as u8 == pl::csr::eem_transceiver::dly_cnt_out_read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_config(config: &SerdesConfig, timer: &mut GlobalTimer) {
|
||||||
|
for lane_no in 0..4 {
|
||||||
|
select_lane(lane_no as u8);
|
||||||
|
apply_delay(config.delay[lane_no], timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn assign_delay(timer: &mut GlobalTimer) -> SerdesConfig {
|
||||||
|
// Select an appropriate delay for lane 0
|
||||||
|
select_lane(0);
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
let mut best_dly = None;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut prev = None;
|
||||||
|
for curr_dly in 0..32 {
|
||||||
|
//let read_align = read_align_fn(curr_dly, timer);
|
||||||
|
let curr_low_rate = read_align(curr_dly, timer);
|
||||||
|
|
||||||
|
if let Some(prev_low_rate) = prev {
|
||||||
|
// This is potentially a crossover position
|
||||||
|
if prev_low_rate <= curr_low_rate && curr_low_rate >= 0.5 {
|
||||||
|
let prev_dev = 0.5 - prev_low_rate;
|
||||||
|
let curr_dev = curr_low_rate - 0.5;
|
||||||
|
let selected_idx = if prev_dev < curr_dev { curr_dly - 1 } else { curr_dly };
|
||||||
|
|
||||||
|
// The setup setup/hold calibration timing (even with
|
||||||
|
// tolerance) might be invalid in other lanes due to skew.
|
||||||
|
// 5 taps is very conservative, generally it is 1 or 2
|
||||||
|
if selected_idx < 5 {
|
||||||
|
prev = None;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
best_dly = Some(selected_idx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only rising slope from <= 0.5 can result in a rising low rate
|
||||||
|
// crossover at 50%.
|
||||||
|
if curr_low_rate <= 0.5 {
|
||||||
|
prev = Some(curr_low_rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if best_dly.is_none() {
|
||||||
|
error!("setup/hold timing calibration failed, retry in 1s...");
|
||||||
|
timer.delay_us(1_000_000);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let best_dly = best_dly.unwrap();
|
||||||
|
|
||||||
|
apply_delay(best_dly, timer);
|
||||||
|
let mut delay_list = [best_dly; 4];
|
||||||
|
|
||||||
|
// Assign delay for other lanes
|
||||||
|
for lane_no in 1..=3 {
|
||||||
|
select_lane(lane_no as u8);
|
||||||
|
|
||||||
|
let mut min_deviation = 0.5;
|
||||||
|
let mut min_idx = 0;
|
||||||
|
for dly_delta in -3..=3 {
|
||||||
|
let index = (best_dly as isize + dly_delta) as u8;
|
||||||
|
let low_rate = read_align(index, timer);
|
||||||
|
// abs() from f32 is not available in core library
|
||||||
|
let deviation = if low_rate < 0.5 { 0.5 - low_rate } else { low_rate - 0.5 };
|
||||||
|
|
||||||
|
if deviation < min_deviation {
|
||||||
|
min_deviation = deviation;
|
||||||
|
min_idx = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_delay(min_idx, timer);
|
||||||
|
delay_list[lane_no] = min_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("setup/hold timing calibration: {:?}", delay_list);
|
||||||
|
|
||||||
|
SerdesConfig { delay: delay_list }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_align(dly: u8, timer: &mut GlobalTimer) -> f32 {
|
||||||
|
unsafe {
|
||||||
|
apply_delay(dly, timer);
|
||||||
|
pl::csr::eem_transceiver::counter_reset_write(1);
|
||||||
|
|
||||||
|
pl::csr::eem_transceiver::counter_enable_write(1);
|
||||||
|
timer.delay_us(2000);
|
||||||
|
pl::csr::eem_transceiver::counter_enable_write(0);
|
||||||
|
|
||||||
|
let (high, low) = (
|
||||||
|
pl::csr::eem_transceiver::counter_high_count_read(),
|
||||||
|
pl::csr::eem_transceiver::counter_low_count_read(),
|
||||||
|
);
|
||||||
|
if pl::csr::eem_transceiver::counter_overflow_read() == 1 {
|
||||||
|
panic!("Unexpected phase detector counter overflow");
|
||||||
|
}
|
||||||
|
|
||||||
|
low as f32 / (low + high) as f32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn align_comma(timer: &mut GlobalTimer) {
|
||||||
|
loop {
|
||||||
|
for slip in 1..=10 {
|
||||||
|
// The soft transceiver has 2 8b10b decoders, which receives lane
|
||||||
|
// 0/1 and lane 2/3 respectively. The decoder are time-multiplexed
|
||||||
|
// to decode exactly 1 lane each sysclk cycle.
|
||||||
|
//
|
||||||
|
// The decoder decodes lane 0/2 data on odd sysclk cycles, buffer
|
||||||
|
// on even cycles, and vice versa for lane 1/3. Data/Clock latency
|
||||||
|
// could change timing. The extend bit flips the decoding timing,
|
||||||
|
// so lane 0/2 data are decoded on even cycles, and lane 1/3 data
|
||||||
|
// are decoded on odd cycles.
|
||||||
|
//
|
||||||
|
// This is needed because transmitting/receiving a 8b10b character
|
||||||
|
// takes 2 sysclk cycles. Adjusting bitslip only via ISERDES
|
||||||
|
// limits the range to 1 cycle. The wordslip bit extends the range
|
||||||
|
// to 2 sysclk cycles.
|
||||||
|
pl::csr::eem_transceiver::wordslip_write((slip > 5) as u8);
|
||||||
|
|
||||||
|
// Apply a double bitslip since the ISERDES is 2x oversampled.
|
||||||
|
// Bitslip is used for comma alignment purposes once setup/hold
|
||||||
|
// timing is met.
|
||||||
|
pl::csr::eem_transceiver::bitslip_write(1);
|
||||||
|
pl::csr::eem_transceiver::bitslip_write(1);
|
||||||
|
timer.delay_us(1);
|
||||||
|
|
||||||
|
pl::csr::eem_transceiver::comma_align_reset_write(1);
|
||||||
|
timer.delay_us(100);
|
||||||
|
|
||||||
|
if pl::csr::eem_transceiver::comma_read() == 1 {
|
||||||
|
debug!("comma alignment completed after {} bitslips", slip);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error!("comma alignment failed, retrying in 1s...");
|
||||||
|
timer.delay_us(1_000_000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(timer: &mut GlobalTimer, cfg: &Config) {
|
||||||
|
for trx_no in 0..pl::csr::CONFIG_EEM_DRTIO_COUNT {
|
||||||
|
unsafe {
|
||||||
|
pl::csr::eem_transceiver::transceiver_sel_write(trx_no as u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = format!("eem_drtio_delay{}", trx_no);
|
||||||
|
|
||||||
|
let cfg_read = cfg.read(&key);
|
||||||
|
match cfg_read {
|
||||||
|
Ok(record) => {
|
||||||
|
info!("loading calibrated timing values from sd card");
|
||||||
|
unsafe {
|
||||||
|
apply_config(&*(record.as_ptr() as *const SerdesConfig), timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
info!("calibrating...");
|
||||||
|
let config = unsafe { assign_delay(timer) };
|
||||||
|
|
||||||
|
match cfg.write(&key, config.as_bytes().to_vec()) {
|
||||||
|
Ok(()) => {
|
||||||
|
info!("storing calibration timing values into sd card");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!(
|
||||||
|
"calibration successful but calibration timing values cannot be stored into sd card. \
|
||||||
|
Error:{}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
align_comma(timer);
|
||||||
|
pl::csr::eem_transceiver::rx_ready_write(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,163 @@
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
use crate::pl::csr;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Copy)]
|
||||||
|
enum State {
|
||||||
|
Reset,
|
||||||
|
ExitReset,
|
||||||
|
Lock,
|
||||||
|
Align,
|
||||||
|
Watch,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct Info {
|
||||||
|
state: State,
|
||||||
|
frame_size: (u16, u16),
|
||||||
|
}
|
||||||
|
|
||||||
|
static mut INFO: [Info; csr::GRABBER_LEN] = [Info {
|
||||||
|
state: State::Reset,
|
||||||
|
frame_size: (0, 0),
|
||||||
|
}; csr::GRABBER_LEN];
|
||||||
|
|
||||||
|
fn get_pll_reset(g: usize) -> bool {
|
||||||
|
unsafe { (csr::GRABBER[g].pll_reset_read)() != 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_pll_reset(g: usize, reset: bool) {
|
||||||
|
let val = if reset { 1 } else { 0 };
|
||||||
|
unsafe { (csr::GRABBER[g].pll_reset_write)(val) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pll_locked(g: usize) -> bool {
|
||||||
|
unsafe { (csr::GRABBER[g].pll_locked_read)() != 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clock_pattern_ok(g: usize) -> bool {
|
||||||
|
unsafe { (csr::GRABBER[g].clk_sampled_read)() == 0b1100011 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clock_pattern_ok_filter(g: usize) -> bool {
|
||||||
|
for _ in 0..128 {
|
||||||
|
if !clock_pattern_ok(g) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn phase_shift(g: usize, direction: u8) {
|
||||||
|
unsafe {
|
||||||
|
(csr::GRABBER[g].phase_shift_write)(direction);
|
||||||
|
while (csr::GRABBER[g].phase_shift_done_read)() == 0 {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clock_align(g: usize) -> bool {
|
||||||
|
while clock_pattern_ok_filter(g) {
|
||||||
|
phase_shift(g, 1);
|
||||||
|
}
|
||||||
|
phase_shift(g, 1);
|
||||||
|
|
||||||
|
let mut count = 0;
|
||||||
|
while !clock_pattern_ok_filter(g) {
|
||||||
|
phase_shift(g, 1);
|
||||||
|
count += 1;
|
||||||
|
if count > 1024 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut window = 1;
|
||||||
|
phase_shift(g, 1);
|
||||||
|
while clock_pattern_ok_filter(g) {
|
||||||
|
phase_shift(g, 1);
|
||||||
|
window += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0..window / 2 {
|
||||||
|
phase_shift(g, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_last_pixels(g: usize) -> (u16, u16) {
|
||||||
|
unsafe { ((csr::GRABBER[g].last_x_read)(), (csr::GRABBER[g].last_y_read)()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_video_clock(g: usize) -> u32 {
|
||||||
|
let freq_count = unsafe { (csr::GRABBER[g].freq_count_read)() } as u32;
|
||||||
|
2 * freq_count * (csr::CONFIG_CLOCK_FREQUENCY / 1000) / (511 * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick() {
|
||||||
|
for g in 0..csr::GRABBER.len() {
|
||||||
|
let next = match unsafe { INFO[g].state } {
|
||||||
|
State::Reset => {
|
||||||
|
set_pll_reset(g, true);
|
||||||
|
unsafe {
|
||||||
|
INFO[g].frame_size = (0, 0);
|
||||||
|
}
|
||||||
|
State::ExitReset
|
||||||
|
}
|
||||||
|
State::ExitReset => {
|
||||||
|
if get_pll_reset(g) {
|
||||||
|
set_pll_reset(g, false);
|
||||||
|
State::Lock
|
||||||
|
} else {
|
||||||
|
State::ExitReset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
State::Lock => {
|
||||||
|
if pll_locked(g) {
|
||||||
|
info!("grabber{} locked: {}MHz", g, get_video_clock(g));
|
||||||
|
State::Align
|
||||||
|
} else {
|
||||||
|
State::Lock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
State::Align => {
|
||||||
|
if pll_locked(g) {
|
||||||
|
if clock_align(g) {
|
||||||
|
info!("grabber{} alignment success", g);
|
||||||
|
State::Watch
|
||||||
|
} else {
|
||||||
|
info!("grabber{} alignment failure", g);
|
||||||
|
State::Reset
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info!("grabber{} lock lost", g);
|
||||||
|
State::Reset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
State::Watch => {
|
||||||
|
if pll_locked(g) {
|
||||||
|
if clock_pattern_ok(g) {
|
||||||
|
let last_xy = get_last_pixels(g);
|
||||||
|
if last_xy != unsafe { INFO[g].frame_size } {
|
||||||
|
// x capture is on ~LVAL which is after
|
||||||
|
// the last increment on DVAL
|
||||||
|
// y capture is on ~FVAL which coincides with the
|
||||||
|
// last increment on ~LVAL
|
||||||
|
info!("grabber{} frame size: {}x{}", g, last_xy.0, last_xy.1 + 1);
|
||||||
|
unsafe { INFO[g].frame_size = last_xy }
|
||||||
|
}
|
||||||
|
State::Watch
|
||||||
|
} else {
|
||||||
|
info!("grabber{} alignment lost", g);
|
||||||
|
State::Reset
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info!("grabber{} lock lost", g);
|
||||||
|
State::Reset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
INFO[g].state = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,10 @@ pub mod mem;
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[path = "../../../build/pl.rs"]
|
#[path = "../../../build/pl.rs"]
|
||||||
pub mod pl;
|
pub mod pl;
|
||||||
|
#[cfg(has_drtio_eem)]
|
||||||
|
pub mod drtio_eem;
|
||||||
|
#[cfg(has_grabber)]
|
||||||
|
pub mod grabber;
|
||||||
#[cfg(has_si5324)]
|
#[cfg(has_si5324)]
|
||||||
pub mod si5324;
|
pub mod si5324;
|
||||||
|
|
||||||
|
|
|
@ -103,6 +103,7 @@ pub enum Message {
|
||||||
SubkernelMsgRecvRequest {
|
SubkernelMsgRecvRequest {
|
||||||
id: u32,
|
id: u32,
|
||||||
timeout: u64,
|
timeout: u64,
|
||||||
|
tags: Vec<u8>,
|
||||||
},
|
},
|
||||||
#[cfg(has_drtio)]
|
#[cfg(has_drtio)]
|
||||||
SubkernelMsgRecvReply {
|
SubkernelMsgRecvReply {
|
||||||
|
|
|
@ -10,7 +10,7 @@ use crate::{eh_artiq, rpc::send_args};
|
||||||
fn rpc_send_common(is_async: bool, service: u32, tag: &CSlice<u8>, data: *const *const ()) {
|
fn rpc_send_common(is_async: bool, service: u32, tag: &CSlice<u8>, data: *const *const ()) {
|
||||||
let core1_tx = unsafe { KERNEL_CHANNEL_1TO0.as_mut().unwrap() };
|
let core1_tx = unsafe { KERNEL_CHANNEL_1TO0.as_mut().unwrap() };
|
||||||
let mut buffer = Vec::<u8>::new();
|
let mut buffer = Vec::<u8>::new();
|
||||||
send_args(&mut buffer, service, tag.as_ref(), data).expect("RPC encoding failed");
|
send_args(&mut buffer, service, tag.as_ref(), data, true).expect("RPC encoding failed");
|
||||||
core1_tx.send(Message::RpcSend { is_async, data: buffer });
|
core1_tx.send(Message::RpcSend { is_async, data: buffer });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ pub extern "C" fn await_finish(id: u32, timeout: u64) {
|
||||||
|
|
||||||
pub extern "C" fn send_message(id: u32, count: u8, tag: &CSlice<u8>, data: *const *const ()) {
|
pub extern "C" fn send_message(id: u32, count: u8, tag: &CSlice<u8>, data: *const *const ()) {
|
||||||
let mut buffer = Vec::<u8>::new();
|
let mut buffer = Vec::<u8>::new();
|
||||||
send_args(&mut buffer, 0, tag.as_ref(), data).expect("RPC encoding failed");
|
send_args(&mut buffer, 0, tag.as_ref(), data, false).expect("RPC encoding failed");
|
||||||
// overwrite service tag, include how many tags are in the message
|
// overwrite service tag, include how many tags are in the message
|
||||||
buffer[3] = count;
|
buffer[3] = count;
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -68,7 +68,7 @@ pub extern "C" fn send_message(id: u32, count: u8, tag: &CSlice<u8>, data: *cons
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub extern "C" fn await_message(id: u32, timeout: u64, min: u8, max: u8) {
|
pub extern "C" fn await_message(id: u32, timeout: u64, tags: &CSlice<u8>, min: u8, max: u8) {
|
||||||
unsafe {
|
unsafe {
|
||||||
KERNEL_CHANNEL_1TO0
|
KERNEL_CHANNEL_1TO0
|
||||||
.as_mut()
|
.as_mut()
|
||||||
|
@ -76,6 +76,7 @@ pub extern "C" fn await_message(id: u32, timeout: u64, min: u8, max: u8) {
|
||||||
.send(Message::SubkernelMsgRecvRequest {
|
.send(Message::SubkernelMsgRecvRequest {
|
||||||
id: id,
|
id: id,
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
|
tags: tags.as_ref().to_vec(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
match unsafe { KERNEL_CHANNEL_0TO1.as_mut().unwrap() }.recv() {
|
match unsafe { KERNEL_CHANNEL_0TO1.as_mut().unwrap() }.recv() {
|
||||||
|
|
|
@ -175,7 +175,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recv_return<F, R>(reader: &mut R, tag_bytes: &[u8], data: *mut (), alloc: &mut F) -> Result<(), Error>
|
pub fn recv_return<'a, F, R>(reader: &mut R, tag_bytes: &'a [u8], data: *mut (), alloc: &mut F) -> Result<&'a [u8], Error>
|
||||||
where
|
where
|
||||||
F: FnMut(usize) -> *mut (),
|
F: FnMut(usize) -> *mut (),
|
||||||
R: Read + ?Sized,
|
R: Read + ?Sized,
|
||||||
|
@ -187,12 +187,14 @@ where
|
||||||
let mut data = data;
|
let mut data = data;
|
||||||
unsafe { recv_value(reader, tag, &mut data, alloc)? };
|
unsafe { recv_value(reader, tag, &mut data, alloc)? };
|
||||||
|
|
||||||
Ok(())
|
Ok(it.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn send_elements<W>(writer: &mut W, elt_tag: Tag, length: usize, data: *const ()) -> Result<(), Error>
|
unsafe fn send_elements<W>(writer: &mut W, elt_tag: Tag, length: usize, data: *const (), write_tags: bool) -> Result<(), Error>
|
||||||
where W: Write + ?Sized {
|
where W: Write + ?Sized {
|
||||||
writer.write_u8(elt_tag.as_u8())?;
|
if write_tags {
|
||||||
|
writer.write_u8(elt_tag.as_u8())?;
|
||||||
|
}
|
||||||
match elt_tag {
|
match elt_tag {
|
||||||
// we cannot use NativeEndian::from_slice_i32 as the data is not mutable,
|
// we cannot use NativeEndian::from_slice_i32 as the data is not mutable,
|
||||||
// and that is not needed as the data is already in native endian
|
// and that is not needed as the data is already in native endian
|
||||||
|
@ -211,14 +213,14 @@ where W: Write + ?Sized {
|
||||||
_ => {
|
_ => {
|
||||||
let mut data = data;
|
let mut data = data;
|
||||||
for _ in 0..length {
|
for _ in 0..length {
|
||||||
send_value(writer, elt_tag, &mut data)?;
|
send_value(writer, elt_tag, &mut data, write_tags)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn send_value<W>(writer: &mut W, tag: Tag, data: &mut *const ()) -> Result<(), Error>
|
unsafe fn send_value<W>(writer: &mut W, tag: Tag, data: &mut *const (), write_tags: bool) -> Result<(), Error>
|
||||||
where W: Write + ?Sized {
|
where W: Write + ?Sized {
|
||||||
macro_rules! consume_value {
|
macro_rules! consume_value {
|
||||||
($ty:ty, | $ptr:ident | $map:expr) => {{
|
($ty:ty, | $ptr:ident | $map:expr) => {{
|
||||||
|
@ -228,7 +230,9 @@ where W: Write + ?Sized {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.write_u8(tag.as_u8())?;
|
if write_tags {
|
||||||
|
writer.write_u8(tag.as_u8())?;
|
||||||
|
}
|
||||||
match tag {
|
match tag {
|
||||||
Tag::None => Ok(()),
|
Tag::None => Ok(()),
|
||||||
Tag::Bool => consume_value!(u8, |ptr| writer.write_u8(*ptr)),
|
Tag::Bool => consume_value!(u8, |ptr| writer.write_u8(*ptr)),
|
||||||
|
@ -240,12 +244,14 @@ where W: Write + ?Sized {
|
||||||
Tag::Bytes | Tag::ByteArray => consume_value!(CSlice<u8>, |ptr| writer.write_bytes((*ptr).as_ref())),
|
Tag::Bytes | Tag::ByteArray => consume_value!(CSlice<u8>, |ptr| writer.write_bytes((*ptr).as_ref())),
|
||||||
Tag::Tuple(it, arity) => {
|
Tag::Tuple(it, arity) => {
|
||||||
let mut it = it.clone();
|
let mut it = it.clone();
|
||||||
writer.write_u8(arity)?;
|
if write_tags {
|
||||||
|
writer.write_u8(arity)?;
|
||||||
|
}
|
||||||
let mut max_alignment = 0;
|
let mut max_alignment = 0;
|
||||||
for _ in 0..arity {
|
for _ in 0..arity {
|
||||||
let tag = it.next().expect("truncated tag");
|
let tag = it.next().expect("truncated tag");
|
||||||
max_alignment = core::cmp::max(max_alignment, tag.alignment());
|
max_alignment = core::cmp::max(max_alignment, tag.alignment());
|
||||||
send_value(writer, tag, data)?
|
send_value(writer, tag, data, write_tags)?
|
||||||
}
|
}
|
||||||
*data = round_up_const(*data, max_alignment);
|
*data = round_up_const(*data, max_alignment);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -260,11 +266,13 @@ where W: Write + ?Sized {
|
||||||
let length = (**ptr).length as usize;
|
let length = (**ptr).length as usize;
|
||||||
writer.write_u32((*ptr).length)?;
|
writer.write_u32((*ptr).length)?;
|
||||||
let tag = it.clone().next().expect("truncated tag");
|
let tag = it.clone().next().expect("truncated tag");
|
||||||
send_elements(writer, tag, length, (**ptr).elements)
|
send_elements(writer, tag, length, (**ptr).elements, write_tags)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Tag::Array(it, num_dims) => {
|
Tag::Array(it, num_dims) => {
|
||||||
writer.write_u8(num_dims)?;
|
if write_tags {
|
||||||
|
writer.write_u8(num_dims)?;
|
||||||
|
}
|
||||||
consume_value!(*const (), |buffer| {
|
consume_value!(*const (), |buffer| {
|
||||||
let elt_tag = it.clone().next().expect("truncated tag");
|
let elt_tag = it.clone().next().expect("truncated tag");
|
||||||
|
|
||||||
|
@ -276,14 +284,14 @@ where W: Write + ?Sized {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
let length = total_len as usize;
|
let length = total_len as usize;
|
||||||
send_elements(writer, elt_tag, length, *buffer)
|
send_elements(writer, elt_tag, length, *buffer, write_tags)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Tag::Range(it) => {
|
Tag::Range(it) => {
|
||||||
let tag = it.clone().next().expect("truncated tag");
|
let tag = it.clone().next().expect("truncated tag");
|
||||||
send_value(writer, tag, data)?;
|
send_value(writer, tag, data, write_tags)?;
|
||||||
send_value(writer, tag, data)?;
|
send_value(writer, tag, data, write_tags)?;
|
||||||
send_value(writer, tag, data)?;
|
send_value(writer, tag, data, write_tags)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Tag::Keyword(it) => {
|
Tag::Keyword(it) => {
|
||||||
|
@ -295,7 +303,7 @@ where W: Write + ?Sized {
|
||||||
writer.write_string(str::from_utf8((*ptr).name.as_ref()).unwrap())?;
|
writer.write_string(str::from_utf8((*ptr).name.as_ref()).unwrap())?;
|
||||||
let tag = it.clone().next().expect("truncated tag");
|
let tag = it.clone().next().expect("truncated tag");
|
||||||
let mut data = ptr.offset(1) as *const ();
|
let mut data = ptr.offset(1) as *const ();
|
||||||
send_value(writer, tag, &mut data)
|
send_value(writer, tag, &mut data, write_tags)
|
||||||
})
|
})
|
||||||
// Tag::Keyword never appears in composite types, so we don't have
|
// Tag::Keyword never appears in composite types, so we don't have
|
||||||
// to accurately advance data.
|
// to accurately advance data.
|
||||||
|
@ -310,7 +318,7 @@ where W: Write + ?Sized {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_args<W>(writer: &mut W, service: u32, tag_bytes: &[u8], data: *const *const ()) -> Result<(), Error>
|
pub fn send_args<W>(writer: &mut W, service: u32, tag_bytes: &[u8], data: *const *const (), write_tags: bool) -> Result<(), Error>
|
||||||
where W: Write + ?Sized {
|
where W: Write + ?Sized {
|
||||||
let (arg_tags_bytes, return_tag_bytes) = split_tag(tag_bytes);
|
let (arg_tags_bytes, return_tag_bytes) = split_tag(tag_bytes);
|
||||||
|
|
||||||
|
@ -322,7 +330,7 @@ where W: Write + ?Sized {
|
||||||
for index in 0.. {
|
for index in 0.. {
|
||||||
if let Some(arg_tag) = args_it.next() {
|
if let Some(arg_tag) = args_it.next() {
|
||||||
let mut data = unsafe { *data.offset(index) };
|
let mut data = unsafe { *data.offset(index) };
|
||||||
unsafe { send_value(writer, arg_tag, &mut data)? };
|
unsafe { send_value(writer, arg_tag, &mut data, write_tags)? };
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -450,7 +458,7 @@ pub mod tag {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct TagIterator<'a> {
|
pub struct TagIterator<'a> {
|
||||||
data: &'a [u8],
|
pub data: &'a [u8],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TagIterator<'a> {
|
impl<'a> TagIterator<'a> {
|
||||||
|
|
|
@ -5,7 +5,7 @@ use core_io::Error as IoError;
|
||||||
use cslice::CSlice;
|
use cslice::CSlice;
|
||||||
use futures::{future::FutureExt, select_biased};
|
use futures::{future::FutureExt, select_biased};
|
||||||
#[cfg(has_drtio)]
|
#[cfg(has_drtio)]
|
||||||
use io::{Cursor, ProtoRead};
|
use io::Cursor;
|
||||||
#[cfg(has_drtio)]
|
#[cfg(has_drtio)]
|
||||||
use ksupport::rpc;
|
use ksupport::rpc;
|
||||||
use ksupport::{kernel, resolve_channel_name};
|
use ksupport::{kernel, resolve_channel_name};
|
||||||
|
@ -451,7 +451,7 @@ async fn handle_run_kernel(
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
#[cfg(has_drtio)]
|
#[cfg(has_drtio)]
|
||||||
kernel::Message::SubkernelMsgRecvRequest { id, timeout } => {
|
kernel::Message::SubkernelMsgRecvRequest { id, timeout, tags } => {
|
||||||
let message_received = subkernel::message_await(id, timeout, timer).await;
|
let message_received = subkernel::message_await(id, timeout, timer).await;
|
||||||
let (status, count) = match message_received {
|
let (status, count) = match message_received {
|
||||||
Ok(ref message) => (kernel::SubkernelStatus::NoError, message.count),
|
Ok(ref message) => (kernel::SubkernelStatus::NoError, message.count),
|
||||||
|
@ -471,7 +471,7 @@ async fn handle_run_kernel(
|
||||||
if let Ok(message) = message_received {
|
if let Ok(message) = message_received {
|
||||||
// receive code almost identical to RPC recv, except we are not reading from a stream
|
// receive code almost identical to RPC recv, except we are not reading from a stream
|
||||||
let mut reader = Cursor::new(message.data);
|
let mut reader = Cursor::new(message.data);
|
||||||
let mut tag: [u8; 1] = [message.tag];
|
let mut current_tags: &[u8] = &tags;
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
loop {
|
loop {
|
||||||
// kernel has to consume all arguments in the whole message
|
// kernel has to consume all arguments in the whole message
|
||||||
|
@ -479,7 +479,7 @@ async fn handle_run_kernel(
|
||||||
kernel::Message::RpcRecvRequest(slot) => slot,
|
kernel::Message::RpcRecvRequest(slot) => slot,
|
||||||
other => panic!("expected root value slot from core1, not {:?}", other),
|
other => panic!("expected root value slot from core1, not {:?}", other),
|
||||||
};
|
};
|
||||||
rpc::recv_return(&mut reader, &tag, slot, &mut |size| {
|
let remaining_tags = rpc::recv_return(&mut reader, ¤t_tags, slot, &mut |size| {
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
0 as *mut ()
|
0 as *mut ()
|
||||||
} else {
|
} else {
|
||||||
|
@ -500,7 +500,7 @@ async fn handle_run_kernel(
|
||||||
.await;
|
.await;
|
||||||
i += 1;
|
i += 1;
|
||||||
if i < count {
|
if i < count {
|
||||||
tag[0] = reader.read_u8()?;
|
current_tags = remaining_tags;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@ use core::cell::RefCell;
|
||||||
|
|
||||||
use ksupport;
|
use ksupport;
|
||||||
use libasync::task;
|
use libasync::task;
|
||||||
|
#[cfg(has_drtio_eem)]
|
||||||
|
use libboard_artiq::drtio_eem;
|
||||||
#[cfg(feature = "target_kasli_soc")]
|
#[cfg(feature = "target_kasli_soc")]
|
||||||
use libboard_artiq::io_expander;
|
use libboard_artiq::io_expander;
|
||||||
use libboard_artiq::{identifier_read, logger, pl};
|
use libboard_artiq::{identifier_read, logger, pl};
|
||||||
|
@ -55,6 +57,21 @@ async fn io_expanders_service(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(has_grabber)]
|
||||||
|
mod grabber {
|
||||||
|
use libasync::delay;
|
||||||
|
use libboard_artiq::grabber;
|
||||||
|
use libboard_zynq::time::Milliseconds;
|
||||||
|
use crate::GlobalTimer;
|
||||||
|
pub async fn grabber_thread(timer: GlobalTimer) {
|
||||||
|
let mut countdown = timer.countdown();
|
||||||
|
loop {
|
||||||
|
grabber::tick();
|
||||||
|
delay(&mut countdown, Milliseconds(200)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static mut LOG_BUFFER: [u8; 1 << 17] = [0; 1 << 17];
|
static mut LOG_BUFFER: [u8; 1 << 17] = [0; 1 << 17];
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
@ -109,6 +126,12 @@ pub fn main_core0() {
|
||||||
|
|
||||||
rtio_clocking::init(&mut timer, &cfg);
|
rtio_clocking::init(&mut timer, &cfg);
|
||||||
|
|
||||||
|
#[cfg(has_drtio_eem)]
|
||||||
|
drtio_eem::init(&mut timer, &cfg);
|
||||||
|
|
||||||
|
#[cfg(has_grabber)]
|
||||||
|
task::spawn(grabber::grabber_thread(timer));
|
||||||
|
|
||||||
task::spawn(ksupport::report_async_rtio_errors());
|
task::spawn(ksupport::report_async_rtio_errors());
|
||||||
|
|
||||||
#[cfg(feature = "target_kasli_soc")]
|
#[cfg(feature = "target_kasli_soc")]
|
||||||
|
|
|
@ -104,6 +104,9 @@ fn init_drtio(timer: &mut GlobalTimer) {
|
||||||
unsafe {
|
unsafe {
|
||||||
pl::csr::rtio_core::reset_phy_write(1);
|
pl::csr::rtio_core::reset_phy_write(1);
|
||||||
pl::csr::gt_drtio::txenable_write(0xffffffffu32 as _);
|
pl::csr::gt_drtio::txenable_write(0xffffffffu32 as _);
|
||||||
|
|
||||||
|
#[cfg(has_drtio_eem)]
|
||||||
|
pl::csr::eem_transceiver::txenable_write(0xffffffffu32 as _);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -212,7 +212,6 @@ pub async fn await_finish(
|
||||||
pub struct Message {
|
pub struct Message {
|
||||||
from_id: u32,
|
from_id: u32,
|
||||||
pub count: u8,
|
pub count: u8,
|
||||||
pub tag: u8,
|
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,8 +235,7 @@ pub async fn message_handle_incoming(id: u32, last: bool, length: usize, data: &
|
||||||
Message {
|
Message {
|
||||||
from_id: id,
|
from_id: id,
|
||||||
count: data[0],
|
count: data[0],
|
||||||
tag: data[1],
|
data: data[1..length].to_vec(),
|
||||||
data: data[2..length].to_vec(),
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ extern crate alloc;
|
||||||
use analyzer::Analyzer;
|
use analyzer::Analyzer;
|
||||||
use dma::Manager as DmaManager;
|
use dma::Manager as DmaManager;
|
||||||
use embedded_hal::blocking::delay::DelayUs;
|
use embedded_hal::blocking::delay::DelayUs;
|
||||||
|
#[cfg(has_grabber)]
|
||||||
|
use libboard_artiq::grabber;
|
||||||
#[cfg(feature = "target_kasli_soc")]
|
#[cfg(feature = "target_kasli_soc")]
|
||||||
use libboard_artiq::io_expander;
|
use libboard_artiq::io_expander;
|
||||||
#[cfg(has_si5324)]
|
#[cfg(has_si5324)]
|
||||||
|
@ -698,6 +700,8 @@ fn hardware_tick(ts: &mut u64, timer: &mut GlobalTimer) {
|
||||||
if now > ts_ms {
|
if now > ts_ms {
|
||||||
ts_ms = now + Milliseconds(200);
|
ts_ms = now + Milliseconds(200);
|
||||||
*ts = ts_ms.0;
|
*ts = ts_ms.0;
|
||||||
|
#[cfg(has_grabber)]
|
||||||
|
grabber::tick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ use core::{cmp::min, option::NoneError, slice, str};
|
||||||
|
|
||||||
use core_io::{Error as IoError, Write};
|
use core_io::{Error as IoError, Write};
|
||||||
use cslice::AsCSlice;
|
use cslice::AsCSlice;
|
||||||
use io::{Cursor, ProtoRead, ProtoWrite};
|
use io::{Cursor, ProtoWrite};
|
||||||
use ksupport::{eh_artiq, kernel, rpc};
|
use ksupport::{eh_artiq, kernel, rpc};
|
||||||
use libboard_artiq::{drtioaux_proto::{MASTER_PAYLOAD_MAX_SIZE, SAT_PAYLOAD_MAX_SIZE},
|
use libboard_artiq::{drtioaux_proto::{MASTER_PAYLOAD_MAX_SIZE, SAT_PAYLOAD_MAX_SIZE},
|
||||||
pl::csr};
|
pl::csr};
|
||||||
|
@ -14,12 +14,12 @@ use libboard_zynq::{time::Milliseconds, timer::GlobalTimer};
|
||||||
use libcortex_a9::sync_channel::Receiver;
|
use libcortex_a9::sync_channel::Receiver;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
enum KernelState {
|
enum KernelState {
|
||||||
Absent,
|
Absent,
|
||||||
Loaded,
|
Loaded,
|
||||||
Running,
|
Running,
|
||||||
MsgAwait(Milliseconds),
|
MsgAwait(Milliseconds, Vec<u8>),
|
||||||
MsgSending,
|
MsgSending,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +66,6 @@ pub struct Sliceable {
|
||||||
/* represents interkernel messages */
|
/* represents interkernel messages */
|
||||||
struct Message {
|
struct Message {
|
||||||
count: u8,
|
count: u8,
|
||||||
tag: u8,
|
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,8 +182,7 @@ impl MessageManager {
|
||||||
None => {
|
None => {
|
||||||
self.in_buffer = Some(Message {
|
self.in_buffer = Some(Message {
|
||||||
count: data[0],
|
count: data[0],
|
||||||
tag: data[1],
|
data: data[1..length].to_vec(),
|
||||||
data: data[2..length].to_vec(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -509,9 +507,9 @@ impl<'a> Manager<'_> {
|
||||||
self.session.messages.accept_outgoing(data)?;
|
self.session.messages.accept_outgoing(data)?;
|
||||||
self.session.kernel_state = KernelState::MsgSending;
|
self.session.kernel_state = KernelState::MsgSending;
|
||||||
}
|
}
|
||||||
kernel::Message::SubkernelMsgRecvRequest { id: _, timeout } => {
|
kernel::Message::SubkernelMsgRecvRequest { id: _, timeout, tags } => {
|
||||||
let max_time = timer.get_time() + Milliseconds(timeout);
|
let max_time = timer.get_time() + Milliseconds(timeout);
|
||||||
self.session.kernel_state = KernelState::MsgAwait(max_time);
|
self.session.kernel_state = KernelState::MsgAwait(max_time, tags);
|
||||||
}
|
}
|
||||||
kernel::Message::UpDestinationsRequest(destination) => {
|
kernel::Message::UpDestinationsRequest(destination) => {
|
||||||
self.control
|
self.control
|
||||||
|
@ -526,9 +524,9 @@ impl<'a> Manager<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_external_messages(&mut self, timer: GlobalTimer) -> Result<(), Error> {
|
fn process_external_messages(&mut self, timer: GlobalTimer) -> Result<(), Error> {
|
||||||
match self.session.kernel_state {
|
match &self.session.kernel_state {
|
||||||
KernelState::MsgAwait(timeout) => {
|
KernelState::MsgAwait(timeout, tags) => {
|
||||||
if timer.get_time() > timeout {
|
if timer.get_time() > *timeout {
|
||||||
self.control.tx.send(kernel::Message::SubkernelMsgRecvReply {
|
self.control.tx.send(kernel::Message::SubkernelMsgRecvReply {
|
||||||
status: kernel::SubkernelStatus::Timeout,
|
status: kernel::SubkernelStatus::Timeout,
|
||||||
count: 0,
|
count: 0,
|
||||||
|
@ -541,8 +539,9 @@ impl<'a> Manager<'_> {
|
||||||
status: kernel::SubkernelStatus::NoError,
|
status: kernel::SubkernelStatus::NoError,
|
||||||
count: message.count,
|
count: message.count,
|
||||||
});
|
});
|
||||||
|
let tags = tags.clone();
|
||||||
self.session.kernel_state = KernelState::Running;
|
self.session.kernel_state = KernelState::Running;
|
||||||
self.pass_message_to_kernel(&message, timer)
|
self.pass_message_to_kernel(&message, tags, timer)
|
||||||
} else {
|
} else {
|
||||||
Err(Error::AwaitingMessage)
|
Err(Error::AwaitingMessage)
|
||||||
}
|
}
|
||||||
|
@ -560,9 +559,9 @@ impl<'a> Manager<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pass_message_to_kernel(&mut self, message: &Message, timer: GlobalTimer) -> Result<(), Error> {
|
fn pass_message_to_kernel(&mut self, message: &Message, tags: Vec<u8>, timer: GlobalTimer) -> Result<(), Error> {
|
||||||
let mut reader = Cursor::new(&message.data);
|
let mut reader = Cursor::new(&message.data);
|
||||||
let mut tag: [u8; 1] = [message.tag];
|
let mut current_tags: &[u8] = &tags;
|
||||||
let mut i = message.count;
|
let mut i = message.count;
|
||||||
loop {
|
loop {
|
||||||
let slot = match recv_w_timeout(&mut self.control.rx, timer, 100)? {
|
let slot = match recv_w_timeout(&mut self.control.rx, timer, 100)? {
|
||||||
|
@ -571,7 +570,7 @@ impl<'a> Manager<'_> {
|
||||||
};
|
};
|
||||||
let mut exception: Option<Sliceable> = None;
|
let mut exception: Option<Sliceable> = None;
|
||||||
let mut unexpected: Option<String> = None;
|
let mut unexpected: Option<String> = None;
|
||||||
rpc::recv_return(&mut reader, &tag, slot, &mut |size| {
|
let remaining_tags = rpc::recv_return(&mut reader, current_tags, slot, &mut |size| {
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
0 as *mut ()
|
0 as *mut ()
|
||||||
} else {
|
} else {
|
||||||
|
@ -610,8 +609,7 @@ impl<'a> Manager<'_> {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
// update the tag for next read
|
current_tags = remaining_tags;
|
||||||
tag[0] = reader.read_u8()?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Reference in New Issue