Verilog Series · Module 06

Verilog Data — Numbers, Strings, Logic, Data Types, Operators
Verilog Series · Module 06

Numbers, Strings, Logic, Data Types & Operators

A complete reference to Verilog’s value system — how numbers are written, what logic values mean, data types, scalars, vectors, parameters, and every operator category.

🔢 Numbers

Verilog supports two forms of number literals: sized (with explicit bit-width) and unsized (default 32-bit). In digital design, always use sized literals — they make the intent explicit and prevent accidental sign or width mismatches.

📐
Sized Numbers
Explicitly declare bit-width, base, and value. Format: <size>'<base><value>. Example: 8'hFF, 4'b1010.
🔄
Unsized Numbers
Plain decimal integers with no width prefix. Default to 32 bits. Example: 255, -8. Avoid in RTL — use sized literals instead.
±
Signed Numbers
Add s after the base character for signed interpretation. Example: 4'sb1011 = −5 in 2’s complement.
_
Underscore Separator
Underscores inside number literals are ignored by the compiler — use them for readability. Example: 16'b1010_0101_1100_0011.

📐 Number Format Anatomy

Every sized number in Verilog follows this exact structure:

8
Size
Number of bits. Must be a positive integer.
separator
h
Base
b / o / d / h
FF
Value
Digits in chosen base

The Four Supported Bases

‘b
Binary
4’b1010
4’b1x0z
‘o
Octal
6’o52
12’o777
‘d
Decimal
8’d255
4’d9
‘h
Hexadecimal
8’hFF
16’hDEAD
Fig 1 — Number literal examples across all bases
// ── Sized numbers ─────────────────────────────────────────────
4'b1010        // 4-bit  binary   → decimal 10
8'hFF          // 8-bit  hex      → decimal 255
6'd35          // 6-bit  decimal  → binary 100011
12'o777        // 12-bit octal    → binary 111_111_111

// ── X and Z values in binary ──────────────────────────────────
4'b1x0z        // bit 3=1, bit 2=unknown, bit 1=0, bit 0=high-Z
8'bxxxx_zzzz   // upper nibble unknown, lower nibble high-Z
4'bx           // all 4 bits unknown  (x extends to fill width)
4'bz           // all 4 bits high-Z   (z extends to fill width)

// ── Signed numbers ────────────────────────────────────────────
4'sb1011       // signed 4-bit: = -5 in 2's complement
8'sd-20        // signed 8-bit decimal -20

// ── Underscore for readability (ignored by compiler) ──────────
16'b1010_0101_1100_0011   // groups of 4 bits
32'h_DEAD_BEEF             // hex groups of 4

// ── Unsized (avoid in RTL — 32-bit default) ───────────────────
255            // 32-bit decimal 255
'b1010        // unsized binary — width inferred from context
Width mismatch behaviour: If the value is wider than the declared size, the most significant bits are truncated. If narrower, the value is zero-padded on the left (or sign-extended for signed). Always size your literals correctly to avoid silent truncation.

Number Comparison Table

LiteralSizeBaseDecimal valueBinary equivalent
4’b1010 4-bit Binary 10 1010
8’hFF 8-bit Hex 255 1111_1111
6’d35 6-bit Decimal 35 100011
12’o777 12-bit Octal 511 111_111_111
4’sb1011 4-bit Binary (signed) −5 1011

💬 Strings

A string in Verilog is a sequence of ASCII characters enclosed in double quotes on a single line. Strings are used primarily in simulation — for $display messages, file names, and test output — not in synthesizable RTL.

📏
Single Line Only
A string cannot span multiple lines. It must open and close on the same line.
🔤
ASCII Storage
Each character occupies 8 bits (one byte). A string of N characters needs N×8 bits of storage in a reg.
🔗
Concatenated in Registers
When assigned to a reg, characters are packed left-to-right, MSB first.
⚙️
Simulation Only
String operations via $display, $monitor, $readmemh are not synthesizable — testbench use only.
Fig 2 — Strings in Verilog: storage and usage
// String in $display — most common use
$display("Simulation started at time %0t", $time);
$display("Result = %0d (expected 255)", result);

