Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: m-labs/nmigen
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 2dc6ae4ac531
Choose a base ref
...
head repository: m-labs/nmigen
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 3d62dac1cbc3
Choose a head ref
  • 3 commits
  • 7 files changed
  • 1 contributor

Commits on Sep 21, 2019

  1. build.plat: NMIGEN_<toolchain>_env→NMIGEN_ENV_<toolchain>

    This is more consistent with other environment variables nMigen uses.
    whitequark committed Sep 21, 2019
    Copy the full SHA
    07a82ed View commit details
  2. build.res: simplify clock constraints.

    Before this commit, it was possible to set and get clock constraints
    placed on Pin objects. This was not a very good implementation, since
    it relied on matching the identity of the provided Pin object to
    a previously requested one. The only reason it worked like that is
    deficiencies in nextpnr.
    
    Since then, nextpnr has been fixed to allow setting constraints on
    arbitrary nets. Correspondingly, backends that are using Synplify
    were changed to use [get_nets] instead of [get_ports] in SDC files.
    However, in some situations, Synplify does not allow specifying
    ports in [get_nets]. (In fact, nextpnr had a similar problem, but
    it has also been fixed.)
    
    The simplest way to address this is to refer to the interior net
    (after the input buffer), which always works. The only downside
    of this is that requesting a clock as a raw pin using
        platform.request("clk", dir="-")
    and directly applying a constraint to it could fail in some cases.
    This is not a significant issue.
    whitequark committed Sep 21, 2019
    Copy the full SHA
    8050cfa View commit details
  3. vendor.lattice_ice40: add iCECube support.

    This also makes some iCE40 and ECP5 overrides more consistent.
    whitequark committed Sep 21, 2019
    Copy the full SHA
    3d62dac View commit details
Showing with 237 additions and 72 deletions.
  1. +1 −1 nmigen/build/plat.py
  2. +3 −27 nmigen/build/res.py
  3. +7 −20 nmigen/test/test_build_res.py
  4. +17 −6 nmigen/vendor/lattice_ecp5.py
  5. +207 −16 nmigen/vendor/lattice_ice40.py
  6. +1 −1 nmigen/vendor/xilinx_7series.py
  7. +1 −1 nmigen/vendor/xilinx_spartan_3_6.py
2 changes: 1 addition & 1 deletion nmigen/build/plat.py
Original file line number Diff line number Diff line change
@@ -62,7 +62,7 @@ def add_file(self, filename, content):

@property
def _toolchain_env_var(self):
return f"NMIGEN_{self.toolchain}_env"
return f"NMIGEN_ENV_{self.toolchain}"

