From 1bb7e9ceefb292c3d8e88b463ec598cb9a0f0b7c Mon Sep 17 00:00:00 2001 From: occheung Date: Wed, 13 Sep 2023 16:43:53 -0700 Subject: [PATCH] shuttler: support pre-DAC gain & offset --- artiq/gateware/shuttler.py | 67 +++++++++++++++++++++++++++++++---- artiq/gateware/targets/efc.py | 34 ++++++++++++++++++ 2 files changed, 95 insertions(+), 6 deletions(-) diff --git a/artiq/gateware/shuttler.py b/artiq/gateware/shuttler.py index 90e4eff68..1eb2221bf 100644 --- a/artiq/gateware/shuttler.py +++ b/artiq/gateware/shuttler.py @@ -100,11 +100,16 @@ class Dac(Module): data (Signal[16]): Output value to be send to the DAC. clear (Signal): Clear accumulated phase offset when loading a new waveform. Input. + gain (Signal[16]): Output value gain. The gain signal represents the + decimal part os the gain in 2's complement. + offset (Signal[16]): Output value offset. i (Endpoint[]): Coefficients of the output lines. """ def __init__(self): self.clear = Signal() self.data = Signal(16) + self.gain = Signal(16) + self.offset = Signal(16) ### @@ -113,8 +118,14 @@ class Dac(Module): Dds(self.clear), ] + # Infer signed multiplication + data_raw = Signal((14, True)) + data_buf = Signal(14) self.sync.rio += [ - self.data.eq(reduce(add, [sub.data for sub in subs])), + data_raw.eq(reduce(add, [sub.data for sub in subs])), + # Extra buffer for better DSP timing + data_buf.eq(((data_raw * Cat(self.gain, ~self.gain[-1])) + (self.offset << 16))[16:]), + self.data.eq(data_buf), ] self.i = [ sub.i for sub in subs ] @@ -229,11 +240,43 @@ class Dds(Module): class Config(Module): def __init__(self): self.clr = Signal(16, reset=0xFFFF) - self.i = Endpoint([("data", 16)]) + self.gain = [ Signal(16) for _ in range(16) ] + self.offset = [ Signal(16) for _ in range(16) ] + + reg_file = Array(self.gain + self.offset + [self.clr]) + self.i = Endpoint([ + ("data", 16), + ("addr", 7), + ]) + self.o = Endpoint([ + ("data", 16), + ]) # This introduces 1 extra latency to everything in config # See the latency/delay attributes in Volt & DDS Endpoints/rtlinks - self.sync.rio += If(self.i.stb, self.clr.eq(self.i.data)) + # + # Gain & offsets are intended for initial calibration only, latency + # is NOT adjusted to match outputs to the DAC interface + # + # Interface address bits mapping: + # 6: Read bit. Assert to read, deassert to write. + # 5: Clear bit. Assert to write clr. clr is write-only. + # 4: Gain/Offset. (De)Assert to access (Gain)Offset registers. + # 0-3: Channel selection for the Gain & Offset registers. + # + # Reading Gain / Offset register generates an RTIOInput event + self.sync.rio += [ + self.o.stb.eq(0), + If(self.i.stb, + If(~self.i.addr[6], + reg_file[self.i.addr[:6]].eq(self.i.data), + ).Else( + # clr register is unreadable, as an optimization + self.o.data.eq(reg_file[self.i.addr[:5]]), + self.o.stb.eq(1), + ) + ), + ] Phy = namedtuple("Phy", "rtlink probes overrides") @@ -258,13 +301,23 @@ class Shuttler(Module, AutoCSR): self.phys = [] self.submodules.cfg = Config() - cfg_rtl_iface = rtlink.Interface(rtlink.OInterface( - data_width=len(self.cfg.i.data), - enable_replace=False)) + cfg_rtl_iface = rtlink.Interface( + rtlink.OInterface( + data_width=len(self.cfg.i.data), + address_width=len(self.cfg.i.addr), + enable_replace=False, + ), + rtlink.IInterface( + data_width=len(self.cfg.o.data), + ), + ) self.comb += [ self.cfg.i.stb.eq(cfg_rtl_iface.o.stb), + self.cfg.i.addr.eq(cfg_rtl_iface.o.address), self.cfg.i.data.eq(cfg_rtl_iface.o.data), + cfg_rtl_iface.i.stb.eq(self.cfg.o.stb), + cfg_rtl_iface.i.data.eq(self.cfg.o.data), ] self.phys.append(Phy(cfg_rtl_iface, [], [])) @@ -277,6 +330,8 @@ class Shuttler(Module, AutoCSR): dac = Dac() self.comb += [ dac.clear.eq(self.cfg.clr[idx]), + dac.gain.eq(self.cfg.gain[idx]), + dac.offset.eq(self.cfg.offset[idx]), self.dac_interface.data[idx // 2][idx % 2].eq(dac.data) ] diff --git a/artiq/gateware/targets/efc.py b/artiq/gateware/targets/efc.py index b88f3f2b5..42a4f031b 100644 --- a/artiq/gateware/targets/efc.py +++ b/artiq/gateware/targets/efc.py @@ -13,6 +13,7 @@ from artiq.gateware.amp import AMPSoC from artiq.gateware import rtio 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 spi2 as rtio_spi from artiq.gateware.drtio.transceiver import eem_serdes from artiq.gateware.drtio.rx_synchronizer import NoRXSynchronizer from artiq.gateware.drtio import * @@ -145,6 +146,20 @@ class Satellite(BaseSoC, AMPSoC): Subsignal('data', Pins('fmc0:HB13_N fmc0:HB12_N fmc0:HB13_P fmc0:HB12_P fmc0:HB15_N fmc0:HB15_P fmc0:HB11_N fmc0:HB09_N fmc0:HB09_P fmc0:HB14_N fmc0:HB14_P fmc0:HB10_N fmc0:HB10_P fmc0:HB11_P')), Subsignal('clk', Pins('fmc0:HB06_CC_P')), IOStandard('LVCMOS18')), + ('afe_ctrl_dir', 0, Pins('fmc0:LA26_N fmc0:HB00_CC_N fmc0:HB17_CC_P'), IOStandard("LVCMOS18")), + ('afe_ctrl_oe_n', 0, Pins('fmc0:HB19_N'), IOStandard("LVCMOS18")), + ('afe_relay', 0, + Subsignal('clk', Pins('fmc0:LA02_N')), + Subsignal('mosi', Pins('fmc0:LA00_CC_N')), + Subsignal('cs_n', Pins('fmc0:LA02_P fmc0:LA01_CC_N')), + IOStandard("LVCMOS18")), + ('afe_adc_spi', 0, + Subsignal('clk', Pins('fmc0:LA29_P')), + Subsignal('mosi', Pins('fmc0:LA29_N')), + Subsignal('miso', Pins('fmc0:LA30_N')), + Subsignal('cs_n', Pins('fmc0:LA28_P')), + IOStandard("LVCMOS18")), + ('afe_adc_error_n', 0, Pins('fmc0:LA28_N'), IOStandard("LVCMOS18")), ] platform.add_extension(shuttler_io) @@ -167,6 +182,25 @@ class Satellite(BaseSoC, AMPSoC): self.csr_devices.append("shuttler") self.rtio_channels.extend(rtio.Channel.from_phy(phy) for phy in self.shuttler.phys) + afe_dir = platform.request("afe_ctrl_dir") + self.comb += afe_dir.eq(0b011) + + afe_oe = platform.request("afe_ctrl_oe_n") + self.comb += afe_oe.eq(0) + + relay_led_phy = rtio_spi.SPIMaster(self.platform.request("afe_relay")) + self.submodules += relay_led_phy + print("SHUTTLER RELAY at RTIO channel 0x{:06x}".format(len(self.rtio_channels))) + self.rtio_channels.append(rtio.Channel.from_phy(relay_led_phy)) + + adc_error_n = platform.request("afe_adc_error_n") + self.comb += adc_error_n.eq(1) + + adc_spi = rtio_spi.SPIMaster(self.platform.request("afe_adc_spi")) + self.submodules += adc_spi + print("SHUTTLER ADC at RTIO channel 0x{:06x}".format(len(self.rtio_channels))) + self.rtio_channels.append(rtio.Channel.from_phy(adc_spi)) + self.config["HAS_RTIO_LOG"] = None self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels) self.rtio_channels.append(rtio.LogChannel())