Making the parity flag not perfectly compatible was a wise choice.

In legacy programs the parity flag was seldom tested and practically never after the instructions where in Z80 it behaved differently.

This allowed the repurposing of the parity flag as an overflow flag, which was an extremely useful extension of Z80.

The instruction sets of Datapoint 2200, Intel 8008 and Intel 8080 share with that of RISC-V the distinction of belonging to the extremely few ISAs where overflow is not detected by hardware.

Datapoint 2200 and its first successors had the excuse that they were extremely simple and cheap designs, which were never intended for general-purpose computing.

RISC-V has absolutely no excuse. The lack of overflow detection is its greatest mistake.

RISC-V has no overflow flag? That's fascinating. I'll have to dig into that!

It doesn't have a carry flag either. Multiprecision integer operations absolutely suck in RISC-V, you need three operations (SLT and two adds, with an awful dependency chain too) to do an add with carry, and op fusion can only do so much. At least add an instruction that computes the carry out, like "Rd = Cout(Rs1 + Rs2)" and the similar one for overflow...

https://gmplib.org/list-archives/gmp-devel/2021-September/00...

taking architecture that explicitly removed carry flag for performance and then trying to to emulate carry flag anyway was stupid in 2021 and is still stupid today

even on x64 you get better performance when you don't use carry flag and just use limbs https://www.chosenplaintext.ca/articles/radix-2-51-trick.htm... - risc-v is even more so

and experimentally gmp bench results show performance in line with arm https://old.reddit.com/r/RISCV/comments/1jsnbdr/gnu_mp_bignu... so panic was for nothing

Limbs work well only when the operations with them are implemented with vector or matrix instructions.

Using limbs is a workaround for the fact that most vector instruction sets also do not implement carry flags (an exception was the discontinued Intel Larrabee). Moreover, usually the fastest integer performance is obtained when using the floating-point multipliers, which limits the size of the limbs to 52 bits.

Despite the fact that using limbs is more cumbersome, the much greater number of arithmetic execution units available for vector instructions compensates that and ensures a greater performance.

Saying that RISC-V "removed carry flag for performance" is a fantasy. The saved hardware is completely negligible and significant performance is lost, not gained.

Moreover, the carry flag is required not only for multi-word operations, but also for detecting unsigned overflow. For this purpose, no limbs can save you.

Regarding the benchmarks linked by you, they show a really pathetic performance. They do not seem so bad as they really are only because they are not compared with x86 CPUs or with any ARM CPU more recent than the 10-year old and obsolete Cortex-A72, which is many times slower than modern ARM CPU cores. Cortex-A53 is an even worse comparison point, being a little core that is much older than a decade (from 2012).

> Regarding the benchmarks linked by you, they do not seem so bad as they really are only because they are not compared with x86 CPUs or with any ARM CPU more recent than the 10-year old and obsolete Cortex-A72, which is many times slower than modern ARM CPU cores

"why do you compare 15y.o. chess GM against 15y.o. Carlsen instead of world champion he is today? if you do you'll see how pathetic performance actually is"

everything gets a weight category. immature architecture gets benched against immature state of architecture. narrow OoO gets compared against narrow OoO

check in 4 more years for results closer to frontline - but where we are, theoretical worries did not come to pass

Unfortunately the criticism from that link is absolutely correct.

Moreover, at that link it is shown only the ugly way in which RISC-V does multi-word addition.

Checking the standard arithmetic operations for overflow is much more horrible and inefficient than that, and checking for overflows must be done in any program that claims to follow safe practices.

Unlike in software, computing the carry and overflow flags in hardware is absolutely trivial and the extra gates needed for this add a cost that is below a rounding error in the total chip cost.

With Z80, it was much easier to compute arithmetic expressions than it is with RISC-V, especially when working with big numbers and especially when mandating correct computations, with no undetected errors.

risc-v has no flags period

modern out-of-order execution gets hindered by such non-parallel cpu state, so it wasn't included

In modern OoOE CPUs the flag register is renamed, like all other architectural registers, so it does not hinder in any way the parallel execution.

OoOE CPUs need hundreds of registers in order to not hinder the parallel execution, so even the 32 general-purpose registers are not enough, so they must be renamed. Once register renaming is implemented, it does not matter any more if there is a single architectural flags register.

When a cheaper solution than register renaming is desired, the correct solution was that of IBM POWER (1990), where there are 8 flag registers (with 4 flags in each, so the total size is 32 bits). That allows the parallel execution of up to 8 instructions per cycle, even without register renaming.

The superior IBM POWER ISA was implemented even in microcontrollers that were smaller and cheaper than the current RISC-V cores.

An alternative solution to having a flags register is to implement instructions with 3 input operands and 2 output operands. In this case, for the instructions that generate flags, they are stored in the second output registers.

In reality, all the 4 basic arithmetic operations with integers have 3 inputs and 2 outputs, when defined correctly. They are redefined to have 2 inputs and 1 output only to allow cheaper hardware, and in this case the additional input and additional output may be enabled only for some of the instructions, where they are stored in special registers, like the flag register, or in some ISAs in special extension registers used for multiplication/division/rotation/shifting.

