if and else
Section 5.2 was about what an always block wakes on and how to keep latches out. This section is about the statements inside, and what each one builds. In software an if is a branch: the processor goes one way or the other. In hardware everything on both sides is built and running, and the if only decides which result reaches the output. The synthesiser turns an if-else chain into one particular structure, a chain of 2-to-1 muxes, and that structure has a property you need to know about: priority.
The chain
always @(*) begin
if (cond_a)
y = a;
else if (cond_b)
y = b;
else
y = c;
end
This is two muxes in series (Figure 1). The first chooses a if cond_a and otherwise the output of the second, which chooses b if cond_b and otherwise c. If both conditions are true, a wins, because the first mux never looks at the second. Earlier conditions beat later ones.
When priority is right
Some specifications want exactly that. An interrupt controller has to take a non-maskable interrupt ahead of everything else and, among the rest, the highest pending one:
`timescale 1ns / 1ps
`default_nettype none
module interrupt_priority_enc (
input wire nmi,
input wire [3:0] irq, // irq[3] highest
output reg [2:0] int_vec, // 0 none, 1 to 4 irq, 5 nmi
output reg int_valid
);
always @(*) begin
int_vec = 3'd0;
int_valid = 1'b0;
if (nmi) begin
int_vec = 3'd5;
int_valid = 1'b1;
end else if (irq[3]) begin
int_vec = 3'd4;
int_valid = 1'b1;
end else if (irq[2]) begin
int_vec = 3'd3;
int_valid = 1'b1;
end else if (irq[1]) begin
int_vec = 3'd2;
int_valid = 1'b1;
end else if (irq[0]) begin
int_vec = 3'd1;
int_valid = 1'b1;
end
end
endmodule
With nmi and any irq up together, the NMI wins, as the specification says. The chain is the priority logic; no other gates are needed. Bus arbiters with a fixed order, if (cpu_req) ... else if (dma_req) ..., and instruction decoders where an exception overrides the normal decode are the same shape. The interrupt controller in a small ARM core is a large priority encoder over hundreds of sources.
else, and the default
An if without an else in a combinational block is the latch of the last section. The else closes the gap, and the default at the top closes it before the conditions are even looked at:
always @(*) begin
out_a = 1'b0;
out_b = 1'b0;
out_c = 1'b0;
if (sel == 2'b00) out_a = 1'b1;
else if (sel == 2'b01) out_b = 1'b1;
else if (sel == 2'b10) out_c = 1'b1;
// sel = 3: all three stay 0
end
Depth
Every else if is another mux on the path to the output. Four levels is four mux delays; that is fine. Thirty-two levels, as in a wide interrupt encoder written naively, is a timing problem, and that is where the parallel structures of the rest of this section come in. Up to eight or ten conditions an if chain is reasonable; beyond that, ask whether the specification needs priority at all.
In a clocked block
Everything above is for always @(*). In a clocked block the same shape means something else:
always @(posedge clk) begin
if (load)
q <= d;
// no else: q holds until the next edge
end
Here holding is what a flip-flop does anyway, and the if becomes the enable: a mux in front of D that passes d when load is high and feeds q back to itself otherwise (Figure 2). Not a latch. A register with a synchronous enable, which is one of the most common things in any design.
So: if without else in @(*) is a latch and almost always a mistake; in @(posedge clk) it is an enable and entirely normal.
A priority encoder, with tests
`timescale 1ns / 1ps
`default_nettype none
module priority_enc4 (
input wire [3:0] req, // req[3] highest
output reg [1:0] enc,
output reg valid
);
always @(*) begin
enc = 2'b00;
valid = 1'b0;
if (req[3]) begin enc = 2'b11; valid = 1'b1; end
else if (req[2]) begin enc = 2'b10; valid = 1'b1; end
else if (req[1]) begin enc = 2'b01; valid = 1'b1; end
else if (req[0]) begin enc = 2'b00; valid = 1'b1; end
end
endmodule
module tb_priority_enc4;
reg [3:0] req;
wire [1:0] enc;
wire valid;
priority_enc4 dut (.req(req), .enc(enc), .valid(valid));
task check;
input [3:0] r;
input [1:0] expected_enc;
input expected_valid;
begin
req = r;
#5;
if (valid !== expected_valid || (expected_valid && enc !== expected_enc))
$display("FAIL: req=%04b enc=%02b (exp %02b) valid=%b (exp %b)", r, enc, expected_enc, valid, expected_valid);
else
$display("PASS: req=%04b enc=%02b valid=%b", r, enc, valid);
end
endtask
initial begin
$dumpfile("tb.vcd");
$dumpvars(0, tb_priority_enc4);
check(4'b0000, 2'b00, 1'b0); // enc is don't-care here
check(4'b0001, 2'b00, 1'b1);
check(4'b0010, 2'b01, 1'b1);
check(4'b0100, 2'b10, 1'b1);
check(4'b1000, 2'b11, 1'b1);
check(4'b1111, 2'b11, 1'b1); // priority
check(4'b1010, 2'b11, 1'b1);
check(4'b0110, 2'b10, 1'b1);
check(4'b0011, 2'b01, 1'b1);
$finish;
end
endmodule
The task keeps the tests readable and self-checking. The single-request vectors check the encoding; the combined ones check that higher bits win.
Practice Exercise
A processor has five exception sources, highest first: machine check, bus fault, usage fault, supervisor call, pendable service call. Write a combinational block producing a 3-bit vector, 5 down to 1 with 0 for none, and a valid flag, and check that all five at once gives 5.
Solution
`timescale 1ns / 1ps
`default_nettype none
module exception_encoder (
input wire machine_check,
input wire bus_fault,
input wire usage_fault,
input wire svc,
input wire pend_sv,
output reg [2:0] exc_vec,
output reg exc_valid
);
always @(*) begin
exc_vec = 3'd0;
exc_valid = 1'b0;
if (machine_check) begin exc_vec = 3'd5; exc_valid = 1'b1; end
else if (bus_fault) begin exc_vec = 3'd4; exc_valid = 1'b1; end
else if (usage_fault) begin exc_vec = 3'd3; exc_valid = 1'b1; end
else if (svc) begin exc_vec = 3'd2; exc_valid = 1'b1; end
else if (pend_sv) begin exc_vec = 3'd1; exc_valid = 1'b1; end
end
endmodule
module tb_exception_encoder;
reg machine_check, bus_fault, usage_fault, svc, pend_sv;
wire [2:0] exc_vec;
wire exc_valid;
exception_encoder dut (
.machine_check(machine_check), .bus_fault(bus_fault), .usage_fault(usage_fault),
.svc(svc), .pend_sv(pend_sv), .exc_vec(exc_vec), .exc_valid(exc_valid)
);
initial begin
$dumpfile("tb.vcd");
$dumpvars(0, tb_exception_encoder);
{machine_check, bus_fault, usage_fault, svc, pend_sv} = 5'b11111; #5;
$display("all: vec=%0d valid=%b (expect 5 1)", exc_vec, exc_valid);
{machine_check, bus_fault, usage_fault, svc, pend_sv} = 5'b00001; #5;
$display("pend_sv only: vec=%0d valid=%b (expect 1 1)", exc_vec, exc_valid);
{machine_check, bus_fault, usage_fault, svc, pend_sv} = 5'b00000; #5;
$display("none: vec=%0d valid=%b (expect 0 0)", exc_vec, exc_valid);
$finish;
end
endmodule
Machine check is the first condition, so the first mux picks its vector before anything else is consulted. The priority is in the structure.
The next article is case, which says something different to the synthesiser: one value, one arm, and no order among them.