Rename fhdl→hdl, genlib→lib.

This commit is contained in:
whitequark 2018-12-15 14:23:42 +00:00
parent b5a1efa0c8
commit 790eb05a92
26 changed files with 50 additions and 50 deletions

0
nmigen/hdl/__init__.py Normal file
View file

879
nmigen/hdl/ast.py Normal file
View file

@ -0,0 +1,879 @@
from abc import ABCMeta, abstractmethod
import builtins
import traceback
from collections import OrderedDict
from collections.abc import Iterable, MutableMapping, MutableSet
from .. import tracer
from ..tools import *
__all__ = [
"Value", "Const", "C", "Operator", "Mux", "Part", "Slice", "Cat", "Repl",
"Signal", "ClockSignal", "ResetSignal",
"Statement", "Assign", "Switch", "Delay", "Tick", "Passive",
"ValueKey", "ValueDict", "ValueSet",
]
class DUID:
"""Deterministic Unique IDentifier"""
__next_uid = 0
def __init__(self):
self.duid = DUID.__next_uid
DUID.__next_uid += 1
class Value(metaclass=ABCMeta):
@staticmethod
def wrap(obj):
"""Ensures that the passed object is a Migen value. Booleans and integers
are automatically wrapped into ``Const``."""
if isinstance(obj, Value):
return obj
elif isinstance(obj, (bool, int)):
return Const(obj)
else:
raise TypeError("Object '{!r}' is not a Migen value".format(obj))
def __init__(self, src_loc_at=0):
super().__init__()
src_loc_at += 3
tb = traceback.extract_stack(limit=src_loc_at)
if len(tb) < src_loc_at:
self.src_loc = None
else:
self.src_loc = (tb[0].filename, tb[0].lineno)
def __bool__(self):
raise TypeError("Attempted to convert Migen value to boolean")
def __invert__(self):
return Operator("~", [self])
def __neg__(self):
return Operator("-", [self])
def __add__(self, other):
return Operator("+", [self, other])
def __radd__(self, other):
return Operator("+", [other, self])
def __sub__(self, other):
return Operator("-", [self, other])
def __rsub__(self, other):
return Operator("-", [other, self])
def __mul__(self, other):
return Operator("*", [self, other])
def __rmul__(self, other):
return Operator("*", [other, self])
def __mod__(self, other):
return Operator("%", [self, other])
def __rmod__(self, other):
return Operator("%", [other, self])
def __div__(self, other):
return Operator("/", [self, other])
def __rdiv__(self, other):
return Operator("/", [other, self])
def __lshift__(self, other):
return Operator("<<", [self, other])
def __rlshift__(self, other):
return Operator("<<", [other, self])
def __rshift__(self, other):
return Operator(">>", [self, other])
def __rrshift__(self, other):
return Operator(">>", [other, self])
def __and__(self, other):
return Operator("&", [self, other])
def __rand__(self, other):
return Operator("&", [other, self])
def __xor__(self, other):
return Operator("^", [self, other])
def __rxor__(self, other):
return Operator("^", [other, self])
def __or__(self, other):
return Operator("|", [self, other])
def __ror__(self, other):
return Operator("|", [other, self])
def __eq__(self, other):
return Operator("==", [self, other])
def __ne__(self, other):
return Operator("!=", [self, other])
def __lt__(self, other):
return Operator("<", [self, other])
def __le__(self, other):
return Operator("<=", [self, other])
def __gt__(self, other):
return Operator(">", [self, other])
def __ge__(self, other):
return Operator(">=", [self, other])
def __len__(self):
return self.shape()[0]
def __getitem__(self, key):
n = len(self)
if isinstance(key, int):
if key not in range(-n, n):
raise IndexError("Cannot index {} bits into {}-bit value".format(key, n))
if key < 0:
key += n
return Slice(self, key, key + 1)
elif isinstance(key, slice):
start, stop, step = key.indices(n)
if step != 1:
return Cat(self[i] for i in range(start, stop, step))
return Slice(self, start, stop)
else:
raise TypeError("Cannot index value with {}".format(repr(key)))
def bool(self):
"""Conversion to boolean.
Returns
-------
Value, out
Output ``Value``. If any bits are set, returns ``1``, else ``0``.
"""
return Operator("b", [self])
def part(self, offset, width):
"""Indexed part-select.
Selects a constant width but variable offset part of a ``Value``.
Parameters
----------
offset : Value, in
start point of the selected bits
width : int
number of selected bits
Returns
-------
Part, out
Selected part of the ``Value``
"""
return Part(self, offset, width)
def eq(self, value):
"""Assignment.
Parameters
----------
value : Value, in
Value to be assigned.
Returns
-------
Assign
Assignment statement that can be used in combinatorial or synchronous context.
"""
return Assign(self, value)
@abstractmethod
def shape(self):
"""Bit length and signedness of a value.
Returns
-------
int, bool
Number of bits required to store `v` or available in `v`, followed by
whether `v` has a sign bit (included in the bit count).
Examples
--------
>>> Value.shape(Signal(8))
8, False
>>> Value.shape(C(0xaa))
8, False
"""
pass # :nocov:
def _lhs_signals(self):
raise TypeError("Value {!r} cannot be used in assignments".format(self))
@abstractmethod
def _rhs_signals(self):
pass # :nocov:
__hash__ = None
class Const(Value):
"""A constant, literal integer value.
Parameters
----------
value : int
shape : int or tuple or None
Either an integer `bits` or a tuple `(bits, signed)`
specifying the number of bits in this `Const` and whether it is
signed (can represent negative values). `shape` defaults
to the minimum width and signedness of `value`.
Attributes
----------
nbits : int
signed : bool
"""
src_loc = None
@staticmethod
def normalize(value, shape):
nbits, signed = shape
mask = (1 << nbits) - 1
value &= mask
if signed and value >> (nbits - 1):
value |= ~mask
return value
def __init__(self, value, shape=None):
self.value = int(value)
if shape is None:
shape = bits_for(self.value), self.value < 0
if isinstance(shape, int):
shape = shape, self.value < 0
self.nbits, self.signed = shape
if not isinstance(self.nbits, int) or self.nbits < 0:
raise TypeError("Width must be a non-negative integer")
self.value = self.normalize(self.value, shape)
def shape(self):
return self.nbits, self.signed
def _rhs_signals(self):
return ValueSet()
def __repr__(self):
return "(const {}'{}d{})".format(self.nbits, "s" if self.signed else "", self.value)
C = Const # shorthand
class Operator(Value):
def __init__(self, op, operands, src_loc_at=0):
super().__init__(src_loc_at=1 + src_loc_at)
self.op = op
self.operands = [Value.wrap(o) for o in operands]
@staticmethod
def _bitwise_binary_shape(a_shape, b_shape):
a_bits, a_sign = a_shape
b_bits, b_sign = b_shape
if not a_sign and not b_sign:
# both operands unsigned
return max(a_bits, b_bits), False
elif a_sign and b_sign:
# both operands signed
return max(a_bits, b_bits), True
elif not a_sign and b_sign:
# first operand unsigned (add sign bit), second operand signed
return max(a_bits + 1, b_bits), True
else:
# first signed, second operand unsigned (add sign bit)
return max(a_bits, b_bits + 1), True
def shape(self):
op_shapes = list(map(lambda x: x.shape(), self.operands))
if len(op_shapes) == 1:
(a_bits, a_sign), = op_shapes
if self.op in ("+", "~"):
return a_bits, a_sign
if self.op == "-":
if not a_sign:
return a_bits + 1, True
else:
return a_bits, a_sign
if self.op == "b":
return 1, False
elif len(op_shapes) == 2:
(a_bits, a_sign), (b_bits, b_sign) = op_shapes
if self.op == "+" or self.op == "-":
bits, sign = self._bitwise_binary_shape(*op_shapes)
return bits + 1, sign
if self.op == "*":
if not a_sign and not b_sign:
# both operands unsigned
return a_bits + b_bits, False
if a_sign and b_sign:
# both operands signed
return a_bits + b_bits - 1, True
# one operand signed, the other unsigned (add sign bit)
return a_bits + b_bits + 1 - 1, True
if self.op in ("<", "<=", "==", "!=", ">", ">=", "b"):
return 1, False
if self.op in ("&", "^", "|"):
return self._bitwise_binary_shape(*op_shapes)
if self.op == "<<":
if b_sign:
extra = 2 ** (b_bits - 1) - 1
else:
extra = 2 ** (b_bits) - 1
return a_bits + extra, a_sign
if self.op == ">>":
if b_sign:
extra = 2 ** (b_bits - 1)
else:
extra = 0
return a_bits + extra, a_sign
elif len(op_shapes) == 3:
if self.op == "m":
s_shape, a_shape, b_shape = op_shapes
return self._bitwise_binary_shape(a_shape, b_shape)
raise NotImplementedError("Operator {}/{} not implemented"
.format(self.op, len(op_shapes))) # :nocov:
def _rhs_signals(self):
return union(op._rhs_signals() for op in self.operands)
def __repr__(self):
return "({} {})".format(self.op, " ".join(map(repr, self.operands)))
def Mux(sel, val1, val0):
"""Choose between two values.
Parameters
----------
sel : Value, in
Selector.
val1 : Value, in
val0 : Value, in
Input values.
Returns
-------
Value, out
Output ``Value``. If ``sel`` is asserted, the Mux returns ``val1``, else ``val0``.
"""
return Operator("m", [sel, val1, val0], src_loc_at=1)
class Slice(Value):
def __init__(self, value, start, end):
if not isinstance(start, int):
raise TypeError("Slice start must be an integer, not '{!r}'".format(start))
if not isinstance(end, int):
raise TypeError("Slice end must be an integer, not '{!r}'".format(end))
n = len(value)
if start not in range(-n, n):
raise IndexError("Cannot start slice {} bits into {}-bit value".format(start, n))
if start < 0:
start += n
if end not in range(-(n+1), n+1):
raise IndexError("Cannot end slice {} bits into {}-bit value".format(end, n))
if end < 0:
end += n
if start > end:
raise IndexError("Slice start {} must be less than slice end {}".format(start, end))
super().__init__()
self.value = Value.wrap(value)
self.start = start
self.end = end
def shape(self):
return self.end - self.start, False
def _lhs_signals(self):
return self.value._lhs_signals()
def _rhs_signals(self):
return self.value._rhs_signals()
def __repr__(self):
return "(slice {} {}:{})".format(repr(self.value), self.start, self.end)
class Part(Value):
def __init__(self, value, offset, width):
if not isinstance(width, int) or width < 0:
raise TypeError("Part width must be a non-negative integer, not '{!r}'".format(width))
super().__init__()
self.value = value
self.offset = Value.wrap(offset)
self.width = width
def shape(self):
return self.width, False
def _lhs_signals(self):
return self.value._lhs_signals()
def _rhs_signals(self):
return self.value._rhs_signals()
def __repr__(self):
return "(part {} {} {})".format(repr(self.value), repr(self.offset), self.width)
class Cat(Value):
"""Concatenate values.
Form a compound ``Value`` from several smaller ones by concatenation.
The first argument occupies the lower bits of the result.
The return value can be used on either side of an assignment, that
is, the concatenated value can be used as an argument on the RHS or
as a target on the LHS. If it is used on the LHS, it must solely
consist of ``Signal`` s, slices of ``Signal`` s, and other concatenations
meeting these properties. The bit length of the return value is the sum of
the bit lengths of the arguments::
len(Cat(args)) == sum(len(arg) for arg in args)
Parameters
----------
*args : Values or iterables of Values, inout
``Value`` s to be concatenated.
Returns
-------
Value, inout
Resulting ``Value`` obtained by concatentation.
"""
def __init__(self, *args):
super().__init__()
self.operands = [Value.wrap(v) for v in flatten(args)]
def shape(self):
return sum(len(op) for op in self.operands), False
def _lhs_signals(self):
return union(op._lhs_signals() for op in self.operands)
def _rhs_signals(self):
return union(op._rhs_signals() for op in self.operands)
def __repr__(self):
return "(cat {})".format(" ".join(map(repr, self.operands)))
class Repl(Value):
"""Replicate a value
An input value is replicated (repeated) several times
to be used on the RHS of assignments::
len(Repl(s, n)) == len(s) * n
Parameters
----------
value : Value, in
Input value to be replicated.
count : int
Number of replications.
Returns
-------
Repl, out
Replicated value.
"""
def __init__(self, value, count):
if not isinstance(count, int) or count < 0:
raise TypeError("Replication count must be a non-negative integer, not '{!r}'"
.format(count))
super().__init__()
self.value = Value.wrap(value)
self.count = count
def shape(self):
return len(self.value) * self.count, False
def _rhs_signals(self):
return self.value._rhs_signals()
def __repr__(self):
return "(repl {!r} {})".format(self.value, self.count)
class Signal(Value, DUID):
"""A varying integer value.
Parameters
----------
shape : int or tuple or None
Either an integer ``bits`` or a tuple ``(bits, 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.
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.
reset : int
Reset (synchronous) or default (combinatorial) value.
When this ``Signal`` is assigned to in synchronous context and the corresponding clock
domain is reset, the ``Signal`` assumes the given value. When this ``Signal`` is unassigned
in combinatorial context (due to conditional assignments not being taken), the ``Signal``
assumes its ``reset`` value. Defaults to 0.
reset_less : bool
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
A function converting integer signal values to human-readable strings (e.g. FSM state
names).
Attributes
----------
nbits : int
signed : bool
name : str
reset : int
reset_less : bool
attrs : dict
"""
def __init__(self, shape=None, name=None, reset=0, reset_less=False, min=None, max=None,
attrs=None, decoder=None, src_loc_at=0):
super().__init__(src_loc_at=src_loc_at)
if name is None:
try:
name = tracer.get_var_name(depth=2 + src_loc_at)
except tracer.NameNotFound:
name = "$signal"
self.name = name
if shape is None:
if min is None:
min = 0
if max is None:
max = 2
max -= 1 # make both bounds inclusive
if not min < max:
raise ValueError("Lower bound {} should be less than higher bound {}"
.format(min, max))
self.signed = min < 0 or max < 0
self.nbits = builtins.max(bits_for(min, self.signed), bits_for(max, self.signed))
else:
if not (min is None and max is None):
raise ValueError("Only one of bits/signedness or bounds may be specified")
if isinstance(shape, int):
self.nbits, self.signed = shape, False
else:
self.nbits, self.signed = shape
if not isinstance(self.nbits, int) or self.nbits < 0:
raise TypeError("Width must be a non-negative integer, not '{!r}'".format(self.nbits))
self.reset = int(reset)
self.reset_less = bool(reset_less)
self.attrs = OrderedDict(() if attrs is None else attrs)
self.decoder = decoder
@classmethod
def like(cls, other, src_loc_at=0, **kwargs):
"""Create Signal based on another.
Parameters
----------
other : Value
Object to base this Signal on.
"""
kw = dict(shape=cls.wrap(other).shape(),
name=tracer.get_var_name(depth=2 + src_loc_at))
if isinstance(other, cls):
kw.update(reset=other.reset, reset_less=other.reset_less,
attrs=other.attrs, decoder=other.decoder)
kw.update(kwargs)
return cls(**kw, src_loc_at=1 + src_loc_at)
def shape(self):
return self.nbits, self.signed
def _lhs_signals(self):
return ValueSet((self,))
def _rhs_signals(self):
return ValueSet((self,))
def __repr__(self):
return "(sig {})".format(self.name)
class ClockSignal(Value):
"""Clock signal for a given clock domain.
``ClockSignal`` s for a given clock domain can be retrieved multiple
times. They all ultimately refer to the same signal.
Parameters
----------
domain : str
Clock domain to obtain a clock signal for. Defaults to ``"sync"``.
"""
def __init__(self, domain="sync"):
super().__init__()
if not isinstance(domain, str):
raise TypeError("Clock domain name must be a string, not '{!r}'".format(domain))
self.domain = domain
def shape(self):
return 1, False
def _rhs_signals(self):
raise NotImplementedError("ClockSignal must be lowered to a concrete signal") # :nocov:
def __repr__(self):
return "(clk {})".format(self.domain)
class ResetSignal(Value):
"""Reset signal for a given clock domain
``ResetSignal`` s for a given clock domain can be retrieved multiple
times. They all ultimately refer to the same signal.
Parameters
----------
domain : str
Clock domain to obtain a reset signal for. Defaults to ``"sync"``.
allow_reset_less : bool
If the clock domain is reset-less, act as a constant ``0`` instead of reporting an error.
"""
def __init__(self, domain="sync", allow_reset_less=False):
super().__init__()
if not isinstance(domain, str):
raise TypeError("Clock domain name must be a string, not '{!r}'".format(domain))
self.domain = domain
self.allow_reset_less = allow_reset_less
def shape(self):
return 1, False
def _rhs_signals(self):
raise NotImplementedError("ResetSignal must be lowered to a concrete signal") # :nocov:
def __repr__(self):
return "(rst {})".format(self.domain)
class _StatementList(list):
def __repr__(self):
return "({})".format(" ".join(map(repr, self)))
class Statement:
@staticmethod
def wrap(obj):
if isinstance(obj, Iterable):
return _StatementList(sum((Statement.wrap(e) for e in obj), []))
else:
if isinstance(obj, Statement):
return _StatementList([obj])
else:
raise TypeError("Object '{!r}' is not a Migen statement".format(obj))
class Assign(Statement):
def __init__(self, lhs, rhs):
self.lhs = Value.wrap(lhs)
self.rhs = Value.wrap(rhs)
def _lhs_signals(self):
return self.lhs._lhs_signals()
def _rhs_signals(self):
return self.rhs._rhs_signals()
def __repr__(self):
return "(eq {!r} {!r})".format(self.lhs, self.rhs)
class Switch(Statement):
def __init__(self, test, cases):
self.test = Value.wrap(test)
self.cases = OrderedDict()
for key, stmts in cases.items():
if isinstance(key, (bool, int)):
key = "{:0{}b}".format(key, len(self.test))
elif isinstance(key, str):
assert len(key) == len(self.test)
else:
raise TypeError("Object '{!r}' cannot be used as a switch key"
.format(key))
if not isinstance(stmts, Iterable):
stmts = [stmts]
self.cases[key] = Statement.wrap(stmts)
def _lhs_signals(self):
signals = union(s._lhs_signals() for ss in self.cases.values() for s in ss) or ValueSet()
return signals
def _rhs_signals(self):
signals = union(s._rhs_signals() for ss in self.cases.values() for s in ss) or ValueSet()
return self.test._rhs_signals() | signals
def __repr__(self):
cases = ["(case {} {})".format(key, " ".join(map(repr, stmts)))
for key, stmts in self.cases.items()]
return "(switch {!r} {})".format(self.test, " ".join(cases))
class Delay(Statement):
def __init__(self, interval=None):
self.interval = None if interval is None else float(interval)
def _rhs_signals(self):
return ValueSet()
def __repr__(self):
if self.interval is None:
return "(delay ε)"
else:
return "(delay {:.3}us)".format(self.interval * 10e6)
class Tick(Statement):
def __init__(self, domain):
self.domain = str(domain)
def _rhs_signals(self):
return ValueSet()
def __repr__(self):
return "(tick {})".format(self.domain)
class Passive(Statement):
def _rhs_signals(self):
return ValueSet()
def __repr__(self):
return "(passive)"
class ValueKey:
def __init__(self, value):
self.value = Value.wrap(value)
def __hash__(self):
if isinstance(self.value, Const):
return hash(self.value)
elif isinstance(self.value, Signal):
return hash(id(self.value))
elif isinstance(self.value, Slice):
return hash((ValueKey(self.value.value), self.value.start, self.value.end))
else: # :nocov:
raise TypeError("Object '{!r}' cannot be used as a key in value collections")
def __eq__(self, other):
if not isinstance(other, ValueKey):
return False
if type(self.value) != type(other.value):
return False
if isinstance(self.value, Const):
return self.value == other.value
elif isinstance(self.value, Signal):
return id(self.value) == id(other.value)
elif isinstance(self.value, Slice):
return (ValueKey(self.value.value) == ValueKey(other.value.value) and
self.value.start == other.value.start and
self.value.end == other.value.end)
else: # :nocov:
raise TypeError("Object '{!r}' cannot be used as a key in value collections")
def __lt__(self, other):
if not isinstance(other, ValueKey):
return False
if type(self.value) != type(other.value):
return False
if isinstance(self.value, Const):
return self.value < other.value
elif isinstance(self.value, Signal):
return self.value.duid < other.value.duid
elif isinstance(self.value, Slice):
return (ValueKey(self.value.value) < ValueKey(other.value.value) and
self.value.start < other.value.start and
self.value.end < other.value.end)
else: # :nocov:
raise TypeError("Object '{!r}' cannot be used as a key in value collections")
def __repr__(self):
return "<{}.ValueKey {!r}>".format(__name__, self.value)
class ValueDict(MutableMapping):
def __init__(self, pairs=()):
self._inner = dict()
for key, value in pairs:
self[key] = value
def __getitem__(self, key):
key = None if key is None else ValueKey(key)
return self._inner[key]
def __setitem__(self, key, value):
key = None if key is None else ValueKey(key)
self._inner[key] = value
def __delitem__(self, key):
key = None if key is None else ValueKey(key)
del self._inner[key]
def __iter__(self):
return map(lambda x: None if x is None else x.value, sorted(self._inner))
def __eq__(self, other):
if not isinstance(other, ValueDict):
return False
if len(self) != len(other):
return False
for ak, bk in zip(self, other):
if ValueKey(ak) != ValueKey(bk):
return False
if self[ak] != other[bk]:
return False
return True
def __len__(self):
return len(self._inner)
def __repr__(self):
pairs = ["({!r}, {!r})".format(k, v) for k, v in self.items()]
return "ValueDict([{}])".format(", ".join(pairs))
class ValueSet(MutableSet):
def __init__(self, elements=()):
self._inner = set()
for elem in elements:
self.add(elem)
def add(self, value):
self._inner.add(ValueKey(value))
def update(self, values):
for value in values:
self.add(value)
def discard(self, value):
self._inner.discard(ValueKey(value))
def __contains__(self, value):
return ValueKey(value) in self._inner
def __iter__(self):
return map(lambda x: x.value, sorted(self._inner))
def __len__(self):
return len(self._inner)
def __repr__(self):
return "ValueSet({})".format(", ".join(repr(x) for x in self))

