b8852d1375177ef3530b2401e4c11f8ddd7f2c37
[nmigen.git] / nmigen / vendor / fpga / lattice_ice40.py
1 from abc import abstractproperty
2 import os
3 import subprocess
4 import tempfile
5
6 from ...hdl.ast import *
7 from ...hdl.dsl import *
8 from ...hdl.ir import *
9 from ...build import *
10
11
12 __all__ = ["LatticeICE40Platform",
13 "IceStormProgrammerMixin", "IceBurnProgrammerMixin", "TinyProgrammerMixin"]
14
15
16 class LatticeICE40Platform(TemplatedPlatform):
17 """
18 Required tools:
19 * ``yosys``
20 * ``nextpnr-ice40``
21 * ``icepack``
22
23 Available overrides:
24 * ``verbose``: enables logging of informational messages to standard error.
25 * ``read_opts``: adds options for ``read`` Yosys command.
26 * ``synth_opts``: adds options for ``synth_ice40`` Yosys command.
27 * ``script_after_read``: inserts commands after ``read_ilang`` in Yosys script.
28 * ``script_after_synth``: inserts commands after ``synth_ice40`` in Yosys script.
29 * ``yosys_opts``: overrides default options (``-q``) for Yosys.
30 * ``nextpnr_opts``: overrides default options (``-q --placer heap``).
31
32 Build products:
33 * ``{{name}}.rpt``: Yosys log.
34 * ``{{name}}.json``: synthesized RTL.
35 * ``{{name}}.tim``: nextpnr log.
36 * ``{{name}}.asc``: ASCII bitstream.
37 * ``{{name}}.bin``: binary bitstream.
38 """
39
40 device = abstractproperty()
41 package = abstractproperty()
42
43 file_templates = {
44 **TemplatedPlatform.build_script_templates,
45 "{{name}}.il": r"""
46 # {{autogenerated}}
47 {{emit_design("rtlil")}}
48 """,
49 "{{name}}.ys": r"""
50 # {{autogenerated}}
51 {% for file in platform.extra_files %}
52 {% if file.endswith(".v") -%}
53 read_verilog {{get_override("read_opts")|join(" ")}} {{file}}
54 {% elif file.endswith(".sv") -%}
55 read_verilog -sv {{get_override("read_opts")|join(" ")}} {{file}}
56 {% endif %}
57 {% endfor %}
58 read_ilang {{name}}.il
59 {{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
60 synth_ice40 {{get_override("synth_opts")|join(" ")}} -top {{name}}
61 {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
62 write_json {{name}}.json
63 """,
64 "{{name}}.pcf": r"""
65 # {{autogenerated}}
66 {% for port, pins, extra in platform.iter_port_constraints() %}
67 {% if pins|count > 1 %}
68 {% for bit in range -%}
69 set_io {{port}}[{{bit}}] {{pins[bit]}}
70 {% endfor %}
71 {% else -%}
72 set_io {{port}} {{pins[0]}}
73 {% endif %}
74 {% endfor %}
75 """,
76 "{{name}}_pre_pack.py": r"""
77 # {{autogenerated}}
78 {% for port, frequency in platform.iter_clock_constraints() -%}
79 {# Clock in MHz #}
80 ctx.addClock("{{port}}", {{frequency/1000000}})
81 {% endfor%}
82 """,
83 }
84 command_templates = [
85 r"""
86 {{get_tool("yosys")}}
87 {{quiet("-q")}}
88 {{get_override("yosys_opts")|join(" ")}}
89 -l {{name}}.rpt
90 {{name}}.ys
91 """,
92 r"""
93 {{get_tool("nextpnr-ice40")}}
94 {{quiet("-q")}}
95 {{get_override("nextpnr_opts")|default(["--placer","heap"])|join(" ")}}
96 -l {{name}}.tim
97 --{{platform.device}}
98 --package {{platform.package}}
99 --json {{name}}.json
100 --pcf {{name}}.pcf
101 --pre-pack {{name}}_pre_pack.py
102 --asc {{name}}.asc
103 """,
104 r"""
105 {{get_tool("icepack")}}
106 {{name}}.asc
107 {{name}}.bin
108 """
109 ]
110
111 def _get_dff(self, clk, d, q):
112 return Instance("$dff",
113 p_CLK_POLARITY=0,
114 p_WIDTH=len(d),
115 i_CLK=clk,
116 i_D=d,
117 o_Q=q)
118
119 def _get_io_buffer(self, pin, port, extras):
120 m = Module()
121
122 if "GLOBAL" in extras:
123 is_global_input = bool(extras["GLOBAL"])
124 del extras["GLOBAL"]
125 else:
126 is_global_input = False
127
128 if "i" in pin.dir and pin.xdr == 2:
129 i0_ff = Signal.like(pin.i0, name="{}_ff".format(pin.i0.name))
130 i1_ff = Signal.like(pin.i1, name="{}_ff".format(pin.i1.name))
131 m.submodules += self._get_dff(pin.i_clk, i0_ff, pin.i0)
132 m.submodules += self._get_dff(pin.i_clk, i1_ff, pin.i1)
133 if "o" in pin.dir and pin.xdr == 2:
134 o1_ff = Signal.like(pin.o1, name="{}_ff".format(pin.o1.name))
135 m.submodules += self._get_dff(pin.o_clk, pin.o1, o1_ff)
136
137 for bit in range(len(port)):
138 io_args = [
139 ("io", "PACKAGE_PIN", port[bit]),
140 *(("p", key, value) for key, value in extras.items()),
141 ]
142
143 if "i" not in pin.dir:
144 i_type = 0b00 # PIN_NO_INPUT aka PIN_INPUT_REGISTERED
145 elif pin.xdr == 0:
146 i_type = 0b01 # PIN_INPUT
147 elif pin.xdr > 0:
148 i_type = 0b00 # PIN_INPUT_REGISTERED
149 if "o" not in pin.dir:
150 o_type = 0b0000 # PIN_NO_OUTPUT
151 elif pin.xdr == 0 and pin.dir == "o":
152 o_type = 0b0110 # PIN_OUTPUT
153 elif pin.xdr == 0:
154 o_type = 0b1010 # PIN_OUTPUT_TRISTATE
155 elif pin.xdr == 1 and pin.dir == "o":
156 o_type = 0b0101 # PIN_OUTPUT_REGISTERED
157 elif pin.xdr == 1:
158 o_type = 0b1101 # PIN_OUTPUT_REGISTERED_ENABLE_REGISTERED
159 elif pin.xdr == 2 and pin.dir == "o":
160 o_type = 0b0100 # PIN_OUTPUT_DDR
161 elif pin.xdr == 2:
162 o_type = 0b1100 # PIN_OUTPUT_DDR_ENABLE_REGISTERED
163 io_args.append(("p", "PIN_TYPE", (o_type << 2) | i_type))
164
165 if hasattr(pin, "i_clk"):
166 io_args.append(("i", "INPUT_CLK", pin.i_clk))
167 if hasattr(pin, "o_clk"):
168 io_args.append(("i", "OUTPUT_CLK", pin.o_clk))
169
170 if "i" in pin.dir:
171 if pin.xdr == 0 and is_global_input:
172 io_args.append(("o", "GLOBAL_BUFFER_OUTPUT", pin.i[bit]))
173 elif pin.xdr < 2:
174 io_args.append(("o", "D_IN_0", pin.i[bit]))
175 elif pin.xdr == 2:
176 # Re-register both inputs before they enter fabric. This increases hold time
177 # to an entire cycle, and adds one cycle of latency.
178 io_args.append(("o", "D_IN_0", i0_ff))
179 io_args.append(("o", "D_IN_1", i1_ff))
180 if "o" in pin.dir:
181 if pin.xdr < 2:
182 io_args.append(("i", "D_OUT_0", pin.o[bit]))
183 elif pin.xdr == 2:
184 # Re-register negedge output after it leaves fabric. This increases setup time
185 # to an entire cycle, and doesn't add latency.
186 io_args.append(("i", "D_OUT_0", pin.o0[bit]))
187 io_args.append(("i", "D_OUT_1", o1_ff))
188
189 if pin.dir in ("oe", "io"):
190 io_args.append(("i", "OUTPUT_ENABLE", pin.oe))
191
192 if is_global_input:
193 m.submodules += Instance("SB_GB_IO", *io_args)
194 else:
195 m.submodules += Instance("SB_IO", *io_args)
196
197 return m
198
199 def get_input(self, pin, port, extras):
200 self._check_feature("single-ended input", pin, extras,
201 valid_xdrs=(0, 1, 2), valid_extras=True)
202 return self._get_io_buffer(pin, port, extras)
203
204 def get_output(self, pin, port, extras):
205 self._check_feature("single-ended output", pin, extras,
206 valid_xdrs=(0, 1, 2), valid_extras=True)
207 return self._get_io_buffer(pin, port, extras)
208
209 def get_tristate(self, pin, port, extras):
210 self._check_feature("single-ended tristate", pin, extras,
211 valid_xdrs=(0, 1, 2), valid_extras=True)
212 return self._get_io_buffer(pin, port, extras)
213
214 def get_input_output(self, pin, port, extras):
215 self._check_feature("single-ended input/output", pin, extras,
216 valid_xdrs=(0, 1, 2), valid_extras=True)
217 return self._get_io_buffer(pin, port, extras)
218
219
220 class IceStormProgrammerMixin:
221 def toolchain_program(self, products, name, *, mode=None):
222 if mode is None and hasattr(self, "prog_mode"):
223 mode = self.prog_mode
224 if mode not in ("sram", "flash"):
225 raise ValueError("iceprog mode must be one of \"sram\" or \"flash\", not {!r}; "
226 "specify it using .build(..., program_opts={\"mode\": \"<mode>\"})"
227 .format(mode))
228
229 iceprog = os.environ.get("ICEPROG", "iceprog")
230 bitstream = products.get("{}.bin".format(name))
231 if mode == "sram":
232 options = ["-S"]
233 if mode == "flash":
234 options = []
235 with tempfile.NamedTemporaryFile(prefix="nmigen_iceprog_",
236 suffix=".bin") as bitstream_file:
237 bitstream_file.write(bitstream)
238 subprocess.run([iceprog, *options, bitstream_file.name], check=True)
239
240
241 class IceBurnProgrammerMixin:
242 def toolchain_program(self, products, name):
243 iceburn = os.environ.get("ICEBURN", "iCEburn")
244 bitstream = products.get("{}.bin".format(name))
245 with tempfile.NamedTemporaryFile(prefix="nmigen_iceburn_",
246 suffix=".bin") as bitstream_file:
247 bitstream_file.write(bitstream)
248 subprocess.run([iceburn, "-evw", bitstream_file.name], check=True)
249
250
251 class TinyProgrammerMixin:
252 def toolchain_program(self, products, name):
253 tinyprog = os.environ.get("TINYPROG", "tinyprog")
254 options = ["-p"]
255 bitstream = products.get("{}.bin".format(name))
256 with tempfile.NamedTemporaryFile(prefix="nmigen_tinyprog_",
257 suffix=".bin") as bitstream_file:
258 bitstream_file.write(bitstream)
259 subprocess.run([tinyprog, *options, bitstream_file.name], check=True)