// Storing a string in a reg
// "numb" = 4 chars × 8 bits = 32 bits needed
reg [31:0] str_reg;
str_reg = "numb";
// Stored as: n=8'h6E, u=8'h75, m=8'h6D, b=8'h62
// str_reg = 32'h6E_75_6D_62

// String in file I/O
integer fh;
fh = $fopen("output.txt", "w");
$fwrite(fh, "Test PASS\n");
$fclose(fh);

// Escape sequences inside strings
"\n"   // newline
"\t"   // tab
"\\"   // backslash
"\""   // double quote
"%d %b %h %o"  // format specifiers for $display

🔵 Logic Values

Every signal in Verilog can hold one of four possible logic values. This four-value system (called 4-state logic) is what allows Verilog to accurately model real digital hardware — including uninitialized signals and tri-state buses that binary 0/1 cannot represent.

0
Logic Zero
Logic low / false. Driven by a source pulling to GND.
1
Logic One
Logic high / true. Driven by a source pulling to VDD.
x
Unknown
Value unknown or undefined — uninitialized, conflicting drivers, or don’t-care.
z
High Impedance
Floating / disconnected. No driver active — tri-state bus in undriven state.
Fig 3 — When each logic value occurs
// ── Value 0 and 1: normal driven states ──────────────────────
assign y = 1'b0;             // y = 0
assign y = 1'b1;             // y = 1

// ── Value x: unknown / uninitialized ─────────────────────────
reg q;                       // q starts as x at simulation start
wire w = a & b;              // if a or b is x, w is x
4'bx                         // all 4 bits = x (extends to fill)

// ── Value z: high-impedance (tri-state) ───────────────────────
assign bus = oe ? data : 8'bz; // float bus when oe=0
wire undriven;               // unconnected wire → z by default

How Gates Handle x and z

SituationResultExplanation
0 AND x 0 Output is always 0 regardless of other input
1 AND x x Result depends on unknown input — propagates x
z into gate x z input treated as x for all logic gates
z into nmos/pmos z MOS switches propagate z to output (exception)
Two drivers: 0 and 1 x Conflict — result is unknown
Two drivers: same value that value No conflict — resolved correctly
x in simulation ≠ don’t care in hardware. In simulation, x means the simulator genuinely doesn’t know the value. In synthesis, a casex/casez statement uses x/z as wildcards. They look similar but behave very differently — don’t confuse them.

💪 Signal Strengths

When multiple sources drive the same wire net simultaneously, Verilog uses a strength resolution system to determine the final value. Every driven signal has both a value (0/1/x/z) and a strength. The stronger source wins. If two equal-strength sources drive opposite values, the result is x.

Strength Level Keyword Category Typical Source
7 — Strongest supply Driving supply0 / supply1 nets — power rails
6 strong Driving Default strength for all gate outputs and assign
5 pull Driving Pull-up / pull-down resistors
4 weak Driving Weak drivers — can be overridden by normal drivers
3 large Capacitive Large capacitive storage node
2 medium Capacitive Medium capacitive storage node
1 small Capacitive Small capacitive storage node (trireg)
0 — Weakest highz High-Z No driver — floating signal
Fig 4 — Strength resolution in a wired-AND scenario
// supply0 always wins over weak1
supply0 gnd;              // strength 7, value 0
wire    net;

assign (weak1, weak0) net = 1'b1;  // strength 4, value 1
assign gnd;             // strength 7, value 0 — wins
// Result: net = 0  (supply beats weak)

// Equal strength conflict → x
assign (strong1, strong0) net = 1'b1;   // strong 1
assign (strong1, strong0) net = 1'b0;   // strong 0
// Result: net = x  (equal strengths conflict)
In practice: Strength resolution is mostly relevant for switch-level modeling and standard cell characterization. In RTL design, the default strong strength is used everywhere. You only specify strengths explicitly when modeling pull-up/pull-down resistors or wired logic.

💾 Data Types

