Types and Widths
Overview
JZ-HDL enforces precise bit widths everywhere. Sizes are explicit (no unsized literals) and most binary operators require equal operand widths. When widths differ, explicit extension modifiers (zero/sign) or intrinsic operators must be used. Unknown (x) and tri‑state (z) bits propagate with strict rules: they may never reach observable sinks unless provably masked.
Literals
Syntax
A sized literal has form:
<width>'<base><value>- width: positive integer,
CONSTname, orCONFIG.<name>(compile‑time constant) - base: one of
b,d,h - value: digits (underscores
_allowed for readability, not leading/trailing)
Examples:
text
8'b1010_0001
16'hFF00
12'd4095Bases and allowed digits
b(binary): digits0,1,x,z, and_(underscores ignored)d(decimal): digits0–9,_only (nox/z)h(hex): digits0–9,A–F/a–f,_only (nox/z)
Unsized literals (e.g., 'hFF) are illegal and cause compile errors. Bare decimal integers (e.g., 1, 42) are also unsized and are not permitted in runtime expressions — use a sized literal (1'b1, 8'd42) or lit(width, value). Bare integers remain valid in compile-time contexts such as CONST/CONFIG values, width brackets, slice indices, and CASE labels.
Intrinsic bit‑width and extension
- Intrinsic width:
- Binary: number of digits (excluding
_) in the value field - Decimal/Hex: minimal bits needed to represent the integer (value 0 → width 1)
- Binary: number of digits (excluding
- If intrinsic width is less than declared width, the literal is extended based on the MSB digit of the intrinsic value:
- MSB =
0or1: zero‑extend (pad with0) - MSB =
x: extend withxbits - MSB =
z: extend withzbits (binary only)
- MSB =
- If intrinsic width exceeds declared width → compile error (overflow).
Examples:
text
4'bx // intrinsic 1 → extends to 4'bxxxx
8'hF // intrinsic 4 → zero-extended to 8'h0F
25'h1FFFFFF // intrinsic 25 → fits; OK
25'h3FFFFFF // intrinsic 26 -> ERROR: overflowx and z semantics in literals
x: intentional don't‑care; allowed only in binary literals.- Any expression containing
xbits is considered x‑dependent. - X‑dependent values may only be used if every
xbit is provably masked before reaching any observable sink.
- Any expression containing
z: high‑impedance (tri‑state). Allowed only in binary literals.- Tri‑state bits are permitted for INOUT/tri‑state uses, but nets read must have at least one active (0/1) driver for each bit.
Forbidden uses:
- Register reset literals and MEM initializers must not contain
xorz. x/zdigits used inhordliterals → compile error.
Bit‑Width Constraints
Strict matching rule
Most binary operators and many constructs require identical operand widths. Violations produce compile errors.
Operators requiring equal widths include:
- Arithmetic:
+,-(binary) - Bitwise:
&,|,^ - Comparisons:
==,!=,<,>,<=,>= - Equality/relational operators
- Many intrinsics where specified (exceptions below)
Extension modifiers
When you intentionally connect signals of different widths, use explicit modifiers so the compiler and synthesizer know how to extend bits:
Operator suffixes (applied to =, =>, <=):
z— zero‑extend the narrower sides— sign‑extend the narrower side
Valid forms:
a =z b; // alias: b zero-extended to a's width, then aliased
a <=s b; // receive with sign-extend
driver =>z sink;Rules:
- If widths are equal, bare operator is fine;
z/sare allowed but redundant. - If widths differ and no modifier is present → compile error.
- Truncation is never implicit. If RHS wider than LHS → compile error.
- Alias operators (
=family) may not have a bare literal RHS (use directional assign to drive constants).
Concatenation & decomposition
- Concatenation
{a, b, c}produces width = sum(width(a), width(b), ...) - When assigning to a concatenation, the RHS expression width must equal the concatenation width, unless
=z/=smodifier is used to extend the RHS. - In synchronous assignments the decomposition
{r_hi, r_lo} <= expr;distributes MSB→first element.
Operator Width Rules
Summary (result width relative to operand widths):
- Unary arithmetic (
-,+): only on width‑1 operands; result width = 1 - Binary add/sub (
+,-): operands must match; result width = operand width (overflow truncated) - Multiply (
*): operands must match; result width = 2 * width (full product) - Divide/Modulus (
/,%): operands must match; result width = dividend width; divisor = 0 at compile time → error - Bitwise (
&,|,^,~): operands must match; result width = operand width - Logical (
&&,||,!): operands width‑1; result width = 1 - Comparison (
==,!=,<,>,<=,>=): operands must match; result width = 1 - Shift (
<<,>>,>>>): LHS width retained - Ternary
cond ? a : b: cond width‑1; a and b widths must match; result = operand width - Concatenation
{}: result width = sum of inputs
Important notes:
- Add/Sub truncate silently — to capture carry extend operands or use uadd/sadd.
- Many intrinsics handle width extension automatically (see Intrinsics section).
Special Operators & Intrinsics (width behavior)
- uadd(a,b): unsigned add; result width = max(width(a),width(b)) + 1 (zero‑extend operands)
- sadd(a,b): signed add; result width = max(width(a),width(b)) + 1 (sign‑extend operands)
- umul(a,b): unsigned multiply; result width = 2 * max(width(a),width(b)) (zero‑extend)
- smul(a,b): signed multiply; result width = 2 * max(width(a),width(b)) (sign‑extend)
- gbit(source,index): returns 1 bit; index must be wide enough (>= clog2(width(source))); out‑of‑range at runtime → returns 0
- sbit(source,index,set): returns full source width with one bit updated; source must be a wire or register
- gslice/sslice: dynamic slice/overwrite with compile‑time constant slice width; index bounds behavior defined (out‑of‑range bits → 0 / ignored)
Use intrinsics when you need automatic, safe extension or dynamic indexing semantics.
lit(width, value) — compile-time integer literal
- Usage:
lit(width, value) - Materializes a compile-time integer into a sized, runtime-legal bit-vector
- Both width and value must be compile-time nonnegative integer expressions (may reference
CONST,CONFIG,clog2(),widthof()) - The value is zero-extended to the given width
lit()is valid in ASYNCHRONOUS RHS, SYNCHRONOUS RHS, REGISTER reset values, and@globalblockslit()is not valid in width brackets, CONFIG blocks, CONST blocks, or OVERRIDE expressions
text
// Compare a register to a computed constant
IF (count == lit(ADDR, WIDTH - 1)) { ... }
// Use as a register reset value
REGISTER {
limit [ADDR] = lit(ADDR, WIDTH - 1);
}
// Errors:
lit(0, 5) // ERROR: width must be >= 1
lit(4, -1) // ERROR: value < 0
lit(4, 16) // ERROR: overflow (16 >= 2^4)
CONST { X = lit(4,3); } // ERROR: not a compile-time integerOperator precedence (highest to lowest)
- Parentheses
( ) - Concatenation
{ } - Unary NOT
~! - Unary arithmetic
-+ */%<<>>>>>- Binary
+- - Relational
<><=>= - Equality
==!= - Bitwise
& - Bitwise
^ - Bitwise
| - Logical
&& - Logical
|| - Ternary
? :
Compile‑time helpers
clog2(value)
- Usage:
clog2(expr) - Evaluated at compile time; returns smallest bit count >= value (ceil(log2(value)))
clog2(1)evaluates to1- Argument must be a positive integer constant (CONST, CONFIG, or literal)
- Typical use: address widths for memories, selector widths for MUX
widthof(identifier)
- Usage:
widthof(identifier) - Returns the declared static bit width of a BUS or local WIRE or REGISTER
- Compile‑time only; name must be module‑local, visible, and resolvable at the point of use
- Not usable at run time inside ASYNCHRONOUS/SYNCHRONOUS expressions
CONST vs CONFIG vs @global
- CONST: module‑local integer constants, usable in widths and compile‑time contexts
- CONFIG: project‑wide integer constants accessed as
CONFIG.NAME, compile‑time only - @global: named sized literals (bit patterns) visible across modules and usable at run time in expressions
Remember: CONST/CONFIG are compile‑time integers; @global entries are sized value literals usable in signal expressions.
Special Semantic Drivers
To ensure electrical clarity and prevent manual bit-width matching errors for common constants, JZ-HDL defines two reserved semantic drivers: GND (Logic 0), VCC (Logic 1).
- Polymorphic Expansion: Special drivers automatically expand to match the bit-width of the target identifier, satisfying the "No Implicit Width Conversion" rule by being width-agnostic by definition. The width of a special driver is determined solely by the width of the driven target, never by surrounding syntax.
- Atomic Assignment: Special drivers are valid only as standalone assignment tokens.
- Expression Proscription: To prevent ambiguous width inference in math, special drivers cannot be used as operands in arithmetic or logical expressions (e.g.,
GND + 1is illegal).- may not appear in expressions
- may not appear in concatenations
- may not be sliced or indexed
- may not appear in slice or index
- Initialization: Special drivers are permitted as reset values in register declarations.
Target Constraints:
GNDandVCCmay drive any valid sink (wire, port, register, latch, etc.).
Driver Interaction:
GNDandVCCdrivers participate fully in the Exclusive Assignment Rule.- Driving a signal with both a
GNDorVCCsemantic driver and any other driver in the same execution path is illegal.
Common errors and how to fix them
- Unsized literal (e.g.,
'hFF): add width, e.g.,8'hFF. - Literal overflow: increase declared width or fix value to fit.
- Operator width mismatch: make operand widths equal via explicit extension (
=z,=s, concatenation, intrinsics) or adjust declarations. - Implicit truncation attempt: ensure LHS is wide enough or slice intentionally.
- Using
xorzin register resets / MEM init: replace with concrete 0/1 literal. - Unary
-on multi‑bit: legal only for width‑1; for multi‑bit two's complement negate use subtraction:zero - valor usesaddpatterns. - Division by zero (constant 0): fix divisor; if dynamic divisor may be zero, guard it with IF/ELSE.
Practical examples
Examples with expected behavior:
text
// Valid: zero-extend 8->16 explicitly
wide_reg =z narrow_val; // aliases (ASYNCHRONOUS) or use <=z in SYNCHRONOUS
// Capturing carry safely
{carry, sum} = uadd(a, b); // result width = max(w(a),w(b)) + 1
// Full product
prod = umul(a, b); // if a,b=8 -> prod is 16 bits
// Illegal: unsized literal
data = 'hFF; // ERROR: unsized literal
// Illegal: overflow
4'b10101; // ERROR: intrinsic width 5 > declared 4
// Binary literal with x: allowed but must be masked before observable sink
temp = 4'b10x1; // temp contains x; ensure downstream maskingMemory/address helpers:
text
CONST { DEPTH = 256; }
ADDR_W = clog2(DEPTH); // ADDR_W = 8widthof use:
text
WIRE { bus [32]; }
CONST { W = widthof(bus); } // valid compile-time query inside same moduleQuick checklist
- Always size literals: use the
width'base_valueform. - Match operand widths for strict operators or use explicit extension (z/s).
- Do not rely on implicit truncation — the compiler forbids it.
- Avoid
x/zreaching registers, MEM init, OUT/INOUT ports, top pins. - Use intrinsics (uadd, umul, ...) for safe automatic sizing when appropriate.
- Use
clog2()andwidthof()for robust compile‑time sizing. - Ensure unary arithmetic is parenthesized (e.g.,
(-a)).