Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/nmigen/nmigen
Browse files Browse the repository at this point in the history
  • Loading branch information
sbourdeauducq committed Feb 2, 2020
2 parents c42c3a0 + a295e35 commit 60447a0
Show file tree
Hide file tree
Showing 16 changed files with 245 additions and 92 deletions.
16 changes: 7 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,24 @@ Other nMigen libraries are built on FHDL and provide various tools and logic cor

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](#migration-from-migen) that makes it possible to build and simulate most Migen designs unmodified, as well as integrate modules written for Migen and nMigen.
nMigen is based on [Migen][], a hardware description language developed by [M-Labs][]. Although Migen works very well in production, its design could be improved in many fundamental ways, and nMigen reimplements Migen concepts from scratch to do so. nMigen also provides an extensive [compatibility layer](#migration-from-migen) 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 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.
The development of nMigen has been supported by [SymbioticEDA][], [LambdaConcept][] and [M-Labs][].

[migen]: https://m-labs.hk/migen
[yosys]: http://www.clifford.at/yosys/
[symbioticeda]: https://www.symbioticeda.com/
[lambdaconcept]: http://lambdaconcept.com/

### HLS?

nMigen is *not* a "Python-to-FPGA" conventional high level synthesis (HLS) tool. It will *not* take a Python program as input and generate a hardware implementation of it. In nMigen, the Python program is executed by a regular Python interpreter, and it emits explicit statements in the FHDL domain-specific language. Writing a conventional HLS tool that uses nMigen as an internal component might be a good idea, on the other hand :)
[m-labs]: http://m-labs.hk

### 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
pip install git+https://github.com/nmigen/nmigen.git
pip install git+https://github.com/nmigen/nmigen-boards.git

### Introduction

Expand Down Expand Up @@ -70,7 +68,7 @@ nMigen is released under the very permissive two-clause BSD license. Under the t

Even though we do not require you to do so, these things are awesome, so please do them if possible:
* tell us that you are using nMigen
* put the [nMigen logo](doc/nmigen_logo.svg) on the page of a product using it, with a link to https://m-labs.hk
* put the [nMigen logo](doc/nmigen_logo.svg) on the page of a product using it
* cite nMigen in publications related to research it has helped
* send us feedback and suggestions for improvements
* send us bug reports when something goes wrong
Expand Down
45 changes: 45 additions & 0 deletions nmigen/_unused.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import sys
import warnings

from ._utils import get_linter_option


__all__ = ["UnusedMustUse", "MustUse"]


class UnusedMustUse(Warning):
pass


class MustUse:
_MustUse__silence = False
_MustUse__warning = UnusedMustUse

def __new__(cls, *args, src_loc_at=0, **kwargs):
frame = sys._getframe(1 + src_loc_at)
self = super().__new__(cls)
self._MustUse__used = False
self._MustUse__context = dict(
filename=frame.f_code.co_filename,
lineno=frame.f_lineno,
source=self)
return self

def __del__(self):
if self._MustUse__silence:
return
if hasattr(self, "_MustUse__used") and not self._MustUse__used:
if get_linter_option(self._MustUse__context["filename"],
self._MustUse__warning.__name__, bool, True):
warnings.warn_explicit(
"{!r} created but never used".format(self), self._MustUse__warning,
**self._MustUse__context)


_old_excepthook = sys.excepthook
def _silence_elaboratable(type, value, traceback):
# Don't show anything if the interpreter crashed; that'd just obscure the exception
# traceback instead of helping.
MustUse._MustUse__silence = True
_old_excepthook(type, value, traceback)
sys.excepthook = _silence_elaboratable
6 changes: 2 additions & 4 deletions nmigen/back/pysim.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,8 +347,6 @@ class _ValueCompiler(ValueVisitor, _Compiler):
helpers = {
"sign": lambda value, sign: value | sign if value & sign else value,
"zdiv": lambda lhs, rhs: 0 if rhs == 0 else lhs // rhs,
"sshl": lambda lhs, rhs: lhs << rhs if rhs >= 0 else lhs >> -rhs,
"sshr": lambda lhs, rhs: lhs >> rhs if rhs >= 0 else lhs << -rhs,
}

def on_ClockSignal(self, value):
Expand Down Expand Up @@ -438,9 +436,9 @@ def sign(value):
if value.operator == "^":
return f"({self(lhs)} ^ {self(rhs)})"
if value.operator == "<<":
return f"sshl({sign(lhs)}, {sign(rhs)})"
return f"({sign(lhs)} << {sign(rhs)})"
if value.operator == ">>":
return f"sshr({sign(lhs)}, {sign(rhs)})"
return f"({sign(lhs)} >> {sign(rhs)})"
if value.operator == "==":
return f"({sign(lhs)} == {sign(rhs)})"
if value.operator == "!=":
Expand Down
3 changes: 3 additions & 0 deletions nmigen/back/rtlil.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,9 @@ def add_port(self, signal, kind):
self.ports[signal] = (len(self.ports), kind)

def resolve(self, signal, prefix=None):
if len(signal) == 0:
return "{ }", "{ }"

if signal in self.wires:
return self.wires[signal]

Expand Down
10 changes: 6 additions & 4 deletions nmigen/build/dsl.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ def __init__(self, names, *, dir="io", invert=False, conn=None, assert_width=Non

if conn is not None:
conn_name, conn_number = conn
if not (isinstance(conn_name, str) and isinstance(conn_number, int)):
raise TypeError("Connector must be None or a pair of string and integer, not {!r}"
if not (isinstance(conn_name, str) and isinstance(conn_number, (int, str))):
raise TypeError("Connector must be None or a pair of string (connector name) and "
"integer/string (connector number), not {!r}"
.format(conn))
names = ["{}_{}:{}".format(conn_name, conn_number, name) for name in names]

Expand Down Expand Up @@ -236,8 +237,9 @@ def __init__(self, name, number, io, *, conn=None):

if conn is not None:
conn_name, conn_number = conn
if not (isinstance(conn_name, str) and isinstance(conn_number, int)):
raise TypeError("Connector must be None or a pair of string and integer, not {!r}"
if not (isinstance(conn_name, str) and isinstance(conn_number, (int, str))):
raise TypeError("Connector must be None or a pair of string (connector name) and "
"integer/string (connector number), not {!r}"
.format(conn))

for conn_pin, plat_pin in mapping.items():
Expand Down
7 changes: 5 additions & 2 deletions nmigen/build/plat.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .. import __version__
from .._toolchain import *
from ..hdl import *
from ..hdl.xfrm import SampleLowerer, DomainLowerer
from ..lib.cdc import ResetSynchronizer
from ..back import rtlil, verilog
from .res import *
Expand Down Expand Up @@ -115,7 +116,9 @@ def prepare(self, elaboratable, name="top", **kwargs):
self._prepared = True

fragment = Fragment.get(elaboratable, self)
fragment.create_missing_domains(self.create_missing_domain, platform=self)
fragment = SampleLowerer()(fragment)
fragment._propagate_domains(self.create_missing_domain, platform=self)
fragment = DomainLowerer()(fragment)

def add_pin_fragment(pin, pin_fragment):
pin_fragment = Fragment.get(pin_fragment, self)
Expand Down Expand Up @@ -144,7 +147,7 @@ def add_pin_fragment(pin, pin_fragment):
add_pin_fragment(pin,
self.get_diff_input_output(pin, p_port, n_port, attrs, invert))

fragment = fragment.prepare(ports=self.iter_ports(), missing_domain=lambda name: None)
fragment._propagate_ports(ports=self.iter_ports(), all_undef_as_ports=False)
return self.toolchain_prepare(fragment, name, **kwargs)

@abstractmethod
Expand Down
2 changes: 1 addition & 1 deletion nmigen/compat/fhdl/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def __iadd__(self, other):


class CompatModule(ir.Elaboratable):
_Elaboratable__silence = True
_MustUse__silence = True

# Actually returns another nMigen Elaboratable (nmigen.dsl.Module), not a Fragment.
def get_fragment(self):
Expand Down
51 changes: 33 additions & 18 deletions nmigen/hdl/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from .. import tracer
from .._utils import *
from .._unused import *


__all__ = [
Expand All @@ -17,9 +18,9 @@
"Signal", "ClockSignal", "ResetSignal",
"UserValue",
"Sample", "Past", "Stable", "Rose", "Fell", "Initial",
"Statement", "Assign", "Assert", "Assume", "Cover", "Switch",
"ValueKey", "ValueDict", "ValueSet", "SignalKey", "SignalDict",
"SignalSet",
"Statement", "Switch",
"Property", "Assign", "Assert", "Assume", "Cover",
"ValueKey", "ValueDict", "ValueSet", "SignalKey", "SignalDict", "SignalSet",
]


Expand Down Expand Up @@ -171,14 +172,28 @@ def __rfloordiv__(self, other):
self.__check_divisor()
return Operator("//", [other, self])

def __check_shamt(self):
width, signed = self.shape()
if signed:
# Neither Python nor HDLs implement shifts by negative values; prohibit any shifts
# by a signed value to make sure the shift amount can always be interpreted as
# an unsigned value.
raise NotImplementedError("Shift by a signed value is not supported")
def __lshift__(self, other):
other = Value.cast(other)
other.__check_shamt()
return Operator("<<", [self, other])
def __rlshift__(self, other):
self.__check_shamt()
return Operator("<<", [other, self])
def __rshift__(self, other):
other = Value.cast(other)
other.__check_shamt()
return Operator(">>", [self, other])
def __rrshift__(self, other):
self.__check_shamt()
return Operator(">>", [other, self])

def __and__(self, other):
return Operator("&", [self, other])
def __rand__(self, other):
Expand Down Expand Up @@ -493,13 +508,13 @@ def _rhs_signals(self):
@final
class AnyConst(AnyValue):
def __repr__(self):
return "(anyconst {}'{})".format(self.nbits, "s" if self.signed else "")
return "(anyconst {}'{})".format(self.width, "s" if self.signed else "")


@final
class AnySeq(AnyValue):
def __repr__(self):
return "(anyseq {}'{})".format(self.nbits, "s" if self.signed else "")
return "(anyseq {}'{})".format(self.width, "s" if self.signed else "")


@final
Expand Down Expand Up @@ -758,15 +773,13 @@ class Signal(Value, DUID):
Parameters
----------
shape : int or tuple or None
Either an integer ``width`` or a tuple ``(width, signed)`` specifying the number of bits
in this ``Signal`` and whether it is signed (can represent negative values).
``shape`` defaults to 1-bit and non-signed.
shape : ``Shape``-castable object or None
Specification for the number of bits in this ``Signal`` and its signedness (whether it
can represent negative values). See ``Shape.cast`` for details.
If not specified, ``shape`` defaults to 1-bit and non-signed.
name : str
Name hint for this signal. If ``None`` (default) the name is inferred from the variable
name this ``Signal`` is assigned to. Name collisions are automatically resolved by
prepending names of objects that contain this ``Signal`` and by appending integer
sequences.
name this ``Signal`` is assigned to.
reset : int or integral Enum
Reset (synchronous) or default (combinatorial) value.
When this ``Signal`` is assigned to in synchronous context and the corresponding clock
Expand All @@ -777,11 +790,6 @@ class Signal(Value, DUID):
If ``True``, do not generate reset logic for this ``Signal`` in synchronous statements.
The ``reset`` value is only used as a combinatorial default or as the initial value.
Defaults to ``False``.
min : int or None
max : int or None
If ``shape`` is ``None``, the signal bit width and signedness are
determined by the integer range given by ``min`` (inclusive,
defaults to 0) and ``max`` (exclusive, defaults to 2).
attrs : dict
Dictionary of synthesis attributes.
decoder : function or Enum
Expand All @@ -798,6 +806,7 @@ class Signal(Value, DUID):
reset : int
reset_less : bool
attrs : dict
decoder : function
"""

def __init__(self, shape=None, *, name=None, reset=0, reset_less=False,
Expand Down Expand Up @@ -1221,7 +1230,13 @@ def __repr__(self):
return "(eq {!r} {!r})".format(self.lhs, self.rhs)


class Property(Statement):
class UnusedProperty(UnusedMustUse):
pass


class Property(Statement, MustUse):
_MustUse__warning = UnusedProperty

def __init__(self, test, *, _check=None, _en=None, src_loc_at=0):
super().__init__(src_loc_at=src_loc_at)
self.test = Value.cast(test)
Expand Down
Loading

0 comments on commit 60447a0

Please sign in to comment.