from abc import abstractproperty from ..hdl import * from ..build import * __all__ = ["LatticeICE40Platform"] class LatticeICE40Platform(TemplatedPlatform): """ Required tools: * ``yosys`` * ``nextpnr-ice40`` * ``icepack`` Available overrides: * ``verbose``: enables logging of informational messages to standard error. * ``read_opts``: adds options for ``read`` Yosys command. * ``synth_opts``: adds options for ``synth_ice40`` Yosys command. * ``script_after_read``: inserts commands after ``read_ilang`` in Yosys script. * ``script_after_synth``: inserts commands after ``synth_ice40`` in Yosys script. * ``yosys_opts``: adds extra options for Yosys. * ``nextpnr_opts``: adds extra and overrides default options (``--placer heap``) for nextpnr. Build products: * ``{{name}}.rpt``: Yosys log. * ``{{name}}.json``: synthesized RTL. * ``{{name}}.tim``: nextpnr log. * ``{{name}}.asc``: ASCII bitstream. * ``{{name}}.bin``: binary bitstream. """ device = abstractproperty() package = abstractproperty() file_templates = { **TemplatedPlatform.build_script_templates, "{{name}}.il": r""" # {{autogenerated}} {{emit_design("rtlil")}} """, "{{name}}.ys": r""" # {{autogenerated}} {% for file in platform.extra_files %} {% if file.endswith(".v") -%} read_verilog {{get_override("read_opts")|join(" ")}} {{file}} {% elif file.endswith(".sv") -%} read_verilog -sv {{get_override("read_opts")|join(" ")}} {{file}} {% endif %} {% endfor %} read_ilang {{name}}.il {{get_override("script_after_read")|default("# (script_after_read placeholder)")}} synth_ice40 {{get_override("synth_opts")|join(" ")}} -top {{name}} {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}} write_json {{name}}.json """, "{{name}}.pcf": r""" # {{autogenerated}} {% for port_name, pin_name, extras in platform.iter_port_constraints_bits() -%} set_io {{port_name}} {{pin_name}} {% endfor %} """, "{{name}}_pre_pack.py": r""" # {{autogenerated}} {% for port, frequency in platform.iter_clock_constraints() -%} {# Clock in MHz #} ctx.addClock("{{port}}", {{frequency/1000000}}) {% endfor%} """, } command_templates = [ r""" {{get_tool("yosys")}} {{quiet("-q")}} {{get_override("yosys_opts")|join(" ")}} -l {{name}}.rpt {{name}}.ys """, r""" {{get_tool("nextpnr-ice40")}} {{quiet("-q")}} {{get_override("nextpnr_opts")|default(["--placer","heap"])|join(" ")}} -l {{name}}.tim --{{platform.device}} --package {{platform.package}} --json {{name}}.json --pcf {{name}}.pcf --pre-pack {{name}}_pre_pack.py --asc {{name}}.asc """, r""" {{get_tool("icepack")}} {{verbose("-v")}} {{name}}.asc {{name}}.bin """ ] def iter_ports(self): for res, pin, port in self._ports: if isinstance(res.io[0], Pins): yield port.io elif isinstance(res.io[0], DiffPairs): if res.extras.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT": yield port.p else: yield port.p yield port.n else: assert False def iter_port_constraints(self): for res, pin, port in self._ports: if isinstance(res.io[0], Pins): yield port.io.name, list(res.io[0].map_names(self._mapping, res)), res.extras elif isinstance(res.io[0], DiffPairs): if res.extras.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT": yield port.p.name, list(res.io[0].p.map_names(self._mapping, res)), res.extras else: yield port.p.name, list(res.io[0].p.map_names(self._mapping, res)), res.extras yield port.n.name, list(res.io[0].n.map_names(self._mapping, res)), res.extras else: assert False def _get_io_buffer(self, m, pin, port, extras, o_invert=None): def _get_dff(clk, d, q): m.submodules += Instance("$dff", p_CLK_POLARITY=0, p_WIDTH=len(d), i_CLK=clk, i_D=d, o_Q=q) def _get_inverter(a, invert): if invert is None: return a else: y = Signal.like(a, name="{}_x{}".format(a.name, 1 if invert else 0)) for bit in range(len(a)): m.submodules += Instance("SB_LUT4", p_LUT_INIT=0b01 if invert else 0b10, i_I0=a[bit], i_I1=Const(0), i_I2=Const(0), i_I3=Const(0), o_O=y[bit]) return y if "GLOBAL" in extras: is_global_input = bool(extras["GLOBAL"]) del extras["GLOBAL"] else: is_global_input = False if "o" in pin.dir: if pin.xdr < 2: pin_o = _get_inverter(pin.o, o_invert) elif pin.xdr == 2: pin_o0 = _get_inverter(pin.o0, o_invert) pin_o1 = _get_inverter(pin.o1, o_invert) if "i" in pin.dir and pin.xdr == 2: i0_ff = Signal.like(pin.i0, name="{}_ff".format(pin.i0.name)) i1_ff = Signal.like(pin.i1, name="{}_ff".format(pin.i1.name)) _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="{}_ff".format(pin.o1.name)) _get_dff(pin.o_clk, pin_o1, o1_ff) for bit in range(len(port)): io_args = [ ("io", "PACKAGE_PIN", port[bit]), *(("p", key, value) for key, value in extras.items()), ] if "i" not in pin.dir: i_type = 0b00 # PIN_NO_INPUT aka PIN_INPUT_REGISTERED elif pin.xdr == 0: i_type = 0b01 # PIN_INPUT elif pin.xdr > 0: i_type = 0b00 # PIN_INPUT_REGISTERED if "o" not in pin.dir: o_type = 0b0000 # PIN_NO_OUTPUT elif pin.xdr == 0 and pin.dir == "o": o_type = 0b0110 # PIN_OUTPUT elif pin.xdr == 0: o_type = 0b1010 # PIN_OUTPUT_TRISTATE elif pin.xdr == 1 and pin.dir == "o": o_type = 0b0101 # PIN_OUTPUT_REGISTERED elif pin.xdr == 1: 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: o_type = 0b1100 # PIN_OUTPUT_DDR_ENABLE_REGISTERED io_args.append(("p", "PIN_TYPE", (o_type << 2) | i_type)) 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)) io_args.append(("o", "D_IN_1", i1_ff)) 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)) if pin.dir in ("oe", "io"): io_args.append(("i", "OUTPUT_ENABLE", pin.oe)) if is_global_input: m.submodules += Instance("SB_GB_IO", *io_args) else: m.submodules += Instance("SB_IO", *io_args) def get_input(self, pin, port, extras): self._check_feature("single-ended input", pin, extras, valid_xdrs=(0, 1, 2), valid_extras=True) m = Module() self._get_io_buffer(m, pin, port, extras) return m def get_output(self, pin, port, extras): self._check_feature("single-ended output", pin, extras, valid_xdrs=(0, 1, 2), valid_extras=True) m = Module() self._get_io_buffer(m, pin, port, extras) return m def get_tristate(self, pin, port, extras): self._check_feature("single-ended tristate", pin, extras, valid_xdrs=(0, 1, 2), valid_extras=True) m = Module() self._get_io_buffer(m, pin, port, extras) return m def get_input_output(self, pin, port, extras): self._check_feature("single-ended input/output", pin, extras, valid_xdrs=(0, 1, 2), valid_extras=True) m = Module() self._get_io_buffer(m, pin, port, extras) return m def get_diff_input(self, pin, p_port, n_port, extras): self._check_feature("differential input", pin, extras, valid_xdrs=(0, 1, 2), valid_extras=True) # 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) m = Module() self._get_io_buffer(m, pin, p_port, extras) return m def get_diff_output(self, pin, p_port, n_port, extras): self._check_feature("differential output", pin, extras, valid_xdrs=(0, 1, 2), valid_extras=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, p_port, extras, o_invert=False) self._get_io_buffer(m, pin, n_port, extras, o_invert=True) return m # Tristate and bidirectional buffers are not supported on iCE40 because it requires external # termination, which is incompatible for input and output differential I/Os.