Verilog data types fall into two fundamental categories based on whether they model physical connections or store values:

🔌 NET Types — Physical Connections

wire Most common. Models a physical wire. Driven by one source; holds z when undriven. Used with assign.
tri Functionally identical to wire. Naming convention signals multiple drivers / tri-state intent.
wand Wired-AND net. Multiple drivers are AND-resolved automatically in hardware.
wor Wired-OR net. Multiple drivers are OR-resolved automatically in hardware.
triand / trior Same as wand/wor but also tri-state capable.
trireg Capacitive net — retains last driven value when all drivers go to z. Models charge storage.
supply0 Permanently tied to logic 0 (GND) at highest drive strength (supply).
supply1 Permanently tied to logic 1 (VDD) at highest drive strength (supply).

📦 VARIABLE Types — Value Storage

reg Most common variable. Holds a 4-state value (0/1/x/z) between assignments. Must be driven from an always or initial block.
integer 32-bit signed integer. Used for loop counters, array indices, and testbench arithmetic. Not a hardware type.
real Double-precision floating-point (64-bit IEEE 754). For analog values and simulation math only.
realtime Same as real but stores simulation time values.
time 64-bit unsigned integer. Stores simulation time via $time. Not synthesizable.
wire vs reg: The most common confusion in Verilog. wire is for continuous assignments (assign) and module ports driven externally. reg is for signals assigned inside always blocks. A reg does not necessarily become a flip-flop in hardware — it only does so if there is a clock edge sensitivity.
Fig 5 — When to use wire vs reg
// wire: driven by assign or module port ─────────────────────
wire y;
assign y = a & b;          // ✅ wire driven by assign

wire sum;
adder u1 (.sum(sum), ...);  // ✅ wire driven by module output

// reg: driven by always or initial block ─────────────────────
reg q;
always @(posedge clk)
  q <= d;                   // ✅ reg driven by always → flip-flop

reg combo;
always @(*)
  combo = a | b;            // ✅ reg driven by always → combinational

// ❌ Common mistakes ─────────────────────────────────────────
wire bad;
always @(*) bad = x;       // ❌ cannot drive wire from always

reg bad2;
assign bad2 = x;           // ❌ cannot use assign on reg (illegal)

📏 Scalars & Vectors

A scalar is a single-bit signal (no range declared). A vector is a multi-bit signal declared with an explicit bit range [MSB:LSB]. Both nets and variables can be scalars or vectors.

Fig 6 — Vector anatomy: [MSB : LSB]
wire [ 7 : 0 ] data ; MSB index (bit 7 = highest) LSB index (bit 0 = lowest) Signal name 8-bit bus bit 7 bit 6 bit 1 bit 0 ← LSB MSB →
Fig 7 — Scalar and vector declarations with bit access
// ── Scalar (1-bit, no range) ──────────────────────────────────
wire       wr;          // 1-bit net
reg        flag;        // 1-bit register

// ── Vectors (multi-bit) ───────────────────────────────────────
wire [7:0]  data;        // 8-bit bus  — data[7]=MSB, data[0]=LSB
reg  [3:0]  nibble;      // 4-bit reg
reg  [31:0] word;         // 32-bit reg

// ── Signed vectors ────────────────────────────────────────────
wire signed [7:0] s_data; // signed: range -128 to +127
reg  signed [3:0] s_reg;  // signed: range -8 to +7

// ── Bit-select (access a single bit) ─────────────────────────
data[0]                    // LSB of data
data[7]                    // MSB of data

// ── Part-select (slice a range of bits) ──────────────────────
data[7:4]                  // upper nibble
data[3:0]                  // lower nibble

// ── Indexed part-select (Verilog-2001) ────────────────────────
data[4 +: 4]               // bits [7:4] — start at 4, width 4, going up
data[7 -: 4]               // bits [7:4] — start at 7, width 4, going down

Memory Arrays

// 2D array: [word_range] [bit_range]
reg [7:0] mem [0:255];      // 256 words × 8 bits = 2KB memory
reg [31:0] regfile [0:31];  // 32-entry × 32-bit register file

