vendor._lattice_ice40: implement lib.io
buffer primitives.
This commit is contained in:
parent
4e3550db43
commit
6857daff54
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue