Some CPUs have a flags register (ARM,x86,...), others don't (MIPS,...). What's the advantage of having a CMP instruction to update the flags register followed by a branch instruction instead of using a zero register and conditional branches to check for sign, overflow etc?
In modern micro-architectures with register renaming the implementation cost for flags or not flags is pretty similar. The main difference I can think of is that some flags indicate the characteristics of a value (Is the value negative? Is the value zero? Does the value have even or odd parity?), while some represent an event that occurred during a previous operation (did the add instruction have a carry out or an overflow?) This led to a less-than-ideal situation on the MIPS when you wanted to simulate 64-bit addition on the 32-bit architecture (or 128-bit addition on the 64-bit architecture.) On most architectures with a carry flag there is a special
add-with-carry instruction, that includes the carry flag from the previous add instruction. This makes simulating multi-precision arithmetic relatively inexpensive on many architectures with flags registers.
On the flip side, testing an N-bit register for zero or not-zero is actually surprisingly expensive. To test an N-bit register for zero you need to perform an N-bit NOR operation, which requires $O(\log N)$ levels of logic to calculate. On architectures with flags registers the extra logic for the zero/not-zero calculation at the end of the ALU stage can cause the clock to run slower (or force the ALU to have two cycle operations.) For this reason, I think, some architectures, like SPARC had two versions of each arithmetic operation, one that set flags and one that didn't.
But MIPS doesn't save anything here. They just moved the problem somewhere else. On MIPS there is a
branch-on-equal instruction. This means that the branch instruction must actually have an ALU stage (including something like a bitwise
xor operation followed by a
nor to reduce down to the single equal/not-equal bit) before determining which way the branch goes.
The DEC Alpha architecture tried to split the difference by using a trick. DEC Alpha had no flags registers, but also didn't have a
branch-on-equal instruction. Instead the branch instructions all look at the state of a single general purpose register. There is
branch-on-less-than-zero, etc. The trick is that you can give every general purpose register an extra 65th bit that tells you whether the other 64 bits are all zero or not. This makes it more like having a flags register: all the branch instructions look at a single bit (that is already calculated) to make their decision, but now you are back to figuring out how to calculate that extra zero indicator bit during a normal ALU cycle. (And you still can't do multi-precision arithmetic just by looking at the carry flag from the previous operation.)
1 From an ISA perspective
Having test instructions which set only the flags is just a way to reduce the register pressure in register starved architectures. If you have enough registers, just modify one of them and ignore the result. The trick of having a register 0 with input value 0 is just an encoding trick convenient when you have enough registers that fixing one of them to 0 is better than increasing the number of instructions. It is then convenient to use it as a target as well (it reduces the number of false dependencies).
Encoding again. If you encode the condition in jumps, you'll have jumps with 3 operands (the two to be compared and the jump target), two of which you'd like to be immediate values, one you'd like to be as big as possible (jumps have often their own encoding format so that the target can use as many bits as possible). Or you drop possibilities.
Using flags gives you more opportunities to set them. It is not just the comparison operations which can set the flags, but whatever you want. (With the caveat that the more operations you have which set flags, the more careful you have to be to ensure that the last operation which set the flags is the one you want). If you have flags, you are able to test the number of conditions (often 16) multiplied by the number of instructions which are able to set the flags (If you are not using flags, you end up with about as many conditional jumps as you have things to test or there are things you don't allow to test as easily (carry or overflow for instance).
2 From an implementer perspective
Testing flags is easy and can be done quickly. The more complex your test is, the more effect it will have on the cycle time (or the pipeline structure if you are pipelined). That's especially true for simpler implementations, when you get to a high end processor using all the tricks of the book, the effect is pretty minimal.
Having flags means that a lot of instructions have multiple results (the natural result and each of the modified flags). And from a micro-architecture POV, multiple results are bad (you have to keep track of their association). When you have just one set of flags, that introduce dependencies (unneeded if the flag is not then used) you have to handle way or another. Again that's especially true for simpler implementations, when you get to an high end processor using all the tricks of the book, the additional difficulties are dwarfed by the rest of the processor.
On a 32-bit machine, an "add-with-carry" instruction used as part of a multi-precision addition sequence needs to accept 65 bits worth of operands and compute a 33 bits sum. The source-register specifications will identify where 64 operand bits should come from, and the destination-register specification will say where the lower 32 bits of the result should go, but what to do with the "add one extra" operand or the upper bit of the result? Being allowed to specify as part of the instruction where the extra operand should come from and where the extra result bit should go would be moderately useful, but it would generally not be so useful as to justify an extra field in the opcode. Having a fixed "location" to handle the carry flag can be little bit awkward from an instruction-scheduling perspective, but it's a lot cheaper than having additional operand fields, and is much more practical than most other ways of trying to handle multi-precision arithmetic.
If one were trying to design an instruction set to allow multi-precision arithmetic but each instruction were limited to two 32-bit operands and one 32-bit destination operand, one could implement a 64-bit "add" in four instructions: "set r5 to 1 if r0+r2 would carry or zero otherwise; compute r4=r1+r3; compute r5=r4+r5; compute r4=r0+r2", but going beyond that would require three instructions for each additional word. Having a carry flag available as a supplemental source and destination reduces the cost to one instruction per word.
Note, btw, that having an instruction bit control whether the instruction updates the flag register may facilitate out-of-order execution, since instructions which use or modify the flag bits must maintain their sequence relative to each other, but instructions which do neither may be freely rearranged. Given the sequence:
ldr r0,[r1] add r0,r0,r2 eors r4,r5,r6
an execution unit could fairly easily recognize that the third instruction could execute without having to wait for data to be read from
[r1], but if the second instruction had been
adds r0,r0,r2 that would only be possible if the execution unit could ensure that by the time anything tried to use the flags, the zero flag would hold the value established in the third instruction but the carry flag would hold the value in the second.