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.
<size>'<base><value>. Example: 8'hFF, 4'b1010.255, -8. Avoid in RTL — use sized literals instead.s after the base character for signed interpretation. Example: 4'sb1011 = −5 in 2’s complement.16'b1010_0101_1100_0011.📐 Number Format Anatomy
Every sized number in Verilog follows this exact structure:
The Four Supported Bases
4’b1x0z
12’o777
4’d9
16’hDEAD
// ── 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
Number Comparison Table
| Literal | Size | Base | Decimal value | Binary 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.
reg.reg, characters are packed left-to-right, MSB first.$display, $monitor, $readmemh are not synthesizable — testbench use only.// 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.
// ── 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
| Situation | Result | Explanation |
|---|---|---|
| 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 |
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 |
// 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)
💾 Data Types
Verilog data types fall into two fundamental categories based on whether they model physical connections or store values:
🔌 NET Types — Physical Connections
z when undriven. Used with assign.
wire. Naming convention signals multiple drivers / tri-state intent.
z. Models charge storage.
📦 VARIABLE Types — Value Storage
always or initial block.
real but stores simulation time values.
$time. Not synthesizable.
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.
// 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.
// ── 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(...);
// 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 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.
op a. Example: ~a (bitwise NOT), &a (reduction AND).a op b. Most common form. Example: a & b, a + b.cond ? a : b. The only ternary operator — implements a 2-to-1 mux.| Operator | Name | Example | Notes |
|---|---|---|---|
| + | 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 |
Always produce a 1-bit result (true=1 / false=0). Treat any non-zero value as true.
| Operator | Name | Example | Result |
|---|---|---|---|
| && | 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 |
Operate bit-by-bit across two operands. Result has same width as the wider operand.
| Operator | Name | Example | Bit-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) |
Unary operators that collapse a multi-bit vector down to a single bit by applying the operation across all bits.
| Operator | Name | Example | Result |
|---|---|---|---|
| &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 |
| Operator | Name | Example | Fill 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 |
Always return a 1-bit result. The key distinction is between logical equality (==) and case equality (===).
| Operator | Name | x/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. |
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.
| Operator | Name | Example | Result |
|---|---|---|---|
| {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
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)
| Priority | Operators | Category |
|---|---|---|
| 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 |
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.
