Snippy Documentation (2.1.0)

Overview

LLVM-snippy is a random test generator (RTG) based on LLVM. It currently only supports RISC-V, but is potentially target-independent.

LLVM-snippy generates snippets – sequences of randomly selected machine instructions with randomly selected registers, immediates, and other attributes that you configure via histograms and organize via sections.

Important

The examples in this guide are currently for RISC-V only, even for the features that are target-independent. We will be adding more examples once more targets become available.

Disclaimers on Versioning

LLVM-snippy versioning works as follows:

  • Settings that are compatible with one minor version are always compatible with any other minor version. Example: any configuration that works in 1.3, will work in 1.7, and any newer 1.x version.

  • The same snippy version with the configuration that has the same seed value for one platform works for any other platform. Example: any configuration for snippy 1.7 and seed=42 will generate the same snippet on Ubuntu and CentOS.

  • Different snippy versions for the same seed value do not generate the same snippet. Example: the same configuration with seed=42 will generate different snippets for snippy 1.3 and 1.7.

Important

No ISG is a ready-made verification solution. Implementing any solution requires additional work.

Input

You run snippy based on one or more YAML configuration files you provide. In the configuration file(s), you specify:

  • Memory sections available for read, read and write, read and execute

  • Histogram of the probabilities of instructions

  • Memory access

  • Branches

  • Options

  • Initial state

If you run snippy based on more than one YAML file, it will concatenate all of the specified and included files and parse it as a single YAML document. As snippys configuration consists of several top-level YAML keys each of them should appear no more than once through all of the input files.

Note

You can print resulting “preprocessed” configuration using -E option

Output

The snippy output includes:

  • An relocatable ELF file with a generated snippet. File name is specified via -o option (default is the name of your config file with “.elf” suffix appended). To properly use the generated snippet, you must link against it using the generated linker script (see below).

  • An optional execution trace of generated snippet. The trace is only generated if simulator model was provided. You can specify the file to output trace with -trace-log option (Default is stdout).

  • A linker script file. The name of file equals to the name of generated ELF file with “.elf” suffix replaced with “.ld”. It is required, for example, with global constants.

  • Dump of the registers state after execution. To produce the register state use –dump-registers-yaml option. See Final Registers State.

  • Dump of the registers state before execution. To produce the the register state use –dump-initial-registers-yaml option. See Dumping Initial Registers State.

Running Snippy

You run snippy using the following as an argument or arguments:

  • A single YAML file. This approach is preferred. You specify all the configuration details in one file and use it as a single argument to run snippy on without having to provide numerous settings via the command line.

  • Two or more YAML files. When running, snippy merges the data from the files together and uses the resulting configuration as the layout for your output snippet.

You can see all available snippy parameters and options in its embedded help. To open it, run:

llvm-snippy --help

To run snippy:

  1. Create one or more configuration files with all the required data in the snippy root directory. The mandatory keys of a snippy configuration are sections and histogram.

  2. Add the “options” key with the options to run snippy on.

  3. Run the configuration file(s):

    llvm-snippy <config>.yaml
    

    where <config> is the name of the configuration file you created. If you have more than one configuration file you want to run, list them as follows:

    llvm-snippy <config1>.yaml <config2>.yaml <config3>.yaml
    
  4. If necessary, print the output.

Adding Options

You specify options to run snippy on in the configuration file(s) under the options key. Though the options key is not mandatory, if you decide to add them, the preferred approach is to have them all in one place.

Alternatively, you can specify the options via the command line when running snippy, though it is a less preferable approach. As with several configuration files, if you provide several command-line options, snippy then combines and adds them to the resulting configuration. For example:

llvm-snippy <options_part1> <file1>.yaml <options_part2> <file2>.yaml

Important

The syntax of specifying options in a YAML file and in the command line is different. As the preferred approach is to provide options via a configuration file, all the examples in this guide present the respective format. However, for some options, the guide provides examples of both approaches.

For the details on the options and how you can specify them, refer to the respective section of this guide.

Limitations

  • You cannot specify the same option in the command line and in YAML. Specifying an option via a YAML file is preferred.

  • You cannot specify an option more than once in a YAML file.

Printing Snippy Output

If you want to verify everything is correct and the way you want it to be, you can dump the resulting snippy configuration after it is pre-processed using the following command-line option:

llvm-snippy <config1>.yaml <config2>.yaml <config3>.yaml -E

YAML Layout

Important

As described in the Running Snippy chapter, the configuration you run through snippy can consist of one or more YAML files. For clarity, in the further chapters of this guide, we use the term (configuration) layout to describe the resulting layout of the configuration, regardless of whether you provide it via one or several YAML files.

The configuration layout must have the following mandatory keys:

  • sections with at least one RX section and one RW section.

  • histogram with at least one instruction.

You can specify the keys directly or via includes, but all keys of the configuration must be unique. For the details on includes, refer to Sublayouts.

Layout Example

Following is an example of the configuration layout that contains:

  • Mandatory sections and histogram keys

  • Optional options key

sections:
    - name:        1
      VMA:       0x10000
      SIZE:      0x10000
      LMA:       0x10000
      ACCESS:    rx
      PHDR:      'header1'
    - name:        2
      VMA:       0x20000
      SIZE:      0x200
      LMA:       0x20000
      ACCESS:    rw
      PHDR:      'header2'

histogram:
    - [ADD, 1.0]
    - [ADDI, 1.0]
    - [SUB, 1.0]
    - [SRA, 1.0]
    - [SRAI, 1.0]
    - [SRL, 1.0]
    - [SRLI, 1.0]
    - [SLL, 1.0]
    - [SLLI, 1.0]
    - [AND, 1.0]
    - [ANDI, 1.0]
    - [OR, 1.0]
    - [ORI, 1.0]
    - [XOR, 1.0]
    - [XORI, 1.0]
    - [LW, 10.0]
    - [SW, 10.0]

options:
    mtriple: "riscv64-unknown-elf"
    mcpu: generic-rv64
    march: "rv64ifc_zifencei"
    model-plugin: None

You may try layout-example.yaml from examples on github or from yml/ and share/examples/ folder in this release as follows and disassemble to see generated snippet.

llvm-snippy --num-instrs=1000 --seed=1 yml/layout-example.yaml \
   -o layout-example-1000.elf
objdump -d layout-example-1000.elf >& layout-example-1000.dis

As set by the rx and rw access parameters sections, snippy will write the code to section 1, and loads/stores will go to/from section 2.

Better always specify seed for reproducibility. If you dont, then snippy will show you which was used.

In this example, options are:

-mtriple

Target architecture specification, mandatory. Snippy is potentially multi-target. In further releases, snippy will support a number of arch specifications (x86, ARM, etc).

-mcpu

CPU model. If empty, snippy auto-detects it.

-march

RISC-V ISA string for which to generate the code. The value must be in lowercase. For the list of supported extensions, refer to the Supported Extensions chapter of this guide.

Alternatively, instead of march, you can use the mattr option: in the case with the example above, it is +f,+c,+zifencei.

-mattr

Indicates which attributes to include (+) or exclude (-). For example:

  • If you want to exclude atomics, use -mattr=-a.

  • If you want to include vector instructions (V extension), use -mattr=+v.

-model-plugin

Hardware model plugin you want to use. Available options: - Path to the model plugin

  • None (default) – Disables snippet execution on a model.

-num-instrs

Number of instructions to generate. The resulting snippet contains more instructions due to the supporting ones.

To generate a full section, use the -num-instrs=all option – it fills the executable section with instructions and generates them until the target executable section is full.

llvm-snippy --num-instrs=all --seed=1 layout-example.yaml \
   -o layout-example-all.elf
objdump -d layout-example-all.elf >& layout-example-all.dis

Important

You cannot use this mode with the following features:

  • Branches

  • Self-check

  • Burst mode

-seed

Seed for the instruction generation. If you do not provide this option, snippy picks a random value.

Snippy considers programs with the same options and seeds as equal within the same version.

-trace-log Execution trace: standard output (stdout) is redirected to the snippy.trace file.

Redirection is shell-dependent and optional. If not redirected, a console output is provided. Trace format depends on the model plugin you use. For more details, refer to Execution Log. The execution trace is omitted if no-model is specified.

You can find various layout example files in the ./yml directory and use them as templates for your configurations.

Top-Level Layout Keys

Top-level keys provide all the configuration parameters for your snippy run.

Although you can run snippy on more than one YAML file, you cannot divide the same top-level key into more than one YAML file or have the same top-level key in several files. For example, if you provide the sections configuration in more than one file, snippy will only take the configuration from the first file in the run, and will ignore any other mentions of sections.

Top-level keys cover the following YAML configurations:

You select the keys depending on how complex you want your YAML configuration for snippy to be.

Sublayouts

You can also use sublayouts using the include key in your YAML configuration file of via the command line. For example:

include:
- sublayout-sections.yaml

options:
  mtriple: riscv64-unknown-elf
  mcpu: generic-rv64
  march: "rv64g"
  num-instrs: 1000

histogram:
  - [FMUL_D, 1.0]

All keys of the configuration must be unique. This way, you need to verify your sublayouts do not duplicate the keys you provide otherwise. For example, if you provide sections and histogram in a “main” configuration file, you cannot have the same keys in any sublayout files that you use with this configuration one.

Snippy adds a relative path to the “main” layout to all its includes. To add an extra directory where to look for include files, use the --layout-include-dir option. You can specify this option more than once for different directories.

Important

If you use the same opcode more than once for histogram, the program displays a warning but does not stop the processing. In such a case, snippy takes the required relative weight from the last mentioned place (i.e., from the last sublayout file that contains this opcode).

