Values, types, expressions and operators

Learning objectives
  • Use four-state values, nets, procedural variables, sized literals, concatenation, replication, arithmetic, logical, bitwise, reduction, comparison, and shift operators
  • Predict width and signedness effects
1 Objective→ 2 Learn→ 3 Example→ 4 Practice→ 5 Verify→ 6 Continue
How to study this lessonUnderstand the idea first. Use the example to see the behaviour, practise where provided, then make sure you can explain the result before continuing.
76543210
Width and signedness control how this bit vector is extended and calculated.
Concept diagram: Values, types, expressions and operators

Explanation

Lexical details that prevent avoidable errors

Verilog identifiers are case-sensitive: dataReady and dataready name different objects. Use consistent names, and keep comments and whitespace for readability; they separate tokens but do not change the hardware meaning. An ordinary identifier starts with a letter or underscore and may contain digits after the first character. Escaped identifiers begin with a backslash and end at whitespace, so they are usually avoided in RTL because they are awkward to read and connect.

Four-state values

Verilog simulation uses 0, 1, x, and z.

  • x means the value is unknown or cannot be determined.
  • z means high impedance, usually an undriven or tri-stated connection.

Do not force an x to zero merely to make a waveform look clean. Find whether it comes from missing reset, incomplete assignment, invalid address, multiple drivers, or a testbench race.

Nets and procedural variables

wire [7:0] sum_wire;  // driven by assign or a module output
reg  [7:0] sum_reg;   // assigned inside initial or always

In Verilog, reg describes a procedural variable. The name does not guarantee a physical register. A combinational always block can drive a reg, while incomplete assignment can accidentally infer a latch.

Literals must communicate width

8'b1010_0110   // 8-bit binary
12'hA5C        // 12-bit hexadecimal
6'd35          // 6-bit decimal
4'b10xz        // four-state literal

Prefer explicit widths in RTL. Truncation drops high bits. Assigning a narrow unsigned value to a wider destination zero-extends it. Signed extension depends on the expression being signed.

reg signed [7:0] temperature;
wire signed [8:0] extended = {temperature[7], temperature};

Make signed conversion visible with $signed(...) or $unsigned(...) when mixed expressions would be unclear.

Operator groups

Group Examples Meaning
Arithmetic + - * / % Numeric calculation
Bitwise & \| ^ ~ Operate independently on bits
Logical && \|\| ! Treat operands as true or false
Reduction &a, \|a, ^a Reduce a vector to one bit
Comparison == != < <= > >= Compare values
Case equality === !== Include x and z in comparison
Shift << >> <<< >>> Move bits; arithmetic right shift needs signed intent
Conditional c ? a : b Select one expression

Use parentheses when a reader might need to remember precedence.

Concatenation and replication

wire [15:0] word = {high_byte, low_byte};
wire [31:0] mask = {4{8'hA5}};
wire [8:0]  full_sum = {1'b0, a} + {1'b0, b};

The extended operands in full_sum preserve the carry. Assigning an 8-bit addition directly to nine bits can still lose the carry if the expression was evaluated at only eight bits.

Equality in design and checking

Logical equality (==) may return x when an operand contains unknown bits. Case equality (===) produces a definite test result and is useful in testbench checks. Case equality in synthesizable datapaths usually hides the wrong question: real gates do not implement a simulation-only unknown state.

Worked example

For unsigned a and b declared [7:0], calculate a carry-preserving sum:

wire [8:0] sum = {1'b0, a} + {1'b0, b};
wire       carry = sum[8];
wire [7:0] result = sum[7:0];

For a=8'hFF and b=8'h01, sum is 9'h100, carry is one, and result is zero.

Diagnose signed extension before changing the destination width

Making the destination wider does not tell the simulator whether a bit pattern represents a positive or negative value. The operands must carry the intended signed interpretation into the calculation.

reg signed [7:0] a;
reg signed [7:0] b;
wire signed [8:0] signed_sum;
wire        [8:0] zero_extended_sum;

assign signed_sum = {a[7], a} + {b[7], b};
assign zero_extended_sum = {1'b0, a} + {1'b0, b};

For a = -8'sd5 and b = 8'sd3, sign extension produces the nine-bit two's-complement value 9'h1FE (−2). Zero extension instead produces 9'h0FE; the bit pattern is valid, but it describes a different number. When an arithmetic result is surprising, check the declaration and extension of each operand first, then inspect the intermediate result before its final assignment.

Review question What to inspect
Can the mathematical result exceed the operand width? Extend operands before the operation and size the intermediate result.
Is a negative value involved? Confirm both operands are signed as intended and sign-extended.
Is only a slice assigned? Confirm discarded bits are intentionally unused or captured as carry/status.
Does a test contain unknown bits? Use a strict testbench comparison and locate the first source of x/z.

Check yourself

Why does reg not necessarily mean a flip-flop? What difference between == and === matters in a self-checking testbench? How can an arithmetic carry be lost before assignment?