hexforge / limitations
Limitations
This is the most useful page on the site. A toolchain that advertises what it supports and stays quiet about the rest wastes a weekend of your life; here is the rest.
The compiler rejects rather than ignores. With two documented exceptions, every unsupported construct is a hard error with a line number and a code. Silently dropping something and producing hardware that does not match the source is the one behaviour this project treats as unacceptable.
What the subset does accept
Stated first so the rest reads as a boundary rather than a wall. Modules with parameters and port lists; wire and reg with packed vectors and one level of unpacked array; continuous assignment; always @(posedge clk) and always @(*); if, case, casez, for with constant bounds; generate with if and for; automatic functions; arithmetic, logical, bitwise, shift, reduction and comparison operators; concatenation and replication; part selects, including indexed ones; localparam; `define, `ifdef and `include. That is enough for the whole of the example set.
Language: rejected constructs
| Construct | Code | Why, and whether it is planned |
|---|---|---|
defparam | E118 | Order-dependent and awkward to elaborate. Use parameter overrides in the instantiation. Not planned. |
| Hierarchical names in expressions | E121 | The design is flattened early, so a cross-module reference has nothing to resolve against. Not planned before hierarchy is preserved. |
specify blocks, path delays | E130 | Only meaningful for simulation and cell libraries. Not planned. |
| User-defined primitives | E131 | Table-driven primitives are a simulation feature. Not planned. |
wand, wor, trireg, tri0, tri1 | E134 | No resolved-net modelling exists in the netlist format. Not planned. |
Gate primitives beyond and or not xor buf | E136 | nmos, pullup, rtran and relatives have no mapping on these devices. Not planned. |
fork / join | E140 | No concurrency model in the front end. Not planned. |
force, release, procedural assign | E141 | Simulation control. Not planned. |
Any delay, including #1 | E145 | Rejected rather than ignored, because a design whose behaviour depends on delays will not work as hardware. Not planned. |
real, realtime, time | E150 | No floating-point representation in the netlist. Not planned. |
| Tasks | E152 | Functions are supported, tasks are not: a task with timing control is not synthesisable, and the useful subset would be a function anyway. Not planned. |
casex | E156 | Accepting it would require four-state semantics the compiler does not have, and its real-world use is usually a bug. Use casez, which is accepted with a warning. |
| Unpacked arrays beyond one dimension | E161 | An implementation limit in the elaborator. Planned for 0.8. |
| Signed arithmetic wider than 64 bits | E164 | Constant folding uses a 64-bit signed representation internally. Planned, no date. |
Nested generate deeper than four levels | E170 | A recursion guard, not a design decision. Raise it and rebuild if you genuinely need five. |
| SystemVerilog of any kind | E101 | logic, always_ff, interfaces, packages, typedef. A different standard; out of scope for this project. |
| VHDL, or mixing languages | E100 | One language, one front end. Not planned. |
The two exceptions to rejecting: system tasks such as $display and $monitor are parsed and dropped with warning W301, because they are harmless in synthesis and removing them from shared source is tedious. And initial blocks are accepted in exactly one shape - a single assignment per memory word, used to give block RAM its initial contents.
Semantics: two-state only
The compiler has no notion of unknown or high-impedance values internally. A literal containing x is an error (E158); a literal containing z is an error everywhere except a continuous assignment driving an output port, where it becomes a tri-state pad.
The practical consequence is that any behaviour your simulator shows that depends on unknowns will not be reproduced. Reset every register you care about, and do not rely on a design that only works because x propagated somewhere convenient. Simulate before synthesis, with an external simulator; HexForge includes none.
Synthesis
- No retiming, no register balancing, no logic replication for timing. Pipelining is your job, in the HDL.
- No clock gating insertion and no automatic enable extraction.
- No DSP or multiplier primitive. A
*operator builds an array multiplier out of LUT4s: 8×8 costs about 180 LUTs, 16×16 about 690, and it is slow. - Carry chains are inferred only for addition and subtraction with a single-bit carry. Comparators wider than 16 bits map as plain logic and show up on the critical path immediately.
- Block RAM inference is narrow: single-port synchronous, depth a multiple of 512, width 1, 2, 4, 8 or 16. Anything else becomes flip-flops, with warning W214 naming the array.
- No internal tri-state nets. Tri-state exists only at output pads.
- The packer fails above roughly 90% occupancy on M1 and roughly 94% on T4. Plan to use four-fifths of a device, not all of it.
- Placement optimises wirelength and knows nothing about slack; routing has no timing awareness at all.
- The router gives up after 40 iterations and reports unrouted nets rather than shipping a partial design.
- No incremental compilation. Every build is from scratch.
- No floorplanning: you cannot constrain a module to a region of the die.
Constraints
Five directives exist: set_io, create_clock, set_input_delay, set_output_delay, set_false_path. Everything else in a constraints file is an error.
So there is no set_multicycle_path, no set_max_delay, no clock group declarations, and no generated clocks - a PLL output must be declared with its own create_clock and the frequency you worked out by hand. Asynchronous crossings cannot be described at all beyond excluding them with set_false_path, and the analyser will keep warning about them, which is the intended outcome.
Devices and hard blocks
- Two families only, Tessera T4 and Mica M1. A third family means measuring a new delay table and frame layout from scratch, which is a months-long job.
- PLLs are never inferred. Instantiate
HX_PLLand supply the dividers. - No DDR input or output registers, which is what keeps Crofter Lite in partial support on the board matrix.
- No block RAM cascading, so no single memory larger than 4 kbit.
- No hard IO standards beyond plain LVCMOS at the board's bank voltage: no differential pairs, no on-chip termination, no slew control.
- Clock regions are not modelled by the packer, which produces an illegal arrangement for a two-clock design whose loads straddle a boundary. Workaround: keep each clock's logic in one half of the die by constraining its pins to that side.
Tooling
- No simulator, no waveform viewer, no formal tools, no synthesis GUI, no project file.
- No Windows build, and none intended.
- No bitstream compression and no partial reconfiguration.
- No programming step: flash the bitstream with the loader that came with the board.
- Error messages name flat cell identifiers like
u_2871. Mapping one back to a line of HDL means reading the header comments in the netlist, which is clumsy and known. - The text layout of every report may change between releases. Parse the
--jsonoutput instead.
Open defects
| Id | Since | What goes wrong |
|---|---|---|
| #141 | 0.4.0 | Delay model extrapolates beyond six routing hops and reads optimistic. Visible as warning W117 in a timing report. |
| #164 | 0.5.0 | Packing above 94% occupancy on M1-8K fails to find a legal arrangement. |
| #198 | 0.6.0 | Two-clock designs straddling a clock region boundary get packed illegally; the bitstream loads and the design misbehaves. |
| #212 | 0.7.0 | Thistle Edge external clock pin is described in the wrong IO bank in t4.hxd, so only the internal oscillator is usable on that board. |
If one of these is in your way, say so at hexforge@hexforge.cloud - priority here is set almost entirely by who is actually blocked. Defect #212 is a two-line fix waiting on someone with that board to confirm the correct bank.