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, }