RISC-V SoC verification using C and assembly with self-checking tests, functional coverage and debug
Published On: 15th August 2026|Last Updated: 15th August 2026|By |
Share This Article

A RISC-V processor inside an SoC can do more than execute firmware. During pre-silicon verification, it can become part of the stimulus mechanism itself. By running carefully designed C or assembly programmes on the processor, verification teams can exercise boot sequences, memory-mapped peripherals, interrupt paths, system-control registers and other hardware/software interactions through the same architectural interfaces that production software will eventually use.

That is powerful, but simply getting a programme to execute is not sufficient verification.

A useful software-driven test needs a clearly defined verification objective, an observable expected result, a reliable pass/fail mechanism, functional-coverage evidence and enough diagnostic information to investigate failure. Otherwise, a test that appears to pass may prove little more than the fact that some instructions executed.

Key takeaway: use C and assembly as complementary verification stimulus. Use assembly where exact architectural control matters; use C where maintainable system-level behaviour matters. In both cases, make the verification result observable outside the software itself and connect the test to the verification plan.

What software-driven RISC-V SoC verification actually tests

At CPU level, verification asks whether the processor implements its required architectural and microarchitectural behaviour. At SoC level, the question becomes broader: does that processor operate correctly when connected to memories, interconnects, peripherals, interrupt logic and system-control functions?

A test programme loaded into a simulation memory and executed by the processor naturally produces instruction fetches, data accesses and control flows. OpenHW’s CORE-V verification documentation uses this model: its test programmes are normally written in C or RISC-V assembler, translated by a toolchain into machine code and then executed by the RTL processor model. [4]

At SoC level, this makes software useful for checking behaviours such as:

  • Reset release followed by successful boot
  • Accesses to the intended memory-map regions
  • Reads and writes to memory-mapped peripheral registers
  • Interrupt generation, routing, entry, servicing and acknowledgement
  • Software-visible system-control functions
  • Sequences involving several IP blocks rather than one isolated interface
  • Representative hardware/software use cases

The important distinction is that software execution is the stimulus mechanism, not the verification argument. Verification still requires an expected result and independent evidence that the hardware behaved correctly.

C or assembly? Choose according to verification intent

There is no general rule that all RISC-V verification software should be written in one language. C and assembly expose different levels of control, and many practical flows use both.

NeedAssembly is usually stronger when…C is usually stronger when…
Instruction controlExact instructions or ordering matterInstruction selection is not the primary test objective
Early bootMinimal runtime assumptions are desirableStartup infrastructure is already trusted
CSR/privileged sequencesPrecise architectural operations are requiredSmall assembly helpers can isolate privileged operations
Peripheral flowsVery small deterministic sequenceLonger, maintainable register-level scenarios are required
Interrupt scenariosEntry/return details are under testHigher-level handler behaviour is under test
System use casesExact control is more important than maintainabilitySeveral peripherals and software operations must be coordinated
DebugA short instruction sequence is easiest to inspectSource-level logic makes a longer scenario easier to understand
ReuseTarget is deliberately architecture-specificCommon libraries and helper functions reduce duplication

Assembly is particularly useful during low-level bring-up because it minimises hidden runtime behaviour. If the test objective is an exact privilege transition, trap sequence or instruction pattern, allowing a compiler to choose the final instruction sequence may obscure the thing being verified. C becomes attractive as scenarios grow. A test that configures a peripheral, enables an interrupt, waits for a hardware event, checks status and then verifies resulting memory contents can become much easier to review and maintain in C.

The most useful pattern is often mixed: a small amount of assembly handles startup or architecture-specific operations while C expresses the main SoC scenario. This hardware/software crossover is explicitly part of Alpinum’s structured RISC-V CPU and SoC verification training, where the SoC section uses C and assembler programmes as part of practical integration, self-checking, coverage and debug work.

Build a verification test around the SCOPE model

A software test should begin with a hardware requirement, not with an attractive piece of firmware to run.

A practical way to review each test is the SCOPE model:

StepQuestion the verification engineer should answer
S: SpecifyWhat exact hardware behaviour or requirement is being verified?
C: CauseHow will C or assembly cause that behaviour through the processor?
O: ObserveAt what trustworthy boundary can the result be observed?
P: Prove pass/failWhat makes the result automatically and unambiguously checkable?
E: EvidenceWhich coverage and debug artefacts demonstrate what happened?

Consider an interrupt-routing test.

“Enable an interrupt and see whether software continues” is too weak. A better verification intent would define the interrupt source, routing configuration, expected privilege/handler behaviour, acknowledgement, resulting peripheral state and the coverage points that must be sampled. The programme can configure the hardware and perform the handler-side operations. The testbench can independently observe the interrupt request, relevant interface activity, completion indication and functional-coverage events.

