# Simple-V (Parallelism Extension Proposal) Specification * Copyright (C) 2017, 2018, 2019 Luke Kenneth Casson Leighton * Status: DRAFTv0.6 * Last edited: 21 jun 2019 * Ancillary resource: [[opcodes]] * Ancillary resource: [[sv_prefix_proposal]] * Ancillary resource: [[abridged_spec]] * Ancillary resource: [[vblock_format]] * Ancillary resource: [[appendix]] With thanks to: * Allen Baum * Bruce Hoult * comp.arch * Jacob Bachmeyer * Guy Lemurieux * Jacob Lifshay * Terje Mathisen * The RISC-V Founders, without whom this all would not be possible. [[!toc ]] # Summary and Background: Rationale Simple-V is a uniform parallelism API for RISC-V hardware that has several unplanned side-effects including code-size reduction, expansion of HINT space and more. The reason for creating it is to provide a manageable way to turn a pre-existing design into a parallel one, in a step-by-step incremental fashion, without adding any new opcodes, thus allowing the implementor to focus on adding hardware where it is needed and necessary. The primary target is for mobile-class 3D GPUs and VPUs, with secondary goals being to reduce executable size (by extending the effectiveness of RV opcodes, RVC in particular) and reduce context-switch latency. Critically: **No new instructions are added**. The parallelism (if any is implemented) is implicitly added by tagging *standard* scalar registers for redirection. When such a tagged register is used in any instruction, it indicates that the PC shall **not** be incremented; instead a loop is activated where *multiple* instructions are issued to the pipeline (as determined by a length CSR), with contiguously incrementing register numbers starting from the tagged register. When the last "element" has been reached, only then is the PC permitted to move on. Thus Simple-V effectively sits (slots) *in between* the instruction decode phase and the ALU(s). The barrier to entry with SV is therefore very low. The minimum compliant implementation is software-emulation (traps), requiring only the CSRs and CSR tables, and that an exception be thrown if an instruction's registers are detected to have been tagged. The looping that would otherwise be done in hardware is thus carried out in software, instead. Whilst much slower, it is "compliant" with the SV specification, and may be suited for implementation in RV32E and also in situations where the implementor wishes to focus on certain aspects of SV, without unnecessary time and resources into the silicon, whilst also conforming strictly with the API. A good area to punt to software would be the polymorphic element width capability for example. Hardware Parallelism, if any, is therefore added at the implementor's discretion to turn what would otherwise be a sequential loop into a parallel one. To emphasise that clearly: Simple-V (SV) is *not*: * A SIMD system * A SIMT system * A Vectorisation Microarchitecture * A microarchitecture of any specific kind * A mandary parallel processor microarchitecture of any kind * A supercomputer extension SV does **not** tell implementors how or even if they should implement parallelism: it is a hardware "API" (Application Programming Interface) that, if implemented, presents a uniform and consistent way to *express* parallelism, at the same time leaving the choice of if, how, how much, when and whether to parallelise operations **entirely to the implementor**. # Basic Operation The principle of SV is as follows: * Standard RV instructions are "prefixed" (extended) through a 48/64 bit format (single instruction option) or a variable length VLIW-like prefix (multi or "grouped" option). * The prefix(es) indicate which registers are "tagged" as "vectorised". Predicates can also be added, and element widths overridden on any src or dest register. * A "Vector Length" CSR is set, indicating the span of any future "parallel" operations. * If any operation (a **scalar** standard RV opcode) uses a register that has been so "marked" ("tagged"), a hardware "macro-unrolling loop" is activated, of length VL, that effectively issues **multiple** identical instructions using contiguous sequentially-incrementing register numbers, based on the "tags". * **Whether they be executed sequentially or in parallel or a mixture of both or punted to software-emulation in a trap handler is entirely up to the implementor**. In this way an entire scalar algorithm may be vectorised with the minimum of modification to the hardware and to compiler toolchains. To reiterate: **There are *no* new opcodes**. The scheme works *entirely* on hidden context that augments *scalar* RISCV instructions. # CSRs * An optional "reshaping" CSR key-value table which remaps from a 1D linear shape to 2D or 3D, including full transposition. There are five additional 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 User Mode there are the following CSRs: * uePCVBLK (a copy of the sub-execution Program Counter, that is relative to the start of the current VBLOCK Group, set on a trap). * ueSTATE (useful for saving and restoring during context switch, and for providing fast transitions) There are also two additional CSRs for Supervisor-Mode: * sePCVBLK * seSTATE And likewise for M-Mode: * mePCVBLK * meSTATE 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. Thus for example, a User Mode trap will end up swapping STATE and ueSTATE (on both entry and exit), allowing User Mode traps to have their own Vectorisation Context set up, separated from and unaffected by normal user applications. If an M Mode trap occurs in the middle of the U Mode trap, STATE is swapped with meSTATE, and restored on exit: the U Mode trap continues unaware that the M Mode trap even occurred. Likewise, Supervisor Mode may perform context-switches, safe in the knowledge that its Vectorisation State is unaffected by User Mode. The access pattern for these groups of CSRs in each mode follows the same pattern for other CSRs that have M-Mode and S-Mode "mirrors": * In M-Mode, the S-Mode and U-Mode CSRs are separate and distinct. * In S-Mode, accessing and changing of the M-Mode CSRs is transparently identical to changing the S-Mode CSRs. Accessing and changing the U-Mode CSRs is permitted. * In U-Mode, accessing and changing of the S-Mode and U-Mode CSRs is prohibited. An interesting side effect of SV STATE being separate and distinct in S Mode is that Vectorised saving of an entire register file to the stack is a single instruction (through accidental provision of LOAD-MULTI semantics). If the SVPrefix P64-LD-type format is used, LOAD-MULTI may even be done with a single standalone 64 bit opcode (P64 may set up SUBVL, VL and MVL from an immediate field, to cover the full regfile). It can even be predicated, which opens up some very interesting possibilities. (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). The reason for setting this limit is so that predication registers, when marked as such, may fit into a single register as opposed to fanning out over several registers. This keeps the hardware implementation a little simpler. The other important factor to note is that the actual MVL is internally stored **offset by one**, so that it can fit into only 6 bits (for RV64) and still cover a range up to XLEN bits. Attempts to set MVL to zero will return an exception. This is expressed more clearly in the "pseudocode" section, where there are subtle differences between CSRRW and CSRRWI. ## 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 However just like MVL it is important to note that the range for VL has subtle design implications, covered in the "CSR pseudocode" section The fixed (specific) setting of VL allows vector LOAD/STORE to be used to switch the entire bank of registers using a single instruction (see Appendix, "Context Switch Example"). The reason for limiting VL to XLEN is down to the fact that predication bits fit into a single register of length XLEN bits. The second and most important change is that, within the limits set by MVL, the value passed in **must** be set in VL (and in the destination register). This has implication for the microarchitecture, as VL is required to be set (limits from MVL notwithstanding) to the actual value requested. RVV has the option to set VL to an arbitrary value that suits the conditions and the micro-architecture: SV does *not* permit this. The reason is so that if SV is to be used for a context-switch or as a substitute for LOAD/STORE-Multiple, the operation can be done with only 2-3 instructions (setup of the CSRs, VSETVL x0, x0, #{regfilelen-1}, single LD/ST operation). If VL does *not* get set to the register file length when VSETVL is called, then a software-loop would be needed. To avoid this need, VL *must* be set to exactly what is requested (limits notwithstanding). Therefore, in turn, unlike RVV, implementors *must* provide pseudo-parallelism (using sequential loops in hardware) if actual hardware-parallelism in the ALUs is not deployed. A hybrid is also permitted (as used in Broadcom's VideoCore-IV) however this must be *entirely* transparent to the ISA. The third change is that VSETVL is implemented as a CSR, where the behaviour of CSRRW (and CSRRWI) must be changed to specifically store the *new* value in the destination register, **not** the old value. Where context-load/save is to be implemented in the usual fashion by using a single CSRRW instruction to obtain the old value, the *secondary* CSR must be used (STATE). This CSR by contrast behaves exactly as standard CSRs, and contains more than just VL. One interesting side-effect of using CSRRWI to set VL is that this may be done with a single instruction, useful particularly for a context-load/save. There are however limitations: CSRWI's immediate is limited to 0-31 (representing VL=1-32). Note that when VL is set to 1, vector operations cease (but not subvector operations: that requires setting SUBVL=1) the hardware loop is reduced to a single element: scalar operations. This is in effect the default, normal operating mode. However it is important to appreciate that this does **not** result in the Register table or SUBVL being disabled. Only when the Register table is empty (P48/64 prefix fields notwithstanding) would SV have no effect. ## SUBVL - Sub Vector Length This is a "group by quantity" that effectively 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. Another way to view SUBVL is that each element in the VL length vector is now SUBVL times elwidth bits in length and now comprises SUBVL discrete sub operations. An inner SUBVL for-loop within a VL for-loop in effect, with the sub-element increased every time in the innermost loop. This is best illustrated in the (simplified) pseudocode example, in the [[appendix]]. The primary use case for SUBVL is for 3D FP Vectors. A Vector of 3D coordinates X,Y,Z for example may be loaded and multiplied then stored, per VL element iteration, rather than having to set VL to three times larger. Setting this CSR to 0 must raise an exception. Setting it to a value greater than 4 likewise. To see the relationship with STATE, see below. The main effect of SUBVL is that predication bits are applied per **group**, rather than by individual element. This saves a not insignificant number of instructions when handling 3D vectors, as otherwise a much longer predicate mask would have to be set up with regularly-repeated bit patterns. See SUBVL Pseudocode illustration in the [[appendix]], for details. ## 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. Interestingly STATE may hypothetically also be modified to make the immediately-following instruction to skip a certain number of elements, by playing with destoffs and srcoffs (and the subvector offsets as well) Setting destoffs and srcoffs is realistically intended for saving state so that exceptions (page faults in particular) may be serviced and the hardware-loop that was being executed at the time of the trap, from user-mode (or Supervisor-mode), may be returned to and continued from exactly where it left off. The reason why this works is because setting User-Mode STATE will not change (not be used) in M-Mode or S-Mode (and is entirely why M-Mode and S-Mode have their own STATE CSRs, meSTATE and seSTATE). 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 | When setting this CSR, the following characteristics will be enforced: * **MAXVL** will be truncated (after offset) to be within the range 1 to XLEN * **VL** will be truncated (after offset) to be within the range 1 to MAXVL * **SUBVL** which sets a SIMD-like quantity, has only 4 values so there are no changes needed * **srcoffs** will be truncated to be within the range 0 to VL-1 * **destoffs** will be truncated to be within the range 0 to VL-1 * **ssvoffs** will be truncated to be within the range 0 to SUBVL-1 * **dsvoffs** will be truncated to be within the range 0 to SUBVL-1 NOTE: if the following instruction is not a twin predicated instruction, and destoffs or dsvoffs has been set to non-zero, subsequent execution behaviour is undefined. **USE WITH CARE**. ### Hardware rules for when to increment STATE offsets The offsets inside STATE are like the indices in a loop, except in hardware. They are also partially (conceptually) similar to a "sub-execution Program Counter". As such, and to allow proper context switching and to define correct exception behaviour, the following rules must be observed: * When the VL CSR is set, srcoffs and destoffs are reset to zero. * Each instruction that contains a "tagged" register shall start execution at the *current* value of srcoffs (and destoffs in the case of twin predication) * Unpredicated bits (in nonzeroing mode) shall cause the element operation to skip, incrementing the srcoffs (or destoffs) * On execution of an element operation, Exceptions shall **NOT** cause srcoffs or destoffs to increment. * On completion of the full Vector Loop (srcoffs = VL-1 or destoffs = VL-1 after the last element is executed), both srcoffs and destoffs shall be reset to zero. This latter is why srcoffs and destoffs may be stored as values from 0 to XLEN-1 in the STATE CSR, because as loop indices they refer to elements. srcoffs and destoffs never need to be set to VL: their maximum operating values are limited to 0 to VL-1. The same corresponding rules apply to SUBVL, svsrcoffs and svdestoffs. ## MVL and VL Pseudocode The pseudo-code for get and set of VL and MVL use the following internal functions as follows: set_mvl_csr(value, rd): regs[rd] = STATE.MVL STATE.MVL = MIN(value, STATE.MVL) get_mvl_csr(rd): regs[rd] = STATE.VL set_vl_csr(value, rd): STATE.VL = MIN(value, STATE.MVL) regs[rd] = STATE.VL # yes returning the new value NOT the old CSR return STATE.VL get_vl_csr(rd): regs[rd] = STATE.VL return STATE.VL Note that where setting MVL behaves as a normal CSR (returns the old value), unlike standard CSR behaviour, setting VL will return the **new** value of VL **not** the old one. For CSRRWI, the range of the immediate is restricted to 5 bits. In order to maximise the effectiveness, an immediate of 0 is used to set VL=1, an immediate of 1 is used to set VL=2 and so on: CSRRWI_Set_MVL(value): set_mvl_csr(value+1, x0) CSRRWI_Set_VL(value): set_vl_csr(value+1, x0) However for CSRRW the following pseudocode is used for MVL and VL, where setting the value to zero will cause an exception to be raised. The reason is that if VL or MVL are set to zero, the STATE CSR is not capable of storing that value. CSRRW_Set_MVL(rs1, rd): value = regs[rs1] if value == 0 or value > XLEN: raise Exception set_mvl_csr(value, rd) CSRRW_Set_VL(rs1, rd): value = regs[rs1] if value == 0 or value > XLEN: raise Exception set_vl_csr(value, rd) In this way, when CSRRW is utilised with a loop variable, the value that goes into VL (and into the destination register) may be used in an instruction-minimal fashion: CSRvect1 = {type: F, key: a3, val: a3, elwidth: dflt} CSRvect2 = {type: F, key: a7, val: a7, elwidth: dflt} CSRRWI MVL, 3 # sets MVL == **4** (not 3) j zerotest # in case loop counter a0 already 0 loop: CSRRW VL, t0, a0 # vl = t0 = min(mvl, a0) ld a3, a1 # load 4 registers a3-6 from x slli t1, t0, 3 # t1 = vl * 8 (in bytes) ld a7, a2 # load 4 registers a7-10 from y add a1, a1, t1 # increment pointer to x by vl*8 fmadd a7, a3, fa0, a7 # v1 += v0 * fa0 (y = a * x + y) sub a0, a0, t0 # n -= vl (t0) st a7, a2 # store 4 registers a7-10 to y add a2, a2, t1 # increment pointer to y by vl*8 zerotest: bnez a0, loop # repeat if n != 0 With the STATE CSR, just like with CSRRWI, in order to maximise the utilisation of the limited bitspace, "000000" in binary represents VL==1, "00001" represents VL==2 and so on (likewise for MVL): CSRRW_Set_SV_STATE(rs1, rd): value = regs[rs1] get_state_csr(rd) STATE.MVL = set_mvl_csr(value[11:6]+1) STATE.VL = set_vl_csr(value[5:0]+1) STATE.destoffs = value[23:18]>>18 STATE.srcoffs = value[23:18]>>12 get_state_csr(rd): regs[rd] = (STATE.MVL-1) | (STATE.VL-1)<<6 | (STATE.srcoffs)<<12 | (STATE.destoffs)<<18 return regs[rd] In both cases, whilst CSR read of VL and MVL return the exact values of VL and MVL respectively, reading and writing the STATE CSR returns those values **minus one**. This is absolutely critical to implement if the STATE CSR is to be used for fast context-switching. ## 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 *NOTE: in prior versions of SV, this table used to be writable and accessible via CSRs. It is now stored in the VBLOCK instruction format. Note that this table does *not* get applied to the SVPrefix P48/64 format, only to scalar opcodes* The purpose of the Register table is three-fold: * To mark integer and floating-point registers as requiring "redirection" if it is ever used as a source or destination in any given operation. This involves a level of indirection through a 5-to-7-bit lookup table, such that **unmodified** operands with 5 bits (3 for some RVC ops) may access up to **128** registers. * To indicate whether, after redirection through the lookup table, the register is a vector (or remains a scalar). * To over-ride the implicit or explicit bitwidth that the operation would normally give the register. Note: clearly, if an RVC operation uses a 3 bit spec'd register (x8-x15) and the Register table contains entried that only refer to registerd x1-x14 or x16-x31, such operations will *never* activate the VL hardware loop! If however the (16 bit) Register table does contain such an entry (x8-x15 or x2 in the case of LWSP), that src or dest reg may be redirected anywhere to the *full* 128 register range. Thus, RVC becomes far more powerful and has many more opportunities to reduce code size that in Standard RV32/RV64 executables. 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 | | .. | | isvec.. | regidx.. | i/f | vew.. | regkey | | 15 | | isvec15 | regidx15 | i/f | vew15 | regkey | 8 bit format: | RegCAM | | 7 | (6..5) | (4..0) | | ------ | | - | ------ | ------- | | 0 | | i/f | vew0 | regnum | Showing the mapping (relationship) between 8-bit and 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 | 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. The 8 bit format is used for a much more compact expression. "isvec" is implicit and, similar to [[sv-prefix-proposal]], the target vector is "regnum<<2", implicitly. Contrast this with the 16-bit format where the target vector is *explicitly* named in bits 8 to 14, and bit 15 may optionally set "scalar" mode. Note that whilst SVPrefix adds one extra bit to each of rd, rs1 etc., and thus the "vector" mode need only shift the (6 bit) regnum by 1 to get the actual (7 bit) register number to use, there is not enough space in the 8 bit format (only 5 bits for regnum) so "regnum<<2" is required. vew has the following meanings, indicating that the instruction's operand size is "over-ridden" in a polymorphic fashion: | 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, implementation-wise) to expand it as follows: 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 ## Predication Table *NOTE: in prior versions of SV, this table used to be writable and accessible via CSRs. It is now stored in the VBLOCK instruction format. The table does **not** apply to SVPrefix opcodes* 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. It is particularly important to note that the *actual* register used can be *different* from the one that is in the instruction, due to the redirection through the lookup table. * 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. Some microarchitectures may choose to interpret this as skipping the operation entirely. Others which wish to stick more closely to a SIMD architecture may choose instead to interpret unpredicated elements as an internal "copy element" operation (which would be necessary in SIMD microarchitectures that perform register-renaming) * 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 | The 8 bit format is a compact and less expressive variant of the full 16 bit format. Using the 8 bit formatis very different: the predicate register to use is implicit, and numbering begins inplicitly from x9. The regnum is still used to "activate" predication, in the same fashion as described above. Thus if we map 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 | The 16 bit Predication CSR Table is a key-value store, so implementation-wise it will be faster to turn the table around (maintain topologically equivalent state): [[!inline raw="yes" pagenames="simple_v_specification/pred_table"]] So when an operation is to be predicated, it is the internal state that is used. In Section 6.4.2 of Hwacha's Manual (EECS-2015-262) the following pseudo-code for operations is given, where p is the explicit (direct) reference to the predication register to be used: for (int i=0; i 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, as well as pseudo-code, below. ## REMAP CSR (Note: both the REMAP and SHAPE sections are best read after the rest of the document has been read) There is one 32-bit CSR which may be used to indicate which registers, if used in any operation, must be "reshaped" (re-mapped) from a linear form to a 2D or 3D transposed form, or "offset" to permit arbitrary access to elements within a register. The 32-bit REMAP CSR may reshape up to 3 registers: | 29..28 | 27..26 | 25..24 | 23 | 22..16 | 15 | 14..8 | 7 | 6..0 | | ------ | ------ | ------ | -- | ------- | -- | ------- | -- | ------- | | shape2 | shape1 | shape0 | 0 | regidx2 | 0 | regidx1 | 0 | regidx0 | regidx0-2 refer not to the Register CSR CAM entry but to the underlying *real* register (see regidx, the value) and consequently is 7-bits wide. When set to zero (referring to x0), clearly reshaping x0 is pointless, so is used to indicate "disabled". shape0-2 refers to one of three SHAPE CSRs. A value of 0x3 is reserved. Bits 7, 15, 23, 30 and 31 are also reserved, and must be set to zero. It is anticipated that these specialist CSRs not be very often used. Unlike the CSR Register and Predication tables, the REMAP CSRs use the full 7-bit regidx so that they can be set once and left alone, whilst the CSR Register entries pointing to them are disabled, instead. ## SHAPE 1D/2D/3D vector-matrix remapping CSRs (Note: both the REMAP and SHAPE sections are best read after the rest of the document has been read) There are three "shape" CSRs, SHAPE0, SHAPE1, SHAPE2, 32-bits in each, which have the same format. When each SHAPE CSR is set entirely to zeros, remapping is disabled: the register's elements are a linear (1D) vector. | 26..24 | 23 | 22..16 | 15 | 14..8 | 7 | 6..0 | | ------- | -- | ------- | -- | ------- | -- | ------- | | permute | offs[2] | zdimsz | offs[1] | ydimsz | offs[0] | xdimsz | offs is a 3-bit field, spread out across bits 7, 15 and 23, which is added to the element index during the loop calculation. xdimsz, ydimsz and zdimsz are offset by 1, such that a value of 0 indicates that the array dimensionality for that dimension is 1. A value of xdimsz=2 would indicate that in the first dimension there are 3 elements in the array. The format of the array is therefore as follows: array[xdim+1][ydim+1][zdim+1] However whilst illustrative of the dimensionality, that does not take the "permute" setting into account. "permute" may be any one of six values (0-5, with values of 6 and 7 being reserved, and not legal). The table below shows how the permutation dimensionality order works: | permute | order | array format | | ------- | ----- | ------------------------ | | 000 | 0,1,2 | (xdim+1)(ydim+1)(zdim+1) | | 001 | 0,2,1 | (xdim+1)(zdim+1)(ydim+1) | | 010 | 1,0,2 | (ydim+1)(xdim+1)(zdim+1) | | 011 | 1,2,0 | (ydim+1)(zdim+1)(xdim+1) | | 100 | 2,0,1 | (zdim+1)(xdim+1)(ydim+1) | | 101 | 2,1,0 | (zdim+1)(ydim+1)(xdim+1) | In other words, the "permute" option changes the order in which nested for-loops over the array would be done. The algorithm below shows this more clearly, and may be executed as a python program: # mapidx = REMAP.shape2 xdim = 3 # SHAPE[mapidx].xdim_sz+1 ydim = 4 # SHAPE[mapidx].ydim_sz+1 zdim = 5 # SHAPE[mapidx].zdim_sz+1 lims = [xdim, ydim, zdim] idxs = [0,0,0] # starting indices order = [1,0,2] # experiment with different permutations, here offs = 0 # experiment with different offsets, here for idx in range(xdim * ydim * zdim): new_idx = offs + idxs[0] + idxs[1] * xdim + idxs[2] * xdim * ydim print new_idx, for i in range(3): idxs[order[i]] = idxs[order[i]] + 1 if (idxs[order[i]] != lims[order[i]]): break print idxs[order[i]] = 0 Here, it is assumed that this algorithm be run within all pseudo-code throughout this document where a (parallelism) for-loop would normally run from 0 to VL-1 to refer to contiguous register elements; instead, where REMAP indicates to do so, the element index is run through the above algorithm to work out the **actual** element index, instead. Given that there are three possible SHAPE entries, up to three separate registers in any given operation may be simultaneously remapped: function op_add(rd, rs1, rs2) # add not VADD! ... ...  for (i = 0; i < VL; i++) xSTATE.srcoffs = i # save context if (predval & 1< See [[appendix]] # Exceptions TODO: expand. Exceptions may occur at any time, in any given underlying scalar operation. This implies that context-switching (traps) may occur, and operation must be returned to where it left off. That in turn implies that the full state - including the current parallel element being processed - has to be saved and restored. This is what the **STATE** CSR is for. The implications are that all underlying individual scalar operations "issued" by the parallelisation have to appear to be executed sequentially. The further implications are that if two or more individual element operations are underway, and one with an earlier index causes an exception, it may be necessary for the microarchitecture to **discard** or terminate operations with higher indices. This being somewhat dissatisfactory, an "opaque predication" variant of the STATE CSR is being considered. # Hints A "HINT" is an operation that has no effect on architectural state, where its use may, by agreed convention, give advance notification to the microarchitecture: branch prediction notification would be a good example. Usually HINTs are where rd=x0. 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]] # Under consideration for element-grouping, if there is unused space within a register (3 16-bit elements in a 64-bit register for example), recommend: * For the unused elements in an integer register, the used element closest to the MSB is sign-extended on write and the unused elements are ignored on read. * The unused elements in a floating-point register are treated as-if they are set to all ones on write and are ignored on read, matching the existing standard for storing smaller FP values in larger registers. --- info register, > One solution is to just not support LR/SC wider than a fixed > implementation-dependent size, which must be at least  >1 XLEN word, which can be read from a read-only CSR > that can also be used for info like the kind and width of  > hw parallelism supported (128-bit SIMD, minimal virtual  > parallelism, etc.) and other things (like maybe the number  > of registers supported).  > That CSR would have to have a flag to make a read trap so > a hypervisor can simulate different values. ---- > And what about instructions like JALR?  answer: they're not vectorised, so not a problem ---- * if opcode is in the RV32 group, rd, rs1 and rs2 bitwidth are XLEN if elwidth==default * if opcode is in the RV32I group, rd, rs1 and rs2 bitwidth are *32* if elwidth == default --- TODO: document different lengths for INT / FP regfiles, and provide as part of info register. 00=32, 01=64, 10=128, 11=reserved. --- TODO, update to remove RegCam and PredCam CSRs, just use SVprefix and VBLOCK format --- Could the 8 bit Register VBLOCK format use regnum<<1 instead, only accessing regs 0 to 64? -- Expand the range of SUBVL and its associated svsrcoffs and svdestoffs by adding a 2nd STATE CSR (or extending STATE to 64 bits). Future version?