Structural modeling

Introduction

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.

Conditional instantiation

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 channel submodule, as follows:

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, A 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 request output and 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)

The 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 intbv type.

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 ConcatSignal object:

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:])

To construct a list of signals that are connected automatically to the bit vector, we can use the Signal call interface to construct _SliceSignal objects:

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()

Function 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.