def build(self, elaboratable, name="top",
build_dir="build", do_build=True,
30 changes: 3 additions & 27 deletions nmigen/build/res.py
Original file line number Diff line number Diff line change
@@ -147,7 +147,7 @@ def resolve(resource, dir, xdr, name, attrs):
self._ports.append((resource, pin, port, attrs))

if pin is not None and resource.clock is not None:
self.add_clock_constraint(pin, resource.clock.frequency)
self.add_clock_constraint(pin.i, resource.clock.frequency)

return pin if pin is not None else port

@@ -212,42 +212,18 @@ def iter_port_constraints_bits(self):
for bit, pin_name in enumerate(pin_names):
yield "{}[{}]".format(port_name, bit), pin_name, attrs

def _map_clock_to_port(self, clock):
if not isinstance(clock, (Signal, Pin)):
raise TypeError("Object {!r} is not a Signal or Pin".format(clock))

if isinstance(clock, Pin):
for res, pin, port, attrs in self._ports:
if clock is pin:
if isinstance(res.ios[0], Pins):
clock = port.io
elif isinstance(res.ios[0], DiffPairs):
clock = port.p
else:
assert False
break
else:
raise ValueError("The Pin object {!r}, which is not a previously requested "
"resource, cannot be used to desigate a clock"
.format(clock))

return clock

def add_clock_constraint(self, clock, frequency):
if not isinstance(clock, Signal):
raise TypeError("Object {!r} is not a Signal".format(clock))
if not isinstance(frequency, (int, float)):
raise TypeError("Frequency must be a number, not {!r}".format(frequency))

clock = self._map_clock_to_port(clock)
if clock in self._clocks:
raise ValueError("Cannot add clock constraint on {!r}, which is already constrained "
"to {} Hz"
.format(clock, self._clocks[clock]))
else:
self._clocks[clock] = float(frequency)

def get_clock_constraint(self, clock):
clock = self._map_clock_to_port(clock)
return self._clocks[clock]

def iter_clock_constraints(self):
return iter(self._clocks.items())
27 changes: 7 additions & 20 deletions nmigen/test/test_build_res.py
Original file line number Diff line number Diff line change
@@ -194,24 +194,17 @@ def test_request_clock(self):
clk50 = self.cm.request("clk50", 0, dir="i")
clk100_port_p, clk100_port_n, clk50_port = self.cm.iter_ports()
self.assertEqual(list(self.cm.iter_clock_constraints()), [
(clk100_port_p, 100e6),
(clk50_port, 50e6)
(clk100.i, 100e6),
(clk50.i, 50e6)
])

def test_add_clock(self):
i2c = self.cm.request("i2c")
self.cm.add_clock_constraint(i2c.scl, 100e3)
scl_port, sda_port = self.cm.iter_ports()
self.cm.add_clock_constraint(i2c.scl.o, 100e3)
self.assertEqual(list(self.cm.iter_clock_constraints()), [
(scl_port, 100e3)
(i2c.scl.o, 100e3)
])

def test_get_clock(self):
clk100 = self.cm.request("clk100", 0)
self.assertEqual(self.cm.get_clock_constraint(clk100), 100e6)
with self.assertRaises(KeyError):
self.cm.get_clock_constraint(Signal())

def test_wrong_resources(self):
with self.assertRaises(TypeError, msg="Object 'wrong' is not a Resource"):
self.cm.add_resources(['wrong'])
@@ -239,20 +232,14 @@ def test_wrong_lookup(self):

def test_wrong_clock_signal(self):
with self.assertRaises(TypeError,
msg="Object None is not a Signal or Pin"):
msg="Object None is not a Signal"):
self.cm.add_clock_constraint(None, 10e6)

def test_wrong_clock_frequency(self):
with self.assertRaises(TypeError,
msg="Frequency must be a number, not None"):
self.cm.add_clock_constraint(Signal(), None)

def test_wrong_clock_pin(self):
with self.assertRaises(ValueError,
msg="The Pin object (rec <unnamed> i), which is not a previously requested "
"resource, cannot be used to desigate a clock"):
self.cm.add_clock_constraint(Pin(1, dir="i"), 1e6)

def test_wrong_request_duplicate(self):
with self.assertRaises(ResourceError,
msg="Resource user_led#0 has already been requested"):
@@ -304,6 +291,6 @@ def test_wrong_request_with_xdr_dict(self):
def test_wrong_clock_constraint_twice(self):
clk100 = self.cm.request("clk100")
with self.assertRaises(ValueError,
msg="Cannot add clock constraint on (sig clk100_0__p), which is already "
msg="Cannot add clock constraint on (sig clk100_0__i), which is already "
"constrained to 100000000.0 Hz"):
self.cm.add_clock_constraint(clk100, 1e6)
self.cm.add_clock_constraint(clk100.i, 1e6)
23 changes: 17 additions & 6 deletions nmigen/vendor/lattice_ecp5.py
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ class LatticeECP5Platform(TemplatedPlatform):
* ``ecppack``
The environment is populated by running the script specified in the environment variable
``NMIGEN_Trellis_env``, if present.
``NMIGEN_ENV_Trellis``, if present.
Available overrides:
* ``verbose``: enables logging of informational messages to standard error.
@@ -29,6 +29,7 @@ class LatticeECP5Platform(TemplatedPlatform):
* ``yosys_opts``: adds extra options for ``yosys``.
* ``nextpnr_opts``: adds extra options for ``nextpnr-ecp5``.
* ``ecppack_opts``: adds extra options for ``ecppack``.
* ``add_preferences``: inserts commands at the end of the LPF file.
Build products:
* ``{{name}}.rpt``: Yosys log.
@@ -46,13 +47,13 @@ class LatticeECP5Platform(TemplatedPlatform):
* ``ddtcmd``
The environment is populated by running the script specified in the environment variable
``NMIGEN_Diamond_env``, if present.
``NMIGEN_ENV_Diamond``, if present.
Available overrides:
* ``script_project``: inserts commands before ``prj_project save`` in Tcl script.
* ``script_after_export``: inserts commands after ``prj_run Export`` in Tcl script.
* ``add_preferences``: inserts commands in LPF file.
* ``add_constraints``: inserts commands in XDC file.
* ``add_preferences``: inserts commands at the end of the LPF file.
* ``add_constraints``: inserts commands at the end of the XDC file.
Build products:
* ``{{name}}_impl/{{name}}_impl.htm``: consolidated log.
@@ -91,6 +92,11 @@ class LatticeECP5Platform(TemplatedPlatform):
"BG756": "caBGA756",
}

