[prim] Break always_comb block to avoid apparent loop
When Verilator schedules its simulation, it tracks dependencies in a
big graph containing processes (including always_comb blocks) and
variables. Here, that graph has a loop between e.g. idx_tree and
sel_tree. The result is an UNOPTFLAT warning and poor
scheduling (which will impact simulator performance).
Often these loops can be broken by judicious use of Verilator's
split_var directive. This tells Verilator to split up an array or
struct into multiple nodes in the graph. Here, that's not enough. The
problem is that the graph looks something like this:
idx_tree[47:44] --- (is_written_in) -->
always_comb block on line 143 --- (sensitive_to) --->
sel_tree[11] --- (is_written_in) -->
always_comb block on line 143 --- (sensitive_to) --->
idx_tree[47:44]
To fix things, we need to split the always_comb block. The Verilator
documentation describes an isolate_assignments directive that looks
like it should handle this, but I've not had any luck with it.
It turns out that splitting out just the definition of sel into its
own always_comb block is enough to silence the warning. Presumably,
this means that Verilator is doing other clever breaking up of blocks,
otherwise it wouldn't fix the chain above, so this seems a bit
fragile.
It would be nice to sort this out by turning things into continuous
assignments, but a Vivado synthesis bug means we can't do that at the
moment. Add a TODO that explains what's going on.
Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/hw/ip/prim/rtl/prim_arbiter_tree.sv b/hw/ip/prim/rtl/prim_arbiter_tree.sv
index 992bc2f..16281fb 100644
--- a/hw/ip/prim/rtl/prim_arbiter_tree.sv
+++ b/hw/ip/prim/rtl/prim_arbiter_tree.sv
@@ -140,10 +140,19 @@
end else begin : gen_nodes
// local helper variable
logic sel;
- always_comb begin : p_node
+
+ // TODO: The always_comb code is split into two blocks to allow Verilator to schedule them
+ // separately (avoiding a spurious UNOPTFLAT warning). The whole lot would probably
+ // be clearer as a set of continuous assignments, rather than using always_comb
+ // blocks. Unfortunately, we can't currently do that because of a Vivado bug,
+ // reported in January 2020. This is tracked with OpenTitan issue #1408. There's
+ // currently no information about a Vivado version with this fixed.
+ always_comb begin : p_sel
// forward path (requests and data)
// each node looks at its two children, and selects the one with higher priority
sel = ~req_tree[C0] | ~prio_tree[C0] & prio_tree[C1];
+ end
+ always_comb begin : p_node
// propagate requests
req_tree[Pa] = req_tree[C0] | req_tree[C1];
prio_tree[Pa] = prio_tree[C1] | prio_tree[C0];