67
nmigen/hdl/cd.py Normal file
View file

@ -0,0 +1,67 @@
from .. import tracer
from .ast import Signal
__all__ = ["ClockDomain", "DomainError"]
class DomainError(Exception):
pass
class ClockDomain:
"""Synchronous domain.
Parameters
----------
name : str or None
Domain name. If ``None`` (the default) the name is inferred from the variable name this
``ClockDomain`` is assigned to (stripping any `"cd_"` prefix).
reset_less : bool
If ``True``, the domain does not use a reset signal. Registers within this domain are
still all initialized to their reset state once, e.g. through Verilog `"initial"`
statements.
async_reset : bool
If ``True``, the domain uses an asynchronous reset, and registers within this domain
are initialized to their reset state when reset level changes. Otherwise, registers
are initialized to reset state at the next clock cycle when reset is asserted.
Attributes
----------
clk : Signal, inout
The clock for this domain. Can be driven or used to drive other signals (preferably
in combinatorial context).
rst : Signal or None, inout
Reset signal for this domain. Can be driven or used to drive.
"""
@staticmethod
def _name_for(domain_name, signal_name):
if domain_name == "sync":
return signal_name
else:
return "{}_{}".format(domain_name, signal_name)
def __init__(self, name=None, reset_less=False, async_reset=False):
if name is None:
try:
name = tracer.get_var_name()
except tracer.NameNotFound:
raise ValueError("Clock domain name must be specified explicitly")
if name.startswith("cd_"):
name = name[3:]
self.name = name
self.clk = Signal(name=self._name_for(name, "clk"), src_loc_at=1)
if reset_less:
self.rst = None
else:
self.rst = Signal(name=self._name_for(name, "rst"), src_loc_at=1)
self.async_reset = async_reset
def rename(self, new_name):
self.name = new_name
self.clk.name = self._name_for(new_name, "clk")
if self.rst is not None:
self.rst.name = self._name_for(new_name, "rst")

