Skip to content

Commit a43f050

Browse files
committed
vendor._lattice_ice40: implement lib.io buffer primitives.
1 parent 4e3550d commit a43f050

File tree

1 file changed

+117
-160
lines changed

1 file changed

+117
-160
lines changed

amaranth/vendor/_lattice_ice40.py

Lines changed: 117 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from ..hdl import *
44
from ..lib.cdc import ResetSynchronizer
5+
from ..lib import io
56
from ..build import *
67

78

@@ -414,206 +415,162 @@ def create_missing_domain(self, name):
414415

415416
return m
416417

417-
def should_skip_port_component(self, port, attrs, component):
418-
# On iCE40, a differential input is placed by only instantiating an SB_IO primitive for
419-
# the pin with z=0, which is the non-inverting pin. The pinout unfortunately differs
420-
# between LP/HX and UP series:
421-
# * for LP/HX, z=0 is DPxxB (B is non-inverting, A is inverting)
422-
# * for UP, z=0 is IOB_xxA (A is non-inverting, B is inverting)
423-
if attrs.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT" and component == "n":
424-
return True
425-
return False
426-
427-
def _get_io_buffer(self, m, pin, port, attrs, *, i_invert=False, o_invert=False,
428-
invert_lut=False):
429-
def get_dff(clk, d, q):
418+
def _get_io_buffer_single(self, buffer, port, *, invert_lut=False):
419+
def get_dff(domain, q, d):
430420
for bit in range(len(d)):
431421
m.submodules += Instance("SB_DFF",
432-
i_C=clk,
422+
i_C=ClockSignal(domain),
433423
i_D=d[bit],
434424
o_Q=q[bit])
435425

436-
def get_ineg(y, invert):
426+
def get_inv(y, a):
437427
if invert_lut:
438-
a = Signal.like(y, name_suffix=f"_x{1 if invert else 0}")
439-
for bit in range(len(y)):
428+
for bit, inv in enumerate(port.invert):
440429
m.submodules += Instance("SB_LUT4",
441-
p_LUT_INIT=Const(0b01 if invert else 0b10, 16),
430+
p_LUT_INIT=Const(0b01 if inv else 0b10, 16),
442431
i_I0=a[bit],
443432
i_I1=Const(0),
444433
i_I2=Const(0),
445434
i_I3=Const(0),
446435
o_O=y[bit])
447-
return a
448-
elif invert:
449-
a = Signal.like(y, name_suffix="_n")
450-
m.d.comb += y.eq(~a)
451-
return a
452436
else:
453-
return y
437+
mask = sum(int(inv) << bit for bit, inv in enumerate(port.invert))
438+
if mask == 0:
439+
m.d.comb += y.eq(a)
440+
elif mask == ((1 << len(port)) - 1):
441+
m.d.comb += y.eq(~a)
442+
else:
443+
m.d.comb += y.eq(a ^ mask)
454444

455-
def get_oneg(a, invert):
456-
if invert_lut:
457-
y = Signal.like(a, name_suffix=f"_x{1 if invert else 0}")
458-
for bit in range(len(a)):
459-
m.submodules += Instance("SB_LUT4",
460-
p_LUT_INIT=Const(0b01 if invert else 0b10, 16),
461-
i_I0=a[bit],
462-
i_I1=Const(0),
463-
i_I2=Const(0),
464-
i_I3=Const(0),
465-
o_O=y[bit])
466-
return y
467-
elif invert:
468-
y = Signal.like(a, name_suffix="_n")
469-
m.d.comb += y.eq(~a)
470-
return y
471-
else:
472-
return a
445+
m = Module()
473446

