From d449b0349df54cba3c5068e2cc625e50d72b165c Mon Sep 17 00:00:00 2001 From: Wanda Date: Wed, 10 Apr 2024 02:46:21 +0200 Subject: [PATCH] vendor._xilinx: implement `lib.io` buffer primitives. --- amaranth/vendor/_xilinx.py | 840 +++++++++++++++++++------------------ 1 file changed, 435 insertions(+), 405 deletions(-) diff --git a/amaranth/vendor/_xilinx.py b/amaranth/vendor/_xilinx.py index 1475649..f27f013 100644 --- a/amaranth/vendor/_xilinx.py +++ b/amaranth/vendor/_xilinx.py @@ -3,12 +3,425 @@ from abc import abstractmethod from ..hdl import * from ..lib.cdc import ResetSynchronizer +from ..lib import io, wiring from ..build import * __all__ = ["XilinxPlatform"] +class InnerBuffer(wiring.Component): + """A private component used to implement ``lib.io`` buffers. + + Works like ``lib.io.Buffer``, with the following differences: + + - ``port.invert`` is ignored (handling the inversion is the outer buffer's responsibility) + - ``t`` is per-pin inverted output enable + """ + def __init__(self, direction, port): + self.direction = direction + self.port = port + members = {} + if direction is not io.Direction.Output: + members["i"] = wiring.In(len(port)) + if direction is not io.Direction.Input: + members["o"] = wiring.Out(len(port)) + members["t"] = wiring.Out(len(port)) + super().__init__(wiring.Signature(members).flip()) + + def elaborate(self, platform): + m = Module() + + for bit in range(len(self.port)): + name = f"buf{bit}" + if isinstance(self.port, io.SingleEndedPort): + if self.direction is io.Direction.Input: + m.submodules[name] = Instance("IBUF", + i_I=self.port.io[bit], + o_O=self.i[bit], + ) + elif self.direction is io.Direction.Output: + m.submodules[name] = Instance("OBUFT", + i_T=self.t[bit], + i_I=self.o[bit], + o_O=self.port.io[bit], + ) + elif self.direction is io.Direction.Bidir: + m.submodules[name] = Instance("IOBUF", + i_T=self.t[bit], + i_I=self.o[bit], + o_O=self.i[bit], + io_IO=self.port.io[bit], + ) + else: + assert False # :nocov: + elif isinstance(self.port, io.DifferentialPort): + if self.direction is io.Direction.Input: + m.submodules[name] = Instance("IBUFDS", + i_I=self.port.p[bit], + i_IB=self.port.n[bit], + o_O=self.i[bit], + ) + elif self.direction is io.Direction.Output: + m.submodules[name] = Instance("OBUFTDS", + i_T=self.t[bit], + i_I=self.o[bit], + o_O=self.port.p[bit], + o_OB=self.port.n[bit], + ) + elif self.direction is io.Direction.Bidir: + m.submodules[name] = Instance("IOBUFDS", + i_T=self.t[bit], + i_I=self.o[bit], + o_O=self.i[bit], + io_IO=self.port.p[bit], + io_IOB=self.port.n[bit], + ) + else: + assert False # :nocov: + else: + raise TypeError(f"Unknown port type {self.port!r}") + + return m + + +class IOBuffer(io.Buffer): + def elaborate(self, platform): + m = Module() + + m.submodules.buf = buf = InnerBuffer(self.direction, self.port) + inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert)) + + if self.direction is not io.Direction.Output: + m.d.comb += self.i.eq(buf.i ^ inv_mask) + + if self.direction is not io.Direction.Input: + m.d.comb += buf.o.eq(self.o ^ inv_mask) + m.d.comb += buf.t.eq(~self.oe.replicate(len(self.port))) + + return m + + +def _make_dff(m, prefix, domain, d, q, *, iob=False, inv_clk=False): + for bit in range(len(q)): + kwargs = {} + if iob: + kwargs["a_IOB"] = "TRUE" + m.submodules[f"{prefix}_ff{bit}"] = Instance("FDCE", + i_C=~ClockSignal(domain) if inv_clk else ClockSignal(domain), + i_CE=Const(1), + i_CLR=Const(0), + i_D=d[bit], + o_Q=q[bit], + **kwargs, + ) + + +class FFBuffer(io.FFBuffer): + def elaborate(self, platform): + m = Module() + + m.submodules.buf = buf = InnerBuffer(self.direction, self.port) + inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert)) + + if self.direction is not io.Direction.Output: + i_inv = Signal.like(self.i) + _make_dff(m, "i", self.i_domain, buf.i, i_inv, iob=True) + m.d.comb += self.i.eq(i_inv ^ inv_mask) + + if self.direction is not io.Direction.Input: + o_inv = Signal.like(self.o) + m.d.comb += o_inv.eq(self.o ^ inv_mask) + _make_dff(m, "o", self.o_domain, o_inv, buf.o, iob=True) + _make_dff(m, "oe", self.o_domain, ~self.oe.replicate(len(self.port)), buf.t, iob=True) + + return m + + +class DDRBufferVirtex2(io.DDRBuffer): + def elaborate(self, platform): + m = Module() + + m.submodules.buf = buf = InnerBuffer(self.direction, self.port) + inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert)) + + if self.direction is not io.Direction.Output: + i0_inv = Signal(len(self.port)) + i1_inv = Signal(len(self.port)) + # First-generation input DDR register: basically just two FFs with opposite + # clocks. Add a register on both outputs, so that they enter fabric on + # the same clock edge, adding one cycle of latency. + i0_ff = Signal(len(self.port)) + i1_ff = Signal(len(self.port)) + _make_dff(m, "i0", self.i_domain, buf.i, i0_ff, iob=True) + _make_dff(m, "i1", self.i_domain, buf.i, i1_ff, iob=True, inv_clk=True) + _make_dff(m, "i0_p", self.i_domain, i0_ff, i0_inv) + _make_dff(m, "i1_p", self.i_domain, i1_ff, i0_inv) + m.d.comb += self.i[0].eq(i0_inv ^ inv_mask) + m.d.comb += self.i[1].eq(i1_inv ^ inv_mask) + + if self.direction is not io.Direction.Input: + o0_inv = Signal(len(self.port)) + o1_inv = Signal(len(self.port)) + o1_ff = Signal(len(self.port)) + m.d.comb += [ + o0_inv.eq(self.o[0] ^ inv_mask), + o1_inv.eq(self.o[1] ^ inv_mask), + ] + _make_dff(m, "o1_p", self.o_domain, o1_inv, o1_ff) + for bit in range(len(self.port)): + m.submodules[f"o_ddr{bit}"] = Instance("FDDRCPE", + i_C0=ClockSignal(self.o_domain), + i_C1=~ClockSignal(self.o_domain), + i_CE=Const(1), + i_PRE=Const(0), + i_CLR=Const(0), + i_D0=o0_inv[bit], + i_D1=o1_ff[bit], + o_Q=buf.o[bit], + ) + _make_dff(m, "oe", self.o_domain, ~self.oe.replicate(len(self.port)), buf.t, iob=True) + + return m + + +class DDRBufferSpartan3E(io.DDRBuffer): + def elaborate(self, platform): + m = Module() + + m.submodules.buf = buf = InnerBuffer(self.direction, self.port) + inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert)) + + # On Spartan 3E/3A, the situation with DDR registers is messy: while the hardware + # supports same-edge alignment, it does so by borrowing the resources of the other + # pin in the differential pair (if any). Since we cannot be sure if the other pin + # is actually unused (or if the pin is even part of a differential pair in the first + # place), we only use the hardware alignment feature in two cases: + # + # - differential inputs (since the other pin's input registers will be unused) + # - true differential outputs (since they use only one pin's output registers, + # as opposed to pseudo-differential outputs that use both) + TRUE_DIFF_S3EA = { + "LVDS_33", "LVDS_25", + "MINI_LVDS_33", "MINI_LVDS_25", + "RSDS_33", "RSDS_25", + "PPDS_33", "PPDS_25", + "TMDS_33", + } + + if self.direction is not io.Direction.Output: + i0_inv = Signal(len(self.port)) + i1_inv = Signal(len(self.port)) + if platform.family == "spartan6" or isinstance(self.port, io.DifferentialPort): + # Second-generation input DDR register: hw realigns i1 to positive clock edge, + # but also misaligns it with i0 input. Re-register first input before it + # enters fabric. This allows both inputs to enter fabric on the same clock + # edge, and adds one cycle of latency. + i0_ff = Signal(len(self.port)) + for bit in range(len(self.port)): + m.submodules[f"i_ddr{bit}"] = Instance("IDDR2", + p_DDR_ALIGNMENT="C0", + p_SRTYPE="ASYNC", + p_INIT_Q0=Const(0), + p_INIT_Q1=Const(0), + i_C0=ClockSignal(self.i_domain), + i_C1=~ClockSignal(self.i_domain), + i_CE=Const(1), + i_S=Const(0), i_R=Const(0), + i_D=buf.i[bit], + o_Q0=i0_ff[bit], + o_Q1=i1_inv[bit] + ) + _make_dff(m, "i0_p", self.i_domain, i0_ff, i0_inv) + else: + # No extra register available for hw alignment, use CLB registers. + i0_ff = Signal(len(self.port)) + i1_ff = Signal(len(self.port)) + for bit in range(len(self.port)): + m.submodules[f"i_ddr{bit}"] = Instance("IDDR2", + p_DDR_ALIGNMENT="NONE", + p_SRTYPE="ASYNC", + p_INIT_Q0=Const(0), + p_INIT_Q1=Const(0), + i_C0=ClockSignal(self.i_domain), + i_C1=~ClockSignal(self.i_domain), + i_CE=Const(1), + i_S=Const(0), i_R=Const(0), + i_D=buf.i[bit], + o_Q0=i0_ff[bit], + o_Q1=i1_ff[bit] + ) + _make_dff(m, "i0_p", self.i_domain, i0_ff, i0_inv) + _make_dff(m, "i1_p", self.i_domain, i1_ff, i0_inv) + m.d.comb += self.i[0].eq(i0_inv ^ inv_mask) + m.d.comb += self.i[1].eq(i1_inv ^ inv_mask) + + if self.direction is not io.Direction.Input: + o0_inv = Signal(len(self.port)) + o1_inv = Signal(len(self.port)) + m.d.comb += [ + o0_inv.eq(self.o[0] ^ inv_mask), + o1_inv.eq(self.o[1] ^ inv_mask), + ] + for bit in range(len(self.port)): + if platform.family == "spartan3e": + merge_ff = False + elif platform.family.startswith("spartan3a"): + if isinstance(self.port, io.DifferentialPort): + iostd = self.port.p.metadata[bit].attrs.get("IOSTANDARD", "LVDS_25") + merge_ff = iostd in TRUE_DIFF_S3EA + else: + merge_ff = False + else: + merge_ff = True + if merge_ff: + m.submodules[f"o_ddr{bit}"] = Instance("ODDR2", + p_DDR_ALIGNMENT="C0", + p_SRTYPE="ASYNC", + p_INIT=Const(0), + i_C0=ClockSignal(self.o_domain), + i_C1=~ClockSignal(self.o_domain), + i_CE=Const(1), + i_S=Const(0), + i_R=Const(0), + i_D0=o0_inv[bit], + i_D1=o1_inv[bit], + o_Q=buf.o[bit], + ) + else: + o1_ff = Signal() + _make_dff(m, f"o1_p{bit}_", self.o_domain, o1_inv[bit], o1_ff) + m.submodules[f"o_ddr{bit}"] = Instance("ODDR2", + p_DDR_ALIGNMENT="NONE", + p_SRTYPE="ASYNC", + p_INIT=Const(0), + i_C0=ClockSignal(self.o_domain), + i_C1=~ClockSignal(self.o_domain), + i_CE=Const(1), + i_S=Const(0), + i_R=Const(0), + i_D0=o0_inv[bit], + i_D1=o1_ff, + o_Q=buf.o[bit], + ) + if platform.family == "spartan6": + for bit in range(len(self.port)): + m.submodules[f"oe_ddr{bit}"] = Instance("ODDR2", + p_DDR_ALIGNMENT="C0", + p_SRTYPE="ASYNC", + p_INIT=Const(0), + i_C0=ClockSignal(self.o_domain), + i_C1=~ClockSignal(self.o_domain), + i_CE=Const(1), + i_S=Const(0), + i_R=Const(0), + i_D0=~self.oe, + i_D1=~self.oe, + o_Q=buf.t[bit], + ) + else: + _make_dff(m, "oe", self.o_domain, ~self.oe.replicate(len(self.port)), buf.t, iob=True) + + return m + + +class DDRBufferVirtex4(io.DDRBuffer): + def elaborate(self, platform): + m = Module() + + m.submodules.buf = buf = InnerBuffer(self.direction, self.port) + inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert)) + + if self.direction is not io.Direction.Output: + i0_inv = Signal(len(self.port)) + i1_inv = Signal(len(self.port)) + for bit in range(len(self.port)): + m.submodules[f"i_ddr{bit}"] = Instance("IDDR", + p_DDR_CLK_EDGE="SAME_EDGE_PIPELINED", + p_SRTYPE="ASYNC", + p_INIT_Q1=Const(0), + p_INIT_Q2=Const(0), + i_C=ClockSignal(self.i_domain), + i_CE=Const(1), + i_S=Const(0), i_R=Const(0), + i_D=buf.i[bit], + o_Q1=i0_inv[bit], + o_Q2=i1_inv[bit] + ) + m.d.comb += self.i[0].eq(i0_inv ^ inv_mask) + m.d.comb += self.i[1].eq(i1_inv ^ inv_mask) + + if self.direction is not io.Direction.Input: + o0_inv = Signal(len(self.port)) + o1_inv = Signal(len(self.port)) + m.d.comb += [ + o0_inv.eq(self.o[0] ^ inv_mask), + o1_inv.eq(self.o[1] ^ inv_mask), + ] + for bit in range(len(self.port)): + m.submodules[f"o_ddr{bit}"] = Instance("ODDR", + p_DDR_CLK_EDGE="SAME_EDGE", + p_SRTYPE="ASYNC", + p_INIT=Const(0), + i_C=ClockSignal(self.o_domain), + i_CE=Const(1), + i_S=Const(0), + i_R=Const(0), + i_D1=o0_inv[bit], + i_D2=o1_inv[bit], + o_Q=buf.o[bit], + ) + _make_dff(m, "oe", self.o_domain, ~self.oe.replicate(len(self.port)), buf.t, iob=True) + + return m + + +class DDRBufferUltrascale(io.DDRBuffer): + def elaborate(self, platform): + m = Module() + + m.submodules.buf = buf = InnerBuffer(self.direction, self.port) + inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert)) + + if self.direction is not io.Direction.Output: + i0_inv = Signal(len(self.port)) + i1_inv = Signal(len(self.port)) + for bit in range(len(self.port)): + m.submodules[f"i_ddr{bit}"] = Instance("IDDRE1", + p_DDR_CLK_EDGE="SAME_EDGE_PIPELINED", + p_IS_C_INVERTED=Const(0), + p_IS_CB_INVERTED=Const(1), + i_C=ClockSignal(self.i_domain), + i_CB=ClockSignal(self.i_domain), + i_R=Const(0), + i_D=buf.i[bit], + o_Q1=i0_inv[bit], + o_Q2=i1_inv[bit] + ) + m.d.comb += self.i[0].eq(i0_inv ^ inv_mask) + m.d.comb += self.i[1].eq(i1_inv ^ inv_mask) + + if self.direction is not io.Direction.Input: + o0_inv = Signal(len(self.port)) + o1_inv = Signal(len(self.port)) + m.d.comb += [ + o0_inv.eq(self.o[0] ^ inv_mask), + o1_inv.eq(self.o[1] ^ inv_mask), + ] + for bit in range(len(self.port)): + m.submodules[f"o_ddr{bit}"] = Instance("ODDRE1", + p_SRVAL=Const(0), + i_C=ClockSignal(self.o_domain), + i_SR=Const(0), + i_D1=o0_inv[bit], + i_D2=o1_inv[bit], + o_Q=buf.o[bit], + ) + _make_dff(m, "oe", self.o_domain, ~self.oe.replicate(len(self.port)), buf.t, iob=True) + + return m + + class XilinxPlatform(TemplatedPlatform): """ .. rubric:: Vivado toolchain @@ -751,413 +1164,30 @@ class XilinxPlatform(TemplatedPlatform): super().add_clock_constraint(clock, frequency) clock.attrs["keep"] = "TRUE" - def _get_xdr_buffer(self, m, pin, iostd, *, i_invert=False, o_invert=False): - XFDDR_FAMILIES = { - "virtex2", - "virtex2p", - "spartan3", - } - XDDR2_FAMILIES = { - "spartan3e", - "spartan3a", - "spartan3adsp", - "spartan6", - } - XDDR_FAMILIES = { - "virtex4", - "virtex5", - "virtex6", - "series7", - } - XDDRE1_FAMILIES = { - "ultrascale", - "ultrascaleplus", - } - - def get_iob_dff(clk, d, q): - # SDR I/O is performed by packing a flip-flop into the pad IOB. - for bit in range(len(q)): - m.submodules += Instance("FDCE", - a_IOB="TRUE", - i_C=clk, - i_CE=Const(1), - i_CLR=Const(0), - i_D=d[bit], - o_Q=q[bit] - ) - - def get_dff(clk, d, q): - for bit in range(len(q)): - m.submodules += Instance("FDCE", - i_C=clk, - i_CE=Const(1), - i_CLR=Const(0), - i_D=d[bit], - o_Q=q[bit] - ) - - def get_ifddr(clk, io, q0, q1): - assert self.family in XFDDR_FAMILIES - for bit in range(len(q0)): - m.submodules += Instance("IFDDRCPE", - i_C0=clk, i_C1=~clk, - i_CE=Const(1), - i_CLR=Const(0), i_PRE=Const(0), - i_D=io[bit], - o_Q0=q0[bit], o_Q1=q1[bit] - ) - - def get_iddr2(clk, d, q0, q1, alignment): - assert self.family in XDDR2_FAMILIES - for bit in range(len(q0)): - m.submodules += Instance("IDDR2", - p_DDR_ALIGNMENT=alignment, - p_SRTYPE="ASYNC", - p_INIT_Q0=C(0, 1), p_INIT_Q1=C(0, 1), - i_C0=clk, i_C1=~clk, - i_CE=Const(1), - i_S=Const(0), i_R=Const(0), - i_D=d[bit], - o_Q0=q0[bit], o_Q1=q1[bit] - ) - - def get_iddr(clk, d, q1, q2): - assert self.family in XDDR_FAMILIES or self.family in XDDRE1_FAMILIES - for bit in range(len(q1)): - if self.family in XDDR_FAMILIES: - m.submodules += Instance("IDDR", - p_DDR_CLK_EDGE="SAME_EDGE_PIPELINED", - p_SRTYPE="ASYNC", - p_INIT_Q1=C(0, 1), p_INIT_Q2=C(0, 1), - i_C=clk, - i_CE=Const(1), - i_S=Const(0), i_R=Const(0), - i_D=d[bit], - o_Q1=q1[bit], o_Q2=q2[bit] - ) - else: - m.submodules += Instance("IDDRE1", - p_DDR_CLK_EDGE="SAME_EDGE_PIPELINED", - p_IS_C_INVERTED=C(0, 1), p_IS_CB_INVERTED=C(1, 1), - i_C=clk, i_CB=clk, - i_R=Const(0), - i_D=d[bit], - o_Q1=q1[bit], o_Q2=q2[bit] - ) - - def get_fddr(clk, d0, d1, q): - for bit in range(len(q)): - if self.family in XFDDR_FAMILIES: - m.submodules += Instance("FDDRCPE", - i_C0=clk, i_C1=~clk, - i_CE=Const(1), - i_PRE=Const(0), i_CLR=Const(0), - i_D0=d0[bit], i_D1=d1[bit], - o_Q=q[bit] - ) - else: - m.submodules += Instance("ODDR2", - p_DDR_ALIGNMENT="NONE", - p_SRTYPE="ASYNC", - p_INIT=C(0, 1), - i_C0=clk, i_C1=~clk, - i_CE=Const(1), - i_S=Const(0), i_R=Const(0), - i_D0=d0[bit], i_D1=d1[bit], - o_Q=q[bit] - ) - - def get_oddr(clk, d1, d2, q): - for bit in range(len(q)): - if self.family in XDDR2_FAMILIES: - m.submodules += Instance("ODDR2", - p_DDR_ALIGNMENT="C0", - p_SRTYPE="ASYNC", - p_INIT=C(0, 1), - i_C0=clk, i_C1=~clk, - i_CE=Const(1), - i_S=Const(0), i_R=Const(0), - i_D0=d1[bit], i_D1=d2[bit], - o_Q=q[bit] - ) - elif self.family in XDDR_FAMILIES: - m.submodules += Instance("ODDR", - p_DDR_CLK_EDGE="SAME_EDGE", - p_SRTYPE="ASYNC", - p_INIT=C(0, 1), - i_C=clk, - i_CE=Const(1), - i_S=Const(0), i_R=Const(0), - i_D1=d1[bit], i_D2=d2[bit], - o_Q=q[bit] - ) - elif self.family in XDDRE1_FAMILIES: - m.submodules += Instance("ODDRE1", - p_SRVAL=C(0, 1), - i_C=clk, - i_SR=Const(0), - i_D1=d1[bit], i_D2=d2[bit], - o_Q=q[bit] - ) - - def get_ineg(y, invert): - if invert: - a = Signal.like(y, name_suffix="_n") - m.d.comb += y.eq(~a) - return a + def get_io_buffer(self, buffer): + if isinstance(buffer, io.Buffer): + result = IOBuffer(buffer.direction, buffer.port) + elif isinstance(buffer, io.FFBuffer): + result = FFBuffer(buffer.direction, buffer.port) + elif isinstance(buffer, io.DDRBuffer): + if self.family in ("virtex2", "virtex2p", "spartan3"): + result = DDRBufferVirtex2(buffer.direction, buffer.port) + elif self.family in ("spartan3e", "spartan3a", "spartan3adsp", "spartan6"): + result = DDRBufferSpartan3E(buffer.direction, buffer.port) + elif self.family in ("virtex4", "virtex5", "virtex6", "series7"): + result = DDRBufferVirtex4(buffer.direction, buffer.port) + elif self.family in ("ultrascale", "ultrascaleplus"): + result = DDRBufferUltrascale(buffer.direction, buffer.port) else: - return y - - def get_oneg(a, invert): - if invert: - y = Signal.like(a, name_suffix="_n") - m.d.comb += y.eq(~a) - return y - else: - return a - - if "i" in pin.dir: - if pin.xdr < 2: - pin_i = get_ineg(pin.i, i_invert) - elif pin.xdr == 2: - pin_i0 = get_ineg(pin.i0, i_invert) - pin_i1 = get_ineg(pin.i1, i_invert) - if "o" in pin.dir: - if pin.xdr < 2: - pin_o = get_oneg(pin.o, o_invert) - elif pin.xdr == 2: - pin_o0 = get_oneg(pin.o0, o_invert) - pin_o1 = get_oneg(pin.o1, o_invert) - - i = o = t = None - if "i" in pin.dir: - i = Signal(pin.width, name=f"{pin.name}_xdr_i") - if "o" in pin.dir: - o = Signal(pin.width, name=f"{pin.name}_xdr_o") - if pin.dir in ("oe", "io"): - t = Signal(1, name=f"{pin.name}_xdr_t") - - if pin.xdr == 0: - if "i" in pin.dir: - i = pin_i - if "o" in pin.dir: - o = pin_o - if pin.dir in ("oe", "io"): - t = ~pin.oe - elif pin.xdr == 1: - if "i" in pin.dir: - get_iob_dff(pin.i_clk, i, pin_i) - if "o" in pin.dir: - get_iob_dff(pin.o_clk, pin_o, o) - if pin.dir in ("oe", "io"): - get_iob_dff(pin.o_clk, ~pin.oe, t) - elif pin.xdr == 2: - # On Spartan 3E/3A, the situation with DDR registers is messy: while the hardware - # supports same-edge alignment, it does so by borrowing the resources of the other - # pin in the differential pair (if any). Since we cannot be sure if the other pin - # is actually unused (or if the pin is even part of a differential pair in the first - # place), we only use the hardware alignment feature in two cases: - # - # - differential inputs (since the other pin's input registers will be unused) - # - true differential outputs (since they use only one pin's output registers, - # as opposed to pseudo-differential outputs that use both) - TRUE_DIFF_S3EA = { - "LVDS_33", "LVDS_25", - "MINI_LVDS_33", "MINI_LVDS_25", - "RSDS_33", "RSDS_25", - "PPDS_33", "PPDS_25", - "TMDS_33", - } - DIFF_S3EA = TRUE_DIFF_S3EA | { - "DIFF_HSTL_I", - "DIFF_HSTL_III", - "DIFF_HSTL_I_18", - "DIFF_HSTL_II_18", - "DIFF_HSTL_III_18", - "DIFF_SSTL3_I", - "DIFF_SSTL3_II", - "DIFF_SSTL2_I", - "DIFF_SSTL2_II", - "DIFF_SSTL18_I", - "DIFF_SSTL18_II", - "BLVDS_25", - } - if "i" in pin.dir: - if self.family in XFDDR_FAMILIES: - # First-generation input DDR register: basically just two FFs with opposite - # clocks. Add a register on both outputs, so that they enter fabric on - # the same clock edge, adding one cycle of latency. - i0_ff = Signal.like(pin_i0, name_suffix="_ff") - i1_ff = Signal.like(pin_i1, name_suffix="_ff") - get_dff(pin.i_clk, i0_ff, pin_i0) - get_dff(pin.i_clk, i1_ff, pin_i1) - get_iob_dff(pin.i_clk, i, i0_ff) - get_iob_dff(~pin.i_clk, i, i1_ff) - elif self.family in XDDR2_FAMILIES: - if self.family == 'spartan6' or iostd in DIFF_S3EA: - # Second-generation input DDR register: hw realigns i1 to positive clock edge, - # but also misaligns it with i0 input. Re-register first input before it - # enters fabric. This allows both inputs to enter fabric on the same clock - # edge, and adds one cycle of latency. - i0_ff = Signal.like(pin_i0, name_suffix="_ff") - get_dff(pin.i_clk, i0_ff, pin_i0) - get_iddr2(pin.i_clk, i, i0_ff, pin_i1, "C0") - else: - # No extra register available for hw alignment, use extra registers. - i0_ff = Signal.like(pin_i0, name_suffix="_ff") - i1_ff = Signal.like(pin_i1, name_suffix="_ff") - get_dff(pin.i_clk, i0_ff, pin_i0) - get_dff(pin.i_clk, i1_ff, pin_i1) - get_iddr2(pin.i_clk, i, i0_ff, i1_ff, "NONE") - else: - # Third-generation input DDR register: does all of the above on its own. - get_iddr(pin.i_clk, i, pin_i0, pin_i1) - if "o" in pin.dir: - if self.family in XFDDR_FAMILIES or self.family == "spartan3e" or (self.family.startswith("spartan3a") and iostd not in TRUE_DIFF_S3EA): - # For this generation, we need to realign o1 input ourselves. - o1_ff = Signal.like(pin_o1, name_suffix="_ff") - get_dff(pin.o_clk, pin_o1, o1_ff) - get_fddr(pin.o_clk, pin_o0, o1_ff, o) - else: - get_oddr(pin.o_clk, pin_o0, pin_o1, o) - if pin.dir in ("oe", "io"): - if self.family == "spartan6": - get_oddr(pin.o_clk, ~pin.oe, ~pin.oe, t) - else: - get_iob_dff(pin.o_clk, ~pin.oe, t) + raise TypeError(f"Family {self.family} doesn't implement DDR buffers") else: - assert False - - return (i, o, t) - - def _get_valid_xdrs(self): - if self.family in {"virtex", "virtexe"}: - return (0, 1) - else: - return (0, 1, 2) - - def get_input(self, pin, port, attrs, invert): - self._check_feature("single-ended input", pin, attrs, - valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD"), i_invert=invert) - for bit in range(pin.width): - m.submodules[f"{pin.name}_{bit}"] = Instance("IBUF", - i_I=port.io[bit], - o_O=i[bit] - ) - return m - - def get_output(self, pin, port, attrs, invert): - self._check_feature("single-ended output", pin, attrs, - valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD"), o_invert=invert) - if self.vendor_toolchain: - for bit in range(pin.width): - m.submodules[f"{pin.name}_{bit}"] = Instance("OBUF", - i_I=o[bit], - o_O=port.io[bit] - ) - else: - m.d.comb += port.eq(self._invert_if(invert, o)) - return m - - def get_tristate(self, pin, port, attrs, invert): - if not self.vendor_toolchain: - return super().get_tristate(pin, port, attrs, invert) - - self._check_feature("single-ended tristate", pin, attrs, - valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD"), o_invert=invert) - for bit in range(pin.width): - m.submodules[f"{pin.name}_{bit}"] = Instance("OBUFT", - i_T=t, - i_I=o[bit], - o_O=port.io[bit] - ) - return m - - def get_input_output(self, pin, port, attrs, invert): - if not self.vendor_toolchain: - return super().get_input_output(pin, port, attrs, invert) - - self._check_feature("single-ended input/output", pin, attrs, - valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD"), i_invert=invert, o_invert=invert) - for bit in range(pin.width): - m.submodules[f"{pin.name}_{bit}"] = Instance("IOBUF", - i_T=t, - i_I=o[bit], - o_O=i[bit], - io_IO=port.io[bit] - ) - return m - - def get_diff_input(self, pin, port, attrs, invert): - if not self.vendor_toolchain: - return super().get_diff_input(pin, port, attrs, invert) - - self._check_feature("differential input", pin, attrs, - valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD", "LVDS_25"), i_invert=invert) - for bit in range(pin.width): - m.submodules[f"{pin.name}_{bit}"] = Instance("IBUFDS", - i_I=port.p[bit], i_IB=port.n[bit], - o_O=i[bit] - ) - return m - - def get_diff_output(self, pin, port, attrs, invert): - if not self.vendor_toolchain: - return super().get_diff_output(pin, port, attrs, invert) - - self._check_feature("differential output", pin, attrs, - valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD", "LVDS_25"), o_invert=invert) - for bit in range(pin.width): - m.submodules[f"{pin.name}_{bit}"] = Instance("OBUFDS", - i_I=o[bit], - o_O=port.p[bit], o_OB=port.n[bit] - ) - return m - - def get_diff_tristate(self, pin, port, attrs, invert): - if not self.vendor_toolchain: - return super().get_diff_tristate(pin, port, attrs, invert) - - self._check_feature("differential tristate", pin, attrs, - valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD", "LVDS_25"), o_invert=invert) - for bit in range(pin.width): - m.submodules[f"{pin.name}_{bit}"] = Instance("OBUFTDS", - i_T=t, - i_I=o[bit], - o_O=port.p[bit], o_OB=port.n[bit] - ) - return m - - def get_diff_input_output(self, pin, port, attrs, invert): - if not self.vendor_toolchain: - return super().get_diff_input_output(pin, port, attrs, invert) - - self._check_feature("differential input/output", pin, attrs, - valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD", "LVDS_25"), i_invert=invert, o_invert=invert) - for bit in range(pin.width): - m.submodules[f"{pin.name}_{bit}"] = Instance("IOBUFDS", - i_T=t, - i_I=o[bit], - o_O=i[bit], - io_IO=port.p[bit], io_IOB=port.n[bit] - ) - return m + raise TypeError(f"Unsupported buffer type {buffer!r}") # :nocov: + if buffer.direction is not io.Direction.Output: + result.i = buffer.i + if buffer.direction is not io.Direction.Input: + result.o = buffer.o + result.oe = buffer.oe + return result # The synchronizer implementations below apply two separate but related timing constraints. #