Home · ASPLOS 2024 · Accelerators & Hardware Design

Longnail: High-Level Synthesis of Portable Custom Instruction Set Extensions for RISC-V Processors from Descriptions in the Open-Source CoreDSL Language

Julian Oppermann, Brindusa Mihaela Damian-Kosterhon, Florian Meisel, Tammo Mürmann, Eyck Jentzsch and Andreas Koch

Conference
ASPLOS 2024
Published
2024
Pages
591-606
Citations
15

RISC-V invites you to add your own instructions to a processor, which is the cheapest way to make an embedded chip fast at one specific job — but actually building one requires knowing the intimate timing of the particular core you are extending, so the people who know which instruction is needed cannot write it, and the hardware that results only works on the core it was written for; this paper (TU Darmstadt and MINRES) shows both problems dissolve if the description says only WHAT the instruction computes and never when, and the processor enters the compiler as nothing but a pair of numbers per operation — the earliest and latest cycle it may happen in — with a scheduler placing the work inside those windows.

The argument in one glance

  1. Problem

    The cheapest way to make a small chip fast is also the hardest to use

    Embedded devices are being asked to run signal processing, machine learning and post-quantum cryptography, which general-purpose embedded cores cannot deliver. The two obvious answers are both bad: a bigger processor is less energy-efficient, and a dedicated accelerator is not cost-effective for a single application. The established middle path is to add CUSTOM INSTRUCTIONS to an existing core, so that extra silicon is spent only on the parts of the computation that matter while everything else stays compatible with the existing software. RISC-V, being open and modular with first-class support for extensions, makes this more attractive than it has ever been.

  2. What people did

    Use a vendor toolchain that generates the extension for you

    This is not a new idea and there are mature commercial answers. Cadence, Synopsys and Codasip each provide a language for describing custom instructions and a toolchain that generates the hardware and integrates it into their processors. These work well, are used in production, and remove most of the manual hardware design. If you are building on one of those vendors' cores, the problem is largely solved for you.

  3. Why it fell short

    Every solution binds you to one processor family, and to hardware expertise

    The common pattern is a proprietary input language plus portability that stops at the vendor's own processor family. Outside those ecosystems, RISC-V has several open extension interfaces, but they are used by writing the hardware yourself — no synthesis tool targets one. And the deeper obstacle is that anything past the simplest custom instruction depends intimately on the core's microarchitecture: which pipeline stage makes a register value available, when a result must be ready, what happens if the instruction takes longer than the pipeline is deep. Cores differ enormously, from five-stage pipelines to machines with no pipeline at all. So the application engineer who knows which instruction is needed cannot write it, and hardware written for one core does not move to another.

  4. This paper's idea

    Describe only what it computes, and let the core enter as two numbers per operation

    This paper (TU Darmstadt and MINRES — Oppermann, Damian-Kosterhon, Meisel, Muermann, Jentzsch, Koch) separates the two halves cleanly. The description language, CoreDSL, is purely BEHAVIOURAL: familiar C-like syntax with arbitrary-width integers, saying what the instruction computes and never when. The processor enters the compiler through what the authors call a VIRTUAL DATASHEET, which gives for each primitive operation — read a register, write a result, touch memory, change the program counter — the EARLIEST and LATEST cycle in which that operation may occur on this particular core. Those numbers are the entire interface. A scheduler then places the computation inside those windows. Eight extensions compile unchanged onto four very different cores, including one with no pipeline, and on a fabricated chip the generated hardware matched or beat the hand-designed version.

Why this problem is worth solving

The middle path here is genuinely attractive, which is what makes its inaccessibility frustrating.

An INSTRUCTION SET EXTENSION adds instructions, and sometimes registers, to an existing processor for a particular application domain. The appeal is economic: the extra design effort and silicon go only to the performance-critical part of the computation, while the rest of the core is untouched and stays compatible with existing compilers, debuggers and software. In this paper’s own example, a sixteen-line description produces an instruction that multiplies four pairs of 8-bit values packed into two registers and sums the products — one instruction replacing a loop. A second example implements a ZERO-OVERHEAD LOOP: registers holding a loop’s start, end and counter, plus a rule that fires continuously and rewrites the program counter when the end is reached, so the loop costs no branch instruction at all. On a simple array-summation benchmark these two together take a measured 18n plus 50 cycles down to 11n plus 50, more than 60 percent faster, for 16 percent more chip area
.