// Access
mem[5] = 8'hAB;             // write byte to address 5
data  = mem[5];             // read byte from address 5
// Note: mem[5][3:0] — bit-select within a memory word is NOT
// supported in all simulators without an intermediate variable

⚙️ Parameters

Parameters are named constants set at compile time. They make modules configurable and reusable — you change a parameter value at instantiation and the entire module adapts, without touching the source.

Default in Module Definition

module adder_n #(
  parameter N = 8
) ( ... );
  wire [N-1:0] sum;
endmodule

Override at Instantiation

// 16-bit instance
adder_n #(.N(16)) u1(...);

// 32-bit instance
adder_n #(.N(32)) u2(...);
Fig 8 — parameter vs localparam vs defparam
// parameter — overridable at instantiation time
parameter  DATA_W  = 8;
parameter  DEPTH   = 16;

// localparam — internal constant, cannot be overridden from outside
localparam ADDR_W  = $clog2(DEPTH);   // derived from DEPTH
localparam MAX_VAL = ((1 << DATA_W) - 1);

// Multiple parameters in one declaration
parameter MEM_SIZE = 256,
           WORD_W  = 32,
           FACTOR  = WORD_W / 2;

// defparam (legacy — avoid in new code)
adder_n u3 (...);
defparam u3.N = 64;    // override N — works but discouraged
localparam vs parameter: Use localparam for constants derived from other parameters (like address width from depth). They cannot be accidentally overridden from outside, which prevents misconfiguration of internal constants.

🧮 Operators

Verilog operators closely follow C syntax with important extensions for hardware — including bitwise reduction, concatenation, and replication. They are classified by the number of operands they take.

1️⃣
Unary
One operand. Written as op a. Example: ~a (bitwise NOT), &a (reduction AND).
2️⃣
Binary
Two operands. Written as a op b. Most common form. Example: a & b, a + b.
3️⃣
Ternary
Three operands. Written as cond ? a : b. The only ternary operator — implements a 2-to-1 mux.
Arithmetic Operators + – * / % **
OperatorNameExampleNotes
+ Addition a + b Result width = max(width_a, width_b)
Subtraction a – b Unsigned by default; use signed for arithmetic right
* Multiplication a * b Result may need wider target to avoid truncation
/ Division a / b Synthesizable but costly — generates large hardware
% Modulus a % b Remainder. Also costly — use power-of-2 when possible
** Power 2 ** 8 Exponentiation — often used with constants only
Logical Operators && || !

Always produce a 1-bit result (true=1 / false=0). Treat any non-zero value as true.

OperatorNameExampleResult
&& Logical AND a && b 1 if both a and b are non-zero
|| Logical OR a || b 1 if either a or b is non-zero
! Logical NOT !a 1 if a is zero; 0 if a is non-zero
Bitwise Operators & | ^ ~ ~^

Operate bit-by-bit across two operands. Result has same width as the wider operand.

OperatorNameExampleBit-level operation
& Bitwise AND 4’b1100 & 4’b1010 = 4’b1000 Each bit ANDed independently
| Bitwise OR 4’b1100 | 4’b1010 = 4’b1110 Each bit ORed independently
^ Bitwise XOR 4’b1100 ^ 4’b1010 = 4’b0110 Each bit XORed independently
~ Bitwise NOT ~4’b1010 = 4’b0101 Every bit inverted
~^ Bitwise XNOR 4’b1100 ~^ 4’b1010 = 4’b1001 Each bit XNORed (equivalence)
Reduction Operators &a |a ^a ~&a …

Unary operators that collapse a multi-bit vector down to a single bit by applying the operation across all bits.

