7 ms·
Macro-op fusion is something that is really quite painful to implement in the front-end. Essentially once you have both macro-op fusion and potentially multiple
by jleahy 6y ago
Macro-op fusion is something that is really quite painful to implement in the front-end. Essentially once you have both macro-op fusion and potentially multiple micro-ops for a single instruction you have fully variable length instructions. Potentially it becomes worse than x86, where macro-op fusion has no need to be pervasive, you just have to cover very few simple cases (eg. cmp+jmp). For RISC-V even trivial things like 'load a 32 bit constant' need macro-op fusion to be competitive with x86.
An 8 wide front-end for ARM64 needs to run a decoder every 4 bytes (so 8 in total), then combine the results. It's an entirely trivial affair.
For RISC-V, without any macro-op fusion, you first need a unit every 2-bytes that determines the instruction length using the first 2 bits (16 of these), then you need a very wide mux to expand out any compressed instructions (the last one can come from 7 or 8 different offsets) so you have 8x 32b instructions, then you need your 8 decoders. It's looking much more unpleasant because of that very wide muxing (8x 8:1 32-bit muxes is not a pleasant sight). Alternatively you could do it the brute force way and have 16 decoders followed by a 16->8 reduction layer (ignoring the decode results that were at bad offsets). The designer is going to have a hard time choosing between those two bad options (personally I would go with the latter I think).
Now let's throw in macro-op fusion, suddenly rather than have two possible lengths (16b or 32b) you can have 16b+16b, 32b+32b, 16b+32b, 32b+16b. Complexity just explodes. If anybody really tried to implement a 8 wide RISC-V front-end with pervasive macro-op fusion then it would be a truly nasty thing.
- jabl 6y agoWhat do you think of the RISC-V argument that a flags register is hard to make fast for a superscalar ooo core, and thus the RISC-V choice of two-register branching instructions is better than the traditional choice of a jmp instruction that reads the flags? This also avoids the need to macro fuse cmp+jmp, but OTOH eats quite severely into the destination offset.
- jleahy 6y agoI think it’s smart, most branches are short and almost all important branches are short (eg. loops). The flags register adds an extra implicit input/output everywhere, and renaming is expensive enough without that. However given they are already paying the cost of a variable length instruction set (for compression) I think a wide set of reg/reg, reg/imm8, reg/imm16, reg/imm32 compare+jumps would have been even better. Right now compare+jump against an imm32 is 3 instructions (4 if it’s long) vs 2 for x86, that number could have been 1 (given variable length). Personally I think only two good choices exist for instruction sets, fully fixed size at 32b or variable with 2b control (max 2 inputs and 1 output reg!) followed by 0b, 2b, 4b, etc data (and thus very powerful instructions like the aforementioned). Anything else seems to be off the pareto curve. Goodbye cmov.
- Taniwha 6y agoyou can also fold a cjmp+4 with a subsequent jump, though that's tough to do before branch prediction
- souprock 6y agoFlags don't have to add an extra implicit input/output everywhere. Both ARM and PowerPC avoid updating the flags unless explicitly requested. Besides fixed-size instructions and the traditional variable-size instructions, one can do variable-size instructions in bundles. An example would be 25-bit and 50-bit instructions packed into 128-bit bundles, with the remaining 3 bits used to specify all the sizes. (eight patterns: nnnnn, nww, wnw, wwn, nnnw, wwnw, wnww, wnnn) Extending that out to a typical cache line of 512 bits might be better. Another option is to use 1 of every 16 bits to indicate where instructions start. Where RISC-V got wasteful was the registers. Compilers are seldom able to use anywhere near 32 registers. On normal code, normal compilers seem to need about 8 to 10 registers free after deducting the ones reserved by the ABI. The ABI might need 3 to 5 registers. (stack, PLT, GOT, TLS, etc.) That means that roughly 11 to 15 registers are needed. Clearly, 4 bits (16 registers) is enough. Shoving some of those ABI-reserved registers out of the general-purpose set wouldn't be a bad idea; most of those are just used for addressing.
- jabl 6y ago> Flags don't have to add an extra implicit input/output everywhere. Both ARM and PowerPC avoid updating the flags unless explicitly requested. You mean things like having variants of common arithmetic instructions that update or don't update flags? > Besides fixed-size instructions and the traditional variable-size instructions, one can do variable-size instructions in bundles. An example would be 25-bit and 50-bit instructions packed into 128-bit bundles, with the remaining 3 bits used to specify all the sizes. (eight patterns: nnnnn, nww, wnw, wwn, nnnw, wwnw, wnww, wnnn) Extending that out to a typical cache line of 512 bits might be better. Another option is to use 1 of every 16 bits to indicate where instructions start. Yeah, something like that could be nice. Though how would jump instructions be encoded? Bundle + offset within bundle? > Where RISC-V got wasteful was the registers. Compilers are seldom able to use anywhere near 32 registers. On normal code, normal compilers seem to need about 8 to 10 registers free after deducting the ones reserved by the ABI. The ABI might need 3 to 5 registers. (stack, PLT, GOT, TLS, etc.) That means that roughly 11 to 15 registers are needed. Clearly, 4 bits (16 registers) is enough. Shoving some of those ABI-reserved registers out of the general-purpose set wouldn't be a bad idea; most of those are just used for addressing. Nah, I think 32 registers was a good choice. (Relatively) common loop optimizations like unrolling or pipelining need more registers. Also, some of those registers are callee saved and some are call clobbered; by making use of this information the compiler can avoid spilling and reloading of registers around function calls. For x86-64 16 registers is fine, partly because in many cases one can operate directly on memory without needing to explicitly load/store to architectural registers, and partly because the target was and is OoO cores that aren't as dependent on those register-consuming compiler optimizations.
- imtringued 6y agoIt's an obvious mistake but macro op fusion is a "good enough" solution for x86. The type of macro op fusion that RISC-V needs is far more complicated.
- Taniwha 6y agoof course what you do is run the 32-bit decoders and 16-bit decoders in parallel making the same internal instruction bundle formats - then you fuse those bundles. BTW your analysis ignores that in the compressed ISA you can have 4-byte instructions aligned on a 2 byte boundary
- imtringued 6y agoThat kind of destroys most benefits of fixed length instructions. I expected compressed instructions to be aligned on 4 bytes which means your 32bit decoder merely has to have a 2x 16bit fallback but nope, now you gotta decode with a 2byte granularity and join two 2 byte instruction pieces into a 4 byte instruction.
- Taniwha 6y agoYes but it's just a 2:1 mux in the path, it's not like you need twice as many 32bit decoders
- socialdemocrat 6y agoI don't see why macro-op fusion should be harder for RISC-V than x86. x86 ISA was designed without this in mind. The RISC-V designers in contrast seem to deliberately design instructions to make it easier to perform macro-op fusion. You can see specific revisions to the spec being made to make macro-op fusion easier. Actual real world performance tests already suggests that RISC-V tend to require less memory for their programs as well as fewer instructions than x86 and ARM. Hence even without pervasive macro-op fusion they are in a good spot. E.g. for CoreMark used to benchmark embedded processors RISC-V BOOM implementation came ahead ARM-32 Cortex-A9 with about 10% fewer instructions, 10% smaller memory usage and about 10% higher clock frequency (thanks to smaller core).
- rwallace 6y agoHow? I was under the impression that RISC-V was deliberately minimalist in the hope of obtaining smaller cores, and I can see how that would allow higher clock frequency, but I would've expected the trade-off to be longer programs. How did it get shorter programs than ARM? What were the cases where a single RISC-V instruction corresponded to multiple ARM instructions?
- legulere 6y agoThe most common instructions have a compressed 16 bit representation. The compressed instructions are optional though and simple cores just have to support 32 bit instructions.
- socialdemocrat 6y agoA lot of it is about having shorter versions for frequently used instructions. E.g. compare and branch is just one instruction on RISC-V. More complex instructions simply are not used frequently so their ability to reduce code size isn't as big as people think. In addition RISC-V has twice as many registers as ARM-32 Cortex-A9, which the particular CoreMark was comparing against. Also a different ABI conventions reduce the number of instructions spent on saving and restoring registers. E.g. RISC-V has certain registers marked as saved, temporary, arguments etc. Then of course for RISC-V it isn't expensive to support compressed instructions. You use the same decoder not different modes and different decoders like ARM. Just 400 gates to implement, so it is kind of a no-brainer. If comparing SPEC CPU2006 benchmark code using GCC compiler then uncompressed RISC-V vs uncompressed ARM is basically the same. Compressed RISC-V and compressed ARM is basically the same. The latter beats x86 by about 26%. Uncompressed RISC-V is just 8% bigger than x86. So I would say in general RISC-V is paying a very small price for its simplicity. One could say there is a benefit in being able to learn from past mistakes.
- ncmncm 6y agoAgreed: I am ready for a Risc6 fork. Keep it close enough to RISC-V that work proving out designs can be ported more or less mechanically, but take advantage of binary incompatibility to fix the worst design choices. Status bits have turned out not to be such a problem as was once thought. Nowadays they just rename status registers along with the others. Perhaps RISC-V's worst choice was the coarse-grained extensions architecture that locks up the most useful instructions with dozens of marginal ones, making them unusable in portable compiled code. As a result, no RISC-V program can assume POPCOUNT is available, because it is locked away in the huge (still-unratified) "B" extension. The most difficult problem modern CPU designers face is coming up with a way to put another hundred-million transistors to productive use -- i.e., that will make programs actually run faster, but without running too hot. Modern chips are crammed with dodgy, poorly documented gimcracks that make programs run absurdly fast much of the time, but not always. Programmers now have two problems: discovering whether their programs actually are running fast, and discovering why the ones that aren't don't. Usually the only way to know our program is slow is when we see a faster one. It was fast until exactly that moment, then became instantly slow.
- sifar 6y ago>> Perhaps RISC-V's worst choice was the coarse-grained extensions architecture that locks up the most useful instructions with dozens of marginal ones, making them unusable in portable compiled code. Yes, this is the unintended consequence of trying to combine minimalism with too much modularity. Ironically this ends up with a more bloated core. Or perhaps the modularity is probably designed to provide more differentiation. Need POPCOUNT, have to implement the B extension. Need vectors, have to implement scatter gather. Also Can't merge the register file with the FP registers. In reality what will happen is people will choose instructions from these extensions and implement them as custom isa. This will lead to a lot of non-portable code. Until some one combines all these useful ops into another standard extension. Repeat.
- nickik 6y agoThe solution to this is profiles. Profiles are the idea to combine extensions for specific fields. There are also special standard extensions that you can implement that overlap with the standard extensions for some special instruction. If you want to only have core + popcount you can do that in your own core. The code will still be portable to all cores that have B extension. If there is a whole industry that needs that combination, it can just be its own profile. There simply is no perfect solution for every possible solution. If you increase the core, you hurt those that have absolutely no use for some of these things. RISC-V did an amazing overall job to find a combination of ideas to attempted to solve everything for all people but the reality is, no single thing they could have designed would not end up with some expert on HN explaining why the choice is wrong.
- simonh 6y agoSure, but as I understand it RISC-V was not intended to be used for big high performance general CPUs. It’s specifically optimised for small core applications, with extensions allowing efficient support for only the additional functionality needed for a specific product. Complaining that RISC-V isn’t well suited to designs competing with Snapdragons and M1 is somewhat missing the point. There has been some talk of a breaking fork of RISC-V better suited to large high performance designs. I think that makes a lot of sense and would allow RISC-V to stay good at what it was intended for.
- andrewflnr 6y agoPlease tell me they're calling it RISC-VI.
- jleahy 6y agoRight, but the OP and most of HN seem to believe that RISC-V is some magic thing that is going to beat M1/Zen/whatever one day while also providing amazingly low power for tiny 32-bit cores. Also, RISC-V is also essentially split into two incompatible instruction sets (32-bit and 64-bit, even if you forget the huge amount of extensions). It would have been better just to design two completely separate instruction sets (32-bit for low power, 64-bit for high performance), but at core it's a teaching instruction set like MIPS before it, so they went for simplicity not optimality.
- KMag 6y agoHow wasteful is it to have a bimodal decoder that assumes 8 4-byte instructions or 8 2-byte instructions, with bail-out circuitry to dispatch fewer instructions and advance to the next instruction-size boundary in the case of a stream of mixed widths? You don't need to handle the entire realm of possibilities in a single cycle if you're willing to pay a performance penalty for mixed-width instruction streams. You'd pay a performance penalty for mixed-width runs of instructions, but hopefully profile-guided-optimization would allow the compiler to know where to make the tradeoff for speed and were to make the tradeoff for size. Also, you mentioned elsewhere that you're a hardware designer. I'm aware that split register files are advantageous for speed, while unified integer and fp register files are advantageous for small/low power implementations. Given that the high-performance implementations are going to use register renaming anyway, has anyone quantified the penalty in using register renaming to allow for a split register file in high-performance implementations while allowing compatible low-power implementations with a single register file? Maybe the transistor savings are dwarfed by other concerns in a modern hardware implementation, but given RISC-V's academic goals, economy for FPGA implementation seems useful.
- gchadwick 6y ago> How wasteful is it to have a bimodal decoder that assumes 8 4-byte instructions or 8 2-byte instructions, with bail-out circuitry to dispatch fewer instructions and advance to the next instruction-size boundary in the case of a stream of mixed widths? I suspect mixed-width streams are extremely common (at least going by compiled RISC-V code I've seen). Potentially you can alter compilers to change this but the compressed instruction set is very limited, it naturally mixes compressed and uncompressed and forcing otherwise will likely produce poorer code overall. I'd also note that branch prediction accuracy is a key part of modern processor performance, they're maybe around 95-99% accurate most of the time. Your scheme looks to have similar mispredict penalties to branch prediction (you have to chuck things away and start over, though at least you don't have to refetch) so will likely require similar level of accuracy to work well. Plus on any mixed stream performance will be very poor as you've explicitly not built a decoder for mixed streams, so you end up dripping things through one at a time or you allow limited decoding on mixed streams (maybe 2 instructions per cycle). Edit: Perhaps the mis-predict penalty isn't so bad compared to a mis-predicted branch as detecting the mis-predict is trivial. Indeed having just realised this you may be better off without the predictor at all, just look at the bytes and decide which you should do. Though depending on the decoders you may end up needing two entirely separate sets of logic for the compressed instruction decoder vs uncompressed instruction decoder anyway, in which case you just decode your stream in both ways then decide which set of decodings you want to use. You still have the issue with any mixed width stream killing your performance though which I suspect would be a major issue with this design (so much so it's not useful).
- imtringued 6y agoGoing by what the article says >First it is combining two instructions into one through compression. >Then it splits it into two through decompression. >The combine them back into one operation through macro-op fusion. The general pattern appears to be: load 8 32bit instructions (2 compressed instructions count as 1 32bit instruction), convert them to up to 16 32 bit instructions. Now your macro op fusion would only have to operate on 32bit instructions. Although the decoder sounds trivial the macro op fusion still has a massive problem. x86 macro op fusion only needs to look at the first instruction to know which macro op fusion to apply because x86 does not fuse more than 2 instructions. The type of macro op fusion that RISC-V needs to be competitive would span up to 6 instructions. At that point your instruction decoding problem is as complicated as a regex which means unbounded complexity compared to whatever x86 does. Solving this problem efficiently would require some sort of "hint prefix" on the first instruction. This is far more complicated than just adding a new instruction.