{josuah.net} | {panoramix-labs.fr}
(DIR) • {josuah.net}
(DIR) • {panoramix-labs.fr}
{git} | {cv} | {links} | {quotes} | {ascii} | {tgtimes} | {gopher} | {mail}
(DIR) • {git}
(BIN) • {cv}
(DIR) • {links}
(DIR) • {quotes}
(DIR) • {ascii}
(HTM) • {tgtimes}
(DIR) • {gopher}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Sequential signals may hide combinational ones
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Verilog is a hardware language with a syntax similar to C, but very different
semantics: signals, circuits, and registers for storing the state.
Registers and sequential logic
──────────────────────────────
┊ ┌─────┐
┊ 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
Wires, signals, and combinational logic
───────────────────────────────────────
┊ 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
How combinational logic appear
──────────────────────────────
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 LowRISC approach
────────────────────
(HTM) The above permits us to explain the approach {LowRISC} is taking. The team
(HTM) 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:
• a `_d` wire that is fed into the register, driving the next value of the
register;
• a `_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
Extra convention
────────────────
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;
In practice?
────────────
Inconvenients:
• More verbose outside the combinational block;
• Extra signals to declare (the `_d` ones):
Advantages:
• Everything is explicit around the registers;
• In expressions using these signals, change `_q` into `_d` to save one clock
cycle;
• Consistent naming using the `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.
Links
─────
(HTM) {https://lowrisc.org/}
(HTM) {https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md}