From aa0154d8e29bdaba4d8c7759a24e57dbd3746018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 21 Aug 2020 11:31:26 +0000 Subject: [PATCH 01/51] phaser: initial --- artiq/gateware/eem.py | 31 +++++- artiq/gateware/rtio/phy/fastlink.py | 120 ++++++++++++++++++++++++ artiq/gateware/rtio/phy/phaser.py | 51 ++++++++++ artiq/gateware/targets/kasli_generic.py | 7 ++ 4 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 artiq/gateware/rtio/phy/fastlink.py create mode 100644 artiq/gateware/rtio/phy/phaser.py diff --git a/artiq/gateware/eem.py b/artiq/gateware/eem.py index fba2c1b5a..35cf36de1 100644 --- a/artiq/gateware/eem.py +++ b/artiq/gateware/eem.py @@ -5,7 +5,7 @@ from migen.genlib.io import DifferentialOutput from artiq.gateware import rtio from artiq.gateware.rtio.phy import spi2, ad53xx_monitor, grabber from artiq.gateware.suservo import servo, pads as servo_pads -from artiq.gateware.rtio.phy import servo as rtservo, fastino +from artiq.gateware.rtio.phy import servo as rtservo, fastino, phaser def _eem_signal(i): @@ -613,7 +613,8 @@ class Fastino(_EEM): Subsignal("clk", Pins(_eem_pin(eem, 0, pol))), Subsignal("mosi", Pins(*(_eem_pin(eem, i, pol) for i in range(1, 7)))), - Subsignal("miso", Pins(_eem_pin(eem, 7, pol))), + Subsignal("miso", Pins(_eem_pin(eem, 7, pol)), + Misc("DIFF_TERM=TRUE")), IOStandard(iostandard), ) for pol in "pn"] @@ -626,3 +627,29 @@ class Fastino(_EEM): log2_width=0) target.submodules += phy target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4)) + + +class Phaser(_EEM): + @staticmethod + def io(eem, iostandard="LVDS_25"): + return [ + ("phaser{}_ser_{}".format(eem, pol), 0, + Subsignal("clk", Pins(_eem_pin(eem, 0, pol))), + Subsignal("mosi", Pins(*(_eem_pin(eem, i, pol) + for i in range(1, 7)))), + Subsignal("miso", Pins(_eem_pin(eem, 7, pol)), + Misc("DIFF_TERM=TRUE")), + IOStandard(iostandard), + ) for pol in "pn"] + + @classmethod + def add_std(cls, target, eem, iostandard="LVDS_25"): + cls.add_extension(target, eem, iostandard=iostandard) + + phy = phaser.Phaser(target.platform.request("phaser{}_ser_p".format(eem)), + target.platform.request("phaser{}_ser_n".format(eem))) + target.submodules += phy + target.rtio_channels.extend([ + rtio.Channel(phy.config, ififo_depth=4), + rtio.Channel(phy.data), + ]) diff --git a/artiq/gateware/rtio/phy/fastlink.py b/artiq/gateware/rtio/phy/fastlink.py new file mode 100644 index 000000000..4448ba9b1 --- /dev/null +++ b/artiq/gateware/rtio/phy/fastlink.py @@ -0,0 +1,120 @@ +from migen import * +from migen.genlib.cdc import MultiReg +from migen.genlib.io import DifferentialOutput, DifferentialInput, DDROutput +from misoc.cores.liteeth_mini.mac.crc import LiteEthMACCRCEngine + +from artiq.gateware.rtio import rtlink + + +class SerDes(Module): + # crc-12 telco: 0x80f + def __init__(self, pins, pins_n, t_clk=7, d_clk=0b1100011, + n_frame=14, n_crc=12, poly=0x80f): + """DDR fast link. + + * One word clock lane with `t_clk` period. + * Multiple data lanes at DDR speed. + * One return data lane at slower speed. + * n_frame//2 - 1 marker bits are used to provide framing. + + * `n_frame` words per frame + * `t_clk` bits per clk cycle with pattern `d_clk` + * `n_crc` CRC bits per frame + """ + n_lanes = len(pins.mosi) # number of data lanes + n_word = n_lanes*t_clk + n_body = n_word*n_frame - (n_frame//2 + 1) - n_crc + + # frame data + self.payload = Signal(n_body) + # readback data + self.readback = Signal(n_frame, reset_less=True) + # data load synchronization event + self.stb = Signal() + + # # # + + self.submodules.crc = LiteEthMACCRCEngine( + data_width=2*n_lanes, width=n_crc, polynom=poly) + + words_ = [] + j = 0 + for i in range(n_frame): # iterate over words + if i == 0: # data and checksum + k = n_word - n_crc + elif i == 1: # marker + words_.append(C(1)) + k = n_word - 1 + elif i < n_frame//2 + 2: # marker + words_.append(C(0)) + k = n_word - 1 + else: # full word + k = n_word + # append corresponding frame body bits + words_.append(self.payload[j:j + k]) + j += k + words_ = Cat(words_) + assert len(words_) == n_frame*n_word - n_crc + words = Signal(len(words_)) + self.comb += words.eq(words_) + + clk = Signal(t_clk, reset=d_clk) + clk_stb = Signal() + i_frame = Signal(max=t_clk*n_frame//2) # DDR + frame_stb = Signal() + # big shift register for clk and mosi + sr = [Signal(n_frame*t_clk - n_crc//n_lanes, reset_less=True) + for i in range(n_lanes)] + assert len(Cat(sr)) == len(words) + # DDR bits for each register + ddr_data = Cat([sri[-2] for sri in sr], [sri[-1] for sri in sr]) + self.comb += [ + # assert one cycle ahead + clk_stb.eq(~clk[0] & clk[-1]), + # double period because of DDR + frame_stb.eq(i_frame == t_clk*n_frame//2 - 1), + + # LiteETHMACCRCEngine takes data LSB first + self.crc.data[::-1].eq(ddr_data), + self.stb.eq(frame_stb & clk_stb), + ] + miso = Signal() + miso_sr = Signal(n_frame, reset_less=True) + self.sync.rio_phy += [ + # shift clock pattern by two bits each DDR cycle + clk.eq(Cat(clk[-2:], clk)), + [sri[2:].eq(sri) for sri in sr], + self.crc.last.eq(self.crc.next), + If(clk[:2] == 0, # TODO: tweak MISO sampling + miso_sr.eq(Cat(miso, miso_sr)), + ), + If(~frame_stb, + i_frame.eq(i_frame + 1), + ), + If(frame_stb & clk_stb, + i_frame.eq(0), + self.crc.last.eq(0), + # transpose, load + Cat(sr).eq(Cat(words[mm::n_lanes] for mm in range(n_lanes))), + self.readback.eq(miso_sr), + ), + If(i_frame == t_clk*n_frame//2 - 2, + # inject crc + ddr_data.eq(self.crc.next), + ), + ] + + clk_ddr = Signal() + miso0 = Signal() + self.specials += [ + DDROutput(clk[-1], clk[-2], clk_ddr, ClockSignal("rio_phy")), + DifferentialOutput(clk_ddr, pins.clk, pins_n.clk), + DifferentialInput(pins.miso, pins_n.miso, miso0), + MultiReg(miso0, miso, "rio_phy"), + ] + for sri, ddr, mp, mn in zip( + sr, Signal(n_lanes), pins.mosi, pins_n.mosi): + self.specials += [ + DDROutput(sri[-1], sri[-2], ddr, ClockSignal("rio_phy")), + DifferentialOutput(ddr, mp, mn), + ] diff --git a/artiq/gateware/rtio/phy/phaser.py b/artiq/gateware/rtio/phy/phaser.py new file mode 100644 index 000000000..4dc2197ad --- /dev/null +++ b/artiq/gateware/rtio/phy/phaser.py @@ -0,0 +1,51 @@ +from migen import * + +from artiq.gateware.rtio import rtlink +from .fastlink import SerDes + + +class Phaser(Module): + def __init__(self, pins, pins_n): + self.config = rtlink.Interface( + rtlink.OInterface(data_width=8, address_width=8, + enable_replace=False), + rtlink.IInterface(data_width=8)) + self.data = rtlink.Interface( + rtlink.OInterface(data_width=32, address_width=8, + enable_replace=True)) + + self.submodules.serializer = SerDes( + pins, pins_n, t_clk=8, d_clk=0b00001111, + n_frame=10, n_crc=6, poly=0x2f) + + header = Record([ + ("we", 1), + ("addr", 7), + ("data", 8), + ("type", 4) + ]) + n_channels = 2 + n_samples = 8 + body = [[(Signal(14), Signal(14)) for i in range(n_channels)] + for j in range(n_samples)] + assert len(Cat(header.raw_bits(), body)) == \ + len(self.serializer.payload) + self.comb += self.serializer.payload.eq(Cat(header.raw_bits(), body)) + + self.sync.rio_phy += [ + If(self.serializer.stb, + header.we.eq(0), + ), + If(self.config.o.stb, + header.we.eq(~self.config.o.address[-1]), + header.addr.eq(self.config.o.address), + header.data.eq(self.config.o.data), + header.type.eq(0), # reserved + ), + ] + + self.sync.rtio += [ + self.config.i.stb.eq(self.config.o.stb & + self.config.o.address[-1]), + self.config.i.data.eq(self.serializer.readback), + ] diff --git a/artiq/gateware/targets/kasli_generic.py b/artiq/gateware/targets/kasli_generic.py index bae599ed9..a68951c43 100755 --- a/artiq/gateware/targets/kasli_generic.py +++ b/artiq/gateware/targets/kasli_generic.py @@ -112,6 +112,12 @@ def peripheral_fastino(module, peripheral): eem.Fastino.add_std(module, peripheral["ports"][0]) +def peripheral_phaser(module, peripheral): + if len(peripheral["ports"]) != 1: + raise ValueError("wrong number of ports") + eem.Phaser.add_std(module, peripheral["ports"][0]) + + peripheral_processors = { "dio": peripheral_dio, "urukul": peripheral_urukul, @@ -122,6 +128,7 @@ peripheral_processors = { "grabber": peripheral_grabber, "mirny": peripheral_mirny, "fastino": peripheral_fastino, + "phaser": peripheral_phaser, } From a34a647ec47237afa19fec6b350cea9a0cb13cde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 21 Aug 2020 15:21:36 +0000 Subject: [PATCH 02/51] phaser: refactor fastlink --- artiq/gateware/rtio/phy/fastlink.py | 80 ++++++++++++++++------------- artiq/gateware/rtio/phy/phaser.py | 11 ++-- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/artiq/gateware/rtio/phy/fastlink.py b/artiq/gateware/rtio/phy/fastlink.py index 4448ba9b1..98602206e 100644 --- a/artiq/gateware/rtio/phy/fastlink.py +++ b/artiq/gateware/rtio/phy/fastlink.py @@ -8,7 +8,7 @@ from artiq.gateware.rtio import rtlink class SerDes(Module): # crc-12 telco: 0x80f - def __init__(self, pins, pins_n, t_clk=7, d_clk=0b1100011, + def __init__(self, n_data=8, t_clk=7, d_clk=0b1100011, n_frame=14, n_crc=12, poly=0x80f): """DDR fast link. @@ -21,9 +21,13 @@ class SerDes(Module): * `t_clk` bits per clk cycle with pattern `d_clk` * `n_crc` CRC bits per frame """ - n_lanes = len(pins.mosi) # number of data lanes + # pins + self.data = [Signal(2) for _ in range(n_data)] + n_lanes = n_data - 2 # number of data lanes n_word = n_lanes*t_clk + t_frame = t_clk*n_frame//2 n_body = n_word*n_frame - (n_frame//2 + 1) - n_crc + t_miso = 0 # miso sampling latency TODO # frame data self.payload = Signal(n_body) @@ -39,6 +43,7 @@ class SerDes(Module): words_ = [] j = 0 + # last, LSB to first, MSB for i in range(n_frame): # iterate over words if i == 0: # data and checksum k = n_word - n_crc @@ -59,62 +64,65 @@ class SerDes(Module): self.comb += words.eq(words_) clk = Signal(t_clk, reset=d_clk) - clk_stb = Signal() - i_frame = Signal(max=t_clk*n_frame//2) # DDR - frame_stb = Signal() + i = Signal(max=t_frame) # big shift register for clk and mosi - sr = [Signal(n_frame*t_clk - n_crc//n_lanes, reset_less=True) + sr = [Signal(t_frame*2 - n_crc//n_lanes, reset_less=True) for i in range(n_lanes)] assert len(Cat(sr)) == len(words) # DDR bits for each register ddr_data = Cat([sri[-2] for sri in sr], [sri[-1] for sri in sr]) self.comb += [ - # assert one cycle ahead - clk_stb.eq(~clk[0] & clk[-1]), - # double period because of DDR - frame_stb.eq(i_frame == t_clk*n_frame//2 - 1), - + self.stb.eq(i == t_frame - 1), # LiteETHMACCRCEngine takes data LSB first self.crc.data[::-1].eq(ddr_data), - self.stb.eq(frame_stb & clk_stb), ] miso = Signal() - miso_sr = Signal(n_frame, reset_less=True) + miso_sr = Signal(t_frame, reset_less=True) self.sync.rio_phy += [ - # shift clock pattern by two bits each DDR cycle + # shift everything by two bits clk.eq(Cat(clk[-2:], clk)), [sri[2:].eq(sri) for sri in sr], self.crc.last.eq(self.crc.next), - If(clk[:2] == 0, # TODO: tweak MISO sampling - miso_sr.eq(Cat(miso, miso_sr)), - ), - If(~frame_stb, - i_frame.eq(i_frame + 1), - ), - If(frame_stb & clk_stb, - i_frame.eq(0), + miso_sr.eq(Cat(miso, miso_sr)), + i.eq(i + 1), + If(self.stb, + i.eq(0), + clk.eq(clk.reset), self.crc.last.eq(0), # transpose, load Cat(sr).eq(Cat(words[mm::n_lanes] for mm in range(n_lanes))), - self.readback.eq(miso_sr), + self.readback.eq(Cat([miso_sr[int(round(t_miso + i*t_clk/2.))] + for i in range(n_frame)])), ), - If(i_frame == t_clk*n_frame//2 - 2, - # inject crc + If(i == t_frame - 2, + # inject crc for the last cycle ddr_data.eq(self.crc.next), ), ] - clk_ddr = Signal() - miso0 = Signal() - self.specials += [ - DDROutput(clk[-1], clk[-2], clk_ddr, ClockSignal("rio_phy")), - DifferentialOutput(clk_ddr, pins.clk, pins_n.clk), - DifferentialInput(pins.miso, pins_n.miso, miso0), - MultiReg(miso0, miso, "rio_phy"), + self.comb += [ + self.data[0].eq(clk[-2:]), + [di.eq(sri[-2:]) for di, sri in zip(self.data[1:-1], sr)], + miso.eq(self.data[-1]), ] - for sri, ddr, mp, mn in zip( - sr, Signal(n_lanes), pins.mosi, pins_n.mosi): + + +class SerInterface(Module): + def __init__(self, pins, pins_n): + n_data = 1 + len(pins.mosi) + 1 + self.data = [Signal(2) for _ in range(n_data)] + clk_ddr = Signal() + miso_reg = Signal() + self.specials += [ + DDROutput(self.data[0][-1], self.data[0][-2], + clk_ddr, ClockSignal("rio_phy")), + DifferentialOutput(clk_ddr, pins.clk, pins_n.clk), + DifferentialInput(pins.miso, pins_n.miso, miso_reg), + MultiReg(miso_reg, self.data[-1], "rio_phy"), + ] + for i in range(len(pins.mosi)): + ddr = Signal() self.specials += [ - DDROutput(sri[-1], sri[-2], ddr, ClockSignal("rio_phy")), - DifferentialOutput(ddr, mp, mn), + DDROutput(self.data[-1], self.data[-2], ddr, ClockSignal("rio_phy")), + DifferentialOutput(ddr, pins.mosi[i], pins_n.mosi[i]), ] diff --git a/artiq/gateware/rtio/phy/phaser.py b/artiq/gateware/rtio/phy/phaser.py index 4dc2197ad..679c2aa05 100644 --- a/artiq/gateware/rtio/phy/phaser.py +++ b/artiq/gateware/rtio/phy/phaser.py @@ -1,7 +1,7 @@ from migen import * from artiq.gateware.rtio import rtlink -from .fastlink import SerDes +from .fastlink import SerDes, SerInterface class Phaser(Module): @@ -15,8 +15,13 @@ class Phaser(Module): enable_replace=True)) self.submodules.serializer = SerDes( - pins, pins_n, t_clk=8, d_clk=0b00001111, - n_frame=10, n_crc=6, poly=0x2f) + n_data=8, t_clk=8, d_clk=0b00001111, + n_frame=10, n_crc=6, poly=0x2f) + self.submodules.intf = SerInterface(pins, pins_n) + self.comb += [ + Cat(self.intf.data[:-1]).eq(Cat(self.serializer.data[:-1])), + self.serializer.data[-1].eq(self.intf.data[-1]), + ] header = Record([ ("we", 1), From 3e99f1ce5a314af568502ca116f68d1a7528d987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Sat, 22 Aug 2020 11:46:41 +0000 Subject: [PATCH 03/51] phaser: refactor link --- artiq/gateware/rtio/phy/fastlink.py | 93 ++++++++++++++--------------- artiq/gateware/rtio/phy/phaser.py | 7 ++- 2 files changed, 49 insertions(+), 51 deletions(-) diff --git a/artiq/gateware/rtio/phy/fastlink.py b/artiq/gateware/rtio/phy/fastlink.py index 98602206e..3f84bb6a8 100644 --- a/artiq/gateware/rtio/phy/fastlink.py +++ b/artiq/gateware/rtio/phy/fastlink.py @@ -1,6 +1,6 @@ from migen import * -from migen.genlib.cdc import MultiReg -from migen.genlib.io import DifferentialOutput, DifferentialInput, DDROutput +from migen.genlib.io import (DifferentialOutput, DifferentialInput, + DDROutput, DDRInput) from misoc.cores.liteeth_mini.mac.crc import LiteEthMACCRCEngine from artiq.gateware.rtio import rtlink @@ -17,16 +17,18 @@ class SerDes(Module): * One return data lane at slower speed. * n_frame//2 - 1 marker bits are used to provide framing. - * `n_frame` words per frame + * `n_data` lanes * `t_clk` bits per clk cycle with pattern `d_clk` - * `n_crc` CRC bits per frame + * `n_frame` words per frame + * `n_crc` CRC bits per frame for divisor poly `poly` """ # pins self.data = [Signal(2) for _ in range(n_data)] - n_lanes = n_data - 2 # number of data lanes - n_word = n_lanes*t_clk - t_frame = t_clk*n_frame//2 - n_body = n_word*n_frame - (n_frame//2 + 1) - n_crc + n_mosi = n_data - 2 # mosi lanes + n_word = n_mosi*t_clk # bits per word + t_frame = t_clk*n_frame # frame duration + n_marker = n_frame//2 + 1 + n_body = n_word*n_frame - n_marker - n_crc t_miso = 0 # miso sampling latency TODO # frame data @@ -39,11 +41,11 @@ class SerDes(Module): # # # self.submodules.crc = LiteEthMACCRCEngine( - data_width=2*n_lanes, width=n_crc, polynom=poly) + data_width=2*n_mosi, width=n_crc, polynom=poly) words_ = [] j = 0 - # last, LSB to first, MSB + # build from LSB to MSB because MSB first for i in range(n_frame): # iterate over words if i == 0: # data and checksum k = n_word - n_crc @@ -64,65 +66,60 @@ class SerDes(Module): self.comb += words.eq(words_) clk = Signal(t_clk, reset=d_clk) - i = Signal(max=t_frame) + i = Signal(max=t_frame//2) # big shift register for clk and mosi - sr = [Signal(t_frame*2 - n_crc//n_lanes, reset_less=True) - for i in range(n_lanes)] + sr = [Signal(t_frame - n_crc//n_mosi, reset_less=True) + for i in range(n_mosi)] assert len(Cat(sr)) == len(words) # DDR bits for each register - ddr_data = Cat([sri[-2] for sri in sr], [sri[-1] for sri in sr]) - self.comb += [ - self.stb.eq(i == t_frame - 1), - # LiteETHMACCRCEngine takes data LSB first - self.crc.data[::-1].eq(ddr_data), - ] - miso = Signal() + crc_data = [sri[-2] for sri in sr] + [sri[-1] for sri in sr] miso_sr = Signal(t_frame, reset_less=True) + miso_sr_next = Signal.like(miso_sr) + self.comb += [ + self.stb.eq(i == t_frame//2 - 1), + # LiteETHMACCRCEngine takes data LSB first + self.crc.data.eq(Cat(reversed(crc_data))), + miso_sr_next.eq(Cat(self.data[-1], miso_sr)), + [di.eq(sri[-2:]) for di, sri in zip(self.data, [clk] + sr)], + ] self.sync.rio_phy += [ # shift everything by two bits - clk.eq(Cat(clk[-2:], clk)), - [sri[2:].eq(sri) for sri in sr], + [sri.eq(Cat(sri[-2:], sri)) for sri in [clk] + sr], + miso_sr.eq(miso_sr_next), self.crc.last.eq(self.crc.next), - miso_sr.eq(Cat(miso, miso_sr)), i.eq(i + 1), If(self.stb, i.eq(0), clk.eq(clk.reset), self.crc.last.eq(0), # transpose, load - Cat(sr).eq(Cat(words[mm::n_lanes] for mm in range(n_lanes))), - self.readback.eq(Cat([miso_sr[int(round(t_miso + i*t_clk/2.))] - for i in range(n_frame)])), + [sri.eq(Cat(words[i::n_mosi])) for i, sri in enumerate(sr)], + # unload miso + self.readback.eq(Cat([miso_sr_next[t_miso + i*t_clk] + for i in range(n_frame)])), ), - If(i == t_frame - 2, + If(i == t_frame//2 - 2, # inject crc for the last cycle - ddr_data.eq(self.crc.next), + Cat(crc_data).eq(self.crc.next), ), ] - self.comb += [ - self.data[0].eq(clk[-2:]), - [di.eq(sri[-2:]) for di, sri in zip(self.data[1:-1], sr)], - miso.eq(self.data[-1]), - ] - class SerInterface(Module): def __init__(self, pins, pins_n): - n_data = 1 + len(pins.mosi) + 1 - self.data = [Signal(2) for _ in range(n_data)] - clk_ddr = Signal() - miso_reg = Signal() - self.specials += [ - DDROutput(self.data[0][-1], self.data[0][-2], - clk_ddr, ClockSignal("rio_phy")), - DifferentialOutput(clk_ddr, pins.clk, pins_n.clk), - DifferentialInput(pins.miso, pins_n.miso, miso_reg), - MultiReg(miso_reg, self.data[-1], "rio_phy"), - ] - for i in range(len(pins.mosi)): + self.data = [Signal(2) for _ in range(2 + len(pins.mosi))] + + for d, pp, pn in zip(self.data, + [pins.clk] + list(pins.mosi), + [pins_n.clk] + list(pins_n.mosi)): ddr = Signal() self.specials += [ - DDROutput(self.data[-1], self.data[-2], ddr, ClockSignal("rio_phy")), - DifferentialOutput(ddr, pins.mosi[i], pins_n.mosi[i]), + DDROutput(d[-1], d[-2], ddr, ClockSignal("rio_phy")), + DifferentialOutput(ddr, pp, pn), ] + ddr = Signal() + self.specials += [ + DifferentialInput(pins.miso, pins_n.miso, ddr), + DDRInput(ddr, self.data[-1][-1], self.data[-1][-2], + ClockSignal("rio_phy")), + ] diff --git a/artiq/gateware/rtio/phy/phaser.py b/artiq/gateware/rtio/phy/phaser.py index 679c2aa05..eb5cb26b2 100644 --- a/artiq/gateware/rtio/phy/phaser.py +++ b/artiq/gateware/rtio/phy/phaser.py @@ -9,7 +9,7 @@ class Phaser(Module): self.config = rtlink.Interface( rtlink.OInterface(data_width=8, address_width=8, enable_replace=False), - rtlink.IInterface(data_width=8)) + rtlink.IInterface(data_width=10)) self.data = rtlink.Interface( rtlink.OInterface(data_width=32, address_width=8, enable_replace=True)) @@ -31,8 +31,9 @@ class Phaser(Module): ]) n_channels = 2 n_samples = 8 - body = [[(Signal(14), Signal(14)) for i in range(n_channels)] - for j in range(n_samples)] + n_bits = 14 + body = [[(Signal(n_bits), Signal(n_bits)) + for i in range(n_channels)] for j in range(n_samples)] assert len(Cat(header.raw_bits(), body)) == \ len(self.serializer.payload) self.comb += self.serializer.payload.eq(Cat(header.raw_bits(), body)) From 7e584d0da108dc2efd408619204e014310e2bb47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Sat, 22 Aug 2020 11:47:01 +0000 Subject: [PATCH 04/51] fastino: use fastlink --- artiq/gateware/rtio/phy/fastino.py | 180 ++++++----------------------- 1 file changed, 38 insertions(+), 142 deletions(-) diff --git a/artiq/gateware/rtio/phy/fastino.py b/artiq/gateware/rtio/phy/fastino.py index 14d9d9cf1..ba8a7b5c9 100644 --- a/artiq/gateware/rtio/phy/fastino.py +++ b/artiq/gateware/rtio/phy/fastino.py @@ -4,132 +4,7 @@ from migen.genlib.io import DifferentialOutput, DifferentialInput, DDROutput from misoc.cores.liteeth_mini.mac.crc import LiteEthMACCRCEngine from artiq.gateware.rtio import rtlink - - -class SerDes(Module): - def transpose(self, i, n): - # i is n,m c-contiguous - # o is m,n c-contiguous - m = len(i)//n - assert n*m == len(i) - - def __init__(self, pins, pins_n): - n_bits = 16 # bits per dac data word - n_channels = 32 # channels per fastino - n_div = 7 # bits per lane and word - assert n_div == 7 - n_frame = 14 # word per frame - n_lanes = len(pins.mosi) # number of data lanes - n_checksum = 12 # checksum bits - n_addr = 4 # readback address bits - n_word = n_lanes*n_div - n_body = n_word*n_frame - (n_frame//2 + 1) - n_checksum - - # dac data words - self.dacs = [Signal(n_bits) for i in range(n_channels)] - # dac update enable - self.enable = Signal(n_channels) - # configuration word - self.cfg = Signal(20) - # readback data - self.dat_r = Signal(n_frame//2*(1 << n_addr)) - # data load synchronization event - self.stb = Signal() - - # # # - - # crc-12 telco - self.submodules.crc = LiteEthMACCRCEngine( - data_width=2*n_lanes, width=n_checksum, polynom=0x80f) - - addr = Signal(4) - body_ = Cat(self.cfg, addr, self.enable, self.dacs) - assert len(body_) == n_body - body = Signal(n_body) - self.comb += body.eq(body_) - - words_ = [] - j = 0 - for i in range(n_frame): # iterate over words - if i == 0: # data and checksum - k = n_word - n_checksum - elif i == 1: # marker - words_.append(C(1)) - k = n_word - 1 - elif i < n_frame//2 + 2: # marker - words_.append(C(0)) - k = n_word - 1 - else: # full word - k = n_word - # append corresponding frame body bits - words_.append(body[j:j + k]) - j += k - words_ = Cat(words_) - assert len(words_) == n_frame*n_word - n_checksum - words = Signal(len(words_)) - self.comb += words.eq(words_) - - clk = Signal(n_div, reset=0b1100011) - clk_stb = Signal() - i_frame = Signal(max=n_div*n_frame//2) # DDR - frame_stb = Signal() - sr = [Signal(n_frame*n_div - n_checksum//n_lanes, reset_less=True) - for i in range(n_lanes)] - assert len(Cat(sr)) == len(words) - # DDR bits for each register - ddr_data = Cat([sri[-2] for sri in sr], [sri[-1] for sri in sr]) - self.comb += [ - # assert one cycle ahead - clk_stb.eq(~clk[0] & clk[-1]), - # double period because of DDR - frame_stb.eq(i_frame == n_div*n_frame//2 - 1), - - # LiteETHMACCRCEngine takes data LSB first - self.crc.data[::-1].eq(ddr_data), - self.stb.eq(frame_stb & clk_stb), - ] - miso = Signal() - miso_sr = Signal(n_frame, reset_less=True) - self.sync.rio_phy += [ - # shift 7 bit clock pattern by two bits each DDR cycle - clk.eq(Cat(clk[-2:], clk)), - [sri[2:].eq(sri) for sri in sr], - self.crc.last.eq(self.crc.next), - If(clk[:2] == 0, # TODO: tweak MISO sampling - miso_sr.eq(Cat(miso, miso_sr)), - ), - If(~frame_stb, - i_frame.eq(i_frame + 1), - ), - If(frame_stb & clk_stb, - i_frame.eq(0), - self.crc.last.eq(0), - # transpose, load - Cat(sr).eq(Cat(words[mm::n_lanes] for mm in range(n_lanes))), - Array([self.dat_r[i*n_frame//2:(i + 1)*n_frame//2] - for i in range(1 << len(addr))])[addr].eq(miso_sr), - addr.eq(addr + 1), - ), - If(i_frame == n_div*n_frame//2 - 2, - # inject crc - ddr_data.eq(self.crc.next), - ), - ] - - clk_ddr = Signal() - miso0 = Signal() - self.specials += [ - DDROutput(clk[-1], clk[-2], clk_ddr, ClockSignal("rio_phy")), - DifferentialOutput(clk_ddr, pins.clk, pins_n.clk), - DifferentialInput(pins.miso, pins_n.miso, miso0), - MultiReg(miso0, miso, "rio_phy"), - ] - for sri, ddr, mp, mn in zip( - sr, Signal(n_lanes), pins.mosi, pins_n.mosi): - self.specials += [ - DDROutput(sri[-1], sri[-2], ddr, ClockSignal("rio_phy")), - DifferentialOutput(ddr, mp, mn), - ] +from .fastlink import SerDes, SerInterface class Fastino(Module): @@ -139,9 +14,31 @@ class Fastino(Module): rtlink.OInterface(data_width=max(16*width, 32), address_width=8, enable_replace=False), - rtlink.IInterface(data_width=32)) + rtlink.IInterface(data_width=14)) - self.submodules.serializer = SerDes(pins, pins_n) + self.submodules.serializer = SerDes( + n_data=8, t_clk=7, d_clk=0b1100011, + n_frame=14, n_crc=12, poly=0x80f) + self.submodules.intf = SerInterface(pins, pins_n) + self.comb += [ + Cat(self.intf.data[:-1]).eq(Cat(self.serializer.data[:-1])), + self.serializer.data[-1].eq(self.intf.data[-1]), + ] + + # dac data words + dacs = [Signal(16) for i in range(32)] + header = Record([ + ("cfg", 4), + ("leds", 8), + ("reserved", 8), + ("addr", 4), + ("enable", len(dacs)), + ]) + body = Cat(header.raw_bits(), dacs) + assert len(body) == len(self.serializer.payload) + self.comb += self.serializer.payload.eq(body) + + # # # # Support staging DAC data (in `dacs`) by writing to the # DAC RTIO addresses, if a channel is not "held" by its @@ -164,37 +61,36 @@ class Fastino(Module): # LSBs of the RTIO address for a DAC channel write must be zero and the # address space is sparse. - hold = Signal.like(self.serializer.enable) + hold = Signal.like(header.enable) - # TODO: stb, timestamp - read_regs = Array([ - self.serializer.dat_r[i*7:(i + 1)*7] - for i in range(1 << 4) - ]) + read_regs = Array([Signal.like(self.serializer.readback) + for _ in range(1 << len(header.addr))]) cases = { # update - 0x20: self.serializer.enable.eq(self.serializer.enable | self.rtlink.o.data), + 0x20: header.enable.eq(header.enable | self.rtlink.o.data), # hold 0x21: hold.eq(self.rtlink.o.data), # cfg - 0x22: self.serializer.cfg[:4].eq(self.rtlink.o.data), + 0x22: header.cfg.eq(self.rtlink.o.data), # leds - 0x23: self.serializer.cfg[4:12].eq(self.rtlink.o.data), + 0x23: header.leds.eq(self.rtlink.o.data), # reserved - 0x24: self.serializer.cfg[12:].eq(self.rtlink.o.data), + 0x24: header.reserved.eq(self.rtlink.o.data), } - for i in range(0, len(self.serializer.dacs), width): + for i in range(0, len(dacs), width): cases[i] = [ - Cat(self.serializer.dacs[i:i + width]).eq(self.rtlink.o.data), + Cat(dacs[i:i + width]).eq(self.rtlink.o.data), [If(~hold[i + j], - self.serializer.enable[i + j].eq(1), + header.enable[i + j].eq(1), ) for j in range(width)] ] self.sync.rio_phy += [ If(self.serializer.stb, - self.serializer.enable.eq(0), + header.enable.eq(0), + read_regs[header.addr].eq(self.serializer.readback), + header.addr.eq(header.addr + 1), ), If(self.rtlink.o.stb & ~self.rtlink.o.address[-1], Case(self.rtlink.o.address[:-1], cases), From a27a03ab3cd45145d3b4d2db7ce8c2959f003e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Sun, 23 Aug 2020 19:02:39 +0000 Subject: [PATCH 05/51] fastlink: fix crc vs data width --- artiq/gateware/rtio/phy/fastlink.py | 2 +- artiq/gateware/rtio/phy/phaser.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/gateware/rtio/phy/fastlink.py b/artiq/gateware/rtio/phy/fastlink.py index 3f84bb6a8..891df9b06 100644 --- a/artiq/gateware/rtio/phy/fastlink.py +++ b/artiq/gateware/rtio/phy/fastlink.py @@ -100,7 +100,7 @@ class SerDes(Module): ), If(i == t_frame//2 - 2, # inject crc for the last cycle - Cat(crc_data).eq(self.crc.next), + Cat(crc_data[-n_crc:]).eq(self.crc.next), ), ] diff --git a/artiq/gateware/rtio/phy/phaser.py b/artiq/gateware/rtio/phy/phaser.py index eb5cb26b2..3ca3c535e 100644 --- a/artiq/gateware/rtio/phy/phaser.py +++ b/artiq/gateware/rtio/phy/phaser.py @@ -46,7 +46,7 @@ class Phaser(Module): header.we.eq(~self.config.o.address[-1]), header.addr.eq(self.config.o.address), header.data.eq(self.config.o.data), - header.type.eq(0), # reserved + header.type.eq(1), # reserved ), ] From 63e4b9532552376a48bbd5b514feadf9c9410088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Sun, 23 Aug 2020 19:41:13 +0000 Subject: [PATCH 06/51] fastlink: rework crc injection --- artiq/gateware/rtio/phy/fastlink.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/artiq/gateware/rtio/phy/fastlink.py b/artiq/gateware/rtio/phy/fastlink.py index 891df9b06..945075a27 100644 --- a/artiq/gateware/rtio/phy/fastlink.py +++ b/artiq/gateware/rtio/phy/fastlink.py @@ -23,7 +23,7 @@ class SerDes(Module): * `n_crc` CRC bits per frame for divisor poly `poly` """ # pins - self.data = [Signal(2) for _ in range(n_data)] + self.data = [Signal(2, reset_less=True) for _ in range(n_data)] n_mosi = n_data - 2 # mosi lanes n_word = n_mosi*t_clk # bits per word t_frame = t_clk*n_frame # frame duration @@ -48,6 +48,7 @@ class SerDes(Module): # build from LSB to MSB because MSB first for i in range(n_frame): # iterate over words if i == 0: # data and checksum + words_.append(C(0, n_crc)) k = n_word - n_crc elif i == 1: # marker words_.append(C(1)) @@ -61,30 +62,31 @@ class SerDes(Module): words_.append(self.payload[j:j + k]) j += k words_ = Cat(words_) - assert len(words_) == n_frame*n_word - n_crc + assert len(words_) == n_frame*n_word words = Signal(len(words_)) self.comb += words.eq(words_) clk = Signal(t_clk, reset=d_clk) i = Signal(max=t_frame//2) - # big shift register for clk and mosi - sr = [Signal(t_frame - n_crc//n_mosi, reset_less=True) - for i in range(n_mosi)] + # big shift register for mosi and + sr = [Signal(t_frame, reset_less=True) for i in range(n_mosi)] assert len(Cat(sr)) == len(words) - # DDR bits for each register - crc_data = [sri[-2] for sri in sr] + [sri[-1] for sri in sr] + sr_t = [sr[i % n_mosi][i//n_mosi] for i in range(len(words))] + data_t = ([d[0] for d in self.data[:-1]] + + [d[1] for d in self.data[:-1]]) miso_sr = Signal(t_frame, reset_less=True) miso_sr_next = Signal.like(miso_sr) self.comb += [ self.stb.eq(i == t_frame//2 - 1), # LiteETHMACCRCEngine takes data LSB first - self.crc.data.eq(Cat(reversed(crc_data))), + self.crc.data.eq(Cat(reversed(sr_t[-2*n_mosi:]))), miso_sr_next.eq(Cat(self.data[-1], miso_sr)), - [di.eq(sri[-2:]) for di, sri in zip(self.data, [clk] + sr)], ] self.sync.rio_phy += [ # shift everything by two bits - [sri.eq(Cat(sri[-2:], sri)) for sri in [clk] + sr], + [di.eq(sri[-2:]) for di, sri in zip(self.data, [clk] + sr)], + clk.eq(Cat(clk[-2:], clk)), + [sri.eq(Cat(C(0, 2), sri)) for sri in sr], miso_sr.eq(miso_sr_next), self.crc.last.eq(self.crc.next), i.eq(i + 1), @@ -97,10 +99,8 @@ class SerDes(Module): # unload miso self.readback.eq(Cat([miso_sr_next[t_miso + i*t_clk] for i in range(n_frame)])), - ), - If(i == t_frame//2 - 2, # inject crc for the last cycle - Cat(crc_data[-n_crc:]).eq(self.crc.next), + Cat(data_t[-n_crc:]).eq(self.crc.next), ), ] From 11c9def5899d0d38cdce23675c05ca003162f59d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 24 Aug 2020 14:49:36 +0000 Subject: [PATCH 07/51] phaser: readback delay, test fastlink --- artiq/gateware/rtio/phy/fastlink.py | 6 ++-- artiq/gateware/rtio/phy/phaser.py | 15 ++++---- artiq/gateware/test/rtio/test_fastlink.py | 43 +++++++++++++++++++++++ 3 files changed, 54 insertions(+), 10 deletions(-) create mode 100644 artiq/gateware/test/rtio/test_fastlink.py diff --git a/artiq/gateware/rtio/phy/fastlink.py b/artiq/gateware/rtio/phy/fastlink.py index 945075a27..27f096a04 100644 --- a/artiq/gateware/rtio/phy/fastlink.py +++ b/artiq/gateware/rtio/phy/fastlink.py @@ -114,12 +114,14 @@ class SerInterface(Module): [pins_n.clk] + list(pins_n.mosi)): ddr = Signal() self.specials += [ - DDROutput(d[-1], d[-2], ddr, ClockSignal("rio_phy")), + # d1 closer to q, LSB first + DDROutput(d[1], d[0], ddr, ClockSignal("rio_phy")), DifferentialOutput(ddr, pp, pn), ] ddr = Signal() self.specials += [ DifferentialInput(pins.miso, pins_n.miso, ddr), - DDRInput(ddr, self.data[-1][-1], self.data[-1][-2], + # q1 closer to d, MSB first + DDRInput(ddr, self.data[-1][1], self.data[-1][0], ClockSignal("rio_phy")), ] diff --git a/artiq/gateware/rtio/phy/phaser.py b/artiq/gateware/rtio/phy/phaser.py index 3ca3c535e..f50ba111b 100644 --- a/artiq/gateware/rtio/phy/phaser.py +++ b/artiq/gateware/rtio/phy/phaser.py @@ -38,20 +38,19 @@ class Phaser(Module): len(self.serializer.payload) self.comb += self.serializer.payload.eq(Cat(header.raw_bits(), body)) - self.sync.rio_phy += [ + re_dly = Signal(3) # stage, send, respond + self.sync.rtio += [ + header.type.eq(1), # reserved If(self.serializer.stb, header.we.eq(0), + re_dly.eq(re_dly[1:]), ), If(self.config.o.stb, - header.we.eq(~self.config.o.address[-1]), + re_dly[-1].eq(~self.config.o.address[-1]), + header.we.eq(self.config.o.address[-1]), header.addr.eq(self.config.o.address), header.data.eq(self.config.o.data), - header.type.eq(1), # reserved ), - ] - - self.sync.rtio += [ - self.config.i.stb.eq(self.config.o.stb & - self.config.o.address[-1]), + self.config.i.stb.eq(re_dly[0] & self.serializer.stb), self.config.i.data.eq(self.serializer.readback), ] diff --git a/artiq/gateware/test/rtio/test_fastlink.py b/artiq/gateware/test/rtio/test_fastlink.py new file mode 100644 index 000000000..dd95120df --- /dev/null +++ b/artiq/gateware/test/rtio/test_fastlink.py @@ -0,0 +1,43 @@ +import unittest + +from migen import * +from artiq.gateware.rtio.phy.fastlink import * + + + +class TestPhaser(unittest.TestCase): + def setUp(self): + self.dut = SerDes(n_data=8, t_clk=8, d_clk=0b00001111, + n_frame=10, n_crc=6, poly=0x2f) + + def test_init(self): + pass + + def record_frame(self, frame): + clk = 0 + marker = 0 + state = "start" + while True: + clk = (clk << 2) & 0xff + clk |= (yield self.dut.data[0]) + if clk == 0x0f: + marker = (marker << 1) & 0x7f + marker |= (yield self.dut.data[1]) & 1 + if marker >> 1 == 0x01: + if state == "start": + state = "end" + elif state == "end": + break + yield + if state == "end": + data = yield from [(yield d) for d in self.dut.data] + frame.append(data) + + def test_frame(self): + frame = [] + run_simulation(self.dut, self.record_frame(frame), + clocks={n: 2 for n in ["sys", "rio", "rio_phy"]}) + self.assertEqual(len(frame), 8*10//2) + self.assertEqual([d[0] for d in frame], [0, 0, 3, 3] * 10) + self.assertEqual([d[1] & 1 for d in frame[4*4 - 1:10*4 - 1:4]], + [0, 0, 0, 0, 0, 1]) From bcefb06e194b62ba731c2bba7a0e5f7066e9c917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 24 Aug 2020 14:51:50 +0000 Subject: [PATCH 08/51] phaser: ddb template, split crc --- artiq/frontend/artiq_ddb_template.py | 12 +++++++++ artiq/gateware/rtio/phy/fastlink.py | 30 ++++++++++++++--------- artiq/gateware/test/rtio/test_fastlink.py | 16 ++++++------ 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/artiq/frontend/artiq_ddb_template.py b/artiq/frontend/artiq_ddb_template.py index 052673e03..86452c607 100755 --- a/artiq/frontend/artiq_ddb_template.py +++ b/artiq/frontend/artiq_ddb_template.py @@ -485,6 +485,18 @@ class PeripheralManager: channel=rtio_offset) return 1 + def process_phaser(self, rtio_offset, peripheral): + self.gen(""" + device_db["{name}"] = {{ + "type": "local", + "module": "artiq.coredevice.phaser", + "class": "Phaser", + "arguments": {{"channel": 0x{channel:06x}}} + }}""", + name=self.get_name("phaser"), + channel=rtio_offset) + return 2 + def process(self, rtio_offset, peripheral): processor = getattr(self, "process_"+str(peripheral["type"])) return processor(rtio_offset, peripheral) diff --git a/artiq/gateware/rtio/phy/fastlink.py b/artiq/gateware/rtio/phy/fastlink.py index 27f096a04..a7003c15e 100644 --- a/artiq/gateware/rtio/phy/fastlink.py +++ b/artiq/gateware/rtio/phy/fastlink.py @@ -30,6 +30,7 @@ class SerDes(Module): n_marker = n_frame//2 + 1 n_body = n_word*n_frame - n_marker - n_crc t_miso = 0 # miso sampling latency TODO + assert n_crc % n_mosi == 0 # frame data self.payload = Signal(n_body) @@ -40,8 +41,10 @@ class SerDes(Module): # # # - self.submodules.crc = LiteEthMACCRCEngine( - data_width=2*n_mosi, width=n_crc, polynom=poly) + self.submodules.crca = LiteEthMACCRCEngine( + data_width=n_mosi, width=n_crc, polynom=poly) + self.submodules.crcb = LiteEthMACCRCEngine( + data_width=n_mosi, width=n_crc, polynom=poly) words_ = [] j = 0 @@ -71,15 +74,17 @@ class SerDes(Module): # big shift register for mosi and sr = [Signal(t_frame, reset_less=True) for i in range(n_mosi)] assert len(Cat(sr)) == len(words) - sr_t = [sr[i % n_mosi][i//n_mosi] for i in range(len(words))] - data_t = ([d[0] for d in self.data[:-1]] + - [d[1] for d in self.data[:-1]]) + crc_insert = ([d[1] for d in self.data[:-1]] + + [d[0] for d in self.data[:-1]]) + crc_insert = Cat(crc_insert[-n_crc:]) miso_sr = Signal(t_frame, reset_less=True) miso_sr_next = Signal.like(miso_sr) self.comb += [ self.stb.eq(i == t_frame//2 - 1), # LiteETHMACCRCEngine takes data LSB first - self.crc.data.eq(Cat(reversed(sr_t[-2*n_mosi:]))), + self.crca.data.eq(Cat([sri[-1] for sri in sr[::-1]])), + self.crcb.data.eq(Cat([sri[-2] for sri in sr[::-1]])), + self.crcb.last.eq(self.crca.next), miso_sr_next.eq(Cat(self.data[-1], miso_sr)), ] self.sync.rio_phy += [ @@ -88,19 +93,20 @@ class SerDes(Module): clk.eq(Cat(clk[-2:], clk)), [sri.eq(Cat(C(0, 2), sri)) for sri in sr], miso_sr.eq(miso_sr_next), - self.crc.last.eq(self.crc.next), + self.crca.last.eq(self.crcb.next), i.eq(i + 1), If(self.stb, i.eq(0), clk.eq(clk.reset), - self.crc.last.eq(0), + self.crca.last.eq(0), # transpose, load [sri.eq(Cat(words[i::n_mosi])) for i, sri in enumerate(sr)], # unload miso self.readback.eq(Cat([miso_sr_next[t_miso + i*t_clk] for i in range(n_frame)])), # inject crc for the last cycle - Cat(data_t[-n_crc:]).eq(self.crc.next), + crc_insert.eq(self.crca.next if n_crc // n_mosi == 1 + else self.crcb.next), ), ] @@ -114,14 +120,14 @@ class SerInterface(Module): [pins_n.clk] + list(pins_n.mosi)): ddr = Signal() self.specials += [ - # d1 closer to q, LSB first + # d1 closer to q DDROutput(d[1], d[0], ddr, ClockSignal("rio_phy")), DifferentialOutput(ddr, pp, pn), ] ddr = Signal() self.specials += [ DifferentialInput(pins.miso, pins_n.miso, ddr), - # q1 closer to d, MSB first - DDRInput(ddr, self.data[-1][1], self.data[-1][0], + # q1 closer to d + DDRInput(ddr, self.data[-1][0], self.data[-1][1], ClockSignal("rio_phy")), ] diff --git a/artiq/gateware/test/rtio/test_fastlink.py b/artiq/gateware/test/rtio/test_fastlink.py index dd95120df..5b62c9aba 100644 --- a/artiq/gateware/test/rtio/test_fastlink.py +++ b/artiq/gateware/test/rtio/test_fastlink.py @@ -16,27 +16,27 @@ class TestPhaser(unittest.TestCase): def record_frame(self, frame): clk = 0 marker = 0 - state = "start" + stb = 0 while True: + if stb == 2: + frame.append((yield self.dut.data)) clk = (clk << 2) & 0xff clk |= (yield self.dut.data[0]) if clk == 0x0f: marker = (marker << 1) & 0x7f marker |= (yield self.dut.data[1]) & 1 if marker >> 1 == 0x01: - if state == "start": - state = "end" - elif state == "end": + stb += 1 + if stb >= 3: break yield - if state == "end": - data = yield from [(yield d) for d in self.dut.data] - frame.append(data) def test_frame(self): frame = [] + self.dut.comb += self.dut.payload.eq((1 << len(self.dut.payload)) - 1) run_simulation(self.dut, self.record_frame(frame), - clocks={n: 2 for n in ["sys", "rio", "rio_phy"]}) + clocks={n: 2 for n in ["sys", "rio", "rio_phy"]}, + vcd_name="fastlink.vcd") self.assertEqual(len(frame), 8*10//2) self.assertEqual([d[0] for d in frame], [0, 0, 3, 3] * 10) self.assertEqual([d[1] & 1 for d in frame[4*4 - 1:10*4 - 1:4]], From 20fcfd95e9b3aef2faecc19dad13851132419207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 24 Aug 2020 15:46:31 +0000 Subject: [PATCH 09/51] phaser: coredevice shim, readback fix --- artiq/coredevice/phaser.py | 45 ++++++++++++++++++++++++++++ artiq/frontend/artiq_ddb_template.py | 5 +++- artiq/gateware/rtio/phy/fastlink.py | 6 ++-- artiq/gateware/rtio/phy/phaser.py | 3 +- 4 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 artiq/coredevice/phaser.py diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py new file mode 100644 index 000000000..9ebd86d74 --- /dev/null +++ b/artiq/coredevice/phaser.py @@ -0,0 +1,45 @@ +from artiq.language.core import kernel, portable, delay +from artiq.coredevice.rtio import rtio_output, rtio_input_data +from artiq.language.units import us +from artiq.language.types import TInt32, TList, TFloat + + +PHASER_ADDR_BOARD_ID = 0x00 +PHASER_BOARD_ID = 19 + +class Phaser: + kernel_invariants = {"core", "channel_base"} + + def __init__(self, dmgr, channel_base, readback_delay=1, + core_device="core"): + self.channel_base = channel_base << 8 + self.core = dmgr.get(core_device) + self.readback_delay = readback_delay + + @kernel + def init(self): + board_id = self.read(PHASER_ADDR_BOARD_ID) + if board_id != PHASER_BOARD_ID: + raise ValueError("invalid board id") + + @kernel + def write(self, addr, data): + """Write data to a Fastino register. + + :param addr: Address to write to. + :param data: Data to write. + """ + rtio_output(self.channel_base | addr | 0x80, data) + + @kernel + def read(self, addr): + """Read from Fastino register. + + TODO: untested + + :param addr: Address to read from. + :return: The data read. + """ + rtio_output(self.channel_base | addr, 0) + response = rtio_input_data(self.channel_base >> 8) + return response >> self.readback_delay diff --git a/artiq/frontend/artiq_ddb_template.py b/artiq/frontend/artiq_ddb_template.py index 86452c607..8a3122f9b 100755 --- a/artiq/frontend/artiq_ddb_template.py +++ b/artiq/frontend/artiq_ddb_template.py @@ -491,7 +491,10 @@ class PeripheralManager: "type": "local", "module": "artiq.coredevice.phaser", "class": "Phaser", - "arguments": {{"channel": 0x{channel:06x}}} + "arguments": {{ + "channel_base": 0x{channel:06x}, + "readback_delay": 1, + }} }}""", name=self.get_name("phaser"), channel=rtio_offset) diff --git a/artiq/gateware/rtio/phy/fastlink.py b/artiq/gateware/rtio/phy/fastlink.py index a7003c15e..0c361b901 100644 --- a/artiq/gateware/rtio/phy/fastlink.py +++ b/artiq/gateware/rtio/phy/fastlink.py @@ -86,6 +86,9 @@ class SerDes(Module): self.crcb.data.eq(Cat([sri[-2] for sri in sr[::-1]])), self.crcb.last.eq(self.crca.next), miso_sr_next.eq(Cat(self.data[-1], miso_sr)), + # unload miso + self.readback.eq(Cat([miso_sr_next[t_miso + i*t_clk] + for i in range(n_frame)])), ] self.sync.rio_phy += [ # shift everything by two bits @@ -101,9 +104,6 @@ class SerDes(Module): self.crca.last.eq(0), # transpose, load [sri.eq(Cat(words[i::n_mosi])) for i, sri in enumerate(sr)], - # unload miso - self.readback.eq(Cat([miso_sr_next[t_miso + i*t_clk] - for i in range(n_frame)])), # inject crc for the last cycle crc_insert.eq(self.crca.next if n_crc // n_mosi == 1 else self.crcb.next), diff --git a/artiq/gateware/rtio/phy/phaser.py b/artiq/gateware/rtio/phy/phaser.py index f50ba111b..19390f51d 100644 --- a/artiq/gateware/rtio/phy/phaser.py +++ b/artiq/gateware/rtio/phy/phaser.py @@ -32,8 +32,7 @@ class Phaser(Module): n_channels = 2 n_samples = 8 n_bits = 14 - body = [[(Signal(n_bits), Signal(n_bits)) - for i in range(n_channels)] for j in range(n_samples)] + body = [Signal(n_bits) for i in range(n_channels*n_samples*2)] assert len(Cat(header.raw_bits(), body)) == \ len(self.serializer.payload) self.comb += self.serializer.payload.eq(Cat(header.raw_bits(), body)) From d1be1212ab939d1506de7d5dca303fcc06fabfd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Wed, 26 Aug 2020 15:10:50 +0000 Subject: [PATCH 10/51] phaser: coredevice shim, dds [wip] --- artiq/coredevice/phaser.py | 176 +++++++++++++++++++++++++-- artiq/frontend/artiq_ddb_template.py | 2 +- artiq/gateware/eem.py | 5 +- artiq/gateware/rtio/phy/phaser.py | 31 +++-- 4 files changed, 190 insertions(+), 24 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 9ebd86d74..bce26882d 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -1,39 +1,80 @@ -from artiq.language.core import kernel, portable, delay +import numpy as np + +from artiq.language.core import kernel, delay_mu, delay from artiq.coredevice.rtio import rtio_output, rtio_input_data -from artiq.language.units import us -from artiq.language.types import TInt32, TList, TFloat +from artiq.language.units import us, ns +from artiq.language.types import TInt32 -PHASER_ADDR_BOARD_ID = 0x00 PHASER_BOARD_ID = 19 +PHASER_ADDR_BOARD_ID = 0x00 +PHASER_ADDR_HW_REV = 0x01 +PHASER_ADDR_GW_REV = 0x02 +PHASER_ADDR_CFG = 0x03 +PHASER_ADDR_STA = 0x04 +PHASER_ADDR_CRC_ERR = 0x05 +PHASER_ADDR_LED = 0x06 +PHASER_ADDR_FAN = 0x07 +PHASER_ADDR_DUC_STB = 0x08 +PHASER_ADDR_ADC_CFG = 0x09 +PHASER_ADDR_SPI_CFG = 0x0a +PHASER_ADDR_SPI_DIV = 0x0b +PHASER_ADDR_SPI_SEL = 0x0c +PHASER_ADDR_SPI_DATW = 0x0d +PHASER_ADDR_SPI_DATR = 0x0e +# PHASER_ADDR_RESERVED0 = 0x0f +PHASER_ADDR_DUC0_CFG = 0x10 +# PHASER_ADDR_DUC0_RESERVED0 = 0x11 +PHASER_ADDR_DUC0_F = 0x12 +PHASER_ADDR_DUC0_P = 0x16 +PHASER_ADDR_DAC0_DATA = 0x18 +PHASER_ADDR_DAC0_TEST = 0x1c +PHASER_ADDR_DUC1_CFG = 0x20 +# PHASER_ADDR_DUC1_RESERVED0 = 0x21 +PHASER_ADDR_DUC1_F = 0x22 +PHASER_ADDR_DUC1_P = 0x26 +PHASER_ADDR_DAC1_DATA = 0x28 +PHASER_ADDR_DAC1_TEST = 0x2c + +PHASER_SEL_DAC = 1 << 0 +PHASER_SEL_TRF0 = 1 << 1 +PHASER_SEL_TRF1 = 1 << 2 +PHASER_SEL_ATT0 = 1 << 3 +PHASER_SEL_ATT1 = 1 << 4 + class Phaser: - kernel_invariants = {"core", "channel_base"} + kernel_invariants = {"core", "channel_base", "t_frame"} - def __init__(self, dmgr, channel_base, readback_delay=1, + def __init__(self, dmgr, channel_base, miso_delay=1, core_device="core"): self.channel_base = channel_base << 8 self.core = dmgr.get(core_device) - self.readback_delay = readback_delay + self.miso_delay = miso_delay + # frame duration in mu (10 words, 8 clock cycles each 4 ns) + # self.core.seconds_to_mu(10*8*4*ns) # unfortunately 319 + self.t_frame = 10*8*4 @kernel def init(self): board_id = self.read(PHASER_ADDR_BOARD_ID) if board_id != PHASER_BOARD_ID: raise ValueError("invalid board id") + delay(20*us) @kernel def write(self, addr, data): - """Write data to a Fastino register. + """Write data to a Phaser FPGA register. :param addr: Address to write to. :param data: Data to write. """ rtio_output(self.channel_base | addr | 0x80, data) + delay_mu(int64(self.t_frame)) @kernel def read(self, addr): - """Read from Fastino register. + """Read from Phaser FPGA register. TODO: untested @@ -42,4 +83,119 @@ class Phaser: """ rtio_output(self.channel_base | addr, 0) response = rtio_input_data(self.channel_base >> 8) - return response >> self.readback_delay + return response >> self.miso_delay + + @kernel + def set_leds(self, leds): + self.write(PHASER_ADDR_LED, leds) + + @kernel + def set_fan(self, duty): + self.write(PHASER_ADDR_FAN, duty) + + @kernel + def set_cfg(self, clk_sel=0, dac_resetb=1, dac_sleep=0, dac_txena=1, + trf0_ps=0, trf1_ps=0, att0_rstn=1, att1_rstn=1): + self.write(PHASER_ADDR_CFG, + (clk_sel << 0) | (dac_resetb << 1) | (dac_sleep << 2) | + (dac_txena << 3) | (trf0_ps << 4) | (trf1_ps << 5) | + (att0_rstn << 6) | (att1_rstn << 7)) + + @kernel + def get_sta(self): + return self.read(PHASER_ADDR_STA) + + @kernel + def get_crc_err(self): + return self.read(PHASER_ADDR_CRC_ERR) + + @kernel + def get_dac_data(self, ch) -> TInt32: + data = 0 + for addr in range(4): + data <<= 8 + data |= self.read(PHASER_ADDR_DAC0_DATA + (ch << 4) + addr) + delay(20*us) # slack + return data + + @kernel + def set_dac_test(self, ch, data: TInt32): + for addr in range(4): + byte = (data >> 24) & 0xff + self.write(PHASER_ADDR_DAC0_TEST + (ch << 4) + addr, byte) + data <<= 8 + return data + + @kernel + def set_duc_cfg(self, ch, clr=0, clr_once=0, select=0): + self.write(PHASER_ADDR_DUC0_CFG + (ch << 4), + (clr << 0) | (clr_once << 1) | (select << 2)) + + @kernel + def spi_cfg(self, select, div, end, clk_phase=0, clk_polarity=0, + half_duplex=0, lsb_first=0, offline=0): + self.write(PHASER_ADDR_SPI_SEL, select) + self.write(PHASER_ADDR_SPI_DIV, div) + self.write(PHASER_ADDR_SPI_CFG, + (offline << 0) | (end << 1) | (clk_phase << 2) | + (clk_polarity << 3) | (half_duplex << 4) | + (lsb_first << 5)) + + @kernel + def spi_write(self, data): + self.write(PHASER_ADDR_SPI_DATW, data) + + @kernel + def spi_read(self): + return self.read(PHASER_ADDR_SPI_DATR) + + @kernel + def dac_write(self, addr, data): + div = 30 # 100 ns min period + t_xfer = self.core.seconds_to_mu((8 + 1)*(div + 2)*4*ns) + self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=0) + self.spi_write(addr) + delay_mu(t_xfer) + self.spi_write(data >> 8) + delay_mu(t_xfer) + self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=1) + self.spi_write(data & 0xff) + delay_mu(t_xfer) + + @kernel + def dac_read(self, addr, div=30) -> TInt32: + t_xfer = self.core.seconds_to_mu((8 + 1)*(div + 2)*4*ns) + self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=0) + self.spi_write(addr | 0x80) + delay_mu(t_xfer) + self.spi_write(0) + delay_mu(t_xfer) + data = self.spi_read() << 8 + delay(10*us) # slack + self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=1) + self.spi_write(0) + delay_mu(t_xfer) + data |= self.spi_read() + return data + + @kernel + def att_write(self, ch, data): + div = 30 # 30 ns min period + t_xfer = self.core.seconds_to_mu((8 + 1)*(div + 2)*4*ns) + self.spi_cfg(select=PHASER_SEL_ATT0 << ch, div=div, end=1) + self.spi_write(data) + delay_mu(t_xfer) + + @kernel + def att_read(self, ch) -> TInt32: + div = 30 + t_xfer = self.core.seconds_to_mu((8 + 1)*(div + 2)*4*ns) + self.spi_cfg(select=PHASER_SEL_ATT0 << ch, div=div, end=0) + self.spi_write(0) + delay_mu(t_xfer) + data = self.spi_read() + delay(10*us) + self.spi_cfg(select=PHASER_SEL_ATT0 << ch, div=div, end=1) + self.spi_write(data) + delay_mu(t_xfer) + return data diff --git a/artiq/frontend/artiq_ddb_template.py b/artiq/frontend/artiq_ddb_template.py index 8a3122f9b..3f208c4a7 100755 --- a/artiq/frontend/artiq_ddb_template.py +++ b/artiq/frontend/artiq_ddb_template.py @@ -493,7 +493,7 @@ class PeripheralManager: "class": "Phaser", "arguments": {{ "channel_base": 0x{channel:06x}, - "readback_delay": 1, + "miso_delay": 1, }} }}""", name=self.get_name("phaser"), diff --git a/artiq/gateware/eem.py b/artiq/gateware/eem.py index 35cf36de1..74ff6647f 100644 --- a/artiq/gateware/eem.py +++ b/artiq/gateware/eem.py @@ -650,6 +650,7 @@ class Phaser(_EEM): target.platform.request("phaser{}_ser_n".format(eem))) target.submodules += phy target.rtio_channels.extend([ - rtio.Channel(phy.config, ififo_depth=4), - rtio.Channel(phy.data), + rtio.Channel.from_phy(phy, ififo_depth=4), + rtio.Channel.from_phy(phy.dds0), + rtio.Channel.from_phy(phy.dds1), ]) diff --git a/artiq/gateware/rtio/phy/phaser.py b/artiq/gateware/rtio/phy/phaser.py index 19390f51d..3d14e6084 100644 --- a/artiq/gateware/rtio/phy/phaser.py +++ b/artiq/gateware/rtio/phy/phaser.py @@ -1,18 +1,27 @@ from migen import * +from misoc.cores.duc import MultiDDS from artiq.gateware.rtio import rtlink from .fastlink import SerDes, SerInterface +class DDSChannel(Module): + def __init__(self): + self.rtlink = rtlink.Interface( + rtlink.OInterface(data_width=32, address_width=4, + enable_replace=True)) + self.submodules.dds = MultiDDS(n=5, fwidth=32, xwidth=16) + + class Phaser(Module): def __init__(self, pins, pins_n): - self.config = rtlink.Interface( + self.rtlink = rtlink.Interface( rtlink.OInterface(data_width=8, address_width=8, enable_replace=False), rtlink.IInterface(data_width=10)) - self.data = rtlink.Interface( - rtlink.OInterface(data_width=32, address_width=8, - enable_replace=True)) + + self.submodules.dds0 = DDSChannel() + self.submodules.dds1 = DDSChannel() self.submodules.serializer = SerDes( n_data=8, t_clk=8, d_clk=0b00001111, @@ -44,12 +53,12 @@ class Phaser(Module): header.we.eq(0), re_dly.eq(re_dly[1:]), ), - If(self.config.o.stb, - re_dly[-1].eq(~self.config.o.address[-1]), - header.we.eq(self.config.o.address[-1]), - header.addr.eq(self.config.o.address), - header.data.eq(self.config.o.data), + If(self.rtlink.o.stb, + re_dly[-1].eq(~self.rtlink.o.address[-1]), + header.we.eq(self.rtlink.o.address[-1]), + header.addr.eq(self.rtlink.o.address), + header.data.eq(self.rtlink.o.data), ), - self.config.i.stb.eq(re_dly[0] & self.serializer.stb), - self.config.i.data.eq(self.serializer.readback), + self.rtlink.i.stb.eq(re_dly[0] & self.serializer.stb), + self.rtlink.i.data.eq(self.serializer.readback), ] From e5e239224070aa3285e3b6972333c049e97f925d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Wed, 26 Aug 2020 17:12:41 +0000 Subject: [PATCH 11/51] phaser: wire up multidds --- artiq/coredevice/phaser.py | 19 ++++++++++--- artiq/gateware/eem.py | 7 +++-- artiq/gateware/rtio/phy/phaser.py | 47 ++++++++++++++++++++++++------- 3 files changed, 56 insertions(+), 17 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index bce26882d..7b5918232 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -48,7 +48,7 @@ class Phaser: def __init__(self, dmgr, channel_base, miso_delay=1, core_device="core"): - self.channel_base = channel_base << 8 + self.channel_base = channel_base self.core = dmgr.get(core_device) self.miso_delay = miso_delay # frame duration in mu (10 words, 8 clock cycles each 4 ns) @@ -69,7 +69,7 @@ class Phaser: :param addr: Address to write to. :param data: Data to write. """ - rtio_output(self.channel_base | addr | 0x80, data) + rtio_output((self.channel_base << 8) | addr | 0x80, data) delay_mu(int64(self.t_frame)) @kernel @@ -81,8 +81,8 @@ class Phaser: :param addr: Address to read from. :return: The data read. """ - rtio_output(self.channel_base | addr, 0) - response = rtio_input_data(self.channel_base >> 8) + rtio_output((self.channel_base << 8) | addr, 0) + response = rtio_input_data(self.channel_base) return response >> self.miso_delay @kernel @@ -199,3 +199,14 @@ class Phaser: self.spi_write(data) delay_mu(t_xfer) return data + + @kernel + def set_frequency_mu(self, ch, osc, ftw): + addr = ((self.channel_base + 1 + ch) << 8) | (osc << 1) + rtio_output(addr, ftw) + + @kernel + def set_amplitude_phase_mu(self, ch, osc, asf=0x7fff, pow=0, clr=0): + addr = ((self.channel_base + 1 + ch) << 8) | (osc << 1) | 1 + data = (asf & 0x7fff) | (clr << 15) | (pow << 16) + rtio_output(addr, data) diff --git a/artiq/gateware/eem.py b/artiq/gateware/eem.py index 74ff6647f..447dd1fe6 100644 --- a/artiq/gateware/eem.py +++ b/artiq/gateware/eem.py @@ -646,11 +646,12 @@ class Phaser(_EEM): def add_std(cls, target, eem, iostandard="LVDS_25"): cls.add_extension(target, eem, iostandard=iostandard) - phy = phaser.Phaser(target.platform.request("phaser{}_ser_p".format(eem)), + phy = phaser.Phaser( + target.platform.request("phaser{}_ser_p".format(eem)), target.platform.request("phaser{}_ser_n".format(eem))) target.submodules += phy target.rtio_channels.extend([ rtio.Channel.from_phy(phy, ififo_depth=4), - rtio.Channel.from_phy(phy.dds0), - rtio.Channel.from_phy(phy.dds1), + rtio.Channel.from_phy(phy.ch0), + rtio.Channel.from_phy(phy.ch1), ]) diff --git a/artiq/gateware/rtio/phy/phaser.py b/artiq/gateware/rtio/phy/phaser.py index 3d14e6084..f78b24ee1 100644 --- a/artiq/gateware/rtio/phy/phaser.py +++ b/artiq/gateware/rtio/phy/phaser.py @@ -6,22 +6,48 @@ from .fastlink import SerDes, SerInterface class DDSChannel(Module): - def __init__(self): + def __init__(self, use_lut=None): self.rtlink = rtlink.Interface( rtlink.OInterface(data_width=32, address_width=4, - enable_replace=True)) - self.submodules.dds = MultiDDS(n=5, fwidth=32, xwidth=16) + enable_replace=True)) + to_rio_phy = ClockDomainsRenamer("rio_phy") + self.submodules.dds = to_rio_phy(MultiDDS( + n=5, fwidth=32, xwidth=16, z=19, zl=10, use_lut=use_lut)) + # TODO: latency + self.comb += self.dds.stb.eq(1) + regs = [] + for i in self.dds.i: + regs.extend([i.f, Cat(i.a, i.clr, i.p)]) + self.sync.rio_phy += [ + If(self.rtlink.o.stb, + Array(regs)[self.rtlink.o.address].eq(self.rtlink.o.data) + ) + ] class Phaser(Module): def __init__(self, pins, pins_n): self.rtlink = rtlink.Interface( rtlink.OInterface(data_width=8, address_width=8, - enable_replace=False), + enable_replace=False), rtlink.IInterface(data_width=10)) - self.submodules.dds0 = DDSChannel() - self.submodules.dds1 = DDSChannel() + self.submodules.ch0 = DDSChannel() + self.submodules.ch1 = DDSChannel(use_lut=self.ch0.dds.mod.cs.lut) + n_channels = 2 + n_samples = 8 + n_bits = 14 + body = [Signal(n_channels*2*n_bits, reset_less=True) + for i in range(n_samples)] + i_sample = Signal(max=n_samples) + self.sync.rio_phy += [ + If(self.ch0.dds.valid, # & self.ch1.dds.valid, + Array(body)[i_sample].eq(Cat( + self.ch0.dds.o.q[2:], self.ch0.dds.o.i[2:], + self.ch1.dds.o.q[2:], self.ch1.dds.o.i[2:])), + i_sample.eq(i_sample + 1), + ), + ] self.submodules.serializer = SerDes( n_data=8, t_clk=8, d_clk=0b00001111, @@ -31,6 +57,11 @@ class Phaser(Module): Cat(self.intf.data[:-1]).eq(Cat(self.serializer.data[:-1])), self.serializer.data[-1].eq(self.intf.data[-1]), ] + self.sync.rio_phy += [ + If(self.serializer.stb, + i_sample.eq(0), + ), + ] header = Record([ ("we", 1), @@ -38,10 +69,6 @@ class Phaser(Module): ("data", 8), ("type", 4) ]) - n_channels = 2 - n_samples = 8 - n_bits = 14 - body = [Signal(n_bits) for i in range(n_channels*n_samples*2)] assert len(Cat(header.raw_bits(), body)) == \ len(self.serializer.payload) self.comb += self.serializer.payload.eq(Cat(header.raw_bits(), body)) From c10ac2c92af72fbf4cf413aba1b68a5b51566f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 27 Aug 2020 14:26:09 +0000 Subject: [PATCH 12/51] phaser: add trf, duc, interfaces, redo body assembly, use more natrual iq ordering (i lsb) --- artiq/coredevice/phaser.py | 116 ++++++++++++++++++++++++------ artiq/gateware/rtio/phy/phaser.py | 17 ++--- 2 files changed, 101 insertions(+), 32 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 7b5918232..d38bb4f71 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -42,6 +42,13 @@ PHASER_SEL_TRF1 = 1 << 2 PHASER_SEL_ATT0 = 1 << 3 PHASER_SEL_ATT1 = 1 << 4 +PHASER_STA_DAC_ALARM = 1 << 0 +PHASER_STA_TRF0_LD = 1 << 1 +PHASER_STA_TRF1_LD = 1 << 2 +PHASER_STA_TERM0 = 1 << 3 +PHASER_STA_TERM1 = 1 << 4 +PHASER_STA_SPI_IDLE = 1 << 5 + class Phaser: kernel_invariants = {"core", "channel_base", "t_frame"} @@ -57,23 +64,23 @@ class Phaser: @kernel def init(self): - board_id = self.read(PHASER_ADDR_BOARD_ID) + board_id = self.read8(PHASER_ADDR_BOARD_ID) if board_id != PHASER_BOARD_ID: raise ValueError("invalid board id") delay(20*us) @kernel - def write(self, addr, data): + def write8(self, addr, data): """Write data to a Phaser FPGA register. :param addr: Address to write to. :param data: Data to write. """ - rtio_output((self.channel_base << 8) | addr | 0x80, data) + rtio_output((self.channel_base << 8) | (addr & 0x7f) | 0x80, data) delay_mu(int64(self.t_frame)) @kernel - def read(self, addr): + def read8(self, addr) -> TInt32: """Read from Phaser FPGA register. TODO: untested @@ -81,40 +88,65 @@ class Phaser: :param addr: Address to read from. :return: The data read. """ - rtio_output((self.channel_base << 8) | addr, 0) + rtio_output((self.channel_base << 8) | (addr & 0x7f), 0) response = rtio_input_data(self.channel_base) return response >> self.miso_delay + @kernel + def write32(self, addr, data: TInt32): + for offset in range(4): + byte = data >> 24 + self.write8(addr + offset, byte) + data <<= 8 + + @kernel + def read32(self, addr) -> TInt32: + data = 0 + for offset in range(4): + data <<= 8 + data |= self.read8(addr + offset) + delay(20*us) # slack + return data + + @kernel + def write16(self, addr, data: TInt32): + self.write8(addr, data >> 8) + self.write8(addr + 1, data) + + @kernel + def read16(self, addr) -> TInt32: + return (self.read8(addr) << 8) | self.read8(addr) + @kernel def set_leds(self, leds): - self.write(PHASER_ADDR_LED, leds) + self.write8(PHASER_ADDR_LED, leds) @kernel def set_fan(self, duty): - self.write(PHASER_ADDR_FAN, duty) + self.write8(PHASER_ADDR_FAN, duty) @kernel def set_cfg(self, clk_sel=0, dac_resetb=1, dac_sleep=0, dac_txena=1, trf0_ps=0, trf1_ps=0, att0_rstn=1, att1_rstn=1): - self.write(PHASER_ADDR_CFG, + self.write8(PHASER_ADDR_CFG, (clk_sel << 0) | (dac_resetb << 1) | (dac_sleep << 2) | (dac_txena << 3) | (trf0_ps << 4) | (trf1_ps << 5) | (att0_rstn << 6) | (att1_rstn << 7)) @kernel def get_sta(self): - return self.read(PHASER_ADDR_STA) + return self.read8(PHASER_ADDR_STA) @kernel def get_crc_err(self): - return self.read(PHASER_ADDR_CRC_ERR) + return self.read8(PHASER_ADDR_CRC_ERR) @kernel def get_dac_data(self, ch) -> TInt32: data = 0 for addr in range(4): data <<= 8 - data |= self.read(PHASER_ADDR_DAC0_DATA + (ch << 4) + addr) + data |= self.read8(PHASER_ADDR_DAC0_DATA + (ch << 4) + addr) delay(20*us) # slack return data @@ -122,32 +154,43 @@ class Phaser: def set_dac_test(self, ch, data: TInt32): for addr in range(4): byte = (data >> 24) & 0xff - self.write(PHASER_ADDR_DAC0_TEST + (ch << 4) + addr, byte) + self.write8(PHASER_ADDR_DAC0_TEST + (ch << 4) + addr, byte) data <<= 8 - return data @kernel def set_duc_cfg(self, ch, clr=0, clr_once=0, select=0): - self.write(PHASER_ADDR_DUC0_CFG + (ch << 4), + self.write8(PHASER_ADDR_DUC0_CFG + (ch << 4), (clr << 0) | (clr_once << 1) | (select << 2)) + @kernel + def set_duc_frequency_mu(self, ch, ftw): + self.write32(PHASER_ADDR_DUC0_F + (ch << 4), ftw) + + @kernel + def set_duc_phase_mu(self, ch, pow): + self.write16(PHASER_ADDR_DUC0_P + (ch << 4), pow) + + @kernel + def duc_stb(self): + self.write8(PHASER_ADDR_DUC_STB, 0) + @kernel def spi_cfg(self, select, div, end, clk_phase=0, clk_polarity=0, half_duplex=0, lsb_first=0, offline=0): - self.write(PHASER_ADDR_SPI_SEL, select) - self.write(PHASER_ADDR_SPI_DIV, div) - self.write(PHASER_ADDR_SPI_CFG, + self.write8(PHASER_ADDR_SPI_SEL, select) + self.write8(PHASER_ADDR_SPI_DIV, div) + self.write8(PHASER_ADDR_SPI_CFG, (offline << 0) | (end << 1) | (clk_phase << 2) | (clk_polarity << 3) | (half_duplex << 4) | (lsb_first << 5)) @kernel def spi_write(self, data): - self.write(PHASER_ADDR_SPI_DATW, data) + self.write8(PHASER_ADDR_SPI_DATW, data) @kernel def spi_read(self): - return self.read(PHASER_ADDR_SPI_DATR) + return self.read8(PHASER_ADDR_SPI_DATR) @kernel def dac_write(self, addr, data): @@ -194,12 +237,45 @@ class Phaser: self.spi_write(0) delay_mu(t_xfer) data = self.spi_read() - delay(10*us) + delay(10*us) # slack self.spi_cfg(select=PHASER_SEL_ATT0 << ch, div=div, end=1) self.spi_write(data) delay_mu(t_xfer) return data + @kernel + def trf_write(self, ch, data, readback=False): + div = 30 # 50 ns min period + t_xfer = self.core.seconds_to_mu((8 + 1)*(div + 2)*4*ns) + read = 0 + if readback: + clk_phase = 1 + else: + clk_phase = 0 + end = 0 + for i in range(4): + if i == 0 or i == 3: + if i == 3: + end = 1 + self.spi_cfg(select=PHASER_SEL_TRF0 << ch, div=div, + lsb_first=1, clk_phase=clk_phase, end=end) + self.spi_write(data & 0xff) + data >>= 8 + delay_mu(t_xfer) + if readback: + read >>= 8 + read |= self.spi_read() << 24 + delay(10*us) # slack + return read + + @kernel + def trf_read(self, ch, addr, cnt_mux_sel=0) -> TInt32: + self.trf_write(ch, 0x80000008 | (addr << 28) | (cnt_mux_sel << 27)) + # single clk pulse to start readback + self.spi_cfg(select=PHASER_SEL_TRF0 << ch, div=30, end=1, clk_polarity=1) + self.spi_cfg(select=PHASER_SEL_TRF0 << ch, div=30, end=1, clk_polarity=0) + return self.trf_write(ch, 0x00000008, readback=True) + @kernel def set_frequency_mu(self, ch, osc, ftw): addr = ((self.channel_base + 1 + ch) << 8) | (osc << 1) diff --git a/artiq/gateware/rtio/phy/phaser.py b/artiq/gateware/rtio/phy/phaser.py index f78b24ee1..1ae6862a6 100644 --- a/artiq/gateware/rtio/phy/phaser.py +++ b/artiq/gateware/rtio/phy/phaser.py @@ -37,15 +37,13 @@ class Phaser(Module): n_channels = 2 n_samples = 8 n_bits = 14 - body = [Signal(n_channels*2*n_bits, reset_less=True) - for i in range(n_samples)] - i_sample = Signal(max=n_samples) + body = Signal(n_samples*n_channels*2*n_bits, reset_less=True) self.sync.rio_phy += [ If(self.ch0.dds.valid, # & self.ch1.dds.valid, - Array(body)[i_sample].eq(Cat( - self.ch0.dds.o.q[2:], self.ch0.dds.o.i[2:], - self.ch1.dds.o.q[2:], self.ch1.dds.o.i[2:])), - i_sample.eq(i_sample + 1), + # recent sample, ch0, i first + Cat(body).eq(Cat(self.ch0.dds.o.i[2:], self.ch0.dds.o.q[2:], + self.ch1.dds.o.i[2:], self.ch1.dds.o.q[2:], + body)), ), ] @@ -57,11 +55,6 @@ class Phaser(Module): Cat(self.intf.data[:-1]).eq(Cat(self.serializer.data[:-1])), self.serializer.data[-1].eq(self.intf.data[-1]), ] - self.sync.rio_phy += [ - If(self.serializer.stb, - i_sample.eq(0), - ), - ] header = Record([ ("we", 1), From 96fc248d7ccb29cca5bf66824675c4837fafc12e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 27 Aug 2020 14:28:19 +0000 Subject: [PATCH 13/51] phaser: synchronize multidds to frame --- artiq/gateware/rtio/phy/phaser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/gateware/rtio/phy/phaser.py b/artiq/gateware/rtio/phy/phaser.py index 1ae6862a6..5109d033c 100644 --- a/artiq/gateware/rtio/phy/phaser.py +++ b/artiq/gateware/rtio/phy/phaser.py @@ -13,8 +13,6 @@ class DDSChannel(Module): to_rio_phy = ClockDomainsRenamer("rio_phy") self.submodules.dds = to_rio_phy(MultiDDS( n=5, fwidth=32, xwidth=16, z=19, zl=10, use_lut=use_lut)) - # TODO: latency - self.comb += self.dds.stb.eq(1) regs = [] for i in self.dds.i: regs.extend([i.f, Cat(i.a, i.clr, i.p)]) @@ -70,6 +68,8 @@ class Phaser(Module): self.sync.rtio += [ header.type.eq(1), # reserved If(self.serializer.stb, + self.ch0.dds.stb.eq(1), # synchronize + self.ch1.dds.stb.eq(1), # synchronize header.we.eq(0), re_dly.eq(re_dly[1:]), ), From 68bfa04abbfee0480861a1a56cf5ca03e4882cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 27 Aug 2020 15:31:42 +0000 Subject: [PATCH 14/51] phaser: trf readback strobe spi changes --- artiq/coredevice/phaser.py | 42 +++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index d38bb4f71..2376f190f 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -18,7 +18,7 @@ PHASER_ADDR_FAN = 0x07 PHASER_ADDR_DUC_STB = 0x08 PHASER_ADDR_ADC_CFG = 0x09 PHASER_ADDR_SPI_CFG = 0x0a -PHASER_ADDR_SPI_DIV = 0x0b +PHASER_ADDR_SPI_DIVLEN = 0x0b PHASER_ADDR_SPI_SEL = 0x0c PHASER_ADDR_SPI_DATW = 0x0d PHASER_ADDR_SPI_DATR = 0x0e @@ -153,7 +153,7 @@ class Phaser: @kernel def set_dac_test(self, ch, data: TInt32): for addr in range(4): - byte = (data >> 24) & 0xff + byte = data >> 24 self.write8(PHASER_ADDR_DAC0_TEST + (ch << 4) + addr, byte) data <<= 8 @@ -176,9 +176,9 @@ class Phaser: @kernel def spi_cfg(self, select, div, end, clk_phase=0, clk_polarity=0, - half_duplex=0, lsb_first=0, offline=0): + half_duplex=0, lsb_first=0, offline=0, length=8): self.write8(PHASER_ADDR_SPI_SEL, select) - self.write8(PHASER_ADDR_SPI_DIV, div) + self.write8(PHASER_ADDR_SPI_DIVLEN, (div - 2 >> 3) | (length - 1 << 5)) self.write8(PHASER_ADDR_SPI_CFG, (offline << 0) | (end << 1) | (clk_phase << 2) | (clk_polarity << 3) | (half_duplex << 4) | @@ -194,20 +194,20 @@ class Phaser: @kernel def dac_write(self, addr, data): - div = 30 # 100 ns min period - t_xfer = self.core.seconds_to_mu((8 + 1)*(div + 2)*4*ns) + div = 32 # 100 ns min period + t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=0) self.spi_write(addr) delay_mu(t_xfer) self.spi_write(data >> 8) delay_mu(t_xfer) self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=1) - self.spi_write(data & 0xff) + self.spi_write(data) delay_mu(t_xfer) @kernel - def dac_read(self, addr, div=30) -> TInt32: - t_xfer = self.core.seconds_to_mu((8 + 1)*(div + 2)*4*ns) + def dac_read(self, addr, div=32) -> TInt32: + t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=0) self.spi_write(addr | 0x80) delay_mu(t_xfer) @@ -223,16 +223,16 @@ class Phaser: @kernel def att_write(self, ch, data): - div = 30 # 30 ns min period - t_xfer = self.core.seconds_to_mu((8 + 1)*(div + 2)*4*ns) + div = 32 # 30 ns min period + t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) self.spi_cfg(select=PHASER_SEL_ATT0 << ch, div=div, end=1) self.spi_write(data) delay_mu(t_xfer) @kernel def att_read(self, ch) -> TInt32: - div = 30 - t_xfer = self.core.seconds_to_mu((8 + 1)*(div + 2)*4*ns) + div = 32 + t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) self.spi_cfg(select=PHASER_SEL_ATT0 << ch, div=div, end=0) self.spi_write(0) delay_mu(t_xfer) @@ -245,21 +245,20 @@ class Phaser: @kernel def trf_write(self, ch, data, readback=False): - div = 30 # 50 ns min period - t_xfer = self.core.seconds_to_mu((8 + 1)*(div + 2)*4*ns) + div = 32 # 50 ns min period + t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) read = 0 + end = 0 + clk_phase = 0 if readback: clk_phase = 1 - else: - clk_phase = 0 - end = 0 for i in range(4): if i == 0 or i == 3: if i == 3: end = 1 self.spi_cfg(select=PHASER_SEL_TRF0 << ch, div=div, lsb_first=1, clk_phase=clk_phase, end=end) - self.spi_write(data & 0xff) + self.spi_write(data) data >>= 8 delay_mu(t_xfer) if readback: @@ -272,8 +271,9 @@ class Phaser: def trf_read(self, ch, addr, cnt_mux_sel=0) -> TInt32: self.trf_write(ch, 0x80000008 | (addr << 28) | (cnt_mux_sel << 27)) # single clk pulse to start readback - self.spi_cfg(select=PHASER_SEL_TRF0 << ch, div=30, end=1, clk_polarity=1) - self.spi_cfg(select=PHASER_SEL_TRF0 << ch, div=30, end=1, clk_polarity=0) + self.spi_cfg(select=0, div=32, end=1, length=1) + self.spi_write(0) + delay((1 + 1)*32*4*ns) return self.trf_write(ch, 0x00000008, readback=True) @kernel From 272dc5d36a5a6a4789001f2ad166b122c3984e64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 28 Aug 2020 16:36:44 +0000 Subject: [PATCH 15/51] phaser: documentation --- artiq/coredevice/phaser.py | 223 +++++++++++++++++++++++--- artiq/gateware/rtio/phy/phaser.py | 5 +- doc/manual/core_drivers_reference.rst | 5 + 3 files changed, 208 insertions(+), 25 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 2376f190f..7eff5b51d 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -1,5 +1,3 @@ -import numpy as np - from artiq.language.core import kernel, delay_mu, delay from artiq.coredevice.rtio import rtio_output, rtio_input_data from artiq.language.units import us, ns @@ -49,44 +47,86 @@ PHASER_STA_TERM0 = 1 << 3 PHASER_STA_TERM1 = 1 << 4 PHASER_STA_SPI_IDLE = 1 << 5 +PHASER_DAC_SEL_DUC = 0 +PHASER_DAC_SEL_TEST = 1 + class Phaser: - kernel_invariants = {"core", "channel_base", "t_frame"} + """Phaser 4-channel, 16-bit, 1 GS/s DAC coredevice driver. - def __init__(self, dmgr, channel_base, miso_delay=1, - core_device="core"): + Phaser contains a 4 channel, 1 GS/s DAC chip with integrated upconversion, + quadrature modulation compensation and interpolation features. + + The coredevice produces 2 IQ data streams with 25 MS/s 14 bit. Each + data stream supports 5 independent numerically controlled oscillators (NCOs) + added together for each channel. Together with a data clock, framing + marker, a checksum and metadata for register access the data is sent in + groups of 8 samples over 1.5 Gb/s FastLink via a single EEM connector. + + On Phaser the data streams are buffered and interpolated from 25 MS/s to 500 + MS/s 16 bit followed by a 500 MS/s digital upconverter in the FPGA. + + The four 16 bit 500 MS/s DAC data streams are sent via a 32 bit parallel + LVDS bus operating at 1 Gb/s per pin pair and processed in the DAC. + + The four analog DAC outputs are passed through anti-aliasing filters and In + the baseband variant, the even channels feed 31.5 dB range and are + available on the front panel. The odd outputs are available on MMCX + connectors on board. + + In the upconverter variant, each of the two IQ (in-phase and quadrature) + output pairs feeds a one quadrature upconverter with integrated PLL/VCO. + The output from the upconverter passes through the step attenuator and is + available at the front panel. + + The DAC, the TRF upconverters and the two attenuators are configured + through a shared SPI bus that is accessed and controlled via FPGA + registers. + + :param channel: Base RTIO channel number + :param core_device: Core device name (default: "core") + :param miso_delay: Fastlink MISO signal delay to account for cable + and buffer round trip. This might be automated later. + """ + kernel_invariants = {"core", "channel_base", "t_frame", "miso_delay"} + + def __init__(self, dmgr, channel_base, miso_delay=1, core_device="core"): self.channel_base = channel_base self.core = dmgr.get(core_device) self.miso_delay = miso_delay # frame duration in mu (10 words, 8 clock cycles each 4 ns) - # self.core.seconds_to_mu(10*8*4*ns) # unfortunately 319 + # self.core.seconds_to_mu(10*8*4*ns) # unfortunately this returns 319 + assert self.core.ref_period == 1*ns self.t_frame = 10*8*4 @kernel def init(self): + """Initialize the board. + + Verifies board presence by reading the board ID register. + Does not alter any state. + """ board_id = self.read8(PHASER_ADDR_BOARD_ID) if board_id != PHASER_BOARD_ID: raise ValueError("invalid board id") - delay(20*us) + delay(20*us) # slack @kernel def write8(self, addr, data): - """Write data to a Phaser FPGA register. + """Write data to FPGA register. - :param addr: Address to write to. - :param data: Data to write. + :param addr: Address to write to (7 bit) + :param data: Data to write (8 bit) """ rtio_output((self.channel_base << 8) | (addr & 0x7f) | 0x80, data) delay_mu(int64(self.t_frame)) @kernel def read8(self, addr) -> TInt32: - """Read from Phaser FPGA register. + """Read from FPGA register. - TODO: untested - - :param addr: Address to read from. - :return: The data read. + :param addr: Address to read from (7 bit) + :return: Data read (8 bit) """ rtio_output((self.channel_base << 8) | (addr & 0x7f), 0) response = rtio_input_data(self.channel_base) @@ -94,6 +134,7 @@ class Phaser: @kernel def write32(self, addr, data: TInt32): + """Write 32 bit to a sequence of FPGA registers.""" for offset in range(4): byte = data >> 24 self.write8(addr + offset, byte) @@ -101,6 +142,7 @@ class Phaser: @kernel def read32(self, addr) -> TInt32: + """Read 32 bit from a sequence of FPGA registers.""" data = 0 for offset in range(4): data <<= 8 @@ -110,39 +152,80 @@ class Phaser: @kernel def write16(self, addr, data: TInt32): + """Write 16 bit to a sequence of FPGA registers.""" self.write8(addr, data >> 8) self.write8(addr + 1, data) @kernel def read16(self, addr) -> TInt32: + """Read 16 bit from a sequence of FPGA registers.""" return (self.read8(addr) << 8) | self.read8(addr) @kernel def set_leds(self, leds): + """Set the front panel LEDs. + + :param leds: LED settings (6 bit) + """ self.write8(PHASER_ADDR_LED, leds) @kernel def set_fan(self, duty): + """Set the fan duty cycle. + + :param duty: Duty cycle (8 bit) + """ self.write8(PHASER_ADDR_FAN, duty) @kernel def set_cfg(self, clk_sel=0, dac_resetb=1, dac_sleep=0, dac_txena=1, trf0_ps=0, trf1_ps=0, att0_rstn=1, att1_rstn=1): + """Set the configuration register. + + :param clk_sel: Select the external SMA clock input + :param dac_resetb: Active low DAC reset pin + :param dac_sleep: DAC sleep pin + :param dac_txena: Enable DAC transmission pin + :param trf0_ps: TRF0 upconverter power save + :param trf1_ps: TRF1 upconverter power save + :param att0_rstn: Active low attenuator 0 reset + :param att1_rstn: Active low attenuator 1 reset + """ self.write8(PHASER_ADDR_CFG, - (clk_sel << 0) | (dac_resetb << 1) | (dac_sleep << 2) | - (dac_txena << 3) | (trf0_ps << 4) | (trf1_ps << 5) | - (att0_rstn << 6) | (att1_rstn << 7)) + (clk_sel << 0) | (dac_resetb << 1) | (dac_sleep << 2) | + (dac_txena << 3) | (trf0_ps << 4) | (trf1_ps << 5) | + (att0_rstn << 6) | (att1_rstn << 7)) @kernel def get_sta(self): + """Get the status register value. + + Bit flags are: + + * `PHASER_STA_DAC_ALARM`: DAC alarm pin + * `PHASER_STA_TRF0_LD`: TRF0 lock detect pin + * `PHASER_STA_TRF1_LD`: TRF1 lock detect pin + * `PHASER_STA_TERM0`: ADC channel 0 termination indicator + * `PHASER_STA_TERM1`: ADC channel 1 termination indicator + * `PHASER_STA_SPI_IDLE`: SPI machine is idle and data registers can be + read/written + + :return: Status register + """ return self.read8(PHASER_ADDR_STA) @kernel def get_crc_err(self): + """Get the frame CRC error counter.""" return self.read8(PHASER_ADDR_CRC_ERR) @kernel def get_dac_data(self, ch) -> TInt32: + """Get a sample of the current DAC data. + + :param ch: DAC channel pair (0 or 1) + :return: DAC data as 32 bit IQ + """ data = 0 for addr in range(4): data <<= 8 @@ -152,6 +235,11 @@ class Phaser: @kernel def set_dac_test(self, ch, data: TInt32): + """Set the DAC test data. + + :param ch: DAC channel pair (0 or 1) + :param data: 32 bit IQ test data + """ for addr in range(4): byte = data >> 24 self.write8(PHASER_ADDR_DAC0_TEST + (ch << 4) + addr, byte) @@ -159,41 +247,84 @@ class Phaser: @kernel def set_duc_cfg(self, ch, clr=0, clr_once=0, select=0): + """Set the digital upconverter and interpolator configuration. + + :param ch: DAC channel pair (0 or 1) + :param clr: Keep the phase accumulator cleared + :param clr_once: Clear the phase accumulator for one cycle + :param select: Select the data to send to the DAC (0: DUC data, 1: test + data) + """ self.write8(PHASER_ADDR_DUC0_CFG + (ch << 4), - (clr << 0) | (clr_once << 1) | (select << 2)) + (clr << 0) | (clr_once << 1) | (select << 2)) @kernel def set_duc_frequency_mu(self, ch, ftw): + """Set the DUC frequency. + + :param ch: DAC channel pair (0 or 1) + :param ftw: DUC frequency tuning word + """ self.write32(PHASER_ADDR_DUC0_F + (ch << 4), ftw) @kernel def set_duc_phase_mu(self, ch, pow): + """Set the DUC phase offset + + :param ch: DAC channel pair (0 or 1) + :param pow: DUC phase offset word + """ self.write16(PHASER_ADDR_DUC0_P + (ch << 4), pow) @kernel def duc_stb(self): + """Strobe the DUC configuration register update. + + Transfer staging to active registers. + This affects both DUC channels. + """ self.write8(PHASER_ADDR_DUC_STB, 0) @kernel def spi_cfg(self, select, div, end, clk_phase=0, clk_polarity=0, half_duplex=0, lsb_first=0, offline=0, length=8): + """Set the SPI machine configuration + + :param select: Chip selects to assert (DAC, TRF0, TRF1, ATT0, ATT1) + :param div: SPI clock divider relative to 250 MHz fabric clock + :param end: Whether to end the SPI transaction and deassert chip select + :param clk_phase: SPI clock phase (sample on first or second edge) + :param clk_polarity: SPI clock polarity (idle low or high) + :param half_duplex: Read MISO data from MOSI wire + :param lsb_first: Transfer the least significant bit first + :param offline: Put the SPI interfaces offline and don't drive voltages + :param length: SPI transfer length (1 to 8 bits) + """ self.write8(PHASER_ADDR_SPI_SEL, select) self.write8(PHASER_ADDR_SPI_DIVLEN, (div - 2 >> 3) | (length - 1 << 5)) self.write8(PHASER_ADDR_SPI_CFG, - (offline << 0) | (end << 1) | (clk_phase << 2) | - (clk_polarity << 3) | (half_duplex << 4) | - (lsb_first << 5)) + (offline << 0) | (end << 1) | (clk_phase << 2) | + (clk_polarity << 3) | (half_duplex << 4) | + (lsb_first << 5)) @kernel def spi_write(self, data): + """Write 8 bits into the SPI data register and start/continue the + transaction.""" self.write8(PHASER_ADDR_SPI_DATW, data) @kernel def spi_read(self): + """Read from the SPI input data register.""" return self.read8(PHASER_ADDR_SPI_DATR) @kernel def dac_write(self, addr, data): + """Write 16 bit to a DAC register. + + :param addr: Register address + :param data: Register data to write + """ div = 32 # 100 ns min period t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=0) @@ -207,6 +338,12 @@ class Phaser: @kernel def dac_read(self, addr, div=32) -> TInt32: + """Read from a DAC register. + + :param addr: Register address to read from + :param div: SPI clock divider. Needs to be at least 250 to read the + temperature register. + """ t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=0) self.spi_write(addr | 0x80) @@ -223,6 +360,11 @@ class Phaser: @kernel def att_write(self, ch, data): + """Set channel attenuation. + + :param ch: RF channel (0 or 1) + :param data: Attenuator data + """ div = 32 # 30 ns min period t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) self.spi_cfg(select=PHASER_SEL_ATT0 << ch, div=div, end=1) @@ -231,6 +373,13 @@ class Phaser: @kernel def att_read(self, ch) -> TInt32: + """Read current attenuation. + + The current attenuation value is read without side effects. + + :param ch: RF channel (0 or 1) + :return: Current attenuation + """ div = 32 t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) self.spi_cfg(select=PHASER_SEL_ATT0 << ch, div=div, end=0) @@ -245,6 +394,12 @@ class Phaser: @kernel def trf_write(self, ch, data, readback=False): + """Write 32 bits to a TRF upconverter. + + :param ch: RF channel (0 or 1) + :param data: Register data (32 bit) + :param readback: Whether to return the read back MISO data + """ div = 32 # 50 ns min period t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) read = 0 @@ -269,8 +424,16 @@ class Phaser: @kernel def trf_read(self, ch, addr, cnt_mux_sel=0) -> TInt32: + """TRF upconverter register read. + + :param ch: RF channel (0 or 1) + :param addr: Register address to read + :param cnt_mux_sel: Report VCO counter min frequency + or max frequency + :return: Register data (32 bit) + """ self.trf_write(ch, 0x80000008 | (addr << 28) | (cnt_mux_sel << 27)) - # single clk pulse to start readback + # single clk pulse with ~LE to start readback self.spi_cfg(select=0, div=32, end=1, length=1) self.spi_write(0) delay((1 + 1)*32*4*ns) @@ -278,11 +441,25 @@ class Phaser: @kernel def set_frequency_mu(self, ch, osc, ftw): + """Set Phaser MultiDDS frequency tuning word. + + :param ch: RF channel (0 or 1) + :param osc: Oscillator number (0 to 4) + :param ftw: Frequency tuning word (32 bit) + """ addr = ((self.channel_base + 1 + ch) << 8) | (osc << 1) rtio_output(addr, ftw) @kernel def set_amplitude_phase_mu(self, ch, osc, asf=0x7fff, pow=0, clr=0): + """Set Phaser MultiDDS amplitude, phase offset and accumulator clear. + + :param ch: RF channel (0 or 1) + :param osc: Oscillator number (0 to 4) + :param asf: Amplitude (15 bit) + :param pow: Phase offset word (16 bit) + :param clr: Clear the phase accumulator (persistent) + """ addr = ((self.channel_base + 1 + ch) << 8) | (osc << 1) | 1 data = (asf & 0x7fff) | (clr << 15) | (pow << 16) rtio_output(addr, data) diff --git a/artiq/gateware/rtio/phy/phaser.py b/artiq/gateware/rtio/phy/phaser.py index 5109d033c..cc6ffa1e5 100644 --- a/artiq/gateware/rtio/phy/phaser.py +++ b/artiq/gateware/rtio/phy/phaser.py @@ -30,6 +30,7 @@ class Phaser(Module): enable_replace=False), rtlink.IInterface(data_width=10)) + # share a CosSinGen LUT between the two channels self.submodules.ch0 = DDSChannel() self.submodules.ch1 = DDSChannel(use_lut=self.ch0.dds.mod.cs.lut) n_channels = 2 @@ -38,7 +39,7 @@ class Phaser(Module): body = Signal(n_samples*n_channels*2*n_bits, reset_less=True) self.sync.rio_phy += [ If(self.ch0.dds.valid, # & self.ch1.dds.valid, - # recent sample, ch0, i first + # recent:ch0:i as low order in body Cat(body).eq(Cat(self.ch0.dds.o.i[2:], self.ch0.dds.o.q[2:], self.ch1.dds.o.i[2:], self.ch1.dds.o.q[2:], body)), @@ -66,7 +67,7 @@ class Phaser(Module): re_dly = Signal(3) # stage, send, respond self.sync.rtio += [ - header.type.eq(1), # reserved + header.type.eq(1), # body type is baseband data If(self.serializer.stb, self.ch0.dds.stb.eq(1), # synchronize self.ch1.dds.stb.eq(1), # synchronize diff --git a/doc/manual/core_drivers_reference.rst b/doc/manual/core_drivers_reference.rst index b0026b6ac..baa1b59e4 100644 --- a/doc/manual/core_drivers_reference.rst +++ b/doc/manual/core_drivers_reference.rst @@ -130,6 +130,11 @@ RF generation drivers .. automodule:: artiq.coredevice.basemod_att :members: +:mod:`artiq.coredevice.phaser` module ++++++++++++++++++++++++++++++++++++++ + +.. automodule:: artiq.coredevice.phaser + :members: DAC/ADC drivers --------------- From e69bb0aeb3c77c66649fae7228fe57a51f02f697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 7 Sep 2020 16:06:16 +0000 Subject: [PATCH 16/51] phaser: add comment about get_dac_data --- artiq/coredevice/phaser.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 7eff5b51d..fec71b1c9 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -223,6 +223,9 @@ class Phaser: def get_dac_data(self, ch) -> TInt32: """Get a sample of the current DAC data. + The data is split accross multiple registers and thus the data + is only valid if constant. + :param ch: DAC channel pair (0 or 1) :return: DAC data as 32 bit IQ """ From 8aaeaa604e49a32f24a77748e5bbd7dffcd3a9da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 7 Sep 2020 16:06:35 +0000 Subject: [PATCH 17/51] phaser: share_lut --- artiq/gateware/rtio/phy/phaser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/artiq/gateware/rtio/phy/phaser.py b/artiq/gateware/rtio/phy/phaser.py index cc6ffa1e5..f8fe99548 100644 --- a/artiq/gateware/rtio/phy/phaser.py +++ b/artiq/gateware/rtio/phy/phaser.py @@ -6,13 +6,13 @@ from .fastlink import SerDes, SerInterface class DDSChannel(Module): - def __init__(self, use_lut=None): + def __init__(self, share_lut=None): self.rtlink = rtlink.Interface( rtlink.OInterface(data_width=32, address_width=4, enable_replace=True)) to_rio_phy = ClockDomainsRenamer("rio_phy") self.submodules.dds = to_rio_phy(MultiDDS( - n=5, fwidth=32, xwidth=16, z=19, zl=10, use_lut=use_lut)) + n=5, fwidth=32, xwidth=16, z=19, zl=10, shae_lut=share_lut)) regs = [] for i in self.dds.i: regs.extend([i.f, Cat(i.a, i.clr, i.p)]) @@ -32,7 +32,7 @@ class Phaser(Module): # share a CosSinGen LUT between the two channels self.submodules.ch0 = DDSChannel() - self.submodules.ch1 = DDSChannel(use_lut=self.ch0.dds.mod.cs.lut) + self.submodules.ch1 = DDSChannel(share_lut=self.ch0.dds.mod.cs.lut) n_channels = 2 n_samples = 8 n_bits = 14 From 4e24700205f200447c1464bc4e094cf33864566c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Wed, 9 Sep 2020 16:52:52 +0000 Subject: [PATCH 18/51] phaser: spelling --- artiq/gateware/rtio/phy/phaser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/gateware/rtio/phy/phaser.py b/artiq/gateware/rtio/phy/phaser.py index f8fe99548..46dfc8005 100644 --- a/artiq/gateware/rtio/phy/phaser.py +++ b/artiq/gateware/rtio/phy/phaser.py @@ -12,7 +12,7 @@ class DDSChannel(Module): enable_replace=True)) to_rio_phy = ClockDomainsRenamer("rio_phy") self.submodules.dds = to_rio_phy(MultiDDS( - n=5, fwidth=32, xwidth=16, z=19, zl=10, shae_lut=share_lut)) + n=5, fwidth=32, xwidth=16, z=19, zl=10, share_lut=share_lut)) regs = [] for i in self.dds.i: regs.extend([i.f, Cat(i.a, i.clr, i.p)]) From fdd2d6f2fbd2b61e976254e0b63f10d343b988a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Sat, 12 Sep 2020 11:02:37 +0000 Subject: [PATCH 19/51] phaser: SI methods --- artiq/coredevice/phaser.py | 84 +++++++++++++++++++++++++---- artiq/gateware/rtio/phy/fastlink.py | 1 + 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index fec71b1c9..877bdef21 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -270,6 +270,18 @@ class Phaser: """ self.write32(PHASER_ADDR_DUC0_F + (ch << 4), ftw) + @kernel + def set_duc_frequency(self, ch, frequency): + """Set the DUC frequency. + + :param ch: DAC channel pair (0 or 1) + :param frequency: DUC frequency in Hz + """ + if ch < 0 or ch > 1: + raise ValueError("invalid channel index") + ftw = int32(round(frequency*((1 << 32)/500e6))) + self.set_duc_frequency_mu(ch, ftw) + @kernel def set_duc_phase_mu(self, ch, pow): """Set the DUC phase offset @@ -279,6 +291,18 @@ class Phaser: """ self.write16(PHASER_ADDR_DUC0_P + (ch << 4), pow) + @kernel + def set_duc_phase(self, ch, phase): + """Set the DUC phase. + + :param ch: DAC channel pair (0 or 1) + :param phase: DUC phase in turns + """ + if ch < 0 or ch > 1: + raise ValueError("invalid channel index") + pow = int32(round(phase*(1 << 16))) & 0xffff + self.set_duc_phase_mu(ch, pow) + @kernel def duc_stb(self): """Strobe the DUC configuration register update. @@ -303,12 +327,16 @@ class Phaser: :param offline: Put the SPI interfaces offline and don't drive voltages :param length: SPI transfer length (1 to 8 bits) """ + if div < 2 or div > 257: + raise ValueError("invalid divider") + if length < 0 or length > 8: + raise ValueError("invalid length") self.write8(PHASER_ADDR_SPI_SEL, select) self.write8(PHASER_ADDR_SPI_DIVLEN, (div - 2 >> 3) | (length - 1 << 5)) self.write8(PHASER_ADDR_SPI_CFG, - (offline << 0) | (end << 1) | (clk_phase << 2) | - (clk_polarity << 3) | (half_duplex << 4) | - (lsb_first << 5)) + ((offline & 1) << 0) | ((end & 1) << 1) | + ((clk_phase & 1) << 2) | ((clk_polarity & 1) << 3) | + ((half_duplex & 1) << 4) | ((lsb_first & 1) << 5)) @kernel def spi_write(self, data): @@ -328,7 +356,7 @@ class Phaser: :param addr: Register address :param data: Register data to write """ - div = 32 # 100 ns min period + div = 34 # 100 ns min period t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=0) self.spi_write(addr) @@ -340,7 +368,7 @@ class Phaser: delay_mu(t_xfer) @kernel - def dac_read(self, addr, div=32) -> TInt32: + def dac_read(self, addr, div=34) -> TInt32: """Read from a DAC register. :param addr: Register address to read from @@ -368,7 +396,7 @@ class Phaser: :param ch: RF channel (0 or 1) :param data: Attenuator data """ - div = 32 # 30 ns min period + div = 34 # 30 ns min period t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) self.spi_cfg(select=PHASER_SEL_ATT0 << ch, div=div, end=1) self.spi_write(data) @@ -383,7 +411,7 @@ class Phaser: :param ch: RF channel (0 or 1) :return: Current attenuation """ - div = 32 + div = 34 t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) self.spi_cfg(select=PHASER_SEL_ATT0 << ch, div=div, end=0) self.spi_write(0) @@ -403,7 +431,7 @@ class Phaser: :param data: Register data (32 bit) :param readback: Whether to return the read back MISO data """ - div = 32 # 50 ns min period + div = 34 # 50 ns min period t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) read = 0 end = 0 @@ -437,7 +465,7 @@ class Phaser: """ self.trf_write(ch, 0x80000008 | (addr << 28) | (cnt_mux_sel << 27)) # single clk pulse with ~LE to start readback - self.spi_cfg(select=0, div=32, end=1, length=1) + self.spi_cfg(select=0, div=34, end=1, length=1) self.spi_write(0) delay((1 + 1)*32*4*ns) return self.trf_write(ch, 0x00000008, readback=True) @@ -453,6 +481,22 @@ class Phaser: addr = ((self.channel_base + 1 + ch) << 8) | (osc << 1) rtio_output(addr, ftw) + @kernel + def set_frequency(self, ch, osc, frequency): + """Set Phaser MultiDDS frequency. + + :param ch: RF channel (0 or 1) + :param osc: Oscillator number (0 to 4) + :param frequency: Frequency in Hz + """ + if ch < 0 or ch > 1: + raise ValueError("invalid channel index") + if osc < 0 or osc > 4: + raise ValueError("invalid oscillator index") + ftw = int32(round(frequency*((1 << 32)/125e6))) + self.set_frequency_mu(ch, osc, ftw) + + @kernel def set_amplitude_phase_mu(self, ch, osc, asf=0x7fff, pow=0, clr=0): """Set Phaser MultiDDS amplitude, phase offset and accumulator clear. @@ -464,5 +508,25 @@ class Phaser: :param clr: Clear the phase accumulator (persistent) """ addr = ((self.channel_base + 1 + ch) << 8) | (osc << 1) | 1 - data = (asf & 0x7fff) | (clr << 15) | (pow << 16) + data = (asf & 0x7fff) | ((clr & 1) << 15) | ((pow & 0xffff) << 16) rtio_output(addr, data) + + @kernel + def set_amplitude_phase(self, ch, osc, amplitude, phase=0., clr=0): + """Set Phaser MultiDDS amplitude and phase. + + :param ch: RF channel (0 or 1) + :param osc: Oscillator number (0 to 4) + :param amplitude: Amplitude in units of full scale + :param phase: Phase in turns + :param clr: Clear the phase accumulator (persistent) + """ + if ch < 0 or ch > 1: + raise ValueError("invalid channel index") + if osc < 0 or osc > 4: + raise ValueError("invalid oscillator index") + asf = int32(round(amplitude*0x7fff)) + if asf < 0 or asf > 0x7fff: + raise ValueError("invalid amplitude") + pow = int32(round(phase*(1 << 16))) + self.set_amplitude_phase_mu(ch, osc, asf, pow, clr) diff --git a/artiq/gateware/rtio/phy/fastlink.py b/artiq/gateware/rtio/phy/fastlink.py index 0c361b901..796b01642 100644 --- a/artiq/gateware/rtio/phy/fastlink.py +++ b/artiq/gateware/rtio/phy/fastlink.py @@ -87,6 +87,7 @@ class SerDes(Module): self.crcb.last.eq(self.crca.next), miso_sr_next.eq(Cat(self.data[-1], miso_sr)), # unload miso + # TODO: align to marker self.readback.eq(Cat([miso_sr_next[t_miso + i*t_clk] for i in range(n_frame)])), ] From e505dfed5bc868f8d6c79bc42df1f82f17a2a5c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Sat, 12 Sep 2020 14:17:40 +0000 Subject: [PATCH 20/51] phaser: refactor coredevice driver --- artiq/coredevice/phaser.py | 322 +++++++++++++++++++------------------ 1 file changed, 165 insertions(+), 157 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 877bdef21..3d5fc3892 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -93,12 +93,15 @@ class Phaser: def __init__(self, dmgr, channel_base, miso_delay=1, core_device="core"): self.channel_base = channel_base self.core = dmgr.get(core_device) + # TODO: auto-align miso-delay in phy self.miso_delay = miso_delay # frame duration in mu (10 words, 8 clock cycles each 4 ns) # self.core.seconds_to_mu(10*8*4*ns) # unfortunately this returns 319 assert self.core.ref_period == 1*ns self.t_frame = 10*8*4 + self.channel = [PhaserChannel(self, ch) for ch in range(2)] + @kernel def init(self): """Initialize the board. @@ -150,17 +153,6 @@ class Phaser: delay(20*us) # slack return data - @kernel - def write16(self, addr, data: TInt32): - """Write 16 bit to a sequence of FPGA registers.""" - self.write8(addr, data >> 8) - self.write8(addr + 1, data) - - @kernel - def read16(self, addr) -> TInt32: - """Read 16 bit from a sequence of FPGA registers.""" - return (self.read8(addr) << 8) | self.read8(addr) - @kernel def set_leds(self, leds): """Set the front panel LEDs. @@ -169,19 +161,32 @@ class Phaser: """ self.write8(PHASER_ADDR_LED, leds) + @kernel + def set_fan_mu(self, pwm): + """Set the fan duty cycle. + + :param pwm: Duty cycle (8 bit) + """ + self.write8(PHASER_ADDR_FAN, pwm) + @kernel def set_fan(self, duty): """Set the fan duty cycle. - :param duty: Duty cycle (8 bit) + :param duty: Duty cycle (0. to 1.) """ - self.write8(PHASER_ADDR_FAN, duty) + pwm = int32(round(duty*255.)) + if pwm < 0 or pwm > 0xff: + raise ValueError("invalid duty cycle") + self.set_fan_mu(pwm) @kernel def set_cfg(self, clk_sel=0, dac_resetb=1, dac_sleep=0, dac_txena=1, trf0_ps=0, trf1_ps=0, att0_rstn=1, att1_rstn=1): """Set the configuration register. + Each flag is a single bit (0 or 1). + :param clk_sel: Select the external SMA clock input :param dac_resetb: Active low DAC reset pin :param dac_sleep: DAC sleep pin @@ -192,9 +197,10 @@ class Phaser: :param att1_rstn: Active low attenuator 1 reset """ self.write8(PHASER_ADDR_CFG, - (clk_sel << 0) | (dac_resetb << 1) | (dac_sleep << 2) | - (dac_txena << 3) | (trf0_ps << 4) | (trf1_ps << 5) | - (att0_rstn << 6) | (att1_rstn << 7)) + ((clk_sel & 1) << 0) | ((dac_resetb & 1) << 1) | + ((dac_sleep & 1) << 2) | ((dac_txena & 1) << 3) | + ((trf0_ps & 1) << 4) | ((trf1_ps & 1) << 5) | + ((att0_rstn & 1) << 6) | ((att1_rstn & 1) << 7)) @kernel def get_sta(self): @@ -216,93 +222,13 @@ class Phaser: @kernel def get_crc_err(self): - """Get the frame CRC error counter.""" + """Get the frame CRC error counter. + + :return: The number of frames with CRC mismatches sind the reset of the + device. Overflows at 256. + """ return self.read8(PHASER_ADDR_CRC_ERR) - @kernel - def get_dac_data(self, ch) -> TInt32: - """Get a sample of the current DAC data. - - The data is split accross multiple registers and thus the data - is only valid if constant. - - :param ch: DAC channel pair (0 or 1) - :return: DAC data as 32 bit IQ - """ - data = 0 - for addr in range(4): - data <<= 8 - data |= self.read8(PHASER_ADDR_DAC0_DATA + (ch << 4) + addr) - delay(20*us) # slack - return data - - @kernel - def set_dac_test(self, ch, data: TInt32): - """Set the DAC test data. - - :param ch: DAC channel pair (0 or 1) - :param data: 32 bit IQ test data - """ - for addr in range(4): - byte = data >> 24 - self.write8(PHASER_ADDR_DAC0_TEST + (ch << 4) + addr, byte) - data <<= 8 - - @kernel - def set_duc_cfg(self, ch, clr=0, clr_once=0, select=0): - """Set the digital upconverter and interpolator configuration. - - :param ch: DAC channel pair (0 or 1) - :param clr: Keep the phase accumulator cleared - :param clr_once: Clear the phase accumulator for one cycle - :param select: Select the data to send to the DAC (0: DUC data, 1: test - data) - """ - self.write8(PHASER_ADDR_DUC0_CFG + (ch << 4), - (clr << 0) | (clr_once << 1) | (select << 2)) - - @kernel - def set_duc_frequency_mu(self, ch, ftw): - """Set the DUC frequency. - - :param ch: DAC channel pair (0 or 1) - :param ftw: DUC frequency tuning word - """ - self.write32(PHASER_ADDR_DUC0_F + (ch << 4), ftw) - - @kernel - def set_duc_frequency(self, ch, frequency): - """Set the DUC frequency. - - :param ch: DAC channel pair (0 or 1) - :param frequency: DUC frequency in Hz - """ - if ch < 0 or ch > 1: - raise ValueError("invalid channel index") - ftw = int32(round(frequency*((1 << 32)/500e6))) - self.set_duc_frequency_mu(ch, ftw) - - @kernel - def set_duc_phase_mu(self, ch, pow): - """Set the DUC phase offset - - :param ch: DAC channel pair (0 or 1) - :param pow: DUC phase offset word - """ - self.write16(PHASER_ADDR_DUC0_P + (ch << 4), pow) - - @kernel - def set_duc_phase(self, ch, phase): - """Set the DUC phase. - - :param ch: DAC channel pair (0 or 1) - :param phase: DUC phase in turns - """ - if ch < 0 or ch > 1: - raise ValueError("invalid channel index") - pow = int32(round(phase*(1 << 16))) & 0xffff - self.set_duc_phase_mu(ch, pow) - @kernel def duc_stb(self): """Strobe the DUC configuration register update. @@ -389,50 +315,141 @@ class Phaser: data |= self.spi_read() return data + +class PhaserChannel: + """Phaser channel IQ pair""" + kernel_invariants = {"channel", "phaser"} + + def __init__(self, phaser, channel): + self.phaser = phaser + self.channel = channel + self.oscillator = [PhaserOscillator(self, osc) for osc in range(5)] + @kernel - def att_write(self, ch, data): + def get_dac_data(self) -> TInt32: + """Get a sample of the current DAC data. + + The data is split accross multiple registers and thus the data + is only valid if constant. + + :return: DAC data as 32 bit IQ. I in the 16 LSB, Q in the 16 MSB + """ + return self.phaser.read32(PHASER_ADDR_DAC0_DATA + (self.channel << 4)) + + @kernel + def set_dac_test(self, data: TInt32): + """Set the DAC test data. + + :param data: 32 bit IQ test data, I in the 16 LSB, Q in the 16 MSB + """ + self.phaser.write32(PHASER_ADDR_DAC0_TEST + (self.channel << 4), data) + + @kernel + def set_duc_cfg(self, clr=0, clr_once=0, select=0): + """Set the digital upconverter and interpolator configuration. + + :param clr: Keep the phase accumulator cleared + :param clr_once: Clear the phase accumulator for one cycle + :param select: Select the data to send to the DAC (0: DUC data, 1: test + data) + """ + if select < 0 or select > 3: + raise ValueError("invalid data select") + self.phaser.write8(PHASER_ADDR_DUC0_CFG + (self.channel << 4), + ((clr & 1) << 0) | ((clr_once & 1) << 1) | + ((select & 3) << 2)) + + @kernel + def set_duc_frequency_mu(self, ftw): + """Set the DUC frequency. + + :param ftw: DUC frequency tuning word + """ + self.phaser.write32(PHASER_ADDR_DUC0_F + (self.channel << 4), ftw) + + @kernel + def set_duc_frequency(self, frequency): + """Set the DUC frequency. + + :param frequency: DUC frequency in Hz + """ + ftw = int32(round(frequency*((1 << 32)/500e6))) + self.set_duc_frequency_mu(ftw) + + @kernel + def set_duc_phase_mu(self, pow): + """Set the DUC phase offset + + :param pow: DUC phase offset word + """ + addr = PHASER_ADDR_DUC0_P + (self.channel << 4) + self.phaser.write8(addr, pow >> 8) + self.phaser.write8(addr + 1, pow) + + @kernel + def set_duc_phase(self, phase): + """Set the DUC phase. + + :param phase: DUC phase in turns + """ + pow = int32(round(phase*(1 << 16))) + self.set_duc_phase_mu(pow) + + @kernel + def set_att_mu(self, data): """Set channel attenuation. - :param ch: RF channel (0 or 1) :param data: Attenuator data """ div = 34 # 30 ns min period - t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) - self.spi_cfg(select=PHASER_SEL_ATT0 << ch, div=div, end=1) - self.spi_write(data) + t_xfer = self.phaser.core.seconds_to_mu((8 + 1)*div*4*ns) + self.phaser.spi_cfg(select=PHASER_SEL_ATT0 << self.channel, div=div, + end=1) + self.phaser.spi_write(data) delay_mu(t_xfer) @kernel - def att_read(self, ch) -> TInt32: + def set_att(self, att): + """Set channel attenuation in SI units. + + :param att: Attenuation in dB + """ + data = 0xff - int32(round(att*8)) + if data < 0 or data > 0xff: + raise ValueError("invalid attenuation") + self.set_att_mu(data) + + @kernel + def get_att_mu(self) -> TInt32: """Read current attenuation. The current attenuation value is read without side effects. - :param ch: RF channel (0 or 1) :return: Current attenuation """ div = 34 - t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) - self.spi_cfg(select=PHASER_SEL_ATT0 << ch, div=div, end=0) - self.spi_write(0) + t_xfer = self.phaser.core.seconds_to_mu((8 + 1)*div*4*ns) + self.phaser.spi_cfg(select=PHASER_SEL_ATT0 << self.channel, div=div, + end=0) + self.phaser.spi_write(0) delay_mu(t_xfer) - data = self.spi_read() + data = self.phaser.spi_read() delay(10*us) # slack - self.spi_cfg(select=PHASER_SEL_ATT0 << ch, div=div, end=1) - self.spi_write(data) + self.phaser.spi_cfg(select=PHASER_SEL_ATT0 << self.channel, div=div, + end=1) + self.phaser.spi_write(data) delay_mu(t_xfer) return data @kernel - def trf_write(self, ch, data, readback=False): + def trf_write(self, data, readback=False): """Write 32 bits to a TRF upconverter. - :param ch: RF channel (0 or 1) - :param data: Register data (32 bit) + :param data: Register data (32 bit) containing encoded address :param readback: Whether to return the read back MISO data """ div = 34 # 50 ns min period - t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) + t_xfer = self.phaser.core.seconds_to_mu((8 + 1)*div*4*ns) read = 0 end = 0 clk_phase = 0 @@ -442,91 +459,82 @@ class Phaser: if i == 0 or i == 3: if i == 3: end = 1 - self.spi_cfg(select=PHASER_SEL_TRF0 << ch, div=div, - lsb_first=1, clk_phase=clk_phase, end=end) - self.spi_write(data) + self.phaser.spi_cfg(select=PHASER_SEL_TRF0 << self.channel, + div=div, lsb_first=1, clk_phase=clk_phase, + end=end) + self.phaser.spi_write(data) data >>= 8 delay_mu(t_xfer) if readback: read >>= 8 - read |= self.spi_read() << 24 + read |= self.phaser.spi_read() << 24 delay(10*us) # slack return read @kernel - def trf_read(self, ch, addr, cnt_mux_sel=0) -> TInt32: + def trf_read(self, addr, cnt_mux_sel=0) -> TInt32: """TRF upconverter register read. - :param ch: RF channel (0 or 1) - :param addr: Register address to read + :param addr: Register address to read (0 to 7) :param cnt_mux_sel: Report VCO counter min frequency or max frequency :return: Register data (32 bit) """ - self.trf_write(ch, 0x80000008 | (addr << 28) | (cnt_mux_sel << 27)) + self.trf_write(0x80000008 | (addr << 28) | (cnt_mux_sel << 27)) # single clk pulse with ~LE to start readback - self.spi_cfg(select=0, div=34, end=1, length=1) - self.spi_write(0) + self.phaser.spi_cfg(select=0, div=34, end=1, length=1) + self.phaser.spi_write(0) delay((1 + 1)*32*4*ns) - return self.trf_write(ch, 0x00000008, readback=True) + return self.trf_write(0x00000008, readback=True) + + +class PhaserOscillator: + """Phaser IQ channel oscillator""" + kernel_invariants = {"channel", "base_addr"} + + def __init__(self, channel, oscillator): + self.channel = channel + self.base_addr = ((self.channel.phaser.channel_base + 1 + + self.channel.channel) << 8) | (oscillator << 1) @kernel - def set_frequency_mu(self, ch, osc, ftw): + def set_frequency_mu(self, ftw): """Set Phaser MultiDDS frequency tuning word. - :param ch: RF channel (0 or 1) - :param osc: Oscillator number (0 to 4) :param ftw: Frequency tuning word (32 bit) """ - addr = ((self.channel_base + 1 + ch) << 8) | (osc << 1) - rtio_output(addr, ftw) + rtio_output(self.base_addr, ftw) @kernel - def set_frequency(self, ch, osc, frequency): + def set_frequency(self, frequency): """Set Phaser MultiDDS frequency. - :param ch: RF channel (0 or 1) - :param osc: Oscillator number (0 to 4) :param frequency: Frequency in Hz """ - if ch < 0 or ch > 1: - raise ValueError("invalid channel index") - if osc < 0 or osc > 4: - raise ValueError("invalid oscillator index") ftw = int32(round(frequency*((1 << 32)/125e6))) - self.set_frequency_mu(ch, osc, ftw) - + self.set_frequency_mu(ftw) @kernel - def set_amplitude_phase_mu(self, ch, osc, asf=0x7fff, pow=0, clr=0): + def set_amplitude_phase_mu(self, asf=0x7fff, pow=0, clr=0): """Set Phaser MultiDDS amplitude, phase offset and accumulator clear. - :param ch: RF channel (0 or 1) - :param osc: Oscillator number (0 to 4) :param asf: Amplitude (15 bit) :param pow: Phase offset word (16 bit) :param clr: Clear the phase accumulator (persistent) """ - addr = ((self.channel_base + 1 + ch) << 8) | (osc << 1) | 1 - data = (asf & 0x7fff) | ((clr & 1) << 15) | ((pow & 0xffff) << 16) - rtio_output(addr, data) + data = (asf & 0x7fff) | ((clr & 1) << 15) | (pow << 16) + rtio_output(self.base_addr | 1, data) @kernel - def set_amplitude_phase(self, ch, osc, amplitude, phase=0., clr=0): + def set_amplitude_phase(self, amplitude, phase=0., clr=0): """Set Phaser MultiDDS amplitude and phase. - :param ch: RF channel (0 or 1) - :param osc: Oscillator number (0 to 4) :param amplitude: Amplitude in units of full scale :param phase: Phase in turns :param clr: Clear the phase accumulator (persistent) """ - if ch < 0 or ch > 1: - raise ValueError("invalid channel index") - if osc < 0 or osc > 4: - raise ValueError("invalid oscillator index") asf = int32(round(amplitude*0x7fff)) if asf < 0 or asf > 0x7fff: raise ValueError("invalid amplitude") pow = int32(round(phase*(1 << 16))) - self.set_amplitude_phase_mu(ch, osc, asf, pow, clr) + self.set_amplitude_phase_mu(asf, pow, clr) From c3728678d615c87b43522de2dc15c3c57c322e59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Sat, 12 Sep 2020 17:19:10 +0000 Subject: [PATCH 21/51] phaser: document, elaborate comments, some fixes --- artiq/coredevice/phaser.py | 159 +++++++++++++++++++++++-------------- 1 file changed, 99 insertions(+), 60 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 3d5fc3892..f61f3edf4 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -1,6 +1,6 @@ from artiq.language.core import kernel, delay_mu, delay from artiq.coredevice.rtio import rtio_output, rtio_input_data -from artiq.language.units import us, ns +from artiq.language.units import us, ns, MHz from artiq.language.types import TInt32 @@ -57,36 +57,56 @@ class Phaser: Phaser contains a 4 channel, 1 GS/s DAC chip with integrated upconversion, quadrature modulation compensation and interpolation features. - The coredevice produces 2 IQ data streams with 25 MS/s 14 bit. Each - data stream supports 5 independent numerically controlled oscillators (NCOs) - added together for each channel. Together with a data clock, framing - marker, a checksum and metadata for register access the data is sent in - groups of 8 samples over 1.5 Gb/s FastLink via a single EEM connector. + The coredevice produces 2 IQ data streams with 25 MS/s and 14 bit per + quadrature. Each data stream supports 5 independent numerically controlled + IQ oscillators (NCOs, DDSs with 32 bit frequency, 16 bit phase, 15 bit + amplitude, and phase accumulator clear functionality) added together. + See :class:`PhaserChannel` and :class:`PhaserOscillator`. - On Phaser the data streams are buffered and interpolated from 25 MS/s to 500 - MS/s 16 bit followed by a 500 MS/s digital upconverter in the FPGA. + Together with a data clock, framing marker, a checksum and metadata for + register access the streams are sent in groups of 8 samples over 1.5 Gb/s + FastLink via a single EEM connector from coredevice to Phaser. + + On Phaser in the FPGA the data streams are buffered and interpolated + from 25 MS/s to 500 MS/s 16 bit followed by a 500 MS/s digital upconverter + with adjustable frequency and phase. The interpolation passband is 20 MHz + wide, passband ripple is less than 1e-3 amplitude, stopband attenuation + is better than 75 dB at offsets > 15 MHz and better than 90 dB at offsets + > 30 MHz. The four 16 bit 500 MS/s DAC data streams are sent via a 32 bit parallel - LVDS bus operating at 1 Gb/s per pin pair and processed in the DAC. + LVDS bus operating at 1 Gb/s per pin pair and processed in the DAC. On the + DAC 2x interpolation, sinx/x compensation, quadrature modulator compensation, + fine and coarse mixing as well as group delay capabilities are available. - The four analog DAC outputs are passed through anti-aliasing filters and In - the baseband variant, the even channels feed 31.5 dB range and are - available on the front panel. The odd outputs are available on MMCX - connectors on board. + The latency/group delay from the RTIO events setting + :class:`PhaserOscillator` or :class:`PhaserChannel` DUC parameters all they + way to the DAC outputs is deterministic. This enables deterministic + absolute phase with respect to other RTIO input and output events. + + The four analog DAC outputs are passed through anti-aliasing filters. + + In the baseband variant, the even/in-phase DAC channels feed 31.5 dB range + attenuators and are available on the front panel. The odd outputs are + available at MMCX connectors on board. In the upconverter variant, each of the two IQ (in-phase and quadrature) output pairs feeds a one quadrature upconverter with integrated PLL/VCO. - The output from the upconverter passes through the step attenuator and is - available at the front panel. + This analog quadrature upconverter supports offset tuning for carrier and + sideband suppression. The output from the upconverter passes through the + 31.5 dB range step attenuator and is available at the front panel. - The DAC, the TRF upconverters and the two attenuators are configured - through a shared SPI bus that is accessed and controlled via FPGA + The DAC, the analog quadrature upconverters and the two attenuators are + configured through a shared SPI bus that is accessed and controlled via FPGA registers. :param channel: Base RTIO channel number :param core_device: Core device name (default: "core") :param miso_delay: Fastlink MISO signal delay to account for cable and buffer round trip. This might be automated later. + + :attr:`channel`: List of two :class:`PhaserChannel` to access oscillators + and digital upconverter. """ kernel_invariants = {"core", "channel_base", "t_frame", "miso_delay"} @@ -176,7 +196,7 @@ class Phaser: :param duty: Duty cycle (0. to 1.) """ pwm = int32(round(duty*255.)) - if pwm < 0 or pwm > 0xff: + if pwm < 0 or pwm > 255: raise ValueError("invalid duty cycle") self.set_fan_mu(pwm) @@ -191,8 +211,8 @@ class Phaser: :param dac_resetb: Active low DAC reset pin :param dac_sleep: DAC sleep pin :param dac_txena: Enable DAC transmission pin - :param trf0_ps: TRF0 upconverter power save - :param trf1_ps: TRF1 upconverter power save + :param trf0_ps: Quadrature upconverter 0 power save + :param trf1_ps: Quadrature upconverter 1 power save :param att0_rstn: Active low attenuator 0 reset :param att1_rstn: Active low attenuator 1 reset """ @@ -209,8 +229,8 @@ class Phaser: Bit flags are: * `PHASER_STA_DAC_ALARM`: DAC alarm pin - * `PHASER_STA_TRF0_LD`: TRF0 lock detect pin - * `PHASER_STA_TRF1_LD`: TRF1 lock detect pin + * `PHASER_STA_TRF0_LD`: Quadrature upconverter 0 lock detect + * `PHASER_STA_TRF1_LD`: Quadrature upconverter 1 lock detect * `PHASER_STA_TERM0`: ADC channel 0 termination indicator * `PHASER_STA_TERM1`: ADC channel 1 termination indicator * `PHASER_STA_SPI_IDLE`: SPI machine is idle and data registers can be @@ -255,7 +275,7 @@ class Phaser: """ if div < 2 or div > 257: raise ValueError("invalid divider") - if length < 0 or length > 8: + if length < 1 or length > 8: raise ValueError("invalid length") self.write8(PHASER_ADDR_SPI_SEL, select) self.write8(PHASER_ADDR_SPI_DIVLEN, (div - 2 >> 3) | (length - 1 << 5)) @@ -298,8 +318,8 @@ class Phaser: """Read from a DAC register. :param addr: Register address to read from - :param div: SPI clock divider. Needs to be at least 250 to read the - temperature register. + :param div: SPI clock divider. Needs to be at least 250 (1 µs SPI + clock) to read the temperature register. """ t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=0) @@ -308,7 +328,7 @@ class Phaser: self.spi_write(0) delay_mu(t_xfer) data = self.spi_read() << 8 - delay(10*us) # slack + delay(20*us) # slack self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=1) self.spi_write(0) delay_mu(t_xfer) @@ -317,12 +337,27 @@ class Phaser: class PhaserChannel: - """Phaser channel IQ pair""" - kernel_invariants = {"channel", "phaser"} + """Phaser channel IQ pair. - def __init__(self, phaser, channel): + :attr:`oscillator`: List of five :class:`PhaserOscillator`. + + .. note:: The amplitude sum of the oscillators must be less than one to + avoid clipping or overflow. If any of the DDS or DUC frequencies are + non-zero, it is not sufficient to ensure that the sum in each + quadrature is within range. + + .. note:: The interpolation filter on Phaser has an intrinsic sinc-like + overshoot in its step response. That overshoot is an direct consequence + of its near-brick-wall frequency response. For large and wide-band + changes in oscillator parameters, the overshoot can lead to clipping + or overflow after the interpolation. Either band-limit any changes + in the oscillator parameters or back off the amplitude sufficiently. + """ + kernel_invariants = {"index", "phaser"} + + def __init__(self, phaser, index): self.phaser = phaser - self.channel = channel + self.index = index self.oscillator = [PhaserOscillator(self, osc) for osc in range(5)] @kernel @@ -334,7 +369,7 @@ class PhaserChannel: :return: DAC data as 32 bit IQ. I in the 16 LSB, Q in the 16 MSB """ - return self.phaser.read32(PHASER_ADDR_DAC0_DATA + (self.channel << 4)) + return self.phaser.read32(PHASER_ADDR_DAC0_DATA + (self.index << 4)) @kernel def set_dac_test(self, data: TInt32): @@ -342,20 +377,18 @@ class PhaserChannel: :param data: 32 bit IQ test data, I in the 16 LSB, Q in the 16 MSB """ - self.phaser.write32(PHASER_ADDR_DAC0_TEST + (self.channel << 4), data) + self.phaser.write32(PHASER_ADDR_DAC0_TEST + (self.index << 4), data) @kernel def set_duc_cfg(self, clr=0, clr_once=0, select=0): """Set the digital upconverter and interpolator configuration. - :param clr: Keep the phase accumulator cleared + :param clr: Keep the phase accumulator cleared (persistent) :param clr_once: Clear the phase accumulator for one cycle :param select: Select the data to send to the DAC (0: DUC data, 1: test - data) + data, other values: reserved) """ - if select < 0 or select > 3: - raise ValueError("invalid data select") - self.phaser.write8(PHASER_ADDR_DUC0_CFG + (self.channel << 4), + self.phaser.write8(PHASER_ADDR_DUC0_CFG + (self.index << 4), ((clr & 1) << 0) | ((clr_once & 1) << 1) | ((select & 3) << 2)) @@ -363,26 +396,27 @@ class PhaserChannel: def set_duc_frequency_mu(self, ftw): """Set the DUC frequency. - :param ftw: DUC frequency tuning word + :param ftw: DUC frequency tuning word (32 bit) """ - self.phaser.write32(PHASER_ADDR_DUC0_F + (self.channel << 4), ftw) + self.phaser.write32(PHASER_ADDR_DUC0_F + (self.index << 4), ftw) @kernel def set_duc_frequency(self, frequency): """Set the DUC frequency. - :param frequency: DUC frequency in Hz + :param frequency: DUC frequency in Hz (passband from -200 MHz to + 200 MHz, wrapping around at +- 250 MHz) """ - ftw = int32(round(frequency*((1 << 32)/500e6))) + ftw = int32(round(frequency*((1 << 32)/(500*MHz)))) self.set_duc_frequency_mu(ftw) @kernel def set_duc_phase_mu(self, pow): """Set the DUC phase offset - :param pow: DUC phase offset word + :param pow: DUC phase offset word (16 bit) """ - addr = PHASER_ADDR_DUC0_P + (self.channel << 4) + addr = PHASER_ADDR_DUC0_P + (self.index << 4) self.phaser.write8(addr, pow >> 8) self.phaser.write8(addr + 1, pow) @@ -403,7 +437,7 @@ class PhaserChannel: """ div = 34 # 30 ns min period t_xfer = self.phaser.core.seconds_to_mu((8 + 1)*div*4*ns) - self.phaser.spi_cfg(select=PHASER_SEL_ATT0 << self.channel, div=div, + self.phaser.spi_cfg(select=PHASER_SEL_ATT0 << self.index, div=div, end=1) self.phaser.spi_write(data) delay_mu(t_xfer) @@ -425,17 +459,17 @@ class PhaserChannel: The current attenuation value is read without side effects. - :return: Current attenuation + :return: Current attenuation in machine units """ div = 34 t_xfer = self.phaser.core.seconds_to_mu((8 + 1)*div*4*ns) - self.phaser.spi_cfg(select=PHASER_SEL_ATT0 << self.channel, div=div, + self.phaser.spi_cfg(select=PHASER_SEL_ATT0 << self.index, div=div, end=0) self.phaser.spi_write(0) delay_mu(t_xfer) data = self.phaser.spi_read() - delay(10*us) # slack - self.phaser.spi_cfg(select=PHASER_SEL_ATT0 << self.channel, div=div, + delay(20*us) # slack + self.phaser.spi_cfg(select=PHASER_SEL_ATT0 << self.index, div=div, end=1) self.phaser.spi_write(data) delay_mu(t_xfer) @@ -443,7 +477,7 @@ class PhaserChannel: @kernel def trf_write(self, data, readback=False): - """Write 32 bits to a TRF upconverter. + """Write 32 bits to upconverter. :param data: Register data (32 bit) containing encoded address :param readback: Whether to return the read back MISO data @@ -459,7 +493,7 @@ class PhaserChannel: if i == 0 or i == 3: if i == 3: end = 1 - self.phaser.spi_cfg(select=PHASER_SEL_TRF0 << self.channel, + self.phaser.spi_cfg(select=PHASER_SEL_TRF0 << self.index, div=div, lsb_first=1, clk_phase=clk_phase, end=end) self.phaser.spi_write(data) @@ -468,34 +502,38 @@ class PhaserChannel: if readback: read >>= 8 read |= self.phaser.spi_read() << 24 - delay(10*us) # slack + delay(20*us) # slack return read @kernel def trf_read(self, addr, cnt_mux_sel=0) -> TInt32: - """TRF upconverter register read. + """Quadrature upconverter register read. :param addr: Register address to read (0 to 7) - :param cnt_mux_sel: Report VCO counter min frequency - or max frequency + :param cnt_mux_sel: Report VCO counter min or max frequency :return: Register data (32 bit) """ self.trf_write(0x80000008 | (addr << 28) | (cnt_mux_sel << 27)) # single clk pulse with ~LE to start readback self.phaser.spi_cfg(select=0, div=34, end=1, length=1) self.phaser.spi_write(0) - delay((1 + 1)*32*4*ns) + delay((1 + 1)*34*4*ns) return self.trf_write(0x00000008, readback=True) class PhaserOscillator: - """Phaser IQ channel oscillator""" + """Phaser IQ channel oscillator (NCO/DDS). + + .. note:: Latencies between oscillators within a channel and between + oscillator paramters (amplitude and phase/frequency) are deterministic + (with respect to the 25 MS/s sample clock) but not matched. + """ kernel_invariants = {"channel", "base_addr"} - def __init__(self, channel, oscillator): + def __init__(self, channel, index): self.channel = channel self.base_addr = ((self.channel.phaser.channel_base + 1 + - self.channel.channel) << 8) | (oscillator << 1) + self.channel.index) << 8) | (index << 1) @kernel def set_frequency_mu(self, ftw): @@ -509,9 +547,10 @@ class PhaserOscillator: def set_frequency(self, frequency): """Set Phaser MultiDDS frequency. - :param frequency: Frequency in Hz + :param frequency: Frequency in Hz (passband from -10 MHz to 10 MHz, + wrapping around at +- 12.5 MHz) """ - ftw = int32(round(frequency*((1 << 32)/125e6))) + ftw = int32(round(frequency*((1 << 32)/(25*MHz)))) self.set_frequency_mu(ftw) @kernel From b619f657b92b3b59ef0a2d4e2f0a5c9c1f7afced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Sat, 12 Sep 2020 19:59:49 +0200 Subject: [PATCH 22/51] phaser: doc tweaks --- artiq/coredevice/phaser.py | 39 ++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index f61f3edf4..98777bd25 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -76,8 +76,9 @@ class Phaser: The four 16 bit 500 MS/s DAC data streams are sent via a 32 bit parallel LVDS bus operating at 1 Gb/s per pin pair and processed in the DAC. On the - DAC 2x interpolation, sinx/x compensation, quadrature modulator compensation, - fine and coarse mixing as well as group delay capabilities are available. + DAC 2x interpolation, sinx/x compensation, quadrature modulator + compensation, fine and coarse mixing as well as group delay capabilities + are available. The latency/group delay from the RTIO events setting :class:`PhaserOscillator` or :class:`PhaserChannel` DUC parameters all they @@ -97,16 +98,19 @@ class Phaser: 31.5 dB range step attenuator and is available at the front panel. The DAC, the analog quadrature upconverters and the two attenuators are - configured through a shared SPI bus that is accessed and controlled via FPGA - registers. + configured through a shared SPI bus that is accessed and controlled via + FPGA registers. :param channel: Base RTIO channel number :param core_device: Core device name (default: "core") :param miso_delay: Fastlink MISO signal delay to account for cable and buffer round trip. This might be automated later. - :attr:`channel`: List of two :class:`PhaserChannel` to access oscillators - and digital upconverter. + Attributes: + + * :attr:`channel`: List of two :class:`PhaserChannel` + To access oscillators, digital upconverters, PLL/VCO analog + quadrature upconverters and attenuators. """ kernel_invariants = {"core", "channel_base", "t_frame", "miso_delay"} @@ -185,7 +189,7 @@ class Phaser: def set_fan_mu(self, pwm): """Set the fan duty cycle. - :param pwm: Duty cycle (8 bit) + :param pwm: Duty cycle in machine units (8 bit) """ self.write8(PHASER_ADDR_FAN, pwm) @@ -228,13 +232,13 @@ class Phaser: Bit flags are: - * `PHASER_STA_DAC_ALARM`: DAC alarm pin - * `PHASER_STA_TRF0_LD`: Quadrature upconverter 0 lock detect - * `PHASER_STA_TRF1_LD`: Quadrature upconverter 1 lock detect - * `PHASER_STA_TERM0`: ADC channel 0 termination indicator - * `PHASER_STA_TERM1`: ADC channel 1 termination indicator - * `PHASER_STA_SPI_IDLE`: SPI machine is idle and data registers can be - read/written + * :const:`PHASER_STA_DAC_ALARM`: DAC alarm pin + * :const:`PHASER_STA_TRF0_LD`: Quadrature upconverter 0 lock detect + * :const:`PHASER_STA_TRF1_LD`: Quadrature upconverter 1 lock detect + * :const:`PHASER_STA_TERM0`: ADC channel 0 termination indicator + * :const:`PHASER_STA_TERM1`: ADC channel 1 termination indicator + * :const:`PHASER_STA_SPI_IDLE`: SPI machine is idle and data registers + can be read/written :return: Status register """ @@ -339,7 +343,9 @@ class Phaser: class PhaserChannel: """Phaser channel IQ pair. - :attr:`oscillator`: List of five :class:`PhaserOscillator`. + Attributes: + + * :attr:`oscillator`: List of five :class:`PhaserOscillator`. .. note:: The amplitude sum of the oscillators must be less than one to avoid clipping or overflow. If any of the DDS or DUC frequencies are @@ -433,7 +439,7 @@ class PhaserChannel: def set_att_mu(self, data): """Set channel attenuation. - :param data: Attenuator data + :param data: Attenuator data in machine units (8 bit) """ div = 34 # 30 ns min period t_xfer = self.phaser.core.seconds_to_mu((8 + 1)*div*4*ns) @@ -448,6 +454,7 @@ class PhaserChannel: :param att: Attenuation in dB """ + # 2 lsb are inactive, resulting in 8 LSB per dB data = 0xff - int32(round(att*8)) if data < 0 or data > 0xff: raise ValueError("invalid attenuation") From b449e7202b0a9dda9a2ef1847005cdc9a736970c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 14 Sep 2020 07:34:55 +0000 Subject: [PATCH 23/51] phaser: rework docs --- artiq/coredevice/phaser.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 98777bd25..7449ab19b 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -57,11 +57,11 @@ class Phaser: Phaser contains a 4 channel, 1 GS/s DAC chip with integrated upconversion, quadrature modulation compensation and interpolation features. - The coredevice produces 2 IQ data streams with 25 MS/s and 14 bit per - quadrature. Each data stream supports 5 independent numerically controlled - IQ oscillators (NCOs, DDSs with 32 bit frequency, 16 bit phase, 15 bit - amplitude, and phase accumulator clear functionality) added together. - See :class:`PhaserChannel` and :class:`PhaserOscillator`. + The coredevice produces 2 IQ (in-phase and quadrature) data streams with 25 + MS/s and 14 bit per quadrature. Each data stream supports 5 independent + numerically controlled IQ oscillators (NCOs, DDSs with 32 bit frequency, 16 + bit phase, 15 bit amplitude, and phase accumulator clear functionality) + added together. See :class:`PhaserChannel` and :class:`PhaserOscillator`. Together with a data clock, framing marker, a checksum and metadata for register access the streams are sent in groups of 8 samples over 1.5 Gb/s @@ -91,13 +91,13 @@ class Phaser: attenuators and are available on the front panel. The odd outputs are available at MMCX connectors on board. - In the upconverter variant, each of the two IQ (in-phase and quadrature) - output pairs feeds a one quadrature upconverter with integrated PLL/VCO. - This analog quadrature upconverter supports offset tuning for carrier and - sideband suppression. The output from the upconverter passes through the - 31.5 dB range step attenuator and is available at the front panel. + In the upconverter variant, each IQ output pair feeds a one quadrature + upconverter with integrated PLL/VCO. This analog quadrature upconverter + supports offset tuning for carrier and sideband suppression. The output + from the upconverter passes through the 31.5 dB range step attenuator and + is available at the front panel. - The DAC, the analog quadrature upconverters and the two attenuators are + The DAC, the analog quadrature upconverters and the attenuators are configured through a shared SPI bus that is accessed and controlled via FPGA registers. @@ -373,7 +373,8 @@ class PhaserChannel: The data is split accross multiple registers and thus the data is only valid if constant. - :return: DAC data as 32 bit IQ. I in the 16 LSB, Q in the 16 MSB + :return: DAC data as 32 bit IQ. I/DACA/DACC in the 16 LSB, + Q/DACB/DACD in the 16 MSB """ return self.phaser.read32(PHASER_ADDR_DAC0_DATA + (self.index << 4)) @@ -381,7 +382,8 @@ class PhaserChannel: def set_dac_test(self, data: TInt32): """Set the DAC test data. - :param data: 32 bit IQ test data, I in the 16 LSB, Q in the 16 MSB + :param data: 32 bit IQ test data, I/DACA/DACC in the 16 LSB, + Q/DACB/DACD in the 16 MSB """ self.phaser.write32(PHASER_ADDR_DAC0_TEST + (self.index << 4), data) From 3a79ef740b7af332df289bc17d23f4ec7c783f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 14 Sep 2020 08:31:54 +0000 Subject: [PATCH 24/51] phaser: work around integer size --- artiq/coredevice/phaser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 7449ab19b..ad7f6b028 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -415,7 +415,7 @@ class PhaserChannel: :param frequency: DUC frequency in Hz (passband from -200 MHz to 200 MHz, wrapping around at +- 250 MHz) """ - ftw = int32(round(frequency*((1 << 32)/(500*MHz)))) + ftw = int32(round(frequency*((1 << 31)/(250*MHz)))) self.set_duc_frequency_mu(ftw) @kernel @@ -559,7 +559,7 @@ class PhaserOscillator: :param frequency: Frequency in Hz (passband from -10 MHz to 10 MHz, wrapping around at +- 12.5 MHz) """ - ftw = int32(round(frequency*((1 << 32)/(25*MHz)))) + ftw = int32(round(frequency*((1 << 31)/(12.5*MHz)))) self.set_frequency_mu(ftw) @kernel From 07418258ae9c8b9f444e5843844a134bfc7929af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 14 Sep 2020 13:12:34 +0000 Subject: [PATCH 25/51] phaser: init [wip] --- artiq/coredevice/phaser.py | 123 ++++++++++++++++++++++++++++++++++--- 1 file changed, 115 insertions(+), 8 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index ad7f6b028..ccdc10970 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -1,6 +1,6 @@ from artiq.language.core import kernel, delay_mu, delay from artiq.coredevice.rtio import rtio_output, rtio_input_data -from artiq.language.units import us, ns, MHz +from artiq.language.units import us, ns, ms, MHz, dB from artiq.language.types import TInt32 @@ -50,6 +50,8 @@ PHASER_STA_SPI_IDLE = 1 << 5 PHASER_DAC_SEL_DUC = 0 PHASER_DAC_SEL_TEST = 1 +PHASER_HW_REV_VARIANT = 1 << 4 + class Phaser: """Phaser 4-channel, 16-bit, 1 GS/s DAC coredevice driver. @@ -130,14 +132,73 @@ class Phaser: def init(self): """Initialize the board. - Verifies board presence by reading the board ID register. - Does not alter any state. + Verifies board and chip presence, resets components, performs communication + and configuration tests and establishes initial conditions. """ board_id = self.read8(PHASER_ADDR_BOARD_ID) if board_id != PHASER_BOARD_ID: raise ValueError("invalid board id") delay(20*us) # slack + # allow a few errors during startup and alignment + if self.get_crc_err() > 20: + raise ValueError("large number of CRC errors") + delay(.1*ms) # slack + + self.set_cfg(dac_resetb=0, att0_rstn=0, att1_rstn=0) + self.set_leds(0x00) + self.set_fan_mu(0) + self.set_cfg() # bring everything out of reset + delay(.1*ms) # slack + + # 4 wire SPI, sif4_enable + self.dac_write(0x02, 0x0082) + if self.dac_read(0x7f) != 0x5409: + raise ValueError("DAC version readback invalid") + delay(.1*ms) + if self.dac_read(0x00) != 0x049c: + raise ValueError("DAC config0 readback invalid") + delay(.1*ms) + + t = self.get_dac_temperature() + if t < 10 or t > 90: + raise ValueError("DAC temperature out of bounds") + delay(.1*ms) + + patterns = [ + [0xffff, 0xffff, 0x0000, 0x0000], # test channel + [0xaa55, 0x55aa, 0x55aa, 0xaa5a], # test iq + [0xaa55, 0xaa55, 0x55aa, 0x55aa], # test byte + [0x7a7a, 0xb6b6, 0xeaea, 0x4545], # ds pattern a + [0x1a1a, 0x1616, 0xaaaa, 0xc6c6], # ds pattern b + ] + delay(.5*ms) + for i in range(len(patterns)): + errors = self.dac_iotest(patterns[i]) + if errors: + raise ValueError("iotest error") + delay(.5*ms) + + hw_rev = self.read8(PHASER_ADDR_HW_REV) + has_upconverter = hw_rev & PHASER_HW_REV_VARIANT + delay(.1*ms) # slack + + for ch in range(2): + # test attenuator write and readback + self.channel[ch].set_att_mu(0x55) + if self.channel[ch].get_att_mu() != 0x55: + raise ValueError("attenuator test failed") + delay(.1*ms) + self.channel[ch].set_att(31.5*dB) + + # dac test data readback + dac_test = [0x10102020, 0x30304040] + self.channel[ch].set_duc_cfg(select=1) + self.channel[ch].set_dac_test(dac_test[ch]) + if self.channel[ch].get_dac_data() != dac_test[ch]: + raise ValueError("DAC test data readback failed") + delay(.1*ms) + @kernel def write8(self, addr, data): """Write data to FPGA register. @@ -201,7 +262,7 @@ class Phaser: """ pwm = int32(round(duty*255.)) if pwm < 0 or pwm > 255: - raise ValueError("invalid duty cycle") + raise ValueError("duty cycle out of bounds") self.set_fan_mu(pwm) @kernel @@ -278,9 +339,9 @@ class Phaser: :param length: SPI transfer length (1 to 8 bits) """ if div < 2 or div > 257: - raise ValueError("invalid divider") + raise ValueError("divider out of bounds") if length < 1 or length > 8: - raise ValueError("invalid length") + raise ValueError("length out of bounds") self.write8(PHASER_ADDR_SPI_SEL, select) self.write8(PHASER_ADDR_SPI_DIVLEN, (div - 2 >> 3) | (length - 1 << 5)) self.write8(PHASER_ADDR_SPI_CFG, @@ -339,6 +400,52 @@ class Phaser: data |= self.spi_read() return data + @kernel + def get_dac_temperature(self) -> TInt32: + """Read the DAC die temperature. + + :return: DAC temperature in degree Celsius + """ + return self.dac_read(0x06, div=257) >> 8 + + @kernel + def dac_iotest(self, pattern) -> TInt32: + """Performs a DAC IO test according to the datasheet. + + :param patterm: List of four int32 containing the pattern + :return: Bit error mask (16 bits) + """ + for addr in range(len(pattern)): + self.dac_write(0x25 + addr, pattern[addr]) + self.dac_write(0x29 + addr, pattern[addr]) + delay(.1*ms) + for ch in range(2): + self.channel[ch].set_duc_cfg(select=1) # test + # dac test data is i msb, q lsb + self.channel[ch].set_dac_test(pattern[2*ch] | (pattern[2*ch + 1] << 16)) + self.dac_write(0x01, 0x8000) # iotest_ena + errors = 0 + # A data delay of 3*50 ps heuristically matches FPGA+board+DAC skews. + # There is plenty of margin and no need to tune at runtime. + # Parity provides another level of safety. + for dly in [-3]: # range(-7, 8) + if dly < 0: + dly = -dly << 3 # data delay, else clock delay + self.dac_write(0x24, dly << 10) + self.dac_write(0x04, 0x0000) # clear iotest_result + delay(.2*ms) # let it rip + # no need to go through the alarm register, + # just read the error mask + # self.dac_write(0x05, 0x0000) # clear alarms + # alarm = self.dac_read(0x05) + # delay(.1*ms) # slack + # if alarm & 0x0080: # alarm_from_iotest + errors |= self.dac_read(0x04) + delay(.1*ms) # slack + self.dac_write(0x24, 0) # reset delays + # self.dac_write(0x01, 0x0000) # clear config + return errors + class PhaserChannel: """Phaser channel IQ pair. @@ -459,7 +566,7 @@ class PhaserChannel: # 2 lsb are inactive, resulting in 8 LSB per dB data = 0xff - int32(round(att*8)) if data < 0 or data > 0xff: - raise ValueError("invalid attenuation") + raise ValueError("attenuation out of bounds") self.set_att_mu(data) @kernel @@ -583,6 +690,6 @@ class PhaserOscillator: """ asf = int32(round(amplitude*0x7fff)) if asf < 0 or asf > 0x7fff: - raise ValueError("invalid amplitude") + raise ValueError("amplitude out of bounds") pow = int32(round(phase*(1 << 16))) self.set_amplitude_phase_mu(asf, pow, clr) From ff57813a9cfbdc03ff0fdbe9eae0e8a639e9c98b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 14 Sep 2020 13:12:44 +0000 Subject: [PATCH 26/51] phaser: init [wip] --- artiq/coredevice/phaser.py | 94 ++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 28 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index ccdc10970..5c3dfddc6 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -129,18 +129,20 @@ class Phaser: self.channel = [PhaserChannel(self, ch) for ch in range(2)] @kernel - def init(self): + def init(self, clk_sel=0): """Initialize the board. Verifies board and chip presence, resets components, performs communication and configuration tests and establishes initial conditions. + + :param clk_sel: Select the external SMA clock input (1 or 0) """ board_id = self.read8(PHASER_ADDR_BOARD_ID) if board_id != PHASER_BOARD_ID: raise ValueError("invalid board id") delay(20*us) # slack - # allow a few errors during startup and alignment + # allow a few errors during startup and alignment if self.get_crc_err() > 20: raise ValueError("large number of CRC errors") delay(.1*ms) # slack @@ -148,7 +150,7 @@ class Phaser: self.set_cfg(dac_resetb=0, att0_rstn=0, att1_rstn=0) self.set_leds(0x00) self.set_fan_mu(0) - self.set_cfg() # bring everything out of reset + self.set_cfg(clk_sel=clk_sel) # bring everything out of reset delay(.1*ms) # slack # 4 wire SPI, sif4_enable @@ -165,6 +167,43 @@ class Phaser: raise ValueError("DAC temperature out of bounds") delay(.1*ms) + delay(.5*ms) # slack + self.dac_write(0x00, 0x019c) # I=2, fifo, clkdiv_sync, qmc off + self.dac_write(0x01, 0x040e) # fifo alarms, parity + self.dac_write(0x02, 0x70a2) # clk alarms, sif4, nco off, mix, mix_gain, 2s + self.dac_write(0x03, 0x6000) # coarse dac 20.6 mA + self.dac_write(0x07, 0x40c1) # alarm mask + self.dac_write(0x09, 0x8000) # fifo_offset + self.dac_write(0x0d, 0x0000) # fmix, no cmix + self.dac_write(0x14, 0x5431) # fine nco ab + self.dac_write(0x15, 0x0323) # coarse nco ab + self.dac_write(0x16, 0x5431) # fine nco cd + self.dac_write(0x17, 0x0323) # coarse nco cd + self.dac_write(0x18, 0x2c60) # P=4, pll run, single cp, pll_ndivsync + self.dac_write(0x19, 0x8404) # M=8 N=1 + self.dac_write(0x1a, 0xfc00) # pll_vco=63 + delay(.2*ms) # slack + self.dac_write(0x1b, 0x0800) # int ref, fuse + self.dac_write(0x1e, 0x9999) # qmc sync from sif and reg + self.dac_write(0x1f, 0x9982) # mix sync, nco sync, istr is istr, sif_sync + self.dac_write(0x20, 0x2400) # fifo sync ISTR-OSTR + self.dac_write(0x22, 0x1be4) # reverse dacs for spectral inversion and layout + self.dac_write(0x24, 0x0000) # clk and data delays + + delay(1*ms) # lock pll + lvolt = self.dac_read(0x18) & 7 + delay(.1*ms) + if lvolt < 2 or lvolt > 5: + raise ValueError("DAC PLL tuning voltage out of bounds") + self.dac_write(0x20, 0x0000) # stop fifo sync + self.dac_write(0x05, 0x0000) # clear alarms + delay(1*ms) # run it + alarm = self.get_sta() & 1 + delay(.1*ms) + if alarm: + alarm = self.dac_read(0x05) + raise ValueError("DAC alarm") + patterns = [ [0xffff, 0xffff, 0x0000, 0x0000], # test channel [0xaa55, 0x55aa, 0x55aa, 0xaa5a], # test iq @@ -173,11 +212,18 @@ class Phaser: [0x1a1a, 0x1616, 0xaaaa, 0xc6c6], # ds pattern b ] delay(.5*ms) - for i in range(len(patterns)): - errors = self.dac_iotest(patterns[i]) - if errors: - raise ValueError("iotest error") - delay(.5*ms) + # A data delay of 3*50 ps heuristically matches FPGA+board+DAC skews. + # There is plenty of margin and no need to tune at runtime. + # Parity provides another level of safety. + for dly in [-3]: # range(-7, 8) + if dly < 0: + dly = -dly << 3 # data delay, else clock delay + self.dac_write(0x24, dly << 10) + for i in range(len(patterns)): + errors = self.dac_iotest(patterns[i]) + if errors: + raise ValueError("iotest error") + delay(.5*ms) hw_rev = self.read8(PHASER_ADDR_HW_REV) has_upconverter = hw_rev & PHASER_HW_REV_VARIANT @@ -424,26 +470,18 @@ class Phaser: # dac test data is i msb, q lsb self.channel[ch].set_dac_test(pattern[2*ch] | (pattern[2*ch + 1] << 16)) self.dac_write(0x01, 0x8000) # iotest_ena - errors = 0 - # A data delay of 3*50 ps heuristically matches FPGA+board+DAC skews. - # There is plenty of margin and no need to tune at runtime. - # Parity provides another level of safety. - for dly in [-3]: # range(-7, 8) - if dly < 0: - dly = -dly << 3 # data delay, else clock delay - self.dac_write(0x24, dly << 10) - self.dac_write(0x04, 0x0000) # clear iotest_result - delay(.2*ms) # let it rip - # no need to go through the alarm register, - # just read the error mask - # self.dac_write(0x05, 0x0000) # clear alarms - # alarm = self.dac_read(0x05) - # delay(.1*ms) # slack - # if alarm & 0x0080: # alarm_from_iotest - errors |= self.dac_read(0x04) - delay(.1*ms) # slack - self.dac_write(0x24, 0) # reset delays - # self.dac_write(0x01, 0x0000) # clear config + self.dac_write(0x04, 0x0000) # clear iotest_result + delay(.2*ms) # let it rip + # no need to go through the alarm register, + # just read the error mask + # self.dac_write(0x05, 0x0000) # clear alarms + # alarm = self.dac_read(0x05) + # delay(.1*ms) # slack + # if alarm & 0x0080: # alarm_from_iotest + errors = self.dac_read(0x04) + delay(.1*ms) # slack + self.dac_write(0x01, 0x0000) # clear config + self.dac_write(0x04, 0x0000) # clear iotest_result return errors From 9b58b712a6a65bd496f6c77506891091cd2685b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Tue, 15 Sep 2020 12:35:26 +0000 Subject: [PATCH 27/51] phaser: doc tweaks --- artiq/coredevice/phaser.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 5c3dfddc6..92e7fa0d9 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -77,10 +77,10 @@ class Phaser: > 30 MHz. The four 16 bit 500 MS/s DAC data streams are sent via a 32 bit parallel - LVDS bus operating at 1 Gb/s per pin pair and processed in the DAC. On the - DAC 2x interpolation, sinx/x compensation, quadrature modulator - compensation, fine and coarse mixing as well as group delay capabilities - are available. + LVDS bus operating at 1 Gb/s per pin pair and processed in the DAC (Texas + Instruments DAC34H84). On the DAC 2x interpolation, sinx/x compensation, + quadrature modulator compensation, fine and coarse mixing as well as group + delay capabilities are available. The latency/group delay from the RTIO events setting :class:`PhaserOscillator` or :class:`PhaserChannel` DUC parameters all they @@ -93,11 +93,12 @@ class Phaser: attenuators and are available on the front panel. The odd outputs are available at MMCX connectors on board. - In the upconverter variant, each IQ output pair feeds a one quadrature - upconverter with integrated PLL/VCO. This analog quadrature upconverter - supports offset tuning for carrier and sideband suppression. The output - from the upconverter passes through the 31.5 dB range step attenuator and - is available at the front panel. + In the upconverter variant, each IQ output pair feeds one quadrature + upconverter (Texas Instruments TRF372017) with integrated PLL/VCO. This + digitally configured analog quadrature upconverter supports offset tuning + for carrier and sideband suppression. The output from the upconverter + passes through the 31.5 dB range step attenuator and is available at the + front panel. The DAC, the analog quadrature upconverters and the attenuators are configured through a shared SPI bus that is accessed and controlled via @@ -461,8 +462,11 @@ class Phaser: :param patterm: List of four int32 containing the pattern :return: Bit error mask (16 bits) """ + if len(pattern) != 4: + raise ValueError("pattern length out of bounds") for addr in range(len(pattern)): self.dac_write(0x25 + addr, pattern[addr]) + # repeat the pattern twice self.dac_write(0x29 + addr, pattern[addr]) delay(.1*ms) for ch in range(2): @@ -534,7 +538,7 @@ class PhaserChannel: @kernel def set_duc_cfg(self, clr=0, clr_once=0, select=0): - """Set the digital upconverter and interpolator configuration. + """Set the digital upconverter (DUC) and interpolator configuration. :param clr: Keep the phase accumulator cleared (persistent) :param clr_once: Clear the phase accumulator for one cycle @@ -555,7 +559,7 @@ class PhaserChannel: @kernel def set_duc_frequency(self, frequency): - """Set the DUC frequency. + """Set the DUC frequency in SI units. :param frequency: DUC frequency in Hz (passband from -200 MHz to 200 MHz, wrapping around at +- 250 MHz) @@ -565,7 +569,7 @@ class PhaserChannel: @kernel def set_duc_phase_mu(self, pow): - """Set the DUC phase offset + """Set the DUC phase offset. :param pow: DUC phase offset word (16 bit) """ @@ -575,7 +579,7 @@ class PhaserChannel: @kernel def set_duc_phase(self, phase): - """Set the DUC phase. + """Set the DUC phase in SI units. :param phase: DUC phase in turns """ @@ -631,7 +635,7 @@ class PhaserChannel: @kernel def trf_write(self, data, readback=False): - """Write 32 bits to upconverter. + """Write 32 bits to quadrature upconverter register. :param data: Register data (32 bit) containing encoded address :param readback: Whether to return the read back MISO data From f3b0398720c513d57ee71126bff8a57f81a8a2cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Wed, 16 Sep 2020 09:19:15 +0000 Subject: [PATCH 28/51] phaser: n=2, m=16, sync_dly --- artiq/coredevice/phaser.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 92e7fa0d9..2e780ecf4 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -20,7 +20,7 @@ PHASER_ADDR_SPI_DIVLEN = 0x0b PHASER_ADDR_SPI_SEL = 0x0c PHASER_ADDR_SPI_DATW = 0x0d PHASER_ADDR_SPI_DATR = 0x0e -# PHASER_ADDR_RESERVED0 = 0x0f +PHASER_ADDR_SYNC_DLY = 0x0f PHASER_ADDR_DUC0_CFG = 0x10 # PHASER_ADDR_DUC0_RESERVED0 = 0x11 PHASER_ADDR_DUC0_F = 0x12 @@ -172,16 +172,17 @@ class Phaser: self.dac_write(0x00, 0x019c) # I=2, fifo, clkdiv_sync, qmc off self.dac_write(0x01, 0x040e) # fifo alarms, parity self.dac_write(0x02, 0x70a2) # clk alarms, sif4, nco off, mix, mix_gain, 2s - self.dac_write(0x03, 0x6000) # coarse dac 20.6 mA + self.dac_write(0x03, 0x4000) # coarse dac 20.6 mA self.dac_write(0x07, 0x40c1) # alarm mask - self.dac_write(0x09, 0x8000) # fifo_offset + self.dac_write(0x09, 0x4000) # fifo_offset + self.set_sync_dly(0) self.dac_write(0x0d, 0x0000) # fmix, no cmix self.dac_write(0x14, 0x5431) # fine nco ab self.dac_write(0x15, 0x0323) # coarse nco ab self.dac_write(0x16, 0x5431) # fine nco cd self.dac_write(0x17, 0x0323) # coarse nco cd self.dac_write(0x18, 0x2c60) # P=4, pll run, single cp, pll_ndivsync - self.dac_write(0x19, 0x8404) # M=8 N=1 + self.dac_write(0x19, 0x8814) # M=16 N=2 self.dac_write(0x1a, 0xfc00) # pll_vco=63 delay(.2*ms) # slack self.dac_write(0x1b, 0x0800) # int ref, fuse @@ -196,7 +197,7 @@ class Phaser: delay(.1*ms) if lvolt < 2 or lvolt > 5: raise ValueError("DAC PLL tuning voltage out of bounds") - self.dac_write(0x20, 0x0000) # stop fifo sync + # self.dac_write(0x20, 0x0000) # stop fifo sync self.dac_write(0x05, 0x0000) # clear alarms delay(1*ms) # run it alarm = self.get_sta() & 1 @@ -488,6 +489,16 @@ class Phaser: self.dac_write(0x04, 0x0000) # clear iotest_result return errors + @kernel + def set_sync_dly(self, dly): + """Set SYNC delay. + + :param dly: DAC SYNC delay setting (0 to 7) + """ + if dly < 0 or dly > 7: + raise ValueError("SYNC delay out of bounds") + self.write8(PHASER_ADDR_SYNC_DLY, dly) + class PhaserChannel: """Phaser channel IQ pair. From c18f515bf98dec24081691c702b9d53882219eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Wed, 16 Sep 2020 11:49:46 +0000 Subject: [PATCH 29/51] phaser: rework rtio channels, sync_dly, init() --- artiq/coredevice/phaser.py | 64 +++++++++++++++++++------------ artiq/gateware/eem.py | 6 ++- artiq/gateware/rtio/phy/phaser.py | 22 ++++++----- 3 files changed, 57 insertions(+), 35 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 2e780ecf4..1251173de 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -21,12 +21,14 @@ PHASER_ADDR_SPI_SEL = 0x0c PHASER_ADDR_SPI_DATW = 0x0d PHASER_ADDR_SPI_DATR = 0x0e PHASER_ADDR_SYNC_DLY = 0x0f + PHASER_ADDR_DUC0_CFG = 0x10 # PHASER_ADDR_DUC0_RESERVED0 = 0x11 PHASER_ADDR_DUC0_F = 0x12 PHASER_ADDR_DUC0_P = 0x16 PHASER_ADDR_DAC0_DATA = 0x18 PHASER_ADDR_DAC0_TEST = 0x1c + PHASER_ADDR_DUC1_CFG = 0x20 # PHASER_ADDR_DUC1_RESERVED0 = 0x21 PHASER_ADDR_DUC1_F = 0x22 @@ -152,6 +154,7 @@ class Phaser: self.set_leds(0x00) self.set_fan_mu(0) self.set_cfg(clk_sel=clk_sel) # bring everything out of reset + self.set_sync_dly(4) # tune? delay(.1*ms) # slack # 4 wire SPI, sif4_enable @@ -175,7 +178,6 @@ class Phaser: self.dac_write(0x03, 0x4000) # coarse dac 20.6 mA self.dac_write(0x07, 0x40c1) # alarm mask self.dac_write(0x09, 0x4000) # fifo_offset - self.set_sync_dly(0) self.dac_write(0x0d, 0x0000) # fmix, no cmix self.dac_write(0x14, 0x5431) # fine nco ab self.dac_write(0x15, 0x0323) # coarse nco ab @@ -183,7 +185,7 @@ class Phaser: self.dac_write(0x17, 0x0323) # coarse nco cd self.dac_write(0x18, 0x2c60) # P=4, pll run, single cp, pll_ndivsync self.dac_write(0x19, 0x8814) # M=16 N=2 - self.dac_write(0x1a, 0xfc00) # pll_vco=63 + self.dac_write(0x1a, 0xfc00) # pll_vco=63, 4 GHz delay(.2*ms) # slack self.dac_write(0x1b, 0x0800) # int ref, fuse self.dac_write(0x1e, 0x9999) # qmc sync from sif and reg @@ -192,19 +194,20 @@ class Phaser: self.dac_write(0x22, 0x1be4) # reverse dacs for spectral inversion and layout self.dac_write(0x24, 0x0000) # clk and data delays + self.clear_dac_alarms() delay(1*ms) # lock pll lvolt = self.dac_read(0x18) & 7 delay(.1*ms) if lvolt < 2 or lvolt > 5: raise ValueError("DAC PLL tuning voltage out of bounds") # self.dac_write(0x20, 0x0000) # stop fifo sync - self.dac_write(0x05, 0x0000) # clear alarms - delay(1*ms) # run it - alarm = self.get_sta() & 1 - delay(.1*ms) - if alarm: - alarm = self.dac_read(0x05) + # alarm = self.get_sta() & 1 + # delay(.1*ms) + alarm = self.get_dac_alarms() + if alarm & ~0x0040: # ignore PLL alarms (see DS) + print(alarm) raise ValueError("DAC alarm") + delay(.5*ms) patterns = [ [0xffff, 0xffff, 0x0000, 0x0000], # test channel @@ -213,11 +216,10 @@ class Phaser: [0x7a7a, 0xb6b6, 0xeaea, 0x4545], # ds pattern a [0x1a1a, 0x1616, 0xaaaa, 0xc6c6], # ds pattern b ] - delay(.5*ms) - # A data delay of 3*50 ps heuristically matches FPGA+board+DAC skews. + # A data delay of 2*50 ps heuristically matches FPGA+board+DAC skews. # There is plenty of margin and no need to tune at runtime. # Parity provides another level of safety. - for dly in [-3]: # range(-7, 8) + for dly in [-2]: # range(-7, 8) if dly < 0: dly = -dly << 3 # data delay, else clock delay self.dac_write(0x24, dly << 10) @@ -226,6 +228,7 @@ class Phaser: if errors: raise ValueError("iotest error") delay(.5*ms) + self.clear_dac_alarms() hw_rev = self.read8(PHASER_ADDR_HW_REV) has_upconverter = hw_rev & PHASER_HW_REV_VARIANT @@ -362,6 +365,16 @@ class Phaser: """ return self.read8(PHASER_ADDR_CRC_ERR) + @kernel + def set_sync_dly(self, dly): + """Set SYNC delay. + + :param dly: DAC SYNC delay setting (0 to 7) + """ + if dly < 0 or dly > 7: + raise ValueError("SYNC delay out of bounds") + self.write8(PHASER_ADDR_SYNC_DLY, dly) + @kernel def duc_stb(self): """Strobe the DUC configuration register update. @@ -456,6 +469,19 @@ class Phaser: """ return self.dac_read(0x06, div=257) >> 8 + @kernel + def get_dac_alarms(self): + """Read the DAC alarm flags. + + :return: DAC alarm flags (see datasheet for bit meaning) + """ + return self.dac_read(0x05) + + @kernel + def clear_dac_alarms(self): + """Clear DAC alarm flags.""" + self.dac_write(0x05, 0x0000) + @kernel def dac_iotest(self, pattern) -> TInt32: """Performs a DAC IO test according to the datasheet. @@ -479,7 +505,7 @@ class Phaser: delay(.2*ms) # let it rip # no need to go through the alarm register, # just read the error mask - # self.dac_write(0x05, 0x0000) # clear alarms + # self.clear_dac_alarms() # alarm = self.dac_read(0x05) # delay(.1*ms) # slack # if alarm & 0x0080: # alarm_from_iotest @@ -489,16 +515,6 @@ class Phaser: self.dac_write(0x04, 0x0000) # clear iotest_result return errors - @kernel - def set_sync_dly(self, dly): - """Set SYNC delay. - - :param dly: DAC SYNC delay setting (0 to 7) - """ - if dly < 0 or dly > 7: - raise ValueError("SYNC delay out of bounds") - self.write8(PHASER_ADDR_SYNC_DLY, dly) - class PhaserChannel: """Phaser channel IQ pair. @@ -702,7 +718,7 @@ class PhaserOscillator: def __init__(self, channel, index): self.channel = channel self.base_addr = ((self.channel.phaser.channel_base + 1 + - self.channel.index) << 8) | (index << 1) + 2*self.channel.index) << 8) | index @kernel def set_frequency_mu(self, ftw): @@ -731,7 +747,7 @@ class PhaserOscillator: :param clr: Clear the phase accumulator (persistent) """ data = (asf & 0x7fff) | ((clr & 1) << 15) | (pow << 16) - rtio_output(self.base_addr | 1, data) + rtio_output(self.base_addr | (1 << 8), data) @kernel def set_amplitude_phase(self, amplitude, phase=0., clr=0): diff --git a/artiq/gateware/eem.py b/artiq/gateware/eem.py index 447dd1fe6..d6314e9be 100644 --- a/artiq/gateware/eem.py +++ b/artiq/gateware/eem.py @@ -652,6 +652,8 @@ class Phaser(_EEM): target.submodules += phy target.rtio_channels.extend([ rtio.Channel.from_phy(phy, ififo_depth=4), - rtio.Channel.from_phy(phy.ch0), - rtio.Channel.from_phy(phy.ch1), + rtio.Channel.from_phy(phy.ch0.frequency), + rtio.Channel.from_phy(phy.ch0.phase_amplitude), + rtio.Channel.from_phy(phy.ch1.frequency), + rtio.Channel.from_phy(phy.ch1.phase_amplitude), ]) diff --git a/artiq/gateware/rtio/phy/phaser.py b/artiq/gateware/rtio/phy/phaser.py index 46dfc8005..634d4f202 100644 --- a/artiq/gateware/rtio/phy/phaser.py +++ b/artiq/gateware/rtio/phy/phaser.py @@ -5,24 +5,28 @@ from artiq.gateware.rtio import rtlink from .fastlink import SerDes, SerInterface -class DDSChannel(Module): - def __init__(self, share_lut=None): +class Phy(Module): + def __init__(self, regs): self.rtlink = rtlink.Interface( rtlink.OInterface(data_width=32, address_width=4, enable_replace=True)) - to_rio_phy = ClockDomainsRenamer("rio_phy") - self.submodules.dds = to_rio_phy(MultiDDS( - n=5, fwidth=32, xwidth=16, z=19, zl=10, share_lut=share_lut)) - regs = [] - for i in self.dds.i: - regs.extend([i.f, Cat(i.a, i.clr, i.p)]) - self.sync.rio_phy += [ + self.sync.rtio += [ If(self.rtlink.o.stb, Array(regs)[self.rtlink.o.address].eq(self.rtlink.o.data) ) ] +class DDSChannel(Module): + def __init__(self, share_lut=None): + to_rio_phy = ClockDomainsRenamer("rio_phy") + self.submodules.dds = to_rio_phy(MultiDDS( + n=5, fwidth=32, xwidth=16, z=19, zl=10, share_lut=share_lut)) + self.submodules.frequency = Phy([i.f for i in self.dds.i]) + self.submodules.phase_amplitude = Phy( + [Cat(i.a, i.clr, i.p) for i in self.dds.i]) + + class Phaser(Module): def __init__(self, pins, pins_n): self.rtlink = rtlink.Interface( From 868a9a1f0c7d4340ca90c57a14a9719e14a57400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Wed, 16 Sep 2020 14:06:38 +0000 Subject: [PATCH 30/51] phaser: new multidds --- artiq/gateware/rtio/phy/phaser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/gateware/rtio/phy/phaser.py b/artiq/gateware/rtio/phy/phaser.py index 634d4f202..bb299ab0c 100644 --- a/artiq/gateware/rtio/phy/phaser.py +++ b/artiq/gateware/rtio/phy/phaser.py @@ -36,7 +36,7 @@ class Phaser(Module): # share a CosSinGen LUT between the two channels self.submodules.ch0 = DDSChannel() - self.submodules.ch1 = DDSChannel(share_lut=self.ch0.dds.mod.cs.lut) + self.submodules.ch1 = DDSChannel(share_lut=self.ch0.dds.cs.lut) n_channels = 2 n_samples = 8 n_bits = 14 From b15e388b5f07e750d2522bbdc0e63789700d5414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 17 Sep 2020 14:13:10 +0000 Subject: [PATCH 31/51] ad53xx: distinguish errors --- artiq/coredevice/ad53xx.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/artiq/coredevice/ad53xx.py b/artiq/coredevice/ad53xx.py index c4ff6d3aa..3445555d9 100644 --- a/artiq/coredevice/ad53xx.py +++ b/artiq/coredevice/ad53xx.py @@ -178,6 +178,8 @@ class AD53xx: self.write_offset_dacs_mu(self.offset_dacs) if not blind: ctrl = self.read_reg(channel=0, op=AD53XX_READ_CONTROL) + if ctrl == 0xffff: + raise ValueError("DAC not found") if ctrl & 0b10000: raise ValueError("DAC over temperature") delay(25*us) From f0959fb871b20e63fd06caf97fad03e8e1788624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 17 Sep 2020 14:13:58 +0000 Subject: [PATCH 32/51] phaser: iotest early, check_alarms --- artiq/coredevice/phaser.py | 94 ++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 1251173de..be50a649e 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -169,44 +169,6 @@ class Phaser: t = self.get_dac_temperature() if t < 10 or t > 90: raise ValueError("DAC temperature out of bounds") - delay(.1*ms) - - delay(.5*ms) # slack - self.dac_write(0x00, 0x019c) # I=2, fifo, clkdiv_sync, qmc off - self.dac_write(0x01, 0x040e) # fifo alarms, parity - self.dac_write(0x02, 0x70a2) # clk alarms, sif4, nco off, mix, mix_gain, 2s - self.dac_write(0x03, 0x4000) # coarse dac 20.6 mA - self.dac_write(0x07, 0x40c1) # alarm mask - self.dac_write(0x09, 0x4000) # fifo_offset - self.dac_write(0x0d, 0x0000) # fmix, no cmix - self.dac_write(0x14, 0x5431) # fine nco ab - self.dac_write(0x15, 0x0323) # coarse nco ab - self.dac_write(0x16, 0x5431) # fine nco cd - self.dac_write(0x17, 0x0323) # coarse nco cd - self.dac_write(0x18, 0x2c60) # P=4, pll run, single cp, pll_ndivsync - self.dac_write(0x19, 0x8814) # M=16 N=2 - self.dac_write(0x1a, 0xfc00) # pll_vco=63, 4 GHz - delay(.2*ms) # slack - self.dac_write(0x1b, 0x0800) # int ref, fuse - self.dac_write(0x1e, 0x9999) # qmc sync from sif and reg - self.dac_write(0x1f, 0x9982) # mix sync, nco sync, istr is istr, sif_sync - self.dac_write(0x20, 0x2400) # fifo sync ISTR-OSTR - self.dac_write(0x22, 0x1be4) # reverse dacs for spectral inversion and layout - self.dac_write(0x24, 0x0000) # clk and data delays - - self.clear_dac_alarms() - delay(1*ms) # lock pll - lvolt = self.dac_read(0x18) & 7 - delay(.1*ms) - if lvolt < 2 or lvolt > 5: - raise ValueError("DAC PLL tuning voltage out of bounds") - # self.dac_write(0x20, 0x0000) # stop fifo sync - # alarm = self.get_sta() & 1 - # delay(.1*ms) - alarm = self.get_dac_alarms() - if alarm & ~0x0040: # ignore PLL alarms (see DS) - print(alarm) - raise ValueError("DAC alarm") delay(.5*ms) patterns = [ @@ -228,6 +190,35 @@ class Phaser: if errors: raise ValueError("iotest error") delay(.5*ms) + + delay(.5*ms) # slack + self.dac_write(0x00, 0x019c) # I=2, fifo, clkdiv_sync, qmc off + self.dac_write(0x01, 0x040e) # fifo alarms, parity + self.dac_write(0x02, 0x70a2) # clk alarms, sif4, nco off, mix, mix_gain, 2s + self.dac_write(0x03, 0xa000) # coarse dac 20.6 mA + self.dac_write(0x07, 0x40c1) # alarm mask + self.dac_write(0x09, 0x4000) # fifo_offset + self.dac_write(0x0d, 0x0000) # fmix, no cmix + self.dac_write(0x14, 0x5431) # fine nco ab + self.dac_write(0x15, 0x0323) # coarse nco ab + self.dac_write(0x16, 0x5431) # fine nco cd + self.dac_write(0x17, 0x0323) # coarse nco cd + self.dac_write(0x18, 0x2c60) # P=4, pll run, single cp, pll_ndivsync + self.dac_write(0x19, 0x8814) # M=16 N=2 + self.dac_write(0x1a, 0xfc00) # pll_vco=63, 4 GHz + delay(.2*ms) # slack + self.dac_write(0x1b, 0x0800) # int ref, fuse + self.dac_write(0x1e, 0x9999) # qmc sync from sif and reg + self.dac_write(0x1f, 0x9982) # mix sync, nco sync, istr is istr, sif_sync + self.dac_write(0x20, 0x2400) # fifo sync ISTR-OSTR + self.dac_write(0x22, 0x1be4) # reverse dacs for spectral inversion and layout + self.dac_write(0x24, 0x0000) # clk and data delays + + delay(2*ms) # lock pll + lvolt = self.dac_read(0x18) & 7 + delay(.1*ms) + if lvolt < 2 or lvolt > 5: + raise ValueError("DAC PLL tuning voltage out of bounds") self.clear_dac_alarms() hw_rev = self.read8(PHASER_ADDR_HW_REV) @@ -236,8 +227,8 @@ class Phaser: for ch in range(2): # test attenuator write and readback - self.channel[ch].set_att_mu(0x55) - if self.channel[ch].get_att_mu() != 0x55: + self.channel[ch].set_att_mu(0x5a) + if self.channel[ch].get_att_mu() != 0x5a: raise ValueError("attenuator test failed") delay(.1*ms) self.channel[ch].set_att(31.5*dB) @@ -250,6 +241,19 @@ class Phaser: raise ValueError("DAC test data readback failed") delay(.1*ms) + # self.dac_write(0x20, 0x0000) # stop fifo sync + # alarm = self.get_sta() & 1 + # delay(.1*ms) + self.check_dac_alarms() + + @kernel + def check_dac_alarms(self): + alarm = self.get_dac_alarms() + delay(.1*ms) # slack + if alarm & ~0x0040: # ignore PLL alarms (see DS) + print(alarm) + raise ValueError("DAC alarm") + @kernel def write8(self, addr, data): """Write data to FPGA register. @@ -506,11 +510,13 @@ class Phaser: # no need to go through the alarm register, # just read the error mask # self.clear_dac_alarms() - # alarm = self.dac_read(0x05) - # delay(.1*ms) # slack - # if alarm & 0x0080: # alarm_from_iotest - errors = self.dac_read(0x04) + alarm = self.get_dac_alarms() delay(.1*ms) # slack + if alarm & 0x0080: # alarm_from_iotest + errors = self.dac_read(0x04) + delay(.1*ms) # slack + else: + errors = 0 self.dac_write(0x01, 0x0000) # clear config self.dac_write(0x04, 0x0000) # clear iotest_result return errors From d730851397356ed5a0d8087018026678212e93b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 21 Sep 2020 15:05:29 +0000 Subject: [PATCH 33/51] phaser: elaborate init sequence, more tests --- artiq/coredevice/phaser.py | 57 +++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index be50a649e..f28fbe69e 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -145,16 +145,17 @@ class Phaser: raise ValueError("invalid board id") delay(20*us) # slack - # allow a few errors during startup and alignment + # allow a few errors during startup and alignment since boot if self.get_crc_err() > 20: - raise ValueError("large number of CRC errors") + raise ValueError("large number of frame CRC errors") delay(.1*ms) # slack + # reset self.set_cfg(dac_resetb=0, att0_rstn=0, att1_rstn=0) self.set_leds(0x00) self.set_fan_mu(0) self.set_cfg(clk_sel=clk_sel) # bring everything out of reset - self.set_sync_dly(4) # tune? + self.set_sync_dly(4) # TODO: tune this? delay(.1*ms) # slack # 4 wire SPI, sif4_enable @@ -163,7 +164,7 @@ class Phaser: raise ValueError("DAC version readback invalid") delay(.1*ms) if self.dac_read(0x00) != 0x049c: - raise ValueError("DAC config0 readback invalid") + raise ValueError("DAC config0 reset readback invalid") delay(.1*ms) t = self.get_dac_temperature() @@ -178,8 +179,9 @@ class Phaser: [0x7a7a, 0xb6b6, 0xeaea, 0x4545], # ds pattern a [0x1a1a, 0x1616, 0xaaaa, 0xc6c6], # ds pattern b ] - # A data delay of 2*50 ps heuristically matches FPGA+board+DAC skews. - # There is plenty of margin and no need to tune at runtime. + # A data delay of 2*50 ps heuristically and reproducibly matches + # FPGA+board+DAC skews. There is plenty of margin (>= 250 ps + # either side) and no need to tune at runtime. # Parity provides another level of safety. for dly in [-2]: # range(-7, 8) if dly < 0: @@ -188,10 +190,9 @@ class Phaser: for i in range(len(patterns)): errors = self.dac_iotest(patterns[i]) if errors: - raise ValueError("iotest error") + raise ValueError("DAC iotest failure") delay(.5*ms) - delay(.5*ms) # slack self.dac_write(0x00, 0x019c) # I=2, fifo, clkdiv_sync, qmc off self.dac_write(0x01, 0x040e) # fifo alarms, parity self.dac_write(0x02, 0x70a2) # clk alarms, sif4, nco off, mix, mix_gain, 2s @@ -226,20 +227,33 @@ class Phaser: delay(.1*ms) # slack for ch in range(2): + channel = self.channel[ch] # test attenuator write and readback - self.channel[ch].set_att_mu(0x5a) - if self.channel[ch].get_att_mu() != 0x5a: + channel.set_att_mu(0x5a) + if channel.get_att_mu() != 0x5a: raise ValueError("attenuator test failed") delay(.1*ms) - self.channel[ch].set_att(31.5*dB) + channel.set_att(31.5*dB) - # dac test data readback - dac_test = [0x10102020, 0x30304040] - self.channel[ch].set_duc_cfg(select=1) - self.channel[ch].set_dac_test(dac_test[ch]) - if self.channel[ch].get_dac_data() != dac_test[ch]: - raise ValueError("DAC test data readback failed") + for i in range(len(channel.oscillator)): + oscillator = channel.oscillator[i] + if i == 0: + asf = 0x7fff + else: + asf = 0 + # pi/4 phase + oscillator.set_amplitude_phase_mu(asf=asf, pow=0x2000, clr=1) + delay_mu(8) + delay(1*us) # settle link, pipeline and impulse response + # test oscillator and DUC and their phase sign + channel.set_duc_phase_mu(0) + channel.set_duc_cfg(select=0, clr=1) + self.duc_stb() + data = channel.get_dac_data() delay(.1*ms) + if data != 0x4a124a12: + print(data) + raise ValueError("DUC+oscillator phase/amplitude test failed") # self.dac_write(0x20, 0x0000) # stop fifo sync # alarm = self.get_sta() & 1 @@ -501,9 +515,14 @@ class Phaser: self.dac_write(0x29 + addr, pattern[addr]) delay(.1*ms) for ch in range(2): - self.channel[ch].set_duc_cfg(select=1) # test + channel = self.channel[ch] + channel.set_duc_cfg(select=1) # test # dac test data is i msb, q lsb - self.channel[ch].set_dac_test(pattern[2*ch] | (pattern[2*ch + 1] << 16)) + data = pattern[2*ch] | (pattern[2*ch + 1] << 16) + channel.set_dac_test(data) + if channel.get_dac_data() != data: + raise ValueError("DAC test data readback failed") + delay(.1*ms) self.dac_write(0x01, 0x8000) # iotest_ena self.dac_write(0x04, 0x0000) # clear iotest_result delay(.2*ms) # let it rip From fdb2867757754ea36a4c220bafcc09d53acb139e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 21 Sep 2020 17:06:26 +0200 Subject: [PATCH 34/51] phaser: fewer iotest patterns --- artiq/coredevice/phaser.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index f28fbe69e..1c45ab783 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -173,11 +173,9 @@ class Phaser: delay(.5*ms) patterns = [ - [0xffff, 0xffff, 0x0000, 0x0000], # test channel - [0xaa55, 0x55aa, 0x55aa, 0xaa5a], # test iq - [0xaa55, 0xaa55, 0x55aa, 0x55aa], # test byte - [0x7a7a, 0xb6b6, 0xeaea, 0x4545], # ds pattern a - [0x1a1a, 0x1616, 0xaaaa, 0xc6c6], # ds pattern b + [0xf05a, 0x05af, 0x5af0, 0xaf05], # test channel/iq/byte/nibble + [0x7a7a, 0xb6b6, 0xeaea, 0x4545], # datasheet pattern a + [0x1a1a, 0x1616, 0xaaaa, 0xc6c6], # datasheet pattern b ] # A data delay of 2*50 ps heuristically and reproducibly matches # FPGA+board+DAC skews. There is plenty of margin (>= 250 ps From 3e036e365a5f362a7faf1becae8b781395d692d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Tue, 22 Sep 2020 09:52:49 +0000 Subject: [PATCH 35/51] phaser: nco, settings and init tweaks --- artiq/coredevice/phaser.py | 315 +++++++++++++++++++++++++++++++------ 1 file changed, 267 insertions(+), 48 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 1c45ab783..cb0dccad3 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -151,15 +151,17 @@ class Phaser: delay(.1*ms) # slack # reset - self.set_cfg(dac_resetb=0, att0_rstn=0, att1_rstn=0) + self.set_cfg(dac_resetb=0, att0_rstn=0, att1_rstn=0, dac_txena=0) self.set_leds(0x00) self.set_fan_mu(0) - self.set_cfg(clk_sel=clk_sel) # bring everything out of reset - self.set_sync_dly(4) # TODO: tune this? + self.set_cfg(clk_sel=clk_sel, dac_txena=0) # bring everything out of reset + # TODO: crossing dac_clk (125 MHz) edges with sync_dly (0-7 ns) + # should change the optimal fifo_offset + self.set_sync_dly(4) delay(.1*ms) # slack # 4 wire SPI, sif4_enable - self.dac_write(0x02, 0x0082) + self.dac_write(0x02, 0x0080) if self.dac_read(0x7f) != 0x5409: raise ValueError("DAC version readback invalid") delay(.1*ms) @@ -168,9 +170,9 @@ class Phaser: delay(.1*ms) t = self.get_dac_temperature() + delay(.5*ms) if t < 10 or t > 90: raise ValueError("DAC temperature out of bounds") - delay(.5*ms) patterns = [ [0xf05a, 0x05af, 0x5af0, 0xaf05], # test channel/iq/byte/nibble @@ -182,8 +184,8 @@ class Phaser: # either side) and no need to tune at runtime. # Parity provides another level of safety. for dly in [-2]: # range(-7, 8) - if dly < 0: - dly = -dly << 3 # data delay, else clock delay + if dly < 0: # use data delay, else use clock delay + dly = -dly << 3 self.dac_write(0x24, dly << 10) for i in range(len(patterns)): errors = self.dac_iotest(patterns[i]) @@ -191,38 +193,195 @@ class Phaser: raise ValueError("DAC iotest failure") delay(.5*ms) - self.dac_write(0x00, 0x019c) # I=2, fifo, clkdiv_sync, qmc off - self.dac_write(0x01, 0x040e) # fifo alarms, parity - self.dac_write(0x02, 0x70a2) # clk alarms, sif4, nco off, mix, mix_gain, 2s - self.dac_write(0x03, 0xa000) # coarse dac 20.6 mA - self.dac_write(0x07, 0x40c1) # alarm mask - self.dac_write(0x09, 0x4000) # fifo_offset - self.dac_write(0x0d, 0x0000) # fmix, no cmix - self.dac_write(0x14, 0x5431) # fine nco ab - self.dac_write(0x15, 0x0323) # coarse nco ab - self.dac_write(0x16, 0x5431) # fine nco cd - self.dac_write(0x17, 0x0323) # coarse nco cd - self.dac_write(0x18, 0x2c60) # P=4, pll run, single cp, pll_ndivsync - self.dac_write(0x19, 0x8814) # M=16 N=2 - self.dac_write(0x1a, 0xfc00) # pll_vco=63, 4 GHz - delay(.2*ms) # slack - self.dac_write(0x1b, 0x0800) # int ref, fuse - self.dac_write(0x1e, 0x9999) # qmc sync from sif and reg - self.dac_write(0x1f, 0x9982) # mix sync, nco sync, istr is istr, sif_sync - self.dac_write(0x20, 0x2400) # fifo sync ISTR-OSTR - self.dac_write(0x22, 0x1be4) # reverse dacs for spectral inversion and layout - self.dac_write(0x24, 0x0000) # clk and data delays + qmc_corr_ena = 0 # msb ab + qmc_offset_ena = 0 # msb ab + invsinc_ena = 0 # msb ab + + interpolation = 1 # 2x + fifo_ena = 1 + alarm_out_ena = 1 + alarm_out_pol = 1 + clkdiv_sync_ena = 1 + self.dac_write(0x00, + (qmc_offset_ena << 14) | (qmc_corr_ena << 12) | + (interpolation << 8) | (fifo_ena << 7) | + (alarm_out_ena << 4) | (alarm_out_pol << 3) | + (clkdiv_sync_ena << 2) | (invsinc_ena << 0)) + iotest_ena = 0 + cnt64_ena = 0 + oddeven_parity = 0 # even + single_parity_ena = 1 + dual_parity_ena = 0 + rev_interface = 0 + dac_complement = 0b0000 # msb A + alarm_fifo = 0b111 # msb 2-away + self.dac_write(0x01, + (iotest_ena << 15) | (cnt64_ena << 12) | + (oddeven_parity << 11) | (single_parity_ena << 10) | + (dual_parity_ena << 9) | (rev_interface << 8) | + (dac_complement << 4) | (alarm_fifo << 1)) + dacclkgone_ena = 1 + dataclkgone_ena = 1 + collisiongone_ena = 1 + sif4_ena = 1 + mixer_ena = 0 + mixer_gain = 1 + nco_ena = 0 + revbus = 0 + twos = 1 + self.dac_write(0x02, + (dacclkgone_ena << 14) | (dataclkgone_ena << 13) | + (collisiongone_ena << 12) | (sif4_ena << 7) | + (mixer_ena << 6) | (mixer_gain << 5) | + (nco_ena << 4) | (revbus << 3) | (twos << 1)) + coarse_dac = 0xa # 20.6 mA, 0-15 + sif_txenable = 0 + self.dac_write(0x03, (coarse_dac << 12) | (sif_txenable << 0)) + mask_alarm_from_zerochk = 0 + mask_alarm_fifo_collision = 0 + mask_alarm_fifo_1away = 0 + mask_alarm_fifo_2away = 0 + mask_alarm_dacclk_gone = 0 + mask_alarm_dataclk_gone = 0 + mask_alarm_output_gone = 0 + mask_alarm_from_iotest = 0 + mask_alarm_from_pll = 0 + mask_alarm_parity = 0b0000 # msb a + self.dac_write(0x07, + (mask_alarm_from_zerochk << 15) | (1 << 14) | + (mask_alarm_fifo_collision << 13) | (mask_alarm_fifo_1away << 12) | + (mask_alarm_fifo_2away << 11) | (mask_alarm_dacclk_gone << 10) | + (mask_alarm_dataclk_gone << 9) | (mask_alarm_output_gone << 8) | + (mask_alarm_from_iotest << 7) | (1 << 6) | + (mask_alarm_from_pll << 5) | (mask_alarm_parity << 1)) + qmc_offseta = 0 # 12b + self.dac_write(0x08, qmc_offseta) + fifo_offset = 2 # 0-7 + qmc_offsetb = 0 # 12b + self.dac_write(0x09, (fifo_offset << 13) | qmc_offsetb) + qmc_offsetc = 0 # 12b + self.dac_write(0x0a, qmc_offsetc) + qmc_offsetd = 0 # 12b + self.dac_write(0x0b, qmc_offsetd) + qmc_gaina = 0 # 11b + self.dac_write(0x0c, qmc_gaina) + cmix_fs8 = 0 + cmix_fs4 = 0 + cmix_fs2 = 0 + cmix_nfs4 = 0 + qmc_gainb = 0 # 11b + self.dac_write(0x0d, + (cmix_fs8 << 15) | (cmix_fs4 << 14) | (cmix_fs2 << 12) | + (cmix_nfs4 << 11) | qmc_gainb) + qmc_gainc = 0 # 11b + self.dac_write(0x0e, qmc_gainc) + output_delayab = 0b00 + output_delaycd = 0b00 + qmc_gaind = 0 # 11b + self.dac_write(0x0f, (output_delayab << 14) | (output_delaycd << 12) | + qmc_gaind) + qmc_phaseab = 0 # 12b + self.dac_write(0x10, qmc_phaseab) + qmc_phasecd = 0 # 12b + self.dac_write(0x11, qmc_phasecd) + pll_reset = 0 + pll_ndivsync_ena = 1 + pll_ena = 1 + pll_cp = 0b01 # single charge pump + pll_p = 0b100 # p=4 + self.dac_write(0x18, + (0b001 << 13) | (pll_reset << 12) | + (pll_ndivsync_ena << 11) | (pll_ena << 10) | + (pll_cp << 6) | (pll_p << 3)) + pll_m2 = 1 # x2 + pll_m = 8 # m = 8 + pll_n = 0b0001 # n = 2 + pll_vcotune = 0b01 + self.dac_write(0x19, + (pll_m2 << 15) | (pll_m << 8) | (pll_n << 4) | (pll_vcotune << 2)) + delay(.5*ms) # slack + pll_vco = 0x3f # 4 GHz + bias_sleep = 0 + tsense_sleep = 0 + pll_sleep = 0 + clkrecv_sleep = 0 + dac_sleep = 0b0000 # msb a + self.dac_write(0x1a, + (pll_vco << 10) | (bias_sleep << 7) | (tsense_sleep << 6) | + (pll_sleep << 5) | (clkrecv_sleep << 4) | (dac_sleep << 0)) + extref_ena = 0 + fuse_sleep = 1 + atest = 0b00000 # atest mode + self.dac_write(0x1b, + (extref_ena << 15) | (fuse_sleep << 11) | (atest << 0)) + syncsel_qmcoffsetab = 0b1001 # sif_sync and register write + syncsel_qmcoffsetcd = 0b1001 # sif_sync and register write + syncsel_qmccorrab = 0b1001 # sif_sync and register write + syncsel_qmccorrcd = 0b1001 # sif_sync and register write + self.dac_write(0x1e, + (syncsel_qmcoffsetab << 12) | (syncsel_qmcoffsetcd << 8) | + (syncsel_qmccorrab << 4) | (syncsel_qmccorrcd << 0)) + syncsel_mixerab = 0b1001 # sif_sync and register write + syncsel_mixercd = 0b1001 # sif_sync and register write + syncsel_nco = 0b1000 # sif_sync + syncsel_fifo_input = 0b10 # external lvds istr + sif_sync = 1 + self.dac_write(0x1e, + (syncsel_mixerab << 12) | (syncsel_mixercd << 8) | + (syncsel_nco << 4) | (syncsel_fifo_input << 2) | + (sif_sync << 1)) + syncsel_fifoin = 0b0010 # istr + syncsel_fifoout = 0b0100 # ostr + clkdiv_sync_sel = 0 # ostr + self.dac_write(0x20, + (syncsel_fifoin << 12) | (syncsel_fifoout << 8) | + (clkdiv_sync_sel << 0)) + path_a_sel = 0b00 + path_b_sel = 0b01 + path_c_sel = 0b10 + path_d_sel = 0b11 + # reverse dacs (DCBA) for spectral inversion and layout + dac_a_sel = 0b11 + dac_b_sel = 0b10 + dac_c_sel = 0b01 + dac_d_sel = 0b00 + self.dac_write(0x22, + (path_a_sel << 14) | (path_b_sel << 12) | + (path_c_sel << 10) | (path_d_sel << 8) | + (dac_a_sel << 6) | (dac_b_sel << 4) | + (dac_c_sel << 2) | (dac_d_sel << 0)) + dac_sleep_en = 0b1111 # msb a + clkrecv_sleep_en = 1 + pll_sleep_en = 1 + lvds_data_sleep_en = 1 + lvds_control_sleep_en = 1 + temp_sense_sleep_en = 1 + bias_sleep_en = 1 + self.dac_write(0x23, + (dac_sleep_en << 12) | (clkrecv_sleep_en << 11) | + (pll_sleep_en << 10) | (lvds_data_sleep_en << 9) | + (lvds_control_sleep_en << 8) | (temp_sense_sleep_en << 7) | + (1 << 6) | (bias_sleep_en << 5) | (0x1f << 0)) + # self.dac_write(0x24, 0x0000) # clk and data delays (tuned above) + ostrtodig_sel = 0 + ramp_ena = 0 + sifdac_ena = 0 + self.dac_write(0x2d, + (ostrtodig_sel << 14) | (ramp_ena << 13) | (0x002 << 1) | + (sifdac_ena << 0)) + grp_delaya = 0x00 + grp_delayb = 0x00 + self.dac_write(0x2e, (grp_delaya << 8) | (grp_delayb << 0)) + grp_delayc = 0x00 + grp_delayd = 0x00 + self.dac_write(0x2f, (grp_delayc << 8) | (grp_delayd << 0)) + sifdac = 0 + self.dac_write(0x30, sifdac) - delay(2*ms) # lock pll lvolt = self.dac_read(0x18) & 7 delay(.1*ms) if lvolt < 2 or lvolt > 5: raise ValueError("DAC PLL tuning voltage out of bounds") - self.clear_dac_alarms() - - hw_rev = self.read8(PHASER_ADDR_HW_REV) - has_upconverter = hw_rev & PHASER_HW_REV_VARIANT - delay(.1*ms) # slack for ch in range(2): channel = self.channel[ch] @@ -233,38 +392,43 @@ class Phaser: delay(.1*ms) channel.set_att(31.5*dB) + # test oscillators and DUC for i in range(len(channel.oscillator)): oscillator = channel.oscillator[i] + asf = 0 if i == 0: asf = 0x7fff - else: - asf = 0 - # pi/4 phase - oscillator.set_amplitude_phase_mu(asf=asf, pow=0x2000, clr=1) + # 6pi/4 phase + oscillator.set_amplitude_phase_mu(asf=asf, pow=0xc000, clr=1) delay_mu(8) - delay(1*us) # settle link, pipeline and impulse response - # test oscillator and DUC and their phase sign - channel.set_duc_phase_mu(0) + # 3pi/4 + channel.set_duc_phase_mu(0x6000) channel.set_duc_cfg(select=0, clr=1) self.duc_stb() + delay(.1*ms) # settle link, pipeline and impulse response data = channel.get_dac_data() delay(.1*ms) - if data != 0x4a124a12: + sqrt2 = 0x5a81 # 0x7fff/sqrt(2) + data_i = data & 0xffff + data_q = (data >> 16) & 0xffff + # allow ripple + if (data_i < sqrt2 - 30 or data_i > sqrt2 or + abs(data_i - data_q) > 2): print(data) raise ValueError("DUC+oscillator phase/amplitude test failed") # self.dac_write(0x20, 0x0000) # stop fifo sync # alarm = self.get_sta() & 1 # delay(.1*ms) + self.clear_dac_alarms() + delay(2*ms) # let it run a bit self.check_dac_alarms() - @kernel - def check_dac_alarms(self): - alarm = self.get_dac_alarms() + hw_rev = self.read8(PHASER_ADDR_HW_REV) + has_upconverter = hw_rev & PHASER_HW_REV_VARIANT delay(.1*ms) # slack - if alarm & ~0x0040: # ignore PLL alarms (see DS) - print(alarm) - raise ValueError("DAC alarm") + + self.set_cfg(clk_sel=clk_sel) # txena @kernel def write8(self, addr, data): @@ -493,6 +657,14 @@ class Phaser: """ return self.dac_read(0x05) + @kernel + def check_dac_alarms(self): + alarm = self.get_dac_alarms() + delay(.1*ms) # slack + if alarm & ~0x0040: # ignore PLL alarms (see DS) + print(alarm) + raise ValueError("DAC alarm") + @kernel def clear_dac_alarms(self): """Clear DAC alarm flags.""" @@ -542,6 +714,17 @@ class Phaser: class PhaserChannel: """Phaser channel IQ pair. + A Phaser channel contains: + + * multiple oscillators (in the coredevice phy), + * an interpolation chain and digital upconverter (DUC) on Phaser, + * several channel-specific settings in the DAC: + * quadrature modulation compensation QMC + * numerically controlled oscillator NCO or coarse mixer CMIX, + * the analog quadrature upconverter (in the Phaser-Upconverter hardware + variant), and + * a digitally controlled step attenuator. + Attributes: * :attr:`oscillator`: List of five :class:`PhaserOscillator`. @@ -636,6 +819,42 @@ class PhaserChannel: pow = int32(round(phase*(1 << 16))) self.set_duc_phase_mu(pow) + @kernel + def set_nco_frequency_mu(self, ftw): + """Set the NCO frequency. + + :param ftw: NCO frequency tuning word (32 bit) + """ + self.phaser.dac_write(0x15 + (self.index << 1), ftw >> 16) + self.phaser.dac_write(0x14 + (self.index << 1), ftw) + + @kernel + def set_nco_frequency(self, frequency): + """Set the NCO frequency in SI units. + + :param frequency: NCO frequency in Hz (passband from -400 MHz + to 400 MHz, wrapping around at +- 500 MHz) + """ + ftw = int32(round(frequency*((1 << 31)/(500*MHz)))) + self.set_nco_frequency_mu(ftw) + + @kernel + def set_nco_phase_mu(self, pow): + """Set the NCO phase offset. + + :param pow: NCO phase offset word (16 bit) + """ + self.phaser.dac_write(0x12 + self.index, pow) + + @kernel + def set_nco_phase(self, phase): + """Set the NCO phase in SI units. + + :param phase: NCO phase in turns + """ + pow = int32(round(phase*(1 << 16))) + self.set_duc_phase_mu(pow) + @kernel def set_att_mu(self, data): """Set channel attenuation. From fd5e22189890d482bb65f88b71d3505314aa858a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Tue, 22 Sep 2020 14:08:39 +0000 Subject: [PATCH 36/51] phaser: dac and trf register maps, init code --- artiq/coredevice/dac34h84.py | 262 ++++++++++++++++++++++++++++++ artiq/coredevice/phaser.py | 293 +++++++++------------------------- artiq/coredevice/trf372017.py | 133 +++++++++++++++ 3 files changed, 468 insertions(+), 220 deletions(-) create mode 100644 artiq/coredevice/dac34h84.py create mode 100644 artiq/coredevice/trf372017.py diff --git a/artiq/coredevice/dac34h84.py b/artiq/coredevice/dac34h84.py new file mode 100644 index 000000000..86e818c7e --- /dev/null +++ b/artiq/coredevice/dac34h84.py @@ -0,0 +1,262 @@ +class DAC34H84: + """DAC34H84 settings and register map. + + For possible values, documentation, and explanation, see the DAC datasheet + at https://www.ti.com/lit/pdf/slas751 + """ + qmc_corr_ena = 0 # msb ab + qmc_offset_ena = 0 # msb ab + invsinc_ena = 0 # msb ab + interpolation = 1 # 2x + fifo_ena = 1 + alarm_out_ena = 1 + alarm_out_pol = 1 + clkdiv_sync_ena = 1 + + iotest_ena = 0 + cnt64_ena = 0 + oddeven_parity = 0 # even + single_parity_ena = 1 + dual_parity_ena = 0 + rev_interface = 0 + dac_complement = 0b0000 # msb A + alarm_fifo = 0b111 # msb 2-away + + dacclkgone_ena = 1 + dataclkgone_ena = 1 + collisiongone_ena = 1 + sif4_ena = 1 + mixer_ena = 0 + mixer_gain = 1 + nco_ena = 0 + revbus = 0 + twos = 1 + + coarse_dac = 0xa # 20.6 mA, 0-15 + sif_txenable = 0 + + mask_alarm_from_zerochk = 0 + mask_alarm_fifo_collision = 0 + mask_alarm_fifo_1away = 0 + mask_alarm_fifo_2away = 0 + mask_alarm_dacclk_gone = 0 + mask_alarm_dataclk_gone = 0 + mask_alarm_output_gone = 0 + mask_alarm_from_iotest = 0 + mask_alarm_from_pll = 0 + mask_alarm_parity = 0b0000 # msb a + + qmc_offseta = 0 # 12b + fifo_offset = 2 # 0-7 + qmc_offsetb = 0 # 12b + + qmc_offsetc = 0 # 12b + + qmc_offsetd = 0 # 12b + + qmc_gaina = 0 # 11b + + cmix_fs8 = 0 + cmix_fs4 = 0 + cmix_fs2 = 0 + cmix_nfs4 = 0 + qmc_gainb = 0 # 11b + + qmc_gainc = 0 # 11b + + output_delayab = 0b00 + output_delaycd = 0b00 + qmc_gaind = 0 # 11b + + qmc_phaseab = 0 # 12b + + qmc_phasecd = 0 # 12b + + pll_reset = 0 + pll_ndivsync_ena = 1 + pll_ena = 1 + pll_cp = 0b01 # single charge pump + pll_p = 0b100 # p=4 + + pll_m2 = 1 # x2 + pll_m = 8 # m = 8 + pll_n = 0b0001 # n = 2 + pll_vcotune = 0b01 + + pll_vco = 0x3f # 4 GHz + bias_sleep = 0 + tsense_sleep = 0 + pll_sleep = 0 + clkrecv_sleep = 0 + dac_sleep = 0b0000 # msb a + + extref_ena = 0 + fuse_sleep = 1 + atest = 0b00000 # atest mode + + syncsel_qmcoffsetab = 0b1001 # sif_sync and register write + syncsel_qmcoffsetcd = 0b1001 # sif_sync and register write + syncsel_qmccorrab = 0b1001 # sif_sync and register write + syncsel_qmccorrcd = 0b1001 # sif_sync and register write + + syncsel_mixerab = 0b1001 # sif_sync and register write + syncsel_mixercd = 0b1001 # sif_sync and register write + syncsel_nco = 0b1000 # sif_sync + syncsel_fifo_input = 0b10 # external lvds istr + sif_sync = 1 + + syncsel_fifoin = 0b0010 # istr + syncsel_fifoout = 0b0100 # ostr + clkdiv_sync_sel = 0 # ostr + + path_a_sel = 0b00 + path_b_sel = 0b01 + path_c_sel = 0b10 + path_d_sel = 0b11 + # reverse dacs (DCBA) for spectral inversion and layout + dac_a_sel = 0b11 + dac_b_sel = 0b10 + dac_c_sel = 0b01 + dac_d_sel = 0b00 + + dac_sleep_en = 0b1111 # msb a + clkrecv_sleep_en = 1 + pll_sleep_en = 1 + lvds_data_sleep_en = 1 + lvds_control_sleep_en = 1 + temp_sense_sleep_en = 1 + bias_sleep_en = 1 + + data_dly = 2 + clk_dly = 0 + + ostrtodig_sel = 0 + ramp_ena = 0 + sifdac_ena = 0 + + grp_delaya = 0x00 + grp_delayb = 0x00 + + grp_delayc = 0x00 + grp_delayd = 0x00 + + sifdac = 0 + + def __init__(self, updates=None): + if updates is None: + return + for key, value in updates.items(): + if not hasattr(self, key): + raise KeyError("invalid setting", key) + setattr(self, key, value) + + def get_mmap(self): + mmap = [] + mmap.append( + (0x00 << 16) | + (self.qmc_offset_ena << 14) | (self.qmc_corr_ena << 12) | + (self.interpolation << 8) | (self.fifo_ena << 7) | + (self.alarm_out_ena << 4) | (self.alarm_out_pol << 3) | + (self.clkdiv_sync_ena << 2) | (self.invsinc_ena << 0)) + mmap.append( + (0x01 << 16) | + (self.iotest_ena << 15) | (self.cnt64_ena << 12) | + (self.oddeven_parity << 11) | (self.single_parity_ena << 10) | + (self.dual_parity_ena << 9) | (self.rev_interface << 8) | + (self.dac_complement << 4) | (self.alarm_fifo << 1)) + mmap.append( + (0x02 << 16) | + (self.dacclkgone_ena << 14) | (self.dataclkgone_ena << 13) | + (self.collisiongone_ena << 12) | (self.sif4_ena << 7) | + (self.mixer_ena << 6) | (self.mixer_gain << 5) | + (self.nco_ena << 4) | (self.revbus << 3) | (self.twos << 1)) + mmap.append((0x03 << 16) | (self.coarse_dac << 12) | (self.sif_txenable << 0)) + mmap.append( + (0x07 << 16) | + (self.mask_alarm_from_zerochk << 15) | (1 << 14) | + (self.mask_alarm_fifo_collision << 13) | + (self.mask_alarm_fifo_1away << 12) | + (self.mask_alarm_fifo_2away << 11) | + (self.mask_alarm_dacclk_gone << 10) | + (self.mask_alarm_dataclk_gone << 9) | + (self.mask_alarm_output_gone << 8) | + (self.mask_alarm_from_iotest << 7) | (1 << 6) | + (self.mask_alarm_from_pll << 5) | (self.mask_alarm_parity << 1)) + mmap.append( + (0x08 << 16) | (self.qmc_offseta << 0)) + mmap.append( + (0x09 << 16) | (self.fifo_offset << 13) | (self.qmc_offsetb << 0)) + mmap.append((0x0a << 16) | (self.qmc_offsetc << 0)) + mmap.append((0x0b << 16) | (self.qmc_offsetd << 0)) + mmap.append((0x0c << 16) | (self.qmc_gaina << 0)) + mmap.append( + (0x0d << 16) | + (self.cmix_fs8 << 15) | (self.cmix_fs4 << 14) | + (self.cmix_fs2 << 12) | (self.cmix_nfs4 << 11) | + (self.qmc_gainb << 0)) + mmap.append((0x0e << 16) | (self.qmc_gainc << 0)) + mmap.append( + (0x0f << 16) | + (self.output_delayab << 14) | (self.output_delaycd << 12) | + (self.qmc_gaind << 0)) + mmap.append((0x10 << 16) | (self.qmc_phaseab << 0)) + mmap.append((0x11 << 16) | (self.qmc_phasecd << 0)) + mmap.append( + (0x18 << 16) | + (0b001 << 13) | (self.pll_reset << 12) | + (self.pll_ndivsync_ena << 11) | (self.pll_ena << 10) | + (self.pll_cp << 6) | (self.pll_p << 3)) + mmap.append( + (0x19 << 16) | + (self.pll_m2 << 15) | (self.pll_m << 8) | (self.pll_n << 4) | + (self.pll_vcotune << 2)) + mmap.append( + (0x1a << 16) | + (self.pll_vco << 10) | (self.bias_sleep << 7) | + (self.tsense_sleep << 6) | + (self.pll_sleep << 5) | (self.clkrecv_sleep << 4) | + (self.dac_sleep << 0)) + mmap.append( + (0x1b << 16) | + (self.extref_ena << 15) | (self.fuse_sleep << 11) | + (self.atest << 0)) + mmap.append( + (0x1e << 16) | + (self.syncsel_qmcoffsetab << 12) | + (self.syncsel_qmcoffsetcd << 8) | + (self.syncsel_qmccorrab << 4) | + (self.syncsel_qmccorrcd << 0)) + mmap.append( + (0x1f << 16) | + (self.syncsel_mixerab << 12) | (self.syncsel_mixercd << 8) | + (self.syncsel_nco << 4) | (self.syncsel_fifo_input << 2) | + (self.sif_sync << 1)) + mmap.append( + (0x20 << 16) | + (self.syncsel_fifoin << 12) | (self.syncsel_fifoout << 8) | + (self.clkdiv_sync_sel << 0)) + mmap.append( + (0x22 << 16) | + (self.path_a_sel << 14) | (self.path_b_sel << 12) | + (self.path_c_sel << 10) | (self.path_d_sel << 8) | + (self.dac_a_sel << 6) | (self.dac_b_sel << 4) | + (self.dac_c_sel << 2) | (self.dac_d_sel << 0)) + mmap.append( + (0x23 << 16) | + (self.dac_sleep_en << 12) | (self.clkrecv_sleep_en << 11) | + (self.pll_sleep_en << 10) | (self.lvds_data_sleep_en << 9) | + (self.lvds_control_sleep_en << 8) | + (self.temp_sense_sleep_en << 7) | (1 << 6) | + (self.bias_sleep_en << 5) | (0x1f << 0)) + mmap.append( + (0x24 << 16) | (self.data_dly << 13) | (self.clk_dly << 10)) + mmap.append( + (0x2d << 16) | + (self.ostrtodig_sel << 14) | (self.ramp_ena << 13) | + (0x002 << 1) | (self.sifdac_ena << 0)) + mmap.append( + (0x2e << 16) | (self.grp_delaya << 8) | (self.grp_delayb << 0)) + mmap.append( + (0x2f << 16) | (self.grp_delayc << 8) | (self.grp_delayd << 0)) + mmap.append((0x30 << 16) | self.sifdac) + return mmap diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index cb0dccad3..36d9c4c21 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -2,6 +2,8 @@ from artiq.language.core import kernel, delay_mu, delay from artiq.coredevice.rtio import rtio_output, rtio_input_data from artiq.language.units import us, ns, ms, MHz, dB from artiq.language.types import TInt32 +from artiq.coredevice.dac34h84 import DAC34H84 +from artiq.coredevice.trf372017 import TRF372017 PHASER_BOARD_ID = 19 @@ -106,10 +108,22 @@ class Phaser: configured through a shared SPI bus that is accessed and controlled via FPGA registers. + .. note:: Various register settings of the DAC and the quadrature + upconverters are available to be modified through the `dac`, `trf0`, + `trf1` dictionaries. These can be set through the device database + (`device_db.py`). The settings are frozen during instantiation of the + class and applied during `init()`. See the :class:`DAC34H84` and + :class:`TRF372017` source for details. + :param channel: Base RTIO channel number :param core_device: Core device name (default: "core") :param miso_delay: Fastlink MISO signal delay to account for cable - and buffer round trip. This might be automated later. + and buffer round trip. Tuning this might be automated later. + :param dac: DAC34H84 DAC settings as a dictionary. + :param trf0: Channel 0 TRF372017 quadrature upconverter settings as a + dictionary. + :param trf1: Channel 1 TRF372017 quadrature upconverter settings as a + dictionary. Attributes: @@ -117,9 +131,11 @@ class Phaser: To access oscillators, digital upconverters, PLL/VCO analog quadrature upconverters and attenuators. """ - kernel_invariants = {"core", "channel_base", "t_frame", "miso_delay"} + kernel_invariants = {"core", "channel_base", "t_frame", "miso_delay", + "dac_mmap"} - def __init__(self, dmgr, channel_base, miso_delay=1, core_device="core"): + def __init__(self, dmgr, channel_base, miso_delay=1, core_device="core", + dac=None, trf0=None, trf1=None): self.channel_base = channel_base self.core = dmgr.get(core_device) # TODO: auto-align miso-delay in phy @@ -129,14 +145,18 @@ class Phaser: assert self.core.ref_period == 1*ns self.t_frame = 10*8*4 - self.channel = [PhaserChannel(self, ch) for ch in range(2)] + self.dac_mmap = DAC34H84(dac).get_mmap() + + self.channel = [PhaserChannel(self, ch, trf) + for ch, trf in enumerate([trf0, trf1])] @kernel def init(self, clk_sel=0): """Initialize the board. - Verifies board and chip presence, resets components, performs communication - and configuration tests and establishes initial conditions. + Verifies board and chip presence, resets components, performs + communication and configuration tests and establishes initial + conditions. :param clk_sel: Select the external SMA clock input (1 or 0) """ @@ -145,18 +165,28 @@ class Phaser: raise ValueError("invalid board id") delay(20*us) # slack + hw_rev = self.read8(PHASER_ADDR_HW_REV) + delay(.1*ms) # slack + has_upconverter = hw_rev & PHASER_HW_REV_VARIANT + + gw_rev = self.read8(PHASER_ADDR_GW_REV) + delay(.1*ms) # slack + # allow a few errors during startup and alignment since boot if self.get_crc_err() > 20: raise ValueError("large number of frame CRC errors") delay(.1*ms) # slack # reset - self.set_cfg(dac_resetb=0, att0_rstn=0, att1_rstn=0, dac_txena=0) + self.set_cfg(dac_resetb=0, dac_sleep=1, att0_rstn=0, att1_rstn=0, + dac_txena=0) self.set_leds(0x00) self.set_fan_mu(0) - self.set_cfg(clk_sel=clk_sel, dac_txena=0) # bring everything out of reset + # bring everything out of reset, keep tx off + self.set_cfg(clk_sel=clk_sel, dac_txena=0) + # TODO: crossing dac_clk (125 MHz) edges with sync_dly (0-7 ns) - # should change the optimal fifo_offset + # should change the optimal fifo_offset by 4 self.set_sync_dly(4) delay(.1*ms) # slack @@ -174,6 +204,10 @@ class Phaser: if t < 10 or t > 90: raise ValueError("DAC temperature out of bounds") + for data in self.dac_mmap: + self.dac_write(data >> 16, data) + delay(.1*ms) + patterns = [ [0xf05a, 0x05af, 0x5af0, 0xaf05], # test channel/iq/byte/nibble [0x7a7a, 0xb6b6, 0xeaea, 0x4545], # datasheet pattern a @@ -183,206 +217,25 @@ class Phaser: # FPGA+board+DAC skews. There is plenty of margin (>= 250 ps # either side) and no need to tune at runtime. # Parity provides another level of safety. - for dly in [-2]: # range(-7, 8) - if dly < 0: # use data delay, else use clock delay - dly = -dly << 3 - self.dac_write(0x24, dly << 10) - for i in range(len(patterns)): - errors = self.dac_iotest(patterns[i]) - if errors: - raise ValueError("DAC iotest failure") - delay(.5*ms) - - qmc_corr_ena = 0 # msb ab - qmc_offset_ena = 0 # msb ab - invsinc_ena = 0 # msb ab - - interpolation = 1 # 2x - fifo_ena = 1 - alarm_out_ena = 1 - alarm_out_pol = 1 - clkdiv_sync_ena = 1 - self.dac_write(0x00, - (qmc_offset_ena << 14) | (qmc_corr_ena << 12) | - (interpolation << 8) | (fifo_ena << 7) | - (alarm_out_ena << 4) | (alarm_out_pol << 3) | - (clkdiv_sync_ena << 2) | (invsinc_ena << 0)) - iotest_ena = 0 - cnt64_ena = 0 - oddeven_parity = 0 # even - single_parity_ena = 1 - dual_parity_ena = 0 - rev_interface = 0 - dac_complement = 0b0000 # msb A - alarm_fifo = 0b111 # msb 2-away - self.dac_write(0x01, - (iotest_ena << 15) | (cnt64_ena << 12) | - (oddeven_parity << 11) | (single_parity_ena << 10) | - (dual_parity_ena << 9) | (rev_interface << 8) | - (dac_complement << 4) | (alarm_fifo << 1)) - dacclkgone_ena = 1 - dataclkgone_ena = 1 - collisiongone_ena = 1 - sif4_ena = 1 - mixer_ena = 0 - mixer_gain = 1 - nco_ena = 0 - revbus = 0 - twos = 1 - self.dac_write(0x02, - (dacclkgone_ena << 14) | (dataclkgone_ena << 13) | - (collisiongone_ena << 12) | (sif4_ena << 7) | - (mixer_ena << 6) | (mixer_gain << 5) | - (nco_ena << 4) | (revbus << 3) | (twos << 1)) - coarse_dac = 0xa # 20.6 mA, 0-15 - sif_txenable = 0 - self.dac_write(0x03, (coarse_dac << 12) | (sif_txenable << 0)) - mask_alarm_from_zerochk = 0 - mask_alarm_fifo_collision = 0 - mask_alarm_fifo_1away = 0 - mask_alarm_fifo_2away = 0 - mask_alarm_dacclk_gone = 0 - mask_alarm_dataclk_gone = 0 - mask_alarm_output_gone = 0 - mask_alarm_from_iotest = 0 - mask_alarm_from_pll = 0 - mask_alarm_parity = 0b0000 # msb a - self.dac_write(0x07, - (mask_alarm_from_zerochk << 15) | (1 << 14) | - (mask_alarm_fifo_collision << 13) | (mask_alarm_fifo_1away << 12) | - (mask_alarm_fifo_2away << 11) | (mask_alarm_dacclk_gone << 10) | - (mask_alarm_dataclk_gone << 9) | (mask_alarm_output_gone << 8) | - (mask_alarm_from_iotest << 7) | (1 << 6) | - (mask_alarm_from_pll << 5) | (mask_alarm_parity << 1)) - qmc_offseta = 0 # 12b - self.dac_write(0x08, qmc_offseta) - fifo_offset = 2 # 0-7 - qmc_offsetb = 0 # 12b - self.dac_write(0x09, (fifo_offset << 13) | qmc_offsetb) - qmc_offsetc = 0 # 12b - self.dac_write(0x0a, qmc_offsetc) - qmc_offsetd = 0 # 12b - self.dac_write(0x0b, qmc_offsetd) - qmc_gaina = 0 # 11b - self.dac_write(0x0c, qmc_gaina) - cmix_fs8 = 0 - cmix_fs4 = 0 - cmix_fs2 = 0 - cmix_nfs4 = 0 - qmc_gainb = 0 # 11b - self.dac_write(0x0d, - (cmix_fs8 << 15) | (cmix_fs4 << 14) | (cmix_fs2 << 12) | - (cmix_nfs4 << 11) | qmc_gainb) - qmc_gainc = 0 # 11b - self.dac_write(0x0e, qmc_gainc) - output_delayab = 0b00 - output_delaycd = 0b00 - qmc_gaind = 0 # 11b - self.dac_write(0x0f, (output_delayab << 14) | (output_delaycd << 12) | - qmc_gaind) - qmc_phaseab = 0 # 12b - self.dac_write(0x10, qmc_phaseab) - qmc_phasecd = 0 # 12b - self.dac_write(0x11, qmc_phasecd) - pll_reset = 0 - pll_ndivsync_ena = 1 - pll_ena = 1 - pll_cp = 0b01 # single charge pump - pll_p = 0b100 # p=4 - self.dac_write(0x18, - (0b001 << 13) | (pll_reset << 12) | - (pll_ndivsync_ena << 11) | (pll_ena << 10) | - (pll_cp << 6) | (pll_p << 3)) - pll_m2 = 1 # x2 - pll_m = 8 # m = 8 - pll_n = 0b0001 # n = 2 - pll_vcotune = 0b01 - self.dac_write(0x19, - (pll_m2 << 15) | (pll_m << 8) | (pll_n << 4) | (pll_vcotune << 2)) - delay(.5*ms) # slack - pll_vco = 0x3f # 4 GHz - bias_sleep = 0 - tsense_sleep = 0 - pll_sleep = 0 - clkrecv_sleep = 0 - dac_sleep = 0b0000 # msb a - self.dac_write(0x1a, - (pll_vco << 10) | (bias_sleep << 7) | (tsense_sleep << 6) | - (pll_sleep << 5) | (clkrecv_sleep << 4) | (dac_sleep << 0)) - extref_ena = 0 - fuse_sleep = 1 - atest = 0b00000 # atest mode - self.dac_write(0x1b, - (extref_ena << 15) | (fuse_sleep << 11) | (atest << 0)) - syncsel_qmcoffsetab = 0b1001 # sif_sync and register write - syncsel_qmcoffsetcd = 0b1001 # sif_sync and register write - syncsel_qmccorrab = 0b1001 # sif_sync and register write - syncsel_qmccorrcd = 0b1001 # sif_sync and register write - self.dac_write(0x1e, - (syncsel_qmcoffsetab << 12) | (syncsel_qmcoffsetcd << 8) | - (syncsel_qmccorrab << 4) | (syncsel_qmccorrcd << 0)) - syncsel_mixerab = 0b1001 # sif_sync and register write - syncsel_mixercd = 0b1001 # sif_sync and register write - syncsel_nco = 0b1000 # sif_sync - syncsel_fifo_input = 0b10 # external lvds istr - sif_sync = 1 - self.dac_write(0x1e, - (syncsel_mixerab << 12) | (syncsel_mixercd << 8) | - (syncsel_nco << 4) | (syncsel_fifo_input << 2) | - (sif_sync << 1)) - syncsel_fifoin = 0b0010 # istr - syncsel_fifoout = 0b0100 # ostr - clkdiv_sync_sel = 0 # ostr - self.dac_write(0x20, - (syncsel_fifoin << 12) | (syncsel_fifoout << 8) | - (clkdiv_sync_sel << 0)) - path_a_sel = 0b00 - path_b_sel = 0b01 - path_c_sel = 0b10 - path_d_sel = 0b11 - # reverse dacs (DCBA) for spectral inversion and layout - dac_a_sel = 0b11 - dac_b_sel = 0b10 - dac_c_sel = 0b01 - dac_d_sel = 0b00 - self.dac_write(0x22, - (path_a_sel << 14) | (path_b_sel << 12) | - (path_c_sel << 10) | (path_d_sel << 8) | - (dac_a_sel << 6) | (dac_b_sel << 4) | - (dac_c_sel << 2) | (dac_d_sel << 0)) - dac_sleep_en = 0b1111 # msb a - clkrecv_sleep_en = 1 - pll_sleep_en = 1 - lvds_data_sleep_en = 1 - lvds_control_sleep_en = 1 - temp_sense_sleep_en = 1 - bias_sleep_en = 1 - self.dac_write(0x23, - (dac_sleep_en << 12) | (clkrecv_sleep_en << 11) | - (pll_sleep_en << 10) | (lvds_data_sleep_en << 9) | - (lvds_control_sleep_en << 8) | (temp_sense_sleep_en << 7) | - (1 << 6) | (bias_sleep_en << 5) | (0x1f << 0)) - # self.dac_write(0x24, 0x0000) # clk and data delays (tuned above) - ostrtodig_sel = 0 - ramp_ena = 0 - sifdac_ena = 0 - self.dac_write(0x2d, - (ostrtodig_sel << 14) | (ramp_ena << 13) | (0x002 << 1) | - (sifdac_ena << 0)) - grp_delaya = 0x00 - grp_delayb = 0x00 - self.dac_write(0x2e, (grp_delaya << 8) | (grp_delayb << 0)) - grp_delayc = 0x00 - grp_delayd = 0x00 - self.dac_write(0x2f, (grp_delayc << 8) | (grp_delayd << 0)) - sifdac = 0 - self.dac_write(0x30, sifdac) + for i in range(len(patterns)): + delay(.5*ms) + errors = self.dac_iotest(patterns[i]) + if errors: + raise ValueError("DAC iotest failure") + delay(10*ms) # let it settle lvolt = self.dac_read(0x18) & 7 delay(.1*ms) if lvolt < 2 or lvolt > 5: raise ValueError("DAC PLL tuning voltage out of bounds") + # self.dac_write(0x20, 0x0000) # stop fifo sync + # alarm = self.get_sta() & 1 + # delay(.1*ms) + self.clear_dac_alarms() + delay(2*ms) # let it run a bit + self.check_dac_alarms() + for ch in range(2): channel = self.channel[ch] # test attenuator write and readback @@ -400,7 +253,7 @@ class Phaser: asf = 0x7fff # 6pi/4 phase oscillator.set_amplitude_phase_mu(asf=asf, pow=0xc000, clr=1) - delay_mu(8) + delay(1*us) # 3pi/4 channel.set_duc_phase_mu(0x6000) channel.set_duc_cfg(select=0, clr=1) @@ -414,19 +267,17 @@ class Phaser: # allow ripple if (data_i < sqrt2 - 30 or data_i > sqrt2 or abs(data_i - data_q) > 2): - print(data) raise ValueError("DUC+oscillator phase/amplitude test failed") - # self.dac_write(0x20, 0x0000) # stop fifo sync - # alarm = self.get_sta() & 1 - # delay(.1*ms) - self.clear_dac_alarms() - delay(2*ms) # let it run a bit - self.check_dac_alarms() - - hw_rev = self.read8(PHASER_ADDR_HW_REV) - has_upconverter = hw_rev & PHASER_HW_REV_VARIANT - delay(.1*ms) # slack + if has_upconverter: + for data in channel.trf_mmap: + channel.trf_write(data) + delay(.1*ms) + delay(1*ms) # lock + lock_detect = self.get_sta() & (PHASER_STA_TRF0_LD << ch) + delay(.1*ms) + if not lock_detect: + raise ValueError("TRF quadrature upconverter lock failure") self.set_cfg(clk_sel=clk_sel) # txena @@ -662,7 +513,6 @@ class Phaser: alarm = self.get_dac_alarms() delay(.1*ms) # slack if alarm & ~0x0040: # ignore PLL alarms (see DS) - print(alarm) raise ValueError("DAC alarm") @kernel @@ -693,7 +543,9 @@ class Phaser: if channel.get_dac_data() != data: raise ValueError("DAC test data readback failed") delay(.1*ms) - self.dac_write(0x01, 0x8000) # iotest_ena + cfg = self.dac_read(0x01) + delay(.1*ms) + self.dac_write(0x01, cfg | 0x8000) # iotest_ena self.dac_write(0x04, 0x0000) # clear iotest_result delay(.2*ms) # let it rip # no need to go through the alarm register, @@ -706,7 +558,7 @@ class Phaser: delay(.1*ms) # slack else: errors = 0 - self.dac_write(0x01, 0x0000) # clear config + self.dac_write(0x01, cfg) # clear config self.dac_write(0x04, 0x0000) # clear iotest_result return errors @@ -741,11 +593,12 @@ class PhaserChannel: or overflow after the interpolation. Either band-limit any changes in the oscillator parameters or back off the amplitude sufficiently. """ - kernel_invariants = {"index", "phaser"} + kernel_invariants = {"index", "phaser", "trf_mmap"} - def __init__(self, phaser, index): + def __init__(self, phaser, index, trf): self.phaser = phaser self.index = index + self.trf_mmap = TRF372017(trf).get_mmap() self.oscillator = [PhaserOscillator(self, osc) for osc in range(5)] @kernel diff --git a/artiq/coredevice/trf372017.py b/artiq/coredevice/trf372017.py new file mode 100644 index 000000000..0dc1ac5dd --- /dev/null +++ b/artiq/coredevice/trf372017.py @@ -0,0 +1,133 @@ +class TRF372017: + """TRF372017 settings and register map. + + For possible values, documentation, and explanation, see the datasheet. + https://www.ti.com/lit/gpn/trf372017 + """ + rdiv = 21 # 13b + ref_inv = 0 + neg_vco = 1 + icp = 0 # 1.94 mA, 5b + icp_double = 0 + cal_clk_sel = 12 # /16, 4b + + ndiv = 420 # 16b + pll_div_sel = 0b01 # /1, 2b + prsc_sel = 1 # 8/9 + vco_sel = 2 # 2b + vcosel_mode = 0 + cal_acc = 0b00 # 2b + en_cal = 1 + + nfrac = 0 # 25b + + pwd_pll = 0 + pwd_cp = 0 + pwd_vco = 0 + pwd_vcomux = 0 + pwd_div124 = 0 + pwd_presc = 0 + pwd_out_buff = 1 + pwd_lo_div = 1 + pwd_tx_div = 0 + pwd_bb_vcm = 0 + pwd_dc_off = 0 + en_extvco = 0 + en_isource = 0 + ld_ana_prec = 0 # 2b + cp_tristate = 0 # 2b + speedup = 0 + ld_dig_prec = 0 + en_dith = 1 + mod_ord = 2 # 3rd order, 2b + dith_sel = 0 + del_sd_clk = 2 # 2b + en_frac = 0 + + vcobias_rtrim = 4 # 3b + pllbias_rtrim = 2 # 2b + vco_bias = 8 # 460 µA, 4b + vcobuf_bias = 2 # 2b + vcomux_bias = 3 # 2b + bufout_bias = 0 # 300 µA, 2b + vco_cal_ib = 0 # PTAT + vco_cal_ref = 2 # 1.04 V, 2b + vco_ampl_ctrl = 3 # 2b + vco_vb_ctrl = 0 # 1.2 V, 2b + en_ld_isource = 0 + + ioff = 0x80 # 8b + qoff = 0x80 # 8b + vref_sel = 4 # 0.85 V, 3b + tx_div_sel = 1 # div2, 2b + lo_div_sel = 3 # div8, 2b + tx_div_bias = 1 # 37.5 µA, 2b + lo_div_bias = 2 # 50 µA, 2b + + vco_trim = 0x20 # 6b + vco_test_mode = 0 + cal_bypass = 0 + mux_ctrl = 1 # lock detect, 3b + isource_sink = 0 + isource_trim = 4 # 3b + pd_tc = 0 # 2b + ib_vcm_sel = 0 # ptat + dcoffset_i = 2 # 150 µA, 2b + vco_bias_sel = 1 # spi + + def __init__(self, updates=None): + if updates is None: + return + for key, value in updates.items(): + if not hasattr(self, key): + raise KeyError("invalid setting", key) + setattr(self, key, value) + + def get_mmap(self): + mmap = [] + mmap.append( + 0x9 | + (self.rdiv << 5) | (self.ref_inv << 19) | (self.neg_vco << 20) | + (self.icp << 21) | (self.icp_double << 26) | + (self.cal_clk_sel << 27)) + mmap.append( + 0xa | + (self.ndiv << 5) | (self.pll_div_sel << 21) | (self.prsc_sel << 23) | + (self.vco_sel << 26) | (self.vcosel_mode << 28) | + (self.cal_acc << 29) | (self.en_cal << 31)) + mmap.append(0xb | (self.nfrac << 5)) + mmap.append( + 0xc | + (self.pwd_pll << 5) | (self.pwd_cp << 6) | (self.pwd_vco << 7) | + (self.pwd_vcomux << 8) | (self.pwd_div124 << 9) | + (self.pwd_presc << 10) | (self.pwd_out_buff << 12) | + (self.pwd_lo_div << 13) | (self.pwd_tx_div << 14) | + (self.pwd_bb_vcm << 15) | (self.pwd_dc_off << 16) | + (self.en_extvco << 17) | (self.en_isource << 18) | + (self.ld_ana_prec << 19) | (self.cp_tristate << 21) | + (self.speedup << 23) | (self.ld_dig_prec << 24) | + (self.en_dith << 25) | (self.mod_ord << 27) | + (self.dith_sel << 28) | (self.del_sd_clk << 29) | + (self.en_frac << 31)) + mmap.append( + 0xd | + (self.vcobias_rtrim << 5) | (self.pllbias_rtrim << 8) | + (self.vco_bias << 10) | (self.vcobuf_bias << 14) | + (self.vcomux_bias << 16) | (self.bufout_bias << 18) | + (1 << 21) | (self.vco_cal_ib << 22) | (self.vco_cal_ref << 23) | + (self.vco_ampl_ctrl << 26) | (self.vco_vb_ctrl << 28) | + (self.en_ld_isource << 31)) + mmap.append( + 0xe | + (self.ioff << 5) | (self.qoff << 13) | (self.vref_sel << 21) | + (self.tx_div_sel << 24) | (self.lo_div_sel << 26) | + (self.tx_div_bias << 28) | (self.lo_div_bias << 30)) + mmap.append( + 0xf | + (self.vco_trim << 7) | (self.vco_test_mode << 14) | + (self.cal_bypass << 15) | (self.mux_ctrl << 16) | + (self.isource_sink << 19) | (self.isource_trim << 20) | + (self.pd_tc << 23) | (self.ib_vcm_sel << 25) | + (1 << 28) | (self.dcoffset_i << 29) | + (self.vco_bias_sel << 31)) + return mmap From 5c76f5c31980bb996ddb99b9dc3c3fe825601305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Tue, 22 Sep 2020 14:36:49 +0000 Subject: [PATCH 37/51] tester: add phaser --- artiq/frontend/artiq_sinara_tester.py | 57 +++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/artiq/frontend/artiq_sinara_tester.py b/artiq/frontend/artiq_sinara_tester.py index 5aed406e4..48b095729 100755 --- a/artiq/frontend/artiq_sinara_tester.py +++ b/artiq/frontend/artiq_sinara_tester.py @@ -51,6 +51,7 @@ class SinaraTester(EnvExperiment): self.samplers = dict() self.zotinos = dict() self.fastinos = dict() + self.phasers = dict() self.grabbers = dict() ddb = self.get_device_db() @@ -77,6 +78,8 @@ class SinaraTester(EnvExperiment): self.zotinos[name] = self.get_device(name) elif (module, cls) == ("artiq.coredevice.fastino", "Fastino"): self.fastinos[name] = self.get_device(name) + elif (module, cls) == ("artiq.coredevice.phaser", "Phaser"): + self.phasers[name] = self.get_device(name) elif (module, cls) == ("artiq.coredevice.grabber", "Grabber"): self.grabbers[name] = self.get_device(name) @@ -111,6 +114,7 @@ class SinaraTester(EnvExperiment): self.samplers = sorted(self.samplers.items(), key=lambda x: x[1].cnv.channel) self.zotinos = sorted(self.zotinos.items(), key=lambda x: x[1].bus.channel) self.fastinos = sorted(self.fastinos.items(), key=lambda x: x[1].channel) + self.phasers = sorted(self.phasers.items(), key=lambda x: x[1].channel_base) self.grabbers = sorted(self.grabbers.items(), key=lambda x: x[1].channel_base) @kernel @@ -391,6 +395,57 @@ class SinaraTester(EnvExperiment): [card_dev for _, (__, card_dev) in enumerate(self.fastinos)] ) + @kernel + def set_phaser_frequencies(self, phaser, duc, osc): + self.core.break_realtime() + phaser.init() + delay(1*ms) + phaser.channel[0].set_duc_frequency(duc) + phaser.channel[0].set_duc_cfg() + phaser.channel[0].set_att(6*dB) + phaser.channel[1].set_duc_frequency(-duc) + phaser.channel[1].set_duc_cfg() + phaser.channel[1].set_att(6*dB) + phaser.duc_stb() + delay(1*ms) + for i in range(len(osc)): + phaser.channel[0].oscillator[i].set_frequency(osc[i]) + phaser.channel[0].oscillator[i].set_amplitude_phase(.2) + phaser.channel[1].oscillator[i].set_frequency(-osc[i]) + phaser.channel[1].oscillator[i].set_amplitude_phase(.2) + delay(1*ms) + + @kernel + def phaser_led_wave(self, phasers): + while not is_enter_pressed(): + self.core.break_realtime() + # do not fill the FIFOs too much to avoid long response times + t = now_mu() - self.core.seconds_to_mu(.2) + while self.core.get_rtio_counter_mu() < t: + pass + for phaser in phasers: + for i in range(6): + phaser.set_leds(1 << i) + delay(100*ms) + phaser.set_leds(0) + delay(100*ms) + + def test_phasers(self): + print("*** Testing Phaser DACs and 6 USER LEDs.") + print("Frequencies:") + for card_n, (card_name, card_dev) in enumerate(self.phasers): + duc = (card_n + 1)*10*MHz + osc = [i*1*MHz for i in range(5)] + print(card_name, + " ".join(["{:.0f}+{:.0f}".format(duc/MHz, f/MHz) for f in osc]), + "MHz") + self.set_phaser_frequencies(card_dev, duc, osc) + print("Press ENTER when done.") + # Test switching on/off USR_LEDs at the same time + self.phaser_led_wave( + [card_dev for _, (__, card_dev) in enumerate(self.phasers)] + ) + @kernel def grabber_capture(self, card_dev, rois): self.core.break_realtime() @@ -443,6 +498,8 @@ class SinaraTester(EnvExperiment): self.test_zotinos() if self.fastinos: self.test_fastinos() + if self.phasers: + self.test_phasers() if self.grabbers: self.test_grabbers() From 85d16e3e5fdd1251be93076690de10c688753ef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Tue, 22 Sep 2020 15:27:38 +0000 Subject: [PATCH 38/51] phaser: tweaks --- artiq/coredevice/dac34h84.py | 2 +- artiq/coredevice/phaser.py | 17 +++++++++-------- artiq/coredevice/trf372017.py | 4 ++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/artiq/coredevice/dac34h84.py b/artiq/coredevice/dac34h84.py index 86e818c7e..d288cd524 100644 --- a/artiq/coredevice/dac34h84.py +++ b/artiq/coredevice/dac34h84.py @@ -32,7 +32,7 @@ class DAC34H84: revbus = 0 twos = 1 - coarse_dac = 0xa # 20.6 mA, 0-15 + coarse_dac = 9 # 18.75 mA, 0-15 sif_txenable = 0 mask_alarm_from_zerochk = 0 diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 36d9c4c21..787e4990e 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -119,6 +119,7 @@ class Phaser: :param core_device: Core device name (default: "core") :param miso_delay: Fastlink MISO signal delay to account for cable and buffer round trip. Tuning this might be automated later. + :param clk_sel: Select the external SMA clock input (1 or 0) :param dac: DAC34H84 DAC settings as a dictionary. :param trf0: Channel 0 TRF372017 quadrature upconverter settings as a dictionary. @@ -134,8 +135,8 @@ class Phaser: kernel_invariants = {"core", "channel_base", "t_frame", "miso_delay", "dac_mmap"} - def __init__(self, dmgr, channel_base, miso_delay=1, core_device="core", - dac=None, trf0=None, trf1=None): + def __init__(self, dmgr, channel_base, miso_delay=1, clk_sel=0, + dac=None, trf0=None, trf1=None, core_device="core"): self.channel_base = channel_base self.core = dmgr.get(core_device) # TODO: auto-align miso-delay in phy @@ -144,6 +145,7 @@ class Phaser: # self.core.seconds_to_mu(10*8*4*ns) # unfortunately this returns 319 assert self.core.ref_period == 1*ns self.t_frame = 10*8*4 + self.clk_sel = clk_sel self.dac_mmap = DAC34H84(dac).get_mmap() @@ -151,14 +153,13 @@ class Phaser: for ch, trf in enumerate([trf0, trf1])] @kernel - def init(self, clk_sel=0): + def init(self): """Initialize the board. Verifies board and chip presence, resets components, performs communication and configuration tests and establishes initial conditions. - :param clk_sel: Select the external SMA clock input (1 or 0) """ board_id = self.read8(PHASER_ADDR_BOARD_ID) if board_id != PHASER_BOARD_ID: @@ -167,7 +168,7 @@ class Phaser: hw_rev = self.read8(PHASER_ADDR_HW_REV) delay(.1*ms) # slack - has_upconverter = hw_rev & PHASER_HW_REV_VARIANT + is_baseband = hw_rev & PHASER_HW_REV_VARIANT gw_rev = self.read8(PHASER_ADDR_GW_REV) delay(.1*ms) # slack @@ -183,7 +184,7 @@ class Phaser: self.set_leds(0x00) self.set_fan_mu(0) # bring everything out of reset, keep tx off - self.set_cfg(clk_sel=clk_sel, dac_txena=0) + self.set_cfg(clk_sel=self.clk_sel, dac_txena=0) # TODO: crossing dac_clk (125 MHz) edges with sync_dly (0-7 ns) # should change the optimal fifo_offset by 4 @@ -269,7 +270,7 @@ class Phaser: abs(data_i - data_q) > 2): raise ValueError("DUC+oscillator phase/amplitude test failed") - if has_upconverter: + if not is_baseband: for data in channel.trf_mmap: channel.trf_write(data) delay(.1*ms) @@ -279,7 +280,7 @@ class Phaser: if not lock_detect: raise ValueError("TRF quadrature upconverter lock failure") - self.set_cfg(clk_sel=clk_sel) # txena + self.set_cfg(clk_sel=self.clk_sel) # txena @kernel def write8(self, addr, data): diff --git a/artiq/coredevice/trf372017.py b/artiq/coredevice/trf372017.py index 0dc1ac5dd..053a8a5fc 100644 --- a/artiq/coredevice/trf372017.py +++ b/artiq/coredevice/trf372017.py @@ -12,7 +12,7 @@ class TRF372017: cal_clk_sel = 12 # /16, 4b ndiv = 420 # 16b - pll_div_sel = 0b01 # /1, 2b + pll_div_sel = 0 # /1, 2b prsc_sel = 1 # 8/9 vco_sel = 2 # 2b vcosel_mode = 0 @@ -106,7 +106,7 @@ class TRF372017: (self.en_extvco << 17) | (self.en_isource << 18) | (self.ld_ana_prec << 19) | (self.cp_tristate << 21) | (self.speedup << 23) | (self.ld_dig_prec << 24) | - (self.en_dith << 25) | (self.mod_ord << 27) | + (self.en_dith << 25) | (self.mod_ord << 26) | (self.dith_sel << 28) | (self.del_sd_clk << 29) | (self.en_frac << 31)) mmap.append( From ad096f294c841bef9f95c85422a02a773acb02c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Tue, 22 Sep 2020 15:35:19 +0000 Subject: [PATCH 39/51] phaser: add hitl test exercising the complete API --- artiq/test/coredevice/test_phaser.py | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 artiq/test/coredevice/test_phaser.py diff --git a/artiq/test/coredevice/test_phaser.py b/artiq/test/coredevice/test_phaser.py new file mode 100644 index 000000000..18aac6a57 --- /dev/null +++ b/artiq/test/coredevice/test_phaser.py @@ -0,0 +1,34 @@ +import unittest +from artiq.experiment import * +from artiq.test.hardware_testbench import ExperimentCase +from artiq.language.core import kernel, delay +from artiq.language.units import us + + +class PhaserExperiment(EnvExperiment): + def build(self): + self.setattr_device("core") + self.setattr_device("phaser0") + + @kernel + def run(self): + self.core.reset() + # The Phaser initialization performs a comprehensive test: + # * Fastlink bringup + # * Fastlink error counter + # * Board identification + # * Hardware identification + # * SPI write, readback, timing + # * Temperature readout + # * DAC identification, IOTEST, alarm sweep, PLL configuration, FIFO + # alignmend + # * DUC+Oscillator configuration, data end-to-end verification and + # readback + # * Attenuator write and readback + # * TRF bringup PLL locking + self.phaser0.init() + + +class PhaserTest(ExperimentCase): + def test(self): + self.execute(PhaserExperiment) From ef65ee18bdd14bbf8c41f8aed401a4b0763c8069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Wed, 23 Sep 2020 08:35:56 +0000 Subject: [PATCH 40/51] dac34h84: unflip spectrum, clear nco --- artiq/coredevice/dac34h84.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/artiq/coredevice/dac34h84.py b/artiq/coredevice/dac34h84.py index d288cd524..6d40826b3 100644 --- a/artiq/coredevice/dac34h84.py +++ b/artiq/coredevice/dac34h84.py @@ -72,6 +72,13 @@ class DAC34H84: qmc_phasecd = 0 # 12b + phase_offsetab = 0 # 16b + phase_offsetcd = 0 # 16b + phase_addab_lsb = 0 # 16b + phase_addab_msb = 0 # 16b + phase_addcd_lsb = 0 # 16b + phase_addcd_msb = 0 # 16b + pll_reset = 0 pll_ndivsync_ena = 1 pll_ena = 1 @@ -109,15 +116,15 @@ class DAC34H84: syncsel_fifoout = 0b0100 # ostr clkdiv_sync_sel = 0 # ostr - path_a_sel = 0b00 - path_b_sel = 0b01 - path_c_sel = 0b10 - path_d_sel = 0b11 - # reverse dacs (DCBA) for spectral inversion and layout - dac_a_sel = 0b11 - dac_b_sel = 0b10 - dac_c_sel = 0b01 - dac_d_sel = 0b00 + path_a_sel = 0 + path_b_sel = 1 + path_c_sel = 2 + path_d_sel = 3 + # swap dac pairs (CDAB) for layout + dac_a_sel = 2 + dac_b_sel = 3 + dac_c_sel = 0 + dac_d_sel = 1 dac_sleep_en = 0b1111 # msb a clkrecv_sleep_en = 1 @@ -201,6 +208,12 @@ class DAC34H84: (self.qmc_gaind << 0)) mmap.append((0x10 << 16) | (self.qmc_phaseab << 0)) mmap.append((0x11 << 16) | (self.qmc_phasecd << 0)) + mmap.append((0x12 << 16) | (self.phase_offsetab << 0)) + mmap.append((0x13 << 16) | (self.phase_offsetcd << 0)) + mmap.append((0x14 << 16) | (self.phase_addab_lsb << 0)) + mmap.append((0x15 << 16) | (self.phase_addab_msb << 0)) + mmap.append((0x16 << 16) | (self.phase_addcd_lsb << 0)) + mmap.append((0x17 << 16) | (self.phase_addcd_msb << 0)) mmap.append( (0x18 << 16) | (0b001 << 13) | (self.pll_reset << 12) | From 03d5f985f8032c5ae076c6dad4f6d8801fc6d473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Wed, 23 Sep 2020 15:40:54 +0000 Subject: [PATCH 41/51] phaser: another artiq-python signed integer quirk --- artiq/coredevice/dac34h84.py | 9 +++++---- artiq/coredevice/phaser.py | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/artiq/coredevice/dac34h84.py b/artiq/coredevice/dac34h84.py index 6d40826b3..155096a1e 100644 --- a/artiq/coredevice/dac34h84.py +++ b/artiq/coredevice/dac34h84.py @@ -121,10 +121,11 @@ class DAC34H84: path_c_sel = 2 path_d_sel = 3 # swap dac pairs (CDAB) for layout - dac_a_sel = 2 - dac_b_sel = 3 - dac_c_sel = 0 - dac_d_sel = 1 + # swap I-Q dacs for spectral inversion + dac_a_sel = 3 + dac_b_sel = 2 + dac_c_sel = 1 + dac_d_sel = 0 dac_sleep_en = 0b1111 # msb a clkrecv_sleep_en = 1 diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 787e4990e..8acaf9010 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -651,7 +651,7 @@ class PhaserChannel: :param frequency: DUC frequency in Hz (passband from -200 MHz to 200 MHz, wrapping around at +- 250 MHz) """ - ftw = int32(round(frequency*((1 << 31)/(250*MHz)))) + ftw = int32(round(frequency*((1 << 30)/(125*MHz)))) self.set_duc_frequency_mu(ftw) @kernel @@ -689,7 +689,7 @@ class PhaserChannel: :param frequency: NCO frequency in Hz (passband from -400 MHz to 400 MHz, wrapping around at +- 500 MHz) """ - ftw = int32(round(frequency*((1 << 31)/(500*MHz)))) + ftw = int32(round(frequency*((1 << 30)/(250*MHz)))) self.set_nco_frequency_mu(ftw) @kernel @@ -831,7 +831,7 @@ class PhaserOscillator: :param frequency: Frequency in Hz (passband from -10 MHz to 10 MHz, wrapping around at +- 12.5 MHz) """ - ftw = int32(round(frequency*((1 << 31)/(12.5*MHz)))) + ftw = int32(round(frequency*((1 << 30)/(6.25*MHz)))) self.set_frequency_mu(ftw) @kernel From 6e6480ec211823b2ca57f4bbd56f8f43a6f97571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 24 Sep 2020 08:38:30 +0000 Subject: [PATCH 42/51] phaser: tweak slacks and errors, identify trf --- artiq/coredevice/phaser.py | 44 +++++++++++++++++++++-------------- artiq/coredevice/trf372017.py | 2 +- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 8acaf9010..56b09f13e 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -167,11 +167,11 @@ class Phaser: delay(20*us) # slack hw_rev = self.read8(PHASER_ADDR_HW_REV) - delay(.1*ms) # slack + delay(20*us) # slack is_baseband = hw_rev & PHASER_HW_REV_VARIANT gw_rev = self.read8(PHASER_ADDR_GW_REV) - delay(.1*ms) # slack + delay(20*us) # slack # allow a few errors during startup and alignment since boot if self.get_crc_err() > 20: @@ -185,11 +185,11 @@ class Phaser: self.set_fan_mu(0) # bring everything out of reset, keep tx off self.set_cfg(clk_sel=self.clk_sel, dac_txena=0) + delay(.1*ms) # slack # TODO: crossing dac_clk (125 MHz) edges with sync_dly (0-7 ns) # should change the optimal fifo_offset by 4 self.set_sync_dly(4) - delay(.1*ms) # slack # 4 wire SPI, sif4_enable self.dac_write(0x02, 0x0080) @@ -207,7 +207,7 @@ class Phaser: for data in self.dac_mmap: self.dac_write(data >> 16, data) - delay(.1*ms) + delay(20*us) patterns = [ [0xf05a, 0x05af, 0x5af0, 0xaf05], # test channel/iq/byte/nibble @@ -219,16 +219,16 @@ class Phaser: # either side) and no need to tune at runtime. # Parity provides another level of safety. for i in range(len(patterns)): - delay(.5*ms) + delay(.2*ms) errors = self.dac_iotest(patterns[i]) if errors: raise ValueError("DAC iotest failure") - delay(10*ms) # let it settle + delay(2*ms) # let it settle lvolt = self.dac_read(0x18) & 7 delay(.1*ms) if lvolt < 2 or lvolt > 5: - raise ValueError("DAC PLL tuning voltage out of bounds") + raise ValueError("DAC PLL lock failed, check clocking") # self.dac_write(0x20, 0x0000) # stop fifo sync # alarm = self.get_sta() & 1 @@ -270,15 +270,24 @@ class Phaser: abs(data_i - data_q) > 2): raise ValueError("DUC+oscillator phase/amplitude test failed") - if not is_baseband: - for data in channel.trf_mmap: - channel.trf_write(data) - delay(.1*ms) - delay(1*ms) # lock - lock_detect = self.get_sta() & (PHASER_STA_TRF0_LD << ch) - delay(.1*ms) - if not lock_detect: - raise ValueError("TRF quadrature upconverter lock failure") + if is_baseband: + continue + + if channel.trf_read(0) & 0x7f != 0x68: + raise ValueError("TRF identification failed") + delay(.1*ms) + + delay(.2*ms) + for data in channel.trf_mmap: + channel.trf_write(data) + + delay(2*ms) # lock + if not (self.get_sta() & (PHASER_STA_TRF0_LD << ch)): + raise ValueError("TRF lock failure") + delay(.1*ms) + if channel.trf_read(0) & 0x1000: + raise ValueError("TRF R_SAT_ERR") + delay(.1*ms) self.set_cfg(clk_sel=self.clk_sel) # txena @@ -799,7 +808,8 @@ class PhaserChannel: self.phaser.spi_cfg(select=0, div=34, end=1, length=1) self.phaser.spi_write(0) delay((1 + 1)*34*4*ns) - return self.trf_write(0x00000008, readback=True) + return self.trf_write(0x00000008 | (cnt_mux_sel << 27), + readback=True) class PhaserOscillator: diff --git a/artiq/coredevice/trf372017.py b/artiq/coredevice/trf372017.py index 053a8a5fc..40957db86 100644 --- a/artiq/coredevice/trf372017.py +++ b/artiq/coredevice/trf372017.py @@ -37,7 +37,7 @@ class TRF372017: ld_ana_prec = 0 # 2b cp_tristate = 0 # 2b speedup = 0 - ld_dig_prec = 0 + ld_dig_prec = 1 en_dith = 1 mod_ord = 2 # 3rd order, 2b dith_sel = 0 From fec2f8b763d257e75bb863bb22b6b29327263245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 24 Sep 2020 10:59:22 +0000 Subject: [PATCH 43/51] phaser: increase slack for iotest --- artiq/coredevice/phaser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 56b09f13e..5dabc36a6 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -219,7 +219,7 @@ class Phaser: # either side) and no need to tune at runtime. # Parity provides another level of safety. for i in range(len(patterns)): - delay(.2*ms) + delay(.5*ms) errors = self.dac_iotest(patterns[i]) if errors: raise ValueError("DAC iotest failure") From 2fba3cfc78252d2b87d4e005099deee4d08c60dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 25 Sep 2020 20:54:59 +0000 Subject: [PATCH 44/51] phaser: debug init, systematic bring-up --- artiq/coredevice/phaser.py | 42 ++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 5dabc36a6..e42accb5a 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -153,13 +153,12 @@ class Phaser: for ch, trf in enumerate([trf0, trf1])] @kernel - def init(self): + def init(self, debug=False): """Initialize the board. Verifies board and chip presence, resets components, performs communication and configuration tests and establishes initial conditions. - """ board_id = self.read8(PHASER_ADDR_BOARD_ID) if board_id != PHASER_BOARD_ID: @@ -179,12 +178,15 @@ class Phaser: delay(.1*ms) # slack # reset - self.set_cfg(dac_resetb=0, dac_sleep=1, att0_rstn=0, att1_rstn=0, - dac_txena=0) + self.set_cfg(dac_resetb=0, dac_sleep=1, dac_txena=0, + trf0_ps=1, trf1_ps=1, + att0_rstn=0, att1_rstn=0) self.set_leds(0x00) self.set_fan_mu(0) - # bring everything out of reset, keep tx off - self.set_cfg(clk_sel=self.clk_sel, dac_txena=0) + # bring dac out of reset, keep tx off + self.set_cfg(clk_sel=self.clk_sel, dac_txena=0, + trf0_ps=1, trf1_ps=1, + att0_rstn=0, att1_rstn=0) delay(.1*ms) # slack # TODO: crossing dac_clk (125 MHz) edges with sync_dly (0-7 ns) @@ -235,7 +237,17 @@ class Phaser: # delay(.1*ms) self.clear_dac_alarms() delay(2*ms) # let it run a bit - self.check_dac_alarms() + alarms = self.get_dac_alarms() + delay(.1*ms) # slack + if alarms & ~0x0040: # ignore PLL alarms (see DS) + if debug: + print(alarms) + self.core.break_realtime() + else: + raise ValueError("DAC alarm") + + # power up trfs, release att reset + self.set_cfg(clk_sel=self.clk_sel, dac_txena=0) for ch in range(2): channel = self.channel[ch] @@ -244,7 +256,7 @@ class Phaser: if channel.get_att_mu() != 0x5a: raise ValueError("attenuator test failed") delay(.1*ms) - channel.set_att(31.5*dB) + channel.set_att_mu(0x00) # minimum attenuation # test oscillators and DUC for i in range(len(channel.oscillator)): @@ -289,7 +301,8 @@ class Phaser: raise ValueError("TRF R_SAT_ERR") delay(.1*ms) - self.set_cfg(clk_sel=self.clk_sel) # txena + # enable dac tx + self.set_cfg(clk_sel=self.clk_sel) @kernel def write8(self, addr, data): @@ -518,13 +531,6 @@ class Phaser: """ return self.dac_read(0x05) - @kernel - def check_dac_alarms(self): - alarm = self.get_dac_alarms() - delay(.1*ms) # slack - if alarm & ~0x0040: # ignore PLL alarms (see DS) - raise ValueError("DAC alarm") - @kernel def clear_dac_alarms(self): """Clear DAC alarm flags.""" @@ -561,9 +567,9 @@ class Phaser: # no need to go through the alarm register, # just read the error mask # self.clear_dac_alarms() - alarm = self.get_dac_alarms() + alarms = self.get_dac_alarms() delay(.1*ms) # slack - if alarm & 0x0080: # alarm_from_iotest + if alarms & 0x0080: # alarm_from_iotest errors = self.dac_read(0x04) delay(.1*ms) # slack else: From 569e5e56cd5cef6a4b26d5c1e2d2fd2677d3bb8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Sat, 26 Sep 2020 20:37:16 +0000 Subject: [PATCH 45/51] phaser: autotune and fix fifo_offset --- artiq/coredevice/phaser.py | 61 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index e42accb5a..6ed6c4b3b 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -115,10 +115,20 @@ class Phaser: class and applied during `init()`. See the :class:`DAC34H84` and :class:`TRF372017` source for details. + .. note:: To establish deterministic latency between RTIO time base and DAC + output, the DAC FIFO read pointer value (`fifo_offset`) must be + fixed. If `tune_fifo_offset=True` (the default) a value with maximum + margin is determined automatically by `dac_tune_fifo_offset` each time + `init()` is called. This value should be used for the `fifo_offset` key + of the `dac` settings of Phaser in `device_db.py` and automatic + tuning should be disabled by `tune_fifo_offset=False`. + :param channel: Base RTIO channel number :param core_device: Core device name (default: "core") :param miso_delay: Fastlink MISO signal delay to account for cable and buffer round trip. Tuning this might be automated later. + :param tune_fifo_offset: Tune the DAC FIFO read pointer offset + (default=True) :param clk_sel: Select the external SMA clock input (1 or 0) :param dac: DAC34H84 DAC settings as a dictionary. :param trf0: Channel 0 TRF372017 quadrature upconverter settings as a @@ -135,8 +145,8 @@ class Phaser: kernel_invariants = {"core", "channel_base", "t_frame", "miso_delay", "dac_mmap"} - def __init__(self, dmgr, channel_base, miso_delay=1, clk_sel=0, - dac=None, trf0=None, trf1=None, core_device="core"): + def __init__(self, dmgr, channel_base, miso_delay=1, tune_fifo_offset=True, + clk_sel=0, dac=None, trf0=None, trf1=None, core_device="core"): self.channel_base = channel_base self.core = dmgr.get(core_device) # TODO: auto-align miso-delay in phy @@ -146,6 +156,7 @@ class Phaser: assert self.core.ref_period == 1*ns self.t_frame = 10*8*4 self.clk_sel = clk_sel + self.tune_fifo_offset = tune_fifo_offset self.dac_mmap = DAC34H84(dac).get_mmap() @@ -232,6 +243,9 @@ class Phaser: if lvolt < 2 or lvolt > 5: raise ValueError("DAC PLL lock failed, check clocking") + if self.tune_fifo_offset: + self.dac_tune_fifo_offset() + # self.dac_write(0x20, 0x0000) # stop fifo sync # alarm = self.get_sta() & 1 # delay(.1*ms) @@ -578,6 +592,49 @@ class Phaser: self.dac_write(0x04, 0x0000) # clear iotest_result return errors + @kernel + def dac_tune_fifo_offset(self): + """Scan through `fifo_offset` and configure midpoint setting. + + :return: Optimal `fifo_offset` setting with maximum margin to write + pointer. + """ + # expect two or three error free offsets: + # + # read offset 01234567 + # write pointer w + # distance 32101234 + # error free x xx + config9 = self.dac_read(0x09) + delay(.1*ms) + good = 0 + for o in range(8): + # set new fifo_offset + self.dac_write(0x09, (config9 & 0x1fff) | (o << 13)) + self.clear_dac_alarms() + delay(.1*ms) # run + alarms = self.get_dac_alarms() + delay(.1*ms) # slack + if (alarms >> 11) & 0x7 == 0: # any fifo alarm + good |= 1 << o + # if there are good offsets accross the wrap around + # offset for computations + if good & 0x81 == 0x81: + good = ((good << 4) & 0xf0) | (good >> 4) + offset = 4 + else: + offset = 0 + # calculate mean + sum = 0 + count = 0 + for o in range(8): + if good & (1 << o): + sum += o + count += 1 + best = ((sum // count) + offset) % 8 + self.dac_write(0x09, (config9 & 0x1fff) | (best << 13)) + return best + class PhaserChannel: """Phaser channel IQ pair. From 6c8bddcf8d251ec76a08baf478504e6be5fd9794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Sat, 26 Sep 2020 21:13:00 +0000 Subject: [PATCH 46/51] phaser: tune sync_dly --- artiq/coredevice/phaser.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 6ed6c4b3b..9727fb92e 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -130,6 +130,7 @@ class Phaser: :param tune_fifo_offset: Tune the DAC FIFO read pointer offset (default=True) :param clk_sel: Select the external SMA clock input (1 or 0) + :param sync_dly: SYNC delay with respect to ISTR. :param dac: DAC34H84 DAC settings as a dictionary. :param trf0: Channel 0 TRF372017 quadrature upconverter settings as a dictionary. @@ -146,7 +147,8 @@ class Phaser: "dac_mmap"} def __init__(self, dmgr, channel_base, miso_delay=1, tune_fifo_offset=True, - clk_sel=0, dac=None, trf0=None, trf1=None, core_device="core"): + clk_sel=0, sync_dly=0, dac=None, trf0=None, trf1=None, + core_device="core"): self.channel_base = channel_base self.core = dmgr.get(core_device) # TODO: auto-align miso-delay in phy @@ -157,6 +159,7 @@ class Phaser: self.t_frame = 10*8*4 self.clk_sel = clk_sel self.tune_fifo_offset = tune_fifo_offset + self.sync_dly = sync_dly self.dac_mmap = DAC34H84(dac).get_mmap() @@ -200,9 +203,10 @@ class Phaser: att0_rstn=0, att1_rstn=0) delay(.1*ms) # slack - # TODO: crossing dac_clk (125 MHz) edges with sync_dly (0-7 ns) - # should change the optimal fifo_offset by 4 - self.set_sync_dly(4) + # TODO: crossing dac_clk (125 MHz) edges with sync_dly (2ns long, + # 0-14 ns delay in steps of 2ns) should change the optimal + # fifo_offset by 4 + self.set_sync_dly(self.sync_dly) # 4 wire SPI, sif4_enable self.dac_write(0x02, 0x0080) @@ -222,6 +226,11 @@ class Phaser: self.dac_write(data >> 16, data) delay(20*us) + # pll_ndivsync_ena disable + config18 = self.dac_read(0x18) + delay(.1*ms) + self.dac_write(0x18, config18 & ~0x0800) + patterns = [ [0xf05a, 0x05af, 0x5af0, 0xaf05], # test channel/iq/byte/nibble [0x7a7a, 0xb6b6, 0xeaea, 0x4545], # datasheet pattern a @@ -257,6 +266,7 @@ class Phaser: if debug: print(alarms) self.core.break_realtime() + # ignore alarms else: raise ValueError("DAC alarm") From c453c24fb06259e845fa49d8baa23934a1035ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Sat, 26 Sep 2020 21:16:08 +0000 Subject: [PATCH 47/51] phaser: tweak slacks --- artiq/coredevice/phaser.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 9727fb92e..32482d047 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -177,14 +177,14 @@ class Phaser: board_id = self.read8(PHASER_ADDR_BOARD_ID) if board_id != PHASER_BOARD_ID: raise ValueError("invalid board id") - delay(20*us) # slack + delay(.1*ms) # slack hw_rev = self.read8(PHASER_ADDR_HW_REV) - delay(20*us) # slack + delay(.1*ms) # slack is_baseband = hw_rev & PHASER_HW_REV_VARIANT gw_rev = self.read8(PHASER_ADDR_GW_REV) - delay(20*us) # slack + delay(.1*ms) # slack # allow a few errors during startup and alignment since boot if self.get_crc_err() > 20: @@ -218,13 +218,13 @@ class Phaser: delay(.1*ms) t = self.get_dac_temperature() - delay(.5*ms) + delay(.1*ms) if t < 10 or t > 90: raise ValueError("DAC temperature out of bounds") for data in self.dac_mmap: self.dac_write(data >> 16, data) - delay(20*us) + delay(40*us) # pll_ndivsync_ena disable config18 = self.dac_read(0x18) From eecd97ce4c1ea5e7196b4b95226e3f7227660435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Sun, 27 Sep 2020 17:15:16 +0000 Subject: [PATCH 48/51] phaser: debug and comments --- artiq/coredevice/phaser.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 32482d047..bcd83926c 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -203,9 +203,8 @@ class Phaser: att0_rstn=0, att1_rstn=0) delay(.1*ms) # slack - # TODO: crossing dac_clk (125 MHz) edges with sync_dly (2ns long, - # 0-14 ns delay in steps of 2ns) should change the optimal - # fifo_offset by 4 + # crossing dac_clk (reference) edges with sync_dly + # changes the optimal fifo_offset by 4 self.set_sync_dly(self.sync_dly) # 4 wire SPI, sif4_enable @@ -253,7 +252,10 @@ class Phaser: raise ValueError("DAC PLL lock failed, check clocking") if self.tune_fifo_offset: - self.dac_tune_fifo_offset() + fifo_offset = self.dac_tune_fifo_offset() + if debug: + print(fifo_offset) + self.core.break_realtime() # self.dac_write(0x20, 0x0000) # stop fifo sync # alarm = self.get_sta() & 1 From 139385a57179165709e95b4ae36d4f97ef764fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Sun, 18 Oct 2020 17:11:09 +0000 Subject: [PATCH 49/51] fastlink: add fastino test --- artiq/gateware/test/rtio/test_fastlink.py | 49 +++++++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/artiq/gateware/test/rtio/test_fastlink.py b/artiq/gateware/test/rtio/test_fastlink.py index 5b62c9aba..4304963d6 100644 --- a/artiq/gateware/test/rtio/test_fastlink.py +++ b/artiq/gateware/test/rtio/test_fastlink.py @@ -23,12 +23,13 @@ class TestPhaser(unittest.TestCase): clk = (clk << 2) & 0xff clk |= (yield self.dut.data[0]) if clk == 0x0f: - marker = (marker << 1) & 0x7f - marker |= (yield self.dut.data[1]) & 1 - if marker >> 1 == 0x01: + if marker == 0x01: stb += 1 if stb >= 3: break + # 10/2 + 1 marker bits + marker = (marker << 1) & 0x3f + marker |= (yield self.dut.data[1]) & 1 yield def test_frame(self): @@ -41,3 +42,45 @@ class TestPhaser(unittest.TestCase): self.assertEqual([d[0] for d in frame], [0, 0, 3, 3] * 10) self.assertEqual([d[1] & 1 for d in frame[4*4 - 1:10*4 - 1:4]], [0, 0, 0, 0, 0, 1]) + + +class TestFastino(unittest.TestCase): + def setUp(self): + self.dut = SerDes( + n_data=8, t_clk=7, d_clk=0b1100011, + n_frame=14, n_crc=12, poly=0x80f) + + def test_init(self): + pass + + def record_frame(self, frame): + clk = 0 + marker = 0 + stb = 0 + while True: + if stb == 2: + frame.append((yield self.dut.data)) + clk = (clk << 2) & 0xff + clk |= (yield self.dut.data[0]) + if clk in (0b11100011, 0b11000111): + if marker == 0x01: + stb += 1 + if stb >= 3: + break + # 14/2 + 1 marker bits + marker = (marker << 1) & 0xff + if clk & 0b100: + marker |= (yield self.dut.data[1]) >> 1 + else: + marker |= (yield self.dut.data[1]) & 1 + yield + + def test_frame(self): + frame = [] + self.dut.comb += self.dut.payload.eq((1 << len(self.dut.payload)) - 1) + run_simulation(self.dut, self.record_frame(frame), + clocks={n: 2 for n in ["sys", "rio", "rio_phy"]}, + vcd_name="fastlink.vcd") + self.assertEqual(len(frame), 7*14//2) + self.assertEqual([d[0] for d in frame], [3, 0, 1, 3, 2, 0, 3] * 7) + print(frame) From d98357051c5a57c93ca59956cf1c93308d7e37ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Sun, 18 Oct 2020 18:01:41 +0000 Subject: [PATCH 50/51] add ref data --- artiq/gateware/test/rtio/test_fastlink.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/artiq/gateware/test/rtio/test_fastlink.py b/artiq/gateware/test/rtio/test_fastlink.py index 4304963d6..3733e8205 100644 --- a/artiq/gateware/test/rtio/test_fastlink.py +++ b/artiq/gateware/test/rtio/test_fastlink.py @@ -83,4 +83,8 @@ class TestFastino(unittest.TestCase): vcd_name="fastlink.vcd") self.assertEqual(len(frame), 7*14//2) self.assertEqual([d[0] for d in frame], [3, 0, 1, 3, 2, 0, 3] * 7) + self.assertEqual(ref, frame) print(frame) + + +ref = [[3, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [1, 3, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [2, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [1, 3, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [2, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [1, 3, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [2, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [3, 2, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [1, 3, 3, 3, 3, 3, 3, 0], [3, 1, 3, 3, 3, 3, 3, 0], [2, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [3, 2, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [1, 3, 3, 3, 3, 3, 3, 0], [3, 1, 3, 3, 3, 3, 3, 0], [2, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [3, 2, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [1, 3, 3, 3, 3, 3, 3, 0], [3, 1, 3, 3, 3, 3, 3, 0], [2, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [3, 2, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [1, 3, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [2, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [3, 3, 1, 1, 1, 2, 1, 0]] From 30d1acee9f7e8c642df3d9c1ba5b67e501b37b39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Sun, 18 Oct 2020 22:27:05 +0200 Subject: [PATCH 51/51] fastlink: fix fastino style link --- artiq/gateware/rtio/phy/fastlink.py | 9 ++++----- artiq/gateware/test/rtio/test_fastlink.py | 12 +++--------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/artiq/gateware/rtio/phy/fastlink.py b/artiq/gateware/rtio/phy/fastlink.py index 796b01642..1a9317f77 100644 --- a/artiq/gateware/rtio/phy/fastlink.py +++ b/artiq/gateware/rtio/phy/fastlink.py @@ -74,9 +74,8 @@ class SerDes(Module): # big shift register for mosi and sr = [Signal(t_frame, reset_less=True) for i in range(n_mosi)] assert len(Cat(sr)) == len(words) - crc_insert = ([d[1] for d in self.data[:-1]] + - [d[0] for d in self.data[:-1]]) - crc_insert = Cat(crc_insert[-n_crc:]) + crc_insert = Cat(([d[0] for d in self.data[1:-1]] + + [d[1] for d in self.data[1:-1]])[:n_crc]) miso_sr = Signal(t_frame, reset_less=True) miso_sr_next = Signal.like(miso_sr) self.comb += [ @@ -106,8 +105,8 @@ class SerDes(Module): # transpose, load [sri.eq(Cat(words[i::n_mosi])) for i, sri in enumerate(sr)], # inject crc for the last cycle - crc_insert.eq(self.crca.next if n_crc // n_mosi == 1 - else self.crcb.next), + crc_insert.eq(self.crca.next if n_crc // n_mosi <= 1 + else self.crca.last), ), ] diff --git a/artiq/gateware/test/rtio/test_fastlink.py b/artiq/gateware/test/rtio/test_fastlink.py index 3733e8205..df8840951 100644 --- a/artiq/gateware/test/rtio/test_fastlink.py +++ b/artiq/gateware/test/rtio/test_fastlink.py @@ -36,8 +36,7 @@ class TestPhaser(unittest.TestCase): frame = [] self.dut.comb += self.dut.payload.eq((1 << len(self.dut.payload)) - 1) run_simulation(self.dut, self.record_frame(frame), - clocks={n: 2 for n in ["sys", "rio", "rio_phy"]}, - vcd_name="fastlink.vcd") + clocks={n: 2 for n in ["sys", "rio", "rio_phy"]}) self.assertEqual(len(frame), 8*10//2) self.assertEqual([d[0] for d in frame], [0, 0, 3, 3] * 10) self.assertEqual([d[1] & 1 for d in frame[4*4 - 1:10*4 - 1:4]], @@ -79,12 +78,7 @@ class TestFastino(unittest.TestCase): frame = [] self.dut.comb += self.dut.payload.eq((1 << len(self.dut.payload)) - 1) run_simulation(self.dut, self.record_frame(frame), - clocks={n: 2 for n in ["sys", "rio", "rio_phy"]}, - vcd_name="fastlink.vcd") + clocks={n: 2 for n in ["sys", "rio", "rio_phy"]}) self.assertEqual(len(frame), 7*14//2) self.assertEqual([d[0] for d in frame], [3, 0, 1, 3, 2, 0, 3] * 7) - self.assertEqual(ref, frame) - print(frame) - - -ref = [[3, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [1, 3, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [2, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [1, 3, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [2, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [1, 3, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [2, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [3, 2, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [1, 3, 3, 3, 3, 3, 3, 0], [3, 1, 3, 3, 3, 3, 3, 0], [2, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [3, 2, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [1, 3, 3, 3, 3, 3, 3, 0], [3, 1, 3, 3, 3, 3, 3, 0], [2, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [3, 2, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [1, 3, 3, 3, 3, 3, 3, 0], [3, 1, 3, 3, 3, 3, 3, 0], [2, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [3, 2, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [1, 3, 3, 3, 3, 3, 3, 0], [3, 3, 3, 3, 3, 3, 3, 0], [2, 3, 3, 3, 3, 3, 3, 0], [0, 3, 3, 3, 3, 3, 3, 0], [3, 3, 1, 1, 1, 2, 1, 0]] + self.assertEqual(frame[-1], [3, 3, 1, 1, 1, 2, 1, 0]) # crc12