Projects
Project overview
A JZ-HDL project (@project ... @endproj) is the chip-level integration unit. It collects:
- project-wide compile-time configuration (CONFIG),
- clock & pin declarations (CLOCKS, IN_PINS, OUT_PINS, INOUT_PINS),
- physical pin mapping (MAP),
- optional blackbox declarations usable anywhere in the project,
- a single root/top instantiation via
@top.
A project:
- defines how modules are bound to board pins and clocks,
- imports module/blackbox definitions from source files,
- provides global configuration values (CONFIG) accessible to all module instantiations as
CONFIG.NAME, - may include
@globalblocks (sized literal constants) that are usable as runtime values across all modules.
Every design must contain exactly one @project block (per compilation unit) and exactly one @top declaration inside it.
Project canonical form
Canonical skeleton:
text
@project(CHIP=<chipid>) <project_name>
@import "<path/to/library.jzhdl>";
CONFIG {
<config_id> = <nonnegative_integer_expression>;
...
}
CLOCKS {
<clock_name> = { period=<ns>, edge=[Rising|Falling] };
}
IN_PINS { ... }
OUT_PINS { ... }
INOUT_PINS { ... }
MAP { ... }
@blackbox <name> { PORT { ... } }
@blackbox ...
@global <global_name>
<const_id> = <width>'<base><value>;
...
@endglob
@top <top_module_name> {
IN [<width>] <port_name> = <pin_expr | _>;
OUT [<width>] <port_name> = <pin_expr | _>;
INOUT [<width>] <port_name> = <pin_expr | _>;
}
@endprojCHIP=<chipid>is optional; defaults toGENERIC. The chip ID is case-insensitive and may be an identifier or string literal.- If
CHIPis notGENERIC, the compiler loads<chipid>.jsonfrom the project directory (falling back to its built-in database). The JSON file describes supported memory configurations, clock generators, and constraints.
Key rules:
@importdirectives (optional) must appear immediately after@projectheader.- One
CONFIGblock maximum;CONFIGnames are visible across modules viaCONFIG.NAME. - All pins declared in IN/OUT/INOUT blocks must be mapped in
MAP. - Exactly one
@topinstantiation must be present; the referenced module or blackbox must be defined in the project or imported.
@import
Purpose
- Import module and blackbox definitions from other .jzhdl source files into the current project namespace.
Placement and rules
- Only valid inside
@project, and must appear immediately after the@projectheader and before CONFIG/CLOCKS/PIN blocks. - Imported files must not contain their own
@project/@endprojpair. - Import paths are resolved and normalized. Re-importing the same resolved file (directly or transitively) is a compile error (IMPORT_FILE_MULTIPLE_TIMES).
- After imports, all module and blackbox definitions form a single project-wide namespace and must be unique.
Semantics
- Modules and blackboxes from imports are available to
@topand@newinstantiations. - Imported definitions are not automatically parameterized; overrides happen at instantiation via
OVERRIDE.
Path normalization
- Import paths use the OS canonical path resolution (POSIX
realpath, Windows_fullpath) to normalize symbolic links,./.., and redundant separators. - On case-insensitive filesystems, canonical resolution normalizes case for correct dedup.
- Diagnostic messages report the original user-supplied path, not the resolved canonical path.
Path security (sandbox)
- All user-specified file paths (
@import,@file()) are subject to sandbox restrictions. - By default: absolute paths and
..traversal are forbidden. Resolved paths must be within the sandbox root (directory of the input file). - CLI flags:
--sandbox-root=<dir>,--allow-absolute-paths,--allow-traversal. - See the formal reference for full details.
Error conditions
- File not found (tool-specific handling)
- Imported file contains
@projectblock → compile error - Duplicate module/blackbox name across imports or local definitions → compile error
- Absolute path without
--allow-absolute-paths→PATH_ABSOLUTE_FORBIDDEN - Path traversal (
..) without--allow-traversal→PATH_TRAVERSAL_FORBIDDEN - Path outside sandbox →
PATH_OUTSIDE_SANDBOX
CONFIG (project-wide constants)
Purpose
- Provide compile-time constants visible in all modules via
CONFIG.NAME.
Syntax
text
CONFIG {
NAME = <nonnegative_integer_expression>; // numeric
NAME = "<string>"; // string
...
}Key properties
- Evaluated at compile time
- Numeric entries: used in compile-time integer contexts (widths, MEM depths, OVERRIDE expressions)
- String entries: used in string contexts such as
@file()path arguments (e.g.,@file(CONFIG.FIRMWARE)) - Not usable as runtime values in ASYNCHRONOUS or SYNCHRONOUS expressions
- No shadowing between CONFIG and module-local CONST (use
CONFIG.NAMEto refer to project-wide values)
Rules & validation
- Only one CONFIG block per project
- Numeric values must be nonnegative integers; expressions may reference earlier CONFIG names (forward references forbidden)
- String values are double-quoted string literals
- Circular dependencies among CONFIG entries → compile error
- Using a string CONFIG where a number is expected →
CONST_STRING_IN_NUMERIC_CONTEXT - Using a numeric CONFIG where a string is expected →
CONST_NUMERIC_IN_STRING_CONTEXT
Common uses
- XLEN, ADDR_WIDTH, feature enables, sizes shared across modules
- File paths for MEM initialization (firmware images, lookup tables)
Error examples
- Using CONFIG in runtime context → CONST_USED_WHERE_FORBIDDEN / CONFIG_USED_WHERE_FORBIDDEN
- Missing CONFIG name referenced in a module instantiation width → CONFIG_USE_UNDECLARED
- String CONFIG used as width → CONST_STRING_IN_NUMERIC_CONTEXT
CLOCKS
Purpose
- Declare named clocks (period and edge) for timing analysis and to validate top‑level clock pin bindings.
Syntax
text
CLOCKS {
clk_name = { period=<positive_number>, edge=[Rising|Falling] };
}Rules
clk_namemust be declared as an input pin (IN_PINS or CLOCKS-matched)- Period is in nanoseconds; must be > 0
- Edge defaults to Rising if omitted
- Names must be unique within CLOCKS
Validation
- Top-level pins referencing clock names must exist and be width
[1] - Duplicate clock name → error
- Period ≤ 0 or invalid edge specifier → error
CLOCK_GEN (clock generators)
Purpose
- Define on-chip clock generation from PLLs, DLLs, or CLKDIVs, deriving new clocks from declared input clocks.
Syntax
text
CLOCK_GEN {
<gen_type> {
IN <input_name> <signal>;
OUT <output_name> <clock_signal>;
WIRE <output_name> <signal>;
...
CONFIG {
<param_name> = <param_value>;
...
}
};
...
}<gen_type> is one of: PLL, DLL, CLKDIV, OSC, BUF, or a numbered variant (e.g., PLL2). Numbered variants represent alternate implementations of the same function — for example, a chip may have both a PLL (integer dividers) and a PLL2 (fractional dividers).
Semantics
<input_name>: The input selector name as defined by the chip data (e.g.,REF_CLK,CE). The bound signal must refer to a clock declared in CLOCKS with a period (or a clock generated by another generator in the same CLOCK_GEN block for chaining).<output_name>: The output selector name as defined by the chip (e.g.,BASE,PHASE,DIV,DIV3,LOCK). The keyword (OUTvsWIRE) must match the output'sis_clockproperty in the chip data.OUTdeclares a clock output: must refer to a clock declared in CLOCKS without a period (the compiler computes it). Must not be declared as an IN_PIN and must not already be driven by another CLOCK_GEN. Only valid for outputs withis_clock: truein the chip data.WIREdeclares a non-clock output (e.g., PLL lock indicator): must not be declared in the CLOCKS block. The compiler automatically declares the signal as a wire in the generated output. Only valid for outputs withis_clock: falsein the chip data (e.g.,LOCK).- CONFIG parameters are chip-specific and validated against the chip's generator definition.
- Not all outputs or parameters need to be used; omitting them is acceptable.
- A single CLOCK_GEN block may contain multiple generators (e.g., a PLL followed by a CLKDIV that divides the PLL output).
Generator types
- PLL (Phase-Locked Loop): Frequency synthesis with VCO, feedback divider, and multiple outputs (BASE, PHASE, DIV, DIV3). Used for generating arbitrary frequencies from a reference clock. Numbered variants (e.g., PLL2) represent alternate PLL implementations on the same chip — for example, a variant with fractional dividers or additional outputs. CONFIG parameters and available outputs are chip-specific.
- DLL (Delay-Locked Loop): Phase alignment without frequency multiplication. Used for clock de-skew.
- CLKDIV (Clock Divider): Simple fixed-ratio frequency division. Divides an input clock by a fixed ratio (e.g., 2, 3.5, 4, 5). No VCO or feedback — simpler and lower-power than PLL. Produces a single BASE output. The
MODECONFIG parameter selects the chip-specific variant (e.g.,localfor IO-logic dividers,globalfor fabric-wide dividers) when multiple variants are available. - OSC (Internal Oscillator): On-chip RC oscillator that generates a clock without any external reference. Does not require an IN clock. The output frequency is determined by chip-specific CONFIG parameters (e.g.,
FREQ_DIV,DEVICE). Produces a single BASE output. - BUF (Clock Buffer): Buffers an input clock onto a global clock network without frequency change. Used to route external clocks through dedicated clock routing resources.
Validation
- Input clock must have a declared period in CLOCKS (or be driven by a prior generator in the same block).
- Output clocks must not have a period in CLOCKS (it is derived).
- VCO frequency (for PLL; derived from reference clock and divider parameters) must be within the chip's valid range.
- CONFIG parameter values must match their declared type: integer parameters reject decimal values (e.g.,
CLKFBOUT_MULT = 5.0is an error; use5), while double parameters accept both (e.g.,CLKFBOUT_MULT_F = 5.125). - CLOCK_GEN chaining is only permitted if the chip explicitly supports it.
PLL example
text
CLOCKS {
sys_clk = { period=37.04 }; // 27 MHz external clock
fast_clk; // derived by PLL (no period)
}
CLOCK_GEN {
PLL {
IN REF_CLK sys_clk;
OUT BASE fast_clk;
WIRE LOCK pll_lock; // non-clock output: PLL lock indicator
CONFIG {
IDIV = 3;
FBDIV = 50;
ODIV = 2;
}
};
}The WIRE LOCK pll_lock line declares the PLL's lock indicator as a wire signal. This signal is typically referenced by differential output pins via the reset attribute (see PIN blocks).
CLKDIV example
text
CLOCKS {
serial_clk = { period=4.0 }; // 250 MHz serial clock
pixel_clk; // 50 MHz pixel clock (serial / 5)
}
CLOCK_GEN {
CLKDIV {
IN REF_CLK serial_clk;
OUT BASE pixel_clk;
CONFIG {
DIV_MODE = 5;
MODE = local;
};
};
}OSC example
text
CLOCKS {
osc_clk; // generated by internal oscillator
}
CLOCK_GEN {
OSC {
OUT BASE osc_clk;
CONFIG {
FREQ_DIV = 10;
};
};
}Chaining PLL + CLKDIV
A common pattern is to use a PLL to synthesize a high-frequency clock, then a CLKDIV to derive a lower-frequency clock from it. Both generators appear in the same CLOCK_GEN block:
text
CLOCKS {
SCLK = { period=37.037 }; // 27 MHz crystal
serial_clk; // 126 MHz (from PLL)
pixel_clk; // 25.2 MHz (serial / 5)
}
CLOCK_GEN {
PLL {
IN REF_CLK SCLK;
OUT BASE serial_clk;
WIRE LOCK pll_lock;
CONFIG {
IDIV = 2;
FBDIV = 13;
ODIV = 8;
};
};
CLKDIV {
IN REF_CLK serial_clk;
OUT BASE pixel_clk;
CONFIG {
DIV_MODE = 5;
};
};
}PIN BLOCKS
Three blocks declare the chip's electrical I/O: IN_PINS, OUT_PINS and INOUT_PINS. Each entry names a logical pin and its electrical properties.
Pin attributes
| Attribute | Required | Values | Default | Description |
|---|---|---|---|---|
standard | Yes (all pins) | See I/O standards below | — | Electrical I/O standard |
drive | Yes (OUT, INOUT) | Integer or fractional (e.g., 8, 3.5) | — | Drive strength in milliamps |
mode | No | SINGLE, DIFFERENTIAL | SINGLE | Single-ended or differential signaling |
width | No | positive integer | 1 | Logical parallel width for differential serializer/deserializer pins |
term | No | ON, OFF | OFF | On-die termination resistor. Always emitted explicitly in constraints (e.g., IN_TERM NONE in XDC when OFF). |
pull | No | UP, DOWN, NONE | NONE | Pull-up/pull-down resistor |
fclk | No | clock name | — | Fast clock for differential serializer/deserializer pins |
pclk | No | clock name | — | Parallel clock for differential serializer/deserializer pins |
reset | No | signal name | — | Reset signal for differential serializer/deserializer pins (typically a CLOCK_GEN LOCK output) |
I/O standards
Single-ended standards (mode=SINGLE or default): LVTTL, LVCMOS33, LVCMOS25, LVCMOS18, LVCMOS15, LVCMOS12, PCI33, SSTL25_I, SSTL25_II, SSTL18_I, SSTL18_II, SSTL15, SSTL135, HSTL18_I, HSTL18_II, HSTL15_I, HSTL15_II
Differential standards (mode=DIFFERENTIAL): LVDS25, LVDS33, BLVDS25, EXT_LVDS25, TMDS33, RSDS, MINI_LVDS, PPDS, SUB_LVDS, SLVS, LVPECL33, DIFF_SSTL25_I, DIFF_SSTL25_II, DIFF_SSTL18_I, DIFF_SSTL18_II, DIFF_SSTL15, DIFF_SSTL135, DIFF_HSTL18_I, DIFF_HSTL18_II, DIFF_HSTL15_I, DIFF_HSTL15_II
Syntax examples
text
IN_PINS {
clk = { standard=LVCMOS33 };
button[2] = { standard=LVCMOS18 };
}
OUT_PINS {
led[6] = { standard=LVCMOS18, drive=8 };
TMDS_CLK = { mode=DIFFERENTIAL, standard=LVDS25, drive=3.5, fclk=serial_clk, pclk=pixel_clk, reset=pll_lock };
TMDS_DATA[3] = { mode=DIFFERENTIAL, standard=LVDS25, drive=3.5, fclk=serial_clk, pclk=pixel_clk, reset=pll_lock };
}
INOUT_PINS {
data_bus[8] = { standard=LVCMOS33, drive=8 };
EDID_DAT = { standard=LVCMOS33, drive=4, pull=UP };
EDID_CLK = { standard=LVCMOS33, drive=4, pull=UP };
}Rules
- Pin names must be unique across all PIN blocks
- Bus syntax
[N]declares a multi-bit pin; width must be positive integer standardrequired for all pins;driverequired for OUT and INOUT pins- For buses, MAP must map each bit individually
modemust be consistent withstandard: differential standards requiremode=DIFFERENTIAL, single-ended standards requiremode=SINGLE(or omitmode)widthis valid only withmode=DIFFERENTIALand sets the logical project-visible parallel width. Backend code generation selects the smallest supported serializer/deserializer ratio greater than or equal towidth; surplus lanes are handled in backend wrapper/template generation and do not change the user-visible port width.termis valid only formode=DIFFERENTIALor for single-ended SSTL/HSTL standards (which support on-die termination)pullis not valid onOUT_PINS(outputs drive the pin, they cannot be pulled)
Validation errors
- Declaring a pin in multiple pin blocks
- Missing or invalid
driveon OUT/INOUT pins - Bus width ≤ 0 → error
mode=DIFFERENTIALwith a single-ended standard (or vice versa) →PIN_MODE_STANDARD_MISMATCHpull=UPorpull=DOWNon an OUT_PINS entry →PIN_PULL_ON_OUTPUTterm=ONon a standard that doesn't support termination →PIN_TERM_INVALID_FOR_STANDARD
Differential serialization attributes
When mode=DIFFERENTIAL selects a serializer/deserializer primitive, clock/reset requirements come from the target chip's selected primitive. On GENERIC, all three are required:
fclk(fast clock): The high-speed serialization clock. Must be an integer multiple ofpclkmatching the selected serializer/deserializer ratio (e.g., 5x for a selected 10:1 serializer with DDR).pclk(parallel clock): The parallel data clock at which the module produces data.reset(serializer reset): The signal used to reset the serializer after power-on or clock stabilization. Typically references the PLLLOCKoutput declared viaWIRE LOCKin theCLOCK_GENblock. The compiler automatically inverts this signal: the serializer is held in reset while the lock signal is low (PLL not locked) and released when it goes high (PLL locked).
Both fclk and pclk must reference clocks declared in CLOCKS or generated by CLOCK_GEN. reset must reference a valid signal name — typically a CLOCK_GEN LOCK wire output. If a differential pin declares width=N, that width is the logical parallel data width seen by the module and @top binding. The backend may select a wider primitive ratio internally, but the extra lanes are padded, tied off, or otherwise handled as a backend detail.
Notes
- CLOCKS entries should reference an IN_PINS clock pin name
- Pin physical mapping is performed in MAP block; missing mapping is an error
- Fractional drive values (e.g.,
3.5) are supported for standards that use them
MAP (physical pin mapping)
Purpose
- Bind logical top-level pin names to physical board/package pins.
Single-ended syntax
text
MAP {
clk = 52;
led[0] = 10;
data_bus[7] = GPIO_27;
}Differential syntax
Differential pins (mode=DIFFERENTIAL) use the { P=<id>, N=<id> } syntax to specify both the positive and negative physical pin:
text
MAP {
TMDS_CLK = { P=33, N=34 };
TMDS_DATA[0] = { P=35, N=36 };
TMDS_DATA[1] = { P=37, N=38 };
TMDS_DATA[2] = { P=39, N=40 };
}Rules
- Every declared pin (from IN/OUT/INOUT) must have corresponding MAP entries
- Bus bits must be mapped individually
- Map entries must reference declared pins; mapping undeclared pins → error
- Duplicate assignment of the same physical pin must be validated (allowed only if tri-state by design)
- Differential pins must use
{ P=<id>, N=<id> }syntax; single-ended pins must use scalar syntax - Both P and N physical pins are checked for duplicate physical location conflicts
- P and N must be different physical pins
Validation errors
- Pin declared but not mapped → compile error
- Mapped pin not declared → compile error
- Duplicate mappings → warning or error (tool-dependent)
- Differential pin with scalar MAP →
MAP_DIFF_EXPECTED_PAIR - Single-ended pin with
{ P, N }MAP →MAP_SINGLE_UNEXPECTED_PAIR - Differential MAP missing P or N →
MAP_DIFF_MISSING_PN - P and N are the same physical pin →
MAP_DIFF_SAME_PIN
BUS (physical pin mapping)
Purpose
- Provide a reusable, project‑level description of a grouped set of signals (a "bus") and a compact way for modules to instantiate and connect those buses.
- Let module authors declare a bus-shaped port and have the tool automatically resolve each member signal's direction based on the module's role (initiator vs responder).
Project-level BUS definition
Syntax (inside @project):
text
BUS <bus_id> {
IN [<width>] <signal>;
OUT [<width>] <signal>;
INOUT [<width>] <signal>;
...
}Notes
- The bus_id is a project-wide identifier and must be unique.
- Directions (IN/OUT/INOUT) in a BUS block are written from the SOURCE (initiator) perspective.
- Widths may be literals or CONFIG expressions (e.g., CONFIG.XLEN).
Chip ID JSON
When CHIP is not GENERIC, the compiler loads a chip data JSON file that describes:
- memory: Array of objects describing supported memory configurations (DISTRIBUTED, BLOCK types with width/depth combinations).
- clock_gen: Array of objects describing clock generators (PLL, PLL2, DLL, CLKDIV, OSC, BUF) with parameters, derived expressions, outputs, and constraints.
The JSON schema includes:
memory[].type:DISTRIBUTEDorBLOCKmemory[].configurations[]:{ width, depth }pairsclock_gen[].type:pll,pll2,dll,clkdiv,osc,buf, or numbered variantsclock_gen[].parameters: Named parameters with type (intordouble), min, max constraintsclock_gen[].derived: Computed values (e.g., VCO frequency) with expressions and valid rangesclock_gen[].outputs: Named outputs (e.g.,BASE,PHASE,DIV,DIV3,LOCK) with frequency expressionsclock_gen[].feedback_wire: Optional feedback wire base name for automatic internal feedback path wiring
All periods are in nanoseconds; all derived frequency expressions output MHz. Expressions are evaluated in a restricted expression language (identifiers + math, no side effects).
The compiler validates MEM declarations and CLOCK_GEN configurations against the chip data. If validation fails, a compile error is emitted with the specific constraint that was violated.
@blackbox
Purpose
- Declare opaque/externally-supplied modules (hard macros, vendor IP, encrypted cores) inside the project.
Syntax
text
@blackbox <name> {
PORT {
IN [<width>] a;
OUT [<width>] b;
INOUT [<width>] bus;
}
}Rules
- Blackbox declarations must appear inside
@project(before@top) - Name shares same namespace as modules; must be unique project-wide
- No body: cannot contain WIRE, REGISTER, ASYNCHRONOUS, MEM, etc.
- Blackboxes may include CONST blocks; interpreted as local configuration for that IP
Instantiation
- Use
@newinside modules to instantiate a blackbox, same as module instantiation. OVERRIDE is allowed and passed through.
Example with OVERRIDE:
text
@module parent
CONST { DATA_WIDTH = 16; }
@new mem0 memory_wrapper {
OVERRIDE {
WORD_WIDTH = 32;
DEPTH = 512;
}
IN [1] clk = clk;
IN [9] addr = mem_addr;
IN [32] wr_data = mem_wdata;
IN [1] wr_en = mem_wen;
OUT [32] rd_data = mem_rdata;
}
@endmodThe OVERRIDE block replaces the child module's CONST values before elaboration. This allows a single module definition to be instantiated with different configurations.
Validation
- Port widths and directions must match at instantiation
- Missing blackbox declaration for
@new→ error
Example use cases
- BRAM/DSP primitives, PLLs, vendor SERDES, encrypted IP
@top (top-level module instantiation)
Purpose
- Define the design root and bind module ports to physical pins or
_(no-connect).
Syntax
text
@top <top_module_name> {
IN [1] clk = hw_clk;
OUT [6] led = leds;
INOUT [8] data = data_bus;
OUT [1] debug = _;
}Rules
- Exactly one @top block required inside a project
- The top module name must refer to a module or blackbox defined in project or imported files
- All module ports must be listed in @top (omitting a port is a compile error)
- For each port:
- If name is
_, the port is intentionally not connected to board pins - Otherwise the name must resolve to a declared pin in a compatible PIN block (IN → IN_PINS/INOUT_PINS/CLOCKS, OUT → OUT_PINS/INOUT_PINS, INOUT → INOUT_PINS)
- If name is
- Port widths must exactly match module port widths
Validation errors
- Top module not found
- Port not listed
- Port width mismatch
- Port direction vs pin category mismatch
- Pin not mapped in MAP
Logical-to-Physical Expansion
For @top port bindings, the compiler handles width matching between module ports and physical pins:
width(port_name)must equalwidth(pin_name)regardless of pin mode (SINGLEorDIFFERENTIAL).- For
mode=DIFFERENTIALpins, the compiler internally handles the 2-pin (P/N) expansion per bit. From the module's perspective, the port is a single-ended signal of the declared width. - If
width=Nis present on a differential pin,@topstill sees widthNeven when backend code generation selects a wider serializer/deserializer primitive.
@global (project-wide sized literals)
Purpose
- Provide named, sized literal constants accessible as runtime values in any module: instruction encodings, bit-pattern constants, opcodes, etc.
Syntax
text
@global <namespace_name>
NAME = <width>'<base><value>;
OTHER = 8'b1010_1100;
@endglobKey properties
- Each constant is a sized literal with explicit width (follows literal rules from Section 2.1)
- Referenced in modules as
namespace_name.NAME - Global constants are values (unlike CONFIG which are compile-time integers)
- Usable anywhere a value may appear: RHS of assignments, conditions, intrinsic operator arguments, etc.
Rules & validation
- The namespace name must be unique across project compilation
- Each const_id must be unique inside its
@globalblock - Values must obey literal rules: size, base, intrinsic-width ≤ declared width (overflow → error)
xbits are allowed in binary literals but must follow Observability Rule (may not propagate into observable sinks)
Use cases
- Instruction encodings, opcode masks, default packet fields, fixed bit patterns used at runtime
Errors
- Duplicate global namespace
- Duplicate constant identifier in the same block
- Invalid literal syntax or overflow
- Attempting to assign to a global constant (read-only)
Example
text
@global ISA
INST_ADD = 17'b0110011_0000000_000;
INST_SUB = 17'b0110011_0100000_000;
@endglobUsage inside a module:
text
IF (opcode == ISA.INST_ADD) { ... }@check (compile-time assertions)
Purpose
- Validate compile-time constraints (widths, config sanity, address ranges) during elaboration. A
@checkfailure aborts compilation.@checknever generates hardware or runtime behavior.
Syntax
text
@check (<constant_expression>, <string_message>);Rules
- The expression must evaluate to a constant, nonnegative integer at compile time.
- If the expression is zero (false), compilation fails with the message.
- Allowed operands: integer literals,
CONST,CONFIG.NAME,clog2(), comparisons, logical operators. - Forbidden: any runtime signal (ports, wires, registers, memory ports, slices).
@checkmay appear inside@projector@module, but not inside blocks (ASYNCHRONOUS, SYNCHRONOUS, etc.).
Examples
text
// Valid: width constraint
CONST { WIDTH = 32; }
@check (WIDTH % 8 == 0, "Width must be a multiple of 8.");
// Valid: project config constraint
@check (CONFIG.DATA_WIDTH >= 8, "Data width must be at least 8.");
// Valid: address width sanity
CONST { DEPTH = 256; ADDR_W = 8; }
@check (ADDR_W == clog2(DEPTH), "Address width does not match depth.");
// Invalid: runtime signal
@check (select == 3, "..."); // ERROR: non-constant expressionValidation rules and common errors (project-level)
- Only one
@projectper file; missing@endproj→ error. @importfiles must be unique after path normalization; duplicate import → error.CONFIGforward reference or circular dependency → compile error.CLOCKSentries must reference declared IN pins and have width[1].- Pin conflicts: same pin declared in multiple PIN blocks → error.
- MAP must include entries for every declared pin; unmapped pins → error.
@topmust reference an existing module/blackbox and list all its ports.- Port/pin direction mismatch (e.g., module IN bound to OUT_PINS, module INOUT bound to IN_PINS) → error.
- Duplicate module or blackbox names across imports and local definitions → compile error.
- Invalid
@globalliteral (unsized, overflow, invalid digits) → compile error.
Best practices and checklists
Project checklist before compilation
- [ ] All module/blackbox names required by
@topand@neware defined or imported - [ ] Each declared pin has a MAP entry (bus bits mapped individually)
- [ ]
CONFIGcontains shared sizes (XLEN, ADDR_WIDTH) used by multiple modules - [ ] Clocks declared in CLOCKS match top-level clock pins (names and widths)
- [ ]
@globalvalues for opcode masks and encodings are defined and used consistently - [ ]
@importpaths are normalized and not duplicated across project imports - [ ] Blackboxes declared for vendor IP; OVERRIDE used at instantiation where needed
- [ ] No port width mismatch between top and module definitions
- [ ] Pin electrical standards and drive strengths set correctly for board hardware
Recommended style
- Use
CONFIGfor integer sizes shared across modules (compile-time); use@globalfor sized bit-patterns used at runtime. - Keep
@importlines centralized at top of@projectfor clarity. - Name clocks clearly (e.g., sys_clk, ref_clk) and document periods in CLOCKS.
- Map pins with clear comments in MAP for board reference.
- Use
_in@topto explicitly ignore unused top ports and avoid accidental omissions.
Examples
Minimal project with @global and imports:
text
@project example_proj
@import "cores.jzhdl";
CONFIG {
XLEN = 32;
REG_DEPTH = 32;
}
CLOCKS {
sys_clk = { period=10 }; // 100 MHz
}
IN_PINS {
sys_clk = { standard=LVCMOS33 };
btn = { standard=LVCMOS18 };
}
OUT_PINS {
leds[4] = { standard=LVCMOS18, drive=8 };
}
MAP {
sys_clk = 1;
btn = 2;
leds[0] = 10;
leds[1] = 11;
leds[2] = 12;
leds[3] = 13;
}
@blackbox pll_ip {
PORT { IN [1] clk_in; OUT [1] clk_out; OUT [1] locked; }
}
@global ISA
INST_ADD = 17'b0110011_0000000_000;
INST_SUB = 17'b0110011_0100000_000;
@endglob
@top top_core {
IN [1] clk = sys_clk;
IN [1] button = btn;
OUT [4] led = leds;
}
@endprojExample with differential signaling, CLOCK_GEN with WIRE LOCK, and pull-up resistors (DVI output):
text
@project dvi_proj
CLOCKS {
SCLK = { period=37.037 }; // 27 MHz crystal
serial_clk; // 185.625 MHz (from PLL)
pixel_clk; // 37.125 MHz (from CLKDIV)
}
IN_PINS {
SCLK = { standard=LVCMOS33 };
}
OUT_PINS {
LED[6] = { standard=LVCMOS33, drive=8 };
TMDS_CLK = { mode=DIFFERENTIAL, standard=LVDS25, drive=3.5, fclk=serial_clk, pclk=pixel_clk, reset=pll_lock };
TMDS_DATA[3] = { mode=DIFFERENTIAL, standard=LVDS25, drive=3.5, fclk=serial_clk, pclk=pixel_clk, reset=pll_lock };
}
INOUT_PINS {
EDID_DAT = { standard=LVCMOS33, drive=4, pull=UP };
EDID_CLK = { standard=LVCMOS33, drive=4, pull=UP };
}
MAP {
SCLK = 4;
LED[0] = 15;
LED[1] = 16;
LED[2] = 17;
LED[3] = 18;
LED[4] = 19;
LED[5] = 20;
TMDS_CLK = { P=33, N=34 };
TMDS_DATA[0] = { P=35, N=36 };
TMDS_DATA[1] = { P=37, N=38 };
TMDS_DATA[2] = { P=39, N=40 };
EDID_DAT = 52;
EDID_CLK = 53;
}
CLOCK_GEN {
PLL {
IN REF_CLK SCLK;
OUT BASE serial_clk; // 185.625 MHz (5x pixel clock)
WIRE LOCK pll_lock; // lock indicator → used as serializer reset
CONFIG {
IDIV = 7;
FBDIV = 54;
ODIV = 4;
};
};
CLKDIV {
IN REF_CLK serial_clk;
OUT BASE pixel_clk; // 185.625 / 5 = 37.125 MHz
CONFIG {
DIV_MODE = 5;
};
};
}
@top dvi_top {
IN [1] clk = pixel_clk;
OUT [6] leds = LED;
OUT [10] tmds_clk = TMDS_CLK;
OUT [10] tmds_d0 = TMDS_DATA[0];
OUT [10] tmds_d1 = TMDS_DATA[1];
OUT [10] tmds_d2 = TMDS_DATA[2];
INOUT [1] edid_dat = EDID_DAT;
INOUT [1] edid_clk = EDID_CLK;
}
@endprojIn this example, WIRE LOCK pll_lock declares the PLL's lock indicator as a wire. The reset=pll_lock attribute on the TMDS pins tells the compiler to hold the serializer in reset until the PLL is locked. The compiler automatically inverts pll_lock for the active-low serializer reset.
Example showing CONFIG + top instantiation using CONFIG:
text
@project param_proj
CONFIG { XLEN = 64; }
CLOCKS { clk = { period=5 }; }
IN_PINS { clk = { standard=LVCMOS33 }; }
OUT_PINS { out_bus[XLEN] = { standard=LVCMOS33, drive=8 }; }
MAP { clk = 1; out_bus[0] = 10; /* ... map remaining bits ... */ }
@top wide_top {
IN [1] clk = clk;
OUT [XLEN] out_bus = out_bus;
}
@endproj