diff --git a/migen/fhdl/structure.py b/migen/fhdl/structure.py index e02866c37..688f2468c 100644 --- a/migen/fhdl/structure.py +++ b/migen/fhdl/structure.py @@ -15,6 +15,16 @@ class HUID: return self.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): return _Operator("~", [self]) def __neg__(self): @@ -83,6 +93,19 @@ class Value(HUID): raise KeyError 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) def __hash__(self): @@ -95,6 +118,22 @@ class _Operator(Value): self.operands = operands 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]) class _Slice(Value): @@ -105,17 +144,101 @@ class _Slice(Value): self.stop = stop 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): Value.__init__(self) self.l = args 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): Value.__init__(self) self.v = v self.n = n 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): from migen.fhdl.size import bits_for @@ -149,11 +272,31 @@ class Signal(Value): return "" 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"): Value.__init__(self) self.cd = cd 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"): Value.__init__(self) self.cd = cd @@ -166,16 +309,56 @@ class _Assign: self.r = r 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): self.cond = cond self.t = list(t) 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)) return self 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)]) return self @@ -188,11 +371,44 @@ def _insert_else(obj, clause): o.f = clause 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): self.test = test self.cases = cases 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: for choice in self.cases.keys(): if key is None or choice > key: @@ -217,6 +433,34 @@ class _ArrayProxy(Value): self.key) 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): if isinstance(key, Value): return _ArrayProxy(self, key) @@ -224,6 +468,27 @@ class Array(list): return list.__getitem__(self, key) 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): self.name = tracer.get_obj_var_name(name) if self.name is None: @@ -237,6 +502,13 @@ class ClockDomain: self.rst = Signal(name_override=self.name + "_rst") def rename(self, new_name): + """Rename the clock domain + + Parameters + ---------- + new_name : str + New name + """ self.name = new_name self.clk.name_override = new_name + "_clk" if self.rst is not None: