2.3 Parameters and hierarchy

We have seen that MyHDL uses functions to model hardware modules. We have also seen that ports are modeled by using signals as parameters. To make designs reusable we will also want to use other objects as parameters. For example, we can change the clock generator function to make it more general and reusable, by making the clock period parameterizable, as follows:

from myhdl import Signal, delay, instance, always, now, Simulation

def ClkDriver(clk, period=20):
    
    lowTime = int(period/2)
    highTime = period - lowTime

    @instance
    def driveClk():
        while True:
            yield delay(lowTime)
            clk.next = 1
            yield delay(highTime)
            clk.next = 0

    return driveClk

In addition to the clock signal, the clock period is a parameter, with a default value of 20.

As the low time of the clock may differ from the high time in case of an odd period, we cannot use the always decorator with a single delay value anymore. Instead, the driveClk function is now a generator function with an explicit definition of the desired behavior. It is decorated with the instance decorator.  You can see that driveClk is a generator function because it contains yield statements.

When a generator function is called, it returns a generator object. This is basically what the instance decorator does. It is less sophisticated than the always decorator, but it can be used to create a generator from any local generator function.

The yield statement is a general Python construct, but MyHDL uses it in a dedicated way. In MyHDL, it has a similar meaning as the wait statement in VHDL: the statement suspends execution of a generator, and its clauses specify the conditions on which the generator should wait before resuming. In this case, the generator waits for a certain delay.

Note that to make sure that the generator runs ``forever'', we wrap its behavior in a while True loop.

Similarly, we can define a general Hello function as follows:

def Hello(clk, to="World!"):

    @always(clk.posedge)
    def sayHello():
        print "%s Hello %s" % (now(), to)

    return sayHello

We can create any number of instances by calling the functions with the appropriate parameters. Hierarchy can be modeled by defining the instances in a higher-level function, and returning them. This pattern can be repeated for an arbitrary number of hierarchical levels. Consequently, the general definition of a MyHDL instance is recursive: an instance is either a sequence of instances, or a generator.

As an example, we will create a higher-level function with four instances of the lower-level functions, and simulate it:

def greetings():

    clk1 = Signal(0)
    clk2 = Signal(0)
    
    clkdriver_1 = ClkDriver(clk1) # positional and default association
    clkdriver_2 = ClkDriver(clk=clk2, period=19) # named association 
    hello_1 = Hello(clk=clk1) # named and default association
    hello_2 = Hello(to="MyHDL", clk=clk2) # named association

    return clkdriver_1, clkdriver_2, hello_1, hello_2


inst = greetings()
sim = Simulation(inst)
sim.run(50)

As in standard Python, positional or named parameter association can be used in instantiations, or a mix of both2.2. All these styles are demonstrated in the example above. Named association can be very useful if there are a lot of parameters, as the argument order in the call does not matter in that case.

The simulation produces the following output:

% python greetings.py
9 Hello MyHDL
10 Hello World!
28 Hello MyHDL
30 Hello World!
47 Hello MyHDL
50 Hello World!
_SuspendSimulation: Simulated 50 timesteps

Warning: Some commonly used terminology has different meanings in Python versus hardware design. Rather than artificially changing terminology, I think it's best to keep it and explicitly describing the differences.

A module in Python refers to all source code in a particular file. A module can be reused by other modules by importing. In hardware design, a module is a reusable block of hardware with a well defined interface. It can be reused in another module by instantiating it.

An instance in Python (and other object-oriented languages) refers to the object created by a class constructor. In hardware design, an instance is a particular incarnation of a hardware module.

Normally, the meaning should be clear from the context. Occasionally, I may qualify terms with the words 'hardware' or 'MyHDL' to avoid ambiguity.



Footnotes

... both2.2
All positional parameters have to go before any named parameter.
About this document