The obstacle is that the description everyone writes today is entangled with the processor it targets.

Cores differ enormously in the one respect that matters here. In a five-stage pipeline a register value might be readable in stage 2 and a result due in stage 5; in a three-stage pipeline the numbers differ; and one of the four cores tested here is not pipelined at all, sequencing instructions with a state machine. Any description that mentions stages, or assumes a result is ready at a particular moment, is bound to one design. The escape is to make the description FULLY BEHAVIOURAL — it states the computation and nothing about timing — and to feed the timing in separately. That is the VIRTUAL DATASHEET: for each primitive the extension can use, it records the EARLIEST and LATEST cycle, counted from instruction fetch, in which that primitive is available on this core. Reading a register might be legal in cycles 2 through 4; writing a result might be required by cycle 5. Two integers per primitive is the whole description of the processor that the compiler sees
.

What people did before, and where it broke

  • Write the hardware by hand against an open interface. RISC-V has several open extension interfaces, and people do implement extensions against them. This works and gives full control.
  • But it requires hardware expertise and does not travel. The person who knows which instruction the algorithm needs is usually an embedded software engineer, not a digital designer, and hardware written against one core’s timing does not move to another.
  • Use a commercial ASIP toolchain. Cadence, Synopsys and Codasip each offer a description language and a generator that produces the hardware and integrates it. These are mature and genuinely remove most of the manual work.
  • But the portability stops at the vendor’s own cores. The input languages are proprietary and the generated hardware targets that vendor’s processor family. One of them also requires the instruction’s behaviour to fit inside the core pipeline, which rules out anything long-running.
  • Design the whole processor instead. Tools in this class are more powerful than any extension mechanism, since nothing is fixed.
  • But that is strictly harder, not easier. Their input languages sit at a lower abstraction level and require structural decisions — including manually distributing logic across pipeline stages — which is precisely the expertise the target user does not have.
  • Standardise a fixed extension interface. A draft RISC-V proposal defines composable extension units behind a common interface so extensions can be reused.
  • But a fixed interface is a lowest common denominator. As drafted it allows access only to integer registers and isolated state, so an extension that changes the program counter, or shares state with another extension, is out of scope.
  • The gap. Everyone chose between portability and depth of integration. Nobody had built a synthesis tool that targets a processor interface at all — and once you notice the only thing that varies between cores is WHEN each primitive is available, a compiler can be portable and specialised at the same time.

How one description reaches four different processors

Say what it computes, learn when each primitive is available, and schedule into the gaps

  1. Write the extension against a specific core

    The traditional route is a hardware description written knowing which pipeline stage supplies each value and when a result is due. It gives full control and produces good hardware, but binds the design to one microarchitecture and requires digital design expertise.

  2. Describe only the computation, in familiar syntax new

    CoreDSL states the instruction encoding and its behaviour in C-like code with arbitrary-width signed and unsigned integers and bit-range operations. It says nothing about stages or timing, which is exactly what allows the same text to target different processors.

  3. Ask the target core what it can do and when new

    An interface generator that already knows each supported core produces a virtual datasheet: for every primitive the extension might use, the earliest and latest cycle it is available, counted from instruction fetch. It also generates the glue logic to attach the result to that particular core.

  4. Make the primitives first-class operations in the compiler new

    Lower the description into a data-flow graph where reading a register or writing a result appears as an ordinary node, not as something bolted on afterwards. That way the processor interface is scheduled alongside the arithmetic rather than constraining it from outside.

  5. Solve a scheduling problem with those windows as bounds new

    Assign every node a start cycle so that operands arrive before use and each interface node falls inside its earliest-to-latest window. Where several combinational operations land in one cycle, break the chain across cycles so the critical path stays short.

  6. Let the constraints choose how deeply to integrate new

    If the schedule fits within the core pipeline, the instruction runs as though it were built in. If the result operation is allowed to happen arbitrarily late, the schedule may run past the pipeline, which selects either stalling the core or running truly decoupled with automatically generated hazard logic.

