.. only:: builder_html or builder_simplepdf
.. |nbsp| unicode:: 0xA0
:trim:
.. contents::
.. rst-class:: break_before
Snippy Documentation (|release|)
################################
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 |nbsp| -- |nbsp| sequences of randomly selected machine instructions with randomly selected registers, immediates, and other attributes that you configure via `histograms <#histogram>`__ and organize via `sections <#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`:
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`:
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`:
Output
------
The snippy output includes:
- An .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). For more information, see
`The type of output .elf file`_.
- 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 `generated 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, for `global constants`_.
- Dump of the registers state after execution. To produce the register
state use **--dump-registers-yaml** option. See
`Final Registers State <#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 <#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 <#sections>`__ and
`histogram <#histogram>`__.
2. `Add the "options" key with the options to run snippy on <#adding-options>`__.
3. Run the configuration file(s):
::
llvm-snippy .yaml
where ```` 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 .yaml .yaml .yaml
4. If necessary, `print the output <#printing-snippy-output>`__.
.. _`_adding_options`:
Adding Options
--------------
You specify `options <#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 .yaml .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 <#options>`__ of this guide.
.. _`_limitations`:
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`:
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 .yaml .yaml .yaml -E
.. _`_yaml_layout`:
YAML Layout
===========
.. important::
As described in the `Running Snippy <#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 <#sections>`__ with at least one RX section and one RW
section.
- `histogram <#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 <#sublayouts>`__.
.. _`_layout_example`:
Layout Example
--------------
Following is an example of the configuration layout that contains:
- Mandatory ``sections`` and ``histogram`` keys
- Optional ``options`` key
.. code:: yaml
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**
The *ISA string* that specifies an architecture for which to generate code.
The value must be in lowercase.
See `Platform Support`_ for information about supported architectures.
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 RISC-V atomics, use ``-mattr=-a``.
- If you want to include RISC-V 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) |nbsp| -- |nbsp| 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 |nbsp| -- |nbsp| 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 <#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 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:
- `Basic configuration <#basic-configuration>`__
- `Memory configuration <#memory-configuration>`__
- `Advanced configuration <#advanced-configuration>`__
You select the keys depending on how complex you want your YAML
configuration for snippy to be.
.. _`_sublayouts`:
Sublayouts
----------
You can also use sublayouts using the ``include`` key in your YAML
configuration file of via the command line. For example:
.. code:: yaml
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`:
Contents of Sublayouts
~~~~~~~~~~~~~~~~~~~~~~
A sublayout file can include:
- Both ``sections`` and ``histogram``.
.. container:: formalpara-title
**Example of ``layout-toplevel.yaml``:**
::
include:
- sublayout-hist.yaml
- sublayout-sections.yaml
- Only ``sections``.
.. container:: formalpara-title
**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``.
.. container:: formalpara-title
**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`:
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`:
Basic Configuration
===================
Mandatory keys of a basic configuration for snippy are
`sections <#sections>`__ and `histogram <#histogram>`__.
Additionally, as a part of your basic configuration for snippy, you can
provide details for `immediate histograms <#immediate-histograms>`__
and `FPU <#fpu-configuration>`__.
.. _`_sections`:
Sections
--------
The ``sections`` key provides a list of sections for the output file
with the following details for each entry:
- ``name`` |nbsp| -- |nbsp| 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`` |nbsp| -- |nbsp| Virtual memory address
- ``SIZE`` |nbsp| -- |nbsp| Section size
- ``LMA`` |nbsp| -- |nbsp| Load memory address
- ``ACCESS`` |nbsp| -- |nbsp| Access type indication (``r``, ``rx``, ``rw``, ``rwx``)
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 |nbsp| -- |nbsp| in this case, you need to provide additional
memory schemes. For the details, refer to `Memory
Scheme <#memory-scheme>`__.
If present, snippy places the code in the executable sections.
- ``PHDR`` (optional) |nbsp| -- |nbsp| 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`:
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 <#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`:
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:
.. code:: yaml
imm-hist:
- [-1, 1.0]
- [2, 1.0]
- [3, 1.0]
- Immediate histograms for each opcode or opcode regex pattern. For
example:
.. code:: yaml
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`:
Operation Codes for Specific Architecture
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To obtain opcodes available for a certain architecture machine
description, run:
.. code:: shell
./llvm-snippy -mtriple=riscv64-unknown-elf -mcpu= \
-march= --list-opcode-names
For supported values of ````, see `Platform Support`_.
.. _`_fpu_configuration`:
FPU Configuration
-----------------
You can control settings for floating point unit (FPU) using the
``fpu-config`` key in your layout.
NaN overwriting
~~~~~~~~~~~~~~~
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``.
The NaN overwriting feature is intended to reduce the number of NaNs thereby
increasing the diversity of results of floating-point operations. The settings
of this feature are specified under the ``fpu-config.overwrite`` key.
These settings define:
- When NaN overwriting should happen (``mode`` and ``nan-rewrite-ratio`` keys),
- What values should be used to overwrite NaNs
(``range``, ``ieee-single.valuegram``, ``ieee-double.valuegram``)
See an example below.
.. container:: formalpara-title
**fpu-config.yaml:**
.. code:: 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`` |nbsp| -- |nbsp| Specific range of the values to
use. ``weight`` is optional and ``1.0`` by default.
- ``rounding-mode`` |nbsp| -- |nbsp| FPU rounding directions according to the IEEE
standard. You can use both abbreviated options and their aliases:
- ``rup`` |nbsp| -- |nbsp| ``toward-positive``
- ``rdn`` |nbsp| -- |nbsp| ``toward-negative``
- ``rtz`` |nbsp| -- |nbsp| ``toward-zero``
- ``rne`` |nbsp| -- |nbsp| ``nearest-ties-to-even``
- ``rmm`` |nbsp| -- |nbsp| ``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) <#value-histogram-valuegram>`__ chapter.
- ``mode`` |nbsp| -- |nbsp| Heuristic that determines which register is a NaN and must
be overwritten. The following options are available:
- ``if-all-operands`` |nbsp| -- |nbsp| A register is considered a NaN if all of its
input registers are potentially NaNs.
- ``if-any-operand`` |nbsp| -- |nbsp| A register is considered a NaN if any of its
input registers are potentially NaNs.
- ``if-model-detected-nan`` |nbsp| -- |nbsp| The model plugin detected this
register to be a NaN.
- ``disabled`` |nbsp| -- |nbsp| ``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.
- ``nan-rewrite-ratio`` sets the threshold when NaN overwriting should happen.
The ``threshold`` is a the proportion of NaN registers among all registers
(tracked separately for each class of registers). For example, if
``nan-rewrite-ratio`` is 0.25, then NaN overwriting happens as soon as snippy
detects that 8 or more registers (either single, double or half precision)
have become NaNs (8 / 32 = 0.25).
This key is optional, default value is 0 (a register is overwritten as
soon as its value is considered a NaN). Non-zero value extends the lifetime
of NaNs, which increases the probability of nontrivial results of
floating-point operations.
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
====================
Memory configuration provides extensive features for describing the way
you want memory to be accessed, and includes keys for:
- `Memory scheme <#memory-scheme>`__
- `Burst mode <#burst-mode>`__
.. _memory-scheme:
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 <#memory-scheme-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`:
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 |nbsp| -- |nbsp| 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) |nbsp| -- |nbsp| Access size 1 byte
- ``LH`` (load, half-word) |nbsp| -- |nbsp| Access size 2 bytes
- ``LW`` (load-word) |nbsp| -- |nbsp| Access size 4 bytes
- ``LD`` (load double word) |nbsp| -- |nbsp| 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 <#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`:
RVV Instructions in Memory Schemes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Vector instructions work on a group of elements simultaneously.
Snippy supports the following memory-related vector instructions:
- `Strided load/store <#strided-loadstore>`__
- `Unit-stride load/store <#unit-stride-loadstore>`__
- `Indexed load/store (ordered and unordered) <#indexed-loadstore>`__
- Whole register load/store
Unit-stride, strided, and indexed instructions also support `segment
instructions <#segment-loadstore>`__.
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-loadstore:
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 <#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-loadstore:
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 <#selecting-applicable-memory-schemes>`__ with the alignment on
one element (``4``, not ``16``).
For more details on unit-stride instructions, refer
`here `__.
.. _indexed-loadstore:
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_loadstore`:
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`:
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 <#selecting-applicable-memory-schemes>`__.
2. Generates a strided instruction |nbsp| -- |nbsp| 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:
.. container:: formalpara-title
**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`` |nbsp| -- |nbsp| Section start address
- ``size`` |nbsp| -- |nbsp| Section size
- ``stride`` |nbsp| -- |nbsp| Stride size
- ``first offset`` |nbsp| -- |nbsp| Number of the first available bit in a stride, which access can start from
- ``last offset`` |nbsp| -- |nbsp| Number of the last available bit in a stride, which access can start from
- ``max-past-last-offset`` |nbsp| -- |nbsp| Maximum number of bits after ``last-offset`` that can be accessed (optional).
- ``access-size`` |nbsp| -- |nbsp| 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`` |nbsp| -- |nbsp| 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 the `example ` . Here, the bytes that can be accessed are indicated in red. The yellow color indicates the bytes accessed by the memory instruction.
.. _mem-scheme-image:
.. figure:: ./_static/snippy-mem.png
:alt: Memory scheme
:scale: 80%
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:
.. container:: formalpara-title
**eviction.yaml (example):**
::
access-evictions:
- mask: 0x003c0000
fixed: 0x80000c81
weight: 2
- mask: 0x04000000
fixed: 0x000000ff
weight: 3
where:
- ``mask`` |nbsp| -- |nbsp| Bites that can be changed
- ``fixed`` |nbsp| -- |nbsp| Fixed bites
- ``weight`` |nbsp| -- |nbsp| Optional value, relative probability of this access range. If you omit it, snippy considers it as ``1``.
See how memory eviction works:
.. figure:: ./_static/addrscheme.png
:alt: Memory eviction
Memory eviction scheme
.. _`_addresses_enumeration_scheme`:
Addresses Enumeration Scheme
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Use this memory scheme to specify individual addresses. Each address can
have multiple parameters.
.. container:: formalpara-title
**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`` |nbsp| -- |nbsp| Snippy takes all addresses in order (default).
- ``false`` |nbsp| -- |nbsp| Snippy selects a random address.
- ``weight`` |nbsp| -- |nbsp| Optional value. If you omit it, snippy considers it as
``1``.
- ``addr`` |nbsp| -- |nbsp| Address value (required).
- ``access-size`` |nbsp| -- |nbsp| 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`` |nbsp| -- |nbsp| 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 <#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.
.. container:: formalpara-title
**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`` |nbsp| -- |nbsp| Address value (required).
- ``access-size`` |nbsp| -- |nbsp| Access size (optional). If you do not provide a
value, the default is 16 bytes.
.. _`_memory_scheme_adjustments`:
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.
You can override this setting for a particular access range. To do it, use the
``misaligned-access`` key in the definition of an access range. It takes
priority over ``--riscv-disable-misaligned-access``.
.. code:: yaml
options:
march: riscv64-linux-gnu
num-instrs: 100
riscv-disable-misaligned-access: true
sections:
- no: 1
VMA: 0x80000000
SIZE: 0x400000
LMA: 0x80000000
ACCESS: rx
access-ranges:
- start: 0x80070000
size: 0x40
stride: 64
first-offset: 0
last-offset: 63
misaligned-access: true
- start: 0x80080000
size: 0x40
stride: 64
first-offset: 0
last-offset: 63
histogram:
- [LW, 1.0]
- [SW, 1.0]
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:
.. code:: yaml
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[=]`` 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[=]`` option. You can
then use it as a `negative memory
scheme <#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
----------
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::
Currently, RVV, calls and branches can not be generated in
a burst group. If an unsupported opcode is specified in the custom
burst group, it will be generated outside this group.
Following is an example of the ``burst`` key:
.. code:: yaml
burst:
mode: custom
min-size: 10
max-size: 30
groupings:
- [ FENCE, LW ]
- [ SW ]
where:
- ``mode`` |nbsp| -- |nbsp| Sets the grouping mode. Available values are:
- ``load`` |nbsp| -- |nbsp| Issues load groups.
- ``store`` |nbsp| -- |nbsp| Issues store groups.
- ``load-store`` |nbsp| -- |nbsp| Issues loads and stores as separate groups.
- ``mixed`` |nbsp| -- |nbsp| Issues groups with mixed loads and stores in one
group.
- ``basic`` |nbsp| -- |nbsp| Is equal to no burst.
- ``custom`` |nbsp| -- |nbsp| 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`` |nbsp| -- |nbsp| 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 <#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:
- `Branchegrams <#control-flow-branchegrams>`__
- `Call graph <#call-graph>`__
- `Register Reservation Config`_
.. _register-reservation:
Register Reservation Config
---------------------------
To control which registers can be read or modified by the generated program
you can specify ``register-reservation`` top-level YAML key.
.. note::
The use of `-reserved-regs-list`_ option is also possible but discouraged
because it reserves registers only for use in primary instructions.
This ``register-reservation`` key contains a sequence of register-to-access map.
Example:
.. code:: yaml
register-reservation:
# Reserved for all reads and writes
- X1: [RW]
# Same
- X2: [R, W]
# Reserved for all writes but can still be read
- X3: [W]
Each specified register maps to a sequence of access bits. Available options
here are:
- **R** -- reserves registers for all reads
- **W** -- reserves registers for all writes
- **RW** == **R & W** -- reserves registers for all reads and writes [#exception]_
- **SR** -- Soft-reserves register for read (Can not be read by primary instruction but can be read by ancillary)
- **SW** -- Soft-reserves register for write (Can not be written by primary instruction but can be modified by ancillary)
- **SRW** == **SR & SW**-- Soft-reserves register for reads and writes. I.e. no primary instructions can access it
You can also leave access bits sequence empty which means register is not
reserved at all (default):
.. code:: yaml
register-reservation:
- X1: [] # has no effect (can be removed)
.. [#exception] Stack Pointer register can still be used for stack access even
if it is reserved as RW but no other instruction will read or modify it.
Without this config snippy still will not modify Stack Pointer but can generate
reads from it in primary instructions.
.. note::
Throughout this manual, *primary instructions* are instructions from
histogram (randomly generated), while *ancillary instructions* are generated
to support primary instructions. For example, instructions that perform
address materialization (needed to emit loads from memory) are the
*ancillary* ones. Ditto instructions that initialize registers emitted when
the **-init-regs-in-elf** option is specified.
Consider an example where we want to reserve registers X10 and X11 for reads and modification by
primary instructions:
.. _soft-reserve-x1011:
.. code:: yaml
register-reservation:
- X10: [SR, SW]
- X11: [SRW] # same because SRW is alias to SR and SW
As mentioned above, the `-reserved-regs-list`_ option
does not prevent register from being read and modified by ancillary instructions
(i.e. it performs "soft" reservation of registers). Therefore adding register to
**--reserved-regs-list** is the same as adding it to **register-reservation**
config with **SRW**. So the previous :ref:`example ` is
equivalent to adding **-reserved-regs-list=X10,X11** option. And every
**-reserved-regs-list** option in snippy is internally converted to a
**register-reservation** config with all mention registers reserved as **SRW**.
.. warning::
It is forbidden to use both **register-reservation** config and
**-reserved-regs-list** option at the same time
You can also use regex to reserve registers (snippy uses whole-word matching).
The following soft-reserves X10-X17 registers for writes and reserves X1-X4 for
all types of accesses:
.. code:: yaml
register-reservation:
- "X[1234]": [SW]
- "X1[0-7]": [R, W]
Note here that register that was matched by a regex will not be re-matched by
the latter regex. For example:
.. code:: yaml
register-reservation:
- X10: [W]
- "X1[0-7]": [RW] # Also matches X10
Here **X10** is reserved as **W** by the first entry of the sequence and the
second regex (that matches it) has no effect on it. So in this program **X10**
CAN be read by both primary and ancillary instructions.
Snippy can implicitly reserve some registers by default:
- **RISC-V:** Stack Pointer register **X2** is reserved as RW if both **-honor-target-abi** and
**external-stack** options are enabled. Otherwise snippy could generate reads
of stack pointer in arithmetic operations from histogram making the program
execution depend on stack location, which is non-reproducible
if external stack is used.
Register names are described in the `Register Naming`_ section.
Register Naming
~~~~~~~~~~~~~~~
RISC-V Floating-point Registers (F, D, Zfh extensions)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RISC-V floating-point registers should be specified using the following syntax: ``_``. *register-name* is the name of one of the 32 floating-point registers (``F0`` ... ``F31``), *precision* is a letter, as specified in the RISC-V Instruction Set:
* ``F`` -- Single precision (32 bit registers)
* ``D`` -- Double precision (64 bit registers)
* ``H`` -- Half precision (16 bit registers)
For example, to refer to the ``F4`` register, use one of the following names: ``F4_F``, ``F4_D``, ``F4_H``. ``F4_D`` denotes the whole 64-bit register, ``F4_F`` -- the lower half of the same register (32 bits out of 64), and ``F4_H`` -- the lower quarter of the same register (16 bits out of 64).
.. note::
Reserving a floating-point register for one precision results in reserving the same register for all precisions.
Example:
.. code:: shell
~/snippy-oss/bin/./llvm-snippy examples/layout-calls.yaml -seed=1 \
-reserved-regs-list=F4_D,F5_D
RISC-V Vector Registers (V extension)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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 the
vector unit so that vector registers get grouped when executing certain
instructions. For the details on vector register grouping, refer
`here `__.
.. note::
Even if you reserve only one register from the vector group, the whole register group is reserved for the respective instructions.
Control Flow (Branchegrams)
---------------------------
A branchegram |nbsp| -- |nbsp| the ``branches`` key of the configuration |nbsp| -- |nbsp| 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:
.. code:: yaml
branches:
permutation: off
...
How to Add Branchegrams
~~~~~~~~~~~~~~~~~~~~~~~
- Include a branchegram YAML file in your snippy layout:
.. code:: yaml
include:
- branchegram.yaml
...
- Add a ``branches`` key along with the ``sections``, ``histogram``,
and other keys in your configuration file(s).
.. _`_generating_loops`:
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.
.. container:: formalpara-title
**branchegram.yaml (example)**
.. code:: yaml
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`:
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=`` – 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`:
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.
.. _generated-call-graph:
Generated Call Graph
~~~~~~~~~~~~~~~~~~~~
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 <#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)`` |nbsp| -- |nbsp| Number of generated functions used as
nodes to construct a call graph.
- ``--function-layers=(int)`` |nbsp| -- |nbsp| Maximum depth of the call graph.
- ``--num-instr-ancil=(int)`` |nbsp| -- |nbsp| 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)`` |nbsp| -- |nbsp| Density of connections inside a call
graph. Passing ``0`` will not generate any connections at all.
- ``--call-graph-force-connect (bool)`` |nbsp| -- |nbsp| 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)`` |nbsp| -- |nbsp| 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:
.. container:: formalpara-title
**layout-calls.yml**
::
function-layers:
function-number:
num-instr-ancil:
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:
.. figure:: ./_static/call_graph.png
:alt: Call graph
Resulting call graph
.. _`_external_call_graph`:
External Call Graph
~~~~~~~~~~~~~~~~~~~
You can specify a call graph in contrast to snippy randomly generating
it. See example:
.. figure:: ./_static/snippy-cg.png
:alt: Call graph
:align: center
:scale: 50%
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`` |nbsp| -- |nbsp| A sample layout file you can find
in the ``./yml`` directory.
- ``function-layers`` |nbsp| -- |nbsp| The number of layers in the graph.
- ``cg-predefined.yaml`` |nbsp| -- |nbsp| A sample layout file used as the graph
description.
- ``call-graph-dump-filename`` |nbsp| -- |nbsp| The direction where to save the
graph dump.
- ``call-graph-dump-format`` |nbsp| -- |nbsp| The format of the graph dump.
An example of the ``call-graph`` key:
.. code:: yaml
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`` |nbsp| -- |nbsp| 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 <#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 <#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`:
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:
.. code:: yaml
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:
.. code:: yaml
./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`:
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 |nbsp| -- |nbsp| the configuration file. For example:
::
./llvm-snippy -mtriple=riscv64-unknown-elf -mcpu= .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`:
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`` |nbsp| -- |nbsp| To disable getting specific warnings. For example:
::
-Wdisable=,,...
If you try to disable a category that does not exist, you will get an
error.
- ``-Werror`` |nbsp| -- |nbsp| 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.
Several warnings are treated as errors by default. They are:
- ``non-reproducible-execution``
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`` |nbsp| -- |nbsp| 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`:
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 to '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= -mabi=lp64 -mattr="-f,-d,-c"
.. _`_generating_abi_with_respect_to_target`:
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:
.. code:: yaml
...
- name: utility
VMA: 0x310000
SIZE: 0x1000
LMA: 0x310000
ACCESS: rw
...
Registers are "soft"-reserved |nbsp| -- |nbsp| 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 <#stack-section>`__.
Preserving caller saved registers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If generated code by snippy can make calls to external functions, you can request
the preservation of caller-saved registers around those calls.
Setting the option in the configuration file:
.. code:: yaml
options:
preserve-caller-saved-regs: [X, F, V]
Setting the option via the command line:
::
-preserve-caller-saved-regs=X,F,V
If this option is specified, all caller-saved registers belonging to the passed
register groups (according to the ABI) are preserved around external function calls.
The notation uses the following abbreviations for register classes:
- X stands for GPR (General Purpose Registers)
- F stands for FPR (Floating-Point Registers)
- V stands for vector registers
Also, when honor-target-abi is enabled, only live caller-saved registers are
preserved around external function calls.
Generated Function Naming
-------------------------
The default name of the function produced by llvm-snippy is "SnippyFunction".
This can be changed using ``--entry-point`` option. It allows you to specify
a custom name for the generated function, which is useful when you want to call
it from a generic startup code. In case of generating several functions (like
with `generated-call-graph`_) this option renames the main (entry) function.
For example if we want entry function to be named "test_code":
.. code:: yaml
options:
entry-point: test_code
Or the same via command line:
::
-entry-point=test_code
.. _`_last_instruction`:
Last Instruction
----------------
The default last instruction is ``EBREAK``.
If you want to set a custom last instruction, use the
``--last-instr=`` option. To emit return, use ``RET``.
Use ``--last-instr=`` with no option specified to make no additional
last instruction.
.. _`_registers_subset_fix`:
Registers Management
--------------------
.. caution::
If you specify spilled or reserved registers, verify you do not use the
same register for both. A register can either be `spilled <#spilling-registers>`__
or reserved by `register-reservation`_ (or `-reserved-regs-list`_)
but not both at the same time.
.. _`_spilling_registers`:
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 <#stack-section>`__.
Following is an example for the ``options`` key:
.. code:: yaml
spilled-regs-list: []
where ```` is a list of registers to spill, for example,
``X1,X2,X3`` (comma-delimited).
.. 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"
.. _-reserved-regs-list:
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 reserves
registers only for the primary instructions. However,
ancillary instructions can still use these reserved registers.
.. warning::
This option is deprecated and will be removed in the future major release.
We recommend replacing it with `register-reservation`_. Adding register to
**--reserved-regs-list** is the same as adding it to **register-reservation**
config with **SRW**
Example for command line:
::
./llvm-snippy ./layout.yaml -seed=1 --reserved-regs-list=X1,X2,X3
The value of ``reserved-regs-list`` should be a comma-separated list of
register names. In the command line, spaces within the list are disallowed.
Alternatively it can be specified in YAML config under the ``options`` key (RISC-V, YAML flow format):
.. code:: yaml
options:
reserved-regs-list: [X1, X2, F10_D]
The same example in YAML block format:
.. code:: yaml
options:
reserved-regs-list:
- X1
- X2
- F10_D
This option also accepts regular expressions. For example:
.. code:: yaml
options:
reserved-regs-list: [X10, "X2[0-9]"]
This code will reserve X10 as well as X20-X29 registers.
Register names are described in the `Register Naming`_ section.
Initialization of 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.
.. _RISC-V vector register initialization:
RISC-V Vector Register Initialization Modes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Use the ``--rvv-init-mode`` option to control how snippy initializes
vector registers. The following modes are available:
- ``splats`` |nbsp| -- |nbsp| 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`` |nbsp| -- |nbsp| To use loads from the read-only section using the
``VL1RE8.V`` instruction.
- ``slides`` |nbsp| -- |nbsp| 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) |nbsp| -- |nbsp| 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. It is planned to remove this
limitation in future releases.
.. _`_dumping_initial_registers_state`:
Dumping Initial Registers State
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. caution::
This functionality is not supported without model plugin
To get a dump with an initial registers state, run:
::
./llvm-snippy -mtriple=riscv64-unknown-elf -mcpu= \
.yaml -model-plugin=.so -num-instrs= \
-seed=0 --dump-initial-registers-yaml[=] \
-o layout.elf
where:
- ``.yaml`` |nbsp| -- |nbsp| Name of your configuration file.
- ``--dump-initial-registers-yaml[=]`` |nbsp| -- |nbsp| 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`:
Loading Initial Registers State
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Snippy supports loading of initial register state from a file to a model.
This feature allows you to initialize registers with specific values rather
than random ones. The file format matches the one snippy uses to save registers
by means of the ``dump-initial-registers-yaml`` option, which makes it easy to
load initial values saved from a previous snippy run.
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=`` option:
::
./llvm-snippy -mtriple=riscv64-unknown-elf -mcpu= \
.yaml -model-plugin= -num-instrs= \
-seed=0 --initial-regs-yaml=initial_registers_state.yml \
-o layout.elf
where ``.yaml`` is the name of your configuration file.
.. _`_value_histogram_valuegram`:
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:
.. container:: formalpara-title
**valuegram.yaml (example):**
.. code:: yaml
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`` |nbsp| -- |nbsp| Specific bit value. You can provide a value in two
formats:
- In the ordinary format of ``(-)(radix)num``
- ``bitpattern`` |nbsp| -- |nbsp| Regular randomly generated bit pattern
(evenly-spaced 1-s)
- ``uniform`` |nbsp| -- |nbsp| Random uniformly distributed value
- ``bitrange`` |nbsp| -- |nbsp| ``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`:
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:
.. code:: yaml
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 <#value-histogram-valuegram>`__. For example:
.. code:: yaml
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"
.. _`_operands_reinitialization`:
Operands reinitialization
-------------------------
You can per-opcode reinitialize all initializeable operand registers using
``operands-reinitialization`` in layout.
.. important::
Specifying ``operands-reinitialization`` with specified ``--valuegram-operands-regs``
option is prohibited.
.. container:: formalpara-title
**operands-reinitialization.yaml (example):**
.. code:: yaml
operands-reinitialization:
- type: valuegram
weight: 2.0
opcodes:
- "FADD_S":
- type: uniform
- type: operands
weight: 2.0
values: [0x1.0p-1f, -inff] # Hex FP literals
- type: operands
values: [0x7f800001, 0x456] # SNaN and subnormal
- type: operands
values: [0x123, 0x456] # Encoded single-precision IEEE754 FP
- "FADD_D":
- type: operands
values: # Each operand value could be specified via valuegram
- - [1.0d, 1.0] # Decimal FP literals
- [-nan, 1.0] # Negative qNaN
- -inf
As you can see in the given example, operands-reinitialization is a list of
weighted data sources. The weight field is optional (1.0 by default) and
it determines the probability with which this data source will be selected
before each primary instruction initialization.
Currently, only ``valuegram`` data source type is supported. It should include:
- ``opcodes`` |nbsp| -- |nbsp| the list of the weighted valuegram opcode settings,
which consist of an opcode regex and a list of weighted value sources.
Value sources ``type`` includes:
- ``uniform`` |nbsp| -- |nbsp| all considering operands will be reinitialized with
uniformly generated values.
- ``operands`` |nbsp| -- |nbsp| specific values for each operand.
- ``values`` |nbsp| -- |nbsp| the ordered list of values. Floating point syntax
and uniform are supported. The number of values must be equal to the number of
initializeable operands of all instructions that this regex matches. Also you
can specify each value via valuegram, the same way as in the **histograms** and
**imm-hist**.
.. note::
If any opcodes specified in the histogram do not match any regex in the valuegram,
they will not be reinitialized in this valuegram context.
If some opcode matches multiple regex patterns in a single valuegram, the first
matching entry will always be used for its reinitialization.
.. important::
``operands-reinitialization`` is not supported with RVV.
.. _`_final_registers_state`:
Final Registers State
---------------------
.. important::
The state is obtained from the model, so this functionality is not
supported without model plugin.
.. caution::
It is not recommended to use the results of comparison of the final
registers state obtained from the model with the state obtained from the
device-under-the-test as a pass/fail criterion for the test. Such an
approach may lead to false failures unless the ``initial-regs-yaml`` option
is used to load registers (before snippet execution) in the model with
values identical to those in the device-under-the-test.
To get a dump with final registers state after the execution, run:
::
./llvm-snippy -mtriple=riscv64-unknown-elf -mcpu= \
.yaml -model-plugin=.so \
-num-instrs= -seed=0 \
--dump-registers-yaml[=] \
-o layout.elf
where:
- ``.yaml`` |nbsp| -- |nbsp| Name of your configuration file.
- ``--dump-registers-yaml[=]`` |nbsp| -- |nbsp| 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 `` option.
- To a standard error (instead of the standard output), use ``-``
instead of ````, 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`:
Stack Section
-------------
Add a stack section in the following cases:
- When using functions and calls to them.
- When setting up `environment
initialization <#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 <#generating-abi-with-respect-to-target>`__ 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`:
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:
::
error: Cannot configure external stack: stack pointer register is explicitly reserved.
.. _`_stack_pointers`:
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`` |nbsp| -- |nbsp| Any random suitable register. Suitable registers
are not reserved and not spilled; registers ``X0``, ``X1``, ``X6``
are excluded.
- ``reg::R`` |nbsp| -- |nbsp| Any register, for example: ``reg::X7``, ``reg::X12``,
etc.
- ``SP`` |nbsp| -- |nbsp| Stack pointer register specified by the ABI.
- ``any-not-SP`` |nbsp| -- |nbsp| Any random suitable register except for SP.
**Default** is:
- ``SP`` |nbsp| -- |nbsp| When the option ``--honor-target-abi`` is set to ``true``.
- ``any-not-SP`` |nbsp| -- |nbsp| 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`` |nbsp| -- |nbsp| 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`:
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
- ``--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`:
Section names prefix
--------------------
You can override section names prefix in the output file using ``--sections-prefix=``.
By default, snippy uses ``.snippy`` as a section names prefix. For example:
::
.snippy.text.rx
.snippy.data.rw
.snippy.stack.rw
etc.
.. _the type of output .elf file:
The type of output .elf file
----------------------------
The type of output .elf file is controlled by the ``--object-type`` option:
.. code:: shell
--object-type=
Where ```` should be one of:
* ``reloc`` - Relocatable object file plus `generated linker script`_. This is the default.
* ``exec`` - Executable .elf. To produce it, snippy uses ld.lld from the snippy pack, and feeds it with commands identical to those written to the `generated linker script`_.
* ``shared`` - Shared object: the same layout as ``exec`` plus dynamic section.
.. important::
If ``--model-plugin`` is not ``none``, and the user would like to obtain executable .elf that is identical to the one executed by the model, then the ``--object-type=exec`` option should be specified.
.. _`_target_specific_configurationrisc_v`:
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`:
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.
.. container:: formalpara-title
**Example:**
::
./llvm-snippy -mtriple=riscv64-unknown-elf -seed=0 -mattr=+v \
./yml/layout-vector.yaml
where:
- ``-mattr=+v`` |nbsp| -- |nbsp| An attribute that enables vector instructions.
- ``./yml/layout-vector.yaml`` |nbsp| -- |nbsp| A sample layout file with vector
instructions.
- ``--init-regs-in-elf`` |nbsp| -- |nbsp| A parameter that registers initialization
including in the output .elf file. For the details, refer to
`Initializing Registers in .elf <#initializing-registers-in-elf>`__.
- ``./yml/memory-aligned.yaml`` |nbsp| -- |nbsp| A sample layout file used as an
example of an aligned access scheme. For the details, refer to
`Memory Scheme <#memory-scheme>`__.
.. _`_vector_unit_configurations`:
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 |nbsp| -- |nbsp| 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 |nbsp| -- |nbsp| 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 <#biased-mode>`__.
As a result, you have a random vector unit mode change on each ``VSET*``
in the code.
.. _`_reachable_vector_configurations`:
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 <#rvv-configuration-dump>`__.
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`` |nbsp| -- |nbsp| 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`` |nbsp| -- |nbsp| 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`` |nbsp| -- |nbsp| 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`` |nbsp| -- |nbsp| 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`` |nbsp| -- |nbsp| 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`` |nbsp| -- |nbsp| Rounding mode. For the details, refer
`here `__.
- ``LMUL`` |nbsp| -- |nbsp| Multiplier that allows you to pack multiple vector
registers into a group. For the details, refer
`here `__.
- ``VMA``, ``VTA`` |nbsp| -- |nbsp| 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`:
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`` |nbsp| -- |nbsp| An attribute that enables vector instructions.
- ``./yml/layout-vector.yaml`` |nbsp| -- |nbsp| A sample layout file with
VSETVL-like instructions.
- ``./yml/riscv-vector-unit.yaml`` |nbsp| -- |nbsp| A sample layout file you can use
to specify the vector unit configuration.
.. container:: formalpara-title
**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`` |nbsp| -- |nbsp| A sample layout file used as an
example of memory access scheme. For the details, refer to `Memory
Strides <#memory-strides>`__.
.. _`_biased_mode`:
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`` |nbsp| -- |nbsp| A fixed probability of the RVV mode change regardless of the
number and type of instructions in the input histogram.
- ``Pvill`` |nbsp| -- |nbsp| 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:
.. code:: yaml
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.
.. container:: formalpara-title
**riscv-vector-ill.yaml:**
.. code:: 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`:
RVV Configuration Dump
~~~~~~~~~~~~~~~~~~~~~~
Use the ``-riscv-dump-rvv-config[=]`` 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:
.. code:: yaml
--- 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
P: 0.33333
- VM Selection Rules:
P: 0.66667
P: 0.33333
- 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 ---
Support for Floating-Point Control and Status Registers
=======================================================
Accessing and initialization of ``fflags`` and ``frm`` CSRs
-----------------------------------------------------------
Snippy supports accessing ``fflags`` and ``frm`` CSRs by means of the following
pseudoinstructions:
- ``ReadFFLAGS``, ``ReadFRM`` and ``ReadFCSR`` - Read CSR into a GPR.
- ``WriteFFLAGSImm``, ``WriteFRMImm`` and ``WriteFCSRImm`` - Write an immediate value into CSR.
- ``SwapFFLAGSImm``, ``SwapFRMImm`` and ``SwapFCSRImm`` - Save previous CSR value into a GPR and write immediate into it.
- ``WriteFFLAGS``, ``WriteFRM``, ``WriteFCSR`` - Write register to CSR. Best used in combination with `Operands Reinitialization <#operands-reinitialization>`__
These pseudoinstructions in conjuction with
`Immediate Histograms <#immediate-histograms>`__ provide flexible manipulation
of static and dynamic rounding modes, reading, clearing and writing exception
flags and so on. A few examples follow below.
When `register initialization code (-init-regs-in-elf) <#initializing-registers-in-elf>`__ is used,
snippy emits service code that randomizes ``frm`` and ``fflags``.
Example: Specifying rounding mode for each opcode. ``FADD_S`` uses randomized
rounding mode via DYN (thanks to the randomization of ``frm``), ``FSUB_S``
uses RNE:
.. code:: yaml
histogram:
- [FADD_S, 1.0]
- [FSUB_S, 1.0]
imm-hist:
opcodes:
- "FADD_S":
- [7, 1.0] # DYN
- "FSUB_S":
- [0, 1.0] # RNE
# COM: RNE - 0b000
# COM: RTZ - 0b001
# COM: RDN - 0b010
# COM: RUP - 0b011
# COM: RMM - 0b100
# COM: DYN - 0b111
Example: Controlled randomization of dynamic rounding mode in the ``frm`` CSR.
The emitted FP instructions that engage DYN mode randomly use 2 (RNE, RTZ)
out of 5 available rounding modes:
.. code:: yaml
histogram:
- [WriteFRMImm, 1.0]
- [SwapFRMImm, 1.0]
- [ReadFRM, 1.0]
- [WriteFRM, 1.0]
imm-hist:
opcodes:
- "WriteFRMImm|SwapFRMImm":
- [0, 1.0] # RNE
- [1, 1.0] # RTZ
operands-reinitialization:
- type: valuegram
opcodes:
- "WriteFRM":
- type: operands
values: [0] # RNE
- type: operands
values: [1] # RTZ
Example: Read ``fflags`` and write immediate values into it:
.. code:: yaml
histogram:
- [ReadFFLAGS, 1.0]
- [WriteFFLAGSImm, 1.0]
- [SwapFFLAGSImm, 1.0]
imm-hist:
opcodes:
- "WriteFFLAGSImm|SwapFFLAGSImm":
- [0, 1.0] # Clear fflags. Other values can be used, but this is the simplest use case.
.. _`_self_check`:
Self-check
==========
.. important::
This functionality is 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:
where ```` 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:
.. code:: yaml
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
.. _selfcheck-related global constants:
Self-check Properties to Global Variables
-----------------------------------------
.. important::
This functionality is 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:
Global Constants
================
In some scenarios, snippy produces global constants.
For example, this is nesassary for
`RISC-V vector register initialization`_,
`selfcheck-related global constants`_ and so on.
Read-only Sections
------------------
When snippet uses global constants,
a read-only section is required to hold constant data.
A description of read-only section should be provided in
the ``sections`` entry of the snippy configuration file.
Please find an example in the next subsection
(a copy of ``./yml/layout-vector-init.yaml``;
you can use this file as a template for your own configuration).
.. _generated linker script:
Generated Linker Script
=======================
A linker script is generated by default when you run snippy and
`the type of output .elf file`_ is relocatable.
The linker script is intended to properly place
sections into the resulting executable image.
If you would like to use the snippet as a part of some application
(which is often so), then you have to pass information from
the linking script to the link stage of the application.
Use whatever means convenient for you to do this:
directly insert the contents of generated script
into the application’s linking script,
or indirectly insert it there
(e.g. by means of the linker’s ``INCLUDE()`` command), and so on.
Let’s consider an example with a read-only section below:
.. container:: formalpara-title
**Command line:**
.. code:: shell
./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
Notice the description of read-only section
(the one with ``ACCESS: r``) in layout-vector-init.yaml:
.. container:: formalpara-title
**layout-vector-init.yaml:**
.. code:: 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]
.. container:: formalpara-title
**Generated linker script, layout.elf.ld:**
.. code:: c
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
}
.. tip::
Additional options for GCC-based toolchains:
- ``-T script-name`` |nbsp| -- |nbsp| Add this option to the GCC invocation to pass
a custom linker script.
- ``--script=script-name`` |nbsp| -- |nbsp| Use this option to pass a custom linker
script directly to ld.
- ``-Wl,--verbose`` |nbsp| -- |nbsp| Run the GCC link step with this option to get
the environment default linker script.
.. _generated_linker_options_file:
Generated Linker Options File
=============================
Additionally, snippy generates a text file containing the linker options users need to pass to their linker. This enables the creation of a binary file identical to the one executed on the internal model.
.. important::
**Why it matters?**
Consider an example where you generate a snippet containing several
functions that call each other for the rv32imc architecture. If the
linker flags you use do not match those Snippy passes to its internal
linker before model execution, your linker might compress some jal
calls into c.jal instructions. Since this compression did not occur
during the internal linking process, a trace mismatch will occur.
During snippet generation, alongside the **.ld** linker
script file snippy will generate **.ldargs** file containing
all necessary linker options (Empty if no specific options are required).
This way, when you have a config:
.. code:: yaml
# layout-generated-linker-options.yaml
options:
mtriple: riscv32
march: rv32imc
num-instrs: 2000
init-regs-in-elf: true
num-instr-ancil: 5
disable-linker-relaxations: true
sections:
- name: 1
VMA: 0x1000
LMA: 0x1000
SIZE: 0x1000
ACCESS: rx
- name: 2
VMA: 0x2000
LMA: 0x2000
SIZE: 0x1000
ACCESS: rw
histogram:
- [ADDI, 1.0]
- [JAL, 1.0]
call-graph:
entry-point: SnippyFunction
function-list:
- name: SnippyFunction
callees:
- foo
- name: foo
And run snippy as follows:
.. code:: bash
llvm-snippy layout-generated-linker-options.yaml -model-plugin= -o path/to/snippet
Then alongside **path/to/snippet.ld** linker script you will see **path/to/snippet.ldargs** file. Then, to match the snippet executed on internal model you just need to pass this **.ldargs** file to your linker via the **@** argument:
.. code:: bash
ld.lld path/to/snippet.elf -T path/to/snippet.ld @path/to/snippet.ldargs -o a.out
.. note::
For more documentation on linker options file see ld manual: https://linux.die.net/man/1/ld
Additionally, you can pass liner options file to your compiler driver (If
you do not call linker directly):
.. code:: bash
riscv64-unknown-elf-gcc startup.S path/to/snippet.elf -T path/to/snippet.ld -Wl,@path/to/snippet.ldargs -o a.out
Converting Traces
=================
Use the ``--trace-SNTF=`` option to convert traces from the model to the
**SNippy Trace Format (SNTF)**. The model you convert from must support callbacks.
If you set model to ``None``, you will get an error.
SNTF file format
----------------
SNTF file begins with a **header**, in which after **#** (comments), starting from a new line, are followed:
1. Format name (SNTF)
2. Version
3. List of enabled features of the current version ``+`` (ex: ``+time``)
4. List of disabled features of the current version ``-`` (ex: ``-next-pc``)
In total, paragraphs 3 and 4 should list all the features of the version from paragraph 2.
All names of **1.0** version features:
* ``time``
* ``pc``
* ``instr-code``
* ``next-pc``
* ``registers-changed``
* ``memory-accesses``
**Header example:**
.. code:: yaml
# SNTF
# 1.0
# +time
# +pc
# +instr-code
# +next-pc
# +registers-changed
# +memory-accesses
After the header, on each line placed entries that were selected in the
header, separated by a whitespace.
**Entry format:**
+-----------+------------+---------------+------------+---------------+-------------------------------+
|**Time in**|**Program** |**Instruction**|**Next** |**Register** |**Memory** |
|**nanosec**|**counter** |**code** |**program** |**values if** |**addrs if** |
| | | |**counter** |**changed** |**changed or read** |
+-----------+------------+---------------+------------+---------------+-------------------------------+
|520 |000086d0 |11010093 |000086d4 |f4=017f7ff0 |2ff4fe98:W:fa,2ff4fe99:R:ee |
+-----------+------------+---------------+------------+---------------+-------------------------------+
* ``time`` is synchronized with when the instruction was fetched from memory. For now it it just a number of instruction.
* Vector registers are printed element by element, the length of which is 64 bits:
(``vlen`` = 128) ``v4=[1]:0000000000227c3d[0]:0000000100227c3d``
(``vlen`` = 256) ``v7=[3]:00000f0000000001[2]:0000000000000a00[1]:0000b00000000000[0]:00000c0000000000``
* The format of memory accesses: ``::``.
Access size is determined by the number of characters in the Value. ``AccessType`` can be either **W** (write) or **R** (read).
**Entry example:**
.. code:: yaml
520 000086d0 11010093 000086d4 f4=017f7ff0 2ff4fe98:W:fa,2ff4fe99:R:ee
So, for example, you can use the following layout:
.. container:: formalpara-title
**layout.yaml**
.. code:: yaml
options:
mtriple: riscv64
mcpu: generic-rv64
num-instrs: 1000
model-plugin:
branches:
...
sections:
...
histogram:
...
Then, to convert the traces, run:
::
llvm-snippy layout.yaml -trace-SNTF
where ```` is the name of the output SNTF file, for
example, ``trace.sntf``.
This way, the result of this conversion is the ``trace.sntf`` file.
.. _`_trace_end_pc_option`:
``--trace-end-pc`` Option
-------------------------
Additionally, you can use the ``--trace-end-pc`` option to set a
specific value of PC, which will be set at the end of the whole trace.
The default value is ``0``.
The SNTF format contains the ``next-pc`` field.
If you set the ``--trace-end-pc`` value, when the trace reaches the end,
it uses this value. If not, the PC value after the last one on which the
instruction is executed will be set to ``0``.
.. _`_comparing_traces`:
.. _`_co_simulation`:
Co-simulation
=============
.. important::
This functionality is 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 |nbsp| -- |nbsp| 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``:
.. code:: yaml
plugin-model:
cosim-model-plugins: [, ]
- Via snippy command line:
::
-plugin-model= \
-cosim-model-plugins=,
.. _`_state_comparison`:
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.
.. container:: formalpara-title
**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`:
Trace Log
---------
.. _`_dump_filename`:
Dump Filename
~~~~~~~~~~~~~
When you use the ``--trace-log=`` with multiple models, a log
from all the co-simulations models is redirected to the
``.plugin`` file. The "primary" model log is directed to as
before. This way, each model gets its own log file.
.. container:: formalpara-title
**Run example:**
::
./llvm-snippy -mtriple=riscv64-unknown-elf -mcpu= \
./yml/layout.yaml -num-instrs=100 -seed=0 ./yml/memory.yaml \
-plugin-model=.so -cosim-model-plugins=.so \
--init-regs-in-elf -trace-log=log -o layout.elf
In this case you get two files: ``log`` and ``log.``.
.. _`_range_of_instruction_addresses`:
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``:
.. code:: yaml
dump-intervals-to-verify: ""
dump-intervals-to-verify: "-"
dump-intervals-to-verify: ".yaml"
- Via the command line:
::
-dump-intervals-to-verify
-dump-intervals-to-verify=-
-dump-intervals-to-verify=.yaml
where you use:
- ``""`` |nbsp| -- |nbsp| To dump the output to a default file
``${output_basename}.intervals-to-verify.yaml``.
- ``"-"`` |nbsp| -- |nbsp| To dump the output to stdout.
- ``".yaml"`` |nbsp| -- |nbsp| To dump the output to ``.yaml``.
Platform Support
================
RISC-V
------
snippy supports the following RISC-V configurations:
- **RV32I** - Base Integer Instruction Set (32-bit)
- **RV64I** - Base Integer Instruction Set (64-bit)
.. _RISC-V C extension:
.. If/when we decide to create a separate section about RISC-V C
extension, the hyperlink references to it may remain the same.
RISC-V Extensions
~~~~~~~~~~~~~~~~~
RISC-V architecture is supported with the following extensions. For the details, please refer to the RISC-V specifications.
.. important::
The list of supported extensions provided in this chapter could be incomplete and is subject to change.
**Standard Extensions**
- **I** - Base Integer Instruction Set
- **M** - Integer Multiplication and Division
- **A** - Atomic Instructions
- **F** - Single-Precision Floating-Point
- **D** - Double-Precision Floating-Point
- **C** - Compressed Instructions
- **V** - Vector Operations
**Zc* Code Size Reduction**
- **Zca** - Compressed non-floating-point
- **Zcb** - Simple code-size saving instructions
- **Zcd** - Compressed double precision loads and stores
- **Zcf** - Compressed single precision loads and stores
- **Zcmop** - Compressed May-Be-Operations
**Zi* Extensions**
- **Zicas** - Custom Atomic Operations
- **Zicbom** - Cache-Block Clean/Flush/Invalidate
- **Zicbop** - Cache-Block Prefetch
- **Zicboz** - Cache-Block Zero
- **Zicond** - Integer Conditional Operations
- **Zifencei** - Instruction-Fetch Fence
- **Zihintntl** - Non-Temporal Locality Hints
- **Zihintpause** - Pause hint
- **Zimop** - May-be operations
**Za* Extensions**
- **Zaamo** - Custom AMO (Atomic Memory Operations) Extension
- **Zabha** - Custom Byte-Hostile Atomic Extension
- **Zacas** - Atomic Compare-and-Swap (CAS) Instructions
**Zba* Extensions**
- **Zba** - Address Generation Instructions
- **Zbb** - Basic Bit-Manipulation
- **Zbc** - Carry-Less Multiplication
- **Zbs** - Single-Bit Operations
**Zbk*, Zk* Cryptography and More**
- **Zbkb** - Bit Manipulation for Cryptography
- **Zbkc** - Cryptography Carry Operations
- **Zbkx** - Crossbar Permutation Instructions
- **Zkn** - NIST Suite: AES, SHA2, SM4, SM3
- **Zknd** - NIST AES Decryption
- **Zkne** - NIST AES Encryption
- **Zknh** - NIST Hash Functions (SHA-256, SHA-512)
- **Zkr** - Entropy Source (TRNG)
- **Zks** - ShangMi Suite: SM4, SM3
- **Zksed** - ShangMi SM4 Block Cipher
- **Zksh** - ShangMi SM3 Hash Function
- **Zkt** - Data Independent Execution Latency
**Zv* Vector Extensions**
- **Zvbb** - Vector Basic Bit-manipulation
- **Zvbc** - Vector Carry-less Multiplication
- **Zvfbfmin** - Vector BF16 Minimum Floating-Point
- **Zvfbfwma** - Vector BF16 Widening Multiply-Accumulate
- **Zvfh** - Vector Minimal Half-Precision Floating-Point
- **Zvfhmin** - Vector Minimal Half-Precision Floating-Point
**Zf*, Zd*, Zh* Floating-Point**
- **Zdinx** - Double-Precision Floating-Point in Integer Registers
- **Zfa** - Additional Floating-Point Instructions
- **Zfbfmin** - BF16 Minimum Floating-Point
- **Zfh** - Half-Precision Floating-Point
- **Zfhmin** - Minimal Half-Precision Floating-Point
- **Zfinx** - Single-Precision Floating-Point in Integer Registers
- **Zhinx** - Half-Precision Floating-Point in Integer Registers
- **Zhinxmin** - Minimal Half-Precision Floating-Point in Integer Registers
**Priviledged Extensions**
- **Svinval** - Fine-Grained Address-Translation Cache Invalidation
Specifics of Supported Extensions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Compressed instruction limitations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- The following instructions from **C** extension are not supported with
`-honor-target-abi <#generating-abi-with-respect-to-target>`__ option:
``C_ADDI16SP``, ``C_ADDI4SPN``, ``C_LDSP``, ``C_LWSP``, ``C_SDSP``, and ``C_SWSP``.
Svinval recomendations
^^^^^^^^^^^^^^^^^^^^^^
The most basic **Svinval** tests can be generated by the following config:
.. code:: yaml
# llvm-snippy svinval-supervisor.yaml -o svinval.elf
options:
mtriple: riscv64
march: rv64i_svinval
num-instrs: 1000
model-plugin: None
sections:
- no: 1
VMA: 0x10000
LMA: 0x10000
SIZE: 0x10000
ACCESS: rx
- no: 2
VMA: 0x20000
LMA: 0x20000
SIZE: 0x10000
ACCESS: rw
histogram:
# supervisor instructions from svinval
- [SFENCE_INVAL_IR, 1.0]
- [SFENCE_W_INVAL, 1.0]
- [SINVAL_VMA, 1.0]
# some other memory operations
- [SD, 1.0]
- [LD, 1.0]
.. note::
Replace supervisor instructions with **HINVAL_FVMA** and **HINVAL_VVMA** to
generate test for hypervisor svinval instructions
However, semantics of the instructions from **Svinval** extension depend on the source
registers used (**X0** or other). And the above config chooses source registers
randomly. Meaning that probability of generating **X0** as at least one operand is
``2/32``. To increase the probability of generating **X0** as a source register you
can explicitly reserve other registers. We will also add
`Operands Reinitialization Config <#operands-reinitialization>`__ to specify
values that are written to source registers:
.. _svinval-hypervisor:
.. code:: yaml
# llvm-snippy svinval-hypervisor.yaml -o svinval.elf
options:
mtriple: riscv64
march: rv64ifd_svinval
num-instrs: 1000
model-plugin: None
# reserve X10-X31
reserved-regs-list: ["X[123][0-9]"]
entry-point: svinval_random
honor-target-abi: true
external-stack: true
sections:
- no: 1
VMA: 0x10000
LMA: 0x10000
SIZE: 0x10000
ACCESS: rx
- no: 2
VMA: 0x20000
LMA: 0x20000
SIZE: 0x10000
ACCESS: rw
histogram:
# hypervisor instructions from svinval
- [HINVAL_VVMA, 1.0]
- [HINVAL_GVMA, 1.0]
# some other memory operations
- [SD, 1.0]
- [LD, 1.0]
operands-reinitialization:
- type: valuegram
weight: 1.0
opcodes:
- "HINVAL_GVMA":
# address spaces
- type: operands
values: [0, 0]
- type: operands
values: [0, 1]
- type: operands
values: [1, 0]
# hand-written virtual-address
- type: operands
values: [1, 0x20000]
- "HINVAL_VVMA":
- type: operands
values: [0, 0]
- type: operands
values: [0, 1]
- type: operands
values: [1, 0]
- type: operands
values: [1, 1]
With this config probability of choosing **X0** as a source register is ``1/5``.
However limiting the number of available registers might affect generation of
all instructions from histogram. Let us take a look at a possible solution to
this problem that involves generating 2 snippets with custom callgraph.
The first snippet will reuse the :ref:`config above ` that
produces a ``svinval_random`` function.
The second snippet we will generate using the following config:
.. code:: yaml
# llvm-snippy svinval-caller.yaml -o svinval-caller.elf
options:
mtriple: riscv64
march: rv64ifd_svinval
num-instrs: 300
model-plugin: None
entry-point: top_level
honor-target-abi: true
external-stack: true
# same sections
sections:
- no: 1
VMA: 0x10000
LMA: 0x10000
SIZE: 0x10000
ACCESS: rx
- no: 2
VMA: 0x20000
LMA: 0x20000
SIZE: 0x10000
ACCESS: rw
histogram:
- [FLD, 1.0]
- [FLW, 1.0]
- [FSD, 1.0]
- [FSW, 1.0]
- [SD, 1.0]
- [SW, 1.0]
- [FNMADD_D, 1.0]
- [FNMSUB_S, 1.0]
- [JAL, 1.0]
call-graph:
entry-point: top_level
function-list:
- name: top_level
callees:
- svinval_random
- name: svinval_random
external: true
This config generates random code that uses all available registers and
periodically calls ``svinval_random`` that was generated previously. Now we
just need to link ``svinval-caller.elf`` and ``svinval.elf`` together and we
will get a random snippet with normal generation of instructions from the second
config and constrained generation of svinval instructions that increases probability
of using **X0** as source register.
.. rst-class:: break_before
.. include:: llvm-ie.rst