A static cast is a SystemVerilog feature that converts an expression from one data type to another, using the single-quote (') operator. This is different from $cast — covered in SystemVerilog Cast Dynamic — which performs its check at run time and can fail gracefully. A static cast's type compatibility is checked once, at compile time, with no run-time validity check at all. The conversion itself, though, still runs at simulation time whenever its operand isn't a constant — casting a variable or signal costs the same as any other expression; it's only the type-checking that's free.
9 min read | Intermediate Level
What You'll Learn
- What a static cast is, and how it differs from
$cast - How casting a string to an integer packs its trailing bytes into a fixed-width value
- The real distinction between real-to-integer truncation and bit-width truncation
- Why a static cast doesn't perform any run-time validity checking
Syntax
'(value or variable or expression)
The type being cast to comes immediately before the single quote — for example, int'(expr) — and the value or expression being cast must be in parentheses.
Static Cast vs $cast
Static cast (type'(expr)) | $cast | |
|---|---|---|
| When checked | Compile time | Run time |
| On an invalid conversion | Silently produces whatever the reinterpreted bits happen to mean — no error | Runtime error (as a task) or returns 0 (as a function) |
| Performance impact | No run-time cost for the type check; the conversion itself still runs whenever its operand isn't a constant | Run-time overhead for both the validity check and the conversion |
Casting a String to an Integer
A string can be cast to a compatible data type like int:
module tb;
initial begin
$display("data=%0d", int'("Hello World"));
end
endmodule
Output
xcelium> run data=1869769828 xmsim: *W,RNQUIE: Simulation is complete.
This number isn't arbitrary. int is 4 bytes wide, and "Hello World" is 11 characters — too many to fit. Only the last 4 characters, "orld", actually get packed into the result, each character's ASCII code becoming one byte: o=0x6F, r=0x72, l=0x6C, d=0x64, giving 0x6F726C64 — which is exactly 1869769828 in decimal.
Quick Check
Q: What would int'("World") produce?
Show Answer
The exact same value, 1869769828. "World" is 5 characters, so only its last 4 — "orld" — get packed, which happens to be identical to the last 4 characters of "Hello World".
Real-to-Integer Truncation
Casting a computed real expression to int drops the fractional part entirely:
module tb;
initial begin
$display("data=%0.3f", int'(3.4 + 2 * 1.5));
end
endmodule
Output
xcelium> run data=6.000 xmsim: *W,RNQUIE: Simulation is complete.
3.4 + 2 * 1.5 evaluates to 6.4, and int'(...) truncates that down to the integer 6, discarding the fractional part rather than rounding it.
module tb;
int data;
initial begin
data = int'(3.145);
$display("data=%0.3f", data);
end
endmodule
Output
xcelium> run data=3.000 xmsim: *W,RNQUIE: Simulation is complete.
int'(3.145) similarly truncates to 3. It's worth being precise about what's happening here: this is real-to-integer truncation — dropping the fractional part of a real number — which is a different mechanism from truncating the bits of a wide integer value, covered next.
Bit-Width Truncation (Narrowing a Vector)
Casting a wider integer value into a narrower type keeps only its least significant bits, discarding everything above that width:
module tb;
bit [31:0] wide = 32'hDEAD_BEEF;
bit [15:0] narrow;
initial begin
narrow = 16'(wide);
$display ("narrow = 0x%0h", narrow);
end
endmodule
This prints narrow = 0xbeef — only the lower 16 bits of 32'hDEAD_BEEF survive the cast; the upper half (DEAD) is simply discarded, with no warning or error of any kind.
This is the property the previous section's int'(3.145) example doesn't actually demonstrate. Truncating a real number's fractional part and truncating a wide integer's most significant bits are two genuinely different mechanisms — both fall under "truncation" loosely, but they don't behave the same way, and it's easy to conflate the two.
Quick Check
Q: Using the same 32'hDEAD_BEEF value, what would 8'(wide) produce?
Show Answer
0xEF — just the single lowest byte. Narrowing to 8 bits keeps only the lowest 8 bits of the original 32-bit value, discarding the rest.
Other Properties
- Static cast enforces type safety at compile time — it won't allow casting between genuinely incompatible data types (like two unrelated class handles with no relationship to each other).
- It's applicable to both variables and full expressions, not just simple values.
- It's generally described as preserving the signedness of the source value, though this depends on the specific source and target types involved — when in doubt, verify the actual result for your specific case rather than assuming.
Where Is Static Cast Used?
Static cast is commonly used when deliberately packing or narrowing a value into a fixed-width field — preparing data for a DPI call with a specific expected width, or extracting a specific byte or field from a wider computed value — situations where the conversion is known to be intentional and correct, so $cast's run-time checking overhead isn't needed.
Common Beginner Mistakes
Mistake #1: Confusing static cast with $cast
A static cast performs zero run-time checking — an invalid or lossy conversion succeeds silently, with no error and no way to detect failure. Why? If there's a real chance a cast might not be meaningful (like recovering a specific class type from a base handle), $cast is the right tool, since it can actually report failure; a static cast simply reinterprets bits and moves on.
Mistake #2: Assuming int'(3.145) demonstrates bit-width truncation
As explained above, this is real-to-integer truncation — dropping a fractional part — not the same mechanism as narrowing a wide integer value down to fewer bits. Why? The two are easy to conflate since both are casually called "truncation," but they behave differently and apply to different situations.
Mistake #3: Mixing a %f format specifier with a non-real value
%f is meant for real-valued arguments, yet several examples in this article pass an already-cast integer to it. Why? While some simulators display a reasonable-looking result anyway, this isn't guaranteed portable behavior across every tool — match your format specifier to your actual argument's type to avoid relying on simulator-specific leniency.
Mistake #4: Assuming a lossy static cast is always harmless
As the bit-width truncation example shows, a static cast that discards real data does so completely silently. Why? There's no warning when a narrowing cast throws away meaningful bits — it's on the code's author to be certain the narrower type is actually wide enough for the values that will realistically flow through it.
Practice Exercise
Challenge: Predict a String-to-Int Cast
Using the same last-4-characters rule demonstrated in this article, predict the result of int'("Verilog").
Hint
"Verilog" is 7 characters — identify the last 4, convert each to its ASCII hex value, and pack them together the same way "Hello World" packed down to "orld".
Solution
1768714087. The last 4 characters of "Verilog" are "ilog": i=0x69, l=0x6C, o=0x6F, g=0x67, packed as 0x696C6F67 — which is 1768714087 in decimal.
Summary
In this tutorial, you learned:
- A static cast (
type'(expr)) converts a value's type at compile time, with no run-time validity checking — unlike$cast - Casting a string to an integer type packs only as many trailing characters as fit the target width, each becoming one byte
- Real-to-integer casting truncates the fractional part; bit-width narrowing truncates the most significant bits of a wide integer value — two genuinely different mechanisms, both loosely called "truncation"
- A lossy or narrowing static cast succeeds silently, with no warning that data was discarded
- Static cast is best used when a conversion is already known to be correct and intentional, since it has no way to report failure the way
$castcan