474-
if "GLOBAL" in attrs:
475-
is_global_input = bool(attrs["GLOBAL"])
476-
del attrs["GLOBAL"]
447+
if isinstance(buffer, io.DDRBuffer):
448+
if buffer.direction is not io.Direction.Output:
449+
# Re-register both inputs before they enter fabric. This increases hold time
450+
# to an entire cycle, and adds one cycle of latency.
451+
i0 = Signal(len(port))
452+
i1 = Signal(len(port))
453+
i0_neg = Signal(len(port))
454+
i1_neg = Signal(len(port))
455+
get_inv(i0_neg, i0)
456+
get_inv(i1_neg, i1)
457+
get_dff(buffer.i_domain, buffer.i[0], i0_neg)
458+
get_dff(buffer.i_domain, buffer.i[1], i1_neg)
459+
if buffer.direction is not io.Direction.Input:
460+
# Re-register negedge output after it leaves fabric. This increases setup time
461+
# to an entire cycle, and doesn't add latency.
462+
o0 = Signal(len(port))
463+
o1 = Signal(len(port))
464+
o1_ff = Signal(len(port))
465+
get_dff(buffer.o_domain, o1_ff, buffer.o[1])
466+
get_inv(o0, buffer.o[0])
467+
get_inv(o1, o1_ff)
477468
else:
478-
is_global_input = False
479-
assert not (is_global_input and i_invert)
480-
481-
if "i" in pin.dir:
482-
if pin.xdr < 2:
483-
pin_i = get_ineg(pin.i, i_invert)
484-
elif pin.xdr == 2:
485-
pin_i0 = get_ineg(pin.i0, i_invert)
486-
pin_i1 = get_ineg(pin.i1, i_invert)
487-
if "o" in pin.dir:
488-
if pin.xdr < 2:
489-
pin_o = get_oneg(pin.o, o_invert)
490-
elif pin.xdr == 2:
491-
pin_o0 = get_oneg(pin.o0, o_invert)
492-
pin_o1 = get_oneg(pin.o1, o_invert)
493-
494-
if "i" in pin.dir and pin.xdr == 2:
495-
i0_ff = Signal.like(pin_i0, name_suffix="_ff")
496-
i1_ff = Signal.like(pin_i1, name_suffix="_ff")
497-
get_dff(pin.i_clk, i0_ff, pin_i0)
498-
get_dff(pin.i_clk, i1_ff, pin_i1)
499-
if "o" in pin.dir and pin.xdr == 2:
500-
o1_ff = Signal.like(pin_o1, name_suffix="_ff")
501-
get_dff(pin.o_clk, pin_o1, o1_ff)
469+
if buffer.direction is not io.Direction.Output:
470+
i = Signal(len(port))
471+
get_inv(buffer.i, i)
472+
if buffer.direction is not io.Direction.Input:
473+
o = Signal(len(port))
474+
get_inv(o, buffer.o)
502475

503476
for bit in range(len(port)):
477+
attrs = port.io.metadata[bit].attrs
478+
479+
is_global_input = bool(attrs.get("GLOBAL", False))
480+
if buffer.direction is io.Direction.Output:
481+
is_global_input = False
482+
if is_global_input:
483+
if port.invert[bit]:
484+
raise ValueError("iCE40 global input buffer doesn't support inversion")
485+
if not isinstance(buffer, io.Buffer):
486+
raise ValueError("iCE40 global input buffer cannot be registered")
487+
504488
io_args = [
505-
("io", "PACKAGE_PIN", port[bit]),
506-
*(("p", key, value) for key, value in attrs.items()),
489+
("io", "PACKAGE_PIN", port.io[bit]),
490+
*(("p", key, value) for key, value in attrs.items() if key != "GLOBAL"),
507491
]
508492

