Instruction Tracing
November 28, 2025 · View on GitHub
Introduction
Instruction tracing is a crucial feature in modern processor design, particularly when it comes to certification, debugging, and formal verification. It allows engineers to track the exact sequence of executed instructions, providing a ground truth that can be compared against expected behavior.
Why Instruction Tracing?
There are several scenarios where instruction tracing is essential:
- Certification and Compliance: In safety-critical systems (e.g., avionics, automotive), it may be necessary to prove that instructions executed exactly as specified.
- Debugging: When an execution does not behave as expected, a trace can help reconstruct the execution flow and understand where and why things went wrong.
How Does It Work?
In this project, we implemented an instruction tracer that uses the E-TRACE encoding format (as specified in the E-Trace specification).
E‑Trace implements a branch‑trace algorithm: it only emits control‑flow "deltas" (branches taken/not‑taken, indirect jumps/calls/returns, interrupts/exceptions) along with PC deltas, assumes sequential instructions implicitly, and can pack multiple retirements into compact packet, achieving a high compression rates while minimizing off‑chip bandwidth and interface complexity.
There exist other encoding formats such as N‑Trace, which is built on the IEEE‑5001 Nexus standard.
System Overview
The tracing system we built is composed of four main elements, illustrated in the block diagram below:
1. Instruction Trace Interface (ITI)
The Instruction Trace Interface (ITI) module is implemented in accordance with the E-Trace specification (2025), specifically section 4.2 concerning the Instruction Trace Interface. It sits between the cva6_rvfi (RISC-V Formal Interface) and the encoder, and plays a role in collecting and formatting instruction trace data.
The ITI currently supports two operating modes:
- Multiple-retirement mode(
block_mode = 1): the trace data for several instructions is grouped together and sent to the encoder as a block. - Single-retirement mode(
block_mode = 0): the trace data are sent to the encoder instructions by instructions.
At the moment, the ITI only supports the 3-bit itype format (itype_len = 3) for classifying instruction types. This is sufficient to distinguish key instruction categories such as branches, uninferable jumps, exceptions, and interrupts. While the E-Trace specification also defines a 4-bit format (itype_len = 4) for finer-grained classification, this is not yet implemented in the current design. Support for the 4-bit version could be added later if needed and if compatible with the encoder.
For more details about the
itypefield, refer to Table 7 of the E-Trace specification, as well as the field description provided just below.
Input Interface
The module receives a structured input named rvfi_to_iti, which encapsulates all the necessary signals to:
- determine the instruction type (
itype) using fields such asopcode,branch_valid, etc., - supply relevant metadata to the encoder like
cycle,privilege level,pc, etc.
Changes were also made to the cva6_rvfi module to collect the signals required by the ITI and to properly resynchronize them, since they may originate from different stages of the processor pipeline. This justifies the use of a structured interface to ensure reliable and consistent data transfer to the ITI.
Multiple-Retirement Mode
In multiple-retirement mode, the ITI groups instructions into blocks, based on the occurrence of special instructions , typically those altering the control flow (e.g., jumps, exceptions, interrupts).
The ITI outputs the following signals to the encoder in this mode:
iret: number of half-words for the retired instructions in the current block,iaddr: address of the first instruction in the block,ilastsize: size of the last instruction in the block.
To support this, the ITI adopts a systolic-like architecture made up of block_retirement submodules. There are Nr_commit_ports such submodules, each able to:
- detect special instructions independently,
- initiate a new block if necessary,
- propagate control and data signals (like
special,iaddr, and incrementediret) along a chain.
This design enables the generation of multiple blocks in a single cycle when several special instructions retire simultaneously.
Single-Retirement Mode
In single-retirement mode, the ITI module forwards the trace information instruction by instruction, without grouping them into blocks.
In this configuration:
- There is no need to track special instructions or segment blocks.
- The encoder receives a simple feed of retired instructions, making this mode well-suited for simpler architectures.
Full System Overview
The overall role of the ITI module in the trace infrastructure is summarized in the following diagram:
Continuous Integration for ITI
The CI (Continuous Integration) test for the ITI is contained in Instr_tracing_test.sh and consist of running a simulation on ariane_testharness with the previous test iti_test.sh and extracting the iti.trace. Then it run the regressionFlow from the E-Trace specification and compare both files. We cleaned both file to compare what is comparable ,we exclude the start and the end because it's not matching based spike version
2. Encoder + Encapsulator
Collaboration and Reference Design
For this module, we collaborate with the University of Bologna by integrating their version of the encoder, available here
Their repository is well-documented and serves as a solid foundation. The encoder design is aligned with the E-Trace specification and leverages an ITI running in multiple-retirement mode to emit trace packets. These packets are structured according to the instruction delta trace algorithm described in the E-Trace specification (Figure 1), which provides a model for encoding deltas (non-linearities) in the control flow.
Each data trace outputed by the ITI, whether block-based or instruction-based, is interpreted to identify these deltas and transmit them in the form of trace packets. These packets vary in format and size depending on the instruction type and control flow transitions.
Custom Implementation and Simplification
While their encoder provides a baseline, it remains under active development. In its current state, its behavior (in terms of number of packets emitted, their contents, or supported options) differs from the Reference Flow.
As a result, we opted to build a simplified version derived from the original architecture. This internal encoder:
- Expects instruction-by-instruction traces as input,
- Supports only Nr_commit_ports = 1 (single commit port),
- Implements only a basic subset of the E-Trace functionality.
This choice facilitates initial validation and integration, though we remain open to switching back to a mature multiple-retirement encoder once available.
Below is a high-level architectural diagram of the current encoder setup (here N=1):
Submodules Description
The encoder is composed of several key submodules:
Submodules Description
The encoder is composed of several key submodules:
-
Priority:
This submodule constitutes the core of the Instruction Delta Trace Algorithm. It is responsible for analyzing control flow transitions across instructions and determining the format and/or subformat of trace packets to be emitted.The logic is driven by three categories of input signals that represent the state of the instruction stream at different points in time:
- lc: Last Cycle - information from the previous retired instruction,
- tc: This Cycle - information from the currently retiring instruction,
- nc: Next Cycle - information from the next instruction.
Some signals are intentionally absent for the sake of understanding
Using these signals, the
Prioritymodule identifies the relevant deltas (e.g., taken branches, jumps, exceptions) and selects the appropriate packet format and/or subformat, which will subsequently guide thePacket_Emitter.Format Subformat Description 3 3 Support 2 Context 1 Trap 0 Synchronisation 2 Address Only 1 Branch 0 Optional extensions For more details, refer to Chapter 7 (Instruction Trace Encoder Output Packet) (2025), or consult the corresponding encoder section in the University of Bologna's implementation.
-
Packet_Emitter:
This submodule is responsible for constructing and filling the trace packet to be emitted. It plays a central role in materializing the output of the delta trace algorithm into a well-structured packet format compliant with the E-Trace specification.Similar to the
Prioritymodule, it receives multiple input signals that span across different time domains (lc,tc,nc).However, the most important inputs for this submodule is the packet format and/or subformat, which is deteminated by the
Prioritymodule. This format/subformat dictates the structure and contents of the packet's payload. Also ppccd means privilege change, precise context change or context change with discontinuity.Based on this, the
Packet_Emitteroutput:- The payload which contains all the fields expected for this packet,
- The packet length (in bytes), depending on the format/subformat and data,
- A branch_map_flush signal that is used to flush the
branch_mapfield. This ensures thebranch_mapis ready to accurately track future branches for the next packet.
For a complete description of each packet type and its associated payload content, refer to Chapter 7 (Instruction Trace Encoder Output Packet) of the E-Trace specification (2025).
-
Filter:
TheFiltersubmodule is responsible for selectively enabling or disabling instruction tracing (with the qualified signal) based on configurable events defined through Control Registers. By filtering out irrelevant or redundant instructions, it can significantly improves trace analysis efficiency and bandwidth usage.The filtering mechanism supports a variety of events, including:
- Range Address: Enables tracing only when the instruction address (
iaddr) falls within a specified address window, defined by alower_addrand ahigher_addrboundary. - Stop Event: Immediately halts instruction tracing when triggered.
- Selected Privilege Level: Enables or disables tracing depending on the current privilege level (e.g., user, supervisor, machine mode).
- Other Events: Filtering can also be triggered by exceptions, interrupts, or other context-specific conditions.
- Range Address: Enables tracing only when the instruction address (
-
Branch_Map:
TheBranch_Mapsubmodule acts as a history buffer for recently executed branch instructions. It plays a crucial role in the generation of compressed trace packets by allowing the encoder to reconstruct control-flow paths through a compact representation.
This module maintains two key outputs:
-
branch_map: a bitfield representing the history of up to 31 conditional branches. For each branch, a bit is added to the right:0if the branch was taken,1if the branch was not taken.
-
branches: an integer count of how many branches are currently stored in the map (max: 31). This counter helps determine the valid width of thebranch_mapfield and guides packet formatting/decoding.
The branch_map is updated as instructions retire. Once the maximum depth is reached or a flush is triggered (e.g., via
branch_map_flushfrom thePacket_Emitter), the map is cleared to allow tracking of the next sequence.Here is an illustrative example, taken from E-Trace Specification – Chapter 12, Example 4.
-
Encapsulation Module
This module was developed by the University of Bologna, and we reused the encapsulator.sv module as-is, without any modifications. We chose not to integrate the other submodules provided in the original repository, as they are designed for the ATB (Advanced Trace Bus) protocol. In our case, we rely on a different trace output interface, named DPTI (Digilent Parallel Transfer Interface), making the use of those ATB-specific modules unnecessary. Here is the repository of the encapsulator from the University of Bologna.
The encapsulator is placed directly after the encoder and is responsible for wrapping each trace packet into a standardized encapsulation format as defined by the encapsulation specification.
This format ensures interoperability, provides framing rules, and facilitates packet-level synchronization in the transmission channel interface. Each encapsulated packet in this implementation is fixed at 320 bits (40 bytes) and includes this field groups:
-
Header (8 bits):
Includes alengthfield (5 bits) specifying the payload size in bytes, aflowfield (2 bits) which is currently set to00(no multi-sink used yet), and anextendbit (1 bit) which is set to1, indicating the presence of a timestamp (T ? in the figure bellow). -
Timestamp (
T * 8bits):
The timestamp is included in our packets and is fixed at 8 bytes (64 bits). Its presence is governed by theextendbit in the header. -
Payload (1–248 bits):
Contains the actual trace packet, emmited by the encoder withpacket_payload_o.
An illustration of the encapsulation structure is shown below:
Once encapsulated, the packets are written into a FIFO buffer, where they await transmission through the trace communication channel.
This design enables modular evolution: improvements or full spec-compliance in the encoder can be adopted later without altering the overall encapsulation and transmission stages.
While the encapsulation specification also defines support for source IDs , null packets (e.g.,
null.idle,null.alignment), and synchronization sequences, these features are not yet implemented in the current prototype.
Continuous Integration for Encoder + Encapsulator
The CI (Continuous Integration) test for the Encoder and Encapsulator is also contained in Instr_tracing_test.sh and consists in running a simulation on ariane_testharness with the previous test iti_test.sh and extracting the encoded.trace, it will convert them in decaspulated csv format with the Decapsuler. Then it runs the regressionFlow from the E-Trace specification and compare both files.
We cleaned both file to compare what is comparable, again we exclude the start and the end because it's not matching based spike version.
3. Transmission Channel (TC) via DPTI
Figure: Digilent Genesys2 development board
Picture from Digilent Reference Manual
Since most onboard peripherals (UART, SPI, etc.) are already used by the ariane_xilinx platform, we use the FTDI FT2232H's DPTI interface exposed on the USB-JTAG bridge (J17):
Figure: USB-JTAG bridge
Picture from Digilent Reference Manual
- DPTI/DSPI header on J17 provides:
- A Digilent Parallel Transfer Interface (DPTI) channel
- A generic SPI channel (not used here due to limited bandwidth)
We selected DPTI because it is a mature, out‑of‑the‑box solution full driver support from Digilent Adept (Adept Drivers & Utilities).
FT2232H Ports in Genesys 2
The FT2232H chip offers two independent channels but in the Top platform ariane_xilinx both of them are used :
| Port | Default Use in ariane_xilinx |
|---|---|
| Port A | Bitstream injection |
| Port B | Debug Module interface |
Because both channels are occupied, we have two deployment options:
-
Re-purpose Port B
- Disconnect the Debug Module in
ariane_xilinx.sv. - Update
genesys.xdcto add DPTI pin constraints (e.g., check the default constraint file section DPTI). - Inject your program via the on‑chip bootrom, modify the Makefile in
corev_apu/fpga/src/bootromaccordingly.
- Disconnect the Debug Module in
-
Add an FT2232H MiniModule
- Use a standard FT2232H Mini Module connected to two PMOD headers.
- This gives you two additional FT2232H channels without touching the existing Port A/B setup.
Figure: FT2232H MiniModule wired to Genesys2 PMODs
For more details on the wiring, check these resources:
- FT2232H Datasheet, Table 3.1
- FT2232H Mini Module Datasheet, Table 3.1 and Table 3.2 and USB Bus-powered section.
Platform Modifications
-
DPTI Controller
- Add
dpti_ctrl.vhdto handle frame send/receive over DPTI. This module is provided by Digilent and can be found in the Digilent Adept SDK, undersamples/dpti/DptiDemo/logic/dpti_ctrl.vhd. - Protocol: 8-bit parallel, synchronous/asynchronous, running at 60 MHz, allowing for up to 480 Mbps throughput.
- The controller requires a clock. It is instantiated in the platform using
xlnx_dpti_clklocated incva6/corev_apu/fpga/xilinx/xlnx_dpti_clk. Alternatively, it can be added to the Vivado project using a Clock Wizard. An example configuration is available insamples/dpti/DptiDemo/logic/clk_wiz_0.v.
- Add
-
Packet Slicer
- Our trace packets are 320 bits (40 bytes).
- Insert
slicer_DPTI.svto segment each packet into 40 eight‑bit frames for DPTI transport.
-
PMOD Pin Mapping & Constraints
- PMOD pins used must support external clock (C‑capable). Follow UG472 (Vivado) guidelines.
- Example excerpt from
genesys2.xdc:## PMOD Header JA #set_property -dict { PACKAGE_PIN U27 IOSTANDARD LVCMOS33 } [get_ports prog_clko]; #IO_L13P_T2_MRCC_14 Sch=ja_p[1] #set_property -dict { PACKAGE_PIN U28 IOSTANDARD LVCMOS33 } [get_ports prog_oen]; #IO_L13N_T2_MRCC_14 Sch=ja_n[1] #set_property -dict { PACKAGE_PIN T26 IOSTANDARD LVCMOS33 } [get_ports prog_rdn]; #IO_L12P_T1_MRCC_14 Sch=ja_p[2] #set_property -dict { PACKAGE_PIN T27 IOSTANDARD LVCMOS33 } [get_ports prog_rxen]; #IO_L12N_T1_MRCC_14 Sch=ja_n[2] #set_property -dict { PACKAGE_PIN T22 IOSTANDARD LVCMOS33 } [get_ports prog_siwun]; #IO_L5P_T0_D06_14 Sch=ja_p[3] #set_property -dict { PACKAGE_PIN T23 IOSTANDARD LVCMOS33 } [get_ports prog_spien]; #IO_L5N_T0_D07_14 Sch=ja_n[3] // Not needed here #set_property -dict { PACKAGE_PIN T20 IOSTANDARD LVCMOS33 } [get_ports prog_txen]; #IO_L4P_T0_D04_14 Sch=ja_p[4] #set_property -dict { PACKAGE_PIN T21 IOSTANDARD LVCMOS33 } [get_ports prog_wrn]; #IO_L4N_T0_D05_14 Sch=ja_n[4] ## PMOD Header JB #set_property -dict { PACKAGE_PIN V29 IOSTANDARD LVCMOS33 } [get_ports { prog_d[0] }]; #IO_L17P_T2_A14_D30_14 Sch=jb_p[1] #set_property -dict { PACKAGE_PIN V30 IOSTANDARD LVCMOS33 } [get_ports { prog_d[1] }]; #IO_L17N_T2_A13_D29_14 Sch=jb_n[1] #set_property -dict { PACKAGE_PIN V25 IOSTANDARD LVCMOS33 } [get_ports { prog_d[2] }]; #IO_L18P_T2_A12_D28_14 Sch=jb_p[2] #set_property -dict { PACKAGE_PIN W26 IOSTANDARD LVCMOS33 } [get_ports { prog_d[3] }]; #IO_L18N_T2_A11_D27_14 Sch=jb_n[2] #set_property -dict { PACKAGE_PIN T25 IOSTANDARD LVCMOS33 } [get_ports { prog_d[4] }]; #IO_L14P_T2_SRCC_14 Sch=jb_p[3] #set_property -dict { PACKAGE_PIN U25 IOSTANDARD LVCMOS33 } [get_ports { prog_d[5] }]; #IO_L14N_T2_SRCC_14 Sch=jb_n[3] #set_property -dict { PACKAGE_PIN U22 IOSTANDARD LVCMOS33 } [get_ports { prog_d[6] }]; #IO_L21P_T3_DQS_14 Sch=jb_p[4] #set_property -dict { PACKAGE_PIN U23 IOSTANDARD LVCMOS33 } [get_ports { prog_d[7] }]; #IO_L21N_T3_DQS_A06_D22_14 Sch=jb_n[4] - Ensure V3V3 pins are tied to VIO in the MiniModule.
-
FT2232H Configuration
- Use FT_Prog (or equivalent) to set the MiniModule into USB FIFO mode.
- ⚠️ Digilent Adept drivers validate a protected EEPROM region not writable via FT_Prog.
- Either use Digilent reprogramming executable
- Or adapt the C++ receiver to be compatible with the FTDI drivers.
Figure: Valid EEPROM for DPTI
Bitstream Build & Deployment
Once hardware wiring and constraints are in place with ITI, Encoder, Encapsulator, etc. :
We used Vivado version: 2018.2
make clean
make fpga TARGET=cv32a6_imac_sv32
Then upload the generated ariane_xilinx.bit via Vivado Lab or your preferred hardware manager.
4. Software-Side Reception
The trace reception is handled on a Linux-based host PC, which is connected either to the FTDI MiniModule or directly to the Genesys2 board via port J17.
To enable communication via DPTI (Digilent Parallel Trace Interface), you must first install the Digilent Adept SDK, specifically version 2.4.2. This provides the required runtime libraries and development headers. The receiver code needs a C++17 version for std::optional.
Setup Instructions
-
Install Adept SDK
- Ensure the Digilent API is installed and correctly configured.
- The official Digilent SDK and documentation provide a guide.
-
Configure and Compile the Receiver
-
The receiver code is located in
Receiver_DPTI.cpp. -
You must edit the
Makefileto match the paths to the Adept libraries and headers.- Refer to the README files in
digilent.adept.sdk_2.4.2/samplesfor more informations .
- Refer to the README files in
-
Then compile the program with:
make clean make
-
-
Device Detection and Debugging
-
The receiver includes debug function, such as
Scan_Device, which:- Lists connected FTDI devices
- Displays their names
-
-
Configure BOARD_NAME
-
You must update the
BOARD_NAMEfield inReceiver_DPTI.cppto match the actual serial string of your connected device. -
Example:
char* BOARD_NAME = (char*) "#tpt_0001#ptc_0002#210300075227"; -
After editing the file, recompile:
make clean make
-
-
Running the Receiver
-
Launch the receiver with:
./Receiver_DPTI -
The program proceeds in two phases:
- Acquisition Phase: Data is streamed and stored in raw format. Stop the acquisition with
SIGINT(Ctrl+C). - Decapsulation Phase: The raw binary packets are parsed and converted into human-readable CSV format.
- Acquisition Phase: Data is streamed and stored in raw format. Stop the acquisition with
-
Figure: Valid execution ./Receiver_DPTI
Output Files
Two output files are generated in the receiver_data/ directory:
YYYYMMDD_hhmm_raw_file.txt- the raw encapsulated packetsYYYYMMDD_hhmm_data.csv- the decoded content in CSV format
These CSV files can be supplied to a decoder for further analysis.
Currently, no custom decoder is implemented. We use the reference decoder provided in the Reference Flow repository to interpret the trace contents. The University of Bologna released a version of their decoder on 3rd July 2025, but we have not evaluated it yet.
5. Decoder
At this stage, we have not yet implemented our own decoder. Instead, we rely on the Reference Flow decoder provided by the E‑Trace specification.
Running the Reference Flow
To perform decoding, you first need to run the full reference flow regression so that it can generate the files from the program binary required by its version of Spike(in the referenceFlow). In addition, your compiled program must be renamed with a .riscv extension so that the flow recognizes it as RISC‑V input and it need to be place in referenceFlow/tests/test_files.
- Prepare your binary
cp path/to/your_program.elf referenceFlow/tests/test_files/your_program.riscv - Execute the full reference flow
cd referenceFlow
./scripts/run_regression.sh \
-t itype3_debug \
--annotate \
--debug\
tests/test_files/your_program.riscv
-t: Specify the test suite used, here we chooseitype3_debugto match our encoder and_debugto add more information in packets likefull_address mode--annotate: This flag is optionnal and using it create annotated version of output files , usefull for understanding/debugging--debug: This flag is optionnal and using it will print in the terminal more informations about each stage
Decoding the Trace
Once the reference flow regression has run successfully (and generated the files needed), you can invoke the decoder step:
cd referenceFlow
./scripts/decoder_model.py \
-u regression_YYYYMMDD_hhmmss/itype3_debug/your_program.decoder.ucf \
-c regression_YYYYMMDD_hhmmss/itype3_debug/hardware.scf \
-i TRACE_OUTPUT
-u: user_config, this file is generated with the first regression, inside there are information about debug flags, object-files, encoder configuration, etc-c: static_config, this file is also generated but correspond to test suite you choose, inside there are informations about default values (size of fields, encoder options, etc)-i: This file is the output of the encoder by default :regression_YYYYMMDD_hhmmss/itype3_debug/your_program.te_inst_rawbut you can use the trace from the receiver, it support CSV format and raw format (needs to be a real binary in this case)
If everything matches, meaning there are no difference between the captured trace and the reference Trace, you will obtain an output file like this:
Figure: Excerpt from decoded trace
⚠️ Mismatches can occur if, for example:
-
The platform's address map (CrossBar) doesn't align with your binary's load addresses
-
The encoder fails to emit a packet (e.g., due to a bug in the encoder)
-
There is receiver desynchronization or signal glitches during trace capture
Current Limitations & Future Work
When we load the program via the onboard Debug Module, you will notice "trace pollution" : extra instructions corresponding to debug and JTAG activity appear in the trace. One way to mitigate this is to use the Filter submodule in the encoder to suppress unwanted instructions. However:
Problem: We have not yet exposed the APB path needed to drive the Filter's control registers at runtime.
Next Steps: Extend the encoder to include an APB interface for the Filter module, allowing dynamic trace filtering before encapsulation.
6. Debug Automation & VSCode Setup
This section presents a set of automation tools and configurations developed to streamline the debug process using Visual Studio Code (VSCode). The goal is to make it easy to:
- Compile user programs into a binary format compatible with the CV32A6 core
- Launch OpenOCD using the expected configuration (
ariane.cfg) - Start GDB in remote debug mode, attached to the target via OpenOCD
VSCode Integration
To integrate these operations directly within the VSCode interface, we make use of two special configuration files:
.vscode/launch.json- defines how to start a debugging session.vscode/tasks.json- defines custom build and terminal commands
With this setup, you can use the built-in debug pane of VSCode to:
- Upload and connect to the board
- Step through code using breakpoints and GDB integration
These files must be placed inside a .vscode/ directory at the root of the project.
File Overview
launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug FPGA via OpenOCD",
"type": "cppdbg",
"targetArchitecture": "x86_64",
"request": "launch",
"program": "${workspaceFolder}/Prog_compiled",
"miDebuggerServerAddress": "localhost:3333",
"useExtendedRemote": true,
"miDebuggerPath": "toolchain/bin/riscv-none-elf-gdb",
"stopAtEntry": true,
"stopAtConnect": true,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"preLaunchTask": "build-and-start-openocd",
"postRemoteConnectCommands": [
{
"text": "source ${workspaceFolder}/gdb_init.txt"
}
]
}
]
}
tasks.json
{
"version": "2.0.0",
"tasks": [
{
"label": "build-exec",
"type": "shell",
"command": "riscv-none-elf-gcc",
"args": [
"-g", "-T", "custom_linker.ld",
"-march=rv32imac_zicsr",
"-lgcc", "-fvisibility=hidden",
"-mcmodel=medany", "-mabi=ilp32",
"-nostartfiles", "crt.S", "syscalls.c", "test.c",
"-o", "Prog_compiled"
],
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "start openocd",
"type": "shell",
"command": "bash",
"args": [
"-c",
"nohup gnome-terminal -- bash -c \"openocd -f ariane.cfg; exec bash\""
],
"problemMatcher": []
},
{
"label": "build-and-start-openocd",
"dependsOn": ["build-exec", "start openocd"]
}
]
}
These configurations are adaptable to your own build system or directory structure.