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: 376180f21e9a
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: c8e6ec2ac703
Choose a head ref

Commits on Aug 21, 2019

  1. vendor.xilinx_series7: use STARTUPE2, not STARTUPE3.

    STARTUPE3 is for Ultrascale.
    dlharmon authored and whitequark committed Aug 21, 2019
    Copy the full SHA
    6737ef7 View commit details

Commits on Aug 22, 2019

  1. back.rtlil: add support for real (float) parameters on Instances.

    Required for Xilinx MMCME2_BASE, etc.
    dlharmon authored and whitequark committed Aug 22, 2019
    Copy the full SHA
    aefde85 View commit details
  2. Copy the full SHA
    47bad3d View commit details
  3. examples/basic/uart: document divisor parameter.

    ret authored and whitequark committed Aug 22, 2019
    Copy the full SHA
    b0ef53e View commit details
  4. vendor: eliminate unnecessary LUT instantiation.

    Fixes #165.
    whitequark committed Aug 22, 2019
    Copy the full SHA
    c77274c View commit details
  5. vendor.lattice_ecp5: add Diamond support.

    whitequark committed Aug 22, 2019
    Copy the full SHA
    7fc1058 View commit details

Commits on Aug 23, 2019

  1. build.run: add BuildPlan.digest(), useful for caching.

    whitequark committed Aug 23, 2019
    Copy the full SHA
    9350620 View commit details
  2. Copy the full SHA
    906385c View commit details
  3. back.pysim: implement sim.add_clock(if_exists=True).

    whitequark committed Aug 23, 2019
    Copy the full SHA
    72cf4ca View commit details

Commits on Aug 25, 2019

  1. vendor.lattice_ecp5: revert default toolchain to Trellis.

    This was unintentionally changed in 7fc1058.
    whitequark committed Aug 25, 2019
    Copy the full SHA
    b4b5d9e View commit details

Commits on Aug 26, 2019

  1. back.verilog: bump Yosys version requirement to 0.9.

    Fixes #55.
    whitequark committed Aug 26, 2019
    Copy the full SHA
    2168ff5 View commit details

Commits on Aug 28, 2019

  1. Copy the full SHA
    b14f557 View commit details
  2. test.tools: use _toolchain.get_tool.

    emilazy authored and whitequark committed Aug 28, 2019
    Copy the full SHA
    98278a0 View commit details

Commits on Aug 30, 2019

  1. build.dsl: allow both str and int resource attributes.

    whitequark committed Aug 30, 2019
    Copy the full SHA
    a4b58cb View commit details
  2. vendor.lattice_ecp5: drive GSR synchronous to user clock by default.

    Fixes #167.
    whitequark committed Aug 30, 2019
    Copy the full SHA
    4e91710 View commit details

Commits on Aug 31, 2019

  1. Copy the full SHA
    c4e8ac7 View commit details
  2. hdl.cd: add negedge clock domains.

    Fixes #185.
    whitequark committed Aug 31, 2019
    Copy the full SHA
    2e20622 View commit details

Commits on Sep 3, 2019

  1. hdl.ast,back.rtlil: implement Cover.

    Fixes #194.
    whitequark committed Sep 3, 2019
    Copy the full SHA
    943ce31 View commit details

Commits on Sep 6, 2019

  1. setup: replace versioneer with setuptools_scm.

    Has the same problems with git-archive but is much less invasive.
    whitequark committed Sep 6, 2019
    Copy the full SHA
    284b533 View commit details
  2. Fix .gitignore.

    whitequark committed Sep 6, 2019
    Copy the full SHA
    38831ab View commit details
  3. Remove nmigen.lib from prelude.

    Currently it's just MultiReg, and there's no particularly good reason
    to privilege this specific CDC primitive so much.
    whitequark committed Sep 6, 2019
    Copy the full SHA
    5e9587b View commit details

Commits on Sep 8, 2019

  1. Copy the full SHA
    ccfbccc View commit details
  2. hdl.mem,lib,examples: use Signal.range().

    whitequark committed Sep 8, 2019
    Copy the full SHA
    eb04a25 View commit details
  3. hdl.dsl: add Default(), an alias for Case() with no arguments.

    Fixes #197.
    whitequark committed Sep 8, 2019
    Copy the full SHA
    3f6abc0 View commit details
  4. hdl.ast: check type of Sample(domain=...).

    Fixes #199.
    whitequark committed Sep 8, 2019
    Copy the full SHA
    9b398b5 View commit details

Commits on Sep 10, 2019

  1. vendor.lattice_ecp5: pass ecppack_opts to ecppack.

    dlharmon authored and whitequark committed Sep 10, 2019
    Copy the full SHA
    27cedf4 View commit details
  2. hdl.ast: warn if reset value is truncated.

    Fixes #183.
    whitequark committed Sep 10, 2019
    Copy the full SHA
    7342662 View commit details

Commits on Sep 11, 2019

  1. back: return name map from convert_fragment().

    whitequark committed Sep 11, 2019
    Copy the full SHA
    d1779bd View commit details
  2. build.plat,vendor: allow clock constraints on arbitrary signals.

    Currently only done for Synopsys based toolchains (i.e. not nextpnr).
    
    Refs #88.
    whitequark committed Sep 11, 2019
    Copy the full SHA
    8c30147 View commit details

Commits on Sep 12, 2019

  1. lib.cdc: adjust MultiReg for new CDC primitive conventions.

    Refs #97.
    whitequark committed Sep 12, 2019
    Copy the full SHA
    8f659b6 View commit details
  2. Copy the full SHA
    9893e3c View commit details
  3. lib.io: style. NFC.

    whitequark committed Sep 12, 2019
    Copy the full SHA
    73244f2 View commit details
  4. lib.cdc: make domain properties private.

    It is not correct to access domain properties from user code, because
    it will not match the reality if DomainRenamer has been applied to
    the module.
    whitequark committed Sep 12, 2019
    Copy the full SHA
    2d2ab6e View commit details
  5. README: update Yosys version requirement.

    whitequark committed Sep 12, 2019
    Copy the full SHA
    2c34b1f View commit details
  6. lib.fifo: remove SyncFIFO.replace.

    This obscure functionality was likely only ever used in old MiSoC
    code, and doesn't justify the added complexity. It was also not
    provided (and could not be reasonably provided) in SyncFIFOBuffered,
    which made its utility extremely marginal.
    whitequark committed Sep 12, 2019
    Copy the full SHA
    1c091e6 View commit details
  7. lib.fifo: make fwft a keyword-only argument.

    Because it accepts a boolean.
    whitequark committed Sep 12, 2019
    Copy the full SHA
    b92e967 View commit details
  8. hdl.mem: use keyword-only arguments as appropriate.

    whitequark committed Sep 12, 2019
    Copy the full SHA
    42805ad View commit details
  9. Copy the full SHA
    c8f8c09 View commit details
  10. build.plat: bypass tool detection if NMIGEN_*_env is set.

    It's not practical to detect tools within the toolchain environment
    for various reasons, so just assume the tools are there if the user
    says they are.
    
    Before this commit, the tools would be searched outside the toolchain
    environment, which of course would always fail for Vivado, ISE, etc.
    whitequark committed Sep 12, 2019
    Copy the full SHA
    9ea3ff7 View commit details

Commits on Sep 13, 2019

  1. lib.fifo: adjust properties to have consistent naming.

    whitequark committed Sep 13, 2019
    Copy the full SHA
    da4b810 View commit details
  2. lib.fifo: adjust for new CDC primitive conventions.

    Fixes #97.
    whitequark committed Sep 13, 2019
    Copy the full SHA
    bdb70ad View commit details
  3. hdl.ast: add Value.{any,all}, mapping to $reduce_{or,and}.

    Refs #147.
    whitequark committed Sep 13, 2019
    Copy the full SHA
    b23a979 View commit details
  4. hdl.ast: add Value.xor, mapping to $reduce_xor.

    Fixes #147.
    whitequark committed Sep 13, 2019
    Copy the full SHA
    32310ae View commit details

Commits on Sep 14, 2019

  1. hdl.dsl: improve error messages for Case().

    whitequark committed Sep 14, 2019
    Copy the full SHA
    f292a19 View commit details
  2. hdl.ast: add Value.matches(), accepting same language as Case().

    Fixes #202.
    whitequark committed Sep 14, 2019
    Copy the full SHA
    e8f79c5 View commit details

Commits on Sep 16, 2019

  1. hdl.{ast,dsl}: add Signal.enum; coerce Enum to Value; accept Enum pat…

    …terns.
    
    Fixes #207.
    whitequark committed Sep 16, 2019
    Copy the full SHA
    4777a7b View commit details

Commits on Sep 20, 2019

  1. back.pysim: fix simulation of Value.xor().

    whitequark committed Sep 20, 2019
    Copy the full SHA
    7f6b3f9 View commit details
  2. test.test_lib_fifo: fix typo.

    whitequark committed Sep 20, 2019
    Copy the full SHA
    276e9c2 View commit details
  3. setup: add setuptools dependency.

    emilazy authored and whitequark committed Sep 20, 2019
    Copy the full SHA
    6f12272 View commit details
  4. setup: improve repository detection.

    emilazy authored and whitequark committed Sep 20, 2019
    Copy the full SHA
    f255002 View commit details
Showing with 1,840 additions and 3,094 deletions.
  1. +0 −1 .gitattributes
  2. +14 −6 .gitignore
  3. +3 −1 README.md
  4. +1 −0 examples/basic/cdc.py
  5. +1 −1 examples/basic/fsm.py
  6. +1 −1 examples/basic/por.py
  7. +11 −4 examples/basic/uart.py
  8. +5 −4 nmigen/__init__.py
  9. +46 −0 nmigen/_toolchain.py
  10. +0 −520 nmigen/_version.py
  11. +1 −1 nmigen/asserts.py
  12. +25 −3 nmigen/back/pysim.py
  13. +45 −35 nmigen/back/rtlil.py
  14. +12 −18 nmigen/back/verilog.py
  15. +4 −5 nmigen/build/dsl.py
  16. +29 −11 nmigen/build/plat.py
  17. +3 −27 nmigen/build/res.py
  18. +16 −0 nmigen/build/run.py
  19. +14 −1 nmigen/compat/genlib/cdc.py
  20. +1 −1 nmigen/compat/genlib/resetsync.py
  21. +237 −65 nmigen/hdl/ast.py
  22. +9 −1 nmigen/hdl/cd.py
  23. +31 −19 nmigen/hdl/dsl.py
  24. +15 −15 nmigen/hdl/mem.py
  25. +31 −13 nmigen/hdl/xfrm.py
  26. +0 −1 nmigen/lib/__init__.py
  27. +12 −12 nmigen/lib/cdc.py
  28. +6 −6 nmigen/lib/coding.py
  29. +178 −129 nmigen/lib/fifo.py
  30. +2 −1 nmigen/lib/io.py
  31. +2 −1 nmigen/test/compat/support.py
  32. +0 −18 nmigen/test/compat/test_fifo.py
  33. +6 −6 nmigen/test/test_build_dsl.py
  34. +10 −23 nmigen/test/test_build_res.py
  35. +157 −28 nmigen/test/test_hdl_ast.py
  36. +13 −0 nmigen/test/test_hdl_cd.py
  37. +56 −5 nmigen/test/test_hdl_dsl.py
  38. +5 −4 nmigen/test/test_hdl_mem.py
  39. +74 −90 nmigen/test/test_lib_fifo.py
  40. +36 −4 nmigen/test/test_sim.py
  41. +2 −1 nmigen/test/tools.py
  42. +165 −0 nmigen/vendor/altera.py
  43. +212 −52 nmigen/vendor/lattice_ecp5.py
  44. +241 −36 nmigen/vendor/lattice_ice40.py
  45. +49 −44 nmigen/vendor/xilinx_7series.py
  46. +47 −43 nmigen/vendor/xilinx_spartan_3_6.py
  47. +0 −7 setup.cfg
  48. +12 −8 setup.py
  49. +0 −1,822 versioneer.py
1 change: 0 additions & 1 deletion .gitattributes

This file was deleted.

20 changes: 14 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
# Python
*.pyc
*.egg-info
*.il
*.v
*.vcd
*.gtkw
**/test/spec_*/
/*.egg-info
/.eggs

# coverage
/.coverage
/htmlcov

# tests
**/test/spec_*/
*.vcd
*.gtkw

