Gray counter

There are cases where you construct a logic out of a chain of primitive elements. Take a gray counter, for example. It is used often for cross-clock-domain negotations (dual clock FIFO fill counters).

Note that most of the work below is simply done by logical gate connections without using a process.

In [1]:
import sys
sys.path.insert(0, '../../')
In [2]:
from myirl.emulation.myhdl import *


@block
def gray_counter1(clk : ClkSignal,
                  enable : Signal,
                  reset : ResetSignal,
                  gray_count :Signal.Output):

    n = len(gray_count)
    gray_bits = [ Signal(bool(), name="u%d" % i) for i in range(n) ]
    code, work = [ Signal(modbv()[n:]) for _ in range(2) ]
    flags = [ Signal(bool(), name="flags%d" % i) for i in range(n + 1) ]
    toggle = Signal(bool(1))

    # This creates connection instances:
    connections = [
        flags[0].wireup(False),
        code.wireup(concat(*reversed(gray_bits))),
        work.wireup(concat("1", code[n-2:], toggle))
    ]  
        
    for i in range(n):
        v = work[i] & ~flags[i]
        connections += [
            gray_bits[i].wireup(v ^ gray_count[i]),
            flags[i + 1].wireup(flags[i] | v )
        ]
                
    @always_seq(clk.posedge, reset)
    def worker():
        if enable == True:
            gray_count.next = code
            toggle.next = ~toggle

    return instances()

Simulation

This particular implementation translates via the yosys' CXXRTL simulator backend, using the 'new style' factory class.

The above code is elaborated implicitely using direct RTL transfer, compiled into C++ code and executed. The result of this is a trace dump into a VCD file.

For fun, we create a Waveform class from an Iterator first:

In [3]:
from myirl.loops import Iterator

class Waveform(Iterator):
    def __init__(self, val, name = 'waveform'):
        it = [ True if i == '1' else False for i in val]
        super().__init__(it, name)
    def convert(self, tgt, tsz = None):
        return "True" if self.val else "False"
    def resolve_type(self):
        return bool
In [4]:
from myirl.emulation.myhdl import *
from myirl.emulation.factory_class import factory    

class WaveformCreatorMixin:
    def waveform(self, top, name, waveconfig):

        # Configure waveform:
        cfg = wavedraw.config(top, 
              waveconfig
        )

        u = [ top + ".u%d" % i for i in range(self.size) ]

        for s in u:
            cfg[s] = None

        return wavedraw.vcd2wave("%s.vcd" % name, top + '.clk', cfg, delta = 1)

class example_design(factory.Module, WaveformCreatorMixin):
    # Note: in ipython, it is mandatory for a class to have an __init__ function
    # in order to retrieve the source for compilation
    def __init__(self, name, simclass, SIZE, *args):
        super().__init__(name, simclass, *args)
        self.size = SIZE

    @factory.testbench('ns')
    def tb_gray1(self):
        SIZE = self.size
        clk = self.ClkSignal(name = 'clk')
        ce = self.Signal(bool(), name='ce')

        reset = self.ResetSignal(0, 1, isasync = True)

        dout = self.Signal(intbv()[SIZE:])    

        @self.always(delay(2))
        def clkgen():
            clk.next = ~clk

        gc = gray_counter1(clk, ce, reset, dout)

        @self.sequence
        def stim():
            ce.next = False
            reset.next = True
            yield delay(6)
            reset.next = False
            for i in Waveform("0011111111111111111111111111111111000"):
                ce.next = i       
                yield clk.negedge

            raise StopSimulation

        return instances()  
    

Run the test bench for a few cycles.

In [5]:
from yosys.simulator import CXXRTL

# Alternatively, we can fall back to GHDL:
from myirl.test.ghdl import GHDL

SIM = CXXRTL


d = example_design("myhdl_sim", SIM, 5)
t = d.tb_gray1()
t.run(180, wavetrace = t.name + ".vcd")
    
DEBUG: Skip non-simulation type <class '__main__.example_design'>
 Adding module with name `gray_counter1` 
 FINALIZE implementation `gray_counter1` of `gray_counter1` 
