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.