Introduction
On this page we will present the design of a reversable 4 bit Johnson counter. A Johnson counter has a special structure that permits glitch-free decoding.
This example is originally from the Xilinx ISE design environment. If you wish, you can download the free version of the Xilinx ISE to review the original design and use it as a reference.
Specification
A Johnson counter basically consists of a circular shift register with an invertor in the loop.
The specification of the counter operation is as follows:
The counter is triggered on the rising edge of the clock (clk). A low pulse on the goLeft input will cause the counter to start shifting left from its current state. A low pulse on the goRight input will cause the counter to start shifting right from its current state. A low pulse on the stop input will cause the counter to hold its current state until goLeft or goRight is pulsed. After power-up, the counter is stopped with all outputs low (LEDs lit).
MyHDL code
The MyHDL code for this design looks as follows.
from myhdl import * ACTIVE = 0 DirType = enum('RIGHT', 'LEFT') @block 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
We use an enumerated type for the direction state dir
. We prefer this for
clarity when the encoding is not specified.
Test bench
The following is a test bench for a basic verification of the design.
from myhdl import * ACTIVE, INACTIVE = bool(0), bool(1) from jc2 import jc2 @block def test(): goLeft, goRight, stop, clk = [Signal(INACTIVE) for i in range(4)] q = Signal(intbv(0)[4:]) @always(delay(10)) def clkgen(): clk.next = not clk jc2_inst = jc2(goLeft, goRight, stop, clk, q) @instance def stimulus(): for i in range(3): yield clk.negedge for sig, nrcycles in ((goLeft, 10), (stop, 3), (goRight, 10)): sig.next = ACTIVE yield clk.negedge sig.next = INACTIVE for i in range(nrcycles-1): yield clk.negedge raise StopSimulation @instance def monitor(): print("goLeft goRight stop clk q") print("------------------------------") while True: yield clk.negedge yield delay(1) pStr = str('{:^6} {:^6} {:^5} '.format( int(goLeft), int(goRight), int(stop))) yield clk.posedge pStr += " C " yield delay(1) pStr += ' '+bin(q, 4) print(pStr) return clkgen, jc2_inst, stimulus, monitor simInst = test() simInst.config_sim(trace=True, tracebackup=False) simInst.run_sim()
We use a number of decorated functions to create a clock generator, a stimulus generator, and a response monitor, together with an instance of the design under test. Such a setup is quite typical.
When we simulate this test bench, the output is as follows:
goLeft goRight stop clk q ---------------------------- 1 1 1 C 0000 1 1 1 C 0000 0 1 1 C 0000 1 1 1 C 0001 1 1 1 C 0011 1 1 1 C 0111 1 1 1 C 1111 1 1 1 C 1110 1 1 1 C 1100 1 1 1 C 1000 1 1 1 C 0000 1 1 1 C 0001 1 1 0 C 0011 1 1 1 C 0011 1 1 1 C 0011 1 0 1 C 0011 1 1 1 C 0001 1 1 1 C 0000 1 1 1 C 1000 1 1 1 C 1100 1 1 1 C 1110 1 1 1 C 1111 1 1 1 C 0111 1 1 1 C 0011 1 1 1 C 0001
You can see the basic operation of the Johnson counter in action.
The presented test bench is rather basic. It is fine to get a quick idea about the design behavior, but it is inadequate as a verification tool. First, some corner case are not verified, such as the simultaneous occurence of some of the input signals. Also, it relies on visual inspection which is prone to human error and not suited for regression testing.
A better approach would be to write a self-checking test bench that checks
Johnson counter properties automatically, plus a number of directed and random
tests to cover corner cases. In addition, such a test bench should be written
using a unit test framework such as Python's unittest
package, to facilitate
test writing and to make it part of a regression test suite. Consult the MyHDL
manual for more info on such techniques.
Automatic conversion to Verilog or VHDL
A working MyHDL design intended for implementation can be converted to Verilog or VHDL
automatically, using the convInst.convert()
function. Remember: there is no point in
converting when the design doesn't work. The simulation will catch much more
errors than the converter. In MyHDL, like in Python, the run-time rules.
Conversion to Verilog can be done with code like the following:
def convert(): left, right, stop, clk = [Signal(INACTIVE) for i in range(4)] q = Signal(intbv(0)[4:]) convInst = jc2 (left, right, stop, clk, q) convInst.convert(hdl='Verilog') convInst.convert(hdl='VHDL') convert()
As you see, conversion works on an instantiated, elaborated design.
The resulting Verilog code is as follows:
module 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) input goLeft; input goRight; input stop; input clk; output [3:0] q; reg [3:0] q; reg [0:0] dir; reg run; always @(posedge clk) begin: JC2_LOGIC if ((goRight == 0)) begin dir <= 1'b0; run <= 1'b1; end else if ((goLeft == 0)) begin dir <= 1'b1; run <= 1'b1; end if ((stop == 0)) begin run <= 1'b0; end if (run) begin if ((dir == 1'b1)) begin q[4-1:1] <= q[3-1:0]; q[0] <= (!q[3]); end else begin q[3-1:0] <= q[4-1:1]; q[3] <= (!q[0]); end end end endmodule
The tests to the dir
variable in the MyHDL code are mapped to a Verilog
case
statement. This is because the Verilog convertor handles comparisons to
enumerated type items in a special way. The goal is to describe finite state
machines efficiently. For example, it is possible to specify a different
encoding using the encoding
attribute of the enumerated type. The choices are
binary
(the default), one_hot
, and one_cold
. Of course, with only 2
states like in this case, this functionality is not relevant. But in general,
it is an additional advantage of the use of enumerated types.
Alternative design
This is not yet the end of the story.
The original design in the Xilinx ISE distribution contains 3 views: Abel, Verilog, and VHDL. Interestingly, the Verilog and VHDL views are inconsistent: they have a different behavior. In Verilog terminology, the Verilog view uses blocking assignments only, while the VHDL view uses non-blocking (= signal) assignments only. The VHDL view seems to be consistent with the Abel view and with the supplied test vectors, so it has been the basis of the design that has been discussed so far. However, it is interesting to investigate what the implications of the Verilog view would be on the MyHDL code. Actually, the role of blocking and non-blocking assignments in Verilog is poorly understood, and I believe MyHDL can help to clarify the issues. (The reason is that MyHDL, like VHDL but unlike Verilog, makes a clear difference between signals and variables).
Consider how long it takes before a control input change influences the counter
output. In the discussed design, it takes two clock edges: one edge to set the
state registers dir
and run
, and one edge to see the influence of the state
registers on the count. You can verify this behavior from the test bench
output. (Note that inputs are sampled before the clock edge, and outputs after
the clock edge).
Now suppose we would prefer to influence the count output after a single clock
edge. Can this be done? The answer is positive: however, to support that we
will need to use variables instead of signals for the state registers dir
and
run
.
The modified MyHDL code looks as follows:
from myhdl import * ACTIVE = 0 DirType = enum('RIGHT', 'LEFT') @block 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
The instance
decorator is used to create the logic
generator from the
corresponding generator function. The main functionality of that function is
wrapped in a while True
loop to keep the generator alive "forever". The first
statement in the loop is a yield
statement that specifies the sensitivity to
the clock edge. Variables dir
and run
are local state variables, that keep
their previous value through each iteration of the while
loop.
You may wonder why we couldn't use the always
decorator with a clock edge
argument as before. The reason is that in that case, the decorated function is
a classic (= non-generator) function that would be called on every clock edge.
However, local variables loose their value whenever a function returns, so they
cannot hold state. Therefore, we need to use a more general approach and code
the desired behavior explicitly in a generator function.
This example is more subtle and complex than it may seem at first sight. As
said before, variables dir
and run
are state variables and will therefore
require a flip-flop in an implementation. However, they are also used
"combinatorially": when they change, they may influence the counter operation
"in the same clock cycle", that is, before the flip-flop output changes. This
is perfectly fine behavior and no problem for synthesis tools, but it tends to
confuse a lot of designers. This coding style is also a favourite theme of this
author :-) ( --- Jan Decaluwe).
To verify whether we now have the desired behavior, we can run the original test bench on the alternative design:
Alternative design ------------------ goLeft goRight stop clk q ------------------------------ 1 1 1 C 0000 1 1 1 C 0000 0 1 1 C 0001 1 1 1 C 0011 1 1 1 C 0111 1 1 1 C 1111 1 1 1 C 1110 1 1 1 C 1100 1 1 1 C 1000 1 1 1 C 0000 1 1 1 C 0001 1 1 1 C 0011 1 1 0 C 0011 1 1 1 C 0011 1 1 1 C 0011 1 0 1 C 0001 1 1 1 C 0000 1 1 1 C 1000 1 1 1 C 1100 1 1 1 C 1110 1 1 1 C 1111 1 1 1 C 0111 1 1 1 C 0011 1 1 1 C 0001 1 1 1 C 0000
If you compare this output with the previous one, you will see that the counter response to a control input change is now indeed one clock cycle earlier.
Like before, we can convert the MyHDL code automatically to Verilog:
module 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) input goLeft; input goRight; input stop; input clk; output [3:0] q; reg [3:0] q; always @(posedge clk) begin: JC2_ALT_LOGIC reg [1-1:0] dir; reg run; if ((goRight == 0)) begin dir = 1'b0; run = 1'b1; end else if ((goLeft == 0)) begin dir = 1'b1; run = 1'b1; end if ((stop == 0)) begin run = 1'b0; end if (run) begin if ((dir == 1'b1)) begin q[4-1:1] <= q[3-1:0]; q[0] <= (!q[3]); end else begin q[3-1:0] <= q[4-1:1]; q[3] <= (!q[0]); end end end endmodule
Verilog's always block is more general than the MyHDL always
decorator,
because you cannot use local state variables with the latter. However, even
though the MyHDL code for the alternative design doesn't use the always
decorator, the convertor is smart enough to see that it can still use it in the
Verilog code in this case.
Note that blocking and non-blocking assignments are mixed in the always block. This is necessary to match the behavior of the alternative MyHDL design. In the Verilog world, you may encounter rules that forbid such a coding style. Such rules are typically created by designers that prefer to focus on "thinking hardware" instead of thinking in terms of code, even when synthesis tools are perfectly able to create efficient hardware from that code. The best you can do with such rules is to ignore them.