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