fhdl.structure: document the API
This commit is contained in:
parent
e469e5e539
commit
73db4944f1
|
@ -15,6 +15,16 @@ class HUID:
|
||||||
return self.huid
|
return self.huid
|
||||||
|
|
||||||
class Value(HUID):
|
class Value(HUID):
|
||||||
|
"""Base class for operands
|
||||||
|
|
||||||
|
Instances of `Value` or its subclasses can be operands to
|
||||||
|
arithmetic, comparison, bitwise, and logic operators.
|
||||||
|
They can be assigned (:meth:`eq`) or indexed/sliced (using the usual
|
||||||
|
Python indexing and slicing notation).
|
||||||
|
|
||||||
|
Values created from integers have the minimum bit width to necessary to
|
||||||
|
represent the integer.
|
||||||
|
"""
|
||||||
def __invert__(self):
|
def __invert__(self):
|
||||||
return _Operator("~", [self])
|
return _Operator("~", [self])
|
||||||
def __neg__(self):
|
def __neg__(self):
|
||||||
|
@ -83,6 +93,19 @@ class Value(HUID):
|
||||||
raise KeyError
|
raise KeyError
|
||||||
|
|
||||||
def eq(self, r):
|
def eq(self, r):
|
||||||
|
"""Assignment
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
r : Value, in
|
||||||
|
Value to be assigned.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
_Assign
|
||||||
|
Assignment statement that can be used in combinatorial or
|
||||||
|
synchronous context.
|
||||||
|
"""
|
||||||
return _Assign(self, r)
|
return _Assign(self, r)
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
|
@ -95,6 +118,22 @@ class _Operator(Value):
|
||||||
self.operands = operands
|
self.operands = operands
|
||||||
|
|
||||||
def Mux(sel, val1, val0):
|
def Mux(sel, val1, val0):
|
||||||
|
"""Multiplex between two values
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
sel : Value(1), in
|
||||||
|
Selector.
|
||||||
|
val1 : Value(N), in
|
||||||
|
val0 : Value(N), in
|
||||||
|
Input values.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Value(N), out
|
||||||
|
Output `Value`. If `sel` is asserted, the Mux returns
|
||||||
|
`val1`, else `val0`.
|
||||||
|
"""
|
||||||
return _Operator("m", [sel, val1, val0])
|
return _Operator("m", [sel, val1, val0])
|
||||||
|
|
||||||
class _Slice(Value):
|
class _Slice(Value):
|
||||||
|
@ -105,17 +144,101 @@ class _Slice(Value):
|
||||||
self.stop = stop
|
self.stop = stop
|
||||||
|
|
||||||
class Cat(Value):
|
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. The bit length of the return value is the sum of
|
||||||
|
the bit lengths of the arguments::
|
||||||
|
|
||||||
|
flen(Cat(*args)) == sum(flen(arg) for arg in args)
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
*args : Value, inout
|
||||||
|
`Value` s to be concatenated.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Cat, inout
|
||||||
|
Resulting `Value` obtained by concatentation.
|
||||||
|
"""
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
Value.__init__(self)
|
Value.__init__(self)
|
||||||
self.l = args
|
self.l = args
|
||||||
|
|
||||||
class Replicate(Value):
|
class Replicate(Value):
|
||||||
|
"""Replicate a value
|
||||||
|
|
||||||
|
An input value is replicated (repeated) several times
|
||||||
|
to be used on the RHS of assignments::
|
||||||
|
|
||||||
|
flen(Replicate(s, n)) == flen(s)*n
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
v : Value, in
|
||||||
|
Input value to be replicated.
|
||||||
|
n : int
|
||||||
|
Number of replications.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Replicate, out
|
||||||
|
Replicated value.
|
||||||
|
"""
|
||||||
def __init__(self, v, n):
|
def __init__(self, v, n):
|
||||||
Value.__init__(self)
|
Value.__init__(self)
|
||||||
self.v = v
|
self.v = v
|
||||||
self.n = n
|
self.n = n
|
||||||
|
|
||||||
class Signal(Value):
|
class Signal(Value):
|
||||||
|
"""A `Value` that can change
|
||||||
|
|
||||||
|
The `Signal` object represents a value that is expected to change
|
||||||
|
in the circuit. It does exactly what Verilog's `wire` and
|
||||||
|
`reg` and VHDL's `signal` do.
|
||||||
|
|
||||||
|
A `Signal` can be indexed to access a subset of its bits. Negative
|
||||||
|
indices (`signal[-1]`) and the extended Python slicing notation
|
||||||
|
(`signal[start:stop:step]`) are supported.
|
||||||
|
The indeces 0 and -1 are the least and most significant bits
|
||||||
|
respectively.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
bits_sign : int or tuple
|
||||||
|
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). `signed` defaults to
|
||||||
|
`False`.
|
||||||
|
name : str or None
|
||||||
|
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.
|
||||||
|
variable : bool
|
||||||
|
Deprecated.
|
||||||
|
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.
|
||||||
|
name_override : str or None
|
||||||
|
Do not use the inferred name but the given one.
|
||||||
|
min : int or None
|
||||||
|
max : int or None
|
||||||
|
If `bits_sign` 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).
|
||||||
|
related : Signal or None
|
||||||
|
"""
|
||||||
def __init__(self, bits_sign=None, name=None, variable=False, reset=0, name_override=None, min=None, max=None, related=None):
|
def __init__(self, bits_sign=None, name=None, variable=False, reset=0, name_override=None, min=None, max=None, related=None):
|
||||||
from migen.fhdl.size import bits_for
|
from migen.fhdl.size import bits_for
|
||||||
|
|
||||||
|
@ -149,11 +272,31 @@ class Signal(Value):
|
||||||
return "<Signal " + (self.backtrace[-1][0] or "anonymous") + " at " + hex(id(self)) + ">"
|
return "<Signal " + (self.backtrace[-1][0] or "anonymous") + " at " + hex(id(self)) + ">"
|
||||||
|
|
||||||
class ClockSignal(Value):
|
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
|
||||||
|
----------
|
||||||
|
cd : str
|
||||||
|
Clock domain to obtain a clock signal for. Defaults to `"sys"`.
|
||||||
|
"""
|
||||||
def __init__(self, cd="sys"):
|
def __init__(self, cd="sys"):
|
||||||
Value.__init__(self)
|
Value.__init__(self)
|
||||||
self.cd = cd
|
self.cd = cd
|
||||||
|
|
||||||
class ResetSignal(Value):
|
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
|
||||||
|
----------
|
||||||
|
cd : str
|
||||||
|
Clock domain to obtain a reset signal for. Defaults to `"sys"`.
|
||||||
|
"""
|
||||||
def __init__(self, cd="sys"):
|
def __init__(self, cd="sys"):
|
||||||
Value.__init__(self)
|
Value.__init__(self)
|
||||||
self.cd = cd
|
self.cd = cd
|
||||||
|
@ -166,16 +309,56 @@ class _Assign:
|
||||||
self.r = r
|
self.r = r
|
||||||
|
|
||||||
class If:
|
class If:
|
||||||
|
"""Conditional execution of statements
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
cond : Value(1), in
|
||||||
|
Condition
|
||||||
|
*t : Statements
|
||||||
|
Statements to execute if `cond` is asserted.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
>>> a = Signal()
|
||||||
|
>>> b = Signal()
|
||||||
|
>>> c = Signal()
|
||||||
|
>>> d = Signal()
|
||||||
|
>>> If(a,
|
||||||
|
... b.eq(1)
|
||||||
|
... ).Elif(c,
|
||||||
|
... b.eq(0)
|
||||||
|
... ).Else(
|
||||||
|
... b.eq(d)
|
||||||
|
... )
|
||||||
|
"""
|
||||||
def __init__(self, cond, *t):
|
def __init__(self, cond, *t):
|
||||||
self.cond = cond
|
self.cond = cond
|
||||||
self.t = list(t)
|
self.t = list(t)
|
||||||
self.f = []
|
self.f = []
|
||||||
|
|
||||||
def Else(self, *f):
|
def Else(self, *f):
|
||||||
|
"""Add an `else` conditional block
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
*f : Statements
|
||||||
|
Statements to execute if all previous conditions fail.
|
||||||
|
"""
|
||||||
_insert_else(self, list(f))
|
_insert_else(self, list(f))
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def Elif(self, cond, *t):
|
def Elif(self, cond, *t):
|
||||||
|
"""Add an `else if` conditional block
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
cond : Value(1), in
|
||||||
|
Condition
|
||||||
|
*t : Statements
|
||||||
|
Statements to execute if previous conditions fail and `cond`
|
||||||
|
is asserted.
|
||||||
|
"""
|
||||||
_insert_else(self, [If(cond, *t)])
|
_insert_else(self, [If(cond, *t)])
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@ -188,11 +371,44 @@ def _insert_else(obj, clause):
|
||||||
o.f = clause
|
o.f = clause
|
||||||
|
|
||||||
class Case:
|
class Case:
|
||||||
|
"""Case/Switch statement
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
test : Value, in
|
||||||
|
Selector value used to decide which block to execute
|
||||||
|
cases : dict
|
||||||
|
Dictionary of cases. The keys are numeric constants to compare
|
||||||
|
with `test`. The values are statements to be executed the
|
||||||
|
corresponding key matches `test`. The dictionary may contain a
|
||||||
|
string key `"default"` to mark a fall-through case that is
|
||||||
|
executed if no other key matches.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
>>> a = Signal()
|
||||||
|
>>> b = Signal()
|
||||||
|
>>> Case(a, {
|
||||||
|
... 0: b.eq(1),
|
||||||
|
... 1: b.eq(0),
|
||||||
|
... "default": b.eq(0),
|
||||||
|
... })
|
||||||
|
"""
|
||||||
def __init__(self, test, cases):
|
def __init__(self, test, cases):
|
||||||
self.test = test
|
self.test = test
|
||||||
self.cases = cases
|
self.cases = cases
|
||||||
|
|
||||||
def makedefault(self, key=None):
|
def makedefault(self, key=None):
|
||||||
|
"""Mark a key as the default case
|
||||||
|
|
||||||
|
Deletes/Substitutes any previously existing default case.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
key : int or None
|
||||||
|
Key to use as default case if no other key matches.
|
||||||
|
By default, the largest key is the default key.
|
||||||
|
"""
|
||||||
if key is None:
|
if key is None:
|
||||||
for choice in self.cases.keys():
|
for choice in self.cases.keys():
|
||||||
if key is None or choice > key:
|
if key is None or choice > key:
|
||||||
|
@ -217,6 +433,34 @@ class _ArrayProxy(Value):
|
||||||
self.key)
|
self.key)
|
||||||
|
|
||||||
class Array(list):
|
class Array(list):
|
||||||
|
"""Addressable multiplexer
|
||||||
|
|
||||||
|
An array is created from an iterable of values and indexed using the
|
||||||
|
usual Python simple indexing notation (no negative indices or
|
||||||
|
slices). It can be indexed by numeric constants, `Value` s, or
|
||||||
|
`Signal` s.
|
||||||
|
|
||||||
|
The result of indexing the array is a proxy for the entry at the
|
||||||
|
given index that can be used on either RHS or LHS of assignments.
|
||||||
|
|
||||||
|
An array can be indexed multiple times.
|
||||||
|
|
||||||
|
Multidimensional arrays are supported by packing inner arrays into
|
||||||
|
outer arrays.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
values : iterable of ints, Values, Signals
|
||||||
|
Entries of the array. Each entry can be a numeric constant, a
|
||||||
|
`Signal` or a `Record`.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
>>> a = Array(range(10))
|
||||||
|
>>> b = Signal(max=10)
|
||||||
|
>>> c = Signal(max=10)
|
||||||
|
>>> b.eq(a[9 - c])
|
||||||
|
"""
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
if isinstance(key, Value):
|
if isinstance(key, Value):
|
||||||
return _ArrayProxy(self, key)
|
return _ArrayProxy(self, key)
|
||||||
|
@ -224,6 +468,27 @@ class Array(list):
|
||||||
return list.__getitem__(self, key)
|
return list.__getitem__(self, key)
|
||||||
|
|
||||||
class ClockDomain:
|
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
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
def __init__(self, name=None, reset_less=False):
|
def __init__(self, name=None, reset_less=False):
|
||||||
self.name = tracer.get_obj_var_name(name)
|
self.name = tracer.get_obj_var_name(name)
|
||||||
if self.name is None:
|
if self.name is None:
|
||||||
|
@ -237,6 +502,13 @@ class ClockDomain:
|
||||||
self.rst = Signal(name_override=self.name + "_rst")
|
self.rst = Signal(name_override=self.name + "_rst")
|
||||||
|
|
||||||
def rename(self, new_name):
|
def rename(self, new_name):
|
||||||
|
"""Rename the clock domain
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
new_name : str
|
||||||
|
New name
|
||||||
|
"""
|
||||||
self.name = new_name
|
self.name = new_name
|
||||||
self.clk.name_override = new_name + "_clk"
|
self.clk.name_override = new_name + "_clk"
|
||||||
if self.rst is not None:
|
if self.rst is not None:
|
||||||
|
|
Loading…
Reference in New Issue