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.
import sys
sys.path.insert(0, '../../')
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()
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:
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.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.
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")
Insert unit gray_counter1_s1_s1_s1_s5 DEBUG: Skip non-simulation type <class '__main__.example_design'> Adding module with name `gray_counter1` FINALIZE implementation `gray_counter1` of `gray_counter1` Compiling /tmp/gray_counter1_d977.pyx because it changed. [1/1] Cythonizing /tmp/gray_counter1_d977.pyx running build_ext building 'gray_counter1_d977' extension 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/ -I/usr/share/yosys/include -I/usr/include/python3.9 -c /tmp/gray_counter1_d977.cpp -o build/temp.linux-x86_64-3.9/tmp/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/ -I/usr/share/yosys/include -I/usr/include/python3.9 -c /tmp/gray_counter1_d977_rtl.cpp -o build/temp.linux-x86_64-3.9/tmp/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/gray_counter1_d977.o build/temp.linux-x86_64-3.9/tmp/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
The resulting waveform is displayed using a signal filter configuration:
import nbwavedrom, wavedraw
waveconfig = { '.reset' : None, '.clk' : None, '.enable' : None }
waveform = d.waveform(t.uut_name, t.name, waveconfig)
nbwavedrom.draw(waveform)
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.
To explicitely convert to HDL, we create an instance and elaborate:
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)
FALLBACK: UNHANDLED ROOT CLASS <class 'ipykernel.zmqshell.ZMQInteractiveShell'>, create new context
Module gray_counter1: Existing instance gray_counter1, rename to gray_counter1_1
Writing 'gray_counter1_1' to file /tmp/myirl_gray_counter1_0wci4dqa/gray_counter1_1.v
!cat {f[0]}
// File generated from source: // /tmp/ipykernel_30160/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