[myhdl-list] "Johnson Counter" example - Using function attributes to define persistent variables
Brought to you by:
jandecaluwe
From: Angel E. <ang...@gm...> - 2010-07-08 15:33:47
|
Hi, I am going through the MyHDL examples on the "MyHDL by example" section of the MyHDL web page. In particular, I have just read the "Johnson Counter" section (http://www.myhdl.org/doku.php/cookbook:jc2). I copy the corresonding MyHDL code here for reference: from myhdl import * ACTIVE = 0 DirType = enum('RIGHT', 'LEFT') def jc2(goLeft, goRight, stop, clk, q): """ A bi-directional 4-bit Johnson counter with stop control. I/O pins: -------- clk : input free-running slow clock goLeft : input signal to shift left (active-low switch) goRight : input signal to shift right (active-low switch) stop : input signal to stop counting (active-low switch) q : 4-bit counter output (active-low LEDs; q[0] is right-most) """ dir = Signal(DirType.LEFT) run = Signal(False) @always(clk.posedge) def logic(): # direction if goRight == ACTIVE: dir.next = DirType.RIGHT run.next = True elif goLeft == ACTIVE: dir.next = DirType.LEFT run.next = True # stop if stop == ACTIVE: run.next = False # counter action if run: if dir == DirType.LEFT: q.next[4:1] = q[3:] q.next[0] = not q[3] else: q.next[3:] = q[4:1] q.next[3] = not q[0] return logic In the example commentary there is an interesting discussion about using variables instead of signals for the "dir" and "run" states. The commentary ends with an alternative implementation of the counter which uses variables. This alternative implementation does not use an "@always" decorator anymore. Instead it relies in using an @instance decorator combined with a "while True" loop and several yield statements. Again, I copy the MyHDL code here for reference: def jc2_alt(goLeft, goRight, stop, clk, q): """ A bi-directional 4-bit Johnson counter with stop control. I/O pins: -------- clk : input free-running clock goLeft : input signal to shift left (active-low switch) goRight : input signal to shift right (active-low switch) stop : input signal to stop counting (active-low switch) q : 4-bit counter output (active-low LEDs; q[0] is right-most) """ @instance def logic(): dir = DirType.LEFT run = False while True: yield clk.posedge # direction if goRight == ACTIVE: dir = DirType.RIGHT run = True elif goLeft == ACTIVE: dir = DirType.LEFT run = True # stop if stop == ACTIVE: run = False # counter action if run: if dir == DirType.LEFT: q.next[4:1] = q[3:] q.next[0] = not q[3] else: q.next[3:] = q[4:1] q.next[3] = not q[0] return logic Despite the benefits of this appreach (i.e. a reduction of the time that it takes to update the state of the block) I think that the corresponding MyHDL code is much more complex and harder to understand than the original code. In the tutorial you explain why you must use this different approach. Basically it is because in Python local variables lose do not keep their value between different runs of the function they are declared in. That is, local variables are not persistent in Python. While this is true, a very simple solution is to instead use "persistent" variables by declaring a attributes for the "logic()" function. That is, for each variable that we want to use in the design, we can declare a corresponding logic function attribute. These function attributes can be given their initial values by setting them outside of the logic() funcion (i.e. on the body of the "jc2()" function, after the "logic()" function has been defined). Using this approach I came up with the following alternative code: def jc2_attribute_based(goLeft, goRight, stop, clk, q): """ A bi-directional 4-bit Johnson counter with stop control. I/O pins: -------- clk : input free-running clock goLeft : input signal to shift left (active-low switch) goRight : input signal to shift right (active-low switch) stop : input signal to stop counting (active-low switch) q : 4-bit counter output (active-low LEDs; q[0] is right-most) """ @always(clk.posedge) def logic(): #try: # dir = logic.dir # run = logic.run #except: # logic.dir = DirType.LEFT # logic.run = False # direction if goRight == ACTIVE: logic.dir = DirType.RIGHT logic.run = True elif goLeft == ACTIVE: logic.dir = DirType.LEFT logic.run = True # stop if stop == ACTIVE: logic.run = False # counter action if logic.run: if logic.dir == DirType.LEFT: q.next[4:1] = q[3:] q.next[0] = not q[3] else: q.next[3:] = q[4:1] q.next[3] = not q[0] logic.dir = DirType.LEFT logic.run = False return logic As you can see this attribute-based version is pretty much identical to the original implementation. I tested it and it works great simulation wise, giving exactly the same result as your proposed "jc2_alt" implementation. The problem is that MyHDL does not currently support function attributes (calling toVHDL on my proposed design generates a very clear "¨Not supported: attribute assignment"). I think that the attribute-based approach is very generic (i.e. it could be applied whenever variables should be used) and it yields (pun intended :-) much simpler and easier to understand code than the "@instance/yield"based approach that is currently needed. Thus, as you can imagine from this long post, I'd like to suggest that MyHDL should get support for function attributes :-) Cheers, Angel |