OperatorNameExampleResult
&a Reduction AND &4’b1111 = 1 1 only if ALL bits are 1 — “are all bits set?”
~&a Reduction NAND ~&4’b1111 = 0 Inverse of reduction AND
|a Reduction OR |4’b0001 = 1 1 if ANY bit is 1 — “is non-zero?”
~|a Reduction NOR ~|4’b0000 = 1 1 only if ALL bits are 0 — “is zero?”
^a Reduction XOR ^4’b1011 = 1 Parity of all bits — 1 if odd number of 1s
~^a Reduction XNOR ~^4’b1011 = 0 Even parity check
Shift Operators << >> <<< >>>
OperatorNameExampleFill bits
<< Logical left shift 4’b1010 << 1 = 4’b0100 Fills with 0s on right — equivalent to ×2
>> Logical right shift 4’b1010 >> 1 = 4’b0101 Fills with 0s on left — equivalent to ÷2
<<< Arithmetic left shift 4’sb1010 <<< 1 Same as logical left shift (fills 0s)
>>> Arithmetic right shift 4’sb1010 >>> 1 = 4’sb1101 Fills with sign bit — preserves sign for 2’s complement
Relational & Equality Operators < > == != === !==

Always return a 1-bit result. The key distinction is between logical equality (==) and case equality (===).

OperatorNamex/z behaviour
< Less than Returns x if either operand contains x or z
> Greater than Returns x if either operand contains x or z
<= Less than or equal Returns x if either operand contains x or z
>= Greater or equal Returns x if either operand contains x or z
== Logical equality Returns x if either operand has x or z bits — cannot distinguish unknown
!= Logical inequality Returns x if either operand has x or z bits
=== Case equality Compares x and z literally — returns 0 or 1 only, never x. Simulation-only.
!== Case inequality Inverse of ===. Returns 0 or 1 only. Simulation-only.
Use === for checking x in testbenches. The expression if (q === 1'bx) tells you the signal is genuinely unknown. With ==, x == 1'bx returns x (not 1), so your if-condition may not fire.
Concatenation & Replication {a,b} {n{a}}
OperatorNameExampleResult
{a,b} Concatenation {4’b1100, 4’b1010} = 8’b1100_1010 Joins bits of multiple operands left-to-right
{n{a}} Replication {4{2’b10}} = 8’b10101010 Repeats operand n times — n must be a constant
// Concatenation examples
wire [7:0] byte_out = {upper, lower};    // join two 4-bit signals
assign {cout, sum} = a + b;              // split result across ports
wire [15:0] word = {byte_a, byte_b};   // combine bytes into word

// Replication examples
wire [7:0] ones  = {8{1'b1}};          // 8'b11111111 = 8'hFF
wire [7:0] zeros = {8{1'b0}};          // 8'b00000000

// Sign-extension using replication
wire [7:0] s4 = 4'sb1011;              // 4-bit signed -5
wire [7:0] s8 = {{4{s4[3]}}, s4};       // sign-extend to 8-bit: 8'sb11111011
Ternary Operator (Conditional) cond ? a : b

The only 3-operand operator in Verilog. Equivalent to a 2-to-1 multiplexer — widely used in assign statements.

// Basic mux
assign out = sel ? a : b;          // if sel=1 → a, else → b

// Nested ternary — 4-to-1 mux (use case statement for readability)
assign out = (sel==2'b00) ? in0 :
             (sel==2'b01) ? in1 :
             (sel==2'b10) ? in2 : in3;

// Enable / tri-state
assign bus = oe ? data : 8'bz;     // drive or float

// Default value when condition is unknown (x)
// If sel = x, ternary result = x — design so sel is never x

Operator Precedence (High → Low)

PriorityOperatorsCategory
Highest + - ! ~ & ~& | ~| ^ ~^ (unary) Unary
2 ** Power
3 * / % Multiply / Divide
4 + - (binary) Arithmetic
5 << >> <<< >>> Shift
6 < <= > >= Relational
7 == != === !== Equality
8 & (binary) Bitwise AND
9 ^ ~^ (binary) Bitwise XOR/XNOR
10 | (binary) Bitwise OR
11 && Logical AND
12 || Logical OR
Lowest ? : Ternary
Always use parentheses to make precedence explicit when mixing operator types. Writing a & b | c is technically legal but far less clear than (a & b) | c. Explicit parentheses eliminate ambiguity for both the compiler and any human reading the code.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top