If you use the same section more than once or if they intersect, the program reports an error and stops the processing.

Contents of Sublayouts

A sublayout file can include:

  • Both sections and histogram.

    Example of ``layout-toplevel.yaml``:

    include:
      - sublayout-hist.yaml
      - sublayout-sections.yaml
    
  • Only sections.

    Example of ``sublayout-sections.yaml``:

    sections:
      - name:      1
        VMA:       0x210000
        SIZE:      0x100000
        LMA:       0x210000
        ACCESS:    rx
      - name:      2
        VMA:       0x100000
        SIZE:      0x100000
        LMA:       0x100000
        ACCESS:    rw
    
  • Only histogram.

    Example of ``sublayout-hist.yaml``:

    histogram:
        - [ADD, 1.0]
        - [ADDI, 1.0]
        - [SUB, 1.0]
        - [SRA, 1.0]
        - [SRAI, 1.0]
        - [SRL, 1.0]
        - [SRLI, 1.0]
        - [SLL, 1.0]
        - [SLLI, 1.0]
        - [AND, 1.0]
        - [ANDI, 1.0]
        - [OR, 1.0]
        - [ORI, 1.0]
        - [XOR, 1.0]
        - [XORI, 1.0]
        - [LW, 10.0]
        - [SW, 10.0]
    

Running Snippy with Sublayouts

Note

For presentation purposes, the step below assumes that:

  1. The name of the main layout file you provide is main.yaml.

  2. The main.yaml file contains all the necessary options, so you do not need to provide them via the command line.

  3. You want to use memory.yaml that comes with the snippy package as a sublayout for the memory scheme.

In the snippy root directory, run:

./llvm-snippy main.yaml ./yml/memory.yaml

Basic Configuration

Mandatory keys of a basic configuration for snippy are sections and histogram. Additionally, as a part of your basic configuration for snippy, you can provide details for immediate histograms and FPU.

Sections

The sections key provides a list of sections for the output file with the following details for each entry:

  • name – Section number (in previous versions, no).

    Note

    no is currently deprecated, but still supported. It will be fully disabled in the future releases, so we recommend using name.

  • VMA – Virtual memory address

  • SIZE – Section size

  • LMA – Load memory address

  • ACCESS – Access type indication (r, rx, rw, rxw)

    Each RW section uses a default memory scheme (aligned): each and whole section is addressed aligned by the subtarget information.

    You can use different memory access schemes for different sections within a single layout – in this case, you need to provide additional memory schemes. For the details, refer to Memory Scheme.

    If present, snippy places the code in the executable sections.

  • PHDR (optional) – Program header you assign to the current section. Program headers (or segments) describe how the program must be loaded into memory and help distinguish between sections (for example, sections with the same access type).

    You can provide the value for PHDR in two ways:

    • Manually for each section in the layout.

    • Using the -enable-phdrs-definition setting. If you set it to true, snippy defines all the program headers that are used by the sections in the layout in the linker script.

      Regardless of how you provide the PHDR values, snippy outputs them into the resulting ELF file with the respective section information.

Histogram

The histogram key covers a list of instructions you want snippy to use and their relative weights (floating point).

For certain architecture configurations, you must set the names of instructions the same way as in the machine description. For the details, refer to Operation Codes for Specific Architecture below.

The histogram key supports regex. For example:

histogram:
  - ["ADDI?", 1.0]
  - ["VSSEG.*E32_V", 1.0]
  - ["(VSSE16_V)|(VSSE32_V)", 1.0]
  - ["VSSE.*", 2.0]

Immediate Histograms

In your layout, use the imm-hist key to specify what immediate values to use for instructions and their probability. For example, you can use it to generate load-from-load sequences.

You can specify:

  • General immediate histograms. For example:

    imm-hist:
      - [-1, 1.0]
      - [2, 1.0]
      - [3, 1.0]
    
  • Immediate histograms for each opcode or opcode regex pattern. For example:

    imm-hist:
      opcodes:
        - "ADDI":
          - [1, 1.0]
          - [2, 1.0]
        - "ADDI?":
          - [3, 1.0]
        - ".+": uniform
    

    This example sets to generate ADDI that matches against the first "ADDI" regex, even though it also matches against the next two entries ("ADDI?" and ".+"), as it is the first one in the configuration that matches this particular opcode. This way, ADDI’s immediates with this configuration can only be either 1 or 2.

You can find sample layouts that include imm-hist keys for different configurations in the ./yml directory.

./llvm-snippy ./yml/layout-example.yaml -seed=1 ./yml/imm-hist.yaml

Operation Codes for Specific Architecture

To obtain opcodes available for a certain architecture machine description, run:

./llvm-snippy -mtriple=riscv64-unknown-elf -mcpu=<processor_model> \
   -march=<ISA_string> --list-opcode-names

See the list of supported instructions for march here.

FPU Configuration

You can control settings for floating point unit (FPU) using the fpu-config key in your layout.

By default, code generated by snippy makes most of the floating-point values “at flight” to be NaNs due to randomization. That makes most of the floating point operations very similar: their operands are very likely to be a NaN value.

To reduce the percentage of NaN operands, use the NaN control feature. To do this, specify the range of values to be used to overwrite NaNs in the overwrite key in fpu-config.

After changing the configuration to a legal one, snippy continues to generate opcodes according to the whole histogram.

See an example below.

fpu-config.yaml:

fpu-config:
  overwrite:
    range:
      min: -9223372036854775808
      max: 9223372036854775807
      weight: 1.0
      rounding-mode: rup
    ieee-single:
      valuegram:
        - [0x7f800000, 1.0]
        - [0x80000000, 1.0]
        - type: bitrange
          min: 0x7f800000
          max: 0x80000000
          weight: 1.0
    ieee-double:
      valuegram:
        - [0x7ff8000000000000, 1.0]
        - [0x8000000000000000, 1.0]
        - type: bitrange
          min: 0x7ff8000000000000
          max: 0x8000000000000000
    mode: if-any-operand

where you define the overwrite settings by the following:

  • range:

    • min, max, weight – Specific range of the values to use. weight is optional and 1.0 by default.

    • rounding-mode – FPU rounding directions according to the IEEE standard. You can use both abbreviated options and their aliases:

      • rup – toward-positive

      • rdn – toward-negative

      • rtz – toward-zero

      • rne – nearest-ties-to-even

      • rmm – nearest-ties-to-away

  • valuegram for ieee-single (single precision), ieee-double (double precision), ieee-half (half precision, for zfh only). For the details on valuegrams, refer to the Value Histogram (Valuegram) chapter.

  • mode – Heuristic that determines which register is a NaN and must be overwritten. The following options are available:

    • if-all-operands – A register is considered a NaN if all of its input registers are potentially NaNs.

    • if-any-operand – A register is considered a NaN if any of its input registers are potentially NaNs.

    • if-model-detected-nan – The model plugin detected this register to be a NaN.

    • disabled – overwrite is disabled.

Additionally, you can use the existing fpu-config.yaml file in the ./yml directory as is or as a template for your own configuration.

Memory Configuration

Memory configuration provides extensive features for describing the way you want memory to be accessed, and includes keys for:

Memory Scheme

A memory scheme is a mechanism that describes which memory addresses the snippet can access. You provide the memory scheme in one of the configuration files. Use the access-* keys (for example, access-ranges, access-evictions, access-addresses) in your YAML file to provide a memory scheme for your configuration. You can also combine your memory scheme into groups.

You can use the files in ./yml as templates for your memory scheme. For example, the memory-aligned.yaml file for an example of an aligned access.

Important

If you do not provide a memory scheme option, snippy generates memory accesses anywhere in an RW section specified by the layout.

Selecting Applicable Memory Schemes

Snippy selects applicable memory schemes to use based on the following information that you provide:

  • Maximum size in bytes of each memory access – that is, how much you need the instruction to load (access-size).

    For example, a memory scheme has the access size set to 2, and you provide the following:

    • LB (load byte) – Access size 1 byte

    • LH (load, half-word) – Access size 2 bytes

    • LW (load-word) – Access size 4 bytes

    • LD (load double word) – Access size 8 bytes

      In this case, only the LB and LH instructions can use addresses from this memory scheme.

    For more details on the rules of vector instructions, refer to RVV Instructions in Memory Schemes.

  • Alignment. Though alignment is not a parameter of memory schemes, you must consider it when it is required in the instruction (for example, with riscv-disable-misaligned-access set to true). In this case, addresses that are not aligned at least on the instruction access size will not be used.

RVV Instructions in Memory Schemes

Vector instructions work on a group of elements simultaneously.

Snippy supports the following memory-related vector instructions:

Unit-stride, strided, and indexed instructions also support segment instructions.

For the details on different types of instructions, refer to the official RVV specification. Additionally, here you can find a description of how addressing works for all types of instructions mentioned in this chapter.

Strided Load/Store

For the strided load/store instructions, snippy can use any memory schemes that are applicable to the element the instruction works on. In this case, snippy uses the algorithm described in Selecting Applicable Memory Schemes as for any basic scalar instructions.

Snippy does not guarantee that a stride bigger than the element size will be chosen, even a stride equal to zero is possible.

For more details on strided instructions, refer here.

Unit-Stride Load/Store

Vector unit-stride operations access elements stored contiguously in memory starting from the base effective address.

This way, if VL is set to 2 and misaligned accesses are disabled, instruction vle32.v (element size is 4 bytes) can access memory at the following addresses: [0-3, 4-7] or [4-7, 8-11], etc.

Currently, snippy treats such memory accesses as one: if a unit-stride instruction works on 4 elements with the size of 4 each, the resulting access size is considered 16 (4x4). Then, snippy selects the memory schemes that allow such access along with the rest of the algorithm described in Selecting Applicable Memory Schemes with the alignment on one element (4, not 16).

