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: f17375a60b8a
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: b1eab9fb3bdb
Choose a head ref
  • 3 commits
  • 6 files changed
  • 1 contributor

Commits on Jun 1, 2019

  1. Copy the full SHA
    8c1b5a2 View commit details
  2. build.res: always return a Pin record.

    In the simple cases, a Pin record consisting of exactly one field
    is equivalent in every way to this single field. In the more complex
    case however, it can be used as a record, making the code more robust
    such that it works with both bidirectional and unidirectional pins.
    whitequark committed Jun 1, 2019
    Copy the full SHA
    53ddff9 View commit details
  3. build.plat: implement.

    whitequark committed Jun 1, 2019
    Copy the full SHA
    b1eab9f View commit details
Showing with 337 additions and 26 deletions.
  1. +1 −0 .gitignore
  2. +2 −0 nmigen/build/__init__.py
  3. +1 −1 nmigen/build/dsl.py
  4. +292 −0 nmigen/build/plat.py
  5. +20 −17 nmigen/build/res.py
  6. +21 −8 nmigen/test/test_build_res.py
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -7,3 +7,4 @@
**/test/spec_*/
/.coverage
/htmlcov
/build
2 changes: 2 additions & 0 deletions nmigen/build/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .dsl import Pins, DiffPairs, Subsignal, Resource
from .plat import Platform, TemplatedPlatform
2 changes: 1 addition & 1 deletion nmigen/build/dsl.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__all__ = ["Pins", "Subsignal", "DiffPairs", "Resource"]
__all__ = ["Pins", "DiffPairs", "Subsignal", "Resource"]


class Pins:
292 changes: 292 additions & 0 deletions nmigen/build/plat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
from collections import OrderedDict
from abc import ABCMeta, abstractmethod, abstractproperty
import os
import sys
import subprocess
import textwrap
import re
import zipfile
import jinja2

from .. import __version__
from ..hdl.ast import *
from ..hdl.dsl import *
from ..hdl.ir import *
from ..back import rtlil, verilog
from .res import ConstraintManager


__all__ = ["Platform", "TemplatedPlatform"]


class BuildPlan:
def __init__(self, script):
self.script = script
self.files = OrderedDict()

def add_file(self, filename, content):
assert isinstance(filename, str) and filename not in self.files
# Just to make sure we don't accidentally overwrite anything.
assert not os.path.normpath(filename).startswith("..")
self.files[filename] = content

def execute(self, root="build", run_script=True):
os.makedirs(root, exist_ok=True)
cwd = os.getcwd()
try:
os.chdir(root)

for filename, content in self.files.items():
dirname = os.path.dirname(filename)
if dirname:
os.makedirs(dirname, exist_ok=True)

mode = "wt" if isinstance(content, str) else "wb"
with open(filename, mode) as f:
f.write(content)

if run_script:
if sys.platform.startswith("win32"):
subprocess.run(["cmd", "/c", "{}.bat".format(self.script)], check=True)
else:
subprocess.run(["sh", "{}.sh".format(self.script)], check=True)

return BuildProducts(os.getcwd())

finally:
os.chdir(cwd)

def archive(self, file):
with zipfile.ZipFile(file, "w") as archive:
# Write archive members in deterministic order and with deterministic timestamp.
for filename in sorted(self.files):
archive.writestr(zipfile.ZipInfo(filename), self.files[filename])


class BuildProducts:
def __init__(self, root):
self._root = root

def get(self, filename, mode="b"):
assert mode in "bt"
with open(os.path.join(self._root, filename), "r" + mode) as f:
return f.read()


class Platform(ConstraintManager, metaclass=ABCMeta):
resources = abstractproperty()
clocks = abstractproperty()

def __init__(self):
super().__init__(self.resources, self.clocks)

self.extra_files = OrderedDict()

self._prepared = False

def add_file(self, filename, content):
if not isinstance(filename, str):
raise TypeError("File name must be a string")
if filename in self.extra_files:
raise ValueError("File {} already exists"
.format(filename))
if hasattr(content, "read"):
content = content.read()
elif not isinstance(content, (str, bytes)):
raise TypeError("File contents must be str, bytes, or a file-like object")
self.extra_files[filename] = content