Eight extensions compiled to four open-source RISC-V cores — two five-stage pipelines, one three-stage, and one non-pipelined state machine — synthesised and placed-and-routed on a commercial 22nm ASIC flow, with correctness checked by register-transfer simulation.

The idea

Separate what varies from what does not, and the varying part is small. The reason custom instructions have been microarchitecture-bound is that people wrote descriptions containing timing, and timing is exactly what differs between cores. Strip it out and the description becomes portable — but then the compiler needs that information from somewhere, and the paper’s insight is how little is actually required. For each primitive an extension can use, two integers suffice: the earliest and latest cycle it may occur in, counted from instruction fetch. Reading a register might be available in cycles 2 through 4; writing a result might be required by cycle 5. That is the entire description of the processor that reaches the compiler, and it is enough to target a five-stage pipeline, a three-stage pipeline and a machine with no pipeline at all from identical source. Note what this is not: it is not a fixed lowest-common-denominator interface, the approach a competing proposal takes and which restricts extensions to integer registers and isolated state. It is a per-core parameterisation, so the output is specialised to each target while the input stays common. Finding that the machine-specific part of a problem compresses to a handful of numbers, so one compiler can be portable and specialised at once, is the same separate-what-changes-from-what-does-not discipline covered elsewhere on this site where the expensive analysis is placed on the part that does not depend on the unknown.

The integration strategy falls out of the scheduling constraints. How deeply a custom instruction should be woven into a processor is normally a design decision with several distinct answers: run inside the pipeline like a built-in instruction; stall the core while a longer computation finishes; or run truly decoupled alongside it with hazard-detection logic. Each needs different hardware. In this design the choice is not made separately — it emerges from the scheduling problem. Setting the LATEST bound on the write-result operation to infinity permits the scheduler to place it beyond the pipeline’s end, and that possibility is precisely what unlocks the stalling and decoupled implementations. The square root example makes it concrete: thirty-two unrolled iterations of an iterative algorithm need ten pipeline stages, which is deeper than any of the four host cores, so no in-pipeline schedule exists and one of the other modes must be used. Arranging a design so a discrete implementation choice becomes a consequence of relaxing a constraint, rather than a separate decision someone must make, is the same let-the-solver-decide structure covered elsewhere on this site where a compiler generates candidates and a cost model selects among them instead of the programmer choosing.

One description, many artifacts, and real silicon. The strongest evidence is not in the benchmark tables. The language was built inside a national research project of twenty-three industrial and academic partners, and was used there not only for hardware synthesis but also to generate instruction-set simulators for verification, to generate compilers targeting the extended cores, and to extract properties for formal verification in a commercial tool — one description acting as the single source of truth for an entire toolchain, which is the thing an open specification makes possible and a proprietary one does not. Then the harder test: industry application engineers who were not hardware designers wrote extensions for an automotive signal-processing application, which were automatically integrated into a core and taped out as a 22nm system-on-chip. On that chip the generated hardware matched or exceeded the manually designed implementations. Demonstrating that a raised abstraction level costs nothing against expert hand-work on real silicon is the strongest form of the automation-need-not-cost-quality claim covered elsewhere on this site where a generated design is measured against the hand-tuned one it replaces rather than against a strawman.

An instruction becoming hardware, twice

Following a description through the compiler, and what changes when the target core does

The paper's own worked example is an add-immediate instruction; the numbers are its solved schedule.

Step 1 of 5

Write the behaviour, mention no timing at all

The description gives an encoding — which bits mean which register and which immediate — and a body in C-like syntax with arbitrary-width integers. For the dot-product example that is a four-iteration loop multiplying 8-bit slices and accumulating. Nothing states when anything happens, which is why the same text can target different processors.