_trellis_required_tools = [
"yosys",
"nextpnr-ecp5",
"ecppack"
]
_trellis_file_templates = {
**TemplatedPlatform.build_script_templates,
"{{name}}.il": r"""
@@ -123,6 +129,7 @@ class LatticeECP5Platform(TemplatedPlatform):
{% for signal, frequency in platform.iter_clock_constraints() -%}
FREQUENCY NET "{{signal|hierarchy(".")}}" {{frequency}} HZ;
{% endfor %}
{{get_override("add_preferences")|default("# (add_preferences placeholder)")}}
"""
}
_trellis_command_templates = [
@@ -157,6 +164,10 @@ class LatticeECP5Platform(TemplatedPlatform):

# Diamond templates

_diamond_required_tools = [
"pnmainc",
"ddtcmd"
]
_diamond_file_templates = {
**TemplatedPlatform.build_script_templates,
"build_{{name}}.sh": r"""
@@ -243,9 +254,9 @@ def __init__(self, *, toolchain="Trellis"):
@property
def required_tools(self):
if self.toolchain == "Trellis":
return ["yosys", "nextpnr-ecp5", "ecppack"]
return self._trellis_required_tools
if self.toolchain == "Diamond":
return ["pnmainc", "ddtcmd"]
return self._diamond_required_tools
assert False

@property
223 changes: 207 additions & 16 deletions nmigen/vendor/lattice_ice40.py
Original file line number Diff line number Diff line change
@@ -9,13 +9,16 @@

class LatticeICE40Platform(TemplatedPlatform):
"""
IceStorm toolchain
------------------
Required tools:
* ``yosys``
* ``nextpnr-ice40``
* ``icepack``
The environment is populated by running the script specified in the environment variable
``NMIGEN_IceStorm_env``, if present.
``NMIGEN_ENV_IceStorm``, if present.
Available overrides:
* ``verbose``: enables logging of informational messages to standard error.
@@ -25,25 +28,51 @@ class LatticeICE40Platform(TemplatedPlatform):
* ``script_after_synth``: inserts commands after ``synth_ice40`` in Yosys script.
* ``yosys_opts``: adds extra options for ``yosys``.
* ``nextpnr_opts``: adds extra options for ``nextpnr-ice40``.
* ``add_pre_pack``: inserts commands at the end in pre-pack Python script.
* ``add_constraints``: inserts commands at the end in the PCF file.
Build products:
* ``{{name}}.rpt``: Yosys log.
* ``{{name}}.json``: synthesized RTL.
* ``{{name}}.tim``: nextpnr log.
* ``{{name}}.asc``: ASCII bitstream.
* ``{{name}}.bin``: binary bitstream.
iCECube2 toolchain
------------------
This toolchain comes in two variants: ``LSE-iCECube2`` and ``Synplify-iCECube2``.
Required tools:
* iCECube2 toolchain
* ``tclsh``
The environment is populated by setting the necessary environment variables based on
``NMIGEN_ENV_iCECube2``, which must point to the root of the iCECube2 installation, and
is required.
Available overrides:
* ``verbose``: enables logging of informational messages to standard error.
* ``lse_opts``: adds options for LSE.
* ``script_after_add``: inserts commands after ``add_file`` in Synplify Tcl script.
* ``script_after_options``: inserts commands after ``set_option`` in Synplify Tcl script.
* ``add_constraints``: inserts commands in SDC file.
* ``script_after_flow``: inserts commands after ``run_sbt_backend_auto`` in SBT
Tcl script.
Build products:
* ``{{name}}_lse.log`` (LSE) or ``{{name}}_design/{{name}}.htm`` (Synplify): synthesis log.
* ``sbt/outputs/router/{{name}}_timing.rpt``: timing report.
* ``{{name}}.edf``: EDIF netlist.
* ``{{name}}.bin``: binary bitstream.
"""

toolchain = "IceStorm"
toolchain = None # selected when creating platform

device = abstractproperty()
package = abstractproperty()

required_tools = [
"yosys",
"nextpnr-ice40",
"icepack",
]
# IceStorm templates

_nextpnr_device_options = {
"iCE40LP384": "--lp384",
@@ -67,7 +96,12 @@ class LatticeICE40Platform(TemplatedPlatform):
"iCE5LP1K": "",
}

file_templates = {
_icestorm_required_tools = [
"yosys",
"nextpnr-ice40",
"icepack",
]
_icestorm_file_templates = {
**TemplatedPlatform.build_script_templates,
"{{name}}.il": r"""
# {{autogenerated}}
@@ -87,21 +121,23 @@ class LatticeICE40Platform(TemplatedPlatform):
{{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
write_json {{name}}.json
""",
"{{name}}.pcf": r"""
# {{autogenerated}}
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
set_io {{port_name}} {{pin_name}}
{% endfor %}
""",
"{{name}}_pre_pack.py": r"""
# {{autogenerated}}
{% for signal, frequency in platform.iter_clock_constraints() -%}
{# Clock in MHz #}
ctx.addClock("{{signal|hierarchy(".")}}", {{frequency/1000000}})
{% endfor%}
{{get_override("add_pre_pack")|default("# (add_pre_pack placeholder)")}}
""",
"{{name}}.pcf": r"""
# {{autogenerated}}
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
set_io {{port_name}} {{pin_name}}
{% endfor %}
{{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
""",
}
command_templates = [
_icestorm_command_templates = [
r"""
{{get_tool("yosys")}}
{{quiet("-q")}}
@@ -130,6 +166,161 @@ class LatticeICE40Platform(TemplatedPlatform):
"""
]

# iCECube2 templates

_icecube2_required_tools = [
"tclsh",
]
_icecube2_file_templates = {
**TemplatedPlatform.build_script_templates,
"build_{{name}}.sh": r"""
# {{autogenerated}}
set -e{{verbose("x")}}
if [ -n "${{platform._toolchain_env_var}}" ]; then
# LSE environment
export LD_LIBRARY_PATH=${{platform._toolchain_env_var}}/LSE/bin/lin64:$LD_LIBRARY_PATH
export PATH=${{platform._toolchain_env_var}}/LSE/bin/lin64:$PATH
export FOUNDRY=${{platform._toolchain_env_var}}/LSE
# Synplify environment
export LD_LIBRARY_PATH=${{platform._toolchain_env_var}}/sbt_backend/bin/linux/opt/synpwrap:$LD_LIBRARY_PATH
export PATH=${{platform._toolchain_env_var}}/sbt_backend/bin/linux/opt/synpwrap:$PATH
export SYNPLIFY_PATH=${{platform._toolchain_env_var}}/synpbase
# Common environment
export SBT_DIR=${{platform._toolchain_env_var}}/sbt_backend
else
echo "Variable ${{platform._toolchain_env_var}} must be set" >&2; exit 1
fi
{{emit_commands("sh")}}
""",
"{{name}}.v": r"""
/* {{autogenerated}} */
{{emit_design("verilog")}}
""",
"{{name}}_lse.prj": r"""
# {{autogenerated}}
-a SBT{{platform.family}}
-d {{platform.device}}
-t {{platform.package}}
{{get_override("lse_opts")|options|default("# (lse_opts placeholder)")}}
{% for file in platform.iter_extra_files(".v") -%}
-ver {{file}}
{% endfor %}
-ver {{name}}.v
-sdc {{name}}.sdc
-top {{name}}
-output_edif {{name}}.edf
-logfile {{name}}_lse.log
""",
"{{name}}_syn.prj": r"""
# {{autogenerated}}
{% for file in platform.iter_extra_files(".v", ".sv", ".vhd", ".vhdl") -%}
add_file -verilog {{file}}
{% endfor %}
add_file -verilog {{name}}.v
add_file -constraint {{name}}.sdc
{{get_override("script_after_add")|default("# (script_after_add placeholder)")}}
impl -add {{name}}_design -type fpga
set_option -technology SBT{{platform.family}}
set_option -part {{platform.device}}
set_option -package {{platform.package}}
{{get_override("script_after_options")|default("# (script_after_options placeholder)")}}
project -result_format edif
project -result_file {{name}}.edf
impl -active {{name}}_design
project -run compile
project -run map
project -run fpga_mapper
file copy -force -- {{name}}_design/{{name}}.edf {{name}}.edf
""",
"{{name}}.sdc": r"""
# {{autogenerated}}
{% for signal, frequency in platform.iter_clock_constraints() -%}
create_clock -name {{signal.name}} -period {{1000000000/frequency}} [get_nets {{signal|hierarchy("/")}}]
{% endfor %}
{{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
""",
"{{name}}.tcl": r"""
# {{autogenerated}}
set device {{platform.device}}-{{platform.package}}
set top_module {{name}}
set proj_dir .
set output_dir .
set edif_file {{name}}
set tool_options ":edifparser -y {{name}}.pcf"
set sbt_root $::env(SBT_DIR)
append sbt_tcl $sbt_root "/tcl/sbt_backend_synpl.tcl"
source $sbt_tcl
run_sbt_backend_auto $device $top_module $proj_dir $output_dir $tool_options $edif_file
{{get_override("script_after_file")|default("# (script_after_file placeholder)")}}
file copy -force -- sbt/outputs/bitmap/{{name}}_bitmap.bin {{name}}.bin
exit
""",
"{{name}}.pcf": r"""
# {{autogenerated}}
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
set_io {{port_name}} {{pin_name}}
{% endfor %}
""",
}
_lse_icecube2_command_templates = [
r"""synthesis -f {{name}}_lse.prj""",
r"""tclsh {{name}}.tcl""",
]
_synplify_icecube2_command_templates = [
r"""synpwrap -prj {{name}}_syn.prj -log {{name}}_syn.log""",
r"""tclsh {{name}}.tcl""",
]

# Common logic

def __init__(self, *, toolchain="IceStorm"):
super().__init__()

assert toolchain in ("IceStorm", "LSE-iCECube2", "Synplify-iCECube2")
self.toolchain = toolchain

@property
def family(self):
if self.device.startswith("iCE40"):
return "iCE40"
if self.device.startswith("iCE5"):
return "iCE5"
assert False

@property
def _toolchain_env_var(self):
if self.toolchain == "IceStorm":
return f"NMIGEN_ENV_{self.toolchain}"
if self.toolchain in ("LSE-iCECube2", "Synplify-iCECube2"):
return f"NMIGEN_ENV_iCECube2"
assert False

@property
def required_tools(self):
if self.toolchain == "IceStorm":
return self._icestorm_required_tools
if self.toolchain in ("LSE-iCECube2", "Synplify-iCECube2"):
return self._icecube2_required_tools
assert False

@property
def file_templates(self):
if self.toolchain == "IceStorm":
return self._icestorm_file_templates
if self.toolchain in ("LSE-iCECube2", "Synplify-iCECube2"):
return self._icecube2_file_templates
assert False

@property
def command_templates(self):
if self.toolchain == "IceStorm":
return self._icestorm_command_templates
if self.toolchain == "LSE-iCECube2":
return self._lse_icecube2_command_templates
if self.toolchain == "Synplify-iCECube2":
return self._synplify_icecube2_command_templates
assert False

def create_missing_domain(self, name):
# For unknown reasons (no errata was ever published, and no documentation mentions this
# issue), iCE40 BRAMs read as zeroes for ~3 us after configuration and release of internal
@@ -287,7 +478,7 @@ def get_oneg(a, invert):
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))
io_args.append(("p", "PIN_TYPE", C((o_type << 2) | i_type, 6)))

if hasattr(pin, "i_clk"):
io_args.append(("i", "INPUT_CLK", pin.i_clk))
2 changes: 1 addition & 1 deletion nmigen/vendor/xilinx_7series.py
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ class Xilinx7SeriesPlatform(TemplatedPlatform):
* ``vivado``
The environment is populated by running the script specified in the environment variable
``NMIGEN_Vivado_env``, if present.
``NMIGEN_ENV_Vivado``, if present.
Available overrides:
* ``script_after_read``: inserts commands after ``read_xdc`` in Tcl script.
2 changes: 1 addition & 1 deletion nmigen/vendor/xilinx_spartan_3_6.py
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ class XilinxSpartan3Or6Platform(TemplatedPlatform):
* ``bitgen``
The environment is populated by running the script specified in the environment variable
``NMIGEN_ISE_env``, if present.
``NMIGEN_ENV_ISE``, if present.
Available overrides:
* ``script_after_run``: inserts commands after ``run`` in XST script.