The same framework applies to boot, reset, peripheral access, system control and other software-driven scenarios.

Make pass/fail explicit

A regression should never depend on an engineer reading a console message and deciding that the output “looks right”. A self-checking test should determine whether its expected result occurred and finish through a defined pass/fail protocol. Common mechanisms include a reserved memory-mapped completion location, a status code or a result/signature region monitored by the testbench. OpenHW’s verification environment, for example, includes virtual peripherals for end-of-simulation status and signature output. [4]

riscv-dv demonstrates the same broader principle through a handshake mechanism. Generated assembly can write status information to a configurable signature address monitored by the RTL environment, including explicit test-pass/test-fail information. [7]

Do not let the DUT be its only checker

There is an important limitation to self-checking software: the code performing the check is itself executing on the device under test.

The riscv-tests documentation makes this limitation explicit. Its tests can use branches to compare calculated and expected values, but it warns that self-checks depend on correct operation of the processor instructions used to implement the checking logic. It therefore recommends additional verification mechanisms and also supports result signatures that can be compared against a golden model. [6]

That gives a useful verification principle:

Software may make the first pass/fail decision, but critical results should also be observable from outside the processor.

Depending on the verification objective, that second layer may be:

  • An interface monitor
  • An assertion
  • A scoreboard
  • An architectural signature
  • An instruction-set reference model
  • Retirement-level lockstep comparison

OpenHW’s UVM environment illustrates the stronger model: results checking can be performed in the environment rather than forcing every test programme to establish its own correctness, while an ISS can provide expected architectural behaviour. [5]

For cases where instruction-by-instruction architectural comparison is required, Alpinum’s guide to RISC-V lockstep co-simulation examines that approach in more detail.

Keep the board-support layer separate from the test intent

A C or assembly programme rarely reaches the hardware without some infrastructure. Even a small bare-metal test may depend on startup code, a linker script, memory placement, exception vectors and target-specific addresses. OpenHW describes this relationship explicitly: its board-support package and linker align the test programme with the resources exposed by the verification environment. [4]

That separation matters for debug.

If the verification test silently combines boot infrastructure, target setup and feature checking into one large programme, a failure can be difficult to classify. A cleaner organisation separates:

  1. Platform/startup infrastructure
  2. Reusable hardware-access helpers
  3. The feature-specific stimulus
  4. The expected-result calculation
  5. Completion and diagnostic reporting

This also makes it easier to determine whether a failure belongs to the DUT, the test, the linker/memory configuration or the software environment. For a broader view of those dependencies, see Alpinum’s article on the RISC-V software ecosystem.

Connect software tests to functional coverage

A passing programme is evidence of one execution. It is not, by itself, evidence that the verification plan is complete. The useful question is not “How many C tests passed?” but “Which required SoC behaviours have those tests demonstrated?”

A requirement-to-test matrix can make that visible:

Verification intentSoftware actionIndependent observationUseful coverage evidence
Reset/bootExecute from reset into known software checkpointBoot address/transactions/completion markerReset type × boot path
Peripheral accessRead/write selected MMIO registersBus/interface monitor and register behaviourPeripheral × access type × response
InterruptConfigure source and enable pathInterrupt assertion, handler entry and acknowledgementSource × route × privilege/handler state
System controlProgramme control register and exercise resulting behaviourAssertions/interface effectsControl setting × resulting mode
Error handlingCause supported error conditionTrap/status/error interfaceError source × handling path
Multi-IP use caseExecute a defined sequence across blocksEnd-to-end scoreboard/checkpointsUse case × subsystem interaction

Functional coverage should represent verification intent rather than merely count software execution. If a test passes while an expected coverage bin remains unhit, one of several things may be wrong: the stimulus did not create the intended hardware condition, the coverage monitor sampled the wrong point, or the requirement-to-coverage mapping was incomplete.

Alpinum’s separate article on functional coverage for RISC-V verification should continue to own the deeper coverage-modelling subject. For the complementary problem of creating random and directed stimulus to close holes, see RISC-V test generation for coverage closure.

A practical debug workflow for software-driven SoC tests

Software-driven failures can cross several abstraction layers. The fastest debug path is therefore usually to classify the failure before opening thousands of waveform signals.