281
nmigen/hdl/dsl.py Normal file
View file

@ -0,0 +1,281 @@
from collections import OrderedDict
from collections.abc import Iterable
from contextlib import contextmanager
from .ast import *
from .ir import *
from .xfrm import *
__all__ = ["Module", "SyntaxError"]
class SyntaxError(Exception):
pass
class _ModuleBuilderProxy:
def __init__(self, builder, depth):
object.__setattr__(self, "_builder", builder)
object.__setattr__(self, "_depth", depth)
class _ModuleBuilderDomain(_ModuleBuilderProxy):
def __init__(self, builder, depth, domain):
super().__init__(builder, depth)
self._domain = domain
def __iadd__(self, assigns):
self._builder._add_statement(assigns, domain=self._domain, depth=self._depth)
return self
class _ModuleBuilderDomains(_ModuleBuilderProxy):
def __getattr__(self, name):
if name == "comb":
domain = None
else:
domain = name
return _ModuleBuilderDomain(self._builder, self._depth, domain)
def __getitem__(self, name):
return self.__getattr__(name)
def __setattr__(self, name, value):
if name == "_depth":
object.__setattr__(self, name, value)
elif not isinstance(value, _ModuleBuilderDomain):
raise AttributeError("Cannot assign 'd.{}' attribute; did you mean 'd.{} +='?"
.format(name, name))
def __setitem__(self, name, value):
return self.__setattr__(name, value)
class _ModuleBuilderRoot:
def __init__(self, builder, depth):
self._builder = builder
self.domain = self.d = _ModuleBuilderDomains(builder, depth)
def __getattr__(self, name):
if name in ("comb", "sync"):
raise AttributeError("'{}' object has no attribute '{}'; did you mean 'd.{}'?"
.format(type(self).__name__, name, name))
raise AttributeError("'{}' object has no attribute '{}'"
.format(type(self).__name__, name))
class _ModuleBuilderSubmodules:
def __init__(self, builder):
object.__setattr__(self, "_builder", builder)
def __iadd__(self, modules):
if isinstance(modules, Iterable):
for module in modules:
self._builder._add_submodule(module)
else:
module = modules
self._builder._add_submodule(module)
return self
def __setattr__(self, name, submodule):
self._builder._add_submodule(submodule, name)
class Module(_ModuleBuilderRoot):
def __init__(self):
_ModuleBuilderRoot.__init__(self, self, depth=0)
self.submodules = _ModuleBuilderSubmodules(self)
self._submodules = []
self._driving = ValueDict()
self._statements = Statement.wrap([])
self._ctrl_context = None
self._ctrl_stack = []
self._stmt_if_cond = []
self._stmt_if_bodies = []
self._stmt_switch_test = None
self._stmt_switch_cases = OrderedDict()
def _check_context(self, construct, context):
if self._ctrl_context != context:
if self._ctrl_context is None:
raise SyntaxError("{} is not permitted outside of {}"
.format(construct, context))
else:
raise SyntaxError("{} is not permitted inside of {}"
.format(construct, self._ctrl_context))
def _get_ctrl(self, name):
if self._ctrl_stack:
top_name, top_data = self._ctrl_stack[-1]
if top_name == name:
return top_data
def _flush_ctrl(self):
while len(self._ctrl_stack) > self.domain._depth:
self._pop_ctrl()
def _set_ctrl(self, name, data):
self._flush_ctrl()
self._ctrl_stack.append((name, data))
return data
@contextmanager
def If(self, cond):
self._check_context("If", context=None)
if_data = self._set_ctrl("If", {"tests": [], "bodies": []})
try:
_outer_case, self._statements = self._statements, []
self.domain._depth += 1
yield
self._flush_ctrl()
if_data["tests"].append(cond)
if_data["bodies"].append(self._statements)
finally:
self.domain._depth -= 1
self._statements = _outer_case
@contextmanager
def Elif(self, cond):
self._check_context("Elif", context=None)
if_data = self._get_ctrl("If")
if if_data is None:
raise SyntaxError("Elif without preceding If")
try:
_outer_case, self._statements = self._statements, []
self.domain._depth += 1
yield
self._flush_ctrl()
if_data["tests"].append(cond)
if_data["bodies"].append(self._statements)
finally:
self.domain._depth -= 1
self._statements = _outer_case
@contextmanager
def Else(self):
self._check_context("Else", context=None)
if_data = self._get_ctrl("If")
if if_data is None:
raise SyntaxError("Else without preceding If/Elif")
try:
_outer_case, self._statements = self._statements, []
self.domain._depth += 1
yield
self._flush_ctrl()
if_data["bodies"].append(self._statements)
finally:
self.domain._depth -= 1
self._statements = _outer_case
self._pop_ctrl()
@contextmanager
def Switch(self, test):
self._check_context("Switch", context=None)
switch_data = self._set_ctrl("Switch", {"test": test, "cases": OrderedDict()})
try:
self._ctrl_context = "Switch"
self.domain._depth += 1
yield
finally:
self.domain._depth -= 1
self._ctrl_context = None
self._pop_ctrl()
@contextmanager
def Case(self, value=None):
self._check_context("Case", context="Switch")
switch_data = self._get_ctrl("Switch")
if value is None:
value = "-" * len(switch_data["test"])
if isinstance(value, str) and len(switch_data["test"]) != len(value):
raise SyntaxError("Case value '{}' must have the same width as test (which is {})"
.format(value, len(switch_data["test"])))
try:
_outer_case, self._statements = self._statements, []
self._ctrl_context = None
yield
self._flush_ctrl()
switch_data["cases"][value] = self._statements
finally:
self._ctrl_context = "Switch"
self._statements = _outer_case
def _pop_ctrl(self):
name, data = self._ctrl_stack.pop()
if name == "If":
if_tests, if_bodies = data["tests"], data["bodies"]
tests, cases = [], OrderedDict()
for if_test, if_case in zip(if_tests + [None], if_bodies):
if if_test is not None:
if_test = Value.wrap(if_test)
if len(if_test) != 1:
if_test = if_test.bool()
tests.append(if_test)
if if_test is not None:
match = ("1" + "-" * (len(tests) - 1)).rjust(len(if_tests), "-")
else:
match = "-" * len(tests)
cases[match] = if_case
self._statements.append(Switch(Cat(tests), cases))
if name == "Switch":
switch_test, switch_cases = data["test"], data["cases"]
self._statements.append(Switch(switch_test, switch_cases))
def _add_statement(self, assigns, domain, depth, compat_mode=False):
def domain_name(domain):
if domain is None:
return "comb"
else:
return domain
while len(self._ctrl_stack) > self.domain._depth:
self._pop_ctrl()
for assign in Statement.wrap(assigns):
if not compat_mode and not isinstance(assign, Assign):
raise SyntaxError(
"Only assignments may be appended to d.{}"
.format(domain_name(domain)))
for signal in assign._lhs_signals():
if signal not in self._driving:
self._driving[signal] = domain
elif self._driving[signal] != domain:
cd_curr = self._driving[signal]
raise SyntaxError(
"Driver-driver conflict: trying to drive {!r} from d.{}, but it is "
"already driven from d.{}"
.format(signal, domain_name(domain), domain_name(cd_curr)))
self._statements.append(assign)
def _add_submodule(self, submodule, name=None):
if not hasattr(submodule, "get_fragment"):
raise TypeError("Trying to add '{!r}', which does not implement .get_fragment(), as "
"a submodule".format(submodule))
self._submodules.append((submodule, name))
def _flush(self):
while self._ctrl_stack:
self._pop_ctrl()
def lower(self, platform):
self._flush()
fragment = Fragment()
for submodule, name in self._submodules:
fragment.add_subfragment(submodule.get_fragment(platform), name)
fragment.add_statements(self._statements)
for signal, domain in self._driving.items():
fragment.add_driver(signal, domain)
return fragment
get_fragment = lower

