add obsolete tag
[libreriscv.git] / simple_v_extension / appendix.mdwn
index b17320105cdcf925b8a85b56ffcf385bc9aa94ba..c29044cfea6b9772be22c43d9b8dc3d968f819ee 100644 (file)
-# Simple-V (Parallelism Extension Proposal) Appendix
+[[!oldstandards]]
+
+# Simple-V (Parallelism Extension Proposal) Appendix (OBSOLETE)
+
+**OBSOLETE**
 
 * Copyright (C) 2017, 2018, 2019 Luke Kenneth Casson Leighton
 * Status: DRAFTv0.6
-* Last edited: 25 jun 2019
+* Last edited: 30 jun 2019
 * main spec [[specification]]
 
 [[!toc ]]
 
+# Fail-on-first modes <a name="ffirst"></a>
+
+Fail-on-first data dependency has different behaviour for traps than
+for conditional testing.  "Conditional" is taken to mean "anything
+that is zero", however with traps, the first element has to
+be given the opportunity to throw the exact same trap that would
+be thrown if this were a scalar operation (when VL=1).
+
+Note that implementors are required to mutually exclusively choose one
+or the other modes: an instruction is **not** permitted to fail on a
+trap *and* fail a conditional test at the same time.  This advice to
+custom opcode writers as well as future extension writers.
+
+## Fail-on-first traps
+
+Except for the first element, ffirst stops sequential element processing
+when a trap occurs.  The first element is treated normally (as if ffirst
+is clear).  Should any subsequent element instruction require a trap,
+instead it and subsequent indexed elements are ignored (or cancelled in
+out-of-order designs), and VL is set to the *last* in-sequence instruction
+that did not take the trap.
+
+Note that predicated-out elements (where the predicate mask bit is
+zero) are clearly excluded (i.e. the trap will not occur).  However,
+note that the loop still had to test the predicate bit: thus on return,
+VL is set to include elements that did not take the trap *and* includes
+the elements that were predicated (masked) out (not tested up to the
+point where the trap occurred).
+
+Unlike conditional tests, "fail-on-first trap" instruction behaviour is
+unaltered by setting zero or non-zero predication mode.
+
+If SUBVL is being used (SUBVL!=1), the first *sub-group* of elements
+will cause a trap as normal (as if ffirst is not set); subsequently, the
+trap must not occur in the *sub-group* of elements.  SUBVL will **NOT**
+be modified.  Traps must analyse (x)eSTATE (subvl offset indices) to
+determine the element that caused the trap.
+
+Given that predication bits apply to SUBVL groups, the same rules apply
+to predicated-out (masked-out) sub-groups in calculating the value that
+VL is set to.
+
+## Fail-on-first conditional tests
+
+ffirst stops sequential (or sequentially-appearing in the case of
+out-of-order designs) element conditional testing on the first element
+result being zero (or other "fail" condition).  VL is set to the number
+of elements that were (sequentially) processed before the fail-condition
+was encountered.
+
+Unlike trap fail-on-first, fail-on-first conditional testing behaviour
+responds to changes in the zero or non-zero predication mode.  Whilst
+in non-zeroing mode, masked-out elements are simply not tested (and
+thus considered "never to fail"), in zeroing mode, masked-out elements
+may be viewed as *always* (unconditionally) failing.  This effectively
+turns VL into something akin to a software-controlled loop.
+
+Note that just as with traps, if SUBVL!=1, the first trap in the
+*sub-group* will cause the processing to end, and, even if there were
+elements within the *sub-group* that passed the test, that sub-group is
+still (entirely) excluded from the count (from setting VL).  i.e. VL is
+set to the total number of *sub-groups* that had no fail-condition up
+until execution was stopped.  However, again: SUBVL must not be modified:
+traps must analyse (x)eSTATE (subvl offset indices) to determine the
+element that caused the trap.
+
+Note again that, just as with traps, predicated-out (masked-out) elements
+are included in the (sequential) count leading up to the fail-condition,
+even though they were not tested.
+
+# Instructions <a name="instructions" />
+
+Despite being a 98% complete and accurate topological remap of RVV
+concepts and functionality, no new instructions are needed.
+Compared to RVV: *All* RVV instructions can be re-mapped, however xBitManip
+becomes a critical dependency for efficient manipulation of predication
+masks (as a bit-field).  Despite the removal of all operations,
+with the exception of CLIP and VSELECT.X
+*all instructions from RVV Base are topologically re-mapped and retain their
+complete functionality, intact*.  Note that if RV64G ever had
+a MV.X added as well as FCLIP, the full functionality of RVV-Base would
+be obtained in SV.
+
+Three instructions, VSELECT, VCLIP and VCLIPI, do not have RV Standard
+equivalents, so are left out of Simple-V.  VSELECT could be included if
+there existed a MV.X instruction in RV (MV.X is a hypothetical
+non-immediate variant of MV that would allow another register to
+specify which register was to be copied).  Note that if any of these three
+instructions are added to any given RV extension, their functionality
+will be inherently parallelised.
+
+With some exceptions, where it does not make sense or is simply too
+challenging, all RV-Base instructions are parallelised:
+
+* CSR instructions, whilst a case could be made for fast-polling of
+  a CSR into multiple registers, or for being able to copy multiple
+  contiguously addressed CSRs into contiguous registers, and so on,
+  are the fundamental core basis of SV.  If parallelised, extreme
+  care would need to be taken.  Additionally, CSR reads are done
+  using x0, and it is *really* inadviseable to tag x0.
+* LUI, C.J, C.JR, WFI, AUIPC are not suitable for parallelising so are
+  left as scalar.
+* LR/SC could hypothetically be parallelised however their purpose is
+  single (complex) atomic memory operations where the LR must be followed
+  up by a matching SC.  A sequence of parallel LR instructions followed
+  by a sequence of parallel SC instructions therefore is guaranteed to
+  not be useful. Not least: the guarantees of a Multi-LR/SC
+  would be impossible to provide if emulated in a trap.
+* EBREAK, NOP, FENCE and others do not use registers so are not inherently
+  paralleliseable anyway.
+
+All other operations using registers are automatically parallelised.
+This includes AMOMAX, AMOSWAP and so on, where particular care and
+attention must be paid.
+
+Example pseudo-code for an integer ADD operation (including scalar
+operations).  Floating-point uses the FP Register Table.
+
+[[!inline raw="yes" pages="simple_v_extension/simple_add_example" ]]
+
+Note that for simplicity there is quite a lot missing from the above
+pseudo-code: PCVBLK, element widths, zeroing on predication, dimensional
+reshaping and offsets and so on.  However it demonstrates the basic
+principle.  Augmentations that produce the full pseudo-code are covered in
+other sections.
+
+## SUBVL Pseudocode <a name="subvl-pseudocode"></a>
+
+Adding in support for SUBVL is a matter of adding in an extra inner
+for-loop, where register src and dest are still incremented inside the
+inner part. Note that the predication is still taken from the VL index.
+
+So whilst elements are indexed by "(i * SUBVL + s)", predicate bits are
+indexed by "(i)"
+
+    function op_add(rd, rs1, rs2) # add not VADD!
+      int i, id=0, irs1=0, irs2=0;
+      predval = get_pred_val(FALSE, rd);
+      rd  = int_vec[rd ].isvector ? int_vec[rd ].regidx : rd;
+      rs1 = int_vec[rs1].isvector ? int_vec[rs1].regidx : rs1;
+      rs2 = int_vec[rs2].isvector ? int_vec[rs2].regidx : rs2;
+      for (i = 0; i < VL; i++)
+       xSTATE.srcoffs = i # save context
+       for (s = 0; s < SUBVL; s++)
+        xSTATE.ssvoffs = s # save context
+        if (predval & 1<<i) # predication uses intregs
+           # actual add is here (at last)
+           ireg[rd+id] <= ireg[rs1+irs1] + ireg[rs2+irs2];
+           if (!int_vec[rd ].isvector) break;
+        if (int_vec[rd ].isvector)  { id += 1; }
+        if (int_vec[rs1].isvector)  { irs1 += 1; }
+        if (int_vec[rs2].isvector)  { irs2 += 1; }
+        if (id == VL or irs1 == VL or irs2 == VL) {
+          # end VL hardware loop
+          xSTATE.srcoffs = 0; # reset
+          xSTATE.ssvoffs = 0; # reset
+          return;
+        }
+
+
+NOTE: pseudocode simplified greatly: zeroing, proper predicate handling,
+elwidth handling etc. all left out.
+
+## Instruction Format
+
+It is critical to appreciate that there are
+**no operations added to SV, at all**.
+
+Instead, by using CSRs to tag registers as an indication of "changed
+behaviour", SV *overloads* pre-existing branch operations into predicated
+variants, and implicitly overloads arithmetic operations, MV, FCVT, and
+LOAD/STORE depending on CSR configurations for bitwidth and predication.
+**Everything** becomes parallelised.  *This includes Compressed
+instructions* as well as any future instructions and Custom Extensions.
+
+Note: CSR tags to change behaviour of instructions is nothing new, including
+in RISC-V.  UXL, SXL and MXL change the behaviour so that XLEN=32/64/128.
+FRM changes the behaviour of the floating-point unit, to alter the rounding
+mode.  Other architectures change the LOAD/STORE byte-order from big-endian
+to little-endian on a per-instruction basis.  SV is just a little more...
+comprehensive in its effect on instructions.
+
+## Branch Instructions
+
+Branch operations are augmented slightly to be a little more like FP
+Compares (FEQ, FNE etc.), by permitting the cumulation (and storage)
+of multiple comparisons into a register (taken indirectly from the predicate
+table) and enhancing them to branch "consensually" depending on *multiple*
+tests.  "ffirst" - fail-on-first - condition mode can also be enabled,
+to terminate the comparisons early.
+See ffirst mode in the Predication Table section.
+
+There are two registers for the comparison operation, therefore there
+is the opportunity to associate two predicate registers (note: not in
+the same way as twin-predication).  The first is a "normal" predicate
+register, which acts just as it does on any other single-predicated
+operation: masks out elements where a bit is zero, applies an inversion
+to the predicate mask, and enables zeroing / non-zeroing mode.
+
+The second (not to be confused with a twin-predication 2nd register)
+is utilised to indicate where the results of each comparison are to
+be stored, as a bitmask.  Additionally, the behaviour of the branch -
+when it occurs - may also be modified depending on whether the 2nd predicate's
+"invert" and "zeroing" bits are set.  These four combinations result
+in "consensual branches", cbranch.ifnone (NOR), cbranch.ifany (OR),
+cbranch.ifall (AND), cbranch.ifnotall (NAND).
+
+| invert | zeroing | description                 | operation | cbranch |
+| ------ | ------- | --------------------------- | --------- | ------- |
+| 0      | 0       | branch if all pass          | AND       | ifall   |
+| 1      | 0       | branch if one fails         | NAND      | ifnall  |
+| 0      | 1       | branch if one passes        | OR        | ifany   |
+| 1      | 1       | branch if all fail          | NOR       | ifnone  |
+
+This inversion capability covers AND, OR, NAND and NOR branching
+based on multiple element comparisons. Without the full set of four,
+it is necessary to have two-sequence branch operations: one conditional, one
+unconditional.
+
+Note that unlike normal computer programming, early-termination of chains
+of AND or OR conditional tests, the chain does *not* terminate early
+except if fail-on-first is set, and even then ffirst ends on the first
+data-dependent zero.  When ffirst mode is not set, *all* conditional
+element tests must be performed (and the result optionally stored in
+the result mask), with a "post-analysis" phase carried out which checks
+whether to branch.
+
+Note also that whilst it may seem excessive to have all four (because
+conditional comparisons may be inverted by swapping src1 and src2),
+data-dependent fail-on-first is *not* invertible and *only* terminates
+on first zero-condition encountered.  Additionally it may be inconvenient
+to have to swap the predicate registers associated with src1 and src2,
+because this involves a new VBLOCK Context.
+
+### Standard Branch <a name="standard_branch"></a>
+
+Branch operations use standard RV opcodes that are reinterpreted to
+be "predicate variants" in the instance where either of the two src
+registers are marked as vectors (active=1, vector=1).
+
+Note that the predication register to use (if one is enabled) is taken from
+the *first* src register, and that this is used, just as with predicated
+arithmetic operations, to mask whether the comparison operations take
+place or not.  The target (destination) predication register
+to use (if one is enabled) is taken from the *second* src register.
+
+If either of src1 or src2 are scalars (whether by there being no
+CSR register entry or whether by the CSR entry specifically marking
+the register as "scalar") the comparison goes ahead as vector-scalar
+or scalar-vector.
+
+In instances where no vectorisation is detected on either src registers
+the operation is treated as an absolutely standard scalar branch operation.
+Where vectorisation is present on either or both src registers, the
+branch may stil go ahead if any only if *all* tests succeed (i.e. excluding
+those tests that are predicated out).
+
+Note that when zero-predication is enabled (from source rs1),
+a cleared bit in the predicate indicates that the result
+of the compare is set to "false", i.e. that the corresponding
+destination bit (or result)) be set to zero.  Contrast this with
+when zeroing is not set: bits in the destination predicate are
+only *set*; they are **not** cleared.  This is important to appreciate,
+as there may be an expectation that, going into the hardware-loop,
+the destination predicate is always expected to be set to zero:
+this is **not** the case.  The destination predicate is only set
+to zero if **zeroing** is enabled.
+
+Note that just as with the standard (scalar, non-predicated) branch
+operations, BLE, BGT, BLEU and BTGU may be synthesised by inverting
+src1 and src2, however note that in doing so, the predicate table
+setup must also be correspondingly adjusted.
+
+In Hwacha EECS-2015-262 Section 6.7.2 the following pseudocode is given
+for predicated compare operations of function "cmp":
+
+    for (int i=0; i<vl; ++i)
+      if ([!]preg[p][i])
+         preg[pd][i] = cmp(s1 ? vreg[rs1][i] : sreg[rs1],
+                           s2 ? vreg[rs2][i] : sreg[rs2]);
+
+With associated predication, vector-length adjustments and so on,
+and temporarily ignoring bitwidth (which makes the comparisons more
+complex), this becomes:
+
+    s1 = reg_is_vectorised(src1);
+    s2 = reg_is_vectorised(src2);
+
+    if not s1 && not s2
+        if cmp(rs1, rs2) # scalar compare
+            goto branch
+        return
+
+    preg = int_pred_reg[rd]
+    reg = int_regfile
+
+    ps = get_pred_val(I/F==INT, rs1);
+    rd = get_pred_val(I/F==INT, rs2); # this may not exist
+
+    ffirst_mode, zeroing = get_pred_flags(rs1)
+    if exists(rd):
+        pred_inversion, pred_zeroing = get_pred_flags(rs2)
+    else
+        pred_inversion, pred_zeroing = False, False
+
+    if not exists(rd) or zeroing:
+        result = (1<<VL)-1 # all 1s
+    else
+        result = preg[rd]
+
+    for (int i = 0; i < VL; ++i)
+      if (zeroing)
+        if not (ps & (1<<i))
+           result &= ~(1<<i);
+      else if (ps & (1<<i))
+          if (cmp(s1 ? reg[src1+i]:reg[src1],
+                               s2 ? reg[src2+i]:reg[src2])
+              result |= 1<<i;
+          else
+              result &= ~(1<<i);
+              if ffirst_mode:
+                break
+
+    if exists(rd):
+        preg[rd] = result # store in destination
+
+    if pred_inversion:
+        if pred_zeroing:
+            # NOR
+            if result == 0:
+                goto branch
+        else:
+            # NAND
+            if (result & ps) != result:
+                goto branch
+    else:
+        if pred_zeroing:
+            # OR
+            if result != 0:
+                goto branch
+        else:
+            # AND
+            if (result & ps) == result:
+                goto branch
+
+Notes:
+
+* Predicated SIMD comparisons would break src1 and src2 further down
+  into bitwidth-sized chunks (see Appendix "Bitwidth Virtual Register
+  Reordering") setting Vector-Length times (number of SIMD elements) bits
+  in Predicate Register rd, as opposed to just Vector-Length bits.
+* The execution of "parallelised" instructions **must** be implemented
+  as "re-entrant" (to use a term from software).  If an exception (trap)
+  occurs during the middle of a vectorised
+  Branch (now a SV predicated compare) operation, the partial results
+  of any comparisons must be written out to the destination
+  register before the trap is permitted to begin.  If however there
+  is no predicate, the **entire** set of comparisons must be **restarted**,
+  with the offset loop indices set back to zero.  This is because
+  there is no place to store the temporary result during the handling
+  of traps.
+
+TODO: predication now taken from src2.  also branch goes ahead
+if all compares are successful.
+
+Note also that where normally, predication requires that there must
+also be a CSR register entry for the register being used in order
+for the **predication** CSR register entry to also be active,
+for branches this is **not** the case.  src2 does **not** have
+to have its CSR register entry marked as active in order for
+predication on src2 to be active.
+
+Also note: SV Branch operations are **not** twin-predicated
+(see Twin Predication section).  This would require three
+element offsets: one to track src1, one to track src2 and a third
+to track where to store the accumulation of the results.  Given
+that the element offsets need to be exposed via CSRs so that
+the parallel hardware looping may be made re-entrant on traps
+and exceptions, the decision was made not to make SV Branches
+twin-predicated.
+
+### Floating-point Comparisons
+
+There does not exist floating-point branch operations, only compare.
+Interestingly no change is needed to the instruction format because
+FP Compare already stores a 1 or a zero in its "rd" integer register
+target, i.e. it's not actually a Branch at all: it's a compare.
+
+In RV (scalar) Base, a branch on a floating-point compare is
+done via the sequence "FEQ x1, f0, f5; BEQ x1, x0, #jumploc".
+This does extend to SV, as long as x1 (in the example sequence given)
+is vectorised.  When that is the case, x1..x(1+VL-1) will also be
+set to 0 or 1 depending on whether f0==f5, f1==f6, f2==f7 and so on.
+The BEQ that follows will *also* compare x1==x0, x2==x0, x3==x0 and
+so on.  Consequently, unlike integer-branch, FP Compare needs no
+modification in its behaviour.
+
+In addition, it is noted that an entry "FNE" (the opposite of FEQ) is
+missing, and whilst in ordinary branch code this is fine because the
+standard RVF compare can always be followed up with an integer BEQ or
+a BNE (or a compressed comparison to zero or non-zero), in predication
+terms that becomes more of an impact.  To deal with this, SV's predication
+has had "invert" added to it.
+
+Also: note that FP Compare may be predicated, using the destination
+integer register (rd) to determine the predicate.  FP Compare is **not**
+a twin-predication operation, as, again, just as with SV Branches,
+there are three registers involved: FP src1, FP src2 and INT rd.
+
+Also: note that ffirst (fail first mode) applies directly to this operation.
+
+### Compressed Branch Instruction
+
+Compressed Branch instructions are, just like standard Branch instructions,
+reinterpreted to be vectorised and predicated based on the source register
+(rs1s) CSR entries.  As however there is only the one source register,
+given that c.beqz a10 is equivalent to beqz a10,x0, the optional target
+to store the results of the comparisions is taken from CSR predication
+table entries for **x0**.
+
+The specific required use of x0 is, with a little thought, quite obvious,
+but is counterintuitive.  Clearly it is **not** recommended to redirect
+x0 with a CSR register entry, however as a means to opaquely obtain
+a predication target it is the only sensible option that does not involve
+additional special CSRs (or, worse, additional special opcodes).
+
+Note also that, just as with standard branches, the 2nd source
+(in this case x0 rather than src2) does **not** have to have its CSR
+register table marked as "active" in order for predication to work.
+
+## Vectorised Dual-operand instructions
+
+There is a series of 2-operand instructions involving copying (and
+sometimes alteration):
+
+* C.MV
+* FMV, FNEG, FABS, FCVT, FSGNJ, FSGNJN and FSGNJX
+* C.LWSP, C.SWSP, C.LDSP, C.FLWSP etc.
+* LOAD(-FP) and STORE(-FP)
+
+All of these operations follow the same two-operand pattern, so it is
+*both* the source *and* destination predication masks that are taken into
+account.  This is different from
+the three-operand arithmetic instructions, where the predication mask
+is taken from the *destination* register, and applied uniformly to the
+elements of the source register(s), element-for-element.
+
+The pseudo-code pattern for twin-predicated operations is as
+follows:
+
+    function op(rd, rs):
+      rd = int_csr[rd].active ? int_csr[rd].regidx : rd;
+      rs = int_csr[rs].active ? int_csr[rs].regidx : rs;
+      ps = get_pred_val(FALSE, rs); # predication on src
+      pd = get_pred_val(FALSE, rd); # ... AND on dest
+      for (int i = 0, int j = 0; i < VL && j < VL;):
+        if (int_csr[rs].isvec) while (!(ps & 1<<i)) i++;
+        if (int_csr[rd].isvec) while (!(pd & 1<<j)) j++;
+        xSTATE.srcoffs = i # save context
+        xSTATE.destoffs = j # save context
+        reg[rd+j] = SCALAR_OPERATION_ON(reg[rs+i])
+        if (int_csr[rs].isvec) i++;
+        if (int_csr[rd].isvec) j++; else break
+
+This pattern covers scalar-scalar, scalar-vector, vector-scalar
+and vector-vector, and predicated variants of all of those.
+Zeroing is not presently included (TODO).  As such, when compared
+to RVV, the twin-predicated variants of C.MV and FMV cover
+**all** standard vector operations: VINSERT, VSPLAT, VREDUCE,
+VEXTRACT, VSCATTER, VGATHER, VCOPY, and more.
+
+Note that:
+
+* elwidth (SIMD) is not covered in the pseudo-code above
+* ending the loop early in scalar cases (VINSERT, VEXTRACT) is also
+  not covered
+* zero predication is also not shown (TODO).
+
+### C.MV Instruction <a name="c_mv"></a>
+
+There is no MV instruction in RV however there is a C.MV instruction.
+It is used for copying integer-to-integer registers (vectorised FMV
+is used for copying floating-point).
+
+If either the source or the destination register are marked as vectors
+C.MV is reinterpreted to be a vectorised (multi-register) predicated
+move operation.  The actual instruction's format does not change:
+
+[[!table  data="""
+15  12 | 11   7 | 6  2 | 1  0 |
+funct4 | rd     | rs   | op   |
+4      | 5      | 5    | 2    |
+C.MV   | dest   | src  | C0   |
+"""]]
+
+A simplified version of the pseudocode for this operation is as follows:
+
+    function op_mv(rd, rs) # MV not VMV!
+      rd = int_csr[rd].active ? int_csr[rd].regidx : rd;
+      rs = int_csr[rs].active ? int_csr[rs].regidx : rs;
+      ps = get_pred_val(FALSE, rs); # predication on src
+      pd = get_pred_val(FALSE, rd); # ... AND on dest
+      for (int i = 0, int j = 0; i < VL && j < VL;):
+        if (int_csr[rs].isvec) while (!(ps & 1<<i)) i++;
+        if (int_csr[rd].isvec) while (!(pd & 1<<j)) j++;
+        xSTATE.srcoffs = i # save context
+        xSTATE.destoffs = j # save context
+        ireg[rd+j] <= ireg[rs+i];
+        if (int_csr[rs].isvec) i++;
+        if (int_csr[rd].isvec) j++; else break
+
+There are several different instructions from RVV that are covered by
+this one opcode:
+
+[[!table  data="""
+src    | dest    | predication   | op             |
+scalar | vector  | none          | VSPLAT         |
+scalar | vector  | destination   | sparse VSPLAT  |
+scalar | vector  | 1-bit dest    | VINSERT        |
+vector | scalar  | 1-bit? src    | VEXTRACT       |
+vector | vector  | none          | VCOPY          |
+vector | vector  | src           | Vector Gather  |
+vector | vector  | dest          | Vector Scatter |
+vector | vector  | src & dest    | Gather/Scatter |
+vector | vector  | src == dest   | sparse VCOPY   |
+"""]]
+
+Also, VMERGE may be implemented as back-to-back (macro-op fused) C.MV
+operations with zeroing off, and inversion on the src and dest predication
+for one of the two C.MV operations.  The non-inverted C.MV will place
+one set of registers into the destination, and the inverted one the other
+set.  With predicate-inversion, copying and inversion of the predicate mask
+need not be done as a separate (scalar) instruction.
+
+Note that in the instance where the Compressed Extension is not implemented,
+MV may be used, but that is a pseudo-operation mapping to addi rd, x0, rs.
+Note that the behaviour is **different** from C.MV because with addi the
+predication mask to use is taken **only** from rd and is applied against
+all elements: rs[i] = rd[i].
+
+### FMV, FNEG and FABS Instructions
+
+These are identical in form to C.MV, except covering floating-point
+register copying.  The same double-predication rules also apply.
+However when elwidth is not set to default the instruction is implicitly
+and automatic converted to a (vectorised) floating-point type conversion
+operation of the appropriate size covering the source and destination
+register bitwidths.
+
+(Note that FMV, FNEG and FABS are all actually pseudo-instructions)
+
+### FVCT Instructions
+
+These are again identical in form to C.MV, except that they cover
+floating-point to integer and integer to floating-point.  When element
+width in each vector is set to default, the instructions behave exactly
+as they are defined for standard RV (scalar) operations, except vectorised
+in exactly the same fashion as outlined in C.MV.
+
+However when the source or destination element width is not set to default,
+the opcode's explicit element widths are *over-ridden* to new definitions,
+and the opcode's element width is taken as indicative of the SIMD width
+(if applicable i.e. if packed SIMD is requested) instead.
+
+For example FCVT.S.L would normally be used to convert a 64-bit
+integer in register rs1 to a 64-bit floating-point number in rd.
+If however the source rs1 is set to be a vector, where elwidth is set to
+default/2 and "packed SIMD" is enabled, then the first 32 bits of
+rs1 are converted to a floating-point number to be stored in rd's
+first element and the higher 32-bits *also* converted to floating-point
+and stored in the second.  The 32 bit size comes from the fact that
+FCVT.S.L's integer width is 64 bit, and with elwidth on rs1 set to
+divide that by two it means that rs1 element width is to be taken as 32.
+
+Similar rules apply to the destination register.
+
+## LOAD / STORE Instructions and LOAD-FP/STORE-FP <a name="load_store"></a>
+
+An earlier draft of SV modified the behaviour of LOAD/STORE (modified
+the interpretation of the instruction fields).  This
+actually undermined the fundamental principle of SV, namely that there
+be no modifications to the scalar behaviour (except where absolutely
+necessary), in order to simplify an implementor's task if considering
+converting a pre-existing scalar design to support parallelism.
+
+So the original RISC-V scalar LOAD/STORE and LOAD-FP/STORE-FP functionality
+do not change in SV, however just as with C.MV it is important to note
+that dual-predication is possible.
+
+In vectorised architectures there are usually at least two different modes
+for LOAD/STORE:
+
+* Read (or write for STORE) from sequential locations, where one
+  register specifies the address, and the one address is incremented
+  by a fixed amount.  This is usually known as "Unit Stride" mode.
+* Read (or write) from multiple indirected addresses, where the
+  vector elements each specify separate and distinct addresses.
+
+To support these different addressing modes, the CSR Register "isvector"
+bit is used.  So, for a LOAD, when the src register is set to
+scalar, the LOADs are sequentially incremented by the src register
+element width, and when the src register is set to "vector", the
+elements are treated as indirection addresses.  Simplified
+pseudo-code would look like this:
+
+    function op_ld(rd, rs) # LD not VLD!
+      rdv = int_csr[rd].active ? int_csr[rd].regidx : rd;
+      rsv = int_csr[rs].active ? int_csr[rs].regidx : rs;
+      ps = get_pred_val(FALSE, rs); # predication on src
+      pd = get_pred_val(FALSE, rd); # ... AND on dest
+      for (int i = 0, int j = 0; i < VL && j < VL;):
+        if (int_csr[rs].isvec) while (!(ps & 1<<i)) i++;
+        if (int_csr[rd].isvec) while (!(pd & 1<<j)) j++;
+        if (int_csr[rd].isvec)
+          # indirect mode (multi mode)
+          srcbase = ireg[rsv+i];
+        else
+          # unit stride mode
+          srcbase = ireg[rsv] + i * XLEN/8; # offset in bytes
+        ireg[rdv+j] <= mem[srcbase + imm_offs];
+        if (!int_csr[rs].isvec &&
+            !int_csr[rd].isvec) break # scalar-scalar LD
+        if (int_csr[rs].isvec) i++;
+        if (int_csr[rd].isvec) j++;
+
+Notes:
+
+* For simplicity, zeroing and elwidth is not included in the above:
+  the key focus here is the decision-making for srcbase; vectorised
+  rs means use sequentially-numbered registers as the indirection
+  address, and scalar rs is "offset" mode.
+* The test towards the end for whether both source and destination are
+  scalar is what makes the above pseudo-code provide the "standard" RV
+  Base behaviour for LD operations.
+* The offset in bytes (XLEN/8) changes depending on whether the
+  operation is a LB (1 byte), LH (2 byes), LW (4 bytes) or LD
+  (8 bytes), and also whether the element width is over-ridden
+  (see special element width section).
+
+## Compressed Stack LOAD / STORE Instructions <a name="c_ld_st"></a>
+
+C.LWSP / C.SWSP and floating-point etc. are also source-dest twin-predicated,
+where it is implicit in C.LWSP/FLWSP etc. that x2 is the source register.
+It is therefore possible to use predicated C.LWSP to efficiently
+pop registers off the stack (by predicating x2 as the source), cherry-picking
+which registers to store to (by predicating the destination).  Likewise
+for C.SWSP.  In this way, LOAD/STORE-Multiple is efficiently achieved.
+
+The two modes ("unit stride" and multi-indirection) are still supported,
+as with standard LD/ST.  Essentially, the only difference is that the
+use of x2 is hard-coded into the instruction.
+
+**Note**: it is still possible to redirect x2 to an alternative target
+register.  With care, this allows C.LWSP / C.SWSP (and C.FLWSP) to be used as
+general-purpose LOAD/STORE operations.
+
+## Compressed LOAD / STORE Instructions
+
+Compressed LOAD and STORE are again exactly the same as scalar LOAD/STORE,
+where the same rules apply and the same pseudo-code apply as for
+non-compressed LOAD/STORE.  Again: setting scalar or vector mode
+on the src for LOAD and dest for STORE switches mode from "Unit Stride"
+to "Multi-indirection", respectively.
+
 # Element bitwidth polymorphism <a name="elwidth"></a>
 
 Element bitwidth is best covered as its own special section, as it
@@ -212,15 +880,15 @@ to those produced by the above algorithm.
 
 ## Polymorphic floating-point operation exceptions and error-handling
 
-For floating-point operations, conversion takes place without
-raising any kind of exception.  Exactly as specified in the standard
-RV specification, NAN (or appropriate) is stored if the result
-is beyond the range of the destination, and, again, exactly as
-with the standard RV specification just as with scalar
-operations, the floating-point flag is raised (FCSR).  And, again, just as
-with scalar operations, it is software's responsibility to check this flag.
-Given that the FCSR flags are "accrued", the fact that multiple element
-operations could have occurred is not a problem.
+For floating-point operations, conversion takes place without raising any
+kind of exception.  Exactly as specified in the standard RV specification,
+NAN (or appropriate) is stored if the result is beyond the range of the
+destination, and, again, exactly as with the standard RV specification
+just as with scalar operations, the floating-point flag is raised
+(FCSR).  And, again, just as with scalar operations, it is software's
+responsibility to check this flag.  Given that the FCSR flags are
+"accrued", the fact that multiple element operations could have occurred
+is not a problem.
 
 Note that it is perfectly legitimate for floating-point bitwidths of
 only 8 to be specified.  However whilst it is possible to apply IEEE 754
@@ -231,11 +899,11 @@ proceeding.
 
 ## Polymorphic shift operators
 
-A special note is needed for changing the element width of left and right
-shift operators, particularly right-shift.  Even for standard RV base,
-in order for correct results to be returned, the second operand RS2 must
-be truncated to be within the range of RS1's bitwidth.  spike's implementation
-of sll for example is as follows:
+A special note is needed for changing the element width of left and
+right shift operators, particularly right-shift.  Even for standard RV
+base, in order for correct results to be returned, the second operand
+RS2 must be truncated to be within the range of RS1's bitwidth.
+spike's implementation of sll for example is as follows:
 
     WRITE_RD(sext_xlen(zext_xlen(RS1) << (RS2 & (xlen-1))));
 
@@ -432,7 +1100,7 @@ Note:
   is also marked as scalar, this is how the compatibility with
   standard RV LOAD/STORE is preserved by this algorithm.
 
-### Example Tables showing LOAD elements
+### Example Tables showing LOAD elements <a name="load_example"></a>
 
 This section contains examples of vectorised LOAD operations, showing
 how the two stage process works (three if zero/sign-extension is included).
@@ -449,13 +1117,12 @@ This is:
 * from register x5 (actually x5-x6) to x8 (actually x8 to half of x11)
 * RV64, where XLEN=64 is assumed.
 
-First, the memory table, which, due to the
-element width being 16 and the operation being LD (64), the 64-bits
-loaded from memory are subdivided into groups of **four** elements.
-And, with VL being 7 (deliberately to illustrate that this is reasonable
-and possible), the first four are sourced from the offset addresses pointed
-to by x5, and the next three from the ofset addresses pointed to by
-the next contiguous register, x6:
+First, the memory table, which, due to the element width being 16 and the
+operation being LD (64), the 64-bits loaded from memory are subdivided
+into groups of **four** elements.  And, with VL being 7 (deliberately
+to illustrate that this is reasonable and possible), the first four are
+sourced from the offset addresses pointed to by x5, and the next three
+from the ofset addresses pointed to by the next contiguous register, x6:
 
 [[!table  data="""
 addr | byte 0 | byte 1 | byte 2 | byte 3 | byte 4 | byte 5 | byte 6 | byte 7 |
@@ -704,9 +1371,9 @@ rs1 equals the bitwidth of rs2, no sign-extending will occur.  It is
 only where the bitwidth of either rs1 or rs2 are different, will the
 lesser-width operand be sign-extended.
 
-Effectively however, both rs1 and rs2 are being sign-extended (or truncated),
-where for add they are both zero-extended.  This holds true for all arithmetic
-operations ending with "W".
+Effectively however, both rs1 and rs2 are being sign-extended (or
+truncated), where for add they are both zero-extended.  This holds true
+for all arithmetic operations ending with "W".
 
 ### addiw
 
@@ -793,7 +1460,7 @@ circumstances it is perfectly fine to simply have the lanes
 "inactive" for predicated elements, even though it results in
 less than 100% ALU utilisation.
 
-## Twin-predication (based on source and destination register)
+## Twin-predication (based on source and destination register) <a name="tpred"></a>
 
 Twin-predication is not that much different, except that that
 the source is independently zero-predicated from the destination.
@@ -921,92 +1588,121 @@ of total length 128 bit given that XLEN is now 128.
 TODO evaluate strncpy and strlen
 <https://groups.google.com/forum/m/#!msg/comp.arch/bGBeaNjAKvc/_vbqyxTUAQAJ>
 
-## strncpy
-
-RVV version: <a name="strncpy"></>
-
-    strncpy: 
-        mv a3, a0               # Copy dst 
-    loop: 
-        setvli x0, a2, vint8    # Vectors of bytes. 
-        vlbff.v v1, (a1)        # Get src bytes 
-        vseq.vi v0, v1, 0       # Flag zero bytes 
-        vmfirst a4, v0          # Zero found? 
-        vmsif.v v0, v0          # Set mask up to and including zero byte. Ppplio
-        vsb.v v1, (a3), v0.t    # Write out bytes 
-        bgez a4, exit           # Done 
-        csrr t1, vl             # Get number of bytes fetched 
-        add a1, a1, t1          # Bump src pointer 
-        sub a2, a2, t1          # Decrement count. 
-        add a3, a3, t1          # Bump dst pointer 
-        bnez a2, loop           # Anymore? 
-
-    exit: 
-        ret 
+## strncpy <a name="strncpy"></>
+
+RVV version:
+
+    strncpy:
+        c.mv a3, a0               # Copy dst
+    loop:
+        setvli x0, a2, vint8    # Vectors of bytes.
+        vlbff.v v1, (a1)        # Get src bytes
+        vseq.vi v0, v1, 0       # Flag zero bytes
+        vmfirst a4, v0          # Zero found?
+        vmsif.v v0, v0          # Set mask up to and including zero byte.
+        vsb.v v1, (a3), v0.t    # Write out bytes
+        c.bgez a4, exit           # Done
+        csrr t1, vl             # Get number of bytes fetched
+        c.add a1, a1, t1          # Bump src pointer
+        c.sub a2, a2, t1          # Decrement count.
+        c.add a3, a3, t1          # Bump dst pointer
+        c.bnez a2, loop           # Anymore?
+
+    exit:
+        c.ret
 
 SV version (WIP):
 
     strncpy:
-        mv a3, a0
-        SETMVLI 8 # set max vector to 8
-        RegCSR[a3] = 8bit, a3, scalar
-        RegCSR[a1] = 8bit, a1, scalar
-        RegCSR[t0] = 8bit, t0, vector
-        PredTb[t0] = ffirst, x0, inv
+        c.mv a3, a0
+        VBLK.RegCSR[t0] = 8bit, t0, vector
+        VBLK.PredTb[t0] = ffirst, x0, inv
     loop:
-        SETVLI a2, t4 # t4 and VL now 1..8
-        ldb t0, (a1) # t0 fail first mode
-        bne t0, x0, allnonzero # still ff
-        # VL points to last nonzero
-        GETVL t4       # from bne tests
-        addi t4, t4, 1 # include zero
-        SETVL t4       # set exactly to t4
-        stb t0, (a3)   # store incl zero
-        ret            # end subroutine
+        VBLK.SETVLI a2, t4, 8 # t4 and VL now 1..8 (MVL=8)
+        c.ldb t0, (a1) # t0 fail first mode
+        c.bne t0, x0, allnonzero # still ff
+        # VL (t4) points to last nonzero
+        c.addi t4, t4, 1 # include zero
+        c.stb t0, (a3)   # store incl zero
+        c.ret            # end subroutine
     allnonzero:
-        stb t0, (a3)    # VL legal range
-        GETVL t4        # from bne tests
-        add a1, a1, t4  # Bump src pointer 
-        sub a2, a2, t4  # Decrement count. 
-        add a3, a3, t4  # Bump dst pointer 
-        bnez a2, loop   # Anymore? 
+        c.stb t0, (a3)    # VL legal range
+        c.add a1, a1, t4  # Bump src pointer
+        c.sub a2, a2, t4  # Decrement count.
+        c.add a3, a3, t4  # Bump dst pointer
+        c.bnez a2, loop   # Anymore?
     exit:
-        ret
+        c.ret
 
 Notes:
 
-* Setting MVL to 8 is just an example. If enough registers are spare it may be set to XLEN which will require a bank of 8 scalar registers for a1, a3 and t0.
-* obviously if that is done, t0 is not separated by 8 full registers, and would overwrite t1 thru t7. x80 would work well, as an example, instead.
-* with the exception of the GETVL (a pseudo code alias for csrr), every single instruction above may use RVC.
-* RVC C.BNEZ can be used because rs1' may be extended to the full 128 registers through redirection
-* RVC C.LW and C.SW may be used because the W format may be overridden by the 8 bit format. All of t0, a3 and a1 are overridden to make that work.
-* with the exception of the GETVL, all Vector Context may be done in VBLOCK form.
-* setting predication to x0 (zero) and invert on t0 is a trick to enable just ffirst on t0
+* Setting MVL to 8 is just an example. If enough registers are spare it
+  may be set to XLEN which will require a bank of 8 scalar registers for
+  a1, a3 and t0.
+* obviously if that is done, t0 is not separated by 8 full registers, and
+  would overwrite t1 thru t7. x80 would work well, as an example, instead.
+* with the exception of the GETVL (a pseudo code alias for csrr), every
+  single instruction above may use RVC.
+* RVC C.BNEZ can be used because rs1' may be extended to the full 128
+  registers through redirection
+* RVC C.LW and C.SW may be used because the W format may be overridden by
+  the 8 bit format. All of t0, a3 and a1 are overridden to make that work.
+* with the exception of the GETVL, all Vector Context may be done in
+  VBLOCK form.
+* setting predication to x0 (zero) and invert on t0 is a trick to enable
+  just ffirst on t0
 * ldb and bne are both using t0, both in ffirst mode
-* ldb will end on illegal mem, reduce VL, but copied all sorts of stuff into t0
-* bne t0 x0 tests up to the NEW VL for nonzero, vector t0 against scalar x0
-* however as t0 is in ffirst mode, the first fail wil ALSO stop the compares, and reduce VL as well
+* t0 vectorised, a1 scalar, both elwidth 8 bit: ldb enters "unit stride,
+  vectorised, no (un)sign-extension or truncation" mode.
+* ldb will end on illegal mem, reduce VL, but copied all sorts of stuff
+  into t0 (could contain zeros).
+* bne t0 x0 tests up to the NEW VL for nonzero, vector t0 against
+  scalar x0
+* however as t0 is in ffirst mode, the first fail will ALSO stop the
+  compares, and reduce VL as well
 * the branch only goes to allnonzero if all tests succeed
-* if it did not, we can safely increment VL by 1 (using a4) to include the zero.
+* if it did not, we can safely increment VL by 1 (using a4) to include
+  the zero.
 * SETVL sets *exactly* the requested amount into VL.
-* the SETVL just after allnonzero label is needed in case the ldb ffirst activates but the bne allzeros does not.
+* the SETVL just after allnonzero label is needed in case the ldb ffirst
+  activates but the bne allzeros does not.
 * this would cause the stb to copy up to the end of the legal memory
-* of course, on the next loop the ldb would throw a trap, as a1 now points to the first illegal mem location.
+* of course, on the next loop the ldb would throw a trap, as a1 now
+  points to the first illegal mem location.
 
 ## strcpy
 
 RVV version:
 
-        mv a3, a0             # Save start 
-    loop: 
+        mv a3, a0             # Save start
+    loop:
         setvli a1, x0, vint8  # byte vec, x0 (Zero reg) => use max hardware len
         vldbff.v v1, (a3)     # Get bytes
         csrr a1, vl           # Get bytes actually read e.g. if fault
-        vseq.vi v0, v1, 0     # Set v0[i] where v1[i] = 0 
+        vseq.vi v0, v1, 0     # Set v0[i] where v1[i] = 0
         add a3, a3, a1        # Bump pointer
         vmfirst a2, v0        # Find first set bit in mask, returns -1 if none
         bltz a2, loop         # Not found?
         add a0, a0, a1        # Sum start + bump
         add a3, a3, a2        # Add index of zero byte
         sub a0, a3, a0        # Subtract start address+bump
-        ret 
+        ret
+
+## DAXPY <a name="daxpy"></a>
+
+[[!inline raw="yes" pages="simple_v_extension/daxpy_example" ]]
+
+Notes:
+
+* Setting MVL to 4 is just an example.  With enough space between the
+  FP regs, MVL may be set to larger values
+* VBLOCK header takes 16 bits, 8-bit mode may be used on the registers,
+  taking only another 16 bits, VBLOCK.SETVL requires 16 bits.  Total
+  overhead for use of VBLOCK: 48 bits (3 16-bit words).
+* All instructions except fmadd may use Compressed variants.  Total
+  number of 16-bit instruction words: 11.
+* Total: 14 16-bit words.  By contrast, RVV requires around 18 16-bit words.
+
+## BigInt add <a name="bigadd"></a>
+
+[[!inline raw="yes" pages="simple_v_extension/bigadd_example" ]]