Observed symptomInvestigate firstThen inspect
Test never reaches completionreset, boot address, image loading, linker/memory maptraps, PC flow, bus access
Early unexpected trapexception cause, privilege assumptions, memory regionoffending instruction and address
Wrong result/signaturefirst incorrect checkpointbus transactions, register/CSR state, reference result
Interrupt never handledenable/configuration and source assertionrouting, masking, handler entry and acknowledgement
Test passes but coverage remains emptywhether intended condition actually occurredmonitor sampling and coverage definition
Assembly passes, equivalent C failsstartup/runtime/compiler-generated sequenceABI assumptions, memory placement and generated assembly
Failure changes with optimisation/buildcompiler-visible assumptions and memory access semanticsgenerated code and ordering
Regression hangs intermittentlymissing timeout, polling and asynchronous event assumptionsevent ordering and uninitialised state

Preserve enough evidence to reproduce the failure. That normally means retaining the binary/ELF identity, relevant build configuration, simulator configuration, test name, seed where applicable, failure code and the smallest useful execution trace. For architectural failures, finding the first point of divergence is often substantially more useful than analysing the final corrupted state. A reference model or lockstep environment can help with this because it compares state at defined architectural boundaries rather than waiting until the end of the programme.

The same principle appears in OpenHW’s UVM flow, where integrated reference-model checking provides instruction-level comparison intended to improve failure localisation. [5] Alpinum’s analysis of the five places RISC-V projects lose verification time provides the wider programme-level context for model alignment, coverage and system-integration debug.

Common mistakes that weaken C and assembly verification

Treating firmware bring-up as verification

A programme that boots, prints a message and accesses a peripheral can be an excellent bring-up milestone. It is not automatically a verification test.

Verification requires an explicit requirement, expected outcome and pass/fail evidence.

Using C where the exact instruction sequence is the test

Compilers are designed to translate language semantics efficiently, not to preserve an engineer’s preferred instruction sequence.

If exact instructions, privilege transitions, fences or other architectural details are the verification target, assembly or tightly controlled low-level helpers are usually more appropriate.

Making the self-check the only oracle

If a processor defect can also corrupt the branch, load, store or other operation used by the software checker, a false pass becomes possible.

Layer software checking with external observation when the risk justifies it.

Confusing architectural certification with design verification

RISC-V’s current Architectural Certification Tests are assembly-language tests intended to certify architectural implementation. Their own documentation explicitly states that they are not complete verification tests and that additional verification is required. [3]

SoC integration introduces requirements outside architectural certification: memory-map behaviour, peripherals, reset, interrupts, system controls and implementation-specific interactions still need verification.

Allowing infinite polling

Every regression test needs a bounded failure path.

The older riscv-tests framework explicitly recommends a timeout facility so that a test that never completes is classified as a failure rather than leaving the simulation indefinitely active. [6]

Trying to solve every problem in software

Software-driven verification is only one part of the verification environment. Assertions may be better for temporal and protocol relationships. UVM agents may be better for controllable interface stimulus. Reference models may be stronger for architectural checking. Formal methods can explore behaviours that are difficult to reach through simulation.

Alpinum’s guide to formal verification in RISC-V verification covers that complementary evidence source.

What a regression-ready software test should demonstrate

Before adding a C or assembly programme to a sign-off regression, a review should be able to answer these questions:

  1. Which requirement or verification-plan item does the test address?
  2. Why is processor-executed software an appropriate way to create the stimulus?
  3. Why was C, assembly or a mixed implementation chosen?
  4. Which startup, linker and target assumptions does the test depend on?
  5. What is the expected result?
  6. How is pass/fail communicated automatically?
  7. Is any critical outcome checked independently of DUT-executed software?
  8. What timeout detects non-completion?
  9. Which functional-coverage points should the test exercise?
  10. What evidence is retained when the test fails?
  11. Can a failure be reproduced with the same binary and environment configuration?
  12. What does this test deliberately not verify?

That final question is important. Good verification tests have boundaries. A software-driven peripheral test should not silently become evidence that every internal implementation state has been verified.

From software execution to verification evidence

Using C and assembly for RISC-V SoC verification is effective because software can exercise the processor and surrounding hardware through realistic system paths. The engineering challenge is turning that execution into evidence. Assembly provides precise control. C provides scalable system-level expression. Self-checking provides automation. External monitors and reference models strengthen the oracle. Functional coverage shows whether planned behaviour occurred. A disciplined debug path makes failures actionable.

None of these techniques is sufficient alone.

Teams building this capability should therefore treat software-driven testing as one layer of a broader verification strategy rather than as a replacement for UVM, assertions, reference-model checking or formal analysis. For engineers who want to practise this workflow in a structured environment, Alpinum’s RISC-V Verification Training course includes RISC-V C and assembly execution, open-source verification environments, self-checking tests, functional coverage and SoC debug exercises.

References

[1] RISC-V International, “The RISC-V Instruction Set Manual, Volume I: Unprivileged Architecture,” Version 20260120, 2026. [Online]. Available:https://docs.riscv.org/reference/isa/v20260120/unpriv/unpriv-index.html 