> That allows the parallel execution of up to 8 instructions per cycle, even without register renaming

Though, PowerPC implementations (like the PowerPC 750) do have renaming of condition registers, which allows them to speculatively execute CR write instructions (though the PowerPC 750 can't speculatively read a CR, it can only speculate one branch at a time).

But I suspect other justification for renaming CRs, is that register renaming is actually how the PowerPC designs from that era handled hazard detection and result forwarding (and maybe why such designs only a few renaming registers, six GPRs and six FPRs on the 750)

What really hindered flags and OOOE were instructions that only partially updated flag bits. For example if increment sets the overflow and zero flags but doesn't change the negative flag, it has a dependency on the old value of the flags register and a chain of increments must be serialised. Not fundamentally, but yes if you treat flags as a single register.

If you fix that by saying that every instruction sets the whole flags register, then it only makes sense to read the flags register in the very next instruction after setting it, and you may as well combine those into one single instruction and then you don't need the register at all.

Exception is ADC chains which both read and set the register. I think RISC-V doesn't support them?

What you say about partially updated flag registers is right.

Because of this, most modern ISAs take care so that the flags register is always updated completely.

In legacy ISAs, like in x86-64 where the carry flag is updated or not updated separately from the other flags, it is handled by the CPU as a distinct register, so the carry flag is renamed independently of the other flags.

Moreover, in x86-64 the overflow flag can be used as second carry flag in some instructions. Having 2 carry flags permits the elimination of some functional dependencies between instructions that would not be eliminated by the renaming of a single carry flag (renaming solves only resource dependencies, not data dependencies).

IMO, the "out-of-order performance/complexity" excuse is complete bullshit.

Out-of-order pipelines actually have a really elegant way of handling flags, they just store a copy of the flags register on every ROB entry. During renaming, instructions that consume flags just gain an extra implicit input pointing to the ROB that will contain the correct flags.

Such pipelines already need deal with so much serialised state. The flags are actually one of the easier things to deal with, they basically get support for free when they implement register renaming.

It's actually the "classic RISC" style in-order pipelines where flags are annoying to deal with. They pipelines don't need to do any register renaming, so they don't have a free solution to handle flags. And the RISC-V ISA is very much optimised for these simpler pipelines.

Instructions that update some flags and not others have to be serialised in this design.

True, and I wouldn't be surprised if modern x86 implementations need to do something a bit smarter.

But if you are designing a new ISA for the modern era (the purported selling point of RISC-V), you just don't implement any such instructions. Only implement clean instructions that update all the flags or none. ARM and PowerPC already did this 35-40 years ago.

In ISAs where this problem exists, i.e. in x86-64 where the carry flag is updated or not updated regardless of what happens with the other flags, the flags register is split into 2 separate registers, which are renamed independently.

The 2 parts of the flags register are reunited only for the purpose of saving or restoring from the main memory. (Actually the x86-64 flags register has 3 independent parts, the carry flag, the other status flags, and a set of configuration flags, which are not modified by most instructions.)

> Out-of-order pipelines actually have a really elegant way of handling flags, they just store a copy of the flags register on every ROB entry.

And yet, flag writing instructions are usually half the throughput on regular ALU instructions, on modern wide ooo designs. [1, 2]

But I generally agree, there is a good way of handling them, it's hust unclear to me how expensive it is. It can't be that expensive, but apparently it is expensive enough, to not put a mask register write port on all integer execution units.

[1] https://dougallj.github.io/applecpu/firestorm-int.html (see how ADD is 6-issue and ADDS 3-issue)

[2] https://developer.arm.com/documentation/111027/4-0/ (again 8-issue ADD, but 4-issue ADDS)

I wonder if the issue is actually the cost of calculating the flags.

N and C are basically free (copy of bit 63, and it's carry out), V is an extra gate or two, but Z requires a full 64-bit wide NOR gate.

Or it might be about making the register file holding the flags smaller (only 3R3W, instead of 6R6W), along with simplifying the associated bypass network and routing (the first three ALUs are also the only units that can consume flags).

When I say ooo cpus get flag handling for "essentially free", I'm only actually talking about the complexity of tracking "implicit state", and that it can be done without extra latency. You still need to spend transistors to support renaming flags, and to actually calculate and storing the flags.

I can see an argument for an ISA that got rid of the N and Z flags, but kept C/V (potentially merged into a single flag). You can trivially reconstruct N/Z from the result register, but not C/V.

It's obviously done like that in RISC-V to simplify it.

Zilog Z80 was orders of magnitude simpler, but its designers never did something so foolish.

At that time, many people still used assembly language and they would have never accepted to write arithmetic expressions in the contorted way forced by RISC-V.

Now compilers hide this aspect of RISC-V so most are not aware of this. Moreover, most people are still using programs compiled from C/C++ with unsafe compilation options, or even from Rust, where by default integer overflow is not checked in programs compiled for "release", so they do not see how much performance RISC-V loses when executing programs that are designed to produce correct results.