RTL-Parameter Searches

OpenPiton’s sims sets cache sizes, associativities, the mesh, and the network. Its buffer depths, queue sizes, and miss-handling counts sit in RTL headers and packages, where sims cannot reach them. An RTL-parameter search changes those values by editing the source, builds each edited design, and keeps a design only when every tile passes and the finish time drops.

The searches run on matmul_scaled.c, a program whose work grows with the hart count and which reports how long each hart took. MACE’s proposer reads those per-hart timings before it picks the next design. A TPE sampler from Optuna searches the same parameters with the same number of simulations.

The program

mace/workloads/matmul_scaled.c gives every hart four rows of a matrix product against the same 56x56 matrix. A 4x4 mesh does four times the work of a 2x2 mesh, and a mesh with no contention finishes both at the same time. The second matrix is 12.25 KB, which is larger than the default 8 KB L1D, so it does not stay in the L1D, and the harts read the same lines from the L2 slices.

Each hart stores the elements it computes on its own stack, reads them back, and checks each one against a closed form. A hart that finds a wrong value ends at the bad trap, so the run passes only when every tile is right. Each hart reads mcycle before its first row and after its last, then prints one line in hart order:

h3 1204 188731

chia_openpiton.parse.hart_cycles() reads these lines from fake_uart.log (see Transcript Parsers). The values are core clock cycles. Harts are numbered row by row, so hart h is on tile (h mod X, h div X) of an X by Y mesh.

The finish time

Printing to the fake UART takes many cycles per character, and the harts print one after another. The line is short for that reason. The testbench’s sim_time, the time of the verdict, includes the printing. How long the printing takes depends on the design, so sim_time is not what the parameters change: a design can shorten the printing and lengthen the computing, and then sim_time falls while the harts finish later.

The finish time a search minimizes is the compute time, the latest hart’s end minus the earliest hart’s start, from the per-hart cycles (mace/codesign/verdict.py). A task’s finish setting can name sim_time instead. Both are recorded. A design has a compute time only when every hart printed its line. The scaling study uses the compute time too, with sim_time reported next to it.

A task’s win_margin is how much lower than the default design’s finish time a design must be, as a fraction of it, to count as beating the default. The task file sets 0.01.

The parameters

Each parameter lists the places its value is written. A search sets the value of every parameter in its space, and a parameter left out keeps its default.

Parameter

Default

Values

Storage bits per unit

Controls

l2_mshr

8

4, 8, 16

154

Entries in each L2 slice’s miss status table

l2_req_buf

8

4, 8, 16

64

Header flits an L2 slice holds for requests from the L1.5 caches

l2_req_data_buf

4

2, 4, 8

64

Data flits an L2 slice holds for those requests

dcache_wbuf

8

4, 8, 16

147

Entries in each Ariane core’s data cache write buffer

store_buffer

4

2, 4, 8

262

Entries in each Ariane core’s speculative and committed store queues

scoreboard

8

4, 8, 16

362

Instructions an Ariane core can have in flight

home_alloc

MIXED

LOW, MIDDLE, HIGH, MIXED

0

Which address bits choose the L2 slice that homes a line

mace/codesign/rtl.py holds the catalog. The storage cost of a design is the flip-flop bits it adds per tile over the default design. The bits per unit come from the entry widths in the RTL: for example, a store buffer entry holds a 56-bit address, 64 bits of data, an 8-bit byte enable, a 2-bit size, and a valid bit, and the value sets two queues. A smaller buffer gives bits back. The cost orders designs by size and is not a synthesis result.

Some parameters are left out. NR_LOAD_PIPE_REGS and the floating-point latencies trade instructions per cycle for clock period, which a simulation cannot see. The L1.5 thread count, which sets how many stores the data cache keeps in flight, is tied to masks written into several L1.5 files. The L1.5 request queue and the router buffers need changes in several files that must agree, and are left for later.

How an edit works

A parameter lists one or more places that hold its value, each as a line of text with the value in it. The text at the default value must occur exactly once in its file. If it does not, the design is rejected with the reason, and nothing is built. A checkout that differs from the one the catalog was written against fails there and never builds the wrong design.

Edits go to the .pyv sources and never to the .tmp.h and .tmp.v files that PyHP writes from them. Each build overwrites those. A value that a header and a PyHP block both hold is written in both places. After a build, the search checks that the generated L2 header holds the new text. A header that was not regenerated would build the default design and look like a parameter with no effect, so a build without the edit is rejected and does not count as a simulation.

The edits are applied through OpenPitonWorkspaceNode.apply_edits, set into PitonConfig.source_edits so that each distinct design builds into its own model directory, and reverted after the runs however they end. A design met again reuses its build. The searches keep the build cache between jobs, so the default design builds once.

The storage budget

A task’s storage_budget_ratio is a multiple of the default design’s storage in the searched parameters, as area_budget_ratio is for the cache co-design. The budget is the ratio’s excess over 1 times that storage, rounded down, as flip-flop bits per tile that a design may add. At the ratio of 1.0 that the task file sets, a design may not add storage, so a larger buffer must be paid for with a smaller one. The ratio is fixed before any search runs, so the budget does not shape the result.