509-
if "i" not in pin.dir:
493+
if buffer.direction is io.Direction.Output:
510494
# If no input pin is requested, it is important to use a non-registered input pin
511495
# type, because an output-only pin would not have an input clock, and if its input
512496
# is configured as registered, this would prevent a co-located input-capable pin
513497
# from using an input clock.
514498
i_type = 0b01 # PIN_INPUT
515-
elif pin.xdr == 0:
499+
elif isinstance(buffer, io.Buffer):
516500
i_type = 0b01 # PIN_INPUT
517-
elif pin.xdr > 0:
501+
if is_global_input:
502+
io_args.append(("o", "GLOBAL_BUFFER_OUTPUT", i[bit]))
503+
else:
504+
io_args.append(("o", "D_IN_0", i[bit]))
505+
elif isinstance(buffer, io.FFBuffer):
506+
i_type = 0b00 # PIN_INPUT_REGISTERED aka PIN_INPUT_DDR
507+
io_args.append(("i", "INPUT_CLK", ClockSignal(buffer.i_domain)))
508+
io_args.append(("o", "D_IN_0", i[bit]))
509+
elif isinstance(buffer, io.DDRBuffer):
518510
i_type = 0b00 # PIN_INPUT_REGISTERED aka PIN_INPUT_DDR
519-
if "o" not in pin.dir:
511+
io_args.append(("i", "INPUT_CLK", ClockSignal(buffer.i_domain)))
512+
io_args.append(("o", "D_IN_0", i0[bit]))
513+
io_args.append(("o", "D_IN_1", i1[bit]))
514+
515+
if buffer.direction is io.Direction.Input:
520516
o_type = 0b0000 # PIN_NO_OUTPUT
521-
elif pin.xdr == 0 and pin.dir == "o":
522-
o_type = 0b0110 # PIN_OUTPUT
523-
elif pin.xdr == 0:
517+
elif isinstance(buffer, io.Buffer):
524518
o_type = 0b1010 # PIN_OUTPUT_TRISTATE
525-
elif pin.xdr == 1 and pin.dir == "o":
526-
o_type = 0b0101 # PIN_OUTPUT_REGISTERED
527-
elif pin.xdr == 1:
519+
io_args.append(("i", "D_OUT_0", o[bit]))
520+
elif isinstance(buffer, io.FFBuffer):
528521
o_type = 0b1101 # PIN_OUTPUT_REGISTERED_ENABLE_REGISTERED
529-
elif pin.xdr == 2 and pin.dir == "o":
530-
o_type = 0b0100 # PIN_OUTPUT_DDR
531-
elif pin.xdr == 2:
522+
io_args.append(("i", "OUTPUT_CLK", ClockSignal(buffer.o_domain)))
523+
io_args.append(("i", "D_OUT_0", o[bit]))
524+
elif isinstance(buffer, io.DDRBuffer):
532525
o_type = 0b1100 # PIN_OUTPUT_DDR_ENABLE_REGISTERED
526+
io_args.append(("i", "OUTPUT_CLK", ClockSignal(buffer.o_domain)))
527+
io_args.append(("i", "D_OUT_0", o0[bit]))
528+
io_args.append(("i", "D_OUT_1", o1[bit]))
529+
533530
io_args.append(("p", "PIN_TYPE", C((o_type << 2) | i_type, 6)))
534531

535-
if hasattr(pin, "i_clk"):
536-
io_args.append(("i", "INPUT_CLK", pin.i_clk))
537-
if hasattr(pin, "o_clk"):
538-
io_args.append(("i", "OUTPUT_CLK", pin.o_clk))
539-
540-
if "i" in pin.dir:
541-
if pin.xdr == 0 and is_global_input:
542-
io_args.append(("o", "GLOBAL_BUFFER_OUTPUT", pin.i[bit]))
543-
elif pin.xdr < 2:
544-
io_args.append(("o", "D_IN_0", pin_i[bit]))
545-
elif pin.xdr == 2:
546-
# Re-register both inputs before they enter fabric. This increases hold time
547-
# to an entire cycle, and adds one cycle of latency.
548-
io_args.append(("o", "D_IN_0", i0_ff[bit]))
549-
io_args.append(("o", "D_IN_1", i1_ff[bit]))
550-
if "o" in pin.dir:
551-
if pin.xdr < 2:
552-
io_args.append(("i", "D_OUT_0", pin_o[bit]))
553-
elif pin.xdr == 2:
554-
# Re-register negedge output after it leaves fabric. This increases setup time
555-
# to an entire cycle, and doesn't add latency.
556-
io_args.append(("i", "D_OUT_0", pin_o0[bit]))
557-
io_args.append(("i", "D_OUT_1", o1_ff[bit]))
558-
559-
if pin.dir in ("oe", "io"):
560-
io_args.append(("i", "OUTPUT_ENABLE", pin.oe))
532+
if buffer.direction is not io.Direction.Input:
533+
io_args.append(("i", "OUTPUT_ENABLE", buffer.oe))
561534

562535
if is_global_input:
563-
m.submodules[f"{pin.name}_{bit}"] = Instance("SB_GB_IO", *io_args)
536+
m.submodules[f"buf{bit}"] = Instance("SB_GB_IO", *io_args)
564537
else:
565-
m.submodules[f"{pin.name}_{bit}"] = Instance("SB_IO", *io_args)
538+
m.submodules[f"buf{bit}"] = Instance("SB_IO", *io_args)
566539

567-
def get_input(self, pin, port, attrs, invert):
568-
self._check_feature("single-ended input", pin, attrs,
569-
valid_xdrs=(0, 1, 2), valid_attrs=True)
570-
m = Module()
571-
self._get_io_buffer(m, pin, port.io, attrs, i_invert=invert)
572540
return m
573541