For more details on unit-stride instructions, refer here.

Indexed Load/Store

Vector indexed operations add the contents of each element of the vector offset operand to the base effective address to give the effective address of each element. Any memory scheme that suits the restrictions of an individual element of this instruction can be selected based on the algorithm described in <>.

For example, the base address is 4, and the selected scheme allows accesses 0, 2, 4, 6, 8. Snippy tries to generate offsets so that the base address + the offset equal 0, 2, 4, 6, 8. This way, for example, with the base address of 4, the following offsets are applicable:

  • -4

  • -2

  • 0

  • 2, etc.

Note

Snippy does not guarantee that all offsets will differ.

For more details on indexed instructions, refer here.

Segment Load/Store

Segment instructions only change the access size: in this case, snippy selects the instruction based on the access size that depends on the segment.

For more details on the segment instructions, refer here.

Memory Strides

Snippy uses memory strides to amplify the test randomization.

In the basic process, snippy:

  1. Selects a memory scheme based on the information you specify. See more in Selecting Applicable Memory Schemes.

  2. Generates a strided instruction – an instruction that can navigate through the selected memory scheme by the stride you specify. Such instructions simultaneously access not one address, but several.

Use memory.yaml for a reference on a memory scheme with strides:

memory.yaml (example):

access-ranges:
    - start: 0x80002000
      size: 0x1000
      stride: 16
      first-offset: 1
      last-offset: 2
      max-past-last-offset: 2
      access-size: 4
      weight: 1
    - start: 0x80008000
      size: 0x1000
      stride: 8
      first-offset: 1
      last-offset: 2
      max-past-last-offset: 4
      weight: 2

where:

  • start – Section start address

  • size – Section size

  • stride – Stride size

  • first offset – Number of the first available bit in a stride, which access can start from

  • last offset – Number of the last available bit in a stride, which access can start from

  • max-past-last-offset – Maximum number of bits after last-offset that can be accessed (optional).

  • access-size – Access size (optional). If it is specified than this range can only be used for accesses not exceeding access-size bytes per one memory access.

  • weight – Optional value, relative probability of this access range. If you omit it, snippy considers it as 1.

If a stride’s memory scheme is supplied, then the address is calculated as:

start + stride * rand(0 .. size/stride) + rand(first-offset .. last-offset)

This formula shows which addresses can be generated as operands of memory instructions, but it does not take into account how many bytes the instruction touches. By default, the instruction can affect any number of bytes so that the start + size of access-ranges is not exceeded.

In order to regulate all bytes that are accessed, there are two optional fields access-size and max-past-last-offset. The first ensures that this memory scheme will not be used for instructions with an access size greater than access-size. The second allows you to controll how far access can exceed last-offset, the offset of the last byte to which access is allowed is last-offset + max-past-last-offset. This way you can generate accesses of access-size bytes without accessing bytes after last-offset + max-past-last-offset.

Note

In order to be able to start accessing memory from the byte with last-offset, the parameter max-past-last-offset must be greater than or equal to 1.

Note

It makes no sense to set a max-past-last-offset greater than access-size, because access will never exceed access-size anyway.

Important

No matter how large the max-past-last-offset and access-size values are, the start of access must always be in the interval [first-offset, last-offset].

See the memory scheme examples for different offsets in figure 1. Here, the bytes that can be accessed are indicated in red. The yellow color indicates the bytes accessed by the memory instruction.

Memory scheme

Fig. 1 Memory scheme

Address examples:

start

end

stride

first-offset

last-offset

max-past-last-offset

addr = start + k * stride + [first-offset, … last-offset]

0

1024

8

0

0

-

[0, 1024), [8, 1024), [16, 1024), [24, 1024), [32, 1024), …

0

1024

8

1

1

-

[1, 1024), [9, 1024), [17, 1024), [25, 1024), [33, 1024), …

0

1024

8

1

2

-

[1, 1024), [2, 1024), [9, 1024), [10, 1024), [17, 1024), [18, 1024), …

0

1024

4

0

2

-

[0, 1024), [1, 1024), [2, 1024), [4, 1024), [5, 1024), [6, 1024), …

0

1024

8

0

0

2

[0, 1], [8, 9], [16, 17], …

0

1024

8

0

1

2

[0, 2], [1, 2], [8, 10], [9, 10], …

0

1024

4

0

1

3

[0, 3], [1, 3], [4, 7], [5, 7], …

0

1024

4

0

0

10

[0, 9], [4, 13], [8, 17], …

0

1024

4

1

2

5

[1, 6], [2, 6], [5, 10], [6, 10], …

0

1024

1000

7

8

-

[7, 1024), [8, 1024), [1007, 1024), [1008, 1024)

0

1024

1000

7

8

100

[7, 107], [8, 107], [1007, 1024), [1008, 1024)

Memory Eviction

Use eviction.yaml for a reference on a memory eviction scheme:

eviction.yaml (example):

access-evictions:
    - mask:  0x003c0000
      fixed: 0x80000c81
      weight: 2
    - mask:  0x04000000
      fixed: 0x000000ff
      weight: 3

where:

  • mask – Bites that can be changed

  • fixed – Fixed bites

  • weight – Optional value, relative probability of this access range. If you omit it, snippy considers it as 1.

See how memory eviction works in figure 2.

Memory eviction

Fig. 2 Memory eviction scheme

Addresses Enumeration Scheme

Use this memory scheme to specify individual addresses. Each address can have multiple parameters.

addresses-memory-mode.yaml (example):

access-addresses:
  - ordered: true
    plain:
      - addr: 0x80002001
        access-size: 8
      - addr: 0x80000000
  - ordered: true
    plain:
      - addr: 0x80001234
      - addr: 0x80000000
        access-size: 4
  - ordered: true
    weight: 3
    plain:
      - addr: 0x80000300

where:

  • ordered:

    • true – Snippy takes all addresses in order (default).

    • false – Snippy selects a random address.

  • weight – Optional value. If you omit it, snippy considers it as 1.

  • addr – Address value (required).

  • access-size – Access size (optional).

    Note

    access-size is supported for negative schemes only. For compatibility, you can pass it in positive memory schemes as well, but it has no effect.

  • plain – Plain scheme mode. In this key, you specify addresses to use.

    plain is used for ordinary non-grouped memory accesses. Addresses you specify in the burst mode are used when generating burst groups.

Negative Memory Scheme

Negative memory scheme is an optional entry in the memory scheme list. The format of this entry is similar to Addresses Enumeration Scheme.

A negative memory scheme tells snippy to not generate any memory accesses to the specified memory locations. Use it in combination with “positive” memory schemes. “Positive” schemes define possible memory accesses, while “negative” schemes apply some restrictions on that accesses.

Note

Unlike the access-addresses entry, a negative memory scheme cannot have an ordered field.

addresses-memory-mode.yaml (example):

restricted-addresses:
    - plain:
          - addr: 0x80002000
            access-size: 128
          - addr: 0x80003F00
            access-size: 200
          - addr: 0x80004000
            access-size: 400
          - addr: 0x80201000
            access-size: 600

where:

  • addr – Address value (required).

  • access-size – Access size (optional). If you do not provide a value, the default is 16 bytes.

Memory Scheme Adjustments

Use the --riscv-disable-misaligned-access option to disable generation of misaligned loads/stores. This option works even if memory scheme you select allows such loads/stores.

Memory Scheme Groups

Use the access-groups key to combine memory schemes into groups. All the child keys of a subgroup included in a group must be of different types.

The following is an example of a memory scheme group configuration:

access-groups:
  - weight: 10
    access-ranges:
       - start: 0x80002000
         size: 0x1000
         stride: 16
         first-offset: 1
         last-offset: 2
         weight: 3
    access-evictions:
      - mask:  0x003c0000
        fixed: 0x80000000
        weight: 2
      - mask:  0x000be000
        fixed: 0x80001000
        weight: 2
      - mask:  0x003d0000
        fixed: 0xFF000000
        weight: 2
  - weight: 5
    access-ranges:
       - start: 0x80002000
         size: 0x1000
         stride: 16
         first-offset: 1
         last-offset: 2
         weight: 2
    access-addresses:
       - ordered: true
         plain:
           - addr: 0x80200000
           - addr: 0x80201234
           - addr: 0x802020BC
        weight: 2
       - ordered: false
         plain:
           - addr: 0x80200000
           - addr: 0x80201234
           - addr: 0x802020BC
        weight: 1
access-ranges:
  - start: 0x80002000
    size: 0x1000
    stride: 16
    first-offset: 1
    last-offset: 2
    weight: 4
  - start: 0x80002000
    size: 0x1000
    stride: 16
    first-offset: 1
    last-offset: 2
    weight: 4

Example:

./llvm-snippy ./yml/layout-accgroups.yaml \
   ./yml/memory-accgroups.yaml -seed=1

Dumping Memory Access Scheme

You can dump used addresses and reuse them in later runs as the address scheme.

  • To dump the applied “positive” memory access scheme (namely, all the individually used addresses), use the --dump-memory-accesses[=<filename>] option.

    • Example for collecting addresses:

      ./llvm-snippy ./yml/layout-accgroups.yaml \
         ./yml/memory-accgroups.yaml -seed=1 \
         --dump-memory-accesses=acc.yaml --num-instrs=1000
      
  • To dump the applied “negative” memory access scheme (namely, all the individually used addresses), use the --dump-memory-accesses-restricted[=<filename>] option. You can then use it as a negative memory scheme for future generator runs.

Note

You can omit specifying the file name. In this case, the program uses the console output.

Burst Mode

