Skip to content
VLSI Mentor

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 isHow you recognise it
CombinationalLogic with no memory. The output depends only on the inputs right now.No clock anywhere in the description
SequentialLogic 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Verilog used as RTL
module and_gate (
    input  wire a,
    input  wire b,
    output wire y
);

    assign y = a & b;

endmodule
  • module — 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: y is the AND of a and b, 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Verilog used as testbench stimulus
initial begin
    a = 1'b0;
    b = 1'b0;

    #10 a = 1'b1;
    #10 b = 1'b1;

    #10 $finish;
end
  • initial 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

QuestionRTL design codeTestbench / simulation code
Written in Verilog?YesYes
Main purposeDescribe the hardwareExercise and observe the hardware
Represents the design itself?Usually yesNot in the example above
Normally intended forSynthesisSimulation only
Typical contentsmux, register, counter, datapathstimulus, 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.

Azvya Education Pvt. Ltd.VLSI Mentor
both of these exist at the same time
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.

A 2-to-1 multiplexer with data inputs a and b, select input sel, and output yabMUXysel
A 2-to-1 multiplexer — sel decides which input reaches the output.

Its behaviour fits in four rows:

sely
0a
1b

That is the whole circuit. Now we write it.

4.1 The Verilog

Azvya Education Pvt. Ltd.VLSI Mentor
mux2.v — a 2-to-1 multiplexer
`default_nettype none

module mux2 (
    input  wire sel,
    input  wire a,
    input  wire b,
    output wire y
);

    assign y = sel ? b : a;

endmodule

Nine 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:

SymbolWhat 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.

Azvya Education Pvt. Ltd.VLSI Mentor
mux2_proc.v — the same mux, procedurally
`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

endmodule

Two 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.

reg is 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.

DeclarationWhat it actually tells you
wireDriven continuously, by assign or by a module output
regAssigned 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:

  • assign for short expressions — a bit of arithmetic, a boolean function, a two-way choice.
  • always @(*) when the logic has several cases, because if/else if/case stays 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.

❌ Incomplete — infers a latch
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(*) begin
    if (sel == 2'b00) y = a;
    if (sel == 2'b01) y = b;
end

When sel is 2'b10 or 2'b11, neither if matches and y is never assigned.

✅ Complete — pure combinational
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(*) begin
    case (sel)
        2'b00:   y = a;
        2'b01:   y = b;
        default: y = 1'b0;
    endcase
end

default 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 always block, 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.

A D flip-flop with data input d, clock input clk and output qD-FFdDclkqQ
A D flip-flop — the triangle marks the clock input as edge-triggered.

7.1 The code

Azvya Education Pvt. Ltd.VLSI Mentor
dff.v — one bit of storage
`default_nettype none

module dff (
    input  wire clk,
    input  wire d,
    output reg  q
);

    always @(posedge clk) begin
        q <= d;
    end

endmodule

7.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 cycles
dff — q takes the value d had at each rising edgesamples d=1samples d=1samples d=0samples d=0samples d=1samples d=1clkdqt0t1t2t3t4t5t6t7
At every rising edge, look at d. That value becomes q and is held until the next rising edge.

Read 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.

✅ Non-blocking — a real 3-stage shift register
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk) begin
    q1 <= d;
    q2 <= q1;
    q3 <= q2;
end

At 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.

❌ Blocking — no longer describes three stages
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk) begin
    q1 = d;
    q2 = q1;
    q3 = q2;
end

Each 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.

Azvya Education Pvt. Ltd.VLSI Mentor
dff_rst.v — a flip-flop with asynchronous reset
`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

endmodule

rst_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.

Azvya Education Pvt. Ltd.VLSI Mentor
counter4.v — a 4-bit counter with enable
`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

endmodule

10.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_n and en

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 cycles
counter4 — reset, count, hold, counten=1 → countsen=1 → countsen=0 → holdsen=0 → holdsclkrst_nencount0001122223t0t1t2t3t4t5t6t7t8t9
With enable high the count advances one per rising edge. With enable low it holds — no edge changes it.

Check 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.

Azvya Education Pvt. Ltd.VLSI Mentor
accumulator.v — a running sum
`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

endmodule

Compare 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 synthesisableSimulation-only
assigninitial blocks
always @(*) and always @(posedge clk)#10 and other delays
if, case, arithmetic, logical operators$display, $finish and other system tasks
module instantiation, parameterswhile 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.

Azvya Education Pvt. Ltd.VLSI Mentor
counter4_tb.v — driving the counter
`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);

endmodule

13.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

Azvya Education Pvt. Ltd.VLSI Mentor
Simulation output
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=4

Reset 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

1

A shift register that will not shift

BLOCKING VS NON-BLOCKING
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk) begin
    q1 = d;
    q2 = q1;
end
Symptom

The 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.

Root Cause

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.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk) begin
    q1 <= d;
    q2 <= q1;
end

Non-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.

Azvya Education Pvt. Ltd.VLSI Mentor
exercise-1.v
// (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?

Edged at that edgeq after
11?
20?
30?
41?

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.

Azvya Education Pvt. Ltd.VLSI Mentor
exercise-3.v
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
endmodule

Exercise 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 a and b now.
  • (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:

  1. 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.
  2. Not a bug — this is a synchronous reset. The sensitivity list is @(posedge clk) only, so rst_n is 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 adds or negedge rst_n so 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 assign or with always @(*).
  • 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, and case all describe selection.
  • reg is 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 / _q naming 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.

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.