574-
def get_output(self, pin, port, attrs, invert):
575-
self._check_feature("single-ended output", pin, attrs,
576-
valid_xdrs=(0, 1, 2), valid_attrs=True)
577-
m = Module()
578-
self._get_io_buffer(m, pin, port.io, attrs, o_invert=invert)
579-
return m
580-
581-
def get_tristate(self, pin, port, attrs, invert):
582-
self._check_feature("single-ended tristate", pin, attrs,
583-
valid_xdrs=(0, 1, 2), valid_attrs=True)
584-
m = Module()
585-
self._get_io_buffer(m, pin, port.io, attrs, o_invert=invert)
586-
return m
587-
588-
def get_input_output(self, pin, port, attrs, invert):
589-
self._check_feature("single-ended input/output", pin, attrs,
590-
valid_xdrs=(0, 1, 2), valid_attrs=True)
591-
m = Module()
592-
self._get_io_buffer(m, pin, port.io, attrs, i_invert=invert, o_invert=invert)
593-
return m
594-
595-
def get_diff_input(self, pin, port, attrs, invert):
596-
self._check_feature("differential input", pin, attrs,
597-
valid_xdrs=(0, 1, 2), valid_attrs=True)
598-
m = Module()
599-
# See comment in should_skip_port_component above.
600-
self._get_io_buffer(m, pin, port.p, attrs, i_invert=invert)
601-
return m
602-
603-
def get_diff_output(self, pin, port, attrs, invert):
604-
self._check_feature("differential output", pin, attrs,
605-
valid_xdrs=(0, 1, 2), valid_attrs=True)
606-
m = Module()
607-
# Note that the non-inverting output pin is not driven the same way as a regular
608-
# output pin. The inverter introduces a delay, so for a non-inverting output pin,
609-
# an identical delay is introduced by instantiating a LUT. This makes the waveform
610-
# perfectly symmetric in the xdr=0 case.
611-
self._get_io_buffer(m, pin, port.p, attrs, o_invert= invert, invert_lut=True)
612-
self._get_io_buffer(m, pin, port.n, attrs, o_invert=not invert, invert_lut=True)
613-
return m
614-
615-
# Tristate bidirectional buffers are not supported on iCE40 because it requires external
616-
# termination, which is different for differential pins configured as inputs and outputs.
542+
def get_io_buffer(self, buffer):
543+
if not isinstance(buffer, (io.Buffer, io.FFBuffer, io.DDRBuffer)):
544+
raise TypeError(f"Unknown IO buffer type {buffer!r}")
545+
if isinstance(buffer.port, io.DifferentialPort):
546+
port_p = io.SingleEndedPort(buffer.port.p, invert=buffer.port.invert,
547+
direction=buffer.port.direction)
548+
port_n = ~io.SingleEndedPort(buffer.port.n, invert=buffer.port.invert,
549+
direction=buffer.port.direction)
550+
if buffer.direction is io.Direction.Bidir:
551+
# Tristate bidirectional buffers are not supported on iCE40 because it requires
552+
# external termination, which is different for differential pins configured
553+
# as inputs and outputs.
554+
raise TypeError("iCE40 does not support bidirectional differential ports")
555+
elif buffer.direction is io.Direction.Output:
556+
m = Module()
557+
invert_lut = isinstance(buffer, io.Buffer)
558+
m.submodules.p = self._get_io_buffer_single(buffer, port_p, invert_lut=invert_lut)
559+
m.submodules.n = self._get_io_buffer_single(buffer, port_n, invert_lut=invert_lut)
560+
return m
561+
elif buffer.direction is io.Direction.Input:
562+
# On iCE40, a differential input is placed by only instantiating an SB_IO primitive
563+
# for the pin with z=0, which is the non-inverting pin. The pinout unfortunately
564+
# differs between LP/HX and UP series:
565+
# * for LP/HX, z=0 is DPxxB (B is non-inverting, A is inverting)
566+
# * for UP, z=0 is IOB_xxA (A is non-inverting, B is inverting)
567+
return self._get_io_buffer_single(buffer, port_p, invert_lut=invert_lut)
568+
else:
569+
assert False # :nocov:
570+
elif isinstance(buffer.port, io.SingleEndedPort):
571+
return self._get_io_buffer_single(buffer, buffer.port)
572+
else:
573+
raise TypeError(f"Unknown port type {buffer.port!r}")
617574

618575
# CDC primitives are not currently specialized for iCE40. It is not known if iCECube2 supports
619576
# the necessary attributes; nextpnr-ice40 does not.

0 commit comments

Comments
 (0)