HCI and related IPs use their own coding style that is similar, but not identical, to that of many other PULP IPs.
Most of the changes are due to preference by the benevelent dictator and main author of these IPs, i.e., yours truly, FrancescoConti88
: I add a comment on the reason of the change near to it.
Many of these are subjective, you are free to disagree - but hey, this is my town and I make the rules.
You can follow the lowRISC guidelines https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md with several changes or extra recommendations, some of which are pretty major:
begin
is on its own line inalways
,always_comb
, andalways_ff
blocks
always_ff @(posedge clk)
begin
// content
end
I find this to be better readable than the lowRISC standard.
- Always start a new line before
else
:
if (condition) begin
foo = bar;
end
else begin
foo = bum;
end
It is much easier to see an else
than one of the many end
s in code, so I prefer this to the lowRISC style.
-
Avoid non-unavoidable preprocessor directives and macros. Basically you will see only
ifdef SYNTHESIS
andifdef VERILATOR
. These can almost always be replaced by parameters and other constructs. -
Parameters and constants are
ALL_CAPS
. Just a style choice, but better to be consistent in new modules in the HCI family. -
Avoid parametric types and do not overuse of
typedef
s. I hate parametric types. They may make the code easier to write, but they make it very difficult to read and interpret, and we spend only 10% of our coding time writing code, the rest is reading! The same can be said of othertypedef
s (for example in a package) to a (much) lower degree. Their main advantage is compile-time type checking, which is nice, but the cost is code readability... For simple logic vectors I prefer to use plainlogic
s. -
All flip-flops must have an asynchronous negative edge-triggered reset (typically
rst_ni
), a "soft" synchronous clear (clear_i
). The system asynchronous reset and the soft clear serve wholly different purposes. Both should be used in all flip-flops in HCI IPs. -
All flip-flops should have an enable signal indicated inside the
always_ff
block:
always_ff @(posedge clk_i or negedge rst_ni)
begin
if(~rst_ni)
q <= '0;
else if(clear_i)
q <= '0;
else if(enable_signal_or_condition)
q <= d;
end
Although it is recommended to keep combinational logic outside of always_ff
's, enable conditions should be there explicitly, otherwise some tools do not correctly infer clock gating cells!
-
Combinational logic can be expressed with continuous assignments (
assign a = b | c
) oralways_comb
blocks. In the latter case, always include a default value for all outputs. Self-explanatory common sense rule! -
No
x
values permitted. Don't care conditions should be avoided to avoid RTL/gate-level mismatches. -
Avoid too complex
always_comb
blocks. Complex combinational logic should be split as much as possible in elementary, explainable units. It should be immediately clear to what a certain line corresponds in RTL terms (a multiplexer, a battery of AND gates, etc.). Often it is easier to achieve this goal by usingassign
blocks, but in some cases this is impossible, or impacts readability. -
interface
s can (and must) be used. The kind of parametric behavior we wrap in HCI IPs can only be coded in SystemVerilog usinginterface
s or couplingstruct
s with auxiliary non-SystemVerilog code generators. The latter solution works very well for fixed-size buses and can be adapted to the case where there is at least a maximum size, but it is IMHO a doorway to over-engineering as it requires external tools, it generates multiple copies of very similar code into an unmaintainable "SystemVerilog blob". It is a reasonable solution when nothing else exists, butinterface
s exist and they are actually pretty awesome. In the following a few guidelines to useinterface
s productively and without losing control. -
Use
interface
s to implement buses, without internal logic; limitmodport
s to the two directions plus possibly an extra "monitoring" one. Internal logic makes interfaces difficult to use and understand, and it is unnecessary - we havemodule
s. -
Do not indicate
modport
s when connecting an interface hierarchically, e.g.:
hci_core_fifo i_fifo (
.clk_i ( clk_i ),
.rst_ni ( rst_ni ),
.clear_i ( clear_i ),
.flags_o ( flags ),
.tcdm_target ( push ), // interface target modport
.tcdm_initiator ( pop ) // interface initiator modport
);
Some tools do not support indicating modport
s explicitly, other tools simply ignore them. It is anyways an unnecessary complication to indicate them!
- Arrays of
interfaces
must be indicated in Verilog-style (a[0:N-1]
), not C-style (a[N]
), with indeces going upwards:
hci_core_intf hci [0:NB_CHAN-1] (
.clk ( clk_i )
);
It is particularly important that all interfaces in the system follow the same convention; the Verilog one is the best supported common ground.
- Use assertions inside
interface
s, waive them only if you are reasonably sure that they must be waived! One of the useful properties ofinterface
s compared to plainstruct
s is that they can carry assertions without cluttering the code.