Compiling /tmp/myirl_gray_counter1_zj7qg0fo/gray_counter1_d977.pyx because it changed.
[1/1] Cythonizing /tmp/myirl_gray_counter1_zj7qg0fo/gray_counter1_d977.pyx
running build_ext
building 'gray_counter1_d977' extension
creating build/temp.linux-x86_64-3.9/tmp/myirl_gray_counter1_zj7qg0fo
x86_64-linux-gnu-gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O2 -Wall -g -ffile-prefix-map=/build/python3.9-RNBry6/python3.9-3.9.2=. -fstack-protector-strong -Wformat -Werror=format-security -g -fwrapv -O2 -g -ffile-prefix-map=/build/python3.9-RNBry6/python3.9-3.9.2=. -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -DCOSIM_NAMESPACE=gray_counter1_d977 -I../../myirl/../ -I/tmp/myirl_gray_counter1_zj7qg0fo/ -I/usr/share/yosys/include -I/usr/include/python3.9 -c /tmp/myirl_gray_counter1_zj7qg0fo/gray_counter1_d977.cpp -o build/temp.linux-x86_64-3.9/tmp/myirl_gray_counter1_zj7qg0fo/gray_counter1_d977.o
x86_64-linux-gnu-gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O2 -Wall -g -ffile-prefix-map=/build/python3.9-RNBry6/python3.9-3.9.2=. -fstack-protector-strong -Wformat -Werror=format-security -g -fwrapv -O2 -g -ffile-prefix-map=/build/python3.9-RNBry6/python3.9-3.9.2=. -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -DCOSIM_NAMESPACE=gray_counter1_d977 -I../../myirl/../ -I/tmp/myirl_gray_counter1_zj7qg0fo/ -I/usr/share/yosys/include -I/usr/include/python3.9 -c /tmp/myirl_gray_counter1_zj7qg0fo/gray_counter1_d977_rtl.cpp -o build/temp.linux-x86_64-3.9/tmp/myirl_gray_counter1_zj7qg0fo/gray_counter1_d977_rtl.o
x86_64-linux-gnu-g++ -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-z,relro -g -fwrapv -O2 -Wl,-z,relro -g -fwrapv -O2 -g -ffile-prefix-map=/build/python3.9-RNBry6/python3.9-3.9.2=. -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 build/temp.linux-x86_64-3.9/tmp/myirl_gray_counter1_zj7qg0fo/gray_counter1_d977.o build/temp.linux-x86_64-3.9/tmp/myirl_gray_counter1_zj7qg0fo/gray_counter1_d977_rtl.o -o build/lib.linux-x86_64-3.9/gray_counter1_d977.cpython-39-x86_64-linux-gnu.so
copying build/lib.linux-x86_64-3.9/gray_counter1_d977.cpython-39-x86_64-linux-gnu.so -> 
Open for writing: testbench.vcd
STOP SIMULATION @152 

Waveform display

The resulting waveform is displayed using a signal filter configuration:

In [6]:
import nbwavedrom, wavedraw
waveconfig = { '.reset' : None, '.clk' : None, '.enable' : None }
waveform = d.waveform(t.uut_name, t.name, waveconfig)
nbwavedrom.draw(waveform)

Notes

This is not so much a recommended design style, as the above gray_counter1 implementation can be owned by several contexts, as it's defined as a global function. If several different contexts are referring to it as a top level object, unwanted effects may occur.

Therefore it is better to stick all kind of parametrizable top level objects into a design context class.

HDL translation

To explicitely convert to HDL, we create an instance and elaborate:

In [7]:
def convert(SIZE):
    clk = ClkSignal()
    ce = Signal(bool())

    reset = ResetSignal(0, 1, isasync = True)

    dout = Signal(intbv()[SIZE:])    

    gc = gray_counter1(clk, ce, reset, dout)
    
    f = gc.elab(targets.Verilog)
    return f

f = convert(8)
 Module gray_counter1: Existing instance gray_counter1, rename to gray_counter1_1 
 Writing 'gray_counter1_1' to file /tmp/myirl_gray_counter1_zj7qg0fo/gray_counter1_1.v 
In [8]:
!cat {f[0]}
// File generated from source:
//     /tmp/ipykernel_82992/733657572.py
// (c) 2016-2022 section5.ch
// Modifications may be lost, edit the source file instead.

`timescale 1 ns / 1 ps
`include "aux.v"
// Architecture myhdl_emulation

module gray_counter1_1
    (
        input wire  clk,
        input wire  enable,
        input wire /* std_ulogic */ reset,
        output reg [7:0] gray_count
    );
    // Local type declarations
    // Signal declarations
    reg  toggle;
    wire [7:0] code;
    wire  flags0;
    wire  u7;
    wire  u6;
    wire  u5;
    wire  u4;
    wire  u3;
    wire  u2;
    wire  u1;
    wire  u0;
    wire [7:0] work;
    wire  flags1;
    wire  flags2;
    wire  flags3;
    wire  flags4;
    wire  flags5;
    wire  flags6;
    wire  flags7;
    wire  flags8;
    
    always @ (posedge clk or posedge reset) begin : WORKER
        if (reset == 1'b1) begin
            gray_count <= 8'h00;
            toggle <= 1'b1;
        end else begin
            if ((enable == 1'b1)) begin
                gray_count <= code;
                toggle <= ~toggle;
            end
        end
    end
    assign flags0 = 1'b0;
    assign code = {u7, u6, u5, u4, u3, u2, u1, u0};
    assign work = {1'b1, code[6-1:0], toggle};
    assign u0 = ((work[0] & ~flags0) ^ gray_count[0]);
    assign flags1 = (flags0 | (work[0] & ~flags0));
    assign u1 = ((work[1] & ~flags1) ^ gray_count[1]);
    assign flags2 = (flags1 | (work[1] & ~flags1));
    assign u2 = ((work[2] & ~flags2) ^ gray_count[2]);
    assign flags3 = (flags2 | (work[2] & ~flags2));
    assign u3 = ((work[3] & ~flags3) ^ gray_count[3]);
    assign flags4 = (flags3 | (work[3] & ~flags3));
    assign u4 = ((work[4] & ~flags4) ^ gray_count[4]);
    assign flags5 = (flags4 | (work[4] & ~flags4));
    assign u5 = ((work[5] & ~flags5) ^ gray_count[5]);
    assign flags6 = (flags5 | (work[5] & ~flags5));
    assign u6 = ((work[6] & ~flags6) ^ gray_count[6]);
    assign flags7 = (flags6 | (work[6] & ~flags6));
    assign u7 = ((work[7] & ~flags7) ^ gray_count[7]);
    assign flags8 = (flags7 | (work[7] & ~flags7));
endmodule // gray_counter1_1