This is specific to RTLIL conversion and does not necessarily apply to HDL targets.
There are a number of programming caveats involved with conditional statements, arising from the fact that we need to create hardware elements from a sequential programming style construct. Statements that perform without warning in Python might therefore throw warnings or even errors when inferring to a RTLIL description.
Plus, implicit behaviour is in place: When a signal is not explicitely assigned to a new value during a process flow graph cycle, it is assumed that it's unaltered, or implicitely: assigned to its previous value. This is tolerated style for synchronous process descriptions.
However, asynchronous processes would infer a Multiplexer logic with its output fed back to an input, i.e. a Latch. Inferring a latch in a clock synchronous design is normally a bad choice alias design flaw.
from myirl.emulation.myhdl import *
First, we create a generator auxiliary for conversion of a unit with a 'standard' interface:
from yosys import display
from myirl.targets import pyosys
def convert(unit, name = "test", optimize = False, ignorefail = False):
tgt = pyosys.RTLIL(name)
if ignorefail:
tgt.warn_combloop = 'warn'
clk = ClkSignal()
reset = ResetSignal(0, 1)
a, q = [ Signal(intbv()[8:]) for _ in range(2) ]
inst = unit(clk, reset, q, a)
d = inst.elab(tgt)
if optimize:
d[0].run("opt; opt_clean")
d[0].display_rtl(unit, fmt='dot')
return display.display_dot(d[0].name)
The code below increments w
every clock cycle only when bit 0 of a
is set. Otherwise, it leaves it as it is. This can be seen as implicit assignment to its current value: w.next = w
.
This generates sane hardware, because Flipflops are created. Without a synchronous clock event, a latch would be created.
@block
def implicit_defaults_unit(clk : ClkSignal, r : ResetSignal,
q : Signal.Output, a : Signal):
w = Signal(intbv(0xaa)[8:])
@always_seq(clk.posedge, r)
def proceed():
if a[0]:
w.next = w + 1
wires = [
q.wireup(w)
]
return instances()
convert(implicit_defaults_unit, optimize = False)
DEBUG CREATE wrapper module for implicit_defaults_unit (EmulationModule 'top_implicit_defaults_unit')
Creating process 'implicit_defaults_unit/proceed' with sensitivity (clk'rising, <r>)
Adding module with name `implicit_defaults_unit`
-- Running command `show -format dot -prefix test implicit_defaults_unit' --
1. Generating Graphviz representation of design.
Writing dot description to `test.dot'.
Dumping module implicit_defaults_unit to page 1.
This may turn up when an @always
decorator argument lacks the .posedge
attribute, i.e. is sensitive to anything else than a clock edge. Hence, an asynchronous process is created and the implicit default creates a feedback from the Muxer output to its input.
If w
is assigned to a combination of its elements, a combinatorial loop would be created that could cause oscillation or excessive power consumption. If the case below would to a constant, this would not cause oscillation but creation of a latch.
@block
def latch_unit(clk : ClkSignal, r : ResetSignal,
q : Signal.Output, a : Signal):
z = Signal(intbv(0xaa)[8:])
@always(clk)
def proceed():
if a[0]:
z.next = z + 1
wires = [
q.wireup(z)
]
return instances()
To turn the combinatorial loop failure into a warning, we pass ignorefail = True
:
convert(latch_unit, optimize = False, ignorefail = True)
DEBUG CREATE wrapper module for latch_unit (EmulationModule 'top_latch_unit') Creating process 'latch_unit/proceed' with sensitivity (<clk>,) Adding module with name `latch_unit` LOOP_ERROR: z_343545826.py::proceed:10: Combinatorial loop for 'z' LATCH_WARNING: Incomplete default assignments, latch created for 'z' -- Running command `show -format dot -prefix test latch_unit' -- 2. Generating Graphviz representation of design. Writing dot description to `test.dot'. Dumping module latch_unit to page 1.
The example below would create redundant logic, as the else
clause is commented out. Creation of the Multiplexer for Line 15
is skipped, however you can see in the RTL display below that a comparator with a dangling output signal is left. During synthesis, this will be optimized away.
@block
def pass_clause_unit(clk : ClkSignal, r : ResetSignal,
q : Signal.Output, a : Signal):
w = Signal(intbv(0xaa)[8:])
b = Signal(bool())
@always_seq(clk.posedge, r)
def proceed():
if a[0]:
w.next = 12
elif a[1]:
b.next = True
if a[5:2] == 4:
w.next = 4
elif a[5:2] == 2:
pass
# else: # Add an else clause
# w.next = 1
else:
b.next = False
w.next = 99
wires = [
q.wireup(w)
]
return instances()
convert(pass_clause_unit)
DEBUG CREATE wrapper module for pass_clause_unit (EmulationModule 'top_pass_clause_unit')
Creating process 'pass_clause_unit/proceed' with sensitivity (clk'rising, <r>)
Adding module with name `pass_clause_unit`
DEBUG: /tmp/ipykernel_726/3058877116.py::proceed:16 Ineffective Muxer for `w`, use 'else' clause
-- Running command `show -format dot -prefix test pass_clause_unit' --
3. Generating Graphviz representation of design.
Writing dot description to `test.dot'.
Dumping module pass_clause_unit to page 1.
With the else
clause effective, the statements make sense and complete into multiplexers, although it's not considered the best style.