3.3.4 Object oriented modeling

The models in the previous sections used high-level built-in data types internally. However, they had a conventional RTL-style interface. Communication with such a module is done through signals that are attached to it during instantiation.

A more advanced approach is to model hardware blocks as objects. Communication with objects is done through method calls. A method encapsulates all details of a certain task performed by the object. As an object has a method interface instead of an RTL-style hardware interface, this is a much higher level approach.

As an example, we will design a synchronized queue object. Such an object can be filled by producer, and independently read by a consumer. When the queue is empty, the consumer should wait until an item is available. The queue can be modeled as an object with a put(item) and a get() method, as follows:

from myhdl import *

def trigger(event):
    event.next = not event

class queue:
    def __init__(self):
       self.l = []
       self.sync = Signal(0)
       self.item = None
    def put(self,item):
       # non time-consuming method
       self.l.append(item)
       trigger(self.sync)
    def get(self):
       # time-consuming method
       if not self.l:
          yield self.sync
       self.item = self.l.pop(0)

The queue object constructor initializes an internal list to hold items, and a sync signal to synchronize the operation between the methods. Whenever put() puts an item in the queue, the signal is triggered. When the get() method sees that the list is empty, it waits on the trigger first. get() is a generator method because it may consume time. As the yield statement is used in MyHDL for timing control, the method cannot ``yield'' the item. Instead, it makes it available in the item instance variable.

To test the queue operation, we will model a producer and a consumer in the test bench. As a waiting consumer should not block a whole system, it should run in a concurrent ``thread''. As always in MyHDL, concurrency is modeled by Python generators. Producer and consumer will thus run independently, and we will monitor their operation through some print statements:

q = queue()

def Producer(q):
    yield delay(120)
    for i in range(5):
        print "%s: PUT item %s" % (now(), i)
        q.put(i)
        yield delay(max(5, 45 - 10*i))

def Consumer(q):
    yield delay(100)
    while 1:
        print "%s: TRY to get item" % now()
        yield q.get()
        print "%s: GOT item %s" % (now(), q.item)
        yield delay(30)

def main():
    P = Producer(q)
    C = Consumer(q)
    return P, C 

sim = Simulation(main())
sim.run()

Note that the generator method get() is called in a yield statement in the Consumer function. The new generator will take over from Consumer, until it is done. Running this test bench produces the following output:

% python queue.py
100: TRY to get item
120: PUT item 0
120: GOT item 0
150: TRY to get item
165: PUT item 1
165: GOT item 1
195: TRY to get item
200: PUT item 2
200: GOT item 2
225: PUT item 3
230: TRY to get item
230: GOT item 3
240: PUT item 4
260: TRY to get item
260: GOT item 4
290: TRY to get item
StopSimulation: No more events
About this document