the type system uses signed and unsigned integers of any bit width, so a 16-bit product of two 8-bit values is expressed directly rather than by masking
loops with known bounds and non-recursive function calls are unrolled and inlined before synthesis, so the body becomes straight-line work

Why it works

  • The description contains no timing, and timing is the only thing that differs materially between cores → the same source is meaningful on every target → so eight extensions compile unchanged onto four cores including one with no pipeline.
  • The processor is summarised as an earliest and latest cycle per primitive → the compiler has enough to specialise without knowing anything structural → giving per-core optimised output rather than a lowest-common-denominator interface.
  • Processor primitives are ordinary nodes in the scheduled graph → interface timing is optimised jointly with the arithmetic instead of constraining it from outside → which is why the authors had to build their own synthesis tool, since existing ones do not expose the scheduler.
  • Removing the upper bound on when a result may be written admits schedules longer than the pipeline → the stalling and decoupled implementations become reachable solutions → so how deeply to integrate stops being a separate decision.
  • The interface generator produces the glue logic and hazard handling per core → the synthesis tool never learns what a pipeline hazard is → keeping the compiler portable while the integration stays core-specific.

What it costs (trade-offs and scope)

  • The scheduler’s cost model is deliberately crude. It assumes uniform delay and area for every operation, with a real technology library listed as future work. This is not cosmetic: two extensions lose frequency on one core precisely because the scheduler could not see what its choice would cost there.
  • Area overheads reach three digits. The square-root extension costs between 22 and 111 percent of the base core’s area depending on target and mode. The tool builds fully spatial data paths with no resource sharing, which is the acknowledged reason and is being worked on.
  • Only the embedded end has been demonstrated. All four cores are microcontroller-class. Prototypes exist for larger application-class cores, and out-of-order superscalar designs are named as future work with the specific complications listed honestly.
  • Automatic hazard handling is not free. Turning it off shrinks one extension from 56 to 46 percent overhead on one core, but then correctness depends on compiler support or on the programmer using the instructions very carefully.
  • There is no established benchmark to compare against. The authors had to define their own eight extensions because no standard set exists for evaluating this class of tool, so the comparison is a demonstration of coverage rather than a contest.

The evaluation

Eight extensions were written to exercise different capabilities — memory access, custom registers, program-counter manipulation, read-only tables, long-running computation, and continuously-running behaviour — and compiled onto four open-source RISC-V cores chosen for microarchitectural diversity: two five-stage pipelines, one three-stage, and one non-pipelined state machine, with base areas differing by more than five times and clock frequencies by three. Everything went through a commercial 22nm synthesis and place-and-route flow, with the target frequency set at or above each base core’s own limit so that area comparisons are stable, and correctness checked by simulating hand-written assembly.

Adding a custom instruction to an embedded processor

PropertyWrite the hardware yourselfVendor extension toolchainBehavioural source plus per-core windows
Who can do it
Portability across cores
Long-running instructions
Control flow and shared state
Quality against hand design
Across the four cores, area overheads range from nothing measurable for a table-lookup extension to 111 percent for an unrolled square root, with frequency usually within a few percent of the unmodified core. A zero-overhead loop costs 7 to 14 percent area for at most 4 percent frequency change. Two extensions in combination take a simple array summation from 18n plus 50 cycles to 11n plus 50, over 60 percent faster for 16 percent more area; on a real application, four extensions gave 2.15 times the performance and 30 percent power savings.Underlined values explain themselves when clicked.

Check yourself

Check your understanding

  1. 1. The description language deliberately cannot express when anything happens. Predict why that restriction is what makes portability possible, and what the compiler needs instead.

  2. 2. For the operation that writes a result, the compiler sets the LATEST allowed cycle to infinity. Predict what that changes about the hardware produced.

  3. 3. Two extensions lost frequency on one particular core while behaving fine on the other three. Predict what went wrong, given the scheduler assumes every operation has the same delay.

  4. 4. The authors built a new synthesis tool rather than using an existing high-level synthesis compiler. Predict the specific reason.

Sources

Related papers on Accelerators & Hardware Design

See all 118 →