A design over the budget is rejected before it builds and costs no simulation. The budget keeps a search from taking the largest value of every buffer. Every method on a task gets the same budget, except rtlparam_default and rtlparam_sweep, which have none: they measure what each value does and are not searches.

Methods

Method

What it does

LLM

Repeats

rtlparam_default

Builds and simulates the default design only, for its finish time and per-hart cycles

No

Once

rtlparam_sweep

The default design, then each parameter alone at each of its other values

No

Once

rtlparam_trades

The default design, then each design under the task’s trades, which fit the storage budget

No

Once

rtlparam_mace

The default design, then one design per round from MACE’s proposer

Yes

Per repeat

rtlparam_bayes

The default design, then designs from Optuna’s TPE sampler, seeded by the repeat

No

Per repeat

Every search starts with the default design, so each result has a baseline from the same run. The proposer makes no LLM call for that first design. For each later design it sees the parameters with their defaults, values, and costs, the storage budget, every design tried with its outcome, and the per-hart cycles of the best design and the latest one. It replies with a BOTTLENECK: line, one sentence on what limits the finish time, and a DESIGN: line with the values to try. The bottleneck line is recorded with the design.

TPE starts modelling after three random trials, where Optuna’s default is ten. With ten simulations, the default would leave the sampler nothing to model.

Trades

At a ratio of 1.0, no single increase fits the budget, so the sweep cannot show an in-budget gain by itself. A design can win only by trading: a larger buffer paid for with a smaller one. examples/rtlparam_pick_trades.py reads a task’s latest sweep, takes the effect of each passing single change on the finish time, and ranks the combinations that fit the budget by the sum of their effects.

The sum assumes the effects add. It chooses which designs to simulate and is not a result. The script prints a trades: list to paste into the task’s rtlparam block. The loader refuses a trade that is the default design, a repeat, outside the space, or over the budget. rtlparam_trades then simulates the default design and each trade, and only those simulations show whether a trade beats the default.

python examples/rtlparam_pick_trades.py --task ariane-4x4-matmul-scaled --db runs/rtlparam.db --k 5

With --write-suite PATH the script also writes a copy of the suite in which that one task lists the picked trades, and the loader checks the copy. Pass the copy to eval_batch.py with --suite. Nothing is written when no combination is predicted to help.

The verdict

examples/rtlparam_go_nogo.py reads every rtlparam_* run of a task from one or more databases and applies one rule: go only if at least one in-budget design beats the default design’s finish time by the margin, in a run in which every tile passed. It lists each in-budget design that passed, best first, marks the ones that reach the margin, and prints GO or NO-GO. A NO-GO ends with the sentence “The default parameters are balanced for this workload.” A task on which only the default design has run gets NO VERDICT and exit code 1. It warns when runs of the default design disagree, since the simulation is deterministic.

python examples/rtlparam_go_nogo.py --task ariane-4x4-matmul-scaled --db runs/rtlparam.db

Counting simulations

A search stops after its number of simulations, after ten times that many proposals, when its time budget runs out, or when its strategy proposes nothing new twice in a row. A design counts as a simulation when it builds and its gate programs run, whether or not it passes. A proposal rejected before a build costs nothing: it was over the storage budget, an edit did not apply, or the build lacked the edit. Reports compare searches by simulations, never by proposals.

Running a batch

MAKEFLAGS=-j1 python examples/eval_batch.py --suite examples/eval/tasks_rtlparam.yaml \
  --piton-root ~/openpiton-eval --tasks ariane-4x4-matmul-scaled --repeats 3 \
  --methods rtlparam_default,rtlparam_sweep,rtlparam_mace,rtlparam_bayes \
  --backend vertex --db-path runs/rtlparam.db

examples/eval/tasks_rtlparam.yaml has one task per mesh: 2x2, 4x2, and 4x4 Ariane. A 1x1 mesh is a single core with no interconnect, so it is left out. Each task’s rtlparam block gives the values each searched parameter may take, the number of simulations per search, the storage budget ratio, the finish time, and the win margin. A parameter left out of the space keeps its default, and the values must include the default. The searches use one checkout.

rtlparam:
  simulations: 10
  storage_budget_ratio: 1.0
  finish: compute_cycles
  win_margin: 0.01
  space:
    l2_mshr: [4, 8, 16]
    dcache_wbuf: [4, 8, 16]

What a run records

A search is one run in the metrics database, labelled with its method and task. It has one evaluations row per proposal:

Column

Holds

design

The value of every parameter, as JSON

sim_time

The testbench’s simulation time, summed over the gate programs, or empty for a design that failed or was rejected

feasible

1 for a design that passed and has a finish time

simulated

0 for a design rejected before any simulation

rejected

The reason, for a design rejected

storage_bits

The bits the design adds per tile over the default design

hart_cycles

Each gate program’s per-hart [start, end] values, as JSON

note

The proposer’s bottleneck line

The compute time is not a column. It is computed from hart_cycles, and the run’s meta records which finish time the search minimized. Each design that built also has a task row. Its rtl_edits column holds the diff of the files the edits changed.