Hardware descriptions need to support the concepts of module instantiation and hierarchy. In MyHDL, an instance is recursively defined as being either a sequence of instances, or a generator. Hierarchy is modeled by defining instances in a higher-level function, and returning them. The following is a schematic example of the basic case.
def top(...): ... instance_1 = module_1(...) instance_2 = module_2(...) ... instance_n = module_n(...) ... return instance_1, instance_2, ... , instance_n
Note that MyHDL uses conventional procedural techniques for modeling structure. This makes it straightforward to model more complex cases.
To model conditional instantiation, we can select the returned instance under parameter control. For example:
SLOW, MEDIUM, FAST = range(3) def top(..., speed=SLOW): ... def slowAndSmall(): ... ... def fastAndLarge(): ... if speed == SLOW: return slowAndSmall() elif speed == FAST: return fastAndLarge() else: raise NotImplementedError
Lists of instances and signals¶
Python lists are easy to create. We can use them to model lists of instances.
Suppose we have a top module that instantiates a single
def top(...): din = Signal(0) dout = Signal(0) clk = Signal(bool(0)) reset = Signal(bool(0)) channel_inst = channel(dout, din, clk, reset) return channel_inst
If we wanted to support an arbitrary number of channels, we can use lists of signals and a list of instances, as follows:
def top(..., n=8): din = [Signal(0) for i in range(n)] dout = [Signal(0) for in range(n)] clk = Signal(bool(0)) reset = Signal(bool(0)) channel_inst = [None for i in range(n)] for i in range(n): channel_inst[i] = channel(dout[i], din[i], clk, reset) return channel_inst
Converting between lists of signals and bit vectors¶
Compared to HDLs such as VHDL and Verilog, MyHDL signals are less flexible for structural modeling. For example, slicing a signal returns a slice of the current value. For behavioral code, this is just fine. However, it implies that you cannot use such as slice in structural descriptions. In other words, a signal slice cannot be used as a signal.
In MyHDL, you can address such cases by a concept called
shadow signals. A shadow signal is constructed out of
other signals and follows their value changes automatically.
For example, a
_SliceSignal follows the value of
an index or a slice from another signal. Likewise,
ConcatSignal follows the
values of a number of signals as a concatenation.
As an example, suppose we have a system with N requesters that
need arbitration. Each requester has a
grant input. To connect them in the system, we can
use list of signals. For example, a list of request signals
can be constructed as follows:
request_list = [Signal(bool()) for i in range(M)]
Suppose that an arbiter module is available that is instantiated as follows:
arb = arbiter(grant_vector, request_vector, clock, reset)
request_vector input is a bit vector that can have
any of its bits asserted. The
grant_vector is an output
bit vector with just a single bit asserted, or none.
Such a module is typically based on bit vectors because
they are easy to process in RTL code. In MyHDL, a bit vector
is modeled using the
We need a way to “connect” the list of signals to the
bit vector and vice versa. Of course, we can do this with explicit
code, but shadow signals can do this automatically. For
example, we can construct a
request_vector as a
request_vector = ConcatSignal(*reversed(request_list)
Note that we reverse the list first. This is done because the index range
of lists is the inverse of the range of
intbv bit vectors.
By reversing, the indices correspond to the same bit.
The inverse problem exist for the
grant_vector. It would be defined as follows:
grant_vector = Signal(intbv(0)[M:])
grant_list = [grant_vector(i) for i in range(M)]
Note the round brackets used for this type of slicing. Also, it may not be
necessary to construct this list explicitly. You can simply use
grant_vector(i) in an instantiation.
To decide when to use normal or shadow signals, consider the data flow. Use normal signals to connect to outputs. Use shadow signals to transform these signals so that they can be used as inputs.
Inferring the list of instances¶
In MyHDL, instances have to be returned explicitly by a top level function. It
may be convenient to assemble the list of instances automatically. For this
purpose, MyHDL provides the function
instances(). Using the first example
in this section, it is used as follows:
from myhdl import instances def top(...): ... instance_1 = module_1(...) instance_2 = module_2(...) ... instance_n = module_n(...) ... return instances()
instances() uses introspection to inspect the type of the local
variables defined by the calling function. All variables that comply with the
definition of an instance are assembled in a list, and that list is returned.