276
nmigen/hdl/ir.py Normal file
View file

@ -0,0 +1,276 @@
import warnings
from collections import defaultdict, OrderedDict
from ..tools import *
from .ast import *
from .cd import *
__all__ = ["Fragment", "DriverConflict"]
class DriverConflict(UserWarning):
pass
class Fragment:
def __init__(self):
self.ports = ValueDict()
self.drivers = OrderedDict()
self.statements = []
self.domains = OrderedDict()
self.subfragments = []
def add_ports(self, *ports, kind):
assert kind in ("i", "o", "io")
for port in flatten(ports):
self.ports[port] = kind
def iter_ports(self):
yield from self.ports.keys()
def add_driver(self, signal, domain=None):
if domain not in self.drivers:
self.drivers[domain] = ValueSet()
self.drivers[domain].add(signal)
def iter_drivers(self):
for domain, signals in self.drivers.items():
for signal in signals:
yield domain, signal
def iter_comb(self):
if None in self.drivers:
yield from self.drivers[None]
def iter_sync(self):
for domain, signals in self.drivers.items():
if domain is None:
continue
for signal in signals:
yield domain, signal
def iter_signals(self):
signals = ValueSet()
signals |= self.ports.keys()
for domain, domain_signals in self.drivers.items():
if domain is not None:
cd = self.domains[domain]
signals.add(cd.clk)
if cd.rst is not None:
signals.add(cd.rst)
signals |= domain_signals
return signals
def add_domains(self, *domains):
for domain in domains:
assert isinstance(domain, ClockDomain)
assert domain.name not in self.domains
self.domains[domain.name] = domain
def iter_domains(self):
yield from self.domains
def add_statements(self, *stmts):
self.statements += Statement.wrap(stmts)
def add_subfragment(self, subfragment, name=None):
assert isinstance(subfragment, Fragment)
self.subfragments.append((subfragment, name))
def _resolve_driver_conflicts(self, hierarchy=("top",), mode="warn"):
assert mode in ("silent", "warn", "error")
driver_subfrags = ValueDict()
# For each signal driven by this fragment and/or its subfragments, determine which
# subfragments also drive it.
for domain, signal in self.iter_drivers():
if signal not in driver_subfrags:
driver_subfrags[signal] = set()
driver_subfrags[signal].add((None, hierarchy))
for i, (subfrag, name) in enumerate(self.subfragments):
# First, recurse into subfragments and let them detect driver conflicts as well.
if name is None:
name = "<unnamed #{}>".format(i)
subfrag_hierarchy = hierarchy + (name,)
subfrag_drivers = subfrag._resolve_driver_conflicts(subfrag_hierarchy, mode)
# Second, classify subfragments by domains they define.
for signal in subfrag_drivers:
if signal not in driver_subfrags:
driver_subfrags[signal] = set()
driver_subfrags[signal].add((subfrag, subfrag_hierarchy))
# Find out the set of subfragments that needs to be flattened into this fragment
# to resolve driver-driver conflicts.
flatten_subfrags = set()
for signal, subfrags in driver_subfrags.items():
if len(subfrags) > 1:
flatten_subfrags.update((f, h) for f, h in subfrags if f is not None)
# While we're at it, show a message.
subfrag_names = ", ".join(sorted(".".join(h) for f, h in subfrags))
message = ("Signal '{}' is driven from multiple fragments: {}"
.format(signal, subfrag_names))
if mode == "error":
raise DriverConflict(message)
elif mode == "warn":
message += "; hierarchy will be flattened"
warnings.warn_explicit(message, DriverConflict, *signal.src_loc)
for subfrag, subfrag_hierarchy in sorted(flatten_subfrags, key=lambda x: x[1]):
# Merge subfragment's everything except clock domains into this fragment.
# Flattening is done after clock domain propagation, so we can assume the domains
# are already the same in every involved fragment in the first place.
self.ports.update(subfrag.ports)
for domain, signal in subfrag.iter_drivers():
self.add_driver(signal, domain)
self.statements += subfrag.statements
self.subfragments += subfrag.subfragments
# Remove the merged subfragment.
for i, (check_subfrag, check_name) in enumerate(self.subfragments): # :nobr:
if subfrag == check_subfrag:
del self.subfragments[i]
break
# If we flattened anything, we might be in a situation where we have a driver conflict
# again, e.g. if we had a tree of fragments like A --- B --- C where only fragments
# A and C were driving a signal S. In that case, since B is not driving S itself,
# processing B will not result in any flattening, but since B is transitively driving S,
# processing A will flatten B into it. Afterwards, we have a tree like AB --- C, which
# has another conflict.
if any(flatten_subfrags):
# Try flattening again.
return self._resolve_driver_conflicts(hierarchy, mode)
# Nothing was flattened, we're done!
return ValueSet(driver_subfrags.keys())
def _propagate_domains_up(self, hierarchy=("top",)):
from .xfrm import DomainRenamer
domain_subfrags = defaultdict(lambda: set())
# For each domain defined by a subfragment, determine which subfragments define it.
for i, (subfrag, name) in enumerate(self.subfragments):
# First, recurse into subfragments and let them propagate domains up as well.
hier_name = name
if hier_name is None:
hier_name = "<unnamed #{}>".format(i)
subfrag._propagate_domains_up(hierarchy + (hier_name,))
# Second, classify subfragments by domains they define.
for domain in subfrag.iter_domains():
domain_subfrags[domain].add((subfrag, name, i))
# For each domain defined by more than one subfragment, rename the domain in each
# of the subfragments such that they no longer conflict.
for domain, subfrags in domain_subfrags.items():
if len(subfrags) == 1:
continue
names = [n for f, n, i in subfrags]
if not all(names):
names = sorted("<unnamed #{}>".format(i) if n is None else "'{}'".format(n)
for f, n, i in subfrags)
raise DomainError("Domain '{}' is defined by subfragments {} of fragment '{}'; "
"it is necessary to either rename subfragment domains "
"explicitly, or give names to subfragments"
.format(domain, ", ".join(names), ".".join(hierarchy)))
if len(names) != len(set(names)):
names = sorted("#{}".format(i) for f, n, i in subfrags)
raise DomainError("Domain '{}' is defined by subfragments {} of fragment '{}', "
"some of which have identical names; it is necessary to either "
"rename subfragment domains explicitly, or give distinct names "
"to subfragments"
.format(domain, ", ".join(names), ".".join(hierarchy)))
for subfrag, name, i in subfrags:
self.subfragments[i] = \
(DomainRenamer({domain: "{}_{}".format(name, domain)})(subfrag), name)
# Finally, collect the (now unique) subfragment domains, and merge them into our domains.
for subfrag, name in self.subfragments:
for domain in subfrag.iter_domains():
self.add_domains(subfrag.domains[domain])
def _propagate_domains_down(self):
# For each domain defined in this fragment, ensure it also exists in all subfragments.
for subfrag, name in self.subfragments:
for domain in self.iter_domains():
if domain in subfrag.domains:
assert self.domains[domain] is subfrag.domains[domain]
else:
subfrag.add_domains(self.domains[domain])
subfrag._propagate_domains_down()
def _propagate_domains(self, ensure_sync_exists):
self._propagate_domains_up()
if ensure_sync_exists and not self.domains:
self.add_domains(ClockDomain("sync"))
self._propagate_domains_down()
def _insert_domain_resets(self):
from .xfrm import ResetInserter
resets = {cd.name: cd.rst for cd in self.domains.values() if cd.rst is not None}
return ResetInserter(resets)(self)
def _lower_domain_signals(self):
from .xfrm import DomainLowerer
return DomainLowerer(self.domains)(self)
def _propagate_ports(self, ports):
# Collect all signals we're driving (on LHS of statements), and signals we're using
# (on RHS of statements, or in clock domains).
self_driven = union(s._lhs_signals() for s in self.statements) or ValueSet()
self_used = union(s._rhs_signals() for s in self.statements) or ValueSet()
for domain, _ in self.iter_sync():
cd = self.domains[domain]
self_used.add(cd.clk)
if cd.rst is not None:
self_used.add(cd.rst)
# Our input ports are all the signals we're using but not driving. This is an over-
# approximation: some of these signals may be driven by our subfragments.
ins = self_used - self_driven
# Our output ports are all the signals we're asked to provide that we're driving. This is
# an underapproximation: some of these signals may be driven by subfragments.
outs = ports & self_driven
# Go through subfragments and refine our approximation for ports.
for subfrag, name in self.subfragments:
# Always ask subfragments to provide all signals we're using and signals we're asked
# to provide. If the subfragment is not driving it, it will silently ignore it.
sub_ins, sub_outs = subfrag._propagate_ports(ports=self_used | ports)
# Refine the input port approximation: if a subfragment is driving a signal,
# it is definitely not our input. But, if a subfragment requires a signal as an input,
# and we aren't driving it, it has to be our input as well.
ins -= sub_outs
ins |= sub_ins - self_driven
# Refine the output port approximation: if a subfragment is driving a signal,
# and we're asked to provide it, we can provide it now.
outs |= ports & sub_outs
# We've computed the precise set of input and output ports.
self.add_ports(ins, kind="i")
self.add_ports(outs, kind="o")
return ins, outs
def prepare(self, ports=(), ensure_sync_exists=True):
from .xfrm import FragmentTransformer
fragment = FragmentTransformer()(self)
fragment._propagate_domains(ensure_sync_exists)
fragment._resolve_driver_conflicts()
fragment = fragment._insert_domain_resets()
fragment = fragment._lower_domain_signals()
fragment._propagate_ports(ports)
return fragment

