Inside Operator
Set membership testing, ranges, arrays, X/Z wildcard behavior, constraint usage.
Module 4 · Page 4.8
The Operator That Makes Constraint Ranges Readable
Without inside, checking whether an 8-bit value belongs to a set of legal opcodes looks like this: (op == 8'h10) || (op == 8'h20) || (op == 8'h30) || (op inside {[8'h40:8'h5F]}). With inside: op inside {8'h10, 8'h20, 8'h30, [8'h40:8'h5F]}. Same semantics, a quarter of the characters, zero chance of missing a parenthesis.
The real power shows up in constraints. The constraint solver treats inside as a first-class construct — it can draw uniformly from a set that mixes individual values and ranges without you building a custom distribution. Trying to replicate that with OR-chained equalities gives you correct coverage but usually a biased distribution.
In procedural simulation code, inside returns a 1-bit result: 1 if the value is in the set, 0 if not. The comparison under the hood uses the wildcard equality operator ==?, which means X and Z bits in the set members act as don't-cares. That is useful when intentional, and surprising when it isn't.
Set Membership — What It Actually Tests
val inside {set} asks: "does val match at least one member of the set?" The set can contain three kinds of members:
- Single value — inside {8'hFF} — tests equality with one specific value. Equivalent to val == 8'hFF (using ==? internally).
- Range [lo:hi] — inside {[0:15]} — tests whether val falls within the closed range [lo, hi] inclusive. Both bounds are included.
- Array/queue variable — inside {arr} — tests membership against all elements of a packed or unpacked array. Each element is tested with ==?.
- Mixed set — inside {0, [4:7], 15, arr} — all member types can appear in one set. The result is 1 if any single test passes.
The operator evaluates each member test in order and short-circuits to 1 as soon as any match is found. In simulation this is purely a combinational expression — it has no state, no side effects. In a constraint block it tells the solver which values are legal for that random variable.
Syntax and Evaluation Rules
// General syntax
// result = expression inside { member_list };
// result is 1-bit: 1 = match found, 0 = no match
logic [7:0] val;
// ── Single values ────────────────────────────────────────────────
val inside {8'h00, 8'hFF} // 1 if val == 0 or val == 255
// ── Range [lo:hi] — closed, inclusive ────────────────────────────
val inside {[8'h10:8'h1F]} // 1 if 0x10 ≤ val ≤ 0x1F
// ── Mixed: values + ranges ────────────────────────────────────────
val inside {8'h00, [8'h10:8'h1F], 8'hFF}
// ── Array membership ──────────────────────────────────────────────
logic [7:0] legal_ops[4] = '{8'h10, 8'h20, 8'h30, 8'h40};
val inside {legal_ops} // tests val against all 4 elements
// ── Negation: NOT inside ──────────────────────────────────────────
!(val inside {[8'h10:8'h1F]}) // 1 if val is outside the range
// ── In an if statement ────────────────────────────────────────────
if (opcode inside {8'h10, 8'h20, 8'h30})
$display("Legal opcode");
// ── In an assign (procedural equivalent) ─────────────────────────
logic is_legal;
assign is_legal = val inside {[8'h00:8'h0F], [8'hF0:8'hFF]};
// ── In a constraint block ─────────────────────────────────────────
// constraint legal_addr { addr inside {[32'h0000:32'h0FFF],
// [32'hF000:32'hFFFF]}; }
// ── In an SVA assertion ───────────────────────────────────────────
// assert property (@(posedge clk)
// cmd_valid |-> cmd_opcode inside {4'h1, 4'h2, 4'h4, 4'h8});| Member form | What it tests | Example |
|---|---|---|
value | val ==? value | inside {8'h10} |
[lo:hi] | val >= lo && val <= hi | inside {[0:255]} |
| Array variable | val ==? arr[0] || val ==? arr[1] || ... | inside {my_arr} |
| Mixed | OR of all individual tests | inside {0, [2:5], 8} |
Visual Evaluation — What Matches and What Doesn't
Set Membership Evaluation Table
Expression: val inside {8'h00, [8'h10:8'h13], 8'hFF}
| val | Matches 8'h00? | Matches [10:13]? | Matches 8'hFF? | inside result |
|---|---|---|---|---|
8'h00 | Yes | No | No | 1 |
8'h0F | No | No | No | 0 |
8'h10 | No | Yes (lo bound) | No | 1 |
8'h12 | No | Yes (in range) | No | 1 |
8'h13 | No | Yes (hi bound) | No | 1 |
8'h14 | No | No (just outside) | No | 0 |
8'hFE | No | No | No | 0 |
8'hFF | No | No | Yes | 1 |
X/Z Wildcard Behavior in Set Members
Because inside uses ==? internally, X and Z bits in set members act as don't-cares. A set member of 4'b1X1X matches any 4-bit value where bit 3 = 1 and bit 1 = 1, regardless of bits 2 and 0.
| val | Set member | Bits compared | Match? | Reason |
|---|---|---|---|---|
4'b1010 | 4'b1X1X | bits 3,1 only (X=don't-care) | Yes | bit3=1✓ bit1=1✓ |
4'b1110 | 4'b1X1X | bits 3,1 only | Yes | bit3=1✓ bit1=1✓ |
4'b0010 | 4'b1X1X | bits 3,1 only | No | bit3=0 ≠ 1 |
4'b1001 | 4'b1X1X | bits 3,1 only | No | bit1=0 ≠ 1 |
4'bXX10 | 4'b1X1X | bits 3,1 — val has X | X (unknown) | val bit3=X: result unknown |
Code Examples — From Basic Checks to Constraint Solving
Example 1 — Beginner: Procedural Set Membership
module tb_inside_basic;
logic [7:0] val;
initial begin
// ── Single values ─────────────────────────────────────────────
val = 8'hFF;
$display("FF in {00,FF} = %0b", val inside {8'h00, 8'hFF}); // 1
val = 8'hAB;
$display("AB in {00,FF} = %0b", val inside {8'h00, 8'hFF}); // 0
// ── Range ─────────────────────────────────────────────────────
val = 8'h12;
$display("12 in [10:1F] = %0b", val inside {[8'h10:8'h1F]}); // 1
val = 8'h20;
$display("20 in [10:1F] = %0b", val inside {[8'h10:8'h1F]}); // 0
// ── Mixed set ─────────────────────────────────────────────────
val = 8'h30;
$display("30 in {00,[10:1F],30,FF} = %0b",
val inside {8'h00, [8'h10:8'h1F], 8'h30, 8'hFF}); // 1
val = 8'h31;
$display("31 in {00,[10:1F],30,FF} = %0b",
val inside {8'h00, [8'h10:8'h1F], 8'h30, 8'hFF}); // 0
// ── Negation ──────────────────────────────────────────────────
val = 8'h50;
$display("50 NOT in [10:1F] = %0b", !(val inside {[8'h10:8'h1F]})); // 1
// ── Array membership ──────────────────────────────────────────
logic [7:0] opcodes[3] = '{8'h10, 8'h20, 8'h30};
val = 8'h20;
$display("20 in opcodes[] = %0b", val inside {opcodes}); // 1
val = 8'h40;
$display("40 in opcodes[] = %0b", val inside {opcodes}); // 0
$finish;
end
endmoduleExpected output:
FF in {00,FF} = 1
AB in {00,FF} = 0
12 in [10:1F] = 1
20 in [10:1F] = 0
30 in {00,[10:1F],30,FF} = 1
31 in {00,[10:1F],30,FF} = 0
50 NOT in [10:1F] = 1
20 in opcodes[] = 1
40 in opcodes[] = 0Example 2 — Intermediate: Opcode Decoder Using Inside
// Instruction set: opcodes are grouped by class
// ALU ops: 8'h10 – 8'h1F
// Load/Store: 8'h20 – 8'h2F
// Branch: 8'h30, 8'h31, 8'h32
// NOP: 8'h00
module tb_opcode_decoder;
logic [7:0] opcode;
function automatic void decode(input logic [7:0] op);
if (op inside {[8'h10:8'h1F]}) $display("0x%02h → ALU operation", op);
else if (op inside {[8'h20:8'h2F]}) $display("0x%02h → Load/Store", op);
else if (op inside {8'h30, 8'h31, 8'h32}) $display("0x%02h → Branch", op);
else if (op == 8'h00) $display("0x%02h → NOP", op);
else $display("0x%02h → ILLEGAL opcode", op);
endfunction
initial begin
decode(8'h00); // NOP
decode(8'h15); // ALU
decode(8'h1F); // ALU (hi bound)
decode(8'h20); // Load/Store (lo bound)
decode(8'h31); // Branch
decode(8'hAB); // ILLEGAL
$finish;
end
endmoduleExpected output:
0x00 → NOP
0x15 → ALU operation
0x1F → ALU operation
0x20 → Load/Store
0x31 → Branch
0xAB → ILLEGAL opcodeExample 3 — Verification: Constrained-Random with Inside
// AXI transaction: constrain address to valid memory map regions
class axi_transaction;
rand logic [31:0] addr;
rand logic [7:0] burst_len;
rand logic [1:0] burst_type;
// Only generate addresses within valid memory map regions
constraint valid_addr {
addr inside {
[32'h0000_0000:32'h0000_FFFF], // BOOT ROM
[32'h1000_0000:32'h1FFF_FFFF], // SRAM
[32'hC000_0000:32'hCFFF_FFFF] // Peripheral APB
};
}
// Burst length: short (1-4) or page-aligned (8, 16)
constraint valid_burst {
burst_len inside {[8'd1:8'd4], 8'd8, 8'd16};
}
// Burst type: FIXED or INCR only (not WRAP for this test)
constraint burst_type_c {
burst_type inside {2'b00, 2'b01}; // FIXED=00, INCR=01
}
// Scoreboard checker: validate received transaction
function void check_legal();
if (!(addr inside {[32'h0000_0000:32'h0000_FFFF],
[32'h1000_0000:32'h1FFF_FFFF],
[32'hC000_0000:32'hCFFF_FFFF]}))
$error("ILLEGAL addr = 0x%08h", addr);
if (!(burst_len inside {[8'd1:8'd4], 8'd8, 8'd16}))
$error("ILLEGAL burst_len = %0d", burst_len);
endfunction
endclass
module tb_constraint_inside;
initial begin
axi_transaction txn = new();
repeat(5) begin
void'(txn.randomize());
$display("addr=0x%08h burst_len=%0d burst_type=%02b",
txn.addr, txn.burst_len, txn.burst_type);
txn.check_legal();
end
$finish;
end
endmoduleExample 4 — Corner Case: X/Z in Value and Wildcard Members
module tb_inside_xz;
logic [3:0] val;
initial begin
// ── Wildcard member: X bits are don't-cares ────────────────────
// 4'b1X1X matches any value where bit3=1 and bit1=1
val = 4'b1010;
$display("1010 inside {1X1X} = %0b", val inside {4'b1X1X}); // 1
val = 4'b1110;
$display("1110 inside {1X1X} = %0b", val inside {4'b1X1X}); // 1
val = 4'b0010;
$display("0010 inside {1X1X} = %0b", val inside {4'b1X1X}); // 0 (bit3=0)
val = 4'b1001;
$display("1001 inside {1X1X} = %0b", val inside {4'b1X1X}); // 0 (bit1=0)
// ── X in the tested value ─────────────────────────────────────
val = 4'bXX10;
$display("XX10 inside {1X1X} = %0b", val inside {4'b1X1X}); // X (bit3 unknown)
// ── Clean value, wildcard member covers all possibilities ─────
// 4'bXXXX as a member matches EVERYTHING — acts as a wildcard
val = 4'hA;
$display("0xA inside {XXXX} = %0b", val inside {4'bXXXX}); // 1
// ── Range endpoint with X: undefined behavior — avoid ─────────
// inside {[4'bXX00:4'b1111]} — bounds contain X, result is X
// Best practice: always use clean (non-X) range bounds
$finish;
end
endmoduleExpected output:
1010 inside {1X1X} = 1
1110 inside {1X1X} = 1
0010 inside {1X1X} = 0
1001 inside {1X1X} = 0
XX10 inside {1X1X} = x
0xA inside {XXXX} = 1Simulation Behavior and Synthesis Considerations
What the Simulator Actually Does
In simulation, val inside {set} is evaluated left-to-right. For each set member, it computes val ==? member. As soon as one test returns 1, the result is 1 and evaluation stops. If all tests return 0, the result is 0. If any test returns X (due to X/Z in val or members) and no earlier test returned 1, the result may be X.
| Scenario | Result | Reason |
|---|---|---|
| val is clean, member is clean, val matches | 1 | Normal equality match |
| val is clean, no member matches | 0 | All tests failed |
| val has X bits, no member has X | X | Wildcard rule: X in val propagates |
| member has X bits (don't-care), val's non-X bits match | 1 | X in member = don't-care, non-X bits agree |
| val has X bits, and a member with matching X pattern exists | 1 | ==? returns 1 when X bits align as don't-cares |
| Range bounds are X | X | Range comparison with X bound is undefined |
Synthesis Support
| Context | Synthesis support | Notes |
|---|---|---|
assign / combinational RTL | Tool-dependent — not all tools support it | Expand to explicit comparisons for safety: (val >= lo && val <= hi) || ... |
| SVA assertion | Fully supported | Standard use case — inside reads naturally in property expressions |
| Constraint block | Fully supported (verification-only) | Native to the constraint solver — preferred form for range membership |
Procedural if / case | Simulation only | Fine for testbench, not synthesized |
Where You'll Use This in Real Projects
// ── 1. CONSTRAINT: address alignment + region filter ─────────────
constraint addr_map {
addr inside {[32'h0000_0000:32'h0FFF_FFFF], // DDR
[32'hA000_0000:32'hA000_FFFF]}; // MMIO
addr[1:0] == 2'b00; // 4-byte aligned
}
// ── 2. SCOREBOARD: validate DUT response code ─────────────────────
function void check_resp(input logic [1:0] resp);
if (!(resp inside {2'b00, 2'b01})) // OKAY or EXOKAY only
$error("Unexpected AXI response: %02b", resp);
endfunction
// ── 3. SVA ASSERTION: legal state transitions ─────────────────────
// assert property (@(posedge clk) disable iff (rst)
// state_valid |-> next_state inside {ST_IDLE, ST_ACTIVE, ST_DRAIN});
// ── 4. COVERAGE: conditional sample based on membership ───────────
// covergroup cg_burst;
// cp_len: coverpoint burst_len {
// bins short = {[1:4]};
// bins medium = {[5:15]};
// bins long = {[16:255]};
// }
// endgroup
// ── 5. DRIVER: select stimulus type based on address region ───────
function string get_region(input logic [31:0] a);
if (a inside {[32'h0000_0000:32'h0FFF_FFFF]}) return "DDR";
else if (a inside {[32'hA000_0000:32'hAFFF_FFFF]}) return "MMIO";
else return "UNMAPPED";
endfunction
// ── 6. PROTOCOL CHECKER: illegal burst type for region ────────────
// AXI rule: WRAP bursts only allowed in cacheable region
if (burst_type == 2'b10 && !(addr inside {[32'h1000_0000:32'h1FFF_FFFF]}))
$error("WRAP burst to non-cacheable address 0x%08h", addr);Bugs Engineers Actually Hit
Bug 1 — Range Bounds Reversed: Empty Range
logic [7:0] val = 8'h15;
// BUGGY: lo > hi — the range [8'h1F:8'h10] is empty in most tools
// No value can satisfy hi ≥ val ≥ lo when lo > hi
if (val inside {[8'h1F:8'h10]}) // silently returns 0 always
$display("Inside");
// Waveform: 'Inside' never prints, even for val=8'h15
// FIXED: low bound first
if (val inside {[8'h10:8'h1F]}) // correct: [lo:hi]
$display("Inside"); // now prints for 0x15Bug 2 — Unsigned Range With Signed Variable
logic signed [7:0] sval = -1; // 8'hFF in two's complement
// Engineer expects: -1 is NOT inside [0:127]
// But the range bounds 0 and 127 are treated as unsigned in inside{}
// -1 = 8'hFF = 255 unsigned
// 255 is NOT in [0:127] → result is 0 — happens to be correct here
if (sval inside {[8'sh00:8'sh7F]})
$display("In positive range");
// TRAP: what about -1 inside [-128:0] (negative range)?
// -128 = 8'b1000_0000 = 8'h80 unsigned (128)
// -1 = 8'b1111_1111 = 8'hFF unsigned (255)
// Range [8'sh80:8'sh00] interpreted as [128:0] unsigned — EMPTY! (128 > 0)
if (sval inside {[8'sh80:8'sh00]}) // BUGGY: empty range
$display("In negative range"); // never prints!
// CORRECT: for signed ranges, ensure comparison context is signed
// Use explicit comparisons when mixing signed values with inside ranges
if ($signed(sval) >= -128 && $signed(sval) <= -1)
$display("Negative value confirmed"); // correct approach for signedBug 3 — Inside in Constraint vs. Procedural: Different Semantics
// In a CONSTRAINT block, inside uses == (exact equality, no wildcard)
// In PROCEDURAL code, inside uses ==? (wildcard equality)
// This is a subtle but important difference
class pkt;
rand logic [3:0] val;
// In constraint: inside uses == — X in member is NOT a don't-care
// Writing {4'b1X10} in a constraint means the literal value with X bits
// The solver cannot randomize to X/Z, so this effectively matches nothing
constraint bad_c { val inside {4'b1X10}; } // ISSUE: no valid 2-state value matches
endclass
// Procedural context — X member IS a wildcard
logic [3:0] check_val = 4'b1010;
$display("proc: 1010 inside {1X10} = %0b",
check_val inside {4'b1X10}); // 1 — wildcard: bit2=X is don't-care
// CORRECT for constraint: list all explicit values or use a range
class pkt_fixed;
rand logic [3:0] val;
// Want: all 4-bit values where bit3=1 and bit1=1 (i.e., 4'b1X1X pattern)
// Enumerate them explicitly: 1010, 1011, 1110, 1111
constraint good_c { val inside {4'b1010, 4'b1011, 4'b1110, 4'b1111}; }
endclassBug 4 — Expecting inside to Work as Synthesizable RTL
// BUGGY: using inside in an assign — synthesis may fail or warn
module decoder (
input logic [7:0] opcode,
output logic is_alu
);
// May synthesize with Synopsys DC 2019+ but not older tools
assign is_alu = opcode inside {[8'h10:8'h1F]}; // RISKY
endmodule
// CORRECT: expand to explicit comparison — portable across all tools
module decoder_safe (
input logic [7:0] opcode,
output logic is_alu
);
assign is_alu = (opcode >= 8'h10) && (opcode <= 8'h1F); // always synthesizable
endmoduleBug 5 — Constraint Contradiction: Inside + Other Constraint
class bad_txn;
rand logic [7:0] burst_len;
// Constraint A: burst length in {1,2,3,4,8,16}
constraint len_set { burst_len inside {[8'd1:8'd4], 8'd8, 8'd16}; }
// Constraint B: burst length must be > 16 (added later, contradicts A)
constraint len_min { burst_len > 16; }
// randomize() will FAIL — no value satisfies both constraints
// $error: "Randomization failed"
endclass
module tb_constraint_debug;
initial begin
bad_txn t = new();
if (!t.randomize())
$error("Randomize failed — check for constraint contradiction");
// DEBUG: disable one constraint to isolate the conflict
t.randomize() with { burst_len inside {[8'd1:8'd4]}; };
// Works → confirms len_min was the conflicting constraint
$finish;
end
endmoduleInterview Questions
Beginner Level
Q1: What does val inside {[4:7]} return for val=4, val=7, val=8? val=4 → 1 (lower bound is inclusive). val=7 → 1 (upper bound is inclusive). val=8 → 0 (just outside the range). Both bounds are always included. Q2: How do you test that a value is NOT in a set? Wrap the expression in !(): !(val inside {[0:15]}). There is no !inside keyword in SystemVerilog — the negation is applied to the result of the inside expression.
Intermediate Level
Q3: What comparison operator does inside use internally for each set member? The wildcard equality operator ==?. This means X and Z bits in set members act as don't-cares — a member of 4'b1X1X matches any value where bits 3 and 1 are 1, regardless of bits 2 and 0. This is different from the standard equality == used in constraint blocks. Q4: Can you use a variable as a range bound in an inside expression? Yes, range bounds can be runtime expressions: val inside {[lo_bound:hi_bound]} where lo_bound and hi_bound are variables is legal in simulation and constraints. If the bounds are X, the result is X. If lo > hi at runtime, the range is empty — the member never matches.
Experienced Engineer Level
Q5: Why might a constraint using inside produce a different distribution than an equivalent OR chain of comparisons? The constraint solver treats inside as a first-class set membership constraint, allowing it to compute the total legal value set and distribute uniformly across it. An OR chain of comparisons requires the solver to reason about a compound boolean — some solvers may weight clauses differently or use heuristics that bias the distribution. For example, inside {[0:3], [252:255]} gives uniform probability across all 8 values. An equivalent OR comparison may over-weight whichever clause the solver processes first. Use inside in constraints when uniform distribution over the set is required. Q6: A signed 8-bit variable holds -1. Does it match inside {[8'sh80:8'sh7F]}? No — and the reason is subtle. 8'sh80 = -128 in signed, but its bit pattern is 8'hFF - 0x7F = 8'h80 = 128 unsigned. 8'sh7F = +127 unsigned. The range comparison is performed on the unsigned bit patterns: [128:127]. Since 128 > 127, this is an empty range — nothing ever matches. This is the signed/unsigned range trap. Use explicit $signed(val) >= -128 && $signed(val) <= 127 for signed range checks.
Best Practices & Coding Guidelines
- Always put lo ≤ hi in ranges — Reversed bounds [hi:lo] produce an empty range with no error — the inside expression quietly returns 0. Always verify bound order in code review.
- Use inside in constraints, not OR chains — The constraint solver distributes uniformly across the inside set. Equivalent OR-comparison constraints may have biased distributions depending on the solver implementation.
- Avoid inside in synthesizable RTL — Expand to explicit comparisons for portability. The synthesized hardware is identical — just a comparator chain — but the RTL compiles cleanly across all tool versions.
- Avoid signed variables with inside ranges — Inside range comparisons use unsigned arithmetic on bit patterns. Negative values have large unsigned patterns. Use explicit $signed() comparisons for signed range checks.
| Task | Preferred approach | Avoid |
|---|---|---|
| Constrain rand var to specific values + ranges | rand_var inside {v1, [lo:hi], v2} | Long OR chain of equality constraints |
| Check opcode class in testbench | if (op inside {[8'h10:8'h1F]}) | Chained if(op==10||op==11||...) |
| Check opcode class in RTL assign | (op >= 8'h10) && (op <= 8'h1F) | op inside {[8'h10:8'h1F]} — synthesis risk |
| Check signed range | $signed(val) >= lo && $signed(val) <= hi | val inside {[lo:hi]} — unsigned comparison |
| Constraint for multiple non-contiguous ranges | inside {[r1lo:r1hi], [r2lo:r2hi]} | Separate constraints per range — harder to maintain |
Summary
The inside operator is where SystemVerilog's verification heritage shows. It exists because writing exhaustive value-range checks as OR chains is error-prone and biases constraint distributions. A single inside {[0:15], 32, 64} is cleaner, solver-friendly, and far easier to maintain when the legal set changes.
- Range bounds are inclusive, low-first.
[lo:hi]includes both endpoints. Reversed bounds give an empty range with no error. - Set members use
==?internally. X/Z in members are don't-cares. In procedural code, X/Z in the tested value may produce an X result. In constraint blocks,insideuses==— X members match nothing randomizable. - Signed variables need special care. Inside range comparisons are unsigned on bit patterns. Use explicit
$signed()comparisons for signed arithmetic ranges. - Not reliably synthesizable. Use in assertions, constraints, and testbench procedural code. Expand to comparator chains in RTL
assignstatements. - Constraint solver treats it as a first-class construct. Prefer it over OR-equality chains in constraints for uniform distribution and solver performance.