def build(self, fragment, name="top",
build_dir="build", do_build=True,
program_opts=None, do_program=False,
**kwargs):
plan = self.prepare(fragment, name, **kwargs)
if not do_build:
return plan

products = plan.execute(build_dir)
if not do_program:
return products

self.toolchain_program(products, name, **(program_opts or {}))

def prepare(self, fragment, name="top", **kwargs):
assert not self._prepared
self._prepared = True

fragment = Fragment.get(fragment, self)

pin_fragments = []
for pin, port in self._se_pins:
if pin.dir == "i":
pin_fragments.append((pin.name, self.get_input(pin, port)))
if pin.dir == "o":
pin_fragments.append((pin.name, self.get_output(pin, port)))
if pin.dir == "io":
pin_fragments.append((pin.name, self.get_tristate(pin, port)))
for pin, p_port, n_port in self._dp_pins:
if pin.dir == "i":
pin_fragments.append((pin.name, self.get_diff_input(pin, p_port, n_port)))
if pin.dir == "o":
pin_fragments.append((pin.name, self.get_diff_output(pin, p_port, n_port)))
if pin.dir == "io":
pin_fragments.append((pin.name, self.get_diff_tristate(pin, p_port, n_port)))

for pin_name, pin_fragment in pin_fragments:
pin_fragment = Fragment.get(pin_fragment, self)
if not isinstance(pin_fragment, Instance):
pin_fragment.flatten = True
fragment.add_subfragment(pin_fragment, name="pin_{}".format(pin_name))

return self.toolchain_prepare(fragment, name, **kwargs)

@abstractmethod
def toolchain_prepare(self, fragment, name, **kwargs):
"""
Convert the ``fragment`` and constraints recorded in this :class:`Platform` into
a :class:`BuildPlan`.
"""
raise NotImplementedError # :nocov:

def toolchain_program(self, products, name, **kwargs):
"""
Extract bitstream for fragment ``name`` from ``products`` and download it to a target.
"""
raise NotImplementedError("Platform {} does not support programming"
.format(self.__class__.__name__))

def _check_feature(self, feature, pin, xdrs):
if not xdrs:
raise NotImplementedError("Platform {} does not support {}"
.format(self.__class__.__name__, feature))
elif pin.xdr not in xdrs:
raise NotImplementedError("Platform {} does not support {} for XDR {}"
.format(self.__class__.__name__, feature, pin.xdr))

def get_input(self, pin, port):
self._check_feature("single-ended input", pin, xdrs=(1,))

m = Module()
m.d.comb += pin.i.eq(port)
return m

def get_output(self, pin, port):
self._check_feature("single-ended output", pin, xdrs=(1,))

m = Module()
m.d.comb += port.eq(pin.o)
return m

def get_tristate(self, pin, port):
self._check_feature("single-ended tristate", pin, xdrs=(1,))

m = Module()
m.submodules += Instance("$tribuf",
p_WIDTH=pin.width,
i_EN=pin.oe,
i_A=pin.o,
o_Y=port,
)
m.d.comb += pin.i.eq(port)
return m

def get_diff_input(self, pin, p_port, n_port):
self._check_feature("differential input", pin, xdrs=())

def get_diff_output(self, pin, p_port, n_port):
self._check_feature("differential output", pin, xdrs=())

def get_diff_tristate(self, pin, p_port, n_port):
self._check_feature("differential tristate", pin, xdrs=())


class TemplatedPlatform(Platform):
file_templates = abstractproperty()
command_templates = abstractproperty()

build_script_templates = {
"build_{{name}}.sh": """
# {{autogenerated}}
set -e{{verbose("x")}}
{{emit_commands("sh")}}
""",
"build_{{name}}.bat": """
@rem {{autogenerated}}
{{emit_commands("bat")}}
""",
}

def toolchain_prepare(self, fragment, name, **kwargs):
# This notice serves a dual purpose: to explain that the file is autogenerated,
# and to incorporate
autogenerated = "Automatically generated by nMigen {}. Do not edit.".format(__version__)

def emit_design(backend):
return {"rtlil": rtlil, "verilog": verilog}[backend].convert(
fragment, name=name, platform=self, ports=list(self.iter_ports()),
ensure_sync_exists=False)