[2] RISC-V International, “The RISC-V Instruction Set Manual, Volume II: Privileged Architecture,” Version 20260120, 2026. [Online]. Available: https://docs.riscv.org/reference/isa/v20260120/priv/priv-index.html 

[3] RISC-V International, “RISC-V Architectural Certification Tests,” riscv-arch-test, 2026. [Online]. Available:https://github.com/riscv/riscv-arch-test. The current ACT documentation explicitly distinguishes architectural certification from complete processor verification.

[4] OpenHW Group, “Test Programs — CORE-V Verification Strategy,” 2026. [Online]. Available:https://docs.openhwgroup.org/projects/core-v-verif/en/latest/test_programs.html 

[5] OpenHW Group, “UVM Testcases in the CORE-V-VERIF Environments,” 2026. [Online]. Available:https://docs.openhwgroup.org/projects/core-v-verif/en/latest/uvm_tests.html 

[6] RISC-V Software Source, “riscv-tests,” [Online]. Available: https://github.com/riscv-software-src/riscv-tests. The documentation covers explicit pass/fail, self-checking limitations, result signatures and timeout requirements.

[7] CHIPS Alliance, “riscv-dv: Random Instruction Generator for RISC-V Processor Verification,” [Online]. Available: https://github.com/chipsalliance/riscv-dv. Its documented capabilities include functional coverage, testbench handshaking, hand-coded assembly, interrupt/debug handling and ISS co-simulation.

[8] CHIPS Alliance, “RISCV-DV Testbench Handshake,” [Online]. Available: https://github.com/chipsalliance/riscv-dv/blob/master/docs/source/handshake.rst 

FAQs

Can C be used to verify a RISC-V SoC, or is assembly required?

Both are useful. C is well suited to longer system-level scenarios and peripheral flows, while assembly provides tighter control over instructions, startup and architecture-specific behaviour. Many verification flows benefit from combining them.

What makes a RISC-V software test self-checking?

A self-checking test evaluates an expected result automatically and produces an unambiguous pass/fail indication. For stronger verification, important outcomes should also be observable outside the DUT through a monitor, signature, assertion, scoreboard or reference model.

How should software-driven SoC tests contribute to functional coverage?

Each test should map to defined verification-plan scenarios or requirements. Coverage should record whether the intended hardware behaviour occurred, rather than simply recording that a C or assembly programme executed.

Are RISC-V Architectural Certification Tests enough to verify an SoC?

No. The ACT project itself states that its architectural certification tests are not complete verification tests. SoC-specific integration, peripherals, reset, interrupts, system controls and implementation-specific behaviours require additional verification.

When is a reference model or lockstep comparison preferable to a self-checking test?

It is particularly useful when architectural state must be compared independently and when locating the first incorrect retired instruction is more informative than observing only a final pass/fail result.

How should a RISC-V test that hangs be debugged?

First determine whether the test reached reset release, its boot address and expected checkpoints. Then inspect traps, memory-map assumptions, interrupt/event dependencies and polling loops. Every regression test should also have a bounded timeout.

Share This Article
Persian Pick
Written by : Mike Bartley

Mike started in software testing in 1988 after completing a PhD in Math, moving to semiconductor Design Verification (DV) in 1994, verifying designs (on Silicon and FPGA) going into commercial and safety-related sectors such as mobile phones, automotive, comms, cloud/data servers, and Artificial Intelligence. Mike built and managed state-of-the-art DV teams inside several companies, specialising in CPU verification.

Mike founded and grew a DV services company to 450+ engineers globally, successfully delivering services and solutions to over 50+ clients.

Mike started Alpinum in April 2016 to deliver a range of start-of-the art industry solutions:

Alpinum AI provides tools and automations using Artificial Intelligence to help companies reduce development costs (by up to 90%!) Alpinum Services provides RTL to GDS VLSI services from nearshore and offshore centres in Vietnam, India, Egypt, Eastern Europe, Mexico and Costa Rica. Alpinum Consulting also provides strategic board level consultancy services, helping companies to grow. Alpinum training department provides self-paced, fully online training in System Verilog, UVM Introduction and Advanced, Formal Verification, DV methodologies for SV, UVM, VHDL and OSVVM and CPU/RISC-V. Alpinum Events organises a number of free-to-attend industry events

You can contact Mike (mike@alpinumconsulting.com or +44 7796 307958) or book a meeting with Mike using Calendly (https://calendly.com/mike-alpinum-consulting).

Connect With Us

We understand that you might have a unique situation that you would like to discuss with us, or just be curious to learn more about our service offerings. Regardless, we would like to hear from you – please feel free to contact us.