# misc user-created
*.il
*.v
/build
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ See the [doc/](doc/) folder for more technical information.

nMigen is a direct descendant of [Migen][] rewritten from scratch to address many issues that became clear in the many years Migen has been used in production. nMigen provides an extensive compatibility layer that makes it possible to build and simulate most Migen designs unmodified, as well as integrate modules written for Migen and nMigen.

nMigen is designed for Python 3.6 and newer. nMigen's Verilog backend depends on [Yosys][]; currently, the `master` branch of Yosys is required.
nMigen is designed for Python 3.6 and newer. nMigen's Verilog backend requires [Yosys][] 0.9 or a newer version.

Thanks [LambdaConcept][] for being a sponsor of this project! Contact sb [at] m-labs.hk if you also wish to support this work.

@@ -28,6 +28,8 @@ nMigen is *not* a "Python-to-FPGA" conventional high level synthesis (HLS) tool.

### Installation

nMigen requires [Yosys][] 0.9 or newer, as well as a device-specific toolchain.

pip install git+https://github.com/m-labs/nmigen.git
pip install git+https://github.com/m-labs/nmigen-boards.git

1 change: 1 addition & 0 deletions examples/basic/cdc.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from nmigen import *
from nmigen.lib.cdc import MultiReg
from nmigen.cli import main


2 changes: 1 addition & 1 deletion examples/basic/fsm.py
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ def __init__(self, divisor):
def elaborate(self, platform):
m = Module()

ctr = Signal(max=self.divisor)
ctr = Signal.range(self.divisor)
stb = Signal()
with m.If(ctr == 0):
m.d.sync += ctr.eq(self.divisor - 1)
2 changes: 1 addition & 1 deletion examples/basic/por.py
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
cd_sync = ClockDomain()
m.domains += cd_por, cd_sync

delay = Signal(max=255, reset=255)
delay = Signal.range(256, reset=255)
with m.If(delay != 0):
m.d.por += delay.eq(delay - 1)
m.d.comb += [
15 changes: 11 additions & 4 deletions examples/basic/uart.py
Original file line number Diff line number Diff line change
@@ -2,6 +2,13 @@


class UART(Elaboratable):
"""
Parameters
----------
divisor : int
Set to ``round(clk-rate / baud-rate)``.
E.g. ``12e6 / 115200`` = ``104``.
"""
def __init__(self, divisor, data_bits=8):
assert divisor >= 4

@@ -24,9 +31,9 @@ def __init__(self, divisor, data_bits=8):
def elaborate(self, platform):
m = Module()

tx_phase = Signal(max=self.divisor)
tx_phase = Signal.range(self.divisor)
tx_shreg = Signal(1 + self.data_bits + 1, reset=-1)
tx_count = Signal(max=len(tx_shreg) + 1)
tx_count = Signal.range(len(tx_shreg) + 1)

m.d.comb += self.tx_o.eq(tx_shreg[0])
with m.If(tx_count == 0):
@@ -47,9 +54,9 @@ def elaborate(self, platform):
tx_phase.eq(self.divisor - 1),
]

rx_phase = Signal(max=self.divisor)
rx_phase = Signal.range(self.divisor)
rx_shreg = Signal(1 + self.data_bits + 1, reset=-1)
rx_count = Signal(max=len(rx_shreg) + 1)
rx_count = Signal.range(len(rx_shreg) + 1)

m.d.comb += self.rx_data.eq(rx_shreg[1:-1])
with m.If(rx_count == 0):
9 changes: 5 additions & 4 deletions nmigen/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from ._version import get_versions
__version__ = get_versions()["full-revisionid"]
del get_versions
import pkg_resources
try:
__version__ = pkg_resources.get_distribution(__name__).version
except pkg_resources.DistributionNotFound:
pass

from .hdl import *
from .lib import *
46 changes: 46 additions & 0 deletions nmigen/_toolchain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import os
import shutil


__all__ = ["ToolNotFound", "get_tool", "has_tool", "require_tool"]


class ToolNotFound(Exception):
pass


def _tool_env_var(name):
return name.upper().replace("-", "_")


def get_tool(name):
return os.environ.get(_tool_env_var(name), overrides.get(name, name))


def has_tool(name):
return shutil.which(get_tool(name)) is not None


def require_tool(name):
env_var = _tool_env_var(name)
path = get_tool(name)
if shutil.which(path) is None:
if path == name:
raise ToolNotFound("Could not find required tool {} in PATH. Place "
"it directly in PATH or specify path explicitly "
"via the {} environment variable".
format(name, env_var))
else:
if os.getenv(env_var):
via = "the {} environment variable".format(env_var)
else:
via = "your packager's toolchain overrides. This is either an " \
"nMigen bug or a packaging error"
raise ToolNotFound("Could not find required tool {} in {} as "
"specified via {}".format(name, path, via))
return path


# Packages for systems like Nix can inject full paths to certain tools by adding them in
# this dictionary, e.g. ``overrides = {"yosys": "/full/path/to/yosys"}``.
overrides = {}
520 changes: 0 additions & 520 deletions nmigen/_version.py

This file was deleted.

2 changes: 1 addition & 1 deletion nmigen/asserts.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .hdl.ast import AnyConst, AnySeq, Assert, Assume
from .hdl.ast import AnyConst, AnySeq, Assert, Assume, Cover
from .hdl.ast import Past, Stable, Rose, Fell, Initial
28 changes: 25 additions & 3 deletions nmigen/back/pysim.py
Original file line number Diff line number Diff line change
@@ -129,6 +129,15 @@ def on_Operator(self, value):
return lambda state: normalize(-arg(state), shape)
if value.op == "b":
return lambda state: normalize(bool(arg(state)), shape)
if value.op == "r|":
return lambda state: normalize(arg(state) != 0, shape)
if value.op == "r&":
val, = value.operands
mask = (1 << len(val)) - 1
return lambda state: normalize(arg(state) == mask, shape)
if value.op == "r^":
# Believe it or not, this is the fastest way to compute a sideways XOR in Python.
return lambda state: normalize(format(arg(state), "b").count("1") % 2, shape)
elif len(value.operands) == 2:
lhs, rhs = map(self, value.operands)
if value.op == "+":
@@ -320,6 +329,9 @@ def on_Assert(self, stmt):
def on_Assume(self, stmt):
pass # :nocov:

def on_Cover(self, stmt):
raise NotImplementedError("Covers not yet implemented for Simulator backend.") # :nocov:

def on_Switch(self, stmt):
test = self.rrhs_compiler(stmt.test)
cases = []
@@ -364,6 +376,7 @@ def __init__(self, fragment, vcd_file=None, gtkw_file=None, traces=()):
self._slot_signals = list() # int/slot -> Signal

self._domains = list() # [ClockDomain]
self._clk_edges = dict() # ClockDomain -> int/edge
self._domain_triggers = list() # int/slot -> ClockDomain

self._signals = SignalSet() # {Signal}
@@ -438,7 +451,7 @@ def sync_process():
sync_process = sync_process()
self.add_process(sync_process)

def add_clock(self, period, phase=None, domain="sync"):
def add_clock(self, period, *, phase=None, domain="sync", if_exists=False):
if self._fastest_clock == self._epsilon or period < self._fastest_clock:
self._fastest_clock = period
if domain in self._all_clocks:
@@ -451,6 +464,13 @@ def add_clock(self, period, phase=None, domain="sync"):
for domain_obj in self._domains:
if not domain_obj.local and domain_obj.name == domain:
clk = domain_obj.clk
break
else:
if if_exists:
return
else:
raise ValueError("Domain '{}' is not present in simulation"
.format(domain))
def clk_process():
yield Passive()
yield Delay(phase)
@@ -481,6 +501,7 @@ def add_fragment(fragment, scope=()):
add_fragment(subfragment, (*scope, name))
add_fragment(root_fragment, scope=("top",))
self._domains = list(domains)
self._clk_edges = {domain: 1 if domain.clk_edge == "pos" else 0 for domain in domains}

def add_signal(signal):
if signal not in self._signals:
@@ -540,7 +561,7 @@ def add_domain_signal(signal, domain):
var_init = signal.decoder(signal.reset).expandtabs().replace(" ", "_")
else:
var_type = "wire"
var_size = signal.nbits
var_size = signal.width
var_init = signal.reset

suffix = None
@@ -635,7 +656,8 @@ def _commit_signal(self, signal_slot, domains):
return

# If the signal is a clock that triggers synchronous logic, record that fact.
if new == 1 and self._domain_triggers[signal_slot] is not None:
if (self._domain_triggers[signal_slot] is not None and
self._clk_edges[self._domain_triggers[signal_slot]] == new):
domains.add(self._domain_triggers[signal_slot])

if self._vcd_writer:
80 changes: 45 additions & 35 deletions nmigen/back/rtlil.py
Original file line number Diff line number Diff line change
@@ -123,6 +123,9 @@ def cell(self, kind, name=None, params={}, ports={}, attrs={}, src=""):
elif isinstance(value, int):
self._append(" parameter \\{} {:d}\n",
param, value)
elif isinstance(value, float):
self._append(" parameter real \\{} \"{!r}\"\n",
param, value)
elif isinstance(value, ast.Const):
self._append(" parameter \\{} {}'{:b}\n",
param, len(value), value.value)
@@ -280,12 +283,12 @@ def resolve(self, signal, prefix=None):
else:
wire_name = signal.name

