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).
This gray counter example is a bit 'overdone' in comparison to the classical examples to demonstrate configureability and procedural concatenation of elements.
This gray_unit
is a (configureable) element to construct gray counters of arbitrary length.
from myirl.emulation.myhdl import *
_in = Signal
_out = Signal.Output
@block
def gray_unit(
clk : ClkSignal,
ce : _in, reset : ResetSignal,
flavor : _in,
ia : _in,
ib: _in,
oa : _out,
ob : _out,
NEW_STYLE = False,
*, INITVAL = False
):
a = Signal(INITVAL, name='a')
@always_seq(clk.posedge, reset)
def worker():
if ce == 1:
a.next = a ^ ((ia ^ flavor) & ib)
if NEW_STYLE:
wire0 = oa.wireup(a)
wire1 = ob.wireup(ib & ~(ia ^ flavor))
else:
@always_comb
def assign():
oa.next = a
ob.next = ib & ~(ia ^ flavor)
return instances()
From this element, we construct a gray counter using a few auxiliaries:
import myhdl
def graycode(v, size):
bv = intbv(v)[size:]
z = bv ^ myhdl.concat("0", bv[size:1])
# print "Gray code:", z, len(bv)
return intbv(z)[size:]
def parity(v):
"""Return parity bit from value v"""
p = False
for i in v:
if i:
p = not p
return p
@block
def gray_counter(
clk : ClkSignal,
ce : Signal,
reset : ResetSignal,
rval : intbv,
direction : Signal,
val : Signal.Output
):
n = len(val)
p = parity(rval)
z = not (p ^ direction) # Important to use `not` to get boolean result
u = [ Signal(bool(z), name="u%d" % (i)) for i in range(n+1) ]
u0 = u[0]
v = [ Signal(bool(), name='v%d' % i) for i in range(n+1) ]
s = Signal(bool())
tmp = list(reversed(u))[:-1]
out = concat(*tmp)
inst_gray = []
@always_seq(clk.posedge, reset)
def worker():
if ce == 1:
u0.next = ~u0
wires = [
val.wireup(out),
v[0].wireup(True)
]
@always_comb
def assign():
if direction == True:
s.next = u[n-1] ^ u[n]
else:
s.next = u[n-1] | u[n]
for i in range(1, n):
inst_gray.append(gray_unit(clk, ce, reset, direction, u[i-1], v[i-1], u[i], v[i], INITVAL = rval[i-1]))
# and MSB:
inst_gray.append( gray_unit(clk, ce, reset, direction, s, v[n-1], u[n], v[n], INITVAL = rval[n-1]))
return instances()
For fun, we create a Waveform
class from an Iterator
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
from myirl.test.common_test import gen_osc
@block
def tb_gray(SIZE):
clk = ClkSignal(name='clk')
ce = Signal(bool(), name='ce')
direction = Signal(bool(1))
direction.init = True
reset = ResetSignal(0, 1, isasync = False)
dout = Signal(intbv()[SIZE:], name='dout')
o = Signal(intbv()[SIZE:])
rv = graycode(0, SIZE)
gc = gray_counter(clk, ce, reset, rv, direction, o, USE_ALIAS=True)
osc = gen_osc(clk, 4)
@always_comb
def assign():
dout.next = o
@instance
def stim():
ce.next = False
direction.next = False
reset.next = True
yield delay(10)
reset.next = False
for i in Waveform("0011111111100000"):
ce.next = i
yield clk.negedge
direction.next = True
for i in Waveform("001111000111110"):
ce.next = i
yield clk.negedge
raise StopSimulation
return instances()
from myirl import targets
from myirl.test.common_test import run_ghdl
def run_testbench(SIZE):
tb = tb_gray(SIZE)
files = tb.elab(targets.VHDL, elab_all=True)
run_ghdl(files, tb, debug = True, vcdfile='tb_gray.vcd')
return files
GRAY_COUNTER_SIZE = 4
f = run_testbench(GRAY_COUNTER_SIZE)
DEBUG CREATE wrapper module for tb_gray (EmulationModule 'top_tb_gray') Creating process 'gray_counter/worker' with sensitivity (clk'rising, <reset>) Using default for NEW_STYLE: False NEW_STYLE: use default False Creating process 'gray_unit/worker' with sensitivity (clk'rising, <reset>) Using default for NEW_STYLE: False NEW_STYLE: use default False Using default for NEW_STYLE: False NEW_STYLE: use default False Using default for NEW_STYLE: False NEW_STYLE: use default False Creating sequential 'tb_gray/stim' Elaborating component gray_unit_s1_s1_s1_s1_s1_s1_s1_s1_0_0 Writing 'gray_unit' to file /tmp/myirl_top_tb_gray_3jljf_pm/gray_unit.vhdl Elaborating component gray_counter_s1_s1_s1__intbv_s1_s4 Writing 'gray_counter' to file /tmp/myirl_top_tb_gray_3jljf_pm/gray_counter.vhdl Elaborating component tb_gray_4 Writing 'tb_gray' to file /tmp/myirl_top_tb_gray_3jljf_pm/tb_gray.vhdl Creating library file /tmp/myirl_module_defs_1886ekfy/module_defs.vhdl ==== COSIM stdout ==== ==== COSIM stderr ==== ==== COSIM stdout ==== analyze /home/testing/.local/lib/python3.9/site-packages/myirl-0.0.0-py3.9-linux-x86_64.egg/myirl/targets/../test/vhdl/txt_util.vhdl analyze /home/testing/.local/lib/python3.9/site-packages/myirl-0.0.0-py3.9-linux-x86_64.egg/myirl/targets/libmyirl.vhdl analyze /tmp/myirl_top_tb_gray_3jljf_pm/gray_unit.vhdl analyze /tmp/myirl_top_tb_gray_3jljf_pm/gray_counter.vhdl analyze /tmp/myirl_top_tb_gray_3jljf_pm/tb_gray.vhdl elaborate tb_gray ==== COSIM stderr ==== ==== COSIM stdout ==== /tmp/myirl_top_tb_gray_3jljf_pm/tb_gray.vhdl:156:9:@256ns:(assertion failure): Stop Simulation /tmp/tb_gray:error: assertion failed in process .tb_gray(myirl).stim /tmp/tb_gray:error: simulation failed ==== COSIM stderr ====
In the non-parametrized variant, two implementations of gray_unit
are emitted. We dump the first:
! cat -n {f[0]}
1 -- File generated from source: 2 -- /tmp/ipykernel_40759/2642985506.py 3 -- (c) 2016-2021 section5.ch 4 -- Modifications may be lost, edit the source file instead. 5 6 library IEEE; 7 use IEEE.std_logic_1164.all; 8 use IEEE.numeric_std.all; 9 10 library work; 11 12 use work.txt_util.all; 13 use work.myirl_conversion.all; 14 15 entity gray_unit is 16 generic ( 17 INITVAL: boolean := FALSE 18 ); 19 port ( 20 clk : in std_ulogic; 21 ce : in std_ulogic; 22 reset : in std_ulogic; 23 flavor : in std_ulogic; 24 ia : in std_ulogic; 25 ib : in std_ulogic; 26 oa : out std_ulogic; 27 ob : out std_ulogic 28 ); 29 end entity gray_unit; 30 31 architecture MyIRL of gray_unit is 32 -- Local type declarations 33 -- Signal declarations 34 signal a : std_ulogic; 35 begin 36 37 worker: 38 process(clk, reset) 39 begin 40 if rising_edge(clk) then 41 if reset = '1' then 42 a <= '0'; 43 else 44 if (ce = '1') then 45 a <= (a xor ((ia xor flavor) and ib)); 46 end if; 47 end if; 48 end if; 49 end process; 50 51 assign: 52 process(a, ib, ia, flavor) 53 begin 54 oa <= a; 55 ob <= (ib and (ia xor flavor)); 56 end process; 57 58 end architecture MyIRL; 59
import wavedraw
import nbwavedrom
TB = "tb_gray"
# Configure waveform:
cfg = wavedraw.config(TB, {
'.clk' : None,
'.reset' : None, '.dout[%d:0]' % (GRAY_COUNTER_SIZE-1) : None, '.ce' : None})
cfg[TB + '.inst_gray_counter_0.reset'] = None
u = [ TB + ".inst_gray_counter_0.u%d" % i for i in range(GRAY_COUNTER_SIZE+1) ]
for s in u:
cfg[s] = None
waveform = wavedraw.vcd2wave("tb_gray.vcd", TB + '.clk', cfg, delta = 10)
nbwavedrom.draw(waveform)
tb_gray.clk tb_gray.reset tb_gray.dout[3:0] tb_gray.ce tb_gray.inst_gray_counter_0.reset tb_gray.inst_gray_counter_0.u0 tb_gray.inst_gray_counter_0.u1 tb_gray.inst_gray_counter_0.u2 tb_gray.inst_gray_counter_0.u3 tb_gray.inst_gray_counter_0.u4
For a more realistic, more compact implementation, we use only a bit of procedural connection generator syntax. We also skip direction (downcounting) and initializer support.
@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()
Test bench:
from myirl.test.common_test import gen_osc
@block
def tb_gray1(SIZE):
clk = ClkSignal(name='clk')
ce = Signal(bool(), name='ce')
reset = ResetSignal(0, 1, isasync = True)
dout = Signal(intbv()[SIZE:], name='dout')
o = Signal(intbv()[SIZE:])
rv = graycode(0, SIZE)
gc = gray_counter1(clk, ce, reset, o)
osc = gen_osc(clk, 2)
@always_comb
def assign():
dout.next = o
@instance
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()
from myirl import targets
from myirl.test.common_test import run_ghdl
def run_testbench(SIZE):
tb = tb_gray1(SIZE)
tb.rename("tb_gray1") # Rename explicitely so that we can rerun
# in interactive mode
files = tb.elab(targets.VHDL, elab_all=True)
run_ghdl(files, tb, vcdfile='tb_gray1.vcd')
return files
GRAY_COUNTER_SIZE = 4
f = run_testbench(GRAY_COUNTER_SIZE)
DEBUG CREATE wrapper module for tb_gray1 (EmulationModule 'top_tb_gray1') Creating process 'gray_counter1/worker' with sensitivity (clk'rising, <reset>) Creating sequential 'tb_gray1/stim' Elaborating component gray_counter1_s1_s1_s1_s4 Writing 'gray_counter1' to file /tmp/myirl_top_tb_gray1_bnnr5vi7/gray_counter1.vhdl Elaborating component tb_gray1_4 Writing 'tb_gray1' to file /tmp/myirl_top_tb_gray1_bnnr5vi7/tb_gray1.vhdl Creating library file /tmp/myirl_module_defs_5dp7bn94/module_defs.vhdl
TB = "tb_gray1"
# Configure waveform:
cfg = wavedraw.config(TB,
{ '.reset' : None, '.dout[%d:0]' % (GRAY_COUNTER_SIZE-1) : None, '.ce' : None
}
)
cfg[TB + '.inst_gray_counter1_0.reset'] = None
u = [ TB + ".inst_gray_counter1_0.u%d" % i for i in range(GRAY_COUNTER_SIZE) ]
for s in u:
cfg[s] = None
waveform = wavedraw.vcd2wave("tb_gray1.vcd", TB + '.clk', cfg, delta = 1)
nbwavedrom.draw(waveform)
tb_gray1.reset tb_gray1.dout[3:0] tb_gray1.ce tb_gray1.inst_gray_counter1_0.reset tb_gray1.inst_gray_counter1_0.u0 tb_gray1.inst_gray_counter1_0.u1 tb_gray1.inst_gray_counter1_0.u2 tb_gray1.inst_gray_counter1_0.u3
!cat {f[0]}
-- File generated from source: -- /tmp/ipykernel_40759/3048462444.py -- (c) 2016-2021 section5.ch -- Modifications may be lost, edit the source file instead. library IEEE; use IEEE.std_logic_1164.all; use IEEE.numeric_std.all; library work; use work.txt_util.all; use work.myirl_conversion.all; entity gray_counter1 is port ( clk : in std_ulogic; enable : in std_ulogic; reset : in std_ulogic; gray_count : out unsigned(3 downto 0) ); end entity gray_counter1; architecture MyIRL of gray_counter1 is -- Local type declarations -- Signal declarations signal toggle : std_ulogic; signal code : unsigned(3 downto 0); signal flags0 : std_ulogic; signal u3 : std_ulogic; signal u2 : std_ulogic; signal u1 : std_ulogic; signal u0 : std_ulogic; signal work : unsigned(3 downto 0); signal flags1 : std_ulogic; signal flags2 : std_ulogic; signal flags3 : std_ulogic; signal flags4 : std_ulogic; begin worker: process(clk, reset) begin if reset = '1' then gray_count <= x"0"; toggle <= '1'; elsif rising_edge(clk) then if (enable = '1') then gray_count <= code; toggle <= not toggle; end if; end if; end process; flags0 <= '0'; code <= (u3 & u2 & u1 & u0); work <= ('1' & code(2-1 downto 0) & toggle); u0 <= ((work(0) and not flags0) xor gray_count(0)); flags1 <= (flags0 or (work(0) and not flags0)); u1 <= ((work(1) and not flags1) xor gray_count(1)); flags2 <= (flags1 or (work(1) and not flags1)); u2 <= ((work(2) and not flags2) xor gray_count(2)); flags3 <= (flags2 or (work(2) and not flags2)); u3 <= ((work(3) and not flags3) xor gray_count(3)); flags4 <= (flags3 or (work(3) and not flags3)); end architecture MyIRL;