Verilog · Chapter 3 · Foundations
RTL Designing
This is the chapter where you start writing hardware. It works in one direction throughout: begin with a circuit you already understand, then write the Verilog that describes it, then read that Verilog line by line until nothing in it is mysterious, then check the behaviour. You will build four things in order — a multiplexer, a flip-flop, a counter, and a small accumulator — and each one adds exactly one new idea to the last. By the end you should be able to look at a piece of RTL and say what hardware it describes, and look at a circuit and write the RTL for it.
Foundation34 min readRTLCombinationalSequentialFlip-FlopVerilog
Chapter 3 · Page 1.3 · Foundations
1. Where This Chapter Sits
In Chapter 2 RTL was one box in a long flow — the point where an engineer writes the description that everything downstream is built from. This chapter opens that box.
Everything here works the same way round, deliberately:
circuit you already know → the Verilog for it → every line explained → what the behaviour looks like
That order matters. Verilog is much easier to learn as a way of writing down circuits you can already picture than as a language with rules to memorise.
2. The One Idea Behind All RTL
RTL stands for register transfer level. The name sounds formal; the idea behind it is small:
A digital design is registers holding values, with logic between them deciding what those registers should hold next.
That is it. A counter is a register holding a number, with an adder deciding the next number. A state machine is a register holding the current state, with logic deciding the next state. A CPU pipeline is the same shape repeated.
For now, almost every RTL block you meet can be understood with two ideas:
| What it is | How you recognise it | |
|---|---|---|
| Combinational | Logic with no memory. The output depends only on the inputs right now. | No clock anywhere in the description |
| Sequential | Logic with memory. It holds a value until a clock edge tells it to take a new one. | A clock edge decides when the value changes |
Every example in this chapter is one of these, or both joined together. Real designs contain things this two-way split does not fully describe — memories and analog interfaces, for instance — but combinational-versus-state carries you a very long way. Keep asking "is this combinational, or does it hold state?" — it is the question that makes RTL readable.
3. Verilog Is the Language. RTL Is How We Use It.
A fair question at this point: if I am writing Verilog, what makes some of it "RTL"?
The short answer:
Verilog is the language. RTL is a way of using that language to describe digital hardware at the register-transfer level.
They are not two different languages. RTL is not a separate thing you install or switch to. It is a style — a level of detail you choose to describe hardware at, written in ordinary Verilog.
Which gives the rule worth remembering:
All the RTL in this course is Verilog. Not all Verilog is RTL.
3.1 What Verilog gets used for
Verilog can be used in several ways. The distinction you need first is between Verilog that describes the hardware you are building and Verilog that drives or observes a simulation.
Two tiny examples make it concrete. You have met both before — the first in Chapter 1, the second in outline.
Verilog describing hardware:
module and_gate (
input wire a,
input wire b,
output wire y
);
assign y = a & b;
endmodulemodule— defines a block of hardware with a boundary.input wire a— a signal entering that block.output wire y— a signal leaving it.assign y = a & b;— describes a continuous combinational relationship:yis the AND ofaandb, always.
Is this Verilog? Yes. Is it RTL? Yes — it describes hardware in terms of signals and logic, and in a synthesisable design context a tool can implement that AND behaviour in the target technology.
Verilog driving a simulation:
initial begin
a = 1'b0;
b = 1'b0;
#10 a = 1'b1;
#10 b = 1'b1;
#10 $finish;
endinitial begin ... end— starts a procedure that runs once when the simulation begins. Here it is a script of test steps.#10— tells the simulator to wait ten time units before the next statement.$finish— asks the simulator to stop the run.
Is this Verilog? Yes. Is it RTL describing your hardware? No — in this example it is testbench code. #10 does not describe a physical "wait ten nanoseconds" block inside your design; it advances simulation time.
3.2 Seeing the difference at a glance
| Question | RTL design code | Testbench / simulation code |
|---|---|---|
| Written in Verilog? | Yes | Yes |
| Main purpose | Describe the hardware | Exercise and observe the hardware |
| Represents the design itself? | Usually yes | Not in the example above |
| Normally intended for | Synthesis | Simulation only |
| Typical contents | mux, register, counter, datapath | stimulus, delays, $display, $finish |
And where each one goes afterwards:
- RTL code → synthesis → an implementation in the target technology.
- Testbench code → simulator → stimulus and observation.
The simulator never turns your testbench into hardware, and synthesis does not translate a line of RTL into a particular gate — it works out an implementation of the behaviour you described.
3.3 Why this matters when you write
RTL is still code, and writing it is still programming in the ordinary sense. But what you are describing is different. You are not writing a sequence of instructions for a processor to execute; you are describing hardware that exists all at once.
assign y1 = a & b;
assign y2 = c | d;Do not read that as "first compute y1, then compute y2." Both pieces of combinational logic are there simultaneously, each one continuously following its own inputs. Swapping the two lines changes nothing.
This is why software habits can mislead a new RTL designer — and why the most useful question to ask of any line of RTL is the one this chapter keeps returning to:
What hardware does this describe, and when does it change?
4. Start With a Circuit You Already Know
Let us build a 2-to-1 multiplexer. A mux is a switch: it has two data inputs, a select line, and one output. The select line decides which input gets through.
Its behaviour fits in four rows:
sel | y |
|---|---|
| 0 | a |
| 1 | b |
That is the whole circuit. Now we write it.
4.1 The Verilog
`default_nettype none
module mux2 (
input wire sel,
input wire a,
input wire b,
output wire y
);
assign y = sel ? b : a;
endmoduleNine lines. Let us read every one.
4.2 Reading it line by line
`default_nettype none
A compiler directive — an instruction to the tool, not hardware. Without it, if you misspell a signal name Verilog quietly invents a new one-bit wire and your design silently does the wrong thing. With it, the misspelling becomes an error you see immediately. Put it at the top of every design file.
module mux2 (
This begins the definition of a hardware block called mux2. A module is a named piece of hardware with a boundary. The opening bracket ( starts the list of signals on that boundary.
input wire sel,
One signal on the boundary. Three separate pieces of information here:
input— the direction. This signal comes into the block from outside.wire— the type. A wire is a connection: it holds nothing itself, it just carries whatever is driving it.sel— the name.
The comma , at the end separates this port from the next one. It is punctuation, not hardware.
input wire a, and input wire b,
The two data inputs. Same three pieces of information, same meaning.
output wire y
The output. Direction output means this signal leaves the block. Notice there is no comma — it is the last port in the list.
);
The ) closes the port list, and the ; ends the statement. A semicolon in Verilog does not create hardware; it marks the end of a statement, the way a full stop ends a sentence.
assign y = sel ? b : a;
This line is the entire circuit. It is a continuous assignment: it says y is always equal to whatever is on the right. Not "compute this once" — always, permanently, like soldering a wire.
The ? : is the conditional operator. Read sel ? b : a as: if sel is 1, take b; otherwise take a. Compare that against the truth table above — it is the same statement in a different notation.
endmodule
Ends the module. No semicolon after it.
4.3 What hardware is this?
A multiplexer, and the ? : is why. Whenever you see a choice between two values in combinational RTL, the hardware is a mux.
Notice what is not happening: a and b are both physically connected all the time. sel does not switch one of them off — it decides which one reaches the output. Both paths exist in the silicon simultaneously. That is the difference between hardware and a program taking a branch.
This is §3 in practice. Writing assign y = sel ? b : a; is not asking something to run an instruction whenever sel changes — it describes selection behaviour that a synthesis tool can implement as combinational logic. That shift in reading is RTL thinking.
4.4 The symbols you just met
Verilog's punctuation confuses beginners more than its concepts do. The ones so far:
| Symbol | What it does |
|---|---|
; | Ends a statement. Creates no hardware. |
, | Separates items in a list, such as ports. |
( ) | Groups things — here, the port list. |
? : | Conditional: cond ? value_if_1 : value_if_0. Describes selection. |
` | Marks a compiler directive, as in `default_nettype. |
More arrive as we need them. Lexical Conventions (Chapter 4) covers the full set.
5. The Same Mux, Written a Second Way
Verilog gives you a second way to describe combinational logic, and it becomes essential once the choice has more than two options.
`default_nettype none
module mux2_proc (
input wire sel,
input wire a,
input wire b,
output reg y
);
always @(*) begin
if (sel) y = b;
else y = a;
end
endmoduleTwo lines changed meaningfully. Both need explaining.
output reg y
The type changed from wire to reg. This is the single most misunderstood word in Verilog, so read the next section carefully before drawing any conclusion from it.
always @(*) begin ... end
An always block — a piece of description that re-runs whenever something it depends on changes. The @(*) part is the sensitivity list, and * means "watch everything this block reads." So whenever sel, a or b changes, the block re-evaluates.
begin and end group the statements inside, the way brackets group a list. With only one statement you could omit them; with two or more you need them.
if (sel) y = b; else y = a;
The same selection as before, written as a familiar if. The = here is a blocking assignment; inside a combinational block it simply means "set y to this value."
This describes exactly the same multiplexer as assign y = sel ? b : a;. Two notations, one circuit.
5.1 reg does not mean register
This trips up nearly everyone, so it is worth being blunt.
regis not a register. It is a variable type.
In Verilog, any signal you assign inside an always block must be declared reg. That is a rule about how the signal is written, not about what hardware appears.
Look at the evidence: mux2_proc above declares y as reg, and the hardware it describes is a multiplexer — pure combinational logic, no memory, no flip-flop anywhere.
| Declaration | What it actually tells you |
|---|---|
wire | Driven continuously, by assign or by a module output |
reg | Assigned inside a procedural block (always or initial) |
Whether real storage appears depends entirely on what the block describes, not on the keyword. We will see the same reg keyword produce an actual flip-flop in §7 — the difference will be the clock, not the declaration.
Read reg as "procedurally assigned." Never as "flip-flop."
5.2 When to use which
Both forms are correct. The practical guidance:
assignfor short expressions — a bit of arithmetic, a boolean function, a two-way choice.always @(*)when the logic has several cases, becauseif/else if/casestays readable where nested? :does not.
6. The Mistake That Creates a Latch
Procedural combinational logic has one classic trap, and it is worth meeting now rather than discovering it in a synthesis report.
always @(*) begin
if (sel == 2'b00) y = a;
if (sel == 2'b01) y = b;
endWhen sel is 2'b10 or 2'b11, neither if matches and y is never assigned.
always @(*) begin
case (sel)
2'b00: y = a;
2'b01: y = b;
default: y = 1'b0;
endcase
enddefault covers every remaining value, so y always gets a value.
Here is why the left version is a problem. You described combinational logic, but you left a case where you did not say what y should be. The only way hardware can satisfy "keep y unchanged here" is to remember the previous value — which needs a memory element. So a latch appears in a circuit you intended to be memoryless.
The rule that prevents it:
In a combinational
alwaysblock, assign every output on every possible path.
A default in a case, or a final else, is usually all it takes. Synthesis tools warn when they infer a latch; treat that warning as an error until you have confirmed you meant it.
2'b00 is a sized literal: 2 bits wide, b for binary, value 00. Number formats are covered properly in Lexical Conventions (Chapter 4).
7. Adding Memory — the Flip-Flop
Everything so far had no memory. Now we add the one component that does.
A D flip-flop stores one bit. It has a data input d, a clock input, and an output q. On the active clock edge it takes whatever is on d and holds it on q until the next active edge.
7.1 The code
`default_nettype none
module dff (
input wire clk,
input wire d,
output reg q
);
always @(posedge clk) begin
q <= d;
end
endmodule7.2 Reading the new parts
always @(posedge clk)
The sensitivity list changed from @(*) to @(posedge clk). This is an edge-sensitive event control: it says the block describes behaviour that updates on a clock edge, rather than behaviour that follows its inputs continuously.
posedge means positive edge — the instant the clock goes from 0 to 1. So this block does nothing at all except at that instant. Between edges, it is not watching d.
Read it as RTL rather than as a script: this is not simply "code that runs when the clock changes." It describes state that updates in step with the active clock edge — which, in a synthesisable design, is the familiar register behaviour.
q <= d;
The assignment operator changed from = to <=. This is a non-blocking assignment, and it is what you use for clocked logic. §8 explains why, with a case where using the wrong one visibly breaks the circuit.
Read this line as: at this clock edge, sample d, and that sampled value becomes the new q.
output reg q
q is assigned inside an always block, so it must be reg — exactly the same rule as the mux. The difference is that this block is clocked, so this time real storage is described. Same keyword, different hardware, decided by the clock.
7.3 What the behaviour looks like
dff — q takes the value d had at each rising edge
8 cyclesRead it the way you will read every sequential waveform: find a rising clock edge, look at d at that moment, and that is the new q.
8. Why Clocked Logic Uses Non-Blocking Assignment
You could ask why q <= d; cannot simply be q = d;. The honest answer needs an example where the two behave differently. Here is the classic one: a shift register — three flip-flops in a row, each feeding the next.
always @(posedge clk) begin
q1 <= d;
q2 <= q1;
q3 <= q2;
endAt each edge, all three right-hand sides are read first, using the old values. Then all three are updated together. The bit moves one stage per clock.
always @(posedge clk) begin
q1 = d;
q2 = q1;
q3 = q2;
endEach statement finishes before the next starts, so q2 reads the value q1 was just given, and q3 reads the new q2. In simulation all three end up taking d at the same edge.
The left version describes what you drew: three separate flip-flops, a value stepping through them. The right version does not — its simulated behaviour no longer matches the pipeline you intended.
The reason is what the two operators mean:
<=(non-blocking) — every right-hand side in the block is evaluated using the values as they were before this edge, and the updates happen together. That matches real flip-flops, which all sample at the same instant.=(blocking) — each statement completes before the next one runs, so later statements see earlier results. The behaviour then depends on the order you wrote the statements in, which is not how a row of flip-flops behaves.
Which gives the working rule:
Use
<=in clocked blocks. Use=in combinational blocks.
That is not an arbitrary style preference. Non-blocking assignment makes clocked code express the simultaneous sampling that registers actually do, and keeps the meaning independent of statement order. The full treatment, including the simulator's event ordering, is Blocking and Non-Blocking Assignments (Chapter 14.3).
9. Reset — Giving State a Known Starting Value
Unless the implementation provides some defined initialisation, you should not assume an ordinary RTL register starts at 0 or 1. Reset is the usual way a design puts its state into a known condition.
`default_nettype none
module dff_rst (
input wire clk,
input wire rst_n,
input wire d,
output reg q
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) q <= 1'b0;
else q <= d;
end
endmodulerst_n — the trailing _n is a naming convention meaning active low: the signal does its job when it is 0, not 1. Very common for resets.
always @(posedge clk or negedge rst_n) — the block now runs on two events: a rising clock edge, or a falling edge on rst_n. That second trigger is what makes this reset asynchronous — it acts immediately, without waiting for a clock edge.
if (!rst_n) q <= 1'b0; — ! is logical NOT, so !rst_n means "rst_n is low," which is when reset is active. 1'b0 is a sized literal: 1 bit, binary, value 0.
else q <= d; — the normal behaviour when reset is not active.
Reset is written first inside the block deliberately: it takes priority over everything else.
10. Putting It Together — a 4-Bit Counter
Now we combine both kinds of logic in one module. A counter is a register that adds one to itself.
`default_nettype none
module counter4 (
input wire clk,
input wire rst_n,
input wire en,
output reg [3:0] count
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) count <= 4'b0000;
else if (en) count <= count + 4'b0001;
end
endmodule10.1 What is new here
[3:0] — this makes count four bits wide instead of one. Read it as "bits 3 down to 0." Bit 3 is the most significant. A multi-bit signal like this is called a vector. (Variables & Data Types, Chapter 5, covers vectors properly.)
count <= count + 4'b0001; — the counter's whole behaviour. The right-hand side reads the current value and adds one; the result becomes the new value at the edge. Because <= samples the old value first, this does exactly what you want — no circular-reasoning problem.
else if (en) — the enable. If en is 0, neither branch assigns count, so it keeps its value. In a clocked block that is correct and intended: a flip-flop holding its value is exactly what a register does. This is different from the latch problem in §6, which was about a combinational block.
10.2 The hardware this describes
Three things, working together:
- a 4-bit register holding
count - an adder computing
count + 1 - selection logic choosing between zero, the adder's result, and the held value — driven by
rst_nanden
That is the RTL shape from §2 in the smallest real form: a register, with combinational logic deciding what it should hold next.
10.3 The behaviour
counter4 — reset, count, hold, count
10 cyclesCheck one edge yourself. At the edge marked "en=0 → holds", count is 2 and en is 0, so neither branch runs and count stays 2 through the next cycle. Then en returns high and counting resumes from 2.
11. Scaling Up — an Accumulator
One more step, and it introduces only one genuinely new idea. An accumulator is a counter that adds a variable amount instead of always adding one.
`default_nettype none
module accumulator (
input wire clk,
input wire rst_n,
input wire en,
input wire [7:0] data_in,
output reg [7:0] sum_q
);
wire [7:0] sum_d;
assign sum_d = sum_q + data_in;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) sum_q <= 8'b0000_0000;
else if (en) sum_q <= sum_d;
end
endmoduleCompare it with the counter. The structure is identical — same reset, same enable, same clocked block. Two differences:
The next value is computed separately. assign sum_d = sum_q + data_in; is combinational logic sitting outside the clocked block, and sum_d is a wire because it is driven by assign. The clocked block then just stores it.
The naming says which is which. sum_q is the value held in the register — _q after the Q output of a flip-flop. sum_d is the value waiting to go in — _d after the D input. Once you adopt this convention you can read any RTL file and immediately see which signals are state and which are the logic feeding it.
8'b0000_0000 — eight bits, binary, all zero. The underscores are ignored by the tools and exist purely to make long numbers readable.
This split — combinational logic computes the next value, a clocked block stores it — is the standard shape of production RTL. Everything from a FIFO pointer to a CPU pipeline stage is written this way.
Where this leads. The mux, register, counter and accumulator in this chapter are the small pieces larger designs are assembled from. RTL Design Patterns is the track that takes these pieces further — how they are arranged into the recurring structures real designs use, such as state machines, FIFOs, pipelines and handshakes. You do not need any of it yet; it is where this foundation goes next.
12. What Synthesis Can and Cannot Build
Not everything you can write in Verilog can become hardware. The language also contains constructs that exist only to control a simulation.
| Usually synthesisable | Simulation-only |
|---|---|
assign | initial blocks |
always @(*) and always @(posedge clk) | #10 and other delays |
if, case, arithmetic, logical operators | $display, $finish and other system tasks |
| module instantiation, parameters | while loops with no static bound |
The practical discipline is simple and worth adopting immediately:
Design files describe hardware. Testbench files drive simulations. Keep them in separate files.
A note on accuracy, because it is often stated too strongly: whether a particular construct is synthesisable can depend on the construct, the target technology and the tool. initial, for instance, is ignored by typical ASIC flows but is supported for register initialisation by some FPGA flows. Rather than memorising a universal list, follow the file-separation rule and read your tool's warnings.
And one thing synthesis never does: translate a line of RTL into a gate. It reads the behaviour you described, optimises it, and builds something in the target technology that behaves the same way. Two tools can produce different structures from identical RTL.
13. Checking Your Work — a Small Testbench
You can reason about the counter, but running it is better. A testbench is a module whose job is to drive your design and let you watch it.
`timescale 1ns/1ps
`default_nettype none
module counter4_tb;
reg clk;
reg rst_n;
reg en;
wire [3:0] count;
counter4 u_dut (.clk(clk), .rst_n(rst_n), .en(en), .count(count));
// A clock: flip every 5 ns, so one full period is 10 ns.
initial begin
clk = 1'b0;
forever #5 clk = ~clk;
end
initial begin
rst_n = 1'b0;
en = 1'b0;
#12 rst_n = 1'b1; // release reset
#8 en = 1'b1; // start counting
#40 en = 1'b0; // hold
#20 $finish;
end
always @(posedge clk)
#1 $display("%0d ns: en=%b count=%0d", $time, en, count);
endmodule13.1 Reading the testbench
module counter4_tb; — note there is no port list. A testbench has no boundary because nothing connects to it; it is the outermost thing in the simulation.
reg clk; reg rst_n; reg en; — these are driven from inside initial blocks, so they must be reg. wire [3:0] count; is driven by the design, so it is a wire. That is the same rule from §5.1, applied.
counter4 u_dut (.clk(clk), ... ); — this creates one copy of your counter, named u_dut. The .port(signal) form connects each port by name, which stays correct even if the port order changes later.
initial begin ... end — an initial block runs once, starting at time zero, and its statements run in order. This is the one place in Verilog where sequence really is sequence — because writing a test genuinely is a list of steps.
forever #5 clk = ~clk; — #5 waits five time units, ~ inverts, so this flips the clock forever, producing a 10 ns period.
#12 rst_n = 1'b1; — waits 12 ns, then releases reset. Reset is released between clock edges on purpose, so there is no ambiguity about which edge saw which value.
$display — prints a line. The #1 before it waits 1 ns past the edge so the printed count is the settled value.
13.2 What you should see
6 ns: en=0 count=0
16 ns: en=0 count=0
26 ns: en=1 count=1
36 ns: en=1 count=2
46 ns: en=1 count=3
56 ns: en=1 count=4
66 ns: en=0 count=4
76 ns: en=0 count=4Reset holds the count at 0. Once en goes high it advances by one per clock. When en drops, the count freezes at 4. That matches the waveform in §10.3 and the code — which is exactly the three-way check you want: code, behaviour and hardware all telling the same story.
14. Mistakes Worth Meeting Early
Three realistic ones, all of which follow from something in this chapter.
14.1 Using blocking assignment in a clocked block
A shift register that will not shift
BLOCKING VS NON-BLOCKINGalways @(posedge clk) begin
q1 = d;
q2 = q1;
endThe design was drawn as two flip-flops in series, so a bit should take two clock edges to travel from d to q2. In simulation, q2 changes at the same edge as q1 — the delay is gone.
Blocking assignment (=) completes each statement before the next one starts. So q2 = q1; reads the value q1 was just given on this edge, not the value it held before it. The simulated behaviour is then a single stage, not the two the design intended — and it depends on the order the statements were written in.
always @(posedge clk) begin
q1 <= d;
q2 <= q1;
endNon-blocking assignment reads every right-hand side using the pre-edge values, then updates together — which is what two real flip-flops do at a shared clock edge.
14.2 An incomplete combinational block
Covered in §6: leaving a path where a combinational output is not assigned asks the hardware to remember, and a latch appears. Cover every case with a default or a final else.
14.3 Assuming reg means a flip-flop
Covered in §5.1. reg only tells you the signal is assigned procedurally. Look at the sensitivity list to find out whether storage is described: @(*) is combinational, @(posedge clk) is sequential.
15. Exercises
Work these out before reading the answers — predicting first is where the learning happens.
Exercise 1 — Read the hardware
What hardware does each line or block describe? Say whether it is combinational or sequential.
// (a)
assign y = a & b;
// (b)
assign y = sel ? b : a;
// (c)
always @(posedge clk)
q <= d;
// (d)
always @(*)
y = a | b;Exercise 2 — Predict the output
Using the flip-flop from §7, d has these values at four consecutive rising edges. What is q after each?
| Edge | d at that edge | q after |
|---|---|---|
| 1 | 1 | ? |
| 2 | 0 | ? |
| 3 | 0 | ? |
| 4 | 1 | ? |
Then answer: between edge 2 and edge 3, d briefly goes to 1 and back to 0. Does q change?
Exercise 3 — Find the bug
This is meant to be a 3-bit counter that resets to zero. There is one real bug. There is also something that looks like a bug but is a legitimate design choice — find both, and say which is which.
module counter3 (
input wire clk,
input wire rst_n,
output reg [2:0] count
);
always @(posedge clk) begin
if (!rst_n) count = 3'b000;
else count = count + 3'b001;
end
endmoduleExercise 4 — Modify the design
Start from counter4 in §10. Change it so that instead of counting up, it counts down by one. Then say what changes in the hardware.
Answers
Exercise 1.
- (a) An AND gate. Combinational — no clock, output depends only on
aandbnow. - (b) A 2-to-1 multiplexer. Combinational — a choice in combinational logic is selection logic.
- (c) A D flip-flop. Sequential —
@(posedge clk)means the value only changes at a clock edge. - (d) An OR gate. Combinational — note that this is
reg-declared and procedural, yet still contains no memory. The sensitivity list@(*), not the keyword, is what tells you.
Exercise 2. q is 1, 0, 0, 1 after edges 1–4 respectively — it simply takes the value d had at each edge.
The follow-up is the important half: no, q does not change. d moved between edges, and a flip-flop only looks at d at an active edge. That glitch is invisible to q.
Exercise 3. One bug, one design choice:
- The bug — blocking assignment in a clocked block. Both assignments use
=. In clocked RTL they should be<=, so the code expresses the simultaneous update a register performs — see §8. - Not a bug — this is a synchronous reset. The sensitivity list is
@(posedge clk)only, sorst_nis examined at the clock edge rather than acting immediately. That is a perfectly normal and widely used style, and it is what this code describes. It is simply a different choice from the asynchronous reset in §9, which addsor negedge rst_nso reset acts without waiting for an edge.
Knowing the difference between "this is wrong" and "this is a different valid choice" is a large part of reading RTL well.
Exercise 4. Change one operator:
else if (en) count <= count - 4'b0001;In hardware, the adder becomes a subtractor. Everything else is unchanged — the same 4-bit register, the same enable and reset selection logic. This is the point worth taking away: a small change in RTL is a small, predictable change in hardware, once you can read what the RTL describes.
16. Summary
You now have the core of RTL design.
- RTL is registers plus the logic between them. Every design is some arrangement of those two things.
- Combinational logic has no memory — its output follows its inputs. Write it with
assignor withalways @(*). - Sequential logic has memory — it changes only at a clock edge. Write it with
always @(posedge clk). - A choice in combinational logic is a multiplexer.
? :,if, andcaseall describe selection. regis not a register. It means "assigned inside a procedural block." The sensitivity list tells you whether storage is described.- Use
<=in clocked blocks and=in combinational blocks — because non-blocking assignment matches how real flip-flops all sample at the same edge. - Assign every output on every path in a combinational block, or you will get a latch you did not ask for.
- Compute the next value combinationally, store it in a clocked block. The
_d/_qnaming makes this visible at a glance.
The question to keep asking of every line you read or write:
What hardware does this describe, and when does it change?
Chapter 4 steps back to the language's building blocks — how Verilog is actually written down, from identifiers and numbers to operators — so the syntax you have been using becomes precise.
Related Tutorials
- Typical VLSI Design Flow — Chapter 2; where RTL sits in the larger process.
- Lexical Conventions — Chapter 4; the language's building blocks in detail.
- Variables & Data Types — Chapter 5; nets, variables and vectors properly.
- Design and Testbench Creation — Chapter 9; testbenches in full.
- Blocking and Non-Blocking Assignments — Chapter 14.3; the complete treatment.
- RTL Design Patterns — the next layer up: how registers, combinational logic and state are combined into FSMs, FIFOs, pipelines, arbiters and clock-domain crossings.
Standards & specifications
- Governing standard
- IEEE Std 1364 (Verilog)(opens IEEE in a new tab)
Defines the Verilog language and its simulation semantics, including the event scheduling model. Synthesis support is defined by tools, not by this standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the Verilog HDL curriculum.
