Thinking in Hardware
The commonest story in teaching Verilog goes like this. A programmer sees if, else, for, case, semicolons. They think they know this. They write it the way they write C. The simulation produces nonsense, the synthesised circuit does something else again, and they decide Verilog is broken.
Verilog is not broken. The picture is. The language describes hardware, and hardware does not execute statements one at a time; it exists, all of it, all the time, gates permanently wired and flip-flops waiting for an edge. Writing Verilog is drawing a schematic in text. The syntax looks like C and the meaning is nothing like it, and that gap causes more beginner bugs than everything else together. Here are the five places it bites.
Everything runs at once
In C, line 5 finishes before line 6 starts. In hardware every gate is evaluating its inputs right now, every flip-flop is ready for the next edge right now, and there is no instruction pointer walking through the file.
`timescale 1ns / 1ps
`default_nettype none
module parallel_example (
input wire a,
input wire b,
input wire c,
output wire y1,
output wire y2,
output wire y3
);
// Three gates, all active at the same time. The order is meaningless.
assign y1 = a & b;
assign y2 = b | c;
assign y3 = a ^ c;
endmodule
When a changes, y1 and y3 both change. The assignments do not take turns. Write them in any order and you get the same hardware. If you ever catch yourself thinking “first this assign runs, then that one”, stop; there is no first.
You are describing, not instructing
assign y = a & b; is not a command to compute something and store it. It says: there is an AND gate, its inputs are a and b, and its output is the wire called y. The gate is there for as long as the chip is. It is never “not running”.
The same applies inside procedural blocks:
always @(posedge clk) begin
q <= d; // a flip-flop with D connected to d, Q driving q, clocked by clk
end
The synthesiser reads that and builds a flip-flop. The “capture at the edge” is what a flip-flop does physically, not a scheduled event. Your source file is the blueprint for a machine; once built, the machine runs without the blueprint.
Time is the clock
In software, waiting is something the operating system does for you. In hardware, time is the clock, and every path from one flip-flop’s Q through logic to another’s D has to settle within one period. That is not a preference; it is a physical deadline, and it is what timing closure means.
`timescale 1ns / 1ps
`default_nettype none
module timing_example (
input wire clk, // 100 MHz, 10 ns period
input wire [7:0] data_in,
output reg [7:0] data_out
);
wire [7:0] processed;
// combinational logic between the edges; it must settle within 10 ns
assign processed = data_in + 8'd1;
// captured at every rising edge
always @(posedge clk) begin
data_out <= processed;
end
endmodule
The @(posedge clk) is not a callback or a timer. It says the flip-flop samples D on that edge, and the edge comes from the oscillator on the board. Nothing in software corresponds to this.
Signals are not variables
int x = 5; in C is a memory location you can read and write whenever you like. A Verilog wire is a conductor with exactly one driver, carrying whatever that driver outputs at this instant. You do not assign to it in a block; you declare its driver with assign or connect it to a module output. Give it two drivers and you have a short circuit, X in simulation and possibly smoke in silicon.
reg is worse, because the name lies. A reg is a signal that is driven from a procedural block, and that is all it means. Whether it becomes a flip-flop or a gate depends on the block:
// flip-flop: assigned in a clocked block
reg q;
always @(posedge clk) begin
q <= d;
end
// combinational: assigned in an always @(*) block, no storage at all
reg y;
always @(*) begin
y = a & b;
end
Same keyword, two different pieces of hardware. This is why SystemVerilog added always_ff and always_comb. Never look at reg and assume a register; look at the block it is assigned in.
Synthesis builds what you meant, not what you typed
The netlist does not look like your source. The synthesiser extracts the Boolean function you described and builds the smallest or fastest circuit that computes it. Write (a & b) | (a & c) and you may get a & (b | c), or something else again depending on the library. It is correct, because the truth table is the same. Your source is a specification, and when you debug a netlist you are looking at what the tool chose, not at your text.
The swap
The five points come together in one example that every beginner asks about. Does this swap a and b?
`timescale 1ns / 1ps
`default_nettype none
module register_swap (
input wire clk,
output reg [7:0] a,
output reg [7:0] b
);
always @(posedge clk) begin
a <= b;
b <= a;
end
endmodule
The programmer’s reading: the first line runs, a becomes b; the second line runs, b becomes the new a, which is b; both end up equal. The hardware reading: two flip-flops, the D of each connected to the Q of the other (Figure 1). At the edge both sample their D inputs, which are the values from before the edge, and both update. They swap.
The non-blocking assignment <= models exactly this: every right-hand side is evaluated with the current values, and every left-hand side is updated afterwards, together. It is not a quirk of the simulator. It is how flip-flops behave.
The habit
Before each line, ask what physical thing it creates. An assign is a gate or a few. An always @(posedge clk) is flip-flops. A module instance is a sub-circuit. If you cannot answer, do not type it yet. And before sending anything for review, check that no code depends on the order of assign statements, that clocked blocks use non-blocking assignments, that every if in a combinational block has an else or a default, and that every signal has exactly one driver.
Come back and reread this article after your first fifty lines of RTL. It reads differently the second time.
Practice Exercise
What hardware does this describe, and what is wrong with it?
always @(posedge clk) begin
temp = a;
a = b;
b = temp;
end
Solution
Blocking assignments update immediately, in order, within the block. Trace it: temp takes a, a takes b, b takes the saved a. Functionally it is a swap, and a synthesiser will build the same two cross-connected flip-flops as the non-blocking version; temp is never stored across an edge, so it disappears.
What is wrong is that it works by accident of ordering. Reorder the lines and it breaks. Read temp from another block and you get simulation results that depend on which block the simulator happened to run first, which the netlist will not reproduce. The rule is simple: in a clocked block, use non-blocking assignments, and then the order does not matter:
always @(posedge clk) begin
a <= b;
b <= a;
end
No temporary is needed, because both right-hand sides are read before either left-hand side changes.
That is the picture. The next section is the grammar: modules, ports, data types and literals, the pieces you need before writing any real RTL.