From 6857daff54bfa208b26a88a822a697105026902c Mon Sep 17 00:00:00 2001 From: Wanda Date: Thu, 4 Apr 2024 22:07:09 +0200 Subject: [PATCH] vendor._lattice_ice40: implement `lib.io` buffer primitives. --- amaranth/vendor/_lattice_ice40.py | 277 +++++++++++++----------------- 1 file changed, 117 insertions(+), 160 deletions(-) diff --git a/amaranth/vendor/_lattice_ice40.py b/amaranth/vendor/_lattice_ice40.py index 2d48777..abd4793 100644 --- a/amaranth/vendor/_lattice_ice40.py +++ b/amaranth/vendor/_lattice_ice40.py @@ -2,6 +2,7 @@ from abc import abstractmethod from ..hdl import * from ..lib.cdc import ResetSynchronizer +from ..lib import io from ..build import * @@ -414,206 +415,162 @@ class LatticeICE40Platform(TemplatedPlatform): return m - def should_skip_port_component(self, port, attrs, component): - # On iCE40, a differential input is placed by only instantiating an SB_IO primitive for - # the pin with z=0, which is the non-inverting pin. The pinout unfortunately differs - # between LP/HX and UP series: - # * for LP/HX, z=0 is DPxxB (B is non-inverting, A is inverting) - # * for UP, z=0 is IOB_xxA (A is non-inverting, B is inverting) - if attrs.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT" and component == "n": - return True - return False - - def _get_io_buffer(self, m, pin, port, attrs, *, i_invert=False, o_invert=False, - invert_lut=False): - def get_dff(clk, d, q): + def _get_io_buffer_single(self, buffer, port, *, invert_lut=False): + def get_dff(domain, q, d): for bit in range(len(d)): m.submodules += Instance("SB_DFF", - i_C=clk, + i_C=ClockSignal(domain), i_D=d[bit], o_Q=q[bit]) - def get_ineg(y, invert): + def get_inv(y, a): if invert_lut: - a = Signal.like(y, name_suffix=f"_x{1 if invert else 0}") - for bit in range(len(y)): + for bit, inv in enumerate(port.invert): m.submodules += Instance("SB_LUT4", - p_LUT_INIT=Const(0b01 if invert else 0b10, 16), + p_LUT_INIT=Const(0b01 if inv else 0b10, 16), i_I0=a[bit], i_I1=Const(0), i_I2=Const(0), i_I3=Const(0), o_O=y[bit]) - return a - elif invert: - a = Signal.like(y, name_suffix="_n") - m.d.comb += y.eq(~a) - return a else: - return y + mask = sum(int(inv) << bit for bit, inv in enumerate(port.invert)) + if mask == 0: + m.d.comb += y.eq(a) + elif mask == ((1 << len(port)) - 1): + m.d.comb += y.eq(~a) + else: + m.d.comb += y.eq(a ^ mask) - def get_oneg(a, invert): - if invert_lut: - y = Signal.like(a, name_suffix=f"_x{1 if invert else 0}") - for bit in range(len(a)): - m.submodules += Instance("SB_LUT4", - p_LUT_INIT=Const(0b01 if invert else 0b10, 16), - i_I0=a[bit], - i_I1=Const(0), - i_I2=Const(0), - i_I3=Const(0), - o_O=y[bit]) - return y - elif invert: - y = Signal.like(a, name_suffix="_n") - m.d.comb += y.eq(~a) - return y - else: - return a + m = Module() - if "GLOBAL" in attrs: - is_global_input = bool(attrs["GLOBAL"]) - del attrs["GLOBAL"] + if isinstance(buffer, io.DDRBuffer): + if buffer.direction is not io.Direction.Output: + # Re-register both inputs before they enter fabric. This increases hold time + # to an entire cycle, and adds one cycle of latency. + i0 = Signal(len(port)) + i1 = Signal(len(port)) + i0_neg = Signal(len(port)) + i1_neg = Signal(len(port)) + get_inv(i0_neg, i0) + get_inv(i1_neg, i1) + get_dff(buffer.i_domain, buffer.i[0], i0_neg) + get_dff(buffer.i_domain, buffer.i[1], i1_neg) + if buffer.direction is not io.Direction.Input: + # Re-register negedge output after it leaves fabric. This increases setup time + # to an entire cycle, and doesn't add latency. + o0 = Signal(len(port)) + o1 = Signal(len(port)) + o1_ff = Signal(len(port)) + get_dff(buffer.o_domain, o1_ff, buffer.o[1]) + get_inv(o0, buffer.o[0]) + get_inv(o1, o1_ff) else: - is_global_input = False - assert not (is_global_input and i_invert) - - 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) - - if "i" in pin.dir and pin.xdr == 2: - 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) - if "o" in pin.dir and pin.xdr == 2: - o1_ff = Signal.like(pin_o1, name_suffix="_ff") - get_dff(pin.o_clk, pin_o1, o1_ff) + if buffer.direction is not io.Direction.Output: + i = Signal(len(port)) + get_inv(buffer.i, i) + if buffer.direction is not io.Direction.Input: + o = Signal(len(port)) + get_inv(o, buffer.o) for bit in range(len(port)): + attrs = port.io.metadata[bit].attrs + + is_global_input = bool(attrs.get("GLOBAL", False)) + if buffer.direction is io.Direction.Output: + is_global_input = False + if is_global_input: + if port.invert[bit]: + raise ValueError("iCE40 global input buffer doesn't support inversion") + if not isinstance(buffer, io.Buffer): + raise ValueError("iCE40 global input buffer cannot be registered") + io_args = [ - ("io", "PACKAGE_PIN", port[bit]), - *(("p", key, value) for key, value in attrs.items()), + ("io", "PACKAGE_PIN", port.io[bit]), + *(("p", key, value) for key, value in attrs.items() if key != "GLOBAL"), ] - if "i" not in pin.dir: + if buffer.direction is io.Direction.Output: # If no input pin is requested, it is important to use a non-registered input pin # type, because an output-only pin would not have an input clock, and if its input # is configured as registered, this would prevent a co-located input-capable pin # from using an input clock. i_type = 0b01 # PIN_INPUT - elif pin.xdr == 0: + elif isinstance(buffer, io.Buffer): i_type = 0b01 # PIN_INPUT - elif pin.xdr > 0: + if is_global_input: + io_args.append(("o", "GLOBAL_BUFFER_OUTPUT", i[bit])) + else: + io_args.append(("o", "D_IN_0", i[bit])) + elif isinstance(buffer, io.FFBuffer): i_type = 0b00 # PIN_INPUT_REGISTERED aka PIN_INPUT_DDR - if "o" not in pin.dir: + io_args.append(("i", "INPUT_CLK", ClockSignal(buffer.i_domain))) + io_args.append(("o", "D_IN_0", i[bit])) + elif isinstance(buffer, io.DDRBuffer): + i_type = 0b00 # PIN_INPUT_REGISTERED aka PIN_INPUT_DDR + io_args.append(("i", "INPUT_CLK", ClockSignal(buffer.i_domain))) + io_args.append(("o", "D_IN_0", i0[bit])) + io_args.append(("o", "D_IN_1", i1[bit])) + + if buffer.direction is io.Direction.Input: o_type = 0b0000 # PIN_NO_OUTPUT - elif pin.xdr == 0 and pin.dir == "o": - o_type = 0b0110 # PIN_OUTPUT - elif pin.xdr == 0: + elif isinstance(buffer, io.Buffer): o_type = 0b1010 # PIN_OUTPUT_TRISTATE - elif pin.xdr == 1 and pin.dir == "o": - o_type = 0b0101 # PIN_OUTPUT_REGISTERED - elif pin.xdr == 1: + io_args.append(("i", "D_OUT_0", o[bit])) + elif isinstance(buffer, io.FFBuffer): o_type = 0b1101 # PIN_OUTPUT_REGISTERED_ENABLE_REGISTERED - elif pin.xdr == 2 and pin.dir == "o": - o_type = 0b0100 # PIN_OUTPUT_DDR - elif pin.xdr == 2: + io_args.append(("i", "OUTPUT_CLK", ClockSignal(buffer.o_domain))) + io_args.append(("i", "D_OUT_0", o[bit])) + elif isinstance(buffer, io.DDRBuffer): o_type = 0b1100 # PIN_OUTPUT_DDR_ENABLE_REGISTERED + io_args.append(("i", "OUTPUT_CLK", ClockSignal(buffer.o_domain))) + io_args.append(("i", "D_OUT_0", o0[bit])) + io_args.append(("i", "D_OUT_1", o1[bit])) + io_args.append(("p", "PIN_TYPE", C((o_type << 2) | i_type, 6))) - if hasattr(pin, "i_clk"): - io_args.append(("i", "INPUT_CLK", pin.i_clk)) - if hasattr(pin, "o_clk"): - io_args.append(("i", "OUTPUT_CLK", pin.o_clk)) - - if "i" in pin.dir: - if pin.xdr == 0 and is_global_input: - io_args.append(("o", "GLOBAL_BUFFER_OUTPUT", pin.i[bit])) - elif pin.xdr < 2: - io_args.append(("o", "D_IN_0", pin_i[bit])) - elif pin.xdr == 2: - # Re-register both inputs before they enter fabric. This increases hold time - # to an entire cycle, and adds one cycle of latency. - io_args.append(("o", "D_IN_0", i0_ff[bit])) - io_args.append(("o", "D_IN_1", i1_ff[bit])) - if "o" in pin.dir: - if pin.xdr < 2: - io_args.append(("i", "D_OUT_0", pin_o[bit])) - elif pin.xdr == 2: - # Re-register negedge output after it leaves fabric. This increases setup time - # to an entire cycle, and doesn't add latency. - io_args.append(("i", "D_OUT_0", pin_o0[bit])) - io_args.append(("i", "D_OUT_1", o1_ff[bit])) - - if pin.dir in ("oe", "io"): - io_args.append(("i", "OUTPUT_ENABLE", pin.oe)) + if buffer.direction is not io.Direction.Input: + io_args.append(("i", "OUTPUT_ENABLE", buffer.oe)) if is_global_input: - m.submodules[f"{pin.name}_{bit}"] = Instance("SB_GB_IO", *io_args) + m.submodules[f"buf{bit}"] = Instance("SB_GB_IO", *io_args) else: - m.submodules[f"{pin.name}_{bit}"] = Instance("SB_IO", *io_args) + m.submodules[f"buf{bit}"] = Instance("SB_IO", *io_args) - def get_input(self, pin, port, attrs, invert): - self._check_feature("single-ended input", pin, attrs, - valid_xdrs=(0, 1, 2), valid_attrs=True) - m = Module() - self._get_io_buffer(m, pin, port.io, attrs, i_invert=invert) return m - def get_output(self, pin, port, attrs, invert): - self._check_feature("single-ended output", pin, attrs, - valid_xdrs=(0, 1, 2), valid_attrs=True) - m = Module() - self._get_io_buffer(m, pin, port.io, attrs, o_invert=invert) - return m - - def get_tristate(self, pin, port, attrs, invert): - self._check_feature("single-ended tristate", pin, attrs, - valid_xdrs=(0, 1, 2), valid_attrs=True) - m = Module() - self._get_io_buffer(m, pin, port.io, attrs, o_invert=invert) - 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), valid_attrs=True) - m = Module() - self._get_io_buffer(m, pin, port.io, attrs, i_invert=invert, o_invert=invert) - return m - - def get_diff_input(self, pin, port, attrs, invert): - self._check_feature("differential input", pin, attrs, - valid_xdrs=(0, 1, 2), valid_attrs=True) - m = Module() - # See comment in should_skip_port_component above. - self._get_io_buffer(m, pin, port.p, attrs, i_invert=invert) - return m - - def get_diff_output(self, pin, port, attrs, invert): - self._check_feature("differential output", pin, attrs, - valid_xdrs=(0, 1, 2), valid_attrs=True) - m = Module() - # Note that the non-inverting output pin is not driven the same way as a regular - # output pin. The inverter introduces a delay, so for a non-inverting output pin, - # an identical delay is introduced by instantiating a LUT. This makes the waveform - # perfectly symmetric in the xdr=0 case. - self._get_io_buffer(m, pin, port.p, attrs, o_invert= invert, invert_lut=True) - self._get_io_buffer(m, pin, port.n, attrs, o_invert=not invert, invert_lut=True) - return m - - # Tristate bidirectional buffers are not supported on iCE40 because it requires external - # termination, which is different for differential pins configured as inputs and outputs. + def get_io_buffer(self, buffer): + if not isinstance(buffer, (io.Buffer, io.FFBuffer, io.DDRBuffer)): + raise TypeError(f"Unknown IO buffer type {buffer!r}") + if isinstance(buffer.port, io.DifferentialPort): + port_p = io.SingleEndedPort(buffer.port.p, invert=buffer.port.invert, + direction=buffer.port.direction) + port_n = ~io.SingleEndedPort(buffer.port.n, invert=buffer.port.invert, + direction=buffer.port.direction) + if buffer.direction is io.Direction.Bidir: + # Tristate bidirectional buffers are not supported on iCE40 because it requires + # external termination, which is different for differential pins configured + # as inputs and outputs. + raise TypeError("iCE40 does not support bidirectional differential ports") + elif buffer.direction is io.Direction.Output: + m = Module() + invert_lut = isinstance(buffer, io.Buffer) + m.submodules.p = self._get_io_buffer_single(buffer, port_p, invert_lut=invert_lut) + m.submodules.n = self._get_io_buffer_single(buffer, port_n, invert_lut=invert_lut) + return m + elif buffer.direction is io.Direction.Input: + # On iCE40, a differential input is placed by only instantiating an SB_IO primitive + # for the pin with z=0, which is the non-inverting pin. The pinout unfortunately + # differs between LP/HX and UP series: + # * for LP/HX, z=0 is DPxxB (B is non-inverting, A is inverting) + # * for UP, z=0 is IOB_xxA (A is non-inverting, B is inverting) + return self._get_io_buffer_single(buffer, port_p, invert_lut=invert_lut) + else: + assert False # :nocov: + elif isinstance(buffer.port, io.SingleEndedPort): + return self._get_io_buffer_single(buffer, buffer.port) + else: + raise TypeError(f"Unknown port type {buffer.port!r}") # CDC primitives are not currently specialized for iCE40. It is not known if iCECube2 supports # the necessary attributes; nextpnr-ice40 does not.