[ < Prev | Up | Next > ]   Node: Build Flow

hexforge / build flow

Build flow

Four programs, three intermediate files, no daemon and no project database. You can stop after any stage, read the file it produced, edit it by hand if you really want to, and carry on.

.v hxc .hxn hxsyn .hxr hxpack .bit hxta parse, elaborate, flatten map, pack, place, route frames + header timing report, stdout + .hxs flash it yourself dashed = file on disk, solid = program
The timing analyser sits off to the side on purpose: it reads the routed design and changes nothing. You can run it, ignore it, and still get the same bitstream - which also means nothing in the flow automatically retimes or re-places a design that misses its clock target.

hxc - front end

Lexes and parses the accepted subset, then elaborates: parameters are resolved, generate loops unrolled, hierarchy flattened into a single namespace with dotted names kept only as comments. Register inference follows the usual rule - an always @(posedge clk) block with non-blocking assignments becomes flip-flops, anything combinational becomes gates. Constant folding and dead-cell removal run once, in that order.

The output is text. A fragment of blink.hxn, which is roughly as readable as this format gets:

# hexforge netlist v4
# source blink.v, 2026-10-04T09:12:41Z, hxc 0.7.2
module blink
  port in  clk
  port in  rst_n
  port out led
  clock clk posedge
  reg   count[23:0] clk async_none
  cell  and2  u_14  a=count.0 b=count.1 y=n_31
  cell  not1  u_15  a=rst_n y=n_32
  net   led = count.23

Flattening before synthesis is a simplification with a cost: error messages point at a flat cell name, and tracing u_14 back to a line of HDL means reading the comment block at the top of the module. Keeping full hierarchy through the flow is on the list for 0.8.

The constraints file

Pin assignment and clock declarations live in a .hxs file next to the source. The syntax is small enough to learn from one example:

# examples/blink/pebble-25.hxs
set_io  clk    A7
set_io  rst_n  C9
set_io  led    B3

create_clock      clk 12.0MHz
set_false_path    rst_n -> *

Four directives are understood: set_io, create_clock, set_input_delay and set_output_delay, plus set_false_path for the analyser. Anything else in the file is an error, not a warning - a constraint that is silently ignored is worse than a build that stops.

hxsyn - mapping, placement, routing

  1. Mapping. Cut-based LUT mapping with k = 4, depth first and area as the tie-break. No technology-specific tricks, no carry-chain inference beyond a pattern match on a + b with a single-bit carry.
  2. Packing. LUTs and flip-flops go into logic tiles of eight each. The packer is greedy with one refinement pass, and it is the weakest part of the tool: above about 90% occupancy it starts failing to find legal arrangements, which is exactly what the red pack_dense_m1 test records.
  3. Placement. Simulated annealing on a wirelength cost, seeded from --seed (default 1). Typical runs take a fifth of total build time.
  4. Routing. Negotiated congestion, the familiar iterative scheme where nets rip up and re-route while the cost of shared resources climbs. It gives up after 40 iterations and reports unrouted nets rather than producing a half-routed design.

Timing is not part of any of those four steps. Placement optimises wirelength, not slack, and the router has no notion of a critical path. On designs below a few thousand LUTs this matters less than it sounds; above that it is the main reason results fall behind mature tools, and it is stated plainly on the timing page.

hxpack - bitstream

Serialises the routed design into device frames, prepends the configuration header for the mode the board uses, and appends a CRC. Nothing clever happens here; the only interesting flag is --dump-frames, which writes a human-readable frame listing next to the bitstream for when a board refuses to configure and you need to know whether the problem is upstream.

Bitstream compression is not implemented. On a T4-25K a full bitstream is 141 312 bytes, which fits every supported board's flash with room to spare, so the work has never been justified.

Determinism

The same source, constraints, device files, seed and version produce a byte-identical bitstream. Each release is checked by building all eight examples twice on two machines and comparing hashes. The build is single-threaded end to end, which costs wall-clock time and buys this property - a trade the project intends to keep.

Change the seed and you get a different placement and slightly different timing; that is the cheapest knob when a design misses its target by a few per cent. Sweeping four or five seeds is standard practice here, and hxta on the result tells you which one to keep.

Typical runtimes

Measured on one machine with eight cores, though only one is used. Routing dominates everything above a thousand LUTs.
DesignLUT4hxchxsynhxpack
blink250.02 s0.6 s0.3 s
uart_loopback3100.05 s3.1 s0.3 s
vga_bars9800.11 s14 s0.4 s
fifo_cdc2 1400.21 s48 s0.4 s
congested T4-45K case19 4001.8 s4 min0.9 s

What the flow does not contain

[ < Prev | Up | Next > ]   Next node: Examples
build flow · diagram regenerated 2026-10-03 · next: timing analysis