# Simple-V (Parallelism Extension Proposal) Specification (Abridged)
* Copyright (C) 2017, 2018, 2019 Luke Kenneth Casson Leighton
* Status: DRAFTv0.6
* Last edited: 25 jun 2019
* See: main [[specification]] and [[appendix]]
[[!toc ]]
# Introduction
Simple-V is a uniform parallelism API for RISC-V hardware that allows
the Program Counter to enter "sub-contexts" in which, ultimately, standard
RISC-V scalar opcodes are executed.
The sub-context execution is "nested" in "re-entrant" form, in the
following order:
* Main standard RISC-V Program Counter (PC)
* VBLOCK sub-execution context (PCVBLK increments whilst PC is paused).
* VL element loops (STATE srcoffs and destoffs increment, PC and PCVBLK pause).
Predication bits may be individually applied per element.
* SUBVL element loops (STATE svsrcoffs/svdestoffs increment, VL pauses).
Individual predicate bits from VL loops apply to the *group* of SUBVL
elements.
An ancillary "SVPrefix" Format (P48/P64) [[sv_prefix_proposal]] may
run its own VL/SUBVL "loops" and specifies its own Register and Predication
format on the 32-bit RV scalar opcode embedded within it.
The [[vblock_format]] specifies how VBLOCK sub-execution contexts
operate.
SV is never actually switched "off". VL or SUBVL may be equal to 1, and
Register or Predicate over-ride tables may be empty: under such circumstances
the behaviour becomes effectively identical to standard RV execution, however
SV is never truly actually "off".
Note: **there are *no* new opcodes**. The scheme works *entirely*
on hidden context that augments (nests) *scalar* RISC-V instructions.
Thus it may cover existing, future and custom scalar extensions, turning
all existing, all future and all custom scalar operations parallel,
without requiring any special (identical, parallel variant) opcodes to do so.
# CSRs
There are five CSRs, available in any privilege level:
* MVL (the Maximum Vector Length)
* VL (which has different characteristics from standard CSRs)
* SUBVL (effectively a kind of SIMD)
* STATE (containing copies of MVL, VL and SUBVL as well as context information)
* PCVBLK (the current operation being executed within a VBLOCK Group)
For Privilege Levels (trap handling) there are the following CSRs,
where x may be u, m, s or h for User, Machine, Supervisor or Hypervisor
Modes respectively:
* (x)ePCVBLK (a copy of the sub-execution Program Counter, that is relative
to the start of the current VBLOCK Group, set on a trap).
* (x) eSTATE (useful for saving and restoring during context switch,
and for providing fast transitions)
The u/m/s CSRs are treated and handled exactly like their (x)epc
equivalents. On entry to or exit from a privilege level, the contents
of its (x)eSTATE are swapped with STATE.
(x)EPCVBLK CSRs must be treated exactly like their corresponding (x)epc
equivalents. See VBLOCK section for details.
## MAXVECTORLENGTH (MVL)
MAXVECTORLENGTH is the same concept as MVL in RVV, except that it
is variable length and may be dynamically set. MVL is
however limited to the regfile bitwidth XLEN (1-32 for RV32,
1-64 for RV64 and so on).
## Vector Length (VL)
VSETVL is slightly different from RVV. Similar to RVV, VL is set to be within
the range 1 <= VL <= MVL (where MVL in turn is limited to 1 <= MVL <= XLEN)
VL = rd = MIN(vlen, MVL)
where 1 <= MVL <= XLEN
## SUBVL - Sub Vector Length
This is a "group by quantity" that effectivrly asks each iteration
of the hardware loop to load SUBVL elements of width elwidth at a
time. Effectively, SUBVL is like a SIMD multiplier: instead of just 1
operation issued, SUBVL operations are issued.
The main effect of SUBVL is that predication bits are applied per
**group**, rather than by individual element. Legal values are 1 to 4.
## STATE
This is a standard CSR that contains sufficient information for a
full context save/restore. It contains (and permits setting of):
* MVL
* VL
* destoffs - the destination element offset of the current parallel
instruction being executed
* srcoffs - for twin-predication, the source element offset as well.
* SUBVL
* svdestoffs - the subvector destination element offset of the current
parallel instruction being executed
* svsrcoffs - for twin-predication, the subvector source element offset
as well.
The format of the STATE CSR is as follows:
| (29..28 | (27..26) | (25..24) | (23..18) | (17..12) | (11..6) | (5...0) |
| ------- | -------- | -------- | -------- | -------- | ------- | ------- |
| dsvoffs | ssvoffs | subvl | destoffs | srcoffs | vl | maxvl |
The relationship between SUBVL and the subvl field is:
| SUBVL | (25..24) |
| ----- | -------- |
| 1 | 0b00 |
| 2 | 0b01 |
| 3 | 0b10 |
| 4 | 0b11 |
Notes:
* The entries are truncated to be within range. Attempts to set VL to
greater than MAXVL will truncate VL.
* Both VL and MAXVL are stored offset by one. 0b000000 represents VL=1,
0b000001 represents VL=2. This allows the full range 1 to XLEN instead
of 0 to only 63.
## VL, MVL and SUBVL instruction aliases
This table contains pseudo-assembly instruction aliases. Note the
subtraction of 1 from the CSRRWI pseudo variants, to compensate for the
reduced range of the 5 bit immediate.
| alias | CSR |
| - | - |
| SETVL rd, rs | CSRRW VL, rd, rs |
| SETVLi rd, #n | CSRRWI VL, rd, #n-1 |
| GETVL rd | CSRRW VL, rd, x0 |
| SETMVL rd, rs | CSRRW MVL, rd, rs |
| SETMVLi rd, #n | CSRRWI MVL,rd, #n-1 |
| GETMVL rd | CSRRW MVL, rd, x0 |
Note: CSRRC and other bitsetting may still be used, they are however not particularly useful (very obscure).
## Register key-value (CAM) table
The purpose of the Register table is to mark which registers change behaviour
if used in a "Standard" (normally scalar) opcode.
16 bit format:
| RegCAM | 15 | (14..8) | 7 | (6..5) | (4..0) |
| ------ | - | - | - | ------ | ------- |
| 0 | isvec0 | regidx0 | i/f | vew0 | regkey |
| 1 | isvec1 | regidx1 | i/f | vew1 | regkey |
| 2 | isvec2 | regidx2 | i/f | vew2 | regkey |
| 3 | isvec3 | regidx3 | i/f | vew3 | regkey |
8 bit format:
| RegCAM | | 7 | (6..5) | (4..0) |
| ------ | | - | ------ | ------- |
| 0 | | i/f | vew0 | regnum |
Mapping the 8-bit to 16-bit format:
| RegCAM | 15 | (14..8) | 7 | (6..5) | (4..0) |
| ------ | - | - | - | ------ | ------- |
| 0 | isvec=1 | regnum0<<2 | i/f | vew0 | regnum0 |
| 1 | isvec=1 | regnum1<<2 | i/f | vew1 | regnum1 |
| 2 | isvec=1 | regnum2<<2 | i/f | vew2 | regnum2 |
| 3 | isvec=1 | regnum2<<2 | i/f | vew3 | regnum3 |
Fields:
* i/f is set to "1" to indicate that the redirection/tag entry is to
be applied to integer registers; 0 indicates that it is relevant to
floating-point registers.
* isvec indicates that the register (whether a src or dest) is to progress
incrementally forward on each loop iteration. this gives the "effect"
of vectorisation. isvec is zero indicates "do not progress", giving
the "effect" of that register being scalar.
* vew overrides the operation's default width. See table below
* regkey is the register which, if encountered in an op (as src or dest)
is to be "redirected"
* in the 16-bit format, regidx is the *actual* register to be used
for the operation (note that it is 7 bits wide)
| vew | bitwidth |
| --- | ------------------- |
| 00 | default (XLEN/FLEN) |
| 01 | 8 bit |
| 10 | 16 bit |
| 11 | 32 bit |
As the above table is a CAM (key-value store) it may be appropriate
(faster, less gates, implementation-wise) to expand it as follows:
struct vectorised {
bool isvector:1;
int vew:2;
bool enabled:1;
int predidx:7;
}
struct vectorised fp_vec[32], int_vec[32];
for (i = 0; i < len; i++) // from VBLOCK Format
tb = int_vec if CSRvec[i].type == 0 else fp_vec
idx = CSRvec[i].regkey // INT/FP src/dst reg in opcode
tb[idx].elwidth = CSRvec[i].elwidth
tb[idx].regidx = CSRvec[i].regidx // indirection
tb[idx].isvector = CSRvec[i].isvector // 0=scalar
tb[idx].enabled = true;
## Predication Table
The Predication Table is a key-value store indicating whether, if a
given destination register (integer or floating-point) is referred to
in an instruction, it is to be predicated. Like the Register table, it
is an indirect lookup that allows the RV opcodes to not need modification.
* regidx is the register that in combination with the
i/f flag, if that integer or floating-point register is referred to in a
(standard RV) instruction results in the lookup table being referenced
to find the predication mask to use for this operation.
* predidx is the *actual* (full, 7 bit) register to be used for the
predication mask.
* inv indicates that the predication mask bits are to be inverted
prior to use *without* actually modifying the contents of the
register from which those bits originated.
* zeroing is either 1 or 0, and if set to 1, the operation must
place zeros in any element position where the predication mask is
set to zero. If zeroing is set to 0, unpredicated elements *must*
be left alone (unaltered), even when elwidth != default.
* ffirst is a special mode that stops sequential element processing when
a data-dependent condition occurs, whether a trap or a conditional test.
The handling of each (trap or conditional test) is slightly different:
see Instruction sections for further details
16 bit format:
| PrCSR | (15..11) | 10 | 9 | 8 | (7..1) | 0 |
| ----- | - | - | - | - | ------- | ------- |
| 0 | predidx | zero0 | inv0 | i/f | regidx | ffirst0 |
| 1 | predidx | zero1 | inv1 | i/f | regidx | ffirst1 |
| 2 | predidx | zero2 | inv2 | i/f | regidx | ffirst2 |
| 3 | predidx | zero3 | inv3 | i/f | regidx | ffirst3 |
Note: predidx=x0, zero=1, inv=1 is a RESERVED encoding. Its use must
generate an illegal instruction trap.
8 bit format:
| PrCSR | 7 | 6 | 5 | (4..0) |
| ----- | - | - | - | ------- |
| 0 | zero0 | inv0 | i/f | regnum |
Mapping from 8 to 16 bit format, the table becomes:
| PrCSR | (15..11) | 10 | 9 | 8 | (7..1) | 0 |
| ----- | - | - | - | - | ------- | ------- |
| 0 | x9 | zero0 | inv0 | i/f | regnum | ff=0 |
| 1 | x10 | zero1 | inv1 | i/f | regnum | ff=0 |
| 2 | x11 | zero2 | inv2 | i/f | regnum | ff=0 |
| 3 | x12 | zero3 | inv3 | i/f | regnum | ff=0 |
Pseudocode for predication:
struct pred {
bool zero; // zeroing
bool inv; // register at predidx is inverted
bool ffirst; // fail-on-first
bool enabled; // use this to tell if the table-entry is active
int predidx; // redirection: actual int register to use
}
struct pred fp_pred_reg[32];
struct pred int_pred_reg[32];
for (i = 0; i < len; i++) // number of Predication entries in VBLOCK
tb = int_pred_reg if PredicateTable[i].type == 0 else fp_pred_reg;
idx = VBLOCKPredicateTable[i].regidx
tb[idx].zero = CSRpred[i].zero
tb[idx].inv = CSRpred[i].inv
tb[idx].ffirst = CSRpred[i].ffirst
tb[idx].predidx = CSRpred[i].predidx
tb[idx].enabled = true
def get_pred_val(bool is_fp_op, int reg):
tb = int_reg if is_fp_op else fp_reg
if (!tb[reg].enabled):
return ~0x0, False // all enabled; no zeroing
tb = int_pred if is_fp_op else fp_pred
if (!tb[reg].enabled):
return ~0x0, False // all enabled; no zeroing
predidx = tb[reg].predidx // redirection occurs HERE
predicate = intreg[predidx] // actual predicate HERE
if (tb[reg].inv):
predicate = ~predicate // invert ALL bits
return predicate, tb[reg].zero
## Fail-on-First Mode
ffirst is a special data-dependent predicate mode. There are two
variants: one is for faults: typically for LOAD/STORE operations,
which may encounter end of page faults during a series of operations.
The other variant is comparisons such as FEQ (or the augmented behaviour
of Branch), and any operation that returns a result of zero (whether
integer or floating-point). In the FP case, this includes negative-zero.
Note that the execution order must "appear" to be sequential for ffirst
mode to work correctly. An in-order architecture must execute the element
operations in sequence, whilst an out-of-order architecture must *commit*
the element operations in sequence (giving the appearance of in-order
execution).
Note also, that if ffirst mode is needed without predication, a special
"always-on" Predicate Table Entry may be constructed by setting
inverse-on and using x0 as the predicate register. This
will have the effect of creating a mask of all ones, allowing ffirst
to be set.
See [[appendix]] for more details on fail-on-first modes.
# Exceptions
TODO: expand.
# Hints
With Simple-V being capable of issuing *parallel* instructions where
rd=x0, the space for possible HINTs is expanded considerably. VL
could be used to indicate different hints. In addition, if predication
is set, the predication register itself could hypothetically be passed
in as a *parameter* to the HINT operation.
No specific hints are yet defined in Simple-V
# Vector Block Format
See ancillary resource: [[vblock_format]]
# Subsets of RV functionality
It is permitted to only implement SVprefix and not the VBLOCK instruction
format option, and vice-versa. UNIX Platforms **MUST** raise illegal
instruction on seeing an unsupported VBLOCK or SVprefix opcode, so that
traps may emulate the format.
It is permitted in SVprefix to either not implement VL or not implement
SUBVL (see [[sv_prefix_proposal]] for full details. Again, UNIX Platforms
*MUST* raise illegal instruction on implementations that do not support
VL or SUBVL.
It is permitted to limit the size of either (or both) the register files
down to the original size of the standard RV architecture. However, below
the mandatory limits set in the RV standard will result in non-compliance
with the SV Specification.