05 · Semantic scalar types
Encode legal state spaces instead of memorizing raw numbers.
Enums, strings, time, real-valued types, and forward declarations improve intent when their conversion and tool-boundary rules are made explicit.
Typed state transition
Raw bits enter through a validation gate
- →
- →
- →
- 01Raw encodinglogic [1:0]
Protocol or register representation.
- 02Explicit caststate_t'(raw)
Makes the representation change visible.
- 03Membership checkinside {IDLE, BUSY, DONE}
Rejects unnamed encodings.
- 04Named behaviorstate.name()
Readable control and diagnostics.
Protocol or register representation.
- Enum default
- Base-type default
- It is not automatically the first declared label.
- byte
- Signed by default
- 8'hFF interpreted as byte is -1 unless declared unsigned.
- real
- Not packed
- It cannot be a member of a packed union or packed struct.
A type name improves readability only when illegal raw encodings remain observable and checked.
Observe
Identify values that form a closed named set, text that is testbench-only, time units that can be rounded, and types that require forward references.
Decide
Use enums for legal states, strings for dynamic text, time/realtime for temporal models, and typedef-forward declarations to break declaration-order dependencies.
Failure
An unchecked cast can create an unnamed enum value, byte signedness can turn 255 into -1, and time precision can silently round a delay.
Prove
Use inside or name-based checks, specify timeunit/timeprecision, test boundary characters, and keep real/string use outside synthesizable bit-level interfaces.
Enums are constraints with names
An enum defines a base integral type and a set of labels. Automatic progression, explicit encodings, .first, .last, .next, .prev, .num, and .name improve models, but a cast can still create a base value without a matching label.
typedef enum logic [1:0] {
IDLE = 2'b00,
BUSY = 2'b01,
DONE = 2'b10
} state_t;
logic [1:0] raw_state;
state_t state;
always_comb begin
state = state_t'(raw_state);
assert (state inside {IDLE, BUSY, DONE})
else $error("illegal state %b", raw_state);
endStrings and special types belong at deliberate boundaries
A string is a dynamic sequence of bytes with methods for length, comparison, conversion, case, and formatting. time is a 64-bit unsigned four-state integral type; realtime is real-valued. shortreal and real model floating values but are not packed hardware fields.
- Specify timeunit and timeprecision in each reusable scope so literal delays have a stable meaning.
- Use $sformatf for constructed diagnostics and explicit ato*/itoa-style conversions when text crosses into numbers.
- Use chandle for opaque foreign handles and void for intentionally discarded expression results.
- Forward typedefs allow mutually referential class declarations; parameterized classes still need compatible specialization.
| Type | Strength | Boundary risk |
|---|---|---|
| enum | Named legal states | Cast can create unnamed value |
| string | Dynamic text and methods | Encoding and synthesis support |
| time | Integral simulation time | Unit and precision rounding |
| realtime / real | Fractional models | Not a packed bit representation |
| chandle | Opaque DPI handle | Lifetime and ownership live outside SV |
| void | Discarded result / no return value | A task itself has no return type |
Events and forward declarations are handle-level promises
An event variable is a handle to a synchronization object. Assigning one event variable to another aliases that object, so a trigger through either handle wakes waiters on the shared event. A forward class typedef similarly introduces only a class type name: it permits handle declarations before the full class, but not construction, member access, or assumptions about an incomplete definition.
- Event assignment does not copy a pulse or create history; both handles refer to the same event object from that point forward.
- A transient -> trigger can still be missed by a waiter that starts later; use a persistent protocol when delivery must survive timing.
- A forward declaration must be resolved by a compatible full declaration in the same scope, including matching parameterization where applicable.
- Before the full class definition, use the incomplete type only where a class handle type is sufficient.
typedef class packet_node;
event data_ready;
event ready_alias = data_ready;
packet_node pending; // a handle is legal before the full definition
initial fork
begin @data_ready; $display("shared event observed"); end
begin #1 -> ready_alias; end
join
class packet_node;
packet_node next;
int payload;
endclassExplain it out loud
Interview reasoning checkpoints
A raw integral value generally requires an explicit cast, and the cast does not guarantee the value matches a named label. Validate membership after untrusted inputs.
- The enum base type defines representation.
- The label set defines intended legal values.
- A cast bridges representation but does not prove legality.
Mention both type safety and the possibility of unnamed encodings.
Assuming the enum variable always contains one of its declared labels.
No. Its default comes from the enum's base type: X for a four-state base or zero for a two-state base. That value may or may not correspond to the first label.
- Initialization follows the representation type.
- Declaration order does not override the base-type default.
- Reset or initialize state explicitly and assert legal state.
This tests whether the candidate separates enum naming from storage semantics.
Relying on the first declared label as implicit reset state.
