from abc import abstractproperty import os import subprocess import tempfile from ...hdl.ast import * from ...hdl.dsl import * from ...hdl.ir import * from ...build import * __all__ = ["LatticeICE40Platform", "IceStormProgrammerMixin", "IceBurnProgrammerMixin", "TinyProgrammerMixin"] 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``: overrides default options (``-q``) for Yosys. * ``nextpnr_opts``: overrides default options (``-q --placer heap``). 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, pins, extra in platform.iter_port_constraints() %} {% if pins|count > 1 %} {% for bit in range -%} set_io {{port}}[{{bit}}] {{pins[bit]}} {% endfor %} {% else -%} set_io {{port}} {{pins[0]}} {% endif %} {% 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")}} {{name}}.asc {{name}}.bin """ ] def _get_dff(self, clk, d, q): return Instance("$dff", p_CLK_POLARITY=0, p_WIDTH=len(d), i_CLK=clk, i_D=d, o_Q=q) def _get_io_buffer(self, pin, port, extras): m = Module() 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)) m.submodules += self._get_dff(pin.i_clk, i0_ff, pin.i0) m.submodules += self._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)) m.submodules += self._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 < 2: io_args.append(("o", "D_IN_0", pin.i[bit])) if 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])) if 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)) m.submodules += Instance("SB_IO", *io_args) return m def get_input(self, pin, port, extras): self._check_feature("single-ended input", pin, extras, valid_xdrs=(0, 1, 2), valid_extras=True) return self._get_io_buffer(pin, port, extras) def get_output(self, pin, port, extras): self._check_feature("single-ended output", pin, extras, valid_xdrs=(0, 1, 2), valid_extras=True) return self._get_io_buffer(pin, port, extras) def get_tristate(self, pin, port, extras): self._check_feature("single-ended tristate", pin, extras, valid_xdrs=(0, 1, 2), valid_extras=True) return self._get_io_buffer(pin, port, extras) 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) return self._get_io_buffer(pin, port, extras) class IceStormProgrammerMixin: def toolchain_program(self, products, name, *, mode=None): if mode is None and hasattr(self, "prog_mode"): mode = self.prog_mode if mode not in ("sram", "flash"): raise ValueError("iceprog mode must be one of \"sram\" or \"flash\", not {!r}; " "specify it using .build(..., program_opts={\"mode\": \"\"})" .format(mode)) iceprog = os.environ.get("ICEPROG", "iceprog") bitstream = products.get("{}.bin".format(name)) if mode == "sram": options = ["-S"] if mode == "flash": options = [] with tempfile.NamedTemporaryFile(prefix="nmigen_iceprog_", suffix=".bin") as bitstream_file: bitstream_file.write(bitstream) subprocess.run([iceprog, *options, bitstream_file.name], check=True) class IceBurnProgrammerMixin: def toolchain_program(self, products, name): iceburn = os.environ.get("ICEBURN", "iCEburn") bitstream = products.get("{}.bin".format(name)) with tempfile.NamedTemporaryFile(prefix="nmigen_iceburn_", suffix=".bin") as bitstream_file: bitstream_file.write(bitstream) subprocess.run([iceburn, "-evw", bitstream_file.name], check=True) class TinyProgrammerMixin: def toolchain_program(self, products, name): tinyprog = os.environ.get("TINYPROG", "tinyprog") options = ["-p"] bitstream = products.get("{}.bin".format(name)) with tempfile.NamedTemporaryFile(prefix="nmigen_tinyprog_", suffix=".bin") as bitstream_file: bitstream_file.write(bitstream) subprocess.run([tinyprog, *options, bitstream_file.name], check=True)