wire_curr = self.rtlil.wire(width=signal.nbits, name=wire_name,
wire_curr = self.rtlil.wire(width=signal.width, name=wire_name,
port_id=port_id, port_kind=port_kind,
attrs=signal.attrs,
src=src(signal.src_loc))
if signal in self.driven and self.driven[signal]:
wire_next = self.rtlil.wire(width=signal.nbits, name=wire_curr + "$next",
wire_next = self.rtlil.wire(width=signal.width, name=wire_curr + "$next",
src=src(signal.src_loc))
else:
wire_next = None
@@ -372,6 +375,9 @@ class _RHSValueCompiler(_ValueCompiler):
(1, "~"): "$not",
(1, "-"): "$neg",
(1, "b"): "$reduce_bool",
(1, "r|"): "$reduce_or",
(1, "r&"): "$reduce_and",
(1, "r^"): "$reduce_xor",
(2, "+"): "$add",
(2, "-"): "$sub",
(2, "*"): "$mul",
@@ -397,10 +403,10 @@ def on_value(self, value):

def on_Const(self, value):
if isinstance(value.value, str):
return "{}'{}".format(value.nbits, value.value)
return "{}'{}".format(value.width, value.value)
else:
value_twos_compl = value.value & ((1 << value.nbits) - 1)
return "{}'{:0{}b}".format(value.nbits, value_twos_compl, value.nbits)
value_twos_compl = value.value & ((1 << value.width) - 1)
return "{}'{:0{}b}".format(value.width, value_twos_compl, value.width)

def on_AnyConst(self, value):
if value in self.s.anys:
@@ -651,27 +657,20 @@ def on_Assign(self, stmt):
else:
self._case.assign(self.lhs_compiler(stmt.lhs), rhs_sigspec)

def on_Assert(self, stmt):
def on_property(self, stmt):
self(stmt._check.eq(stmt.test))
self(stmt._en.eq(1))

en_wire = self.rhs_compiler(stmt._en)
check_wire = self.rhs_compiler(stmt._check)
self.state.rtlil.cell("$assert", ports={
self.state.rtlil.cell("$" + stmt._kind, ports={
"\\A": check_wire,
"\\EN": en_wire,
}, src=src(stmt.src_loc))

def on_Assume(self, stmt):
self(stmt._check.eq(stmt.test))
self(stmt._en.eq(1))

en_wire = self.rhs_compiler(stmt._en)
check_wire = self.rhs_compiler(stmt._check)
self.state.rtlil.cell("$assume", ports={
"\\A": check_wire,
"\\EN": en_wire,
}, src=src(stmt.src_loc))
on_Assert = on_property
on_Assume = on_property
on_Cover = on_property

def on_Switch(self, stmt):
self._check_rhs(stmt.test)
@@ -704,13 +703,13 @@ def on_statement(self, stmt):
except LegalizeValue as legalize:
with self._case.switch(self.rhs_compiler(legalize.value),
src=src(legalize.src_loc)) as switch:
bits, sign = legalize.value.shape()
tests = ["{:0{}b}".format(v, bits) for v in legalize.branches]
tests[-1] = "-" * bits
width, signed = legalize.value.shape()
tests = ["{:0{}b}".format(v, width) for v in legalize.branches]
tests[-1] = "-" * width
for branch, test in zip(legalize.branches, tests):
with self.case(switch, (test,)):
self._wrap_assign = False
branch_value = ast.Const(branch, (bits, sign))
branch_value = ast.Const(branch, (width, signed))
with self.state.expand_to(legalize.value, branch_value):
super().on_statement(stmt)
self._wrap_assign = True
@@ -720,7 +719,7 @@ def on_statements(self, stmts):
self.on_statement(stmt)


def _convert_fragment(builder, fragment, hierarchy):
def _convert_fragment(builder, fragment, name_map, hierarchy):
if isinstance(fragment, ir.Instance):
port_map = OrderedDict()
for port_name, (value, dir) in fragment.named_ports.items():
@@ -807,7 +806,8 @@ def _convert_fragment(builder, fragment, hierarchy):
sub_params[param_name] = param_value

sub_type, sub_port_map = \
_convert_fragment(builder, subfragment, hierarchy=hierarchy + (sub_name,))
_convert_fragment(builder, subfragment, name_map,
hierarchy=hierarchy + (sub_name,))

sub_ports = OrderedDict()
for port, value in sub_port_map.items():
@@ -842,7 +842,7 @@ def _convert_fragment(builder, fragment, hierarchy):
if signal not in group_signals:
continue
if domain is None:
prev_value = ast.Const(signal.reset, signal.nbits)
prev_value = ast.Const(signal.reset, signal.width)
else:
prev_value = signal
case.assign(lhs_compiler(signal), rhs_compiler(prev_value))
@@ -871,7 +871,7 @@ def _convert_fragment(builder, fragment, hierarchy):
if signal not in group_signals:
continue
wire_curr, wire_next = compiler_state.resolve(signal)
sync.update(wire_curr, rhs_compiler(ast.Const(signal.reset, signal.nbits)))
sync.update(wire_curr, rhs_compiler(ast.Const(signal.reset, signal.width)))

# The Verilog simulator trigger needs to change at time 0, so if we haven't
# yet done that in some process, do it.
@@ -881,8 +881,8 @@ def _convert_fragment(builder, fragment, hierarchy):

# For every signal in every sync domain, assign \sig to \sig$next. The sensitivity
# list, however, differs between domains: for domains with sync reset, it is
# `posedge clk`, for sync domains with async reset it is `posedge clk or
# posedge rst`.
# `[pos|neg]edge clk`, for sync domains with async reset it is `[pos|neg]edge clk
# or posedge rst`.
for domain, signals in fragment.drivers.items():
if domain is None:
continue
@@ -894,7 +894,7 @@ def _convert_fragment(builder, fragment, hierarchy):
cd = fragment.domains[domain]

triggers = []
triggers.append(("posedge", compiler_state.resolve_curr(cd.clk)))
triggers.append((cd.clk_edge + "edge", compiler_state.resolve_curr(cd.clk)))
if cd.async_reset:
triggers.append(("posedge", compiler_state.resolve_curr(cd.rst)))

@@ -926,25 +926,35 @@ def _convert_fragment(builder, fragment, hierarchy):
if wire in driven:
continue
wire_curr, _ = compiler_state.wires[wire]
module.connect(wire_curr, rhs_compiler(ast.Const(wire.reset, wire.nbits)))
module.connect(wire_curr, rhs_compiler(ast.Const(wire.reset, wire.width)))

# Finally, collect the names we've given to our ports in RTLIL, and correlate these with
# the signals represented by these ports. If we are a submodule, this will be necessary
# to create a cell for us in the parent module.
# Collect the names we've given to our ports in RTLIL, and correlate these with the signals
# represented by these ports. If we are a submodule, this will be necessary to create a cell
# for us in the parent module.
port_map = OrderedDict()
for signal in fragment.ports:
port_map[compiler_state.resolve_curr(signal)] = signal

# Finally, collect tha names we've given to each wire in RTLIL, and provide these to
# the caller, to allow manipulating them in the toolchain.
for signal in compiler_state.wires:
wire_name = compiler_state.resolve_curr(signal)
if wire_name.startswith("\\"):
wire_name = wire_name[1:]
name_map[signal] = hierarchy + (wire_name,)

return module.name, port_map


def convert_fragment(fragment, name="top"):
assert isinstance(fragment, ir.Fragment)
builder = _Builder()
_convert_fragment(builder, fragment, hierarchy=(name,))
return str(builder)
name_map = ast.SignalDict()
_convert_fragment(builder, fragment, name_map, hierarchy=(name,))
return str(builder), name_map


def convert(elaboratable, name="top", platform=None, **kwargs):
fragment = ir.Fragment.get(elaboratable, platform).prepare(**kwargs)
return convert_fragment(fragment, name)
il_text, name_map = convert_fragment(fragment, name)
return il_text
30 changes: 12 additions & 18 deletions nmigen/back/verilog.py
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
import re
import subprocess

from .._toolchain import *
from . import rtlil


@@ -13,24 +14,16 @@ class YosysError(Exception):


def _yosys_version():
try:
version = subprocess.check_output([os.getenv("YOSYS", "yosys"), "-V"], encoding="utf-8")
except FileNotFoundError as e:
if os.getenv("YOSYS"):
raise YosysError("Could not find Yosys in {} as specified via the YOSYS environment "
"variable".format(os.getenv("YOSYS"))) from e
else:
raise YosysError("Could not find Yosys in PATH. Place `yosys` in PATH or specify "
"path explicitly via the YOSYS environment variable") from e

m = re.match(r"^Yosys ([\d.]+)\+(\d+)", version)
tag, offset = m[1], m[2]
yosys_path = require_tool("yosys")
version = subprocess.check_output([yosys_path, "-V"], encoding="utf-8")
m = re.match(r"^Yosys ([\d.]+)(?:\+(\d+))?", version)
tag, offset = m[1], m[2] or 0
return tuple(map(int, tag.split("."))), offset


def _convert_il_text(il_text, strip_src):
version, offset = _yosys_version()
if version < (0, 8):
if version < (0, 9):
raise YosysError("Yosys %d.%d is not suppored", *version)

attr_map = []
@@ -42,17 +35,18 @@ def _convert_il_text(il_text, strip_src):
read_ilang <<rtlil
{}
rtlil
proc_prune
{prune}proc_prune
proc_init
proc_arst
proc_dff
proc_clean
memory_collect
attrmap {}
write_verilog -norename
""".format(il_text, " ".join(attr_map))
""".format(il_text, " ".join(attr_map),
prune="# " if version == (0, 9) and offset == 0 else "")

popen = subprocess.Popen([os.getenv("YOSYS", "yosys"), "-q", "-"],
popen = subprocess.Popen([require_tool("yosys"), "-q", "-"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
@@ -65,8 +59,8 @@ def _convert_il_text(il_text, strip_src):


def convert_fragment(*args, strip_src=False, **kwargs):
il_text = rtlil.convert_fragment(*args, **kwargs)
return _convert_il_text(il_text, strip_src)
il_text, name_map = rtlil.convert_fragment(*args, **kwargs)
return _convert_il_text(il_text, strip_src), name_map


def convert(*args, strip_src=False, **kwargs):
9 changes: 4 additions & 5 deletions nmigen/build/dsl.py
Original file line number Diff line number Diff line change
@@ -92,8 +92,9 @@ def DiffPairsN(*args, **kwargs):
class Attrs(OrderedDict):
def __init__(self, **attrs):
for key, value in attrs.items():
if not (value is None or isinstance(value, str) or hasattr(value, "__call__")):
raise TypeError("Value of attribute {} must be None, str, or callable, not {!r}"
if not (value is None or isinstance(value, (str, int)) or hasattr(value, "__call__")):
raise TypeError("Value of attribute {} must be None, int, str, or callable, "
"not {!r}"
.format(key, value))

super().__init__(**attrs)
@@ -103,10 +104,8 @@ def __repr__(self):
for key, value in self.items():
if value is None:
items.append("!" + key)
elif hasattr(value, "__call__"):
items.append(key + "=" + repr(value))
else:
items.append(key + "=" + value)
items.append(key + "=" + repr(value))
return "(attrs {})".format(" ".join(items))


40 changes: 29 additions & 11 deletions nmigen/build/plat.py
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
import jinja2

from .. import __version__
from .._toolchain import *
from ..hdl.ast import *
from ..hdl.cd import *
from ..hdl.dsl import *
@@ -19,10 +20,11 @@


class Platform(ResourceManager, metaclass=ABCMeta):
resources = abstractproperty()
connectors = abstractproperty()
default_clk = None
default_rst = None
resources = abstractproperty()
connectors = abstractproperty()
default_clk = None
default_rst = None
required_tools = abstractproperty()

def __init__(self):
super().__init__(self.resources, self.connectors)
@@ -58,10 +60,18 @@ def add_file(self, filename, content):
raise TypeError("File contents must be str, bytes, or a file-like object")
self.extra_files[filename] = content

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

def build(self, elaboratable, name="top",
build_dir="build", do_build=True,
program_opts=None, do_program=False,
**kwargs):
if self._toolchain_env_var not in os.environ:
for tool in self.required_tools:
require_tool(tool)

plan = self.prepare(elaboratable, name, **kwargs)
if not do_build:
return plan
@@ -72,6 +82,11 @@ def build(self, elaboratable, name="top",

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

def has_required_tools(self):
if self._toolchain_env_var in os.environ:
return True
return all(has_tool(name) for name in self.required_tools)

@abstractmethod
def create_missing_domain(self, name):
# Simple instantiation of a clock domain driven directly by the board clock and reset.
@@ -230,13 +245,13 @@ class TemplatedPlatform(Platform):
"build_{{name}}.sh": """
# {{autogenerated}}
set -e{{verbose("x")}}
[ -n "$NMIGEN_{{platform.toolchain}}_env" ] && . "$NMIGEN_{{platform.toolchain}}_env"
[ -n "${{platform._toolchain_env_var}}" ] && . "${{platform._toolchain_env_var}}"
{{emit_commands("sh")}}
""",
"build_{{name}}.bat": """
@rem {{autogenerated}}
{{quiet("@echo off")}}
if defined NMIGEN_{{platform.toolchain}}_env call %NMIGEN_{{platform.toolchain}}_env%
if defined {{platform._toolchain_env_var}} call %{{platform._toolchain_env_var}}%
{{emit_commands("bat")}}
""",
}
@@ -246,9 +261,12 @@ def toolchain_prepare(self, fragment, name, **kwargs):
# and to incorporate the nMigen version into generated code.
autogenerated = "Automatically generated by nMigen {}. Do not edit.".format(__version__)

name_map = None
def emit_design(backend):
nonlocal name_map
backend_mod = {"rtlil": rtlil, "verilog": verilog}[backend]
return backend_mod.convert_fragment(fragment, name=name)
design_text, name_map = backend_mod.convert_fragment(fragment, name=name)
return design_text

def emit_commands(format):
commands = []
@@ -263,10 +281,6 @@ def emit_commands(format):
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:
@@ -289,6 +303,9 @@ def options(opts):
else:
return " ".join(opts)

def hierarchy(signal, separator):
return separator.join(name_map[signal][1:])

def verbose(arg):
if "NMIGEN_verbose" in os.environ:
return arg
@@ -306,6 +323,7 @@ def render(source, origin):
source = textwrap.dedent(source).strip()
compiled = jinja2.Template(source, trim_blocks=True, lstrip_blocks=True)
compiled.environment.filters["options"] = options
compiled.environment.filters["hierarchy"] = hierarchy
except jinja2.TemplateSyntaxError as e:
e.args = ("{} (at {}:{})".format(e.message, origin, e.lineno),)
raise
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())
16 changes: 16 additions & 0 deletions nmigen/build/run.py
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
import subprocess
import tempfile
import zipfile
import hashlib


__all__ = ["BuildPlan", "BuildProducts", "LocalBuildProducts"]
@@ -32,6 +33,21 @@ def add_file(self, filename, content):
assert isinstance(filename, str) and filename not in self.files
self.files[filename] = content

def digest(self, size=64):
"""
Compute a `digest`, a short byte sequence deterministically and uniquely identifying
this build plan.
"""
hasher = hashlib.blake2b(digest_size=size)
for filename in sorted(self.files):
hasher.update(filename.encode("utf-8"))
content = self.files[filename]
if isinstance(content, str):
content = content.encode("utf-8")
hasher.update(content)
hasher.update(self.script.encode("utf-8"))
return hasher.digest()

def archive(self, file):
"""
Archive files from the build plan into ``file``, which can be either a filename, or
15 changes: 14 additions & 1 deletion nmigen/compat/genlib/cdc.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import warnings

from ...tools import deprecated
from ...lib.cdc import MultiReg
from ...lib.cdc import MultiReg as NativeMultiReg
from ...hdl.ast import *
from ..fhdl.module import CompatModule
from ..fhdl.structure import If
@@ -8,6 +10,17 @@
__all__ = ["MultiReg", "GrayCounter", "GrayDecoder"]


class MultiReg(NativeMultiReg):
def __init__(self, i, o, odomain="sync", n=2, reset=0):
if odomain != "sync":
warnings.warn("instead of `MultiReg(..., odomain={!r})`, "
"use `MultiReg(..., o_domain={!r})`"
.format(odomain, odomain),
DeprecationWarning, stacklevel=2)
super().__init__(i, o, o_domain=odomain, n=n, reset=reset)
self.odomain = odomain


@deprecated("instead of `migen.genlib.cdc.GrayCounter`, use `nmigen.lib.coding.GrayEncoder`")
class GrayCounter(CompatModule):
def __init__(self, width):
2 changes: 1 addition & 1 deletion nmigen/compat/genlib/resetsync.py
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@
"a clock domain name as an argument, not a clock domain object")
class CompatResetSynchronizer(NativeResetSynchronizer):
def __init__(self, cd, async_reset):
super().__init__(async_reset, cd.name)
super().__init__(async_reset, domain=cd.name)


AsyncResetSynchronizer = CompatResetSynchronizer
302 changes: 237 additions & 65 deletions nmigen/hdl/ast.py

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion nmigen/hdl/cd.py
Original file line number Diff line number Diff line change
@@ -45,7 +45,8 @@ def _name_for(domain_name, signal_name):
else:
return "{}_{}".format(domain_name, signal_name)

def __init__(self, name=None, reset_less=False, async_reset=False, local=False):
def __init__(self, name=None, *, clk_edge="pos", reset_less=False, async_reset=False,
local=False):
if name is None:
try:
name = tracer.get_var_name()
@@ -55,9 +56,16 @@ def __init__(self, name=None, reset_less=False, async_reset=False, local=False):
name = name[3:]
if name == "comb":
raise ValueError("Domain '{}' may not be clocked".format(name))

if clk_edge not in ("pos", "neg"):
raise ValueError("Domain clock edge must be one of 'pos' or 'neg', not {!r}"
.format(clk_edge))

self.name = name

self.clk = Signal(name=self._name_for(name, "clk"), src_loc_at=1)
self.clk_edge = clk_edge

if reset_less:
self.rst = None
else:
50 changes: 31 additions & 19 deletions nmigen/hdl/dsl.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from collections import OrderedDict, namedtuple
from collections.abc import Iterable
from contextlib import contextmanager
from enum import Enum
import warnings

from ..tools import flatten, bits_for, deprecated
@@ -167,8 +168,8 @@ def _set_ctrl(self, name, data):

def _check_signed_cond(self, cond):
cond = Value.wrap(cond)
bits, sign = cond.shape()
if sign:
width, signed = cond.shape()
if signed:
warnings.warn("Signed values in If/Elif conditions usually result from inverting "
"Python booleans with ~, which leads to unexpected results: ~True is "
"-2, which is truthful. Replace `~flag` with `not flag`. (If this is "
@@ -258,22 +259,30 @@ def Switch(self, test):
self._pop_ctrl()

@contextmanager
def Case(self, *values):
def Case(self, *patterns):
self._check_context("Case", context="Switch")
src_loc = tracer.get_src_loc(src_loc_at=1)
switch_data = self._get_ctrl("Switch")
new_values = ()
for value in values:
if isinstance(value, str) and len(value) != len(switch_data["test"]):
raise SyntaxError("Case value '{}' must have the same width as test (which is {})"
.format(value, len(switch_data["test"])))
if isinstance(value, int) and bits_for(value) > len(switch_data["test"]):
warnings.warn("Case value '{:b}' is wider than test (which has width {}); "
"comparison will never be true"
.format(value, len(switch_data["test"])),
new_patterns = ()
for pattern in patterns:
if not isinstance(pattern, (int, str, Enum)):
raise SyntaxError("Case pattern must be an integer, a string, or an enumeration, "
"not {!r}"
.format(pattern))
if isinstance(pattern, str) and any(bit not in "01-" for bit in pattern):
raise SyntaxError("Case pattern '{}' must consist of 0, 1, and - (don't care) bits"
.format(pattern))
if isinstance(pattern, str) and len(pattern) != len(switch_data["test"]):
raise SyntaxError("Case pattern '{}' must have the same width as switch value "
"(which is {})"
.format(pattern, len(switch_data["test"])))
if isinstance(pattern, int) and bits_for(pattern) > len(switch_data["test"]):
warnings.warn("Case pattern '{:b}' is wider than switch value "
"(which has width {}); comparison will never be true"
.format(pattern, len(switch_data["test"])),
SyntaxWarning, stacklevel=3)
continue
new_values = (*new_values, value)
new_patterns = (*new_patterns, pattern)
try:
_outer_case, self._statements = self._statements, []
self._ctrl_context = None
@@ -282,13 +291,16 @@ def Case(self, *values):
# If none of the provided cases can possibly be true, omit this branch completely.
# This needs to be differentiated from no cases being provided in the first place,
# which means the branch will always match.
if not (values and not new_values):
switch_data["cases"][new_values] = self._statements
switch_data["case_src_locs"][new_values] = src_loc
if not (patterns and not new_patterns):
switch_data["cases"][new_patterns] = self._statements
switch_data["case_src_locs"][new_patterns] = src_loc
finally:
self._ctrl_context = "Switch"
self._statements = _outer_case

def Default(self):
return self.Case()

@contextmanager
def FSM(self, reset=None, domain="sync", name="fsm"):
self._check_context("FSM", context=None)
@@ -393,7 +405,7 @@ def _pop_ctrl(self):
fsm_state_src_locs = data["state_src_locs"]
if not fsm_states:
return
fsm_signal.nbits = bits_for(len(fsm_encoding) - 1)
fsm_signal.width = bits_for(len(fsm_encoding) - 1)
if fsm_reset is None:
fsm_signal.reset = fsm_encoding[next(iter(fsm_states))]
else:
@@ -417,9 +429,9 @@ def domain_name(domain):
self._pop_ctrl()

for assign in Statement.wrap(assigns):
if not compat_mode and not isinstance(assign, (Assign, Assert, Assume)):
if not compat_mode and not isinstance(assign, (Assign, Assert, Assume, Cover)):
raise SyntaxError(
"Only assignments, asserts, and assumes may be appended to d.{}"
"Only assignments and property checks may be appended to d.{}"
.format(domain_name(domain)))

assign = SampleDomainInjector(domain)(assign)
30 changes: 15 additions & 15 deletions nmigen/hdl/mem.py
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@


class Memory:
def __init__(self, width, depth, init=None, name=None, simulate=True):
def __init__(self, width, depth, *, init=None, name=None, simulate=True):
if not isinstance(width, int) or width < 0:
raise TypeError("Memory width must be a non-negative integer, not '{!r}'"
.format(width))
@@ -53,12 +53,12 @@ def init(self, new_init):
raise TypeError("Memory initialization value at address {:x}: {}"
.format(addr, e)) from None

def read_port(self, domain="sync", transparent=True):
def read_port(self, domain="sync", *, transparent=True):
if domain == "comb" and not transparent:
raise ValueError("Read port cannot be simultaneously asynchronous and non-transparent")
return ReadPort(self, domain, transparent)
return ReadPort(self, domain, transparent=transparent)

def write_port(self, domain="sync", priority=0, granularity=None):
def write_port(self, domain="sync", *, priority=0, granularity=None):
if granularity is None:
granularity = self.width
if not isinstance(granularity, int) or granularity < 0:
@@ -70,33 +70,33 @@ def write_port(self, domain="sync", priority=0, granularity=None):
.format(granularity, self.width))
if self.width // granularity * granularity != self.width:
raise ValueError("Write port granularity must divide memory width evenly")
return WritePort(self, domain, priority, granularity)
return WritePort(self, domain, priority=priority, granularity=granularity)

def __getitem__(self, index):
"""Simulation only."""
return self._array[index]


class ReadPort(Elaboratable):
def __init__(self, memory, domain, transparent):
def __init__(self, memory, domain, *, transparent):
self.memory = memory
self.domain = domain
self.transparent = transparent

self.addr = Signal(max=memory.depth,
self.addr = Signal.range(memory.depth,
name="{}_r_addr".format(memory.name), src_loc_at=2)
self.data = Signal(memory.width,
name="{}_r_data".format(memory.name), src_loc_at=2)
if self.domain != "comb" and not transparent:
self.en = Signal(name="{}_r_en".format(memory.name), src_loc_at=2)
self.en = Signal(name="{}_r_en".format(memory.name), src_loc_at=2, reset=1)
else:
self.en = Const(1)

def elaborate(self, platform):
f = Instance("$memrd",
p_MEMID=self.memory,
p_ABITS=self.addr.nbits,
p_WIDTH=self.data.nbits,
p_ABITS=self.addr.width,
p_WIDTH=self.data.width,
p_CLK_ENABLE=self.domain != "comb",
p_CLK_POLARITY=1,
p_TRANSPARENT=self.transparent,
@@ -142,13 +142,13 @@ def elaborate(self, platform):


class WritePort(Elaboratable):
def __init__(self, memory, domain, priority, granularity):
def __init__(self, memory, domain, *, priority, granularity):
self.memory = memory
self.domain = domain
self.priority = priority
self.granularity = granularity

self.addr = Signal(max=memory.depth,
self.addr = Signal.range(memory.depth,
name="{}_w_addr".format(memory.name), src_loc_at=2)
self.data = Signal(memory.width,
name="{}_w_data".format(memory.name), src_loc_at=2)
@@ -158,8 +158,8 @@ def __init__(self, memory, domain, priority, granularity):
def elaborate(self, platform):
f = Instance("$memwr",
p_MEMID=self.memory,
p_ABITS=self.addr.nbits,
p_WIDTH=self.data.nbits,
p_ABITS=self.addr.width,
p_WIDTH=self.data.width,
p_CLK_ENABLE=1,
p_CLK_POLARITY=1,
p_PRIORITY=self.priority,
@@ -189,7 +189,7 @@ class DummyPort:
It does not include any read/write port specific attributes, i.e. none besides ``"domain"``;
any such attributes may be set manually.
"""
def __init__(self, width, addr_bits, domain="sync", name=None, granularity=None):
def __init__(self, width, addr_bits, domain="sync", *, name=None, granularity=None):
self.domain = domain

if granularity is None:
44 changes: 31 additions & 13 deletions nmigen/hdl/xfrm.py
Original file line number Diff line number Diff line change
@@ -196,6 +196,10 @@ def on_Assert(self, stmt):
def on_Assume(self, stmt):
pass # :nocov:

@abstractmethod
def on_Cover(self, stmt):
pass # :nocov:

@abstractmethod
def on_Switch(self, stmt):
pass # :nocov:
@@ -217,6 +221,8 @@ def on_statement(self, stmt):
new_stmt = self.on_Assert(stmt)
elif type(stmt) is Assume:
new_stmt = self.on_Assume(stmt)
elif type(stmt) is Cover:
new_stmt = self.on_Cover(stmt)
elif isinstance(stmt, Switch):
# Uses `isinstance()` and not `type() is` because nmigen.compat requires it.
new_stmt = self.on_Switch(stmt)
@@ -247,6 +253,9 @@ def on_Assert(self, stmt):
def on_Assume(self, stmt):
return Assume(self.on_value(stmt.test), _check=stmt._check, _en=stmt._en)

def on_Cover(self, stmt):
return Cover(self.on_value(stmt.test), _check=stmt._check, _en=stmt._en)

def on_Switch(self, stmt):
cases = OrderedDict((k, self.on_statement(s)) for k, s in stmt.cases.items())
return Switch(self.on_value(stmt.test), cases)
@@ -396,11 +405,12 @@ def on_Assign(self, stmt):
self.on_value(stmt.lhs)
self.on_value(stmt.rhs)

def on_Assert(self, stmt):
def on_property(self, stmt):
self.on_value(stmt.test)

def on_Assume(self, stmt):
self.on_value(stmt.test)
on_Assert = on_property
on_Assume = on_property
on_Cover = on_property

def on_Switch(self, stmt):
self.on_value(stmt.test)
@@ -513,7 +523,7 @@ def _insert_resets(self, fragment):
domain = fragment.domains[domain_name]
if domain.rst is None:
continue
stmts = [signal.eq(Const(signal.reset, signal.nbits))
stmts = [signal.eq(Const(signal.reset, signal.width))
for signal in signals if not signal.reset_less]
fragment.add_statements(Switch(domain.rst, {1: stmts}))

@@ -598,12 +608,13 @@ def map_statements(self, fragment, new_fragment):


class SwitchCleaner(StatementVisitor):
def on_Assign(self, stmt):
def on_ignore(self, stmt):
return stmt

on_Assert = on_Assign

on_Assume = on_Assign
on_Assign = on_ignore
on_Assert = on_ignore
on_Assume = on_ignore
on_Cover = on_ignore

def on_Switch(self, stmt):
cases = OrderedDict((k, self.on_statement(s)) for k, s in stmt.cases.items())
@@ -651,9 +662,14 @@ def on_Assign(self, stmt):
if lhs_signals:
self.unify(*stmt._lhs_signals())

on_Assert = on_Assign
def on_property(self, stmt):
lhs_signals = stmt._lhs_signals()
if lhs_signals:
self.unify(*stmt._lhs_signals())

on_Assume = on_Assign
on_Assert = on_property
on_Assume = on_property
on_Cover = on_property

def on_Switch(self, stmt):
for case_stmts in stmt.cases.values():
@@ -681,12 +697,14 @@ def on_Assign(self, stmt):
if any_lhs_signal in self.signals:
return stmt

def on_Assert(self, stmt):
def on_property(self, stmt):
any_lhs_signal = next(iter(stmt._lhs_signals()))
if any_lhs_signal in self.signals:
return stmt

on_Assume = on_Assert
on_Assert = on_property
on_Assume = on_property
on_Cover = on_property


class _ControlInserter(FragmentTransformer):
@@ -714,7 +732,7 @@ def __call__(self, value):

class ResetInserter(_ControlInserter):
def _insert_control(self, fragment, domain, signals):
stmts = [s.eq(Const(s.reset, s.nbits)) for s in signals if not s.reset_less]
stmts = [s.eq(Const(s.reset, s.width)) for s in signals if not s.reset_less]
fragment.add_statements(Switch(self.controls[domain], {1: stmts}, src_loc=self.src_loc))


1 change: 0 additions & 1 deletion nmigen/lib/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
from .cdc import MultiReg
24 changes: 12 additions & 12 deletions nmigen/lib/cdc.py
Original file line number Diff line number Diff line change
@@ -16,15 +16,15 @@ class MultiReg(Elaboratable):
Signal to be resynchronised
o : Signal(), out
Signal connected to synchroniser output
odomain : str
o_domain : str
Name of output clock domain
n : int
Number of flops between input and output.
reset : int
Reset value of the flip-flops. On FPGAs, even if ``reset_less`` is True, the MultiReg is
still set to this value during initialization.
reset_less : bool
If True (the default), this MultiReg is unaffected by ``odomain`` reset.
If True (the default), this MultiReg is unaffected by ``o_domain`` reset.
See "Note on Reset" below.
Platform override
@@ -42,18 +42,18 @@ class MultiReg(Elaboratable):
consider setting ``reset_less`` to False if any of the following is true:
- You are targeting an ASIC, or an FPGA that does not allow arbitrary initial flip-flop states;
- Your design features warm (non-power-on) resets of ``odomain``, so the one-time
- Your design features warm (non-power-on) resets of ``o_domain``, so the one-time
initialization at power on is insufficient;
- Your design features a sequenced reset, and the MultiReg must maintain its reset value until
``odomain`` reset specifically is deasserted.
``o_domain`` reset specifically is deasserted.
MultiReg is reset by the ``odomain`` reset only.
MultiReg is reset by the ``o_domain`` reset only.
"""
def __init__(self, i, o, odomain="sync", n=2, reset=0, reset_less=True):
def __init__(self, i, o, *, o_domain="sync", n=2, reset=0, reset_less=True):
self.i = i
self.o = o
self.odomain = odomain

self._o_domain = o_domain
self._regs = [Signal(self.i.shape(), name="cdc{}".format(i), reset=reset,
reset_less=reset_less)
for i in range(n)]
@@ -64,16 +64,16 @@ def elaborate(self, platform):

m = Module()
for i, o in zip((self.i, *self._regs), self._regs):
m.d[self.odomain] += o.eq(i)
m.d[self._o_domain] += o.eq(i)
m.d.comb += self.o.eq(self._regs[-1])
return m


class ResetSynchronizer(Elaboratable):
def __init__(self, arst, domain="sync", n=2):
def __init__(self, arst, *, domain="sync", n=2):
self.arst = arst
self.domain = domain

self._domain = domain
self._regs = [Signal(1, name="arst{}".format(i), reset=1)
for i in range(n)]

@@ -86,8 +86,8 @@ def elaborate(self, platform):
for i, o in zip((0, *self._regs), self._regs):
m.d.reset_sync += o.eq(i)
m.d.comb += [
ClockSignal("reset_sync").eq(ClockSignal(self.domain)),
ClockSignal("reset_sync").eq(ClockSignal(self._domain)),
ResetSignal("reset_sync").eq(self.arst),
ResetSignal(self.domain).eq(self._regs[-1])
ResetSignal(self._domain).eq(self._regs[-1])
]
return m
12 changes: 6 additions & 6 deletions nmigen/lib/coding.py
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ class Encoder(Elaboratable):
----------
i : Signal(width), in
One-hot input.
o : Signal(max=width), out
o : Signal.range(width), out
Encoded binary.
n : Signal, out
Invalid: either none or multiple input bits are asserted.
@@ -34,7 +34,7 @@ def __init__(self, width):
self.width = width

self.i = Signal(width)
self.o = Signal(max=max(2, width))
self.o = Signal.range(width)
self.n = Signal()

def elaborate(self, platform):
@@ -64,7 +64,7 @@ class PriorityEncoder(Elaboratable):
----------
i : Signal(width), in
Input requests.
o : Signal(max=width), out
o : Signal.range(width), out
Encoded binary.
n : Signal, out
Invalid: no input bits are asserted.
@@ -73,7 +73,7 @@ def __init__(self, width):
self.width = width

self.i = Signal(width)
self.o = Signal(max=max(2, width))
self.o = Signal.range(width)
self.n = Signal()

def elaborate(self, platform):
@@ -98,7 +98,7 @@ class Decoder(Elaboratable):
Attributes
----------
i : Signal(max=width), in
i : Signal.range(width), in
Input binary.
o : Signal(width), out
Decoded one-hot.
@@ -108,7 +108,7 @@ class Decoder(Elaboratable):
def __init__(self, width):
self.width = width

self.i = Signal(max=max(2, width))
self.i = Signal.range(width)
self.n = Signal()
self.o = Signal(width)

307 changes: 178 additions & 129 deletions nmigen/lib/fifo.py

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion nmigen/lib/io.py
Original file line number Diff line number Diff line change
@@ -102,4 +102,5 @@ def __init__(self, width, dir, xdr=0, name=None, src_loc_at=0):
self.dir = dir
self.xdr = xdr

super().__init__(pin_layout(self.width, self.dir, self.xdr), name=name, src_loc_at=src_loc_at + 1)
super().__init__(pin_layout(self.width, self.dir, self.xdr),
name=name, src_loc_at=src_loc_at + 1)
3 changes: 2 additions & 1 deletion nmigen/test/compat/support.py
Original file line number Diff line number Diff line change
@@ -12,4 +12,5 @@ def test_to_verilog(self):
verilog.convert(self.tb)

def run_with(self, generator):
run_simulation(self.tb, generator)
with _ignore_deprecated():
run_simulation(self.tb, generator)
18 changes: 0 additions & 18 deletions nmigen/test/compat/test_fifo.py
Original file line number Diff line number Diff line change
@@ -36,21 +36,3 @@ def gen():
self.assertEqual((yield self.tb.dut.dout[32:]), i*2)
yield
self.run_with(gen())

def test_replace(self):
seq = [x for x in range(20) if x % 5]
def gen():
for cycle in count():
yield self.tb.dut.we.eq(cycle % 2 == 0)
yield self.tb.dut.re.eq(cycle % 7 == 0)
yield self.tb.dut.replace.eq(
(yield self.tb.dut.din[:32]) % 5 == 1)
if (yield self.tb.dut.readable) and (yield self.tb.dut.re):
try:
i = seq.pop(0)
except IndexError:
break
self.assertEqual((yield self.tb.dut.dout[:32]), i)
self.assertEqual((yield self.tb.dut.dout[32:]), i*2)
yield
self.run_with(gen())
12 changes: 6 additions & 6 deletions nmigen/test/test_build_dsl.py
Original file line number Diff line number Diff line change
@@ -109,9 +109,9 @@ def test_wrong_assert_width(self):

class AttrsTestCase(FHDLTestCase):
def test_basic(self):
a = Attrs(IO_STANDARD="LVCMOS33", PULLUP="1")
a = Attrs(IO_STANDARD="LVCMOS33", PULLUP=1)
self.assertEqual(a["IO_STANDARD"], "LVCMOS33")
self.assertEqual(repr(a), "(attrs IO_STANDARD=LVCMOS33 PULLUP=1)")
self.assertEqual(repr(a), "(attrs IO_STANDARD='LVCMOS33' PULLUP=1)")

def test_remove(self):
a = Attrs(FOO=None)
@@ -126,8 +126,8 @@ def test_callable(self):

def test_wrong_value(self):
with self.assertRaises(TypeError,
msg="Value of attribute FOO must be None, str, or callable, not 1"):
a = Attrs(FOO=1)
msg="Value of attribute FOO must be None, int, str, or callable, not 1.0"):
a = Attrs(FOO=1.0)


class ClockTestCase(FHDLTestCase):
@@ -142,7 +142,7 @@ class SubsignalTestCase(FHDLTestCase):
def test_basic_pins(self):
s = Subsignal("a", Pins("A0"), Attrs(IOSTANDARD="LVCMOS33"))
self.assertEqual(repr(s),
"(subsignal a (pins io A0) (attrs IOSTANDARD=LVCMOS33))")
"(subsignal a (pins io A0) (attrs IOSTANDARD='LVCMOS33'))")

def test_basic_diffpairs(self):
s = Subsignal("a", DiffPairs("A0", "B0"))
@@ -223,7 +223,7 @@ def test_basic(self):
self.assertEqual(repr(r), "(resource serial 0"
" (subsignal tx (pins o A0))"
" (subsignal rx (pins i A1))"
" (attrs IOSTANDARD=LVCMOS33))")
" (attrs IOSTANDARD='LVCMOS33'))")

def test_family(self):
ios = [Subsignal("clk", Pins("A0", dir="o"))]
33 changes: 10 additions & 23 deletions nmigen/test/test_build_res.py
Original file line number Diff line number Diff line change
@@ -81,7 +81,7 @@ def test_request_tristate(self):
self.assertEqual(len(ports), 2)
scl, sda = ports
self.assertEqual(ports[1].name, "i2c_0__sda__io")
self.assertEqual(ports[1].nbits, 1)
self.assertEqual(ports[1].width, 1)

self.assertEqual(list(self.cm.iter_single_ended_pins()), [
(i2c.scl, scl, {}, False),
@@ -102,9 +102,9 @@ def test_request_diffpairs(self):
self.assertEqual(len(ports), 2)
p, n = ports
self.assertEqual(p.name, "clk100_0__p")
self.assertEqual(p.nbits, clk100.width)
self.assertEqual(p.width, clk100.width)
self.assertEqual(n.name, "clk100_0__n")
self.assertEqual(n.nbits, clk100.width)
self.assertEqual(n.width, clk100.width)

self.assertEqual(list(self.cm.iter_differential_pins()), [
(clk100, p, n, {}, False),
@@ -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)
185 changes: 157 additions & 28 deletions nmigen/test/test_hdl_ast.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
import warnings
from enum import Enum

from ..hdl.ast import *
from .tools import *


class UnsignedEnum(Enum):
FOO = 1
BAR = 2
BAZ = 3


class SignedEnum(Enum):
FOO = -1
BAR = 0
BAZ = +1


class StringEnum(Enum):
FOO = "a"
BAR = "b"


class ValueTestCase(FHDLTestCase):
def test_wrap(self):
self.assertIsInstance(Value.wrap(0), Const)
@@ -14,6 +32,19 @@ def test_wrap(self):
msg="Object ''str'' is not an nMigen value"):
Value.wrap("str")

def test_wrap_enum(self):
e1 = Value.wrap(UnsignedEnum.FOO)
self.assertIsInstance(e1, Const)
self.assertEqual(e1.shape(), (2, False))
e2 = Value.wrap(SignedEnum.FOO)
self.assertIsInstance(e2, Const)
self.assertEqual(e2.shape(), (2, True))

def test_wrap_enum_wrong(self):
with self.assertRaises(TypeError,
msg="Only enumerations with integer values can be converted to nMigen values"):
Value.wrap(StringEnum.FOO)

def test_bool(self):
with self.assertRaises(TypeError,
msg="Attempted to convert nMigen value to boolean"):
@@ -250,6 +281,59 @@ def test_bool(self):
self.assertEqual(repr(v), "(b (const 1'd0))")
self.assertEqual(v.shape(), (1, False))

def test_any(self):
v = Const(0b101).any()
self.assertEqual(repr(v), "(r| (const 3'd5))")

def test_all(self):
v = Const(0b101).all()
self.assertEqual(repr(v), "(r& (const 3'd5))")

def test_xor(self):
v = Const(0b101).xor()
self.assertEqual(repr(v), "(r^ (const 3'd5))")

def test_matches(self):
s = Signal(4)
self.assertRepr(s.matches(), "(const 1'd0)")
self.assertRepr(s.matches(1), """
(== (sig s) (const 1'd1))
""")
self.assertRepr(s.matches(0, 1), """
(r| (cat (== (sig s) (const 1'd0)) (== (sig s) (const 1'd1))))
""")
self.assertRepr(s.matches("10--"), """
(== (& (sig s) (const 4'd12)) (const 4'd8))
""")

def test_matches_enum(self):
s = Signal.enum(SignedEnum)
self.assertRepr(s.matches(SignedEnum.FOO), """
(== (& (sig s) (const 2'd3)) (const 2'd3))
""")

def test_matches_width_wrong(self):
s = Signal(4)
with self.assertRaises(SyntaxError,
msg="Match pattern '--' must have the same width as match value (which is 4)"):
s.matches("--")
with self.assertWarns(SyntaxWarning,
msg="Match pattern '10110' is wider than match value (which has width 4); "
"comparison will never be true"):
s.matches(0b10110)

def test_matches_bits_wrong(self):
s = Signal(4)
with self.assertRaises(SyntaxError,
msg="Match pattern 'abc' must consist of 0, 1, and - (don't care) bits"):
s.matches("abc")

def test_matches_pattern_wrong(self):
s = Signal(4)
with self.assertRaises(SyntaxError,
msg="Match pattern must be an integer, a string, or an enumeration, not 1.0"):
s.matches(1.0)

def test_hash(self):
with self.assertRaises(TypeError):
hash(Const(0) + Const(0))
@@ -297,7 +381,7 @@ def test_repr(self):
class BitSelectTestCase(FHDLTestCase):
def setUp(self):
self.c = Const(0, 8)
self.s = Signal(max=self.c.nbits)
self.s = Signal.range(self.c.width)

def test_shape(self):
s1 = self.c.bit_select(self.s, 2)
@@ -321,7 +405,7 @@ def test_repr(self):
class WordSelectTestCase(FHDLTestCase):
def setUp(self):
self.c = Const(0, 8)
self.s = Signal(max=self.c.nbits)
self.s = Signal.range(self.c.width)

def test_shape(self):
s1 = self.c.word_select(self.s, 2)
@@ -390,8 +474,8 @@ def test_acts_like_array(self):

def test_becomes_immutable(self):
a = Array([1,2,3])
s1 = Signal(max=len(a))
s2 = Signal(max=len(a))
s1 = Signal.range(len(a))
s2 = Signal.range(len(a))
v1 = a[s1]
v2 = a[s2]
with self.assertRaisesRegex(ValueError,
@@ -407,31 +491,31 @@ def test_becomes_immutable(self):
def test_repr(self):
a = Array([1,2,3])
self.assertEqual(repr(a), "(array mutable [1, 2, 3])")
s = Signal(max=len(a))
s = Signal.range(len(a))
v = a[s]
self.assertEqual(repr(a), "(array [1, 2, 3])")


class ArrayProxyTestCase(FHDLTestCase):
def test_index_shape(self):
m = Array(Array(x * y for y in range(1, 4)) for x in range(1, 4))
a = Signal(max=3)
b = Signal(max=3)
a = Signal.range(3)
b = Signal.range(3)
v = m[a][b]
self.assertEqual(v.shape(), (4, False))

def test_attr_shape(self):
from collections import namedtuple
pair = namedtuple("pair", ("p", "n"))
a = Array(pair(i, -i) for i in range(10))
s = Signal(max=len(a))
s = Signal.range(len(a))
v = a[s]
self.assertEqual(v.p.shape(), (4, False))
self.assertEqual(v.n.shape(), (6, True))

def test_repr(self):
a = Array([1, 2, 3])
s = Signal(max=3)
s = Signal.range(3)
v = a[s]
self.assertEqual(repr(v), "(proxy (array [1, 2, 3]) (sig s))")

@@ -446,30 +530,52 @@ def test_shape(self):
self.assertEqual(s3.shape(), (2, False))
s4 = Signal((2, True))
self.assertEqual(s4.shape(), (2, True))
s5 = Signal(max=16)
self.assertEqual(s5.shape(), (4, False))
s6 = Signal(min=4, max=16)
s5 = Signal(0)
self.assertEqual(s5.shape(), (0, False))
s6 = Signal.range(16)
self.assertEqual(s6.shape(), (4, False))
s7 = Signal(min=-4, max=16)
self.assertEqual(s7.shape(), (5, True))
s8 = Signal(min=-20, max=16)
self.assertEqual(s8.shape(), (6, True))
s9 = Signal(0)
self.assertEqual(s9.shape(), (0, False))
s10 = Signal(max=1)
self.assertEqual(s10.shape(), (0, False))
s7 = Signal.range(4, 16)
self.assertEqual(s7.shape(), (4, False))
s8 = Signal.range(-4, 16)
self.assertEqual(s8.shape(), (5, True))
s9 = Signal.range(-20, 16)
self.assertEqual(s9.shape(), (6, True))
s10 = Signal.range(0)
self.assertEqual(s10.shape(), (1, False))
s11 = Signal.range(1)
self.assertEqual(s11.shape(), (1, False))
# deprecated
with warnings.catch_warnings():
warnings.filterwarnings(action="ignore", category=DeprecationWarning)
d6 = Signal(max=16)
self.assertEqual(d6.shape(), (4, False))
d7 = Signal(min=4, max=16)
self.assertEqual(d7.shape(), (4, False))
d8 = Signal(min=-4, max=16)
self.assertEqual(d8.shape(), (5, True))
d9 = Signal(min=-20, max=16)
self.assertEqual(d9.shape(), (6, True))
d10 = Signal(max=1)
self.assertEqual(d10.shape(), (0, False))

def test_shape_bad(self):
with self.assertRaises(ValueError,
msg="Lower bound 10 should be less or equal to higher bound 4"):
Signal(min=10, max=4)
with self.assertRaises(ValueError,
msg="Only one of bits/signedness or bounds may be specified"):
Signal(2, min=10)
with self.assertRaises(TypeError,
msg="Width must be a non-negative integer, not '-10'"):
Signal(-10)

def test_min_max_deprecated(self):
with self.assertWarns(DeprecationWarning,
msg="instead of `Signal(min=0, max=10)`, use `Signal.range(0, 10)`"):
Signal(max=10)
with warnings.catch_warnings():
warnings.filterwarnings(action="ignore", category=DeprecationWarning)
with self.assertRaises(ValueError,
msg="Lower bound 10 should be less or equal to higher bound 4"):
Signal(min=10, max=4)
with self.assertRaises(ValueError,
msg="Only one of bits/signedness or bounds may be specified"):
Signal(2, min=10)

def test_name(self):
s1 = Signal()
self.assertEqual(s1.name, "s1")
@@ -487,6 +593,17 @@ def test_reset(self):
self.assertEqual(s1.reset, 0b111)
self.assertEqual(s1.reset_less, True)

def test_reset_narrow(self):
with self.assertWarns(SyntaxWarning,
msg="Reset value 8 requires 4 bits to represent, but the signal only has 3 bits"):
Signal(3, reset=8)
with self.assertWarns(SyntaxWarning,
msg="Reset value 4 requires 4 bits to represent, but the signal only has 3 bits"):
Signal((3, True), reset=4)
with self.assertWarns(SyntaxWarning,
msg="Reset value -5 requires 4 bits to represent, but the signal only has 3 bits"):
Signal((3, True), reset=-5)

def test_attrs(self):
s1 = Signal()
self.assertEqual(s1.attrs, {})
@@ -500,7 +617,7 @@ def test_repr(self):
def test_like(self):
s1 = Signal.like(Signal(4))
self.assertEqual(s1.shape(), (4, False))
s2 = Signal.like(Signal(min=-15))
s2 = Signal.like(Signal.range(-15, 1))
self.assertEqual(s2.shape(), (5, True))
s3 = Signal.like(Signal(4, reset=0b111, reset_less=True))
self.assertEqual(s3.reset, 0b111)
@@ -524,6 +641,13 @@ class Color(Enum):
self.assertEqual(s.decoder(1), "RED/1")
self.assertEqual(s.decoder(3), "3")

def test_enum(self):
s1 = Signal.enum(UnsignedEnum)
self.assertEqual(s1.shape(), (2, False))
s2 = Signal.enum(SignedEnum)
self.assertEqual(s2.shape(), (2, True))
self.assertEqual(s2.decoder(SignedEnum.FOO), "FOO/-1")


class ClockSignalTestCase(FHDLTestCase):
def test_domain(self):
@@ -606,7 +730,7 @@ def test_signal(self):

def test_wrong_value_operator(self):
with self.assertRaises(TypeError,
"Sampled value may only be a signal or a constant, not "
"Sampled value must be a signal or a constant, not "
"(+ (sig $signal) (const 1'd1))"):
Sample(Signal() + 1, 1, "sync")

@@ -615,6 +739,11 @@ def test_wrong_clocks_neg(self):
"Cannot sample a value 1 cycles in the future"):
Sample(Signal(), -1, "sync")

def test_wrong_domain(self):
with self.assertRaises(TypeError,
"Domain name must be a string or None, not 0"):
Sample(Signal(), 1, 0)


class InitialTestCase(FHDLTestCase):
def test_initial(self):
13 changes: 13 additions & 0 deletions nmigen/test/test_hdl_cd.py
Original file line number Diff line number Diff line change
@@ -23,6 +23,19 @@ def test_name(self):
cd_reset = ClockDomain(local=True)
self.assertEqual(cd_reset.local, True)

def test_edge(self):
sync = ClockDomain()
self.assertEqual(sync.clk_edge, "pos")
sync = ClockDomain(clk_edge="pos")
self.assertEqual(sync.clk_edge, "pos")
sync = ClockDomain(clk_edge="neg")
self.assertEqual(sync.clk_edge, "neg")

def test_edge_wrong(self):
with self.assertRaises(ValueError,
msg="Domain clock edge must be one of 'pos' or 'neg', not 'xxx'"):
ClockDomain("sync", clk_edge="xxx")

def test_with_reset(self):
pix = ClockDomain()
self.assertIsNotNone(pix.clk)
61 changes: 56 additions & 5 deletions nmigen/test/test_hdl_dsl.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections import OrderedDict
from enum import Enum

from ..hdl.ast import *
from ..hdl.cd import *
@@ -74,7 +75,7 @@ def test_d_wrong(self):
def test_d_asgn_wrong(self):
m = Module()
with self.assertRaises(SyntaxError,
msg="Only assignments, asserts, and assumes may be appended to d.sync"):
msg="Only assignments and property checks may be appended to d.sync"):
m.d.sync += Switch(self.s1, {})

def test_comb_wrong(self):
@@ -307,7 +308,7 @@ def test_Switch(self):
)
""")

def test_Switch_default(self):
def test_Switch_default_Case(self):
m = Module()
with m.Switch(self.w1):
with m.Case(3):
@@ -324,6 +325,23 @@ def test_Switch_default(self):
)
""")

def test_Switch_default_Default(self):
m = Module()
with m.Switch(self.w1):
with m.Case(3):
m.d.comb += self.c1.eq(1)
with m.Default():
m.d.comb += self.c2.eq(1)
m._flush()
self.assertRepr(m._statements, """
(
(switch (sig w1)
(case 0011 (eq (sig c1) (const 1'd1)))
(default (eq (sig c2) (const 1'd1)))
)
)
""")

def test_Switch_const_test(self):
m = Module()
with m.Switch(1):
@@ -338,16 +356,33 @@ def test_Switch_const_test(self):
)
""")

def test_Switch_enum(self):
class Color(Enum):
RED = 1
BLUE = 2
m = Module()
se = Signal.enum(Color)
with m.Switch(se):
with m.Case(Color.RED):
m.d.comb += self.c1.eq(1)
self.assertRepr(m._statements, """
(
(switch (sig se)
(case 01 (eq (sig c1) (const 1'd1)))
)
)
""")

def test_Case_width_wrong(self):
m = Module()
with m.Switch(self.w1):
with self.assertRaises(SyntaxError,
msg="Case value '--' must have the same width as test (which is 4)"):
msg="Case pattern '--' must have the same width as switch value (which is 4)"):
with m.Case("--"):
pass
with self.assertWarns(SyntaxWarning,
msg="Case value '10110' is wider than test (which has width 4); comparison "
"will never be true"):
msg="Case pattern '10110' is wider than switch value (which has width 4); "
"comparison will never be true"):
with m.Case(0b10110):
pass
self.assertRepr(m._statements, """
@@ -356,6 +391,22 @@ def test_Case_width_wrong(self):
)
""")

def test_Case_bits_wrong(self):
m = Module()
with m.Switch(self.w1):
with self.assertRaises(SyntaxError,
msg="Case pattern 'abc' must consist of 0, 1, and - (don't care) bits"):
with m.Case("abc"):
pass

def test_Case_pattern_wrong(self):
m = Module()
with m.Switch(self.w1):
with self.assertRaises(SyntaxError,
msg="Case pattern must be an integer, a string, or an enumeration, not 1.0"):
with m.Case(1.0):
pass

def test_Case_outside_Switch_wrong(self):
m = Module()
with self.assertRaises(SyntaxError,
9 changes: 5 additions & 4 deletions nmigen/test/test_hdl_mem.py
Original file line number Diff line number Diff line change
@@ -60,6 +60,7 @@ def test_read_port_non_transparent(self):
self.assertEqual(rdport.transparent, False)
self.assertEqual(len(rdport.en), 1)
self.assertIsInstance(rdport.en, Signal)
self.assertEqual(rdport.en.reset, 1)

def test_read_port_asynchronous(self):
mem = Memory(width=8, depth=4)
@@ -123,8 +124,8 @@ def test_name(self):

def test_sizes(self):
p1 = DummyPort(width=8, addr_bits=2)
self.assertEqual(p1.addr.nbits, 2)
self.assertEqual(p1.data.nbits, 8)
self.assertEqual(p1.en.nbits, 1)
self.assertEqual(p1.addr.width, 2)
self.assertEqual(p1.data.width, 8)
self.assertEqual(p1.en.width, 1)
p2 = DummyPort(width=8, addr_bits=2, granularity=2)
self.assertEqual(p2.en.nbits, 4)
self.assertEqual(p2.en.width, 4)
164 changes: 74 additions & 90 deletions nmigen/test/test_lib_fifo.py
Original file line number Diff line number Diff line change
@@ -12,10 +12,10 @@ def assertSyncFIFOWorks(self, fifo, xfrm=lambda x: x):
def process():
yield from fifo.write(1)
yield from fifo.write(2)
while not (yield fifo.readable):
while not (yield fifo.r_rdy):
yield
if not fifo.fwft:
yield fifo.re.eq(1)
yield fifo.r_en.eq(1)
yield
self.assertEqual((yield from fifo.read()), 1)
self.assertEqual((yield from fifo.read()), 2)
@@ -32,7 +32,7 @@ def test_sync_not_fwft(self):
self.assertSyncFIFOWorks(SyncFIFO(width=8, depth=4, fwft=False))

def test_sync_buffered(self):
self.assertSyncFIFOWorks(SyncFIFO(width=8, depth=4, fwft=True))
self.assertSyncFIFOWorks(SyncFIFOBuffered(width=8, depth=4))

def test_async(self):
self.assertAsyncFIFOWorks(AsyncFIFO(width=8, depth=4))
@@ -45,57 +45,48 @@ class FIFOModel(Elaboratable, FIFOInterface):
"""
Non-synthesizable first-in first-out queue, implemented naively as a chain of registers.
"""
def __init__(self, width, depth, fwft, rdomain, wdomain):
super().__init__(width, depth, fwft)
def __init__(self, width, depth, *, fwft, r_domain, w_domain):
super().__init__(width, depth, fwft=fwft)

self.rdomain = rdomain
self.wdomain = wdomain
self.r_domain = r_domain
self.w_domain = w_domain

self.replace = Signal()
self.level = Signal(max=self.depth + 1)
self.level = Signal.range(self.depth + 1)

def elaborate(self, platform):
m = Module()

storage = Memory(self.width, self.depth)
wrport = m.submodules.wrport = storage.write_port(domain=self.wdomain)
rdport = m.submodules.rdport = storage.read_port (domain="comb")
w_port = m.submodules.w_port = storage.write_port(domain=self.w_domain)
r_port = m.submodules.r_port = storage.read_port (domain="comb")

produce = Signal(max=self.depth)
consume = Signal(max=self.depth)
produce = Signal.range(self.depth)
consume = Signal.range(self.depth)

m.d.comb += self.readable.eq(self.level > 0)
m.d.comb += rdport.addr.eq((consume + 1) % self.depth)
m.d.comb += self.r_rdy.eq(self.level > 0)
m.d.comb += r_port.addr.eq((consume + 1) % self.depth)
if self.fwft:
m.d.comb += self.dout.eq(rdport.data)
with m.If(self.re & self.readable):
m.d.comb += self.r_data.eq(r_port.data)
with m.If(self.r_en & self.r_rdy):
if not self.fwft:
m.d[self.rdomain] += self.dout.eq(rdport.data)
m.d[self.rdomain] += consume.eq(rdport.addr)

m.d.comb += self.writable.eq(self.level < self.depth)
m.d.comb += wrport.data.eq(self.din)
with m.If(self.we):
with m.If(~self.replace & self.writable):
m.d.comb += wrport.addr.eq((produce + 1) % self.depth)
m.d.comb += wrport.en.eq(1)
m.d[self.wdomain] += produce.eq(wrport.addr)
with m.If(self.replace):
# The result of trying to replace an element in an empty queue is irrelevant.
# The result of trying to replace the element that is currently being read
# is undefined.
m.d.comb += Assume(self.level > 0)
m.d.comb += wrport.addr.eq(produce)
m.d.comb += wrport.en.eq(1)

with m.If(ResetSignal(self.rdomain) | ResetSignal(self.wdomain)):
m.d[self.r_domain] += self.r_data.eq(r_port.data)
m.d[self.r_domain] += consume.eq(r_port.addr)

m.d.comb += self.w_rdy.eq(self.level < self.depth)
m.d.comb += w_port.data.eq(self.w_data)
with m.If(self.w_en & self.w_rdy):
m.d.comb += w_port.addr.eq((produce + 1) % self.depth)
m.d.comb += w_port.en.eq(1)
m.d[self.w_domain] += produce.eq(w_port.addr)

with m.If(ResetSignal(self.r_domain) | ResetSignal(self.w_domain)):
m.d.sync += self.level.eq(0)
with m.Else():
m.d.sync += self.level.eq(self.level
+ (self.writable & self.we & ~self.replace)
- (self.readable & self.re))
+ (self.w_rdy & self.w_en)
- (self.r_rdy & self.r_en))

m.d.comb += Assert(ResetSignal(self.rdomain) == ResetSignal(self.wdomain))
m.d.comb += Assert(ResetSignal(self.r_domain) == ResetSignal(self.w_domain))

return m

@@ -106,40 +97,36 @@ class FIFOModelEquivalenceSpec(Elaboratable):
signals, the behavior of the implementation under test exactly matches the ideal model,
except for behavior not defined by the model.
"""
def __init__(self, fifo, rdomain, wdomain):
def __init__(self, fifo, r_domain, w_domain):
self.fifo = fifo

self.rdomain = rdomain
self.wdomain = wdomain
self.r_domain = r_domain
self.w_domain = w_domain

def elaborate(self, platform):
m = Module()
m.submodules.dut = dut = self.fifo
m.submodules.gold = gold = FIFOModel(dut.width, dut.depth, dut.fwft,
self.rdomain, self.wdomain)
m.submodules.gold = gold = FIFOModel(dut.width, dut.depth, fwft=dut.fwft,
r_domain=self.r_domain, w_domain=self.w_domain)

m.d.comb += [
gold.re.eq(dut.readable & dut.re),
gold.we.eq(dut.we),
gold.din.eq(dut.din),
gold.r_en.eq(dut.r_rdy & dut.r_en),
gold.w_en.eq(dut.w_en),
gold.w_data.eq(dut.w_data),
]
if hasattr(dut, "replace"):
m.d.comb += gold.replace.eq(dut.replace)
else:
m.d.comb += gold.replace.eq(0)

m.d.comb += Assert(dut.readable.implies(gold.readable))
m.d.comb += Assert(dut.writable.implies(gold.writable))
m.d.comb += Assert(dut.r_rdy.implies(gold.r_rdy))
m.d.comb += Assert(dut.w_rdy.implies(gold.w_rdy))
if hasattr(dut, "level"):
m.d.comb += Assert(dut.level == gold.level)

if dut.fwft:
m.d.comb += Assert(dut.readable
.implies(dut.dout == gold.dout))
m.d.comb += Assert(dut.r_rdy
.implies(dut.r_data == gold.r_data))
else:
m.d.comb += Assert((Past(dut.readable, domain=self.rdomain) &
Past(dut.re, domain=self.rdomain))
.implies(dut.dout == gold.dout))
m.d.comb += Assert((Past(dut.r_rdy, domain=self.r_domain) &
Past(dut.r_en, domain=self.r_domain))
.implies(dut.r_data == gold.r_data))

return m

@@ -150,10 +137,10 @@ class FIFOContractSpec(Elaboratable):
consecutively, they must be read out consecutively at some later point, no matter all other
circumstances, with the exception of reset.
"""
def __init__(self, fifo, rdomain, wdomain, bound):
def __init__(self, fifo, r_domain, w_domain, bound):
self.fifo = fifo
self.rdomain = rdomain
self.wdomain = wdomain
self.r_domain = r_domain
self.w_domain = w_domain
self.bound = bound

def elaborate(self, platform):
@@ -162,48 +149,45 @@ def elaborate(self, platform):

m.domains += ClockDomain("sync")
m.d.comb += ResetSignal().eq(0)
if self.wdomain != "sync":
m.domains += ClockDomain(self.wdomain)
m.d.comb += ResetSignal(self.wdomain).eq(0)
if self.rdomain != "sync":
m.domains += ClockDomain(self.rdomain)
m.d.comb += ResetSignal(self.rdomain).eq(0)

if hasattr(fifo, "replace"):
m.d.comb += fifo.replace.eq(0)
if self.w_domain != "sync":
m.domains += ClockDomain(self.w_domain)
m.d.comb += ResetSignal(self.w_domain).eq(0)
if self.r_domain != "sync":
m.domains += ClockDomain(self.r_domain)
m.d.comb += ResetSignal(self.r_domain).eq(0)

entry_1 = AnyConst(fifo.width)
entry_2 = AnyConst(fifo.width)

with m.FSM(domain=self.wdomain) as write_fsm:
with m.FSM(domain=self.w_domain) as write_fsm:
with m.State("WRITE-1"):
with m.If(fifo.writable):
with m.If(fifo.w_rdy):
m.d.comb += [
fifo.din.eq(entry_1),
fifo.we.eq(1)
fifo.w_data.eq(entry_1),
fifo.w_en.eq(1)
]
m.next = "WRITE-2"
with m.State("WRITE-2"):
with m.If(fifo.writable):
with m.If(fifo.w_rdy):
m.d.comb += [
fifo.din.eq(entry_2),
fifo.we.eq(1)
fifo.w_data.eq(entry_2),
fifo.w_en.eq(1)
]
m.next = "DONE"

with m.FSM(domain=self.rdomain) as read_fsm:
with m.FSM(domain=self.r_domain) as read_fsm:
read_1 = Signal(fifo.width)
read_2 = Signal(fifo.width)
with m.State("READ"):
m.d.comb += fifo.re.eq(1)
m.d.comb += fifo.r_en.eq(1)
if fifo.fwft:
readable = fifo.readable
r_rdy = fifo.r_rdy
else:
readable = Past(fifo.readable, domain=self.rdomain)
with m.If(readable):
r_rdy = Past(fifo.r_rdy, domain=self.r_domain)
with m.If(r_rdy):
m.d.sync += [
read_1.eq(read_2),
read_2.eq(fifo.dout),
read_2.eq(fifo.r_data),
]
with m.If((read_1 == entry_1) & (read_2 == entry_2)):
m.next = "DONE"
@@ -214,18 +198,18 @@ def elaborate(self, platform):
with m.If(Past(Initial(), self.bound - 1)):
m.d.comb += Assert(read_fsm.ongoing("DONE"))

if self.wdomain != "sync" or self.rdomain != "sync":
m.d.comb += Assume(Rose(ClockSignal(self.wdomain)) |
Rose(ClockSignal(self.rdomain)))
if self.w_domain != "sync" or self.r_domain != "sync":
m.d.comb += Assume(Rose(ClockSignal(self.w_domain)) |
Rose(ClockSignal(self.r_domain)))

return m


class FIFOFormalCase(FHDLTestCase):
def check_sync_fifo(self, fifo):
self.assertFormal(FIFOModelEquivalenceSpec(fifo, rdomain="sync", wdomain="sync"),
self.assertFormal(FIFOModelEquivalenceSpec(fifo, r_domain="sync", w_domain="sync"),
mode="bmc", depth=fifo.depth + 1)
self.assertFormal(FIFOContractSpec(fifo, rdomain="sync", wdomain="sync",
self.assertFormal(FIFOContractSpec(fifo, r_domain="sync", w_domain="sync",
bound=fifo.depth * 2 + 1),
mode="hybrid", depth=fifo.depth * 2 + 1)

@@ -253,9 +237,9 @@ def test_sync_buffered_potm1(self):
def check_async_fifo(self, fifo):
# TODO: properly doing model equivalence checking on this likely requires multiclock,
# which is not really documented nor is it clear how to use it.
# self.assertFormal(FIFOModelEquivalenceSpec(fifo, rdomain="read", wdomain="write"),
# self.assertFormal(FIFOModelEquivalenceSpec(fifo, r_domain="read", w_domain="write"),
# mode="bmc", depth=fifo.depth * 3 + 1)
self.assertFormal(FIFOContractSpec(fifo, rdomain="read", wdomain="write",
self.assertFormal(FIFOContractSpec(fifo, r_domain="read", w_domain="write",
bound=fifo.depth * 4 + 1),
mode="hybrid", depth=fifo.depth * 4 + 1)

40 changes: 36 additions & 4 deletions nmigen/test/test_sim.py
Original file line number Diff line number Diff line change
@@ -57,6 +57,27 @@ def test_bool(self):
self.assertStatement(stmt, [C(1, 4)], C(1))
self.assertStatement(stmt, [C(2, 4)], C(1))

def test_any(self):
stmt = lambda y, a: y.eq(a.any())
self.assertStatement(stmt, [C(0b00, 2)], C(0))
self.assertStatement(stmt, [C(0b01, 2)], C(1))
self.assertStatement(stmt, [C(0b10, 2)], C(1))
self.assertStatement(stmt, [C(0b11, 2)], C(1))

def test_all(self):
stmt = lambda y, a: y.eq(a.all())
self.assertStatement(stmt, [C(0b00, 2)], C(0))
self.assertStatement(stmt, [C(0b01, 2)], C(0))
self.assertStatement(stmt, [C(0b10, 2)], C(0))
self.assertStatement(stmt, [C(0b11, 2)], C(1))

def test_xor_unary(self):
stmt = lambda y, a: y.eq(a.xor())
self.assertStatement(stmt, [C(0b00, 2)], C(0))
self.assertStatement(stmt, [C(0b01, 2)], C(1))
self.assertStatement(stmt, [C(0b10, 2)], C(1))
self.assertStatement(stmt, [C(0b11, 2)], C(0))

def test_add(self):
stmt = lambda y, a, b: y.eq(a + b)
self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(1, 4))
@@ -82,7 +103,7 @@ def test_or(self):
stmt = lambda y, a, b: y.eq(a | b)
self.assertStatement(stmt, [C(0b1100, 4), C(0b1010, 4)], C(0b1110, 4))

def test_xor(self):
def test_xor_binary(self):
stmt = lambda y, a, b: y.eq(a ^ b)
self.assertStatement(stmt, [C(0b1100, 4), C(0b1010, 4)], C(0b0110, 4))

@@ -403,7 +424,7 @@ def test_add_process_wrong(self):
"a generator function"):
sim.add_process(1)

def test_add_clock_wrong(self):
def test_add_clock_wrong_twice(self):
m = Module()
s = Signal()
m.d.sync += s.eq(0)
@@ -413,6 +434,18 @@ def test_add_clock_wrong(self):
msg="Domain 'sync' already has a clock driving it"):
sim.add_clock(1)

def test_add_clock_wrong_missing(self):
m = Module()
with self.assertSimulation(m) as sim:
with self.assertRaises(ValueError,
msg="Domain 'sync' is not present in simulation"):
sim.add_clock(1)

def test_add_clock_if_exists(self):
m = Module()
with self.assertSimulation(m) as sim:
sim.add_clock(1, if_exists=True)

def test_eq_signal_unused_wrong(self):
self.setUp_lhs_rhs()
self.s = Signal()
@@ -515,9 +548,8 @@ def test_memory_read_before_write(self):
def process():
yield self.wrport.data.eq(0x33)
yield self.wrport.en.eq(1)
yield self.rdport.en.eq(1)
yield
self.assertEqual((yield self.rdport.data), 0x00)
self.assertEqual((yield self.rdport.data), 0xaa)
yield
self.assertEqual((yield self.rdport.data), 0xaa)
yield Delay(1e-6) # let comb propagate
3 changes: 2 additions & 1 deletion nmigen/test/tools.py
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@
from ..hdl.ast import *
from ..hdl.ir import *
from ..back import rtlil
from .._toolchain import require_tool


__all__ = ["FHDLTestCase"]
@@ -94,7 +95,7 @@ def assertFormal(self, spec, mode="bmc", depth=1):
script=script,
rtlil=rtlil.convert(Fragment.get(spec, platform="formal"))
)
with subprocess.Popen(["sby", "-f", "-d", spec_name], cwd=spec_dir,
with subprocess.Popen([require_tool("sby"), "-f", "-d", spec_name], cwd=spec_dir,
universal_newlines=True,
stdin=subprocess.PIPE, stdout=subprocess.PIPE) as proc:
stdout, stderr = proc.communicate(config)
165 changes: 165 additions & 0 deletions nmigen/vendor/altera.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
from abc import abstractproperty

from ..hdl import *
from ..build import *


__all__ = ["AlteraPlatform"]


class AlteraPlatform(TemplatedPlatform):
"""
Required tools:
* ``quartus_map``
* ``quartus_fit``
* ``quartus_asm``
* ``quartus_sta``
The environment is populated by running the script specified in the environment variable
``NMIGEN_Quartus_env``, if present.
Available overrides:
* ``nproc``: sets the number of cores used by all tools.
* ``quartus_map_opts``: adds extra options for ``quartus_map``.
* ``quartus_fit_opts``: adds extra options for ``quartus_fit``.
* ``quartus_asm_opts``: adds extra options for ``quartus_asm``.
* ``quartus_sta_opts``: adds extra options for ``quartus_sta``.
Build products:
* ``*.rpt``: toolchain reports.
* ``{{name}}.rbf``: raw binary bitstream.
"""

toolchain = "Quartus"

device = abstractproperty()
package = abstractproperty()
speed = abstractproperty()
suffix = ""

file_templates = {
**TemplatedPlatform.build_script_templates,
"build_{{name}}.sh": r"""
# {{autogenerated}}
if [ -n "$NMIGEN_{{platform.toolchain}}_env" ]; then
QUARTUS_ROOTDIR=$(dirname $(dirname "$NMIGEN_{{platform.toolchain}}_env"))
# Quartus' qenv.sh does not work with `set -e`.
. "$NMIGEN_{{platform.toolchain}}_env"
fi
set -e{{verbose("x")}}
{{emit_commands("sh")}}
""",
"{{name}}.v": r"""
/* {{autogenerated}} */
{{emit_design("verilog")}}
""",
"{{name}}.qsf": r"""
# {{autogenerated}}
{% if get_override("nproc") -%}
set_global_assignment -name NUM_PARALLEL_PROCESSORS {{get_override("nproc")}}
{% endif %}
{% for file in platform.iter_extra_files(".v") -%}
set_global_assignment -name VERILOG_FILE "{{file}}"
{% endfor %}
{% for file in platform.iter_extra_files(".sv") -%}
set_global_assignment -name SYSTEMVERILOG_FILE "{{file}}"
{% endfor %}
set_global_assignment -name VERILOG_FILE "{{name}}.v"
set_global_assignment -name TOP_LEVEL_ENTITY "{{name}}"
set_global_assignment -name DEVICE {{platform.device}}{{platform.package}}{{platform.speed}}{{platform.suffix}}
{% for port_name, pin_name, extras in platform.iter_port_constraints_bits() -%}
set_location_assignment -to "{{port_name}}" PIN_{{pin_name}}
{% for key, value in extras.items() -%}
set_instance_assignment -to "{{port_name}}" -name {{key}} "{{value}}"
{% endfor %}
{% endfor %}
set_global_assignment -name GENERATE_RBF_FILE ON
""",
"{{name}}.sdc": r"""
{% for signal, frequency in platform.iter_clock_constraints() -%}
create_clock -period {{1000000000/frequency}} [get_nets {{signal|hierarchy("/")}}]
{% endfor %}
""",
}
command_templates = [
r"""
{{get_tool("quartus_map")}}
{{get_override("quartus_map_opts")|options}}
--rev="{{name}}" "{{name}}"
""",
r"""
{{get_tool("quartus_fit")}}
{{get_override("quartus_fit_opts")|options}}
--rev="{{name}}" "{{name}}"
""",
r"""
{{get_tool("quartus_asm")}}
{{get_override("quartus_asm_opts")|options}}
--rev="{{name}}" "{{name}}"
""",
r"""
{{get_tool("quartus_sta")}}
{{get_override("quartus_sta_opts")|options}}
--rev="{{name}}" "{{name}}"
""",
]

def create_missing_domain(self, name):
# TODO: investigate this
return super().create_missing_domain(name)

# TODO: fix all of the following
@staticmethod
def _invert_if(invert, value):
if invert:
return ~value
else:
return value

def get_input(self, pin, port, attrs, invert):
self._check_feature("single-ended input", pin, attrs,
valid_xdrs=(0,), valid_attrs=True)

m = Module()
m.d.comb += pin.i.eq(self._invert_if(invert, port))
return m

def get_output(self, pin, port, attrs, invert):
self._check_feature("single-ended output", pin, attrs,
valid_xdrs=(0,), valid_attrs=True)

m = Module()
m.d.comb += port.eq(self._invert_if(invert, pin.o))
return m

def get_tristate(self, pin, port, attrs, invert):
self._check_feature("single-ended tristate", pin, attrs,
valid_xdrs=(0,), valid_attrs=True)

m = Module()
m.submodules += Instance("$tribuf",
p_WIDTH=pin.width,
i_EN=pin.oe,
i_A=self._invert_if(invert, pin.o),
o_Y=port,
)
return m

def get_input_output(self, pin, port, attrs, invert):
self._check_feature("single-ended input/output", pin, attrs,
valid_xdrs=(0,), valid_attrs=True)

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

# TODO: support differential IO
Loading