A draft is only as good as its spec. Write the interface, the behavior, and the corner cases before asking for code.
Unwritten corners get filled with guesses. Written corners get built and tested.
Spec section | Example content |
|---|---|
Interface | Ports, widths, clock and reset, active levels. |
Behavior | One sentence per output: what it computes and when. |
Corners | Reset values, terminal counts, out-of-range selects, overflow. |
Constraints | No latches, one clock, named parameters instead of literals. |
Name the rules the draft must obey. Defaults before case branches, full sensitivity, and explicit widths are typical rules.
Record tool and language assumptions separately from requirements. Simulation checks behavior for exercised cases; elaboration, lint, synthesis, and implementation review address other questions. None authorizes silently changing the approved behavior.
Rule given | What it prevents |
|---|---|
Assign every output a default first | Latches from uncovered paths. |
Size every expression explicitly | Truncation and dropped carries. |
Review operand widths and signed types before using < or > | Unintended unsigned interpretation of negative bit patterns. |
List every assumption in the reply | Silent guesses passing as facts. |
Give the assistant a synthetic requirement: two unsigned four-bit operands produce an exact five-bit sum, exposed as carry and sum. Ask for ports and expected boundary values before RTL. A modulo-16 result is a different design and needs a different requirement.
Operands | Expected {carry,sum} | Why check it |
|---|---|---|
0 + 0 | 0_0000 | Zero boundary |
7 + 8 | 0_1111 | Largest sum without carry |
15 + 1 | 1_0000 | First carry boundary |
15 + 15 | 1_1110 | Largest result |
The adder below is an illustrative example written for this course, never real assistant output. It drops the carry, and the exhaustive check counts exactly where.
// Illustrative example written for this course, never real model output.
module gen_add_buggy (
input logic [3:0] a, b,
output logic [3:0] sum
);
always_comb sum = a + b; // BUG: the carry out is dropped
endmodule
module gen_add_fixed (
input logic [3:0] a, b,
output logic [3:0] sum,
output logic carry
);
always_comb {carry, sum} = a + b;
endmodule
module tb_add_width;
logic [3:0] a, b, sum_b, sum_f;
logic carry_f;
int errors = 0;
int dropped = 0;
gen_add_buggy bb (.a(a), .b(b), .sum(sum_b));
gen_add_fixed ff (.a(a), .b(b), .sum(sum_f), .carry(carry_f));
initial begin
for (int x = 0; x < 16; x++) begin
for (int y = 0; y < 16; y++) begin
a = x[3:0]; b = y[3:0]; #1;
if ({carry_f, sum_f} !== (x + y)) errors++;
if (sum_b !== ((x + y) & 15)) errors++;
if (carry_f) dropped++;
end
end
if (dropped != 120) errors++;
if (errors == 0) $display("PASS: 256 adds exact with carry, 120 dropped carries caught");
$finish;
end
endmoduleWhere the carry goes. Without an overflow policy the draft silently drops it, and 120 of 256 pairs lose information.
One expression keeps the full 5-bit result. Separate assignments invite one of them being written with the wrong width.