This notebook is a collection of a few WTF moments you could have as Python enthusiast. Bottomline: This is a debatable code style.
To mimic VHDL or Verilog assignment style, you could be tempted to redefine your assignments to emulate:
a <= x - 1
This is simply done by inheriting from the Signal
class:
from myirl import *
from myirl import targets
class HDLSignal(Signal):
def __le__(self, other):
return base.GenAssign(self, other)
a, b = [ HDLSignal(intbv()[5:], name=n) for n in ['a', 'b'] ]
logic = kernel.sensitivity.LogicContext()
logic += [
a <= 5,
b <= a & b
]
from myirl.targets.dummy import DummyVHDLModule
d = DummyVHDLModule()
for stmt in logic:
stmt.emit(d)
a <= "00101"; b <= (a and b);
However, there's a catch: You will not be able to use the <=
operator for instancing of a comparator.
With this abuse of the new @
(matrix multiplication) operator, we could get some remedy (with side effects):
class MyOp(base.ConvertibleExpr):
def __init__(self, func):
self.func = func
def __rmatmul__(self, other):
return MyOp(lambda t, self=self, other=other : self.func(other, t))
def __matmul__(self, other):
return self.func(other)
ge = MyOp(lambda x, y: base.Ge(x, y)) # Generate an operator
le = MyOp(lambda x, y: base.Ge(y, x)) # swapped
a @le@ b
b >= a
expr = a @ge@ b
To verify it converts correctly, check:
expr, expr._convert(targets.VHDL, in_condition = False)
(a >= b, '(a >= b)')
However, we could also alter the <=
operator into >=
by this cheap hack:
class Chameleon(base.BoolOp):
_opid = "ge"
def __init__(self, lhs, rhs):
super().__init__(rhs, lhs) # Note swapped RHS/LHS, because we use '>=' instead '<=':
def emit(self, ctx):
tmp = base.GenAssign(self.right, self.left)
tmp.emit(ctx)
class HDLSignal(Signal):
def __le__(self, other):
return Chameleon(self, other)
To make sure it combines with boolean expressions, too:
a, b = [ HDLSignal(intbv()[5:], name=n) for n in ['a', 'b'] ]
logic = kernel.sensitivity.LogicContext()
logic.If = targets.vhdl.VHDLIf
# Here's the user's RTL:
logic += [
a <= 5,
b <= a & b,
logic.If((a > 3) & (a <= 5)).Then(
b <= 1
).Else(
b <= (a <= 2)
)
]
Note that a <= 2
works as expression, because it is internally swapped to a >=
.
d = DummyVHDLModule()
for stmt in logic:
stmt.emit(d)
a <= to_unsigned(5, 5); b <= (a and b); if ((a > "00011") and ("00101" >= a)) then b <= to_unsigned(1, 5); else b <= from_bool(("00010" >= a)); end if;
Note: Earlier kernel versions were unable to properly collect the operands respectively source and destination from this construct. This is now solved.
To use this style, we must make sure to use the import the style_hdl
module as follows (this overrides Signal
and process
by a derived functionality).
from myirl.library.style_hdl import *
from myirl.test.common_test import run_ghdl, clkgen
from myirl import targets, simulation
@block
def unit1():
a, b = [ Signal(intbv()[8:]) for _ in range(2) ]
q = Signal(intbv(19)[9:])
clk = ClkSignal(name = "master_clock")
rst = ResetSignal(ResetSignal.NEG_ASYNC)
en = Signal(bool())
thresh = Signal(bool(True))
oscillator = clkgen(clk, 2)
@genprocess(clk, EDGE=clk.POS, RESET=rst)
def worker1():
yield [
worker1.If(en == True).Then(
q <= a + b
)
]
@genprocess(clk, EDGE=clk.POS, RESET=rst)
def worker2():
yield [
worker2.If(q <= 4).Then(
thresh <= '1'
).Else(
thresh <= '0'
)
]
@simulation.generator
def seq1():
yield [
rst.set(False),
simulation.wait('1 ns'),
rst.set(True),
simulation.assert_(thresh == True, "#1 '<=' test failed"),
simulation.wait(clk.posedge),
simulation.assert_(q == 19, "failed to reset"),
a.set(2), b.set(1), en.set(True),
simulation.wait(clk.posedge),
simulation.wait(clk.posedge),
simulation.wait('1 ns'),
simulation.assert_(thresh == True, "#2 '<=' test failed"),
a.set(2), b.set(3), en.set(True),
simulation.wait(clk.posedge),
simulation.wait(clk.posedge),
simulation.assert_(q == 5, "failed to add"),
simulation.wait('1 ns'),
simulation.assert_(thresh == False, "#3 '<=' test failed"),
simulation.print_(a, b, q)
]
return instances()
def test_unit1():
inst = unit1()
files = inst.elab(targets.VHDL, elab_all = True)
run_ghdl(files, inst, debug = True, vcdfile="unit1.vcd")
test_unit1()
Writing 'clkgen' to file /tmp/myirl_unit1_wx7bk3m5/clkgen.vhdl Writing 'unit1' to file /tmp/myirl_unit1_wx7bk3m5/unit1.vhdl Creating library file module_defs.vhdl WORK DIR of instance [Instance unit1 I/F: [// ID: unit1_0 ]] /tmp/myirl_unit1_wx7bk3m5/ ==== COSIM stdout ==== 0x02 0x03 0x005 /tmp/unit1:info: simulation stopped by --stop-time @1us
For coding in IRL, the context.If().Then().Else()
chains are hard to read. Therefore, the following hack allows to write this nicer:
import myirl
Then = MyOp(lambda x, y: x.Then(*y))
Else = MyOp(lambda x, y: x.Else(*y))
assign = MyOp(lambda x, y: x.set(y))
@myirl.block
def unit(a : Signal, b : Signal.Output):
v = HDLSignal(bool())
@myirl.genprocess_ctx(a)
def worker(context):
yield [
context.If (a == 1) @Then@ [
b @assign@ 2,
v @assign@ True
] @Else@ [
b @assign@ 4
]
]
return instances()
a, b = [ HDLSignal(intbv()[5:]) for _ in range(2) ]
u = unit(a, b)
f = u.elab(targets.VHDL)
Writing 'unit' to file /tmp/myirl_unit_5d7izann/unit.vhdl
!grep worker {f[0]} -A 8
worker: process(a) begin case a is when "00001" => b <= "00010"; v <= '1'; when others => b <= "00100";
To make complex wirings for interface bulk signal types more readable, we define a different operator class in particular for connections. For example, a Port class may be in/out from the source, out/in ('reverse') from the destination. Within a module though, we may have to distribute the signals of a Port to several instances.
class MyOpX(base.ConvertibleExpr):
def __init__(self, func):
self.func = func
def __rlshift__(self, other):
return MyOpX(lambda t, self=self, other=other : self.func(other, t))
def __rshift__(self, other):
return self.func(other)
Create a SpecialOps
derivative and pass that as TYPE
parameter to your bulk signal class.
The @hdlmacro
generates the connections between self
and the other
port class.
from myirl.library.bulksignals import *
class SpecialOps(ContainerGen):
twoway = MyOpX(lambda x, y: x.assign(y)) # Generate an operator
@bulkwrapper(targets.vhdl, TYPE=SpecialOps)
class Port:
_inputs = ['input']
_outputs = ['output']
_other = []
def __init__(self):
self.input = Signal(bool())
self.output = Signal(bool())
@hdlmacro
def assign(self, other):
"Do the two way connection between peers"
yield [
self.output.set(other.input),
other.output.set(self.input)
]
@hdlmacro
def __le__(self, other):
"Wire signals members one way to peer"
yield [
self.input.set(other.input),
self.output.set(other.output)
]
p, q0, q1 = [ Port(name=n) for n in ['p', 'p0', 'p1'] ]
quiet = p.rename('p'), q0.rename('q0'), q1.rename('q1')
connections = [
p <<Port.twoway>> q0,
q1 <= p
]
for stmt in connections:
print("----------------")
stmt.emit(d)
---------------- p_out.output <= p0_in.input; p0_out.output <= p_in.input; ---------------- p1_in.input <= p_in.input; p1_out.output <= p_out.output;
Derived classes may redefine the __le__
method to implement custom assignments, such as flexbv
types performing latency and precision verification behind the curtains. The .set
Method may also be overriden by some BulkSignal types internally. So there are a few ways to shoot yourself into the foot.
A guideline to keep it clean:
.set
for 1:1 assignment in synchronous or asynchronous processes.wireup
for direct connections (outside process)<=
for one way assignments only<<custom.operator>>
style for two way custom connections between signal containersThe <=
style assignment, when used on vector data types and tuple notation, exhibits a pitfall:
from myirl.library.vectorsignal import VectorSignal
v, w = [ VectorSignal(2, intbv()[5:]) for _ in range(2) ]
v <= w[0] + w[1], v[0] + v[1]
(ADD(vec_v_0398_0, vec_v_0398_1) >= ARRAY(v_ce16), ADD(vec_v_ce16_0, vec_v_ce16_1))
This results in the last expression being a tuple
instead of type assignment, plus not do what's desired, effectively it is: v.set(w[0] + w[1]
and a new expression is created after the ,
.
Correct would be:
v <= (w[0] + w[1], v[0] + v[1])
However, this is not resolving. We therefore have to use:
v.set((w[0] + w[1], v[0] + v[1]))
<myirl.vector._VectorAssign at 0x7f3e255e8e50>