Burst mode allows you to combine specified instructions in several groups of a specific size in the generated snippet.

To manage the burst mode, provide burst mode attributes in your layout in the burst key, which you can add:

  • Directly in the configuration file.

  • As a sublayout of the configuration file (for example, using the .yml/burstdesc.yaml file.

Important

Not all the opcodes can be specified in burst groups. Currently, only loads, stores, floating point loads/stores, atomics, and fences are supported.

Following is an example of the burst key:

burst:
  mode: custom
  min-size: 10
  max-size: 30
  groupings:
    - [ FENCE, LW ]
    - [ SW ]

where:

  • mode – Sets the grouping mode. Available values are:

    • load – Issues load groups.

    • store – Issues store groups.

    • load-store – Issues loads and stores as separate groups.

    • mixed – Issues groups with mixed loads and stores in one group.

    • basic – Is equal to no burst.

    • custom – Sets custom grouping configuration. This mode allows to randomly combine different types of opcodes in groups (for example, all loads and one store in one group, and all other stores and fences in another group).

  • min-size and max-size set the number of memory access instructions in a burst group. There is no restriction on the maximum size.

  • groupings – Specifies the burst more precisely (for the custom mode only).

    Note

    You can have an unlimited amount of burst groups and opcodes in each group.

The example above specifies two different burst groups:

  1. LW and FENCE instructions

  2. SW instructions

Based on these settings, snippy will generate separate permuted groups (some groups of LWs and FENCEs, and some groups of SWs) divided by other instructions (optionally).

You can then have the result of the generation dumped. For more details, see Dumping Memory Access Scheme.

For example:

./llvm-snippy -mtriple=riscv64-linux-gnu -mcpu=generic-rv64 \
   ./yml/layout.yaml ./yml/memory.yaml -model-plugin=None \
   -num-instrs=50 -seed=0 ./yml/burst-store.yaml -o layout.elf

Advanced Configuration

Advanced configuration includes keys for:

Control Flow (Branchegrams)

A branchegram – the branches key of the configuration – provides branches and loops for your generated snippet. You can see an example of this key in the layout-branches.yaml file in the ./yml directory.

If you specify branches in the histogram, permutation is on by default, even if no branchegram is used. You can request any number of branches when permutation is on. To switch it off, use the permutation field in the branchegram:

branches:
  permutation: off
...

How to Add Branchegrams

  • Include a branchegram YAML file in your snippy layout:

    include:
      - branchegram.yaml
    ...
    
  • Add a branches key along with the sections, histogram, and other keys in your configuration file(s).

Generating Loops

Use the loop-ratio field in branches to set up the probability of whether snippy will generate a loop when generating a branch.

Important

With permutation on and off in branchegrams, snippy no longer supports the permutate-cf option.

branchegram.yaml (example)

branches:
  alignment: 32   # in bytes, 1 by default
  loop-ratio: 0.5 # loop/all branches probability, 0.5 by default
  number-of-loop-iterations:
      min: 2  # 4 by default
      max: 32 # 4 by default
  max-depth:
    if: 500 # unlimited by default
    loop: 4 # 4 by default
  distance:
    blocks:
      min: 1  # 0 by default
      max: 20 # by default calculated, depending on max branch distance
    pc:
      min: 0  # 0 by default
      max: 120 # by default calculated, depending on max branch distance

Example:

./llvm-snippy ./yml/layout-example.yaml ./yml/branchegram.yaml \
   -seed=1 -num-instrs=1000

Note

You can provide the distance both in blocks and in PC.

The PC distance currently only works for the cycles without nesting. If you specify PC distance restrictions, and a nested loop is generated, you will get an error.

Restricting Compressed Instructions for Loop Counters

Use --riscv-loop-control-logic-compression= to restrict force compression of instructions for loop counters. The options include:

  • on – To use compressed instruction as much as possible.

  • off – To avoid using compressed instruction as much as possible.

  • random – Compression can be any.

Dumping Control Flow Graphs

Use the following options to manage how the control flow graph is dumped:

  • --dump-cfg – Enables dumping the control flow graph in the .dot format.

  • --view-cfg – Enables dumping the control flow graph in the .dot format and launching the .dot file viewer.

  • --cfg-basename=<filename> – Sets the base name of the file to dump the control flow graph to. By default, snippy dumps the generated control flow graph to the current working directory.

./llvm-snippy ./yml/layout-example.yaml ./yml/branchegram.yaml \
              -seed=1 -num-instrs=10 --dump-cfg
dot -Tpng SnippyFunction-cfg-dump.dot > dump.png

Now you can enjoy generated control flow picture.

Call Graph

Snippy uses call graphs to keep the instructions in a correct order. To generate a call instruction, you need to specify instructions that the llvm target considers to be ‘call’ instructions. For example, for RISCV it is JAL and JALR.

Important

The type of the generated call might differ from the one specified in the histogram. It is a limitation of the current implementation and will be improved in the future releases.

For snippy to generate a call graph, you can either use a specific configuration file with all its properties (for the details, refer to the External Call Graph chapter), or have it generated randomly. In this case, snippy executes calls among the generated function according to this randomly generated call graph.

Use the following settings to configure a call graph generation:

  • --function-number=(int) – Number of generated functions used as nodes to construct a call graph.

  • --function-layers=(int) – Maximum depth of the call graph.

  • --num-instr-ancil=(int) – Number of instructions in ancillary functions (while the number of instructions in the main/entry function is specified by -num-instrs).

  • --call-graph-density=(int) – Density of connections inside a call graph. Passing 0 will not generate any connections at all.

  • --call-graph-force-connect (bool) – Indicates whether you want each generated function to be potentially reachable. This way, if you set it to true, every node of the call graph that doesn’t get a path from the graph top (entry function) during random generation is forcefully connected to form a path.

  • --call-graph-dump-filename (string) – Specifies the file in the *.dot format to which you want snippy to dump the generated call graph topology.

Following is the expected format of a call graph layout file:

layout-calls.yml

function-layers: <int>
function-number: <int>
num-instr-ancil: <int>

Following is an example of settings for a call graph:

./llvm-snippy ./yml/layout-calls.yaml --function-layers=4 \
     --function-number=16 -num-instrs=1000 -seed=0 \
    ./yml/memory-aligned.yaml -o calls.elf

See the resulting graph in figure 3.

Call graph

Fig. 3 Resulting call graph

External Call Graph

You can specify a call graph in contrast to snippy randomly generating it. See figure 4 as an example:

Call graph

Fig. 4 External call graph

In this graph, you can link an external fun3 function. This function must be able to clean up all register and memory side effects, namely the ones that are visible to snippy through the specified sections in the layout. Other side effects are allowed.

To specify a call graph:

  • Add a call-graph key to your configuration file(s).

  • Alternatively, use a predefined YAML file in the command line. For example:

    ./llvm-snippy ./yml/layout-calls.yaml --function-layers=4 \
        --function-number=16 -num-instrs=1000 -seed=0 \
        ./yml/cg-predefined.yaml -call-graph-dump-format=yaml \
        -call-graph-dump-filename=cg.yaml
    

    where:

    • layout-calls.yaml – A sample layout file you can find in the ./yml directory.

    • function-layers – The number of layers in the graph.

    • cg-predefined.yaml – A sample layout file used as the graph description.

    • call-graph-dump-filename – The direction where to save the graph dump.

    • call-graph-dump-format – The format of the graph dump.

Following is an example of the call-graph key:

call-graph:
  entry-point: SnippyFunction
  function-list:
    - name: SnippyFunction
      callees:
        - fun1
        - fun2
        - fun3
    - name: fun1
      callees:
        - fun2
    - name: fun2
      callees:
        - fun3
    - name: fun3
      external: true

The generated snippet includes a part of the trace:

...
core   0: 0x0000000000211d32 (0x00000097) auipc   ra, 0x0
core   0: 3 0x0000000000211d32 (0x00000097) x1  0x0000000000211d32
core   0: 0x0000000000211d36 (0x064080e7) jalr    ra, ra, 100
core   0: 3 0x0000000000211d36 (0x064080e7) x1  0x0000000000211d3a
core   0: 0x0000000000211d96 (0x00008082) ret
...

where jalr is fun3 – a weak symbol in the snippet (a stub for the model) to which you want to link the user function.

Options

As described in the Running Snippy chapter, you can specify the options for your configuration either in the configuration YAML file(s) or in the command line. The preferred approach is to do it via a YAML file, so the examples in the guide have the respective format.

You provide options via the options top-level key in configuration file(s) or as includes (sublayouts). If you specify the same option in more than one YAML file, and then pass both files in your snippy run, the options do not merge, and the first YAML file that you pass takes precedence.

At the same time, the options you provide via the configuration files have priority over the ones in the includes. The options you provide via includes will be merged, and their amount and the order in which you pass them does not matter.

See the chapters that follow for details.

Specifying Options in YAML Files

Note

This approach is preferred.

To specify options in the configuration YAML file(s), list them in the options key. You can use the options key in the following layout files as examples or templates:

  • layout-example.yaml

  • layout-accgroups.yaml

For example, in layout-example.yaml, the options key looks as follows:

mtriple: "riscv64-unknown-elf"
mcpu: generic-rv64
march: "rv64gc"
model-plugin: None

You can also use the files specified below in the snippy command line to generate the snippet based on the provided layouts without having to enter long lists of options:

./llvm-snippy layout-example.yaml
./llvm-snippy layout-accgroups.yaml

Note

Though the preferred approach of specifying snippy options is using a single YAML file with all the required parameters, you can use more than one file as an argument and specify the options key in any of them. If you use more than one file to base the snippy run on, it merges the data from all the files and uses it for the run.

Specifying Settings as Options via Command Line

You can specify snippy settings as options via the command line. For this, when running a snippy configuration, first provide the options you want to use, and then – the configuration file. For example:

./llvm-snippy -mtriple=riscv64-unknown-elf -mcpu=<processor_model> <config>.yaml

The full list of all options you can use as settings is available in the llvm-snippy options section of the embedded snippy help. To call the snippy help:

./llvm-snippy --help

Dumping Options

You can dump all snippy options you specify using the following command-line option:

./llvm-snippy -mtriple=riscv64-unknown-elf --dump-options

Warning Control Options

You can control the errors and warnings you get in your snippy runs. To do it, you need to provide one of the following options followed by a comma-separated list of warning categories that you want to be affected:

  • -Wdisable – To disable getting specific warnings. For example:

    -Wdisable=<warning_category1>,<warning_category2>,...
    

    If you try to disable a category that does not exist, you will get an error.

  • -Werror – To treat specific warnings as errors. Some warnings are treated as errors by default, and you can override it by -Wno-error, as described further.

    Currently, the only warning that is treated as an error by default is -Werror=non-reproducible-execution. This way, this warning is added by default to any other warning that you add via -Werror.

  • -Wno-error – To not treat specific warnings as errors (that is, to remove them from the list of warnings treated as errors).

    -Wno-error takes priority over -Werror, which makes it possible to enable treating of all warnings as errors, and then disable it for some selected warnings. For example:

    -Werror -Wno-error=no-model-exec,memory-scheme
    

    treats all warnings as errors, except for the warnings of the no-model-exec and memory-scheme categories.

    The opposite is impossible. If you try to disable treating all warnings as errors by -Wno-error, and then enable handling some warnings as errors via -Werror, the result will be as if no -Werror is specified.

Note

If you specify -Werror and -Wno-error options via the command line, every occurrence of each appends to their value. For example, -Werror=A -Werror=B is equivalent to -Werror=A,B.

Warning Categories

When you get a warning, you can see its category in parentheses. For example:

warning: (memory-access) Possibly wrong memory scheme: Following scheme may generate accesses outside of all provided RW sections in layout

warning: (no-model-exec) Skipping snippet execution on the model: model was set no 'None'

So, if you do not want to keep getting such warnings, pass:

-Wdisable=memory-access,no-model-exec

ABI Option

You can set ABI to be used in the generated .elf file. For example:

-mcpu=<processor_model> -mabi=lp64 -mattr="-f,-d,-c"

Generating ABI with Respect to Target

To automatically form a list of registers that need to be spilled to follow a target ABI (callee-saved registers), use the --honor-target-abi option. When using this option, you must also add the utility key to your layout with attributes that specify the section for snippy to spill tp and gp to. For example:

...
  - name:      utility
    VMA:       0x310000
    SIZE:      0x1000
    LMA:       0x310000
    ACCESS:    rw
...

Registers are “soft”-reserved – reserved register mechanism is no longer intended to preserve ABI, just to increase registers pressure. This means that even reserved register can be used in ancillary instructions.

You can only reserve registers for main instructions. For more details, refer to Reserving Registers.

If one of registers in a spill-list happens to be reserved via the --reserved-regs-list option, it will be spilled anyway, and no error or warning will be raised.

Important

When --honor-target-abi option is used, the --spilled-regs-list option is ignored (if used) and the following warning is displayed:

"warning: --spilled-regs-list is ignored: --honor-target-abi is enabled"

Important

If you use this option, you cannot specify in the histogram opcodes that require a stack pointer. (For example, for RISC-V, this is C_LWSP, C_LDSP, C_SWSP, C_SDSP, C_FLWSP, C_FLDSP, C_FSWSP, C_FSDSP, C_ADDI16SP, C_ADDI4SPN.) You get the following error:

"error: Incompatible options: When --honor-target-abi is enabled, generation of SP-relative instructions is not supported."

Important

You must also specify the snippy stack section.

Last Instruction

The default last instruction is EBREAK.

If you want to set a custom last instruction, use the --last-instr=<string> option. To emit return, use RET.

Use --last-instr= with no option specified to make no additional last instruction.

Registers Subset Fix

If you specify spilled or reserved registers, verify you do not use the same register for both. A register can either be spilled or reserved, not both at the same time.

Spilling Registers

Use the spilled-regs-list option for the registers you want to spill before the snippet execution. Once executed, the registers will be restored.

Important

You must also specify the snippy stack section.

Following is an example for the options key:

spilled-regs-list: [<string>]

where <string> is a list of registers to spill, for example, X1,X2,X3 (comma-delimited, no spaces).

Note

You do not need to explicitly specify the stack pointer (X2) as spilled as it is spilled by default when the stack is being used.

In the following example, a spilled register (X1) is specified via the command line. X1 will be spilled in prologue and restored in epilogue:

./llvm-snippy examples/layout-calls.yaml -seed=1 \
   -spilled-regs-list="X1,X2,X5"

Reserving Registers

Use the reserved-regs-list option for the registers that you want to reserve and not use in the snippet code.

Note

We do not recommend using this option as it prohibits using these registers in the whole snippet for the main instructions. However, ancillary instructions can still use these reserved registers.

Example for command line:

./llvm-snippy ./layout.yaml -seed=1 --reserved-regs-list=X1,X2,X3

Here reserved-regs-list is specified as a comma-separated list of register names.

Alternatively it can be specified in YAML config under options key:

options:
  reserved-regs-list: [<REGLIST>]

where <REGLIST> is a list of registers to reserve, for example, X1, X2, X3.

Or using non-flow sequence:

options:
  reserved-regs-list:
    - X1
    - X2
    - F10_D

This option also accepts regular expressions. For example:

options:
  reserved-regs-list: [X10, "X2[0-9]"]

This code will reserve X10 as well as X20-X29 registers.

Float Registers

To reserve RISC-V float registers, you need to additionally provide the register’s extension, otherwise you get an error.

For RISC-V, the following three types of float registers are available:

  • XX_F – Single precision (32 bit registers)

  • XX_D – Double precision (64 bit registers)

  • XX_H – Half precision (16 bit registers)

where XX is the register.

Each of them has 32 registers: F0_X - F31_X. For example, for F4, it is F4_F, F4_D, or F4_H.

All three register types refer to one of the parts of the register, making F0_D a full register, F0_F – half of the same register (32 bits out of 64), and F0_H – quarter of the same register (16 bits out of 64). If you reserve any of them, it leads to all of them being reserved as well.

This way, in the current implementation, to add F4 to the list of reserved registers, pass it with any of the three extensions in the options key of your configuration:

~/snippy-oss/bin/./llvm-snippy examples/layout-calls.yaml -seed=1 \
   -reserved-regs-list=F4_D,F5_D
Vector Registers

In snippy’s RISC-V backend, vector registers are named V0 - V31, and you reserve them by their names.

The V extension in the RISC-V architecture allows you to configure a vector unit so that vector registers get grouped when executing certain instructions. For the details on vector register grouping, refer here.

Respectively, for vector groups, the reservation rules are as follows: if you reserve at least one register from the vector group, this whole particular register group is considered to be reserved for this instruction.

Initial Registers

Initializing Registers in .elf

To include initialization of registers with random values into the output .elf file, use the --init-regs-in-elf setting in the command line.

If you do not explicitly specify both init-regs-in-elf and initial-reg-yaml, then snippy issues a non-reproducible-execution warning, which is treated as error by default. In this case, snippy fails unless you pass -Wno-error=non-reproducible-execution. For the details on how to control errors and warnings, refer to the Warning Control Options chapter of this guide.

Vector Register Initialization Modes

Use the --rvv-init-mode option to control how snippy initializes vector registers. The following modes are available:

  • splats – To use splats. It writes the value to xreg and moves it to vreg using the VMV.V.X instruction. This mode does not require an r section.

  • loads – To use loads from the read-only section using the VL1RE8.V instruction.

  • slides – To use slides for v0-v31 initialization. It writes the value to xreg and slides it into vreg using the VSLIDE1DOWN.VX instruction. It repeats until vreg is filled. This mode does not require an r section.

  • mixed (default) – To use slides for v1-v31 initialization. v0 will be initialized using load.

Important

In the current implementation, you cannot set illegal configurations for the splats and slides modes. This will be fixed in the future releases.

Dumping Initial Registers State

Important

Currently, this functionality does not supported without model plugin

To get a dump with an initial registers state, run:

./llvm-snippy -mtriple=riscv64-unknown-elf -mcpu=<processor_model> \
    <config>.yaml -model-plugin=<model_plugin>.so -num-instrs=<int> \
    -seed=0 --dump-initial-registers-yaml[=<filename>] \
    -o layout.elf

where:

  • <config>.yaml – Name of your configuration file.

  • --dump-initial-registers-yaml[=<filename>] – Request for initial registers state to be dumped to file. The file name is optional. If you do not specify it, snippy uses the default value (initial_registers_state.yml).

Dumping Initial Registers Load

You can use the initial registers state dump to initialize registers on the next run not to random but to the same values.

To load the initial registers dump file, use the --initial-regs-yaml=<filename> option:

./llvm-snippy -mtriple=riscv64-unknown-elf -mcpu=<processor_model> \
    <config>.yaml -model-plugin=<model_plugin> -num-instrs=<int> \
    -seed=0 --initial-regs-yaml=initial_registers_state.yml \
    -o layout.elf

where <config>.yaml is the name of your configuration file.

Value Histogram (Valuegram)

Configure a valuegram to initialize registers with values from a given set before execution.

You can provide the data for a valuegram in a form of sequences and mappings. They are backward compatible, and you can use both or either of them, since they describe equal values. For example:

valuegram.yaml (example):

histograms:
- reg-type: X
  values:
    - [0xffff, 1.0]
    - type: bitvalue
      value: 0xffff
      weight: 1.0
    - [bitpattern, 1.0]
    - type: bitpattern
      weight: 1.0
    - type: uniform
    - [uniform, 1.0]
    - type: bitrange
      min: 0x8000000000000001
      max: 0x800fffffffffffff
      weight: 1.0
...

where type options include:

  • bitvalue – Specific bit value. You can provide a value in two formats:

    • In the ordinary format of (-)(radix)num

  • bitpattern – Regular randomly generated bit pattern (evenly-spaced 1-s)

  • uniform – Random uniformly distributed value

  • bitrange – min to max range of bit values

You must include a valuegram as a separate YAML to your configuration via the --initial-regs-yaml setting.

For example, if you run:

./llvm-snippy ./yml/layout-example.yaml -seed=1 --init-regs-in-elf \
   ./yml/valuegram.yaml

The result may look like:

....
x8 <- 0x0080200802008020
x9 <- 0x00000000000000FF
x10 <- 0x2010080402010080
x11 <- 0x992E2E3F0C6DA5A4
x12 <- 0x00000000000000FF
....

where 0x0080200802008020 and 0x2010080402010080 are two different bitpatterns.

Initializing Operand Registers

You can initialize all operand registers before each non-service instruction via valuegrams. To do this, use the --valuegram-operands-regs option followed by the path to a YAML file with initialization values to use. Operands that are addresses are not initialized. For example:

...
  options:
    valuegram-operands-regs: "registers.yaml"

Important

Currently, this functionality does not support initialization of vector registers.

The input YAML file can have one of the following formats:

  • Specific register values. For example:

    registers:
            - [ X0,  0x03 ]
            - [ X7,  0x03 ]
            ...
            - [ F0,  0x01 ]
            - [ F1,  0x02 ]
            ...
    
  • Probabilistic patterns for groups of registers. The values you provide in this format are consistent with the format of valuegrams. For example:

    histograms:
      - reg-type: X
        values:
          - [uniform, 1.0]
          - [bitpattern, 1.0]
      - reg-type: F
        values:
          - [0123, 1.0]
          - [0xAA, 1.0]
    

For example:

./llvm-snippy examples/layout-example.yaml -seed=1 --init-regs-in-elf \
   --valuegram-operands-regs="./yml/valuegram.yaml"

Final Registers State

Important

Currently, this functionality does not supported without model plugin

To get a dump with final registers state after the execution, run:

./llvm-snippy -mtriple=riscv64-unknown-elf -mcpu=<processor_model> \
    <config>.yaml -model-plugin=<model_plugin>.so \
    -num-instrs=<int> -seed=0 \
    --dump-registers-yaml[=<filename>] \
    -o layout.elf

where:

  • <config>.yaml – Name of your configuration file.

  • --dump-registers-yaml[=<filename>] – Request for the final register dump file. The file name is optional. If you do not specify it, snippy uses the default value (registers_state.yml).

Execution Log

Important

Currently, this functionality does not supported without model plugin

By default, snippy prints the model execution log from a plugin to the standard output. To redirect the log:

  • To a specific file, use the --trace-log <filename> option.

  • To a standard error (instead of the standard output), use - instead of <filename>, For example: --trace-log -.

The program injects a special watermark line #===Simulation Start=== to clearly separate executions of the generation phase from the final snippy execution.

Address Hazard Mode

Important

Currently, this functionality does not supported without model plugin

In this mode, snippy tries to create a data dependency when forming an address part of the memory instruction. To enable this mode use the --enable-address-value-hazards option.

In the following example, an address for the second load in the register s1 is formed by transforming it by adding the a0 register value. That way, the value of s1 that was formed by the first load becomes important, and a data hazard is formed here:

...
lw s1, 0(a2)
lui a0, 12
addiw a0, a0, 1234
add s1, s1, a0
lw a1, 0(s1)
...

Stack Section

Add a stack section in the following cases:

  • When using functions and calls to them.

  • When setting up environment initialization for the snippet.

  • If you want to call a program that snippy generated from an external application. In this case, you must also use the –honor-target-abi option to properly save the register state.

To specify stack, add a stack section in the layout file. This makes the stack location and size configurable. For example:

...
  - name:      stack
    VMA:       0x0
    SIZE:      0x100000
    LMA:       0x0
    ACCESS:    rw
...

The access mask of a stack section must be rw, otherwise you get an error.

External Stack

Another option is to use external application/system stack space. You can use it in the same cases as a snippy stack space.

To enable using an external stack space, use the --external-stack option.

Important

If you use this function, you cannot run snippy on a model. You get the following error:

LLVM ERROR: Cannot run snippet on model: external stack was enabled.

Like the usual snippy stack option, it contradicts reserving a stack pointer:

LLVM ERROR: Cannot configure external stack: stack pointer register is explicitly reserved.

Stack Pointers

By default, snippy uses any suitable random register as a stack pointer. You can change the register to use as a stack pointer via the following setting:

--redefine-sp=

The options are:

  • any – Any random suitable register. Suitable registers are not reserved and not spilled; registers X0, X1, X6 are excluded.

  • reg::R – Any register, for example: reg::X7, reg::X12, etc.

  • SP – Stack pointer register specified by the ABI.

  • any-not-SP – Any random suitable register except for SP.

Default is:

  • SP – When the option --honor-target-abi is set to true.

  • any-not-SP – When the histogram contains SP-based instructions (For example, for RISC-V, this is C_LWSP, C_LDSP, C_SWSP, C_SDSP, C_FLWSP, C_FLDSP, C_FSWSP, C_FSDSP, C_ADDI16SP, C_ADDI4SPN.).

  • any – In all other cases.

This setting has the same value in all functions in the call graph.

Important

If you use this option with “any”, “SP” or register which is a stack pointer (for example, “reg::X2” for RISC-V), you cannot specify in the histogram opcodes that require a stack pointer. (For example, for RISC-V, this is C_LWSP, C_LDSP, C_SWSP, C_SDSP, C_FLWSP, C_FLDSP, C_FSWSP, C_FSDSP, C_ADDI16SP, C_ADDI4SPN.) You get the following error:

"error: Incompatible options: When the stack pointer is redefined to 'SP', generation of SP-relative instructions is not supported. Redefine it to 'any-not-SP' or remove SP-relative instructions from the histogram."

Static stack

Snippy has the ability to use a static stack, meaning that it do not rely on stack pointer value, but calculate the stack location for each function statically. This functionality reserves part of stack for every function and each of them saves values to the predefined addresses in any call.

To enable static stack:

--enable-static-stack=true

If no option is specified, then snippy uses static stack automatically whenever possible.

Static stack can be explicitly disabled by --enable-static-stack=false.

The feature is not supported (and therefore Snippy does not enable it automatically) when:

  • --honor-target-abi is enabled

  • section stack is not provided

  • external stack is provided

  • calls to external functions are possible

  • SMC is enabled

  • --num-instrs=all is specified

  • loop generation is possible

Benefits this feature provides:

  • When stack area is specified it becomes possible to generate instructions with SP register operand and stack-pointer specific instructions. (For RISC-V, these include compressed SP-based instructions that implicitly use X2.)

  • There are more registers available, as the SP register is no longer reserved.

Drawbacks of this feature:

  • Snippets that use static stack may occupy more stack space. The wider the function call graph, the greater the increase of the required stack size. We are going to implement some optimizations that would partially mitigate this effect in the future.

  • Increasing the size of the ancillary code.

    • The size of each function body is getting bigger. Additional ancillary instructions are used to load the address of the functions static stack area to the temporary register. This is done in both prologue and epilogue (for RISC-V, approximately 3 + 3 = 6 instructions).

    • Additional ancillary instructions are generated whenever a function is called. These are necessary to load the address of the callees static stack area (approximately 6 instructions per a function call). This is necessary for spilling registers before a call and reloading them after a call.

    • Lets consider an example – a snippet that contains three functions, each of which contains one call to another function, and let each function consist of 1000 instructions, i.e. total 3000 instructions. Static stack would add 6 instructions per a function plus 6 instruction per call, total 36 instructions, with represents approximately 1.2% increase.

Section names prefix

You can override section names prefix in the output file using --sections-prefix=<string>.

By default, snippy uses .snippy as a section names prefix. For example:

.snippy.text.rx
.snippy.data.rw
.snippy.stack.rw

etc.

Target-Specific Configuration – RISC-V

This chapter covers target-specific configuration and attributes. Currently, it only covers RISC-V, but it will expand to other targets accordingly once they become available.

RVV Support

Snippy supports RVV and have some knowledge about its semantics to avoid (or produce by request) illegal instructions and modes. Just specify vector instructions in layout.

Example:

./llvm-snippy -mtriple=riscv64-unknown-elf -seed=0 -mattr=+v \
    ./yml/layout-vector.yaml

where:

  • -mattr=+v – An attribute that enables vector instructions.

  • ./yml/layout-vector.yaml – A sample layout file with vector instructions.

  • --init-regs-in-elf – A parameter that registers initialization including in the output .elf file. For the details, refer to Initializing Registers in .elf.

  • ./yml/memory-aligned.yaml – A sample layout file used as an example of an aligned access scheme. For the details, refer to Memory Scheme.

Vector Unit Configurations

You configure reachable vector unit configurations by describing this set via a dedicated configuration section.

You can specify the probability for selecting a new RVV configuration in two mutually exclusive modes:

  • Histogram mode – For this, you need to have VSET*-like instructions in a histogram (see an example in the layout-vector.yaml file). This mode is preferable.

  • Biased mode – For this, no VSET* instructions are allowed in histograms, and you specify the desired bias for the mode change directly. For the details, refer to Biased Mode.

As a result, you have a random vector unit mode change on each VSET* in the code.

Reachable Vector Configurations

You describe a set of reachable RVV configurations using settings that you specify via a dedicated configuration section. If necessary, you can then dump the information on the dedicated section in the configuration file.

By default, if a certain RVV mode is set, snippy only generates the instructions that are possible in this RVV mode, that is, the instructions that are compatible with each other and this mode and do not cause an exception.

Note

The way some of the facilities function is specific to snippy, while some other facilities partially or fully match the official RVV specification. We will provide the links to specific descriptions where applicable.

Below is the list of settings you can specify to control the set of reachable RVV configurations for the snippet:

  • VM – Vector mask. Via VM, you provide the mask value as per the RVV specification. For example, with VM = all_ones, snippy sets masks to only be ones.

    Not all vector instructions require or consume masks. For the details on vector masking, refer here.

  • VL – Vector length. VL sets the number of elements to be processed by instructions. For the details on VL, refer here.

    VL can be an unsigned integer (for example, 2, 4), or:

    • max_encodable – Maximum possible value of VL that you can set via a particular VSET instruction. A VSET instruction, in turn, has its own limitations that depend on the selected RVV mode and the mode-changing instruction.

      For example, the vector length is set as 32, but the main VSET instruction that sets this vector unit configuration cannot use it, as its max is 16. In this case, max_encodable is 16.

      Important

      Even though VL is taken from the RVV specification, currently, the definition of max_encodable does not fully match the one provided there. Consider the description above when setting max_encodable as the VL value.

    • any_legal – Allows any legal value given the selected RVV mode and the mode-changing instruction.

    For all VL initializers, you also need to provide the width of the elements in VL via the SEW parameter.

  • SEW – Selected element width. This parameter defines the widths of the elements in VL (in bits). Possible parameters are sew_8, sew_16, sew_32, sew_64. For them, you specify the relative weights of the elements in VSET.

    For the details on SEW, refer here.

  • VXRM – Rounding mode. For the details, refer here.

  • LMUL – Multiplier that allows you to pack multiple vector registers into a group. For the details, refer here.

  • VMA, VTA – Vector mask agnostic and vector tail agnostic modes. For the details, refer here.

If you do not specify a configuration, the only reachable RVV configuration is:

{ SEW=64, VL=2, TU, MU, VM=unmasked, LMUL=1, VXRM=rnu }
Example of Vector Configuration

Following is an example of vector options specific to RISC-V:

./llvm-snippy -mtriple=riscv64-unknown-elf -seed=0 \
   -mattr=+v ./yml/layout-vector.yaml \
   ./yml/riscv-vector-unit.yaml

where:

  • -mattr=+v – An attribute that enables vector instructions.

  • ./yml/layout-vector.yaml – A sample layout file with VSETVL-like instructions.

  • ./yml/riscv-vector-unit.yaml – A sample layout file you can use to specify the vector unit configuration.

    riscv-vector-unit.yaml (example):

    riscv-vector-unit:
      mode-distribution:
        VM:
          - [all_ones, 2.0]
        VL:
          - [max_encodable, 2.0]
          - [any_legal, 1.0]
        VXRM:
          rnu: 1.0
          rne: 1.0
          rdn: 1.0
          ron: 1.0
        VTYPE:
          SEW:
            sew_8: 1.0
            sew_16: 1.0
            sew_32: 1.0
            sew_64: 1.0
          LMUL:
            m1: 1.0
            m2: 1.0
            m4: 1.0
            m8: 1.0
            mf2: 1.0
            mf4: 1.0
            mf8: 1.0
          VMA:
            mu: 1.0
            ma: 1.0
          VTA:
            tu: 1.0
            ta: 1.0
    

    Note

    You do not need to list all the values and set zero weights for those that you do not want to be generated.

  • ./yml/memory-stride1.yaml – A sample layout file used as an example of memory access scheme. For the details, refer to Memory Strides.

Biased Mode

To specify a mode-changing bias, add the mode-change-bias key to the RVV-configuration YAML file. Use this mode to set:

  • P – A fixed probability of the RVV mode change regardless of the number and type of instructions in the input histogram.

  • Pvill – A probability that an illegal configuration (SEW, LMUL) will be selected when the opcode vset{i}vl{i} is selected. It is equivalent to the probability of setting a vill bit in a CSR vtype.

    When an illegal configuration is selected, snippy generates only the opcodes that are legal for that configuration. These opcodes must be explicitly specified in the histogram, otherwise snippy generates an error. For example:

    histogram:
           # Illegal when vill set
           - [VAADDU_VV, 1.0]
           - [VAADD_VX, 1.0]
           - [VADC_VIM, 1.0]
           ...
    
           # Legal when vill set
           - [VL1RE16_V, 1.0]
           - [VL1RE32_V, 1.0]
           - [VL1RE64_V, 1.0]
           ...
    

    After changing the configuration to a legal one, snippy continues to generate opcodes according to the whole histogram.

See an example below.

Important

You cannot use VSET* instructions in the histogram in the biased mode.

riscv-vector-ill.yaml:

riscv-vector-unit:
  mode-change-bias:
    P: 0.2
    Pvill: 1.0
  mode-distribution:
    VM:
      - [all_ones, 2.0]
    VL:
      - [max_encodable, 1.0]
    VXRM:
      rnu: 1.0
    VTYPE:
      SEW:
        sew_8: 1.0
      LMUL:
        m8: 1.0
      VMA:
        mu: 1.0
      VTA:
        tu: 1.0

The mode-change-bias key represents the weight of mode changing instructions relative to all other instructions in a histogram (regardless of the instruction type). More specifically, the final weight of VSET* instructions is calculated as follows:

WEIGHT = WEIGHT_OF_ALL_INSTRUCTIONS_FROM_HISTOGRAM * mode-change-bias::P / 3

where it is divided by 3 because there are exactly 3 mode-changing instructions: VSETVL, VSETVLI, VSETIVLI.

For example, if mode-change-bias::P is 1.0, the probability to encounter a VSET* instruction is about 50 %.

Example snippy run:

./llvm-snippy -mtriple=riscv64-unknown-elf -seed=0 -mattr=+v \
   ./yml/layout-vector-nvs.yaml ./yml/riscv-vector-ill.yaml \
   -num-instrs=1000

Important

You need to have some possibly illegal instructions (like V1SR_V) in histogram. Otherwise you will get error “can not create any primary instruction in this context”, which in some cryptic manner informs you (in this case), that in illegal context there are no possibly illegal instructions found. In other context this error may mean different things (like no legal found, etc).

RVV Configuration Dump

Use the -riscv-dump-rvv-config[=<filename>] option to dump information on a declared RVV configuration.

You can omit specifying the filename. In this case, snippy prints the information to stdout.

The resulting output looks similar to the following:

--- RVV Configuration Info ---
 - Derived VLEN: 128 (VLENB = 16)
 - Mode Change Decision Policy: Configuration Bias
 - Mode Change Probability: 0.5
 - VL Selection Rules:
   P: 0.66667 <vlmax>
   P: 0.33333 <any_legal>
 - VM Selection Rules:
   P: 0.66667 <all_ones>
   P: 0.33333 <any_legal>
 - Configuration Bag Listing:
   P: 0.25 Conf: { e64, m1, tu, mu, vxrm: rnu }/MaxVL: 2
   P: 0.25 Conf: { e64, m2, tu, mu, vxrm: rnu }/MaxVL: 4
   P: 0.25 Conf: { e64, m4, tu, mu, vxrm: rnu }/MaxVL: 8
   P: 0.25 Conf: { e64, m8, tu, mu, vxrm: rnu }/MaxVL: 16
   P: 0.5 Conf: {  Illegal Configurations:  1344 points }/MaxVL: 0
 - Configuration Bag Size: 4
 - State Cardinality: 30 ~ {MASKS}
--- RVV Configuration End  ---

Self-check

Important

Currently, this functionality does not supported without model plugin

Self-check is a code generation mode. After each (or each N) main instruction, snippy stores an etalon value and the result of the instruction to the selfcheck key. After execution, you have a section filled with pairs of values (etalon1-real1, etalon2-real2, etc). These values can be then compared to check if there are any differences.

To use this feature:

  1. Enable the self-check setting via options:

    selfcheck: <N>
    

where <N> is each Nth instruction snippy will have self-checked. For example, if you want snippy to self-check each 100th instruction, use 100. . To your layout, add the selfcheck key with the parameters of where you want to store the self-check values:

  • Name: selfcheck

  • Access: rw

    For example:

    sections:
    ...
        - name: selfcheck
          VMA:       0x310000
          SIZE:      0x100000
          LMA:       0x310000
          ACCESS:    rw
    ...
    

    Note

    In snippy, sections are ordered by their address (VMA) and not by the order you specify them in the configuration.

As a result, the section gets filled after each instruction with a register destination etalon and a real register value in the following format:

REAL-1, ETA-1, REAL-2, ETA-2, REAL-3, ETA-3, ....

where every value spans 128-bit.

Self-check for RVV

Important

Currently, this functionality does not supported without model plugin

As the self-check feature is target-independent, it needs to be additionally enabled for RVV.

Self-check for RVV is currently in the experimental state. To enable it:

--enable-selfcheck-rvv

Self-check Properties to Global Variables

Important

Currently, this functionality does not supported without model plugin

Self-check requires a certain amount of additional memory to be available. If you need to learn where the selfcheck key is after generation in other applications, add its properties (such as VMA, size, and byte stride) to global constants. This makes it possible to link them as external variables.

To add the properties of the selfcheck key as global constants, use the --selfcheck-gv setting. Once generated, global variables explicitly indicate where the selfcheck key starts and ends.

Following is an example of the external user code to work with snippy self-check global variables:

extern const unsigned long long *__snippy_selfcheck_section_address;

extern unsigned long long __snippy_selfcheck_section_size;

extern unsigned __snippy_selfcheck_data_byte_stride;

/* some code */

static void check_selfcheck_section() {
  const char *ptr_pos = (const char *)__snippy_selfcheck_section_address;
  unsigned distance_to_next_pair = 2 * __snippy_selfcheck_data_byte_stride;
  start_analysis_report();

  for (const char *end_pos = ptr_pos + __snippy_selfcheck_section_size;
       ptr_pos != end_pos;
       ptr_pos += distance_to_next_pair)
    if (check_two_cells_identity(ptr_pos,
           ptr_pos + __snippy_selfcheck_data_byte_stride,
           __snippy_selfcheck_data_byte_stride))
      return;

  success_report();
}

Global Constants

In some scenarios, snippy needs a read-only section to write the constant data to. For example, this section can be used to store constants for register initialization, or when creating global constants, for example, selfcheck-related ones.

You provide a read-only section to write the constant data to in the sections entry of your snippy configuration. You can see an example of such a section in layout-vector-init.yaml in the ./yml directory: use this section as is or as a template for your own configuration.

Generated Linker Script

A linker script is generated by default when you run snippy.

The generated linker script is used to properly place sections of the snippy object file in the resulting executable image. At the link stage of an application that uses a snippy object, you need to:

  1. Use you own linker script. For example, the one that is default for the environment.

  2. Insert the generated linker script there. For example, using the INCLUDE() statement.

If a default environment linker script is used, and it cannot be modified, then:

  1. Create a copy of such a script.

  2. Modify it as required.

  3. Pass this copy of the script explicitly at the link stage.

Tip

Additional options for GCC-based toolchains:

  • -T script-name – Add this option to the GCC invocation to pass a custom linker script.

  • --script=script-name – Use this option to pass a custom linker script directly to ld.

  • -Wl,--verbose – Run the GCC link step with this option to get the environment default linker script.

This way, when running the following example:

./llvm-snippy -mtriple=riscv64-unknown-elf -mattr=+v \
    ./yml/layout-vector-init.yaml -num-instrs=100 -seed=0 \
    ./yml/memory-aligned.yaml \
    -init-regs-in-elf -o layout.elf

it generates the following linker script:

layout.elf.ld:

MEMORY {
  SNIPPY (rwx) : ORIGIN = 2162688, LENGTH = 4194304
}
SECTIONS {
  .snippy.2.rx 2162688: {
  KEEP(*(.snippy.2.rx))
} >SNIPPY
  .snippy.1.r 3211264: {
  KEEP(*(.snippy.1.r))
} >SNIPPY
  .snippy.3.rw 5242880 (NOLOAD) : {
  KEEP(*(.snippy.3.rw))
} >SNIPPY
}

Both output files (layout.elf and layout.elf.ld) have the same name, but different extensions.

Also, in your layout, provide a read-only first section to place global constants:

layout-vector-init.yaml:

sections:
  - name:        1
    VMA:       0x310000
    SIZE:      0x100000
    LMA:       0x310000
    ACCESS:    r
  - name:        2
    VMA:       0x210000
    SIZE:      0x100000
    LMA:       0x210000
    ACCESS:    rx
  - name:        3
    VMA:       0x500000
    SIZE:      0x100000
    LMA:       0x500000
    ACCESS:    rw

histogram:
    - [VADC_VIM, 1.0]
    - [VADD_VI, 1.0]
    - [VAND_VI, 1.0]
    - [VMERGE_VIM, 1.0]

Co-simulation

Important

Currently, this functionality does not supported without model plugin

Co-simulation allows you to run a generated snippy .elf file on multiple models and perform a step-by-step state comparison of them.

The generation process only uses one “primary” model plugin – the one you specify in the plugin-model string. All the other plugins are “secondary”, or co-simulation, models, and you specify them in the cosim-model-plugins string.

Important

Do not use the same plugin for both plugin-model and cosim-model-plugins: it leads to an undefined result.

To specify two and more models:

  • Via options:

    plugin-model: <primary_model>
    cosim-model-plugins: [<cosim_model1>, <cosim_model2>]
    
  • Via snippy command line:

    -plugin-model=<primary_model> \
    -cosim-model-plugins=<model_1>,<model_2>
    

State Comparison

At each step of the simulation, the program compares a state of each model against a “primary” one. If the program finds a difference, it issues an error and terminates further execution.

Note

For now, only the register file state is considered.

Error message:

LLVM ERROR: Interpreters states differ

Preceding the error message, you can also see a combined log from all the simulator executions with messages from the respective simulators. Use the log to check the last executed instruction and find the cause of the error.

Trace Log

Dump Filename

When you use the --trace-log=<filename> with multiple models, a log from all the co-simulations models is redirected to the <filename>.plugin file. The “primary” model log is directed to as before. This way, each model gets its own log file.

Run example:

./llvm-snippy -mtriple=riscv64-unknown-elf -mcpu=<processor_model> \
    ./yml/layout.yaml -num-instrs=100 -seed=0 ./yml/memory.yaml \
    -plugin-model=<model_plugin>.so -cosim-model-plugins=<co-sim_model>.so \
    --init-regs-in-elf -trace-log=log -o layout.elf

In this case you get two files: log and log.<co-sim_model>.

Range of Instruction Addresses

You can specify the range of instruction addresses that you need the data from. Snippy then uses this setting to generate an output file with metadata of all the useful addresses in a closed [first, last] interval in the hex format.

To specify the range:

  • Via options:

    dump-intervals-to-verify: ""
    dump-intervals-to-verify: "-"
    dump-intervals-to-verify: "<filename>.yaml"
    
  • Via the command line:

    -dump-intervals-to-verify
    -dump-intervals-to-verify=-
    -dump-intervals-to-verify=<filename>.yaml
    

where you use:

  • "" – To dump the output to a default file ${output_basename}.intervals-to-verify.yaml.

  • "-" – To dump the output to stdout.

  • "<filename>.yaml" – To dump the output to <filename>.yaml.

LLVM-IE Documentation

Overview

llvm-ie is a utility tool designed to conveniently extract instructions from various computer architectures supported by LLVM based on specific criteria.

In LLVM, a wide range of backend components—including instructions, registers, and scheduling models—is defined in TableGen (.td) files. The build process compiles these descriptions into the C++ data structures that form the backend’s internal representation.

llvm-ie specifically queries the information derived from instruction definitions, which contains rich details such as whether an instruction is a branch, a call, or accesses memory.

Initially, llvm-ie was created to extract RISC-V instructions belonging to specific RISC-V extensions. However, its functionality was later expanded to include architecture-independent filters and a modular design for adding new architecture backends.

While the tool was originally developed as a helper utility for llvm-snippy, which uses it to eliminate boilerplate code, we believe llvm-ie can be useful in other scenarios as well.

Running llvm-ie

To use llvm-ie, you must explicitly specify the target backend using the -arch option.

For example, to list instructions corresponding to the “A” extension for the RISC-V architecture, use the following command:

llvm-ie -arch=riscv -riscv-ext "a"

Important

Currently, llvm-ie only supports the RISC-V backend.

Target-Independent Options

Currently, llvm-ie supports the following architecture-independent filters:

  • -memory-access: Filters for instructions that perform memory accesses (loads/stores).

  • -control-flow: Filters for instructions that affect control flow (jumps, branches, calls).

Target-Dependent Options

RISC-V Options

Currently, llvm-ie supports the following RISC-V-specific options:

  • -riscv-ext: Filters instructions by RISC-V extensions using expression logic.

  • -rv32: Restricts the output to instructions available in the RV32 base instruction set.

  • -rv64: Restricts the output to instructions available in the RV64 base instruction set.

Extension Expressions

The -riscv-ext option is the most powerful, as it allows you to get instructions belonging to specific RISC-V extensions. This option takes an expression composed of RISC-V extension names as operands and three basic operations:

  • + : Union (OR)

  • - : Exclusion (NOT)

  • & : Intersection (AND)

You can draw an analogy to set algebra: extension names are sets, and the basic operations +, -, and & correspond to union, set difference, and intersection, respectively.

Operation Notes and Caveats

  1. Supported Operations: Only these three operations (+, -, &) are currently supported. Parentheses and other operations are not available.

  2. Precedence and Syntax: The & operator has higher precedence than + and -. To simplify the parser, the & operator must be written without spaces between its operands (e.g., "c&d" instead of "c & d").

  3. Parser Behavior: The current parser is designed to be permissive and may output extra instructions. This was a deliberate decision to avoid accidentally filtering out required instructions. For example, when asking for the “V” extension, llvm-ie might also include instructions that require both the “V” and “F” extensions to be present. To get a more precise output, you can use more complex expressions. For instance, to request the “V” extension but exclude instructions that also require the “F” extension, you could use:

    llvm-ie -arch=riscv -riscv-ext "v - f"
    

Usage Examples

# Instructions requiring both C and D extensions
llvm-ie -arch=riscv -riscv-ext "d&c"

# Vector instructions excluding D and F extensions
llvm-ie -arch=riscv -riscv-ext "v - d - f"

# Compressed instructions that access memory
llvm-ie -arch=riscv -riscv-ext "c" -memory-access

# RV32 control-flow instructions from I, M, C, B extensions
llvm-ie -arch=riscv -riscv-ext "i + m + c + b" -rv32 -control-flow