def emit_commands(format):
commands = []
for index, command_tpl in enumerate(self.command_templates):
command = render(command_tpl, origin="<command#{}>".format(index + 1))
command = re.sub(r"\s+", " ", command)
if format == "sh":
commands.append(command)
elif format == "bat":
commands.append(command + " || exit /b")
else:
assert False
return "\n".join(commands)

def get_tool(tool):
tool_env = tool.upper().replace("-", "_")
return os.environ.get(tool_env, tool)

def get_override(var):
var_env = "NMIGEN_{}".format(var)
if var_env in os.environ:
return os.environ[var_env]
elif var in kwargs:
return kwargs[var]
else:
return jinja2.Undefined(name=var)

def verbose(arg):
if "NMIGEN_verbose" in os.environ:
return arg
else:
return jinja2.Undefined(name="quiet")

def quiet(arg):
if "NMIGEN_verbose" in os.environ:
return jinja2.Undefined(name="quiet")
else:
return arg

def render(source, origin):
try:
source = textwrap.dedent(source).strip()
compiled = jinja2.Template(source, trim_blocks=True, lstrip_blocks=True)
except jinja2.TemplateSyntaxError as e:
e.args = ("{} (at {}:{})".format(e.message, origin, e.lineno),)
raise
return compiled.render({
"name": name,
"platform": self,
"emit_design": emit_design,
"emit_commands": emit_commands,
"get_tool": get_tool,
"get_override": get_override,
"verbose": verbose,
"quiet": quiet,
"autogenerated": autogenerated,
})

plan = BuildPlan(script="build_{}".format(name))
for filename_tpl, content_tpl in self.file_templates.items():
plan.add_file(render(filename_tpl, origin=filename_tpl),
render(content_tpl, origin=filename_tpl))
for filename, content in self.extra_files.items():
plan.add_file(filename, content)
return plan
37 changes: 20 additions & 17 deletions nmigen/build/res.py
Original file line number Diff line number Diff line change
@@ -15,16 +15,20 @@ class ConstraintError(Exception):


class ConstraintManager:
def __init__(self, resources):
def __init__(self, resources, clocks):
self.resources = OrderedDict()
self.requested = OrderedDict()
self.clocks = OrderedDict()

self._ports = []
self._tristates = []
self._diffpairs = []
self._se_pins = []
self._dp_pins = []

self.add_resources(resources)
for name_number, frequency in clocks:
if not isinstance(name_number, tuple):
name_number = (name_number, 0)
self.add_clock(*name_number, frequency)

def add_resources(self, resources):
for r in resources:
@@ -129,19 +133,16 @@ def match_constraints(value, subsignal):
yield (value, subsignal.io[0], subsignal.extras)

for (pin, io, extras) in match_constraints(value, resource):
if isinstance(io, DiffPairs):
p = Signal(pin.width, name="{}_p".format(pin.name))
n = Signal(pin.width, name="{}_n".format(pin.name))
self._diffpairs.append((pin, p, n))
self._ports.append((p, io.p.names, extras))
self._ports.append((n, io.n.names, extras))
elif isinstance(io, Pins):
if pin.dir == "io":
port = Signal(pin.width, name="{}_io".format(pin.name))
self._tristates.append((pin, port))
else:
port = getattr(pin, pin.dir)
if isinstance(io, Pins):
port = Signal(pin.width, name="{}_io".format(pin.name))
self._se_pins.append((pin, port))
self._ports.append((port, io.names, extras))
elif isinstance(io, DiffPairs):
p_port = Signal(pin.width, name="{}_p".format(pin.name))
n_port = Signal(pin.width, name="{}_n".format(pin.name))
self._dp_pins.append((pin, p_port, n_port))
self._ports.append((p_port, io.p.names, extras))
self._ports.append((n_port, io.n.names, extras))
else:
assert False # :nocov:

@@ -165,8 +166,10 @@ def iter_clock_constraints(self):
raise ConstraintError("Cannot constrain frequency of resource {}#{} because "
"it has been requested as a tristate buffer"
.format(name, number))
if isinstance(resource.io[0], DiffPairs):
if isinstance(resource.io[0], Pins):
port_name = "{}_io".format(pin.name)
elif isinstance(resource.io[0], DiffPairs):
port_name = "{}_p".format(pin.name)
else:
port_name = getattr(pin, pin.dir).name
assert False
yield (port_name, period)
Loading