josuah.net | panoramix-labs.fr
Verilog is a hardware language with a syntax similar to C, but very different semantics: signals, circuits, and registers for storing the state.
┌─────┐ some circuit >──┤D Q├──> some circuit │ │ updated one clock edge later ┌─> │ │ └─────┘ clock
They use the <=
assignment operator to build-up sequential logic. Like software variables, they can hold values across multiple clock cycles. An assignment to a register (on D
) gets read (on Q
) on the next clock cycle.
reg r; always_ff @(posedge clk) begin r <= 1; └── This is the clock connected to all registers end └── This is the input signal D fed into the register wire w = r; └── This is the output signal Q read from the register
some circuit >───────────────> some circuit the_wire_name
Most verilog expressions gets converted to a circuit of gates implementing it. The =
assignment used in various contexts permit to attach an end of a wire to a name, a label. The always_comb
block can only have =
, no <=
, and implement combinational logic.
input i; wire [7:0] w; always_comb begin w = i ^ 8'b10101010; end
But what if complex expressions are assigned to a register instead of a wire? The signal coming into the register may be represented as an expression itself, and expressions are combinational logic. The flip-flop-only always_ff
contains, in fact, a mix of combinational and sequential logic.
The restriction in always_ff
is that adding extra combinational logic is forbidden (no =
operator) but combinational logic feeding registers can still occur. It helps with preventing <=
being typoed into =
.
input i; reg [7:0] r; always_ff @(posedge clk) begin r <= i ^ 8'b10101010; end
This is not a problem per-se, just a remark.
The above permits us to explain the approach LowRISC is taking. The team described it in their style guide for SystemVerilog:
It is done by coupling a always_ff
that applies the changes to registers, with always_comb
that build-up the next values. Each register is having:
_d
wire that is fed into the register, driving the next value of the register;_q
register, representing the output value of the register, which is delayed by one clock.On each clock, this happens alone in a block: something_q <= something_d
; and the rest is purely combinational.
In other words, the sequential operation have been isolated, by giving the input signal a name (something_d
here).
wire something_d; reg [7:0] something_q; always_ff @(posedge clk) begin // apply the changes to the registers something_q <= something_d; end always_comb begin // by default, the value stays the same as the register previous value // this was happening under the hood in the previous examples, it is now // explicit something_d = someting_q; // for giving something_q another value, something_d can be changed with // in the combinational logic if (i > 3) begin something_d = something_d + 1; end end
In addition to LowRISC guideline, I also use this convention: When declaring signals with logic
, the _d
wire comes first, followed by _q
and _q2
etc. That way, there is one line per signal, including its past state. This help with reading code faster:
logic ack_d, ack_q, ack_q2; logic state_d, state_q; logic counter_q;
Inconvenients:
_d
ones):Advantages:
_q
into _d
to save one clock cycle;Q
/D
signal naming of registers.It is also frequent to have a reset signal that puts all registers to a default value. The LowRISC approach makes it convenient to integrate it within the always_ff
block, introducing a little bit of combinational logic, without much effect.
always_ff @(posedge clk) begin if (rst) begin something0_q <= 0; something1_q <= 0; something2_q <= 0; something3_q <= 0; end else begin something0_q <= something0_d; something1_q <= something1_d; something2_q <= something2_d; something3_q <= something3_d; end end
In a real world example, I would have been using logic
instead of wire
and reg
, the _q
and _d
helping with making the distinction. I would also likely use rst_ni
instead of rst
and clk_i
instead of clk
.
Also sometimes the whole design is so trivial that a separate always_comb
would be an overkill.
https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md