224
nmigen/hdl/xfrm.py Normal file
View file

@ -0,0 +1,224 @@
from collections import OrderedDict
from collections.abc import Iterable
from ..tools import flatten
from .ast import *
from .ast import _StatementList
from .cd import *
from .ir import *
__all__ = ["ValueTransformer", "StatementTransformer", "FragmentTransformer",
"DomainRenamer", "DomainLowerer", "ResetInserter", "CEInserter"]
class ValueTransformer:
def on_Const(self, value):
return value
def on_Signal(self, value):
return value
def on_ClockSignal(self, value):
return value
def on_ResetSignal(self, value):
return value
def on_Operator(self, value):
return Operator(value.op, [self.on_value(o) for o in value.operands])
def on_Slice(self, value):
return Slice(self.on_value(value.value), value.start, value.end)
def on_Part(self, value):
return Part(self.on_value(value.value), self.on_value(value.offset), value.width)
def on_Cat(self, value):
return Cat(self.on_value(o) for o in value.operands)
def on_Repl(self, value):
return Repl(self.on_value(value.value), value.count)
def on_unknown_value(self, value):
raise TypeError("Cannot transform value '{!r}'".format(value)) # :nocov:
def on_value(self, value):
if isinstance(value, Const):
new_value = self.on_Const(value)
elif isinstance(value, Signal):
new_value = self.on_Signal(value)
elif isinstance(value, ClockSignal):
new_value = self.on_ClockSignal(value)
elif isinstance(value, ResetSignal):
new_value = self.on_ResetSignal(value)
elif isinstance(value, Operator):
new_value = self.on_Operator(value)
elif isinstance(value, Slice):
new_value = self.on_Slice(value)
elif isinstance(value, Part):
new_value = self.on_Part(value)
elif isinstance(value, Cat):
new_value = self.on_Cat(value)
elif isinstance(value, Repl):
new_value = self.on_Repl(value)
else:
new_value = self.on_unknown_value(value)
if isinstance(new_value, Value):
new_value.src_loc = value.src_loc
return new_value
def __call__(self, value):
return self.on_value(value)
class StatementTransformer:
def on_value(self, value):
return value
def on_Assign(self, stmt):
return Assign(self.on_value(stmt.lhs), self.on_value(stmt.rhs))
def on_Switch(self, stmt):
cases = OrderedDict((k, self.on_statement(v)) for k, v in stmt.cases.items())
return Switch(self.on_value(stmt.test), cases)
def on_statements(self, stmt):
return _StatementList(flatten(self.on_statement(stmt) for stmt in stmt))
def on_unknown_statement(self, stmt):
raise TypeError("Cannot transform statement '{!r}'".format(stmt)) # :nocov:
def on_statement(self, stmt):
if isinstance(stmt, Assign):
return self.on_Assign(stmt)
elif isinstance(stmt, Switch):
return self.on_Switch(stmt)
elif isinstance(stmt, Iterable):
return self.on_statements(stmt)
else:
return self.on_unknown_statement(stmt)
def __call__(self, value):
return self.on_statement(value)
class FragmentTransformer:
def map_subfragments(self, fragment, new_fragment):
for subfragment, name in fragment.subfragments:
new_fragment.add_subfragment(self(subfragment), name)
def map_domains(self, fragment, new_fragment):
for domain in fragment.iter_domains():
new_fragment.add_domains(fragment.domains[domain])
def map_statements(self, fragment, new_fragment):
if hasattr(self, "on_statement"):
new_fragment.add_statements(map(self.on_statement, fragment.statements))
else:
new_fragment.add_statements(fragment.statements)
def map_drivers(self, fragment, new_fragment):
for domain, signal in fragment.iter_drivers():
new_fragment.add_driver(signal, domain)
def on_fragment(self, fragment):
new_fragment = Fragment()
self.map_subfragments(fragment, new_fragment)
self.map_domains(fragment, new_fragment)
self.map_statements(fragment, new_fragment)
self.map_drivers(fragment, new_fragment)
return new_fragment
def __call__(self, value):
return self.on_fragment(value)
class DomainRenamer(FragmentTransformer, ValueTransformer, StatementTransformer):
def __init__(self, domain_map):
if isinstance(domain_map, str):
domain_map = {"sync": domain_map}
self.domain_map = OrderedDict(domain_map)
def on_ClockSignal(self, value):
if value.domain in self.domain_map:
return ClockSignal(self.domain_map[value.domain])
return value
def on_ResetSignal(self, value):
if value.domain in self.domain_map:
return ResetSignal(self.domain_map[value.domain])
return value
def map_domains(self, fragment, new_fragment):
for domain in fragment.iter_domains():
cd = fragment.domains[domain]
if domain in self.domain_map:
if cd.name == domain:
# Rename the actual ClockDomain object.
cd.rename(self.domain_map[domain])
else:
assert cd.name == self.domain_map[domain]
new_fragment.add_domains(cd)
def map_drivers(self, fragment, new_fragment):
for domain, signals in fragment.drivers.items():
if domain in self.domain_map:
domain = self.domain_map[domain]
for signal in signals:
new_fragment.add_driver(signal, domain)
class DomainLowerer(FragmentTransformer, ValueTransformer, StatementTransformer):
def __init__(self, domains):
self.domains = domains
def _resolve(self, domain, context):
if domain not in self.domains:
raise DomainError("Signal {!r} refers to nonexistent domain '{}'"
.format(context, domain))
return self.domains[domain]
def on_ClockSignal(self, value):
cd = self._resolve(value.domain, value)
return cd.clk
def on_ResetSignal(self, value):
cd = self._resolve(value.domain, value)
if cd.rst is None:
if value.allow_reset_less:
return Const(0)
else:
raise DomainError("Signal {!r} refers to reset of reset-less domain '{}'"
.format(value, value.domain))
return cd.rst
class _ControlInserter(FragmentTransformer):
def __init__(self, controls):
if isinstance(controls, Value):
controls = {"sync": controls}
self.controls = OrderedDict(controls)
def on_fragment(self, fragment):
new_fragment = super().on_fragment(fragment)
for domain, signals in fragment.drivers.items():
if domain is None or domain not in self.controls:
continue
self._insert_control(new_fragment, domain, signals)
return new_fragment
def _insert_control(self, fragment, domain, signals):
raise NotImplementedError # :nocov:
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]
fragment.add_statements(Switch(self.controls[domain], {1: stmts}))
class CEInserter(_ControlInserter):
def _insert_control(self, fragment, domain, signals):
stmts = [s.eq(s) for s in signals]
fragment.add_statements(Switch(self.controls[domain], {0: stmts}))