diff --git a/amaranth/vendor/_lattice_ecp5.py b/amaranth/vendor/_lattice_ecp5.py index bb63537ed..c78e3eae8 100644 --- a/amaranth/vendor/_lattice_ecp5.py +++ b/amaranth/vendor/_lattice_ecp5.py @@ -1,9 +1,169 @@ from abc import abstractmethod from ..hdl import * +from ..lib import io, wiring from ..build import * +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() + + if isinstance(self.port, io.SingleEndedPort): + io_port = self.port.io + elif isinstance(self.port, io.DifferentialPort): + io_port = self.port.p + else: + raise TypeError(f"Unknown port type {self.port!r}") + + for bit in range(len(self.port)): + name = f"buf{bit}" + if self.direction is io.Direction.Input: + m.submodules[name] = Instance("IB", + i_I=io_port[bit], + o_O=self.i[bit], + ) + elif self.direction is io.Direction.Output: + m.submodules[name] = Instance("OBZ", + i_T=self.t[bit], + i_I=self.o[bit], + o_O=io_port[bit], + ) + elif self.direction is io.Direction.Bidir: + m.submodules[name] = Instance("BB", + i_T=self.t[bit], + i_I=self.o[bit], + o_O=self.i[bit], + io_B=io_port[bit], + ) + else: + assert False # :nocov: + + 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_oereg(m, domain, oe, q): + for bit in range(len(q)): + m.submodules[f"oe_ff{bit}"] = Instance("OFS1P3DX", + i_SCLK=ClockSignal(domain), + i_SP=Const(1), + i_CD=Const(0), + i_D=oe, + o_Q=q[bit], + ) + + +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) + for bit in range(len(self.port)): + m.submodules[f"i_ff{bit}"] = Instance("IFS1P3DX", + i_SCLK=ClockSignal(self.o_domain), + i_SP=Const(1), + i_CD=Const(0), + i_D=buf.i[bit], + o_Q=i_inv[bit], + ) + 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) + for bit in range(len(self.port)): + m.submodules[f"o_ff{bit}"] = Instance("OFS1P3DX", + i_SCLK=ClockSignal(self.o_domain), + i_SP=Const(1), + i_CD=Const(0), + i_D=o_inv[bit], + o_Q=buf.o[bit], + ) + _make_oereg(m, self.o_domain, ~self.oe, buf.t) + + return m + + +class DDRBuffer(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.i_ddr = Instance("IDDRX1F", + i_SCLK=ClockSignal(self.i_domain), + i_RST=Const(0), + i_D=buf.i[bit], + o_Q0=i0_inv[bit], + o_Q1=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.o_ddr = Instance("ODDRX1F", + i_SCLK=ClockSignal(self.o_domain), + i_RST=Const(0), + i_D0=o0_inv[bit], + i_D1=o1_inv[bit], + o_Q=buf.o[bit], + ) + _make_oereg(m, self.o_domain, ~self.oe, buf.t) + + return m + + class LatticeECP5Platform(TemplatedPlatform): """ .. rubric:: Trellis toolchain @@ -346,319 +506,21 @@ def create_missing_domain(self, name): m.d.comb += ClockSignal("sync").eq(clk_i) return m - _single_ended_io_types = [ - "HSUL12", "LVCMOS12", "LVCMOS15", "LVCMOS18", "LVCMOS25", "LVCMOS33", "LVTTL33", - "SSTL135_I", "SSTL135_II", "SSTL15_I", "SSTL15_II", "SSTL18_I", "SSTL18_II", - ] - _differential_io_types = [ - "BLVDS25", "BLVDS25E", "HSUL12D", "LVCMOS18D", "LVCMOS25D", "LVCMOS33D", - "LVDS", "LVDS25E", "LVPECL33", "LVPECL33E", "LVTTL33D", "MLVDS", "MLVDS25E", - "SLVS", "SSTL135D_I", "SSTL135D_II", "SSTL15D_I", "SSTL15D_II", "SSTL18D_I", - "SSTL18D_II", "SUBLVDS", - ] - - def should_skip_port_component(self, port, attrs, component): - # On ECP5, a differential IO is placed by only instantiating an IO buffer primitive at - # the PIOA or PIOC location, which is always the non-inverting pin. - if attrs.get("IO_TYPE", "LVCMOS25") in self._differential_io_types and component == "n": - return True - return False - - def _get_xdr_buffer(self, m, pin, *, i_invert=False, o_invert=False): - def get_ireg(clk, d, q): - for bit in range(len(q)): - m.submodules += Instance("IFS1P3DX", - i_SCLK=clk, - i_SP=Const(1), - i_CD=Const(0), - i_D=d[bit], - o_Q=q[bit] - ) - - def get_oreg(clk, d, q): - for bit in range(len(q)): - m.submodules += Instance("OFS1P3DX", - i_SCLK=clk, - i_SP=Const(1), - i_CD=Const(0), - i_D=d[bit], - o_Q=q[bit] - ) - - def get_oereg(clk, oe, q): - for bit in range(len(q)): - m.submodules += Instance("OFS1P3DX", - i_SCLK=clk, - i_SP=Const(1), - i_CD=Const(0), - i_D=oe, - o_Q=q[bit] - ) - - def get_iddr(sclk, d, q0, q1): - for bit in range(len(d)): - m.submodules += Instance("IDDRX1F", - i_SCLK=sclk, - i_RST=Const(0), - i_D=d[bit], - o_Q0=q0[bit], o_Q1=q1[bit] - ) - - def get_iddrx2(sclk, eclk, d, q0, q1, q2, q3): - for bit in range(len(d)): - m.submodules += Instance("IDDRX2F", - i_SCLK=sclk, - i_ECLK=eclk, - i_RST=Const(0), - i_D=d[bit], - o_Q0=q0[bit], o_Q1=q1[bit], o_Q2=q2[bit], o_Q3=q3[bit] - ) - - def get_iddr71b(sclk, eclk, d, q0, q1, q2, q3, q4, q5, q6): - for bit in range(len(d)): - m.submodules += Instance("IDDR71B", - i_SCLK=sclk, - i_ECLK=eclk, - i_RST=Const(0), - i_D=d[bit], - o_Q0=q0[bit], o_Q1=q1[bit], o_Q2=q2[bit], o_Q3=q3[bit], - o_Q4=q4[bit], o_Q5=q5[bit], o_Q6=q6[bit], - ) - - def get_oddr(sclk, d0, d1, q): - for bit in range(len(q)): - m.submodules += Instance("ODDRX1F", - i_SCLK=sclk, - i_RST=Const(0), - i_D0=d0[bit], i_D1=d1[bit], - o_Q=q[bit] - ) - - def get_oddrx2(sclk, eclk, d0, d1, d2, d3, q): - for bit in range(len(q)): - m.submodules += Instance("ODDRX2F", - i_SCLK=sclk, - i_ECLK=eclk, - i_RST=Const(0), - i_D0=d0[bit], i_D1=d1[bit], i_D2=d2[bit], i_D3=d3[bit], - o_Q=q[bit] - ) - - def get_oddr71b(sclk, eclk, d0, d1, d2, d3, d4, d5, d6, q): - for bit in range(len(q)): - m.submodules += Instance("ODDR71B", - i_SCLK=sclk, - i_ECLK=eclk, - i_RST=Const(0), - i_D0=d0[bit], i_D1=d1[bit], i_D2=d2[bit], i_D3=d3[bit], - i_D4=d4[bit], i_D5=d5[bit], i_D6=d6[bit], - o_Q=q[bit] - ) - - def get_ineg(z, invert): - if invert: - a = Signal.like(z, name_suffix="_n") - m.d.comb += z.eq(~a) - return a - else: - return z - - def get_oneg(a, invert): - if invert: - z = Signal.like(a, name_suffix="_n") - m.d.comb += z.eq(~a) - return z - 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) - elif pin.xdr == 4: - pin_i0 = get_ineg(pin.i0, i_invert) - pin_i1 = get_ineg(pin.i1, i_invert) - pin_i2 = get_ineg(pin.i2, i_invert) - pin_i3 = get_ineg(pin.i3, i_invert) - elif pin.xdr == 7: - pin_i0 = get_ineg(pin.i0, i_invert) - pin_i1 = get_ineg(pin.i1, i_invert) - pin_i2 = get_ineg(pin.i2, i_invert) - pin_i3 = get_ineg(pin.i3, i_invert) - pin_i4 = get_ineg(pin.i4, i_invert) - pin_i5 = get_ineg(pin.i5, i_invert) - pin_i6 = get_ineg(pin.i6, 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) - elif pin.xdr == 4: - pin_o0 = get_oneg(pin.o0, o_invert) - pin_o1 = get_oneg(pin.o1, o_invert) - pin_o2 = get_oneg(pin.o2, o_invert) - pin_o3 = get_oneg(pin.o3, o_invert) - elif pin.xdr == 7: - pin_o0 = get_oneg(pin.o0, o_invert) - pin_o1 = get_oneg(pin.o1, o_invert) - pin_o2 = get_oneg(pin.o2, o_invert) - pin_o3 = get_oneg(pin.o3, o_invert) - pin_o4 = get_oneg(pin.o4, o_invert) - pin_o5 = get_oneg(pin.o5, o_invert) - pin_o6 = get_oneg(pin.o6, 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(pin.width, 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).replicate(pin.width) - elif pin.xdr == 1: - if "i" in pin.dir: - get_ireg(pin.i_clk, i, pin_i) - if "o" in pin.dir: - get_oreg(pin.o_clk, pin_o, o) - if pin.dir in ("oe", "io"): - get_oereg(pin.o_clk, ~pin.oe, t) - elif pin.xdr == 2: - if "i" in pin.dir: - get_iddr(pin.i_clk, i, pin_i0, pin_i1) - if "o" in pin.dir: - get_oddr(pin.o_clk, pin_o0, pin_o1, o) - if pin.dir in ("oe", "io"): - get_oereg(pin.o_clk, ~pin.oe, t) - elif pin.xdr == 4: - if "i" in pin.dir: - get_iddrx2(pin.i_clk, pin.i_fclk, i, pin_i0, pin_i1, pin_i2, pin_i3) - if "o" in pin.dir: - get_oddrx2(pin.o_clk, pin.o_fclk, pin_o0, pin_o1, pin_o2, pin_o3, o) - if pin.dir in ("oe", "io"): - get_oereg(pin.o_clk, ~pin.oe, t) - elif pin.xdr == 7: - if "i" in pin.dir: - get_iddr71b(pin.i_clk, pin.i_fclk, i, pin_i0, pin_i1, pin_i2, pin_i3, pin_i4, pin_i5, pin_i6) - if "o" in pin.dir: - get_oddr71b(pin.o_clk, pin.o_fclk, pin_o0, pin_o1, pin_o2, pin_o3, pin_o4, pin_o5, pin_o6, o) - if pin.dir in ("oe", "io"): - get_oereg(pin.o_clk, ~pin.oe, t) + 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): + result = DDRBuffer(buffer.direction, buffer.port) else: - assert False - - return (i, o, t) - - def get_input(self, pin, port, attrs, invert): - self._check_feature("single-ended input", pin, attrs, - valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert) - for bit in range(pin.width): - m.submodules[f"{pin.name}_{bit}"] = Instance("IB", - 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=(0, 1, 2, 4, 7), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert) - for bit in range(pin.width): - m.submodules[f"{pin.name}_{bit}"] = Instance("OB", - i_I=o[bit], - o_O=port.io[bit] - ) - return m - - def get_tristate(self, pin, port, attrs, invert): - self._check_feature("single-ended tristate", pin, attrs, - valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert) - for bit in range(pin.width): - m.submodules[f"{pin.name}_{bit}"] = Instance("OBZ", - i_T=t[bit], - i_I=o[bit], - o_O=port.io[bit] - ) - return m - - def get_input_output(self, pin, port, attrs, invert): - self._check_feature("single-ended input/output", pin, attrs, - valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert) - for bit in range(pin.width): - m.submodules[f"{pin.name}_{bit}"] = Instance("BB", - i_T=t[bit], - i_I=o[bit], - o_O=i[bit], - io_B=port.io[bit] - ) - return m - - def get_diff_input(self, pin, port, attrs, invert): - self._check_feature("differential input", pin, attrs, - valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert) - for bit in range(pin.width): - m.submodules[f"{pin.name}_{bit}"] = Instance("IB", - i_I=port.p[bit], - o_O=i[bit] - ) - return m - - def get_diff_output(self, pin, port, attrs, invert): - self._check_feature("differential output", pin, attrs, - valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert) - for bit in range(pin.width): - m.submodules[f"{pin.name}_{bit}"] = Instance("OB", - i_I=o[bit], - o_O=port.p[bit], - ) - return m - - def get_diff_tristate(self, pin, port, attrs, invert): - self._check_feature("differential tristate", pin, attrs, - valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert) - for bit in range(pin.width): - m.submodules[f"{pin.name}_{bit}"] = Instance("OBZ", - i_T=t[bit], - i_I=o[bit], - o_O=port.p[bit], - ) - return m - - def get_diff_input_output(self, pin, port, attrs, invert): - self._check_feature("differential input/output", pin, attrs, - valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert) - for bit in range(pin.width): - m.submodules[f"{pin.name}_{bit}"] = Instance("BB", - i_T=t[bit], - i_I=o[bit], - o_O=i[bit], - io_B=port.p[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 # CDC primitives are not currently specialized for ECP5. # While Diamond supports false path constraints; nextpnr-ecp5 does not.