(no commit message)
[libreriscv.git] / openpower / sv / overview.mdwn
index 571d21def460435a6b6b75a724d6c0e0f070ec97..794155bdc56d254af7622fa1ba0528bd554b0ee2 100644 (file)
@@ -1,17 +1,32 @@
 # SV Overview
 
+**SV is in DRAFT STATUS**. SV has not yet been submitted to the OpenPOWER
+Foundation ISA WG for review.
+
 This document provides an overview and introduction as to why SV (a
-Cray-style Vector augmentation to OpenPOWER) exists, and how it works.
+[[!wikipedia Cray]]-style Vector augmentation to
+[[!wikipedia OpenPOWER]]) exists, and how it works.
+
+**Sponsored by NLnet under the Privacy and Enhanced Trust Programme**
 
 Links:
 
+* This page: [http://libre-soc.org/openpower/sv/overview](http://libre-soc.org/openpower/sv/overview)
+* [FOSDEM2021 SimpleV for Power ISA](https://fosdem.org/2021/schedule/event/the_libresoc_project_simple_v_vectorization/)
+* FOSDEM2021 presentation <https://www.youtube.com/watch?v=FS6tbfyb2VA>
 * [[discussion]] and
   [bugreport](https://bugs.libre-soc.org/show_bug.cgi?id=556)
   feel free to add comments, questions.
 * [[SV|sv]]
 * [[sv/svp64]]
+* [x86 REP instruction](https://c9x.me/x86/html/file_module_x86_id_279.html):
+  a useful way to quickly understand that the core of the SV concept
+  is not new.
+* [Article about register tagging](http://science.lpnu.ua/sites/default/files/journal-paper/2019/jul/17084/volum3number1text-9-16_1.pdf) showing
+  that tagging is not a new idea either. Register tags
+  are also used in the Mill Architecture.
 
-Contents:
+Table of contents:
 
 [[!toc]]
 
@@ -30,13 +45,18 @@ more problematic with each power of two SIMD width increase introduced
 through an ISA revision.  The opcode proliferation, at O(N^6), inexorably
 spirals out of control in the ISA, detrimentally impacting the hardware,
 the software, the compilers and the testing and compliance.  Here are
-the typical dimensions that result in such massive proliferation:
+the typical dimensions that result in such massive proliferation,
+based on mass-volume DSPs and Micro-Processors:
 
 * Operation (add, mul)
 * bitwidth (8, 16, 32, 64, 128)
 * Conversion between bitwidths (FP16-FP32-64)
 * Signed/unsigned
 * HI/LO swizzle (Audio L/R channels)
+   - HI/LO selection on src 1
+   - selection on src 2
+   - selection on dest
+   - Example: AndesSTAR Audio DSP 
 * Saturation (Clamping at max range)
 
 These typically are multiplied up to produce explicit opcodes numbering
@@ -53,16 +73,19 @@ Unlike in SIMD, powers of two limitations are not involved in the ISA
 or in the assembly code.
 
 SimpleV takes the Cray style Vector principle and applies it in the
-abstract to a Scalar ISA, in the process allowing register file size
-increases using "tagging" (similar to how x86 originally extended
+abstract to a Scalar ISA in the same way that x86 used to do its "REP" instruction.  In the process, "context" is applied, allowing amongst other things
+a register file size
+increase using "tagging" (similar to how x86 originally extended
 registers from 32 to 64 bit).
 
+![Single-Issue concept](/openpower/svp64-primer/img/power_pipelines.svg){ width=40% height=20% }
+
 ## SV
 
-The fundamentals are:
+The fundamentals are (just like x86 "REP"):
 
 * The Program Counter (PC) gains a "Sub Counter" context (Sub-PC)
-* Vectorisation pauses the PC and runs a Sub-PC loop from 0 to VL-1
+* Vectorization pauses the PC and runs a Sub-PC loop from 0 to VL-1
   (where VL is Vector Length)
 * The [[Program Order]] of "Sub-PC" instructions must be preserved,
   just as is expected of instructions ordered by the PC.
@@ -73,14 +96,24 @@ The fundamentals are:
 * Once the loop is completed *only then* is the Program Counter
   allowed to move to the next instruction.
 
+![Multi-Issue with Predicated SIMD back-end ALUs](/openpower/svp64-primer/img/sv_multi_issue.svg){ width=40% height=40% }
+
 Hardware (and simulator) implementors are free and clear to implement this
 as literally a for-loop, sitting in between instruction decode and issue.
 Higher performance systems may deploy SIMD backends, multi-issue and
 out-of-order execution, although it is strongly recommended to add
 predication capability directly into SIMD backend units.
 
-In OpenPOWER ISA v3.0B pseudo-code form, an ADD operation, assuming both
-source and destination have been "tagged" as Vectors, is simply:
+A typical Cray-style Scalable Vector ISA (where a SIMD one has a fixed
+non-negotiable static parameter instead of a runtime-dynamic VL)
+performs its arithmetic as:
+
+    for i = 0 to VL-1:
+         VPR(RT)[i] = VPR[RA][i] + VPR(RB)[i]
+
+In Power ISA v3.0B pseudo-code form, an ADD operation in Simple-V,
+assuming both source and destination have been "tagged" as Vectors,
+is simply:
 
     for i = 0 to VL-1:
          GPR(RT+i) = GPR(RA+i) + GPR(RB+i)
@@ -95,14 +128,14 @@ additional instructions have been added, almost all of them SIMD.
 
 RISC-V RVV as of version 0.9 is over 188 instructions (more than the
 rest of RV64G combined: 80 for RV64G and 27 for C). Over 95% of that
-functionality is added to OpenPOWER v3 0B, by SimpleV augmentation,
+functionality is added to Power v3.0B, by SimpleV augmentation,
 with around 5 to 8 instructions.
 
-Even in OpenPOWER v3.0B, the Scalar Integer ISA is around 150
+Even in Power ISA v3.0B, the Scalar Integer ISA is around 150
 instructions, with IEEE754 FP adding approximately 80 more. VSX, being
 based on SIMD design principles, adds somewhere in the region of 600 more.
 SimpleV again provides over 95% of VSX functionality, simply by augmenting
-the *Scalar* OpenPOWER ISA, and in the process providing features such
+the *Scalar* Power ISA, and in the process providing features such
 as predication, which VSX is entirely missing.
 
 AVX512, SVE2, VSX, RVV, all of these systems have to provide different
@@ -110,7 +143,7 @@ types of register files: Scalar and Vector is the minimum. AVX512
 even provides a mini mask regfile, followed by explicit instructions
 that handle operations on each of them *and map between all of them*.
 SV simply not only uses the existing scalar regfiles (including CRs),
-but because operations exist within OpenPOWER to cover interactions
+but because operations exist within Power ISA to cover interactions
 between the scalar regfiles (`mfcr`, `fcvt`) there is very little that
 needs to be added.
 
@@ -121,9 +154,7 @@ by SimpleV:
   (VSX Rijndael and SHA primitives; VSX shuffle and bitpermute operations)
 * register files above 128 entries
 * Vector lengths over 64
-* Unit-strided LD/ST and other comprehensive memory operations
-  (struct-based LD/ST from RVV for example)
-* 32-bit instruction lengths. [[svp64]] had to be added as 64 bit.
+* 32-bit instruction lengths. [[sv/svp64]] had to be added as 64 bit.
 
 These limitations, which stem inherently from the adaptation process of
 starting from a Scalar ISA, are not insurmountable. Over time, they may
@@ -139,11 +170,10 @@ The rest of this document builds on the above simple loop to add:
 * Compacted operations into registers (normally only provided by SIMD)
 * Fail-on-first (introduced in ARM SVE2)
 * A new concept: Data-dependent fail-first
-* Condition-Register based *post-result* predication (also new)
 * A completely new concept: "Twin Predication"
 * vec2/3/4 "Subvectors" and Swizzling (standard fare for 3D)
 
-All of this is *without modifying the OpenPOWER v3.0B ISA*, except to add
+All of this is *without modifying the Power v3.0B ISA*, except to add
 "wrapping context", similar to how v3.1B 64 Prefixes work.
 
 # Adding Scalar / Vector
@@ -185,14 +215,14 @@ on the standard register file, just with a loop.  Scalar happens to set
 that loop size to one.
 
 The important insight from the above is that, strictly speaking, Simple-V
-is not really a Vectorisation scheme at all: it is more of a hardware
+is not really a Vectorization scheme at all: it is more of a hardware
 ISA "Compression scheme", allowing as it does for what would normally
 require multiple sequential instructions to be replaced with just one.
 This is where the rule that Program Order must be preserved in Sub-PC
 execution derives from.  However in other ways, which will emerge below,
 the "tagging" concept presents an opportunity to include features
 definitely not common outside of Vector ISAs, and in that regard it's
-definitely a class of Vectorisation.
+definitely a class of Vectorization.
 
 ## Register "tagging"
 
@@ -203,7 +233,7 @@ is encoded in two to three bits, depending on the instruction.
 The reason for using so few bits is because there are up to *four*
 registers to mark in this way (`fma`, `isel`) which starts to be of
 concern when there are only 24 available bits to specify the entire SV
-Vectorisation Context.  In fact, for a small subset of instructions it
+Vectorization Context.  In fact, for a small subset of instructions it
 is just not possible to tag every single register.  Under these rare
 circumstances a tag has to be shared between two registers.
 
@@ -227,12 +257,12 @@ Condition Registers have a slightly different scheme, along the same
 principle, which takes into account the fact that each CR may be bit-level
 addressed by Condition Register operations.
 
-Readers familiar with OpenPOWER will know of Rc=1 operations that create
+Readers familiar with the Power ISA will know of Rc=1 operations that create
 an associated post-result "test", placing this test into an implicit
 Condition Register.  The original researchers who created the POWER ISA
 chose CR0 for Integer, and CR1 for Floating Point.  These *also become
-Vectorised* - implicitly - if the associated destination register is
-also Vectorised.  This allows for some very interesting savings on
+Vectorized* - implicitly - if the associated destination register is
+also Vectorized.  This allows for some very interesting savings on
 instruction count due to the very same CR Vectors being predication masks.
 
 # Adding single predication
@@ -245,7 +275,7 @@ effectively the same as "no predicate".
 
     function op_add(RT, RA, RB) # add not VADD!
       int id=0, irs1=0, irs2=0;
-      predval = get_pred_val(FALSE, rd);
+      predval = get_pred_val(FALSE, RT); # dest mask
       for i = 0 to VL-1:
         if (predval & 1<<i) # predication bit test
            ireg[RT+id] <= ireg[RA+irs1] + ireg[RB+irs2];
@@ -260,15 +290,18 @@ through the registers*.
 
 A particularly interesting case is if the destination is scalar, and the
 first few bits of the predicate are zero.  The loop proceeds to increment
-the Scalar *source* registers until the first nonzero predicate bit is
-found, whereupon a single result is computed, and *then* the loop exits.
-This therefore uses the predicate to perform Vector source indexing.
-This case was not possible without the predicate mask.
+the Vector *source* registers until the first nonzero predicate bit is
+found, whereupon a single *Scalar* result is computed, and *then* the loop
+exits.
+This in effect uses the predicate to perform *Vector source indexing*.
+This case was not possible without the predicate mask. Also, interestingly,
+the predicate mode `1<<r3` is specifically provided as a way to select
+one single entry from a Vector.
 
 If all three registers are marked as Vector then the "traditional"
 predicated Vector behaviour is provided.  Yet, just as before, all other
 options are still provided, right the way back to the pure-scalar case,
-as if this were a straight OpenPOWER v3.0B non-augmented instruction.
+as if this were a straight Power ISA v3.0B non-augmented instruction.
 
 Single Predication therefore provides several modes traditionally seen
 in Vector ISAs:
@@ -279,7 +312,7 @@ in Vector ISAs:
   and the destination a vector, and having no predicate set or having
   multiple bits set.
 * VSELECT is provided by setting up (at least one of) the sources as a
-  vector, using a single bit in olthe predicate, and the destination as
+  vector, using a single bit in the predicate, and the destination as
   a scalar.
 
 All of this capability and coverage without even adding one single actual
@@ -297,7 +330,7 @@ into account:
 
     function op_add(RT, RA, RB) # add not VADD!
       int id=0, irs1=0, irs2=0;
-      predval = get_pred_val(FALSE, rd);
+      predval = get_pred_val(FALSE, RT); # dest pred
       for i = 0 to VL-1:
         if (predval & 1<<i) # predication bit test
            ireg[RT+id] <= ireg[RA+irs1] + ireg[RB+irs2];
@@ -327,7 +360,7 @@ example) 64 bit operations (only).  The override widths are 8, 16 and
 32 for integer, and FP16 and FP32 for IEEE754 (with BF16 to be added in
 the future).
 
-This presents a particularly intriguing conundrum given that the OpenPOWER
+This presents a particularly intriguing conundrum given that the Power
 Scalar ISA was never designed with for example 8 bit operations in mind,
 let alone Vectors of 8 bit.
 
@@ -349,7 +382,7 @@ structure, where all types uint16_t etc. are in little-endian order:
         uint8_t  b[0]; // array of type uint8_t
         uint16_t s[0]; // array of LE ordered uint16_t
         uint32_t i[0];
-        uint64_t l[0]; // default OpenPOWER ISA uses this
+        uint64_t l[0]; // default Power ISA uses this
     } reg_t;
 
     reg_t int_regfile[128]; // SV extends to 128 regs
@@ -358,6 +391,8 @@ This means that Vector elements start from locations specified by 64 bit
 "register" but that from that location onwards the elements *overlap
 subsequent registers*.
 
+![image](/openpower/svp64-primer/img/svp64_regs.svg){ width=40% height=40% }
+
 Here is another way to view the same concept, bearing in mind that it
 is assumed a LE memory order:
 
@@ -456,7 +491,7 @@ element width.  Our first simple loop thus becomes:
        src1 = get_polymorphed_reg(RA, srcwid, i)
        src2 = get_polymorphed_reg(RB, srcwid, i)
        result = src1 + src2 # actual add here
-       set_polymorphed_reg(rd, destwid, i, result)
+       set_polymorphed_reg(RT, destwid, i, result)
 
 With this loop, if elwidth=16 and VL=3 the first 48 bits of the target
 register will contain three 16 bit addition results, and the upper 16
@@ -464,10 +499,9 @@ bits will be *unaltered*.
 
 Note that things such as zero/sign-extension (and predication) have
 been left out to illustrate the elwidth concept. Also note that it turns
-out to be important to perform the operation at the maximum bitwidth -
-`max(srcwid, destwid)` - such that any truncation, rounding errors or
+out to be important to perform the operation internally at effectively an *infinite* bitwidth such that any truncation, rounding errors or
 other artefacts may all be ironed out.  This turns out to be important
-when applying Saturation for Audio DSP workloads.
+when applying Saturation for Audio DSP workloads, particularly for multiply and IEEE754 FP rounding.  By "infinite" this is conceptual only: in reality, the application of the different truncations and width-extensions set a fixed deterministic practical limit on the internal precision needed, on a per-operation basis.
 
 Other than that, element width overrides, which can be applied to *either*
 source or destination or both, are pretty straightforward, conceptually.
@@ -482,7 +516,7 @@ of the destination.  The only situation where a full overwrite occurs
 is on "default" behaviour.  This is extremely important to consider the
 register file as a byte-level store, not a 64-bit-level store.
 
-## Why LE regfile?
+## Why LE regfile?
 
 The concept of having a regfile where the byte ordering of the underlying
 SRAM seems utter nonsense.  Surely, a hardware implementation gets to
@@ -499,22 +533,32 @@ Consequently it becomes critically important to decide a byte-order.
 That decision was - arbitrarily - LE mode.  Actually it wasn't arbitrary
 at all: it was such hell to implement BE supported interpretations of CRs
 and LD/ST in LibreSOC, based on a terse spec that provides insufficient
-clarity and assumes significant working knowledge of OpenPOWER, with
+clarity and assumes significant working knowledge of the Power ISA, with
 arbitrary insertions of 7-index here and 3-bitindex there, the decision
 to pick LE was extremely easy.
 
-Without such a decision, if two words are packed as elements into a
-64 bit register, what does this mean? Should they be inverted so that
-the lower indexed element goes into the HI or the LO word? should the
-8 bytes of each register be inverted? Should the bytes in each element
-be inverted? These arw all equally valid and legitimate interpretations
-of what constitutes "BE" and they all cause merry mayhem.
+Without such a decision, if two words are packed as elements into a 64
+bit register, what does this mean? Should they be inverted so that the
+lower indexed element goes into the HI or the LO word? should the 8
+bytes of each register be inverted? Should the bytes in each element
+be inverted? Should the element indexing loop order be broken onto
+discontiguous chunks such as 32107654 rather than 01234567, and if so
+at what granularity of discontinuity? These are all equally valid and
+legitimate interpretations of what constitutes "BE" and they all cause
+merry mayhem.
 
 The decision was therefore made: the c typedef union is the canonical
 definition, and its members are defined as being in LE order. From there,
 implementations may choose whatever internal HDL wire order they like
 as long as the results produced conform to the elwidth pseudocode.
 
+*Note: it turns out that both x86 SIMD and NEON SIMD follow this convention, namely that both are implicitly LE, even though their ISA Manuals may not explicitly spell this out*
+
+* <https://developer.arm.com/documentation/ddi0406/c/Application-Level-Architecture/Application-Level-Memory-Model/Endian-support/Endianness-in-Advanced-SIMD?lang=en>
+* <https://stackoverflow.com/questions/24045102/how-does-endianness-work-with-simd-registers>
+* <https://llvm.org/docs/BigEndianNEON.html>
+
+
 ## Source and Destination overrides
 
 A minor fly in the ointment: what happens if the source and destination
@@ -522,18 +566,23 @@ are over-ridden to different widths?  For example, FP16 arithmetic is
 not accurate enough and may introduce rounding errors when up-converted
 to FP32 output.  The rule is therefore set:
 
-    The operation MUST take place at the larger of the two widths.
+    The operation MUST take place effectively at infinite precision:
+    actual precision determined by the operation and the operand widths
 
 In pseudocode this is:
 
     for i = 0 to VL-1:
        src1 = get_polymorphed_reg(RA, srcwid, i)
        src2 = get_polymorphed_reg(RB, srcwid, i)
-       opwidth = max(srcwid, destwid)
+       opwidth = max(srcwid, destwid) # usually
        result = op_add(src1, src2, opwidth) # at max width
        set_polymorphed_reg(rd, destwid, i, result)
 
-It will turn out that under some conditions the combination of the
+In reality the source and destination widths determine the actual required
+precision in a given ALU.  The reason for setting "effectively" infinite precision
+is illustrated for example by Saturated-multiply, where if the internal precision was insufficient it would not be possible to correctly determine the maximum clip range had been exceeded.
+
+Thus it will turn out that under some conditions the combination of the
 extension of the source registers followed by truncation of the result
 gets rid of bits that didn't matter, and the operation might as well have
 taken place at the narrower width and could save resources that way.
@@ -560,8 +609,8 @@ be first read at their overridden bitwidth and *then* sign-extended:
        src2 = get_polymorphed_reg(RB, srcwid, i)
        opwidth = max(srcwid, destwid)
        # srces known to be less than result width
-       src1 = sign_extend(src1, srcwid, destwid)
-       src2 = sign_extend(src2, srcwid, destwid)
+       src1 = sign_extend(src1, srcwid, opwidth)
+       src2 = sign_extend(src2, srcwid, opwidth)
        result = op_signed(src1, src2, opwidth) # at max width
        set_polymorphed_reg(rd, destwid, i, result)
 
@@ -583,11 +632,11 @@ truncated.  Only then can the arithmetic saturation condition be detected:
        # unsigned add
        result = op_add(src1, src2, opwidth) # at max width
        # now saturate (unsigned)
-       sat = max(result, (1<<destwid)-1)
+       sat = min(result, (1<<destwid)-1)
        set_polymorphed_reg(rd, destwid, i, sat)
        # set sat overflow
        if Rc=1:
-          CR.ov = (sat != result)
+          CR[i].ov = (sat != result)
 
 So the actual computation took place at the larger width, but was
 post-analysed as an unsigned operation.  If however "signed" saturation
@@ -615,9 +664,9 @@ truncating down to 8 bit for example.
        opwidth = max(srcwid, destwid)
        # logical op, signed has no meaning
        result = op_xor(src1, src2, opwidth)
-       # now saturate (unsigned)
-       sat = max(result, (1<<destwid-1)-1)
-       sat = min(result, -(1<<destwid-1))
+       # now saturate (signed)
+       sat = min(result, (1<<destwid-1)-1)
+       sat = max(result, -(1<<destwid-1))
        set_polymorphed_reg(rd, destwid, i, sat)
 
 Overall here the rule is: apply common sense then document the behaviour
@@ -673,7 +722,7 @@ reordering of XYZW, ARGB etc. and access of sub-portions of the same in
 arbitrary order *without* requiring timeconsuming scalar mv instructions
 (scalar due to the convoluted offsets).
 
-Swizzling does not just do permutations: it allows multiple copying of
+Swizzling does not just do permutations: it allows arbitrary selection and multiple copying of
 vec2/3/4 elements, such as XXXZ as the source operand, which will take
 3 copies of the vec4 first element (vec4[0]), placing them at positions vec4[0],
 vec4[1] and vec4[2], whilst the "Z" element (vec4[2]) was copied into vec4[3].
@@ -690,15 +739,15 @@ those:
     swizzle = get_swizzle_immed() # 12 bits
     for (s = 0; s < SUBVL; s++)
         remap = (swizzle >> 3*s) & 0b111
-        if remap < 4:
-           sm = id*SUBVL + remap
+        if remap == 0b000: continue            # skip
+        if remap == 0b001: break               # end marker
+        if remap == 0b010: ireg[rd+s] <= 0.0   # constant 0
+        elif remap == 0b011: ireg[rd+s] <= 1.0 # constant 1
+        else:                                  # XYZW
+           sm = id*SUBVL + (remap-4)
            ireg[rd+s] <= ireg[RA+sm]
-        elif remap == 4:
-              ireg[rd+s] <= 0.0
-        elif remap == 5:
-              ireg[rd+s] <= 1.0
 
-Note that a value of 6 (and 7) will leave the target subvector element
+Note that a value of 0b000 will leave the target subvector element
 untouched. This is equivalent to a predicate mask which is built-in,
 in immediate form, into the [[sv/mv.swizzle]] operation.  mv.swizzle is
 rare in that it is one of the few instructions needed to be added that
@@ -707,7 +756,8 @@ Compute workloads it is unusual: it is only because SV is targetted at
 3D and Video that it is being considered.
 
 Some 3D GPU ISAs also allow for two-operand subvector swizzles.  These are
-sufficiently unusual, and the immediate opcode space required so large,
+sufficiently unusual, and the immediate opcode space required so large
+(12 bits per vec4 source),
 that the tradeoff balance was decided in SV to only add mv.swizzle.
 
 # Twin Predication
@@ -739,7 +789,7 @@ are termed single-source, single-destination.
 LDST Address-generation, or AGEN, is a special case of single source,
 because elwidth overriding does not make sense to apply to the computation
 of the 64 bit address itself, but it *does* make sense to apply elwidth
-overrides to the data being accessed *at* that address.
+overrides to the data being accessed *at* that memory address.
 
 It also turns out that by using a single bit set in the source or
 destination, *all* the sequential ordered standard patterns of Vector
@@ -749,55 +799,12 @@ The only one missing from the list here, because it is non-sequential,
 is VGATHER (and VSCATTER): moving registers by specifying a vector of
 register indices (`regs[rd] = regs[regs[rs]]` in a loop).  This one is
 tricky because it typically does not exist in standard scalar ISAs.
-If it did it would be called [[sv/mv.x]]. Once Vectorised, it's a
+If it did it would be called [[sv/mv.x]]. Once Vectorized, it's a
 VGATHER/VSCATTER.
 
-# CR predicate result analysis
-
-OpenPOWER has Condition Registers.  These store an analysis of the result
-of an operation to test it for being greater, less than or equal to zero.
-What if a test could be done, similar to branch BO testing, which hooked
-into the predication system?
-
-    for i in range(VL):
-        # predication test, skip all masked out elements.
-        if predicate_masked_out(i): continue # skip
-        result = op(iregs[RA+i], iregs[RB+i])
-        CRnew = analyse(result) # calculates eq/lt/gt
-        # Rc=1 always stores the CR
-        if RC1 or Rc=1: crregs[offs+i] = CRnew
-        if RC1: continue # RC1 mode skips result store
-        # now test CR, similar to branch
-        if CRnew[BO[0:1]] == BO[2]:
-            # result optionally stored but CR always is
-            iregs[RT+i] = result
-
-Note that whilst the Vector of CRs is always written to the CR regfile,
-only those result elements that pass the BO test get written to the
-integer regfile (when RC1 mode is not set).  In RC1 mode the CR is always
-stored, but the result never is. This effectively turns every arithmetic
-operation into a type of `cmp` instruction.
-
-Here for example if FP overflow occurred, and the CR testing was carried
-out for that, all valid results would be stored but invalid ones would
-not, but in addition the Vector of CRs would contain the indicators of
-which ones failed.  With the invalid results being simply not written
-this could save resources (save on register file writes).
-
-Also expected is, due to the fact that the predicate mask is effectively
-ANDed with the post-result analysis as a secondary type of predication,
-that there would be savings to be had in some types of operations where
-the post-result analysis, if not included in SV, would need a second
-predicate calculation followed by a predicate mask AND operation.
-
-Note, hilariously, that Vectorised Condition Register Operations (crand,
-cror) may also have post-result analysis applied to them.  With Vectors
-of CRs being utilised *for* predication, possibilities for compact and
-elegant code begin to emerge from this innocuous-looking addition to SV.
-
 # Exception-based Fail-on-first
 
-One of the major issues with Vectorised LD/ST operations is when a
+One of the major issues with Vectorized LD/ST operations is when a
 batch of LDs cross a page-fault boundary.  With considerable resources
 being taken up with in-flight data, a large Vector LD being cancelled
 or unable to roll back is either a detriment to performance or can cause
@@ -847,9 +854,7 @@ Note: see <https://bugs.libre-soc.org/show_bug.cgi?id=561>
 
 # Data-dependent fail-first
 
-This is a minor variant on the CR-based predicate-result mode.  Where
-pred-result continues with independent element testing (any of which may
-be parallelised), data-dependent fail-first *stops* at the first failure:
+Data-dependent fail-first *stops* at the first failure:
 
     if Rc=0: BO = inv<<2 | 0b00 # test CR.eq bit z/nz
     for i in range(VL):
@@ -859,7 +864,7 @@ be parallelised), data-dependent fail-first *stops* at the first failure:
         CRnew = analyse(result) # calculates eq/lt/gt
         # now test CR, similar to branch
         if CRnew[BO[0:1]] != BO[2]:
-            VL = i # truncate: only successes allowed
+            VL = i+VLi # truncate: only successes allowed
             break
         # test passed: store result (and CR?)
         if not RC1: iregs[RT+i] = result
@@ -873,9 +878,45 @@ that tested for the possibility of that failure, in advance of doing
 the actual calculation.
 
 The only minor downside here though is the change to VL, which in some
-implementations may cause pipeline stalls.  This was one of the reasons
-why CR-based pred-result analysis was added, because that at least is
-entirely paralleliseable.
+implementations may cause pipeline stalls.
+
+# Vertical-First Mode
+
+![image](/openpower/sv/sv_horizontal_vs_vertical.svg){ width=40% height=40% }
+
+This is a relatively new addition to SVP64 under development as of
+July 2021.  Where Horizontal-First is the standard Cray-style for-loop,
+Vertical-First typically executes just the **one** scalar element
+in each Vectorized operation. That element is selected by srcstep
+and dststep *neither of which are changed as a side-effect of execution*.
+Illustrating this in pseodocode, with a branch/loop.
+To create loops, a new instruction `svstep` must be called,
+explicitly, with Rc=1:
+
+```
+loop:
+  sv.addi r0.v, r8.v, 5 # GPR(0+dststep) = GPR(8+srcstep) + 5
+  sv.addi r0.v, r8, 5   # GPR(0+dststep) = GPR(8        ) + 5
+  sv.addi r0, r8.v, 5   # GPR(0        ) = GPR(8+srcstep) + 5
+  svstep.               # srcstep++, dststep++, CR0.eq = srcstep==VL
+  beq loop
+```
+
+Three examples are illustrated of different types of Scalar-Vector
+operations. Note that in its simplest form  **only one** element is
+executed per instruction **not** multiple elements per instruction.
+(The more advanced version of Vertical-First mode may execute multiple
+elements per instruction, however the number executed **must** remain
+a fixed quantity.)
+
+Now that such explicit loops can increment inexorably towards VL,
+of course we now need a way to test if srcstep or dststep have reached
+VL. This is achieved in one of two ways: [[sv/svstep]] has an Rc=1 mode
+where CR0 will be updated if VL is reached. A standard v3.0B Branch
+Conditional may rely on that.  Alternatively, the number of elements
+may be transferred into CTR, as is standard practice in Power ISA.
+Here, SVP64 [[sv/branches]] have a mode which allows CTR to be decremented
+by the number of vertical elements executed.
 
 # Instruction format
 
@@ -897,26 +938,26 @@ The other key question is of course: what's the actual instruction format,
 and what's in it? Bearing in mind that this requires OPF review, the
 current draft is at the [[sv/svp64]] page, and includes space for all the
 different modes, the predicates, element width overrides, SUBVL and the
-register extensions, in 24 bits.  This just about fits into an OpenPOWER
+register extensions, in 24 bits.  This just about fits into a Power
 v3.1B 64 bit Prefix by borrowing some of the Reserved Encoding space.
-The v3.1B suffix - containing as it does a 32 bit OpenPOWER instruction -
+The v3.1B suffix - containing as it does a 32 bi Power instruction -
 aligns perfectly with SV.
 
 Further reading is at the main [[SV|sv]] page.
 
 # Conclusion
 
-Starting from a scalar ISA - OpenPOWER v3.0B - it was shown above that,
+Starting from a scalar ISA - Power v3.0B - it was shown above that,
 with conceptual sub-loops, a Scalar ISA can be turned into a Vector one,
 by embedding Scalar instructions - unmodified - into a Vector "context"
 using "Prefixing".  With careful thought, this technique reaches 90%
 par with good Vector ISAs, increasing to 95% with the addition of a
-mere handful of additional context-vectoriseable scalar instructions
+mere handful of additional context-vectorizeable scalar instructions
 ([[sv/mv.x]] amongst them).
 
 What is particularly cool about the SV concept is that custom extensions
 and research need not be concerned about inventing new Vector instructions
 and how to get them to interact with the Scalar ISA: they are effectively
 one and the same.  Any new instruction added at the Scalar level is
-inherently and automatically Vectorised, following some simple rules.
+inherently and automatically Vectorized, following some simple rules.