Composite classes explicitely act as a local custom signal type in the foreground, but can implicitely generate hardware instances. Obviously, a composite class element can not be passed through the interface.
For example, one might want to instance many Counter
signals with incrementing/reset logic spelled out explicitely. Later, it might be decided to swap them out against a Gray coded variant. In this case, the increment logic is internally a different one, however, we can handle this within the class
that we still can write counter.next = counter + 1
.
Another use case is when it is desired to create logic elements with one main output signal and a few input signals, e.g. instead of
inst = unit(clk, en, val, WIDTH=8)
we'd spell
val = Unit(clk, en, WIDTH=8)
where val
is again a signal instance. However it might be wise to check if a @blackbox_inline
implementation is the better option, when val
is driven from within unit
.
Some libraries may choose to use lower caps for the classic instanced units whereas the class style is used for a Composite generator class. Due to VHDL not being case sensitive, those two styles should not be mixed.
Let's try a simple counter scenario. The idea is, to swap the c
Signal against an extended Gray counter later on.
from myirl.emulation.myhdl import *
from myirl.library.basictypes import *
@block
def counter_unit(clk : ClkSignal, reset: ResetSignal, en : Bool, finished : Bool.Output, COUNT_END, WIDTH = 8):
c = Signal(intbv(-1)[WIDTH:])
@always_seq(clk.posedge, reset)
def worker():
if c == COUNT_END:
finished.next = True
elif en:
c.next = c + 1
finished.next = False
return instances()
clk = ClkSignal()
rst = ResetSignal(0, 1)
en = Bool()
fin = Bool()
uut = counter_unit(clk, rst, en, fin, COUNT_END = 144)
f = uut.elab(targets.VHDL)
FALLBACK: UNHANDLED ROOT CLASS <class 'ipykernel.zmqshell.ZMQInteractiveShell'>, create new context Using default for WIDTH: 8 WIDTH: use default 8 Writing 'counter_unit' to file /tmp/myirl_counter_unit_0o0w51yp/counter_unit.vhdl Warning: Implicit truncation of ADD(c, C:1) result
We now swap out the counter signal against a gray counter with minimal changes in the actual RTL description. A bit of derivation framework has to be added below.
First, we create an assignment generator class for the gray counter signal class:
from myirl.emulation.myhdl import *
class GCAssign(base.SigAssign):
def __init__(self, sig, other):
print("INIT GCASSIGN")
self._assignments = [
sig.toggle.set(~sig.toggle),
sig.reg_code.set(sig.next_code)
]
super().__init__(sig, other)
def emit(self, ctx):
for a in self._assignments:
a.emit(ctx)
Like the @blackbox_component
decorator, the @Composite.block
creates IRL objects from inside a class.
from examples.lib_blackbox import blackbox_component
from myirl.composite import Composite
import myirl
# Not a container, we don't pass this through the hierarchy
class GrayCounter(Composite):
def __init__(self, n):
self.n = n
self.toggle = Signal(bool(1), name = "toggle")
self.work, self.reg_code, self.next_code = [ Signal(intbv(0)[n:]) for _ in range(3) ]
self.work.rename("work")
self.flags = [ Signal(bool()) for _ in range(n + 1) ]
self.gbits = [ Signal(bool(), name="u%d" % i) for i in range(n) ]
instances = [
self.bb_gc(self.reg_code, self.toggle, self.next_code )
]
super().__init__(instances)
def get(self):
return self.next_code
def set(self, other):
if isinstance(other, int):
return base.GenAssign(self.reg_code, other)
elif isinstance(other, base.Add):
return GCAssign(self, other)
else:
raise ValueError("Trying to assign to %s" % type(other))
def size(self, effective = None):
return self.n
def evaluate(self):
self.toggle.evaluate()
return self.next_code.evaluate()
# Manual setting of source and drivers,
# better would be to obtain it automatically from the logic
def get_sources(self, sigs):
for s in self.toggle, self.reg_code, self.next_code:
sigs[s.identifier] = s
def get_drivers(self, sigs):
for s in self.toggle, self.reg_code:
sigs[s.identifier] = s
@Composite.block
def bb_gc(self,
cur_code : Signal,
toggle : Signal.Type(bool),
next_code : Signal.Output):
connections = self.logic(toggle, cur_code)
connections += [
next_code.set(myirl.concat(*reversed(self.gbits)))
]
return connections
def logic(self, toggle, cur_code):
connections = [
self.flags[0] .wireup(False),
self.work .wireup(
base.Concat("1", *reversed(self.gbits[:self.n-2]), toggle))
]
for i in range(self.n):
v = self.work[i] & ~self.flags[i]
connections += [
self.gbits[i] .wireup (v ^ cur_code[i]),
self.flags[i + 1] .wireup (self.flags[i] | v )
]
return connections
Note: Instead of instancing an owned @Composite.block
, we may also instance external @block
units, likewise.
Now our gray counter instance looks like this:
from cyrite.library.counter.gray import graycode
@block
def counter_unit_gray(clk : ClkSignal, reset : ResetSignal, en : Bool, finished : Bool.Output,
COUNT_END, WIDTH = 8):
c = GrayCounter(WIDTH)
# Need to translate the end value to gray code:
endval = int(graycode(COUNT_END, WIDTH))
@always_seq(clk.posedge, reset)
def worker():
if c == endval:
finished.next = True
elif en:
c.next = c + 1
finished.next = False
return instances()
All combinatorial logic is actually buried in the associated inline component, however we have to explicitely call the graycode()
function to translate the binary value into the corresponding gray code.
from myirl.test.ghdl import GHDL
@sim.testbench(GHDL, 'ns')
@block
def tb_counter(unit):
clk = ClkSignal()
rst = ResetSignal(0, 1)
en, finished = [ Bool() for _ in range(2) ]
counter = Signal(intbv(0)[8:])
@always(delay(3))
def clkgen():
clk.next = ~clk
@always_seq(clk.posedge, rst)
def worker():
if en and not finished:
counter.next = counter + 1
@instance
def main():
rst.next = True
yield delay(20)
rst.next = False
en.next = True
while finished == False:
yield clk.posedge
assert counter == 20
print("DONE")
raise StopSimulation
uut = unit(clk, rst, en, finished, COUNT_END = 20)
return instances()
tb = tb_counter(counter_unit_gray)
tb.run(-1, wavetrace = 'test_counter.vcd')
/home/testing/.local/lib/python3.9/site-packages/myirl-0.0.0-py3.9-linux-x86_64.egg/myirl/emulation/myhdl2irl.py:655: UserWarning: /tmp/ipykernel_26086/1827286769.py:tb_counter():18 Replacing logical `not` by inversion warnings.warn(self.get_location(node) + \
FALLBACK: UNHANDLED ROOT CLASS <class 'myirl.test.ghdl.GHDLTestbench'>, create new context FALLBACK: UNHANDLED ROOT CLASS <class 'myirl.test.ghdl.GHDLTestbench'>, create new context Using default for WIDTH: 8 WIDTH: use default 8 Declare obj 'bb_gc' in context '(EmulationModule 'tb_counter')' DEBUG Inline instance [_inline 'bb_gc/bb_gc'] Writing 'bb_gc' to file /tmp/bb_gc.vhdl INIT GCASSIGN Writing 'counter_unit_gray' to file /tmp/counter_unit_gray.vhdl Writing 'tb_counter' to file /tmp/tb_counter.vhdl Warning: Implicit truncation of ADD(counter, C:1) result Creating library file /tmp/module_defs.vhdl ==== 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/bb_gc.vhdl analyze /tmp/counter_unit_gray.vhdl analyze /tmp/tb_counter.vhdl elaborate tb_counter ==== COSIM stderr ==== /tmp/bb_gc.vhdl:27:12:warning: declaration of "work" hides library "tb_counterded7" [-Whide] ==== COSIM stdout ==== DONE simulation stopped @141ns
0
In particular when designing FIFOs, the instances of specific counters might be a design choice.
It makes then sense to configure the counter type (Binary, LFSR, Gray, ...) during initialization of a factory class as a self.Counter
member.
Eventually, start and end values may have to be known or set to a specific value.
For Gray counters, there is a conversion function graycode()
, however for LFSR sequences, there is no such thing due to the non-deterministic sequence. For efficient search algorithms, you may want to implement your own depending on the counter size.