1 from abc
import abstractproperty
4 from ..lib
.cdc
import ResetSynchronizer
8 __all__
= ["LatticeICE40Platform"]
11 class LatticeICE40Platform(TemplatedPlatform
):
21 The environment is populated by running the script specified in the environment variable
22 ``NMIGEN_ENV_IceStorm``, if present.
25 * ``verbose``: enables logging of informational messages to standard error.
26 * ``read_verilog_opts``: adds options for ``read_verilog`` Yosys command.
27 * ``synth_opts``: adds options for ``synth_ice40`` Yosys command.
28 * ``script_after_read``: inserts commands after ``read_ilang`` in Yosys script.
29 * ``script_after_synth``: inserts commands after ``synth_ice40`` in Yosys script.
30 * ``yosys_opts``: adds extra options for ``yosys``.
31 * ``nextpnr_opts``: adds extra options for ``nextpnr-ice40``.
32 * ``add_pre_pack``: inserts commands at the end in pre-pack Python script.
33 * ``add_constraints``: inserts commands at the end in the PCF file.
36 * ``{{name}}.rpt``: Yosys log.
37 * ``{{name}}.json``: synthesized RTL.
38 * ``{{name}}.tim``: nextpnr log.
39 * ``{{name}}.asc``: ASCII bitstream.
40 * ``{{name}}.bin``: binary bitstream.
45 This toolchain comes in two variants: ``LSE-iCECube2`` and ``Synplify-iCECube2``.
51 The environment is populated by setting the necessary environment variables based on
52 ``NMIGEN_ENV_iCECube2``, which must point to the root of the iCECube2 installation, and
56 * ``verbose``: enables logging of informational messages to standard error.
57 * ``lse_opts``: adds options for LSE.
58 * ``script_after_add``: inserts commands after ``add_file`` in Synplify Tcl script.
59 * ``script_after_options``: inserts commands after ``set_option`` in Synplify Tcl script.
60 * ``add_constraints``: inserts commands in SDC file.
61 * ``script_after_flow``: inserts commands after ``run_sbt_backend_auto`` in SBT
65 * ``{{name}}_lse.log`` (LSE) or ``{{name}}_design/{{name}}.htm`` (Synplify): synthesis log.
66 * ``sbt/outputs/router/{{name}}_timing.rpt``: timing report.
67 * ``{{name}}.edf``: EDIF netlist.
68 * ``{{name}}.bin``: binary bitstream.
71 toolchain
= None # selected when creating platform
73 device
= abstractproperty()
74 package
= abstractproperty()
78 _nextpnr_device_options
= {
79 "iCE40LP384": "--lp384",
80 "iCE40LP1K": "--lp1k",
81 "iCE40LP4K": "--lp8k",
82 "iCE40LP8K": "--lp8k",
83 "iCE40HX1K": "--hx1k",
84 "iCE40HX4K": "--hx8k",
85 "iCE40HX8K": "--hx8k",
86 "iCE40UP5K": "--up5k",
87 "iCE40UP3K": "--up5k",
92 _nextpnr_package_options
= {
100 _icestorm_required_tools
= [
105 _icestorm_file_templates
= {
106 **TemplatedPlatform
.build_script_templates
,
111 "{{name}}.debug.v": r
"""
112 /* {{autogenerated}} */
113 {{emit_debug_verilog()}}
117 {% for file in platform.iter_extra_files(".v") -%}
118 read_verilog {{get_override("read_verilog_opts")|options}} {{file}}
120 {% for file in platform.iter_extra_files(".sv") -%}
121 read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}}
123 {% for file in platform.iter_extra_files(".il") -%}
126 read_ilang {{name}}.il
127 {{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
128 synth_ice40 {{get_override("synth_opts")|options}} -top {{name}}
129 {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
130 write_json {{name}}.json
134 {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
135 set_io {{port_name}} {{pin_name}}
137 {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
138 set_frequency {{net_signal|hierarchy(".")}} {{frequency/1000000}}
140 {{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
143 _icestorm_command_templates
= [
145 {{invoke_tool("yosys")}}
147 {{get_override("yosys_opts")|options}}
152 {{invoke_tool("nextpnr-ice40")}}
154 {{get_override("nextpnr_opts")|options}}
156 {{platform._nextpnr_device_options[platform.device]}}
158 {{platform.package|lower}}{{platform._nextpnr_package_options[platform.device]|
165 {{invoke_tool("icepack")}}
174 _icecube2_required_tools
= [
179 _icecube2_file_templates
= {
180 **TemplatedPlatform
.build_script_templates
,
181 "build_{{name}}.sh": r
"""
183 set -e{{verbose("x")}}
184 if [ -n "${{platform._toolchain_env_var}}" ]; then
186 export LD_LIBRARY_PATH=${{platform._toolchain_env_var}}/LSE/bin/lin64:$LD_LIBRARY_PATH
187 export PATH=${{platform._toolchain_env_var}}/LSE/bin/lin64:$PATH
188 export FOUNDRY=${{platform._toolchain_env_var}}/LSE
189 # Synplify environment
190 export LD_LIBRARY_PATH=${{platform._toolchain_env_var}}/sbt_backend/bin/linux/opt/synpwrap:$LD_LIBRARY_PATH
191 export PATH=${{platform._toolchain_env_var}}/sbt_backend/bin/linux/opt/synpwrap:$PATH
192 export SYNPLIFY_PATH=${{platform._toolchain_env_var}}/synpbase
194 export SBT_DIR=${{platform._toolchain_env_var}}/sbt_backend
196 echo "Variable ${{platform._toolchain_env_var}} must be set" >&2; exit 1
198 {{emit_commands("sh")}}
201 /* {{autogenerated}} */
204 "{{name}}.debug.v": r
"""
205 /* {{autogenerated}} */
206 {{emit_debug_verilog()}}
208 "{{name}}_lse.prj": r
"""
210 -a SBT{{platform.family}}
211 -d {{platform.device}}
212 -t {{platform.package}}
213 {{get_override("lse_opts")|options|default("# (lse_opts placeholder)")}}
214 {% for file in platform.iter_extra_files(".v") -%}
220 -output_edif {{name}}.edf
221 -logfile {{name}}_lse.log
223 "{{name}}_syn.prj": r
"""
225 {% for file in platform.iter_extra_files(".v", ".sv", ".vhd", ".vhdl") -%}
226 add_file -verilog {{file|tcl_escape}}
228 add_file -verilog {{name}}.v
229 add_file -constraint {{name}}.sdc
230 {{get_override("script_after_add")|default("# (script_after_add placeholder)")}}
231 impl -add {{name}}_design -type fpga
232 set_option -technology SBT{{platform.family}}
233 set_option -part {{platform.device}}
234 set_option -package {{platform.package}}
235 {{get_override("script_after_options")|default("# (script_after_options placeholder)")}}
236 project -result_format edif
237 project -result_file {{name}}.edf
238 impl -active {{name}}_design
241 project -run fpga_mapper
242 file copy -force -- {{name}}_design/{{name}}.edf {{name}}.edf
246 {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
247 {% if port_signal is not none -%}
248 create_clock -name {{port_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_escape}}]
250 create_clock -name {{net_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_nets {{net_signal|hierarchy("/")|tcl_escape}}]
253 {{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
257 set device {{platform.device}}-{{platform.package}}
258 set top_module {{name}}
261 set edif_file {{name}}
262 set tool_options ":edifparser -y {{name}}.pcf"
263 set sbt_root $::env(SBT_DIR)
264 append sbt_tcl $sbt_root "/tcl/sbt_backend_synpl.tcl"
266 run_sbt_backend_auto $device $top_module $proj_dir $output_dir $tool_options $edif_file
267 {{get_override("script_after_file")|default("# (script_after_file placeholder)")}}
268 file copy -force -- sbt/outputs/bitmap/{{name}}_bitmap.bin {{name}}.bin
273 {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
274 set_io {{port_name}} {{pin_name}}
278 _lse_icecube2_command_templates
= [
279 r
"""synthesis -f {{name}}_lse.prj""",
280 r
"""tclsh {{name}}.tcl""",
282 _synplify_icecube2_command_templates
= [
283 r
"""synpwrap -prj {{name}}_syn.prj -log {{name}}_syn.log""",
284 r
"""tclsh {{name}}.tcl""",
289 def __init__(self
, *, toolchain
="IceStorm"):
292 assert toolchain
in ("IceStorm", "LSE-iCECube2", "Synplify-iCECube2")
293 self
.toolchain
= toolchain
297 if self
.device
.startswith("iCE40"):
299 if self
.device
.startswith("iCE5"):
304 def _toolchain_env_var(self
):
305 if self
.toolchain
== "IceStorm":
306 return f
"NMIGEN_ENV_{self.toolchain}"
307 if self
.toolchain
in ("LSE-iCECube2", "Synplify-iCECube2"):
308 return f
"NMIGEN_ENV_iCECube2"
312 def required_tools(self
):
313 if self
.toolchain
== "IceStorm":
314 return self
._icestorm
_required
_tools
315 if self
.toolchain
in ("LSE-iCECube2", "Synplify-iCECube2"):
316 return self
._icecube
2_required
_tools
320 def file_templates(self
):
321 if self
.toolchain
== "IceStorm":
322 return self
._icestorm
_file
_templates
323 if self
.toolchain
in ("LSE-iCECube2", "Synplify-iCECube2"):
324 return self
._icecube
2_file
_templates
328 def command_templates(self
):
329 if self
.toolchain
== "IceStorm":
330 return self
._icestorm
_command
_templates
331 if self
.toolchain
== "LSE-iCECube2":
332 return self
._lse
_icecube
2_command
_templates
333 if self
.toolchain
== "Synplify-iCECube2":
334 return self
._synplify
_icecube
2_command
_templates
338 def default_clk_constraint(self
):
339 # Internal high-speed oscillator: 48 MHz / (2 ^ div)
340 if self
.default_clk
== "SB_HFOSC":
341 return Clock(48e6
/ 2 ** self
.hfosc_div
)
342 # Internal low-speed oscillator: 10 KHz
343 elif self
.default_clk
== "SB_LFOSC":
345 # Otherwise, use the defined Clock resource.
346 return super().default_clk_constraint
348 def create_missing_domain(self
, name
):
349 # For unknown reasons (no errata was ever published, and no documentation mentions this
350 # issue), iCE40 BRAMs read as zeroes for ~3 us after configuration and release of internal
351 # global reset. Note that this is a *time-based* delay, generated purely by the internal
352 # oscillator, which may not be observed nor influenced directly. For details, see links:
353 # * https://github.com/cliffordwolf/icestorm/issues/76#issuecomment-289270411
354 # * https://github.com/cliffordwolf/icotools/issues/2#issuecomment-299734673
356 # To handle this, it is necessary to have a global reset in any iCE40 design that may
357 # potentially instantiate BRAMs, and assert this reset for >3 us after configuration.
358 # (We add a margin of 5x to allow for PVT variation.) If the board includes a dedicated
359 # reset line, this line is ORed with the power on reset.
361 # If an internal oscillator is selected as the default clock source, the power-on-reset
362 # delay is increased to 100 us, since the oscillators are only stable after that long.
364 # The power-on reset timer counts up because the vendor tools do not support initialization
366 if name
== "sync" and self
.default_clk
is not None:
369 # Internal high-speed clock: 6 MHz, 12 MHz, 24 MHz, or 48 MHz depending on the divider.
370 if self
.default_clk
== "SB_HFOSC":
371 if not hasattr(self
, "hfosc_div"):
372 raise ValueError("SB_HFOSC divider exponent (hfosc_div) must be an integer "
374 if not isinstance(self
.hfosc_div
, int) or self
.hfosc_div
< 0 or self
.hfosc_div
> 3:
375 raise ValueError("SB_HFOSC divider exponent (hfosc_div) must be an integer "
376 "between 0 and 3, not {!r}"
377 .format(self
.hfosc_div
))
379 m
.submodules
+= Instance("SB_HFOSC",
382 p_CLKHF_DIV
="0b{0:b}".format(self
.hfosc_div
),
384 delay
= int(100e-6 * self
.default_clk_frequency
)
385 # Internal low-speed clock: 10 KHz.
386 elif self
.default_clk
== "SB_LFOSC":
388 m
.submodules
+= Instance("SB_LFOSC",
392 delay
= int(100e-6 * self
.default_clk_frequency
)
393 # User-defined clock signal.
395 clk_i
= self
.request(self
.default_clk
).i
396 delay
= int(15e-6 * self
.default_clk_frequency
)
398 if self
.default_rst
is not None:
399 rst_i
= self
.request(self
.default_rst
).i
403 # Power-on-reset domain
404 m
.domains
+= ClockDomain("por", reset_less
=True, local
=True)
405 timer
= Signal(range(delay
))
407 m
.d
.comb
+= ClockSignal("por").eq(clk_i
)
408 with m
.If(timer
== delay
):
409 m
.d
.por
+= ready
.eq(1)
411 m
.d
.por
+= timer
.eq(timer
+ 1)
414 m
.domains
+= ClockDomain("sync")
415 m
.d
.comb
+= ClockSignal("sync").eq(clk_i
)
416 if self
.default_rst
is not None:
417 m
.submodules
.reset_sync
= ResetSynchronizer(~ready | rst_i
, domain
="sync")
419 m
.d
.comb
+= ResetSignal("sync").eq(~ready
)
423 def should_skip_port_component(self
, port
, attrs
, component
):
424 # On iCE40, a differential input is placed by only instantiating an SB_IO primitive for
425 # the pin with z=0, which is the non-inverting pin. The pinout unfortunately differs
426 # between LP/HX and UP series:
427 # * for LP/HX, z=0 is DPxxB (B is non-inverting, A is inverting)
428 # * for UP, z=0 is IOB_xxA (A is non-inverting, B is inverting)
429 if attrs
.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT" and component
== "n":
433 def _get_io_buffer(self
, m
, pin
, port
, attrs
, *, i_invert
=False, o_invert
=False,
435 def get_dff(clk
, d
, q
):
436 m
.submodules
+= Instance("$dff",
443 def get_ineg(y
, invert
):
445 a
= Signal
.like(y
, name_suffix
="_x{}".format(1 if invert
else 0))
446 for bit
in range(len(y
)):
447 m
.submodules
+= Instance("SB_LUT4",
448 p_LUT_INIT
=Const(0b01 if invert
else 0b10, 16),
456 a
= Signal
.like(y
, name_suffix
="_n")
462 def get_oneg(a
, invert
):
464 y
= Signal
.like(a
, name_suffix
="_x{}".format(1 if invert
else 0))
465 for bit
in range(len(a
)):
466 m
.submodules
+= Instance("SB_LUT4",
467 p_LUT_INIT
=Const(0b01 if invert
else 0b10, 16),
475 y
= Signal
.like(a
, name_suffix
="_n")
481 if "GLOBAL" in attrs
:
482 is_global_input
= bool(attrs
["GLOBAL"])
485 is_global_input
= False
486 assert not (is_global_input
and i_invert
)
490 pin_i
= get_ineg(pin
.i
, i_invert
)
492 pin_i0
= get_ineg(pin
.i0
, i_invert
)
493 pin_i1
= get_ineg(pin
.i1
, i_invert
)
496 pin_o
= get_oneg(pin
.o
, o_invert
)
498 pin_o0
= get_oneg(pin
.o0
, o_invert
)
499 pin_o1
= get_oneg(pin
.o1
, o_invert
)
501 if "i" in pin
.dir and pin
.xdr
== 2:
502 i0_ff
= Signal
.like(pin_i0
, name_suffix
="_ff")
503 i1_ff
= Signal
.like(pin_i1
, name_suffix
="_ff")
504 get_dff(pin
.i_clk
, i0_ff
, pin_i0
)
505 get_dff(pin
.i_clk
, i1_ff
, pin_i1
)
506 if "o" in pin
.dir and pin
.xdr
== 2:
507 o1_ff
= Signal
.like(pin_o1
, name_suffix
="_ff")
508 get_dff(pin
.o_clk
, pin_o1
, o1_ff
)
510 for bit
in range(len(port
)):
512 ("io", "PACKAGE_PIN", port
[bit
]),
513 *(("p", key
, value
) for key
, value
in attrs
.items()),
516 if "i" not in pin
.dir:
517 # If no input pin is requested, it is important to use a non-registered input pin
518 # type, because an output-only pin would not have an input clock, and if its input
519 # is configured as registered, this would prevent a co-located input-capable pin
520 # from using an input clock.
521 i_type
= 0b01 # PIN_INPUT
523 i_type
= 0b01 # PIN_INPUT
525 i_type
= 0b00 # PIN_INPUT_REGISTERED aka PIN_INPUT_DDR
526 if "o" not in pin
.dir:
527 o_type
= 0b0000 # PIN_NO_OUTPUT
528 elif pin
.xdr
== 0 and pin
.dir == "o":
529 o_type
= 0b0110 # PIN_OUTPUT
531 o_type
= 0b1010 # PIN_OUTPUT_TRISTATE
532 elif pin
.xdr
== 1 and pin
.dir == "o":
533 o_type
= 0b0101 # PIN_OUTPUT_REGISTERED
535 o_type
= 0b1101 # PIN_OUTPUT_REGISTERED_ENABLE_REGISTERED
536 elif pin
.xdr
== 2 and pin
.dir == "o":
537 o_type
= 0b0100 # PIN_OUTPUT_DDR
539 o_type
= 0b1100 # PIN_OUTPUT_DDR_ENABLE_REGISTERED
540 io_args
.append(("p", "PIN_TYPE", C((o_type
<< 2) | i_type
, 6)))
542 if hasattr(pin
, "i_clk"):
543 io_args
.append(("i", "INPUT_CLK", pin
.i_clk
))
544 if hasattr(pin
, "o_clk"):
545 io_args
.append(("i", "OUTPUT_CLK", pin
.o_clk
))
548 if pin
.xdr
== 0 and is_global_input
:
549 io_args
.append(("o", "GLOBAL_BUFFER_OUTPUT", pin
.i
[bit
]))
551 io_args
.append(("o", "D_IN_0", pin_i
[bit
]))
553 # Re-register both inputs before they enter fabric. This increases hold time
554 # to an entire cycle, and adds one cycle of latency.
555 io_args
.append(("o", "D_IN_0", i0_ff
[bit
]))
556 io_args
.append(("o", "D_IN_1", i1_ff
[bit
]))
559 io_args
.append(("i", "D_OUT_0", pin_o
[bit
]))
561 # Re-register negedge output after it leaves fabric. This increases setup time
562 # to an entire cycle, and doesn't add latency.
563 io_args
.append(("i", "D_OUT_0", pin_o0
[bit
]))
564 io_args
.append(("i", "D_OUT_1", o1_ff
[bit
]))
566 if pin
.dir in ("oe", "io"):
567 io_args
.append(("i", "OUTPUT_ENABLE", pin
.oe
))
570 m
.submodules
["{}_{}".format(pin
.name
, bit
)] = Instance("SB_GB_IO", *io_args
)
572 m
.submodules
["{}_{}".format(pin
.name
, bit
)] = Instance("SB_IO", *io_args
)
574 def get_input(self
, pin
, port
, attrs
, invert
):
575 self
._check
_feature
("single-ended input", pin
, attrs
,
576 valid_xdrs
=(0, 1, 2), valid_attrs
=True)
578 self
._get
_io
_buffer
(m
, pin
, port
.io
, attrs
, i_invert
=invert
)
581 def get_output(self
, pin
, port
, attrs
, invert
):
582 self
._check
_feature
("single-ended output", pin
, attrs
,
583 valid_xdrs
=(0, 1, 2), valid_attrs
=True)
585 self
._get
_io
_buffer
(m
, pin
, port
.io
, attrs
, o_invert
=invert
)
588 def get_tristate(self
, pin
, port
, attrs
, invert
):
589 self
._check
_feature
("single-ended tristate", pin
, attrs
,
590 valid_xdrs
=(0, 1, 2), valid_attrs
=True)
592 self
._get
_io
_buffer
(m
, pin
, port
.io
, attrs
, o_invert
=invert
)
595 def get_input_output(self
, pin
, port
, attrs
, invert
):
596 self
._check
_feature
("single-ended input/output", pin
, attrs
,
597 valid_xdrs
=(0, 1, 2), valid_attrs
=True)
599 self
._get
_io
_buffer
(m
, pin
, port
.io
, attrs
, i_invert
=invert
, o_invert
=invert
)
602 def get_diff_input(self
, pin
, port
, attrs
, invert
):
603 self
._check
_feature
("differential input", pin
, attrs
,
604 valid_xdrs
=(0, 1, 2), valid_attrs
=True)
606 # See comment in should_skip_port_component above.
607 self
._get
_io
_buffer
(m
, pin
, port
.p
, attrs
, i_invert
=invert
)
610 def get_diff_output(self
, pin
, port
, attrs
, invert
):
611 self
._check
_feature
("differential output", pin
, attrs
,
612 valid_xdrs
=(0, 1, 2), valid_attrs
=True)
614 # Note that the non-inverting output pin is not driven the same way as a regular
615 # output pin. The inverter introduces a delay, so for a non-inverting output pin,
616 # an identical delay is introduced by instantiating a LUT. This makes the waveform
617 # perfectly symmetric in the xdr=0 case.
618 self
._get
_io
_buffer
(m
, pin
, port
.p
, attrs
, o_invert
= invert
, invert_lut
=True)
619 self
._get
_io
_buffer
(m
, pin
, port
.n
, attrs
, o_invert
=not invert
, invert_lut
=True)
622 # Tristate bidirectional buffers are not supported on iCE40 because it requires external
623 # termination, which is different for differential pins configured as inputs and outputs.
625 # CDC primitives are not currently specialized for iCE40. It is not known if iCECube2 supports
626 # the necessary attributes; nextpnr-ice40 does not.