back.rtlil: avoid sync process emission in RTLIL.

Avoiding emission of sync processes in RTLIL allows us to avoid a dependency on
matching the behavior expected by Yosys, which generally expects sync processes
in RTLIL to match those emitted by the output from its own Verilog parser.
This also simplifies the logic used in emitting RTLIL overall.

Combinatorial processes are still emitted however. Without these the RTLIL does
not have a high-level understanding of Switch statements, which significantly
diminishes the quality of emitted Verilog, as these are converted to `$mux`
cells in Yosys, which become `?` constructs when converted back to Verilog.

Fixes #603.
Fixes #672.
This commit is contained in:
Irides 2022-01-01 12:18:33 -06:00 committed by GitHub
parent aa749567e4
commit 5a4d45b599
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -328,16 +328,23 @@ class _ValueCompilerState:
else:
wire_name = signal.name
is_sync_driven = signal in self.driven and self.driven[signal]
attrs = dict(signal.attrs)
if signal._enum_class is not None:
attrs["enum_base_type"] = signal._enum_class.__name__
for value in signal._enum_class:
attrs["enum_value_{:0{}b}".format(value.value, signal.width)] = value.name
# For every signal in the sync domain, assign \sig's initial value (using the \init reg
# attribute) to the reset value.
if is_sync_driven:
attrs["init"] = ast.Const(signal.reset, signal.width)
wire_curr = self.rtlil.wire(width=signal.width, name=wire_name,
port_id=port_id, port_kind=port_kind,
attrs=attrs, src=_src(signal.src_loc))
if signal in self.driven and self.driven[signal]:
if is_sync_driven:
wire_next = self.rtlil.wire(width=signal.width, name=wire_curr + "$next",
src=_src(signal.src_loc))
else:
@ -944,40 +951,42 @@ def _convert_fragment(builder, fragment, name_map, hierarchy):
stmt_compiler._has_rhs = False
stmt_compiler._wrap_assign = False
stmt_compiler(group_stmts)
# For every driven signal in the sync domain, create a flop of appropriate type. Which type
# is appropriate depends on the domain: for domains with sync reset, it is a $dff, for
# domains with async reset it is an $adff. The latter is directly provided with the reset
# value as a parameter to the cell, which is directly assigned during reset.
for domain, signal in fragment.iter_sync():
cd = fragment.domains[domain]
# For every signal in the sync domain, assign \sig's initial value (which will
# end up as the \init reg attribute) to the reset value.
with process.sync("init") as sync:
for domain, signal in fragment.iter_sync():
if signal not in group_signals:
continue
wire_curr, wire_next = compiler_state.resolve(signal)
sync.update(wire_curr, rhs_compiler(ast.Const(signal.reset, signal.width)))
wire_clk = compiler_state.resolve_curr(cd.clk)
wire_rst = compiler_state.resolve_curr(cd.rst) if cd.rst is not None else None
wire_curr, wire_next = compiler_state.resolve(signal)
# For every signal in every sync domain, assign \sig to \sig$next. The sensitivity
# list, however, differs between domains: for domains with sync reset, it is
# `[pos|neg]edge clk`, for sync domains with async reset it is `[pos|neg]edge clk
# or posedge rst`.
for domain, signals in fragment.drivers.items():
if domain is None:
continue
signals = signals & group_signals
if not signals:
continue
cd = fragment.domains[domain]
triggers = []
triggers.append((cd.clk_edge + "edge", compiler_state.resolve_curr(cd.clk)))
if cd.async_reset:
triggers.append(("posedge", compiler_state.resolve_curr(cd.rst)))
for trigger in triggers:
with process.sync(*trigger) as sync:
for signal in signals:
wire_curr, wire_next = compiler_state.resolve(signal)
sync.update(wire_curr, wire_next)
if not cd.async_reset:
# For sync reset flops, the reset value comes from logic inserted by
# `hdl.xfrm.DomainLowerer`.
module.cell("$dff", ports={
"\\CLK": wire_clk,
"\\D": wire_next,
"\\Q": wire_curr
}, params={
"CLK_POLARITY": int(cd.clk_edge == "pos"),
"WIDTH": signal.width
})
else:
# For async reset flops, the reset value is provided directly to the cell.
module.cell("$adff", ports={
"\\ARST": wire_rst,
"\\CLK": wire_clk,
"\\D": wire_next,
"\\Q": wire_curr
}, params={
"ARST_POLARITY": ast.Const(1),
"ARST_VALUE": ast.Const(signal.reset, signal.width),
"CLK_POLARITY": int(cd.clk_edge == "pos"),
"WIDTH": signal.width
})
# Any signals that are used but neither driven nor connected to an input port always
# assume their reset values. We need to assign the reset value explicitly, since only
@ -1010,7 +1019,7 @@ def _convert_fragment(builder, fragment, name_map, hierarchy):
for signal in fragment.ports:
port_map[compiler_state.resolve_curr(signal)] = signal
# Finally, collect tha names we've given to each wire in RTLIL, and provide these to
# Finally, collect the names we've given to each wire in RTLIL, and provide these to
# the caller, to allow manipulating them in the toolchain.
for signal in compiler_state.wires:
wire_name = compiler_state.resolve_curr(signal)