Skip to main content

Behavioral Modeling

Behavioral modeling describes a circuit's behavior with procedural code — if, case, loops — inside initial or always blocks, rather than as a gate netlist or a single equation. It's the style that looks most like software, and correspondingly the one where the "this is hardware, not a sequential program" distinction from Introduction to Verilog matters most.

initial blocks​

An initial block executes its statements once, starting at simulation time 0, and never again:

initial begin
a = 0;
b = 0;
cin = 0;
end

initial blocks are overwhelmingly a simulation-only construct — they're the standard way to set up starting conditions and drive stimulus in a testbench (covered in a later page on simple testbenches). Synthesis tools generally ignore initial blocks entirely for ASIC targets; some FPGA flows support them narrowly for specifying a register's power-up value, but relying on that behavior for anything beyond a reset value is not portable. Treat initial as belonging to the simulation/testbench side of Verilog, not to synthesizable RTL.

A module can contain more than one initial block (and more than one always block). Each one is its own independent process — all initial blocks in a module start running concurrently at time 0, not one after another; the same is true of every always block running alongside them. This is the same "concurrent hardware, not sequential instructions" idea from Introduction to Verilog, applied specifically to procedural blocks: two initial blocks in the same module are two separate, simultaneously-running processes, not two steps of one program.

always blocks​

An always block repeats its statements every time something in its sensitivity list changes — this is the workhorse construct for describing real, synthesizable hardware behaviorally:

always @(a, b, cin) begin
sum = a ^ b ^ cin;
cout = (a & b) | (b & cin) | (a & cin);
end

Modern Verilog lets you write always @(*) (or always @*) instead of listing every signal by hand — the simulator automatically includes every signal read inside the block, which is both less typing and immune to the classic bug of forgetting to list a signal (which silently creates an unintended latch in synthesis while still simulating "correctly" until that missing signal actually changes). Prefer @(*) over a manual sensitivity list for combinational blocks; a manual list is only really needed when deliberately triggering on something narrower than "every input," which is rare.

This always block, the assign pair from Dataflow Modeling, and the six gate primitives from Gate-Level Modeling all describe the exact same circuit — the full adder's five gates:

a, b, cin feed the same XOR/AND/OR gate network, every time, regardless of which of the three styles wrote it, producing sum and cout

Three modeling styles, one circuit — the point isn't that one is "more correct," it's that behavioral modeling scales to logic that's awkward to express as a single equation or a hand-wired gate list, which is most of what real RTL actually looks like.

The begin/end block​

begin ... end groups multiple statements into one block, the way { } does in C-family languages — required whenever an always or initial block contains more than one statement, and omittable (though rarely worth omitting, for clarity) when there's exactly one.

Previewing sequential behavior​

Everything on this page so far describes combinational behavior — sum/cout react immediately to a/b/cin with no memory. Changing the sensitivity list to a clock edge is what turns an always block into sequential hardware — a register:

always @(posedge clk) begin
q <= d; // note <=, not = — explained next page
end

@(posedge clk) means "run this block's statements only at the rising edge of clk," which is precisely the edge-triggered behavior of the D flip-flop from Digital Design's Latches & Flip-Flops — this one line of Verilog is that flip-flop. The <= operator here (nonblocking assignment) is deliberate and not interchangeable with = the way it might look — that distinction, and why getting it backwards produces genuinely wrong hardware rather than just a style inconsistency, is the entire subject of the next page.

What's next​

The = used in this page's combinational examples and the <= used in the sequential preview above are Verilog's two assignment operators, and mixing them up in the wrong context is one of the most common — and most consequential — Verilog mistakes. The next page covers blocking vs. nonblocking assignment in depth, with a worked example of exactly what goes wrong when they're swapped.