RISC-V somehow manages to be more fragmented than x86 (which is impressive), and just can't compete on instruction density.
I think a large part of the issue with RISC-V is that it predates (public knowledge of) ARMv8 by a year or two, so it couldn't use it as inspiration. If you compare RISC-V to 32-bit ARM, the comparisons are much more favourable.
Does it? https://people.eecs.berkeley.edu/~krste/papers/EECS-2016-1.p... has a section on ARMv8 (section 2.5)
It says they became aware of it a year after they started the RISC-V project, but that’s five years before that paper was published.
But version 1.0 of the spec [1] was released all the way back in May 2011, and the first RISC-V chip was taped out at the same time. This is 5 months before ARMv8 was even announced, and we didn't start seeing actual aarch64 chips until late 2013.
And TBH, I'm not sure anyone realised just how good of an ISA aarch64 is until quite a bit later.
RISC-V 1.0 isn't binary compatible with modern RISC-V, they hadn't frozen the encoding, but rough design is all there.
[1] https://www2.eecs.berkeley.edu/Pubs/TechRpts/2011/Archive/EE...
The only reason it's "competitive" is the compressed instructions, which means it's paying all the costs of variable length instructions, yet only getting marginal benefits. IMO a modern ISA taking advantage of variable length instructions should be able to absolutely smash the code density of a fixed width ISA like aarch64. At minimum, it should be competitive with x86 code density, if not smashing that too (because x86 has a lot of legacy baggage)
Compressed instructions aren't a bad idea for very small cores. They give you a decent code density boost with minimal added complexity.
But for large cores you either want to go full fixed length (like AArch64 and Qualcomm's proposal, which bought non-compressed RISC-V into the range of AArch64) or adopt a much more complex variable length scheme that can actually beat x86 on code density.
It is a different front end design, so that's why Qualcomm didn't want to reengineer their aarch64 core more than they had to, but the rest of the riscv community was right to not embrace it.
Not to mention that a lot of the aarch64 derived pieces in the proposed qualcomm extension are almost certainly patent encumbered. Qualcomm can absolutely handle just about any patent fight, but other risc-v companies can't.
The important point is that there is no reason why a 16-bit/32-bit encoding shouldn't have smashed Aarch64's 32-bit only code density.
My secondary point, is that why should RISC-V limit itself to just 16-bit/32-bit? It has the encoding space set aside for 6 bytes, 8 bytes, 10 bytes and all the way up to 24 bytes (which is overkill). If it's already paying the variable length tax, it should be making better use of it. IMO, a 2, 4, 6, 8, 10... byte scheme should be able to massively improve on x86's code density.
I'm saying the opposite. Maybe some theoretical CISC-V would leave RISC-V behind, but x86(and -64) makes wild choices for instruction density, and RV64GC already clearly beats x86-64 in .text density.
> My secondary point, is that why should RISC-V limit itself to just 16-bit/32-bit? It has the encoding space set aside for 6 bytes, 8 bytes, 10 bytes and all the way up to 24 bytes (which is overkill). If it's already paying the variable length tax, it should be making better use of it. IMO, a 2, 4, 6, 8, 10... byte scheme should be able to massively improve on x86's code density.
There's nonlinear issues as you add more options. A 16-32 decoder is pretty simple, a 16-32-48 isn't the worse thing in the world (and a 32bit immediate might make it worth it), but you start to hit weird explosions in gate count once you go much past that. Hence x86's splitting into essentially multiple front end banks in modern designs, and even then typically only has one decoder per bank that can decode everything, and even that takes multiple cycles for some instruction sequences, even just to discover the length.
The larger lengths in the RISC-V spec are more targeted towards bespoke stuff like GPGPU that's maxing out issuing a single instruction per instruction stream anyway. When you look at shader machine code, it's clear density was essentially an afterthought, but they love them some 64bit wide instructions. Which unsurprisingly is pretty much the same width of vertical microcode in archs that still do such a thing.
Maybe I'm misremembering. Or maybe the numbers I'm remembering took into account the fact that most compilers unroll more aggressively on x86 than on targets they consider to be "embedded" (another pet peeve of mine)
I stand by my assessment that the code density of rv64gc (and especially rv64g) is lower than it would be if they had actually put a focus on code density.
> A 16-32 decoder is pretty simple, a 16-32-48 isn't the worse thing in the world (and a 32bit immediate might make it worth it), but you start to hit weird explosions in gate count once you go much past that.
Not sure I would say 16-32 is simple, certainly massively simpler than x86. My point is that you have already paid the tax for going variable length, and 16-32-48 isn't that much more complex. And probably worth it for 32-bit immediate/offsets.
And maybe 16-32-48-64 is worth it... Hard to tell, but I wouldn't entirely rule it out without study. The advantage would either be immediates/offsets that are too big to fit in 48 bits. Or some kind of VLIW style scheme which actually packed three 20-bit instructions into aligned 64-bit packets. (Or other mixtures of sizes like 30-30, 30-15-15, 40-20, or 15-15-15; We are talking about a complete break from RISC-V. There is a thread somewhere on HN where we brainstorm something like this).
But beyond that, no point really. Just pointing out that RISC-V reserved the space.
Maybe I need to prototype the 64-bit aligned packets idea someday, at least far enough to get instruction density numbers.
It is, with a prefix encoding, you can reuse the RVC decode path 1-to-1 and get the 48/64-bit instruction starts with a simple bitshift (or simply handle the 48/64-bit instructions via the fusion path). This seems to be the encoding direction RISC-V is headed in.
Arm uses complex instructions with multiple writeback, that require cracking, to improve code density. RISC-V uses a variable length encoding to improve code density. Both have anaougus decoding complexity, but RISC-V achieves higher code density, while impacting the cost of things before decode (how much, idk).
> Arm uses complex instructions with multiple writeback, that require cracking, to improve code density.
While smaller cores have the option of cracking the multiple writeback instructions, many arm cores just pay the extra cost of having a 3 read, 2 write register file, so they aren’t actually cracking those instructions.
They do crack other instructions.
But the cracking seems to be more about ALU limitations (aarch64 has instructions that can do both a shift of any width and an add, but the ALUs might not support this, or only support smaller shifts of 1-3 bits (useful for addressing)
What this means is that despite the cracking, each μop in an aarch64 core is quite a bit more powerful than a typical RISC-V instruction (especially compressed instructions).
So to be competitive on backend performance, a high performance RISC-V is going to spend a lot of resources post-decode doing massive amounts of instruction fusion to try to get μops of similar capabilities to aarch64 (or just settle for simpler μops, and pay scheduling costs of more μops)
So the costs of the RISC-V compressed instruction approach aren’t just limited to pre-decode.
No, every high performance core I know of cracks them at decode, some re-fuse some of them after rename (Apple). Because otherwise you would need to rename up to 4 destinations per rename slot, effectively 4xing your already limiting rename stage.
Cracking other stuff later in the pipeline isn't expensive.
Though, I guess fusing after cracking makes things easier because you don't actually have to search for fusion candidates (supported by the fact that Apple's Firestorm doesn't seem to make any effort to fuse things that aren't alu + branch, crypto, or amx)
Yeah, fusing is probably easier, if you already know what to fuse. On the other hand, if you want to fuse load pair on RISC-V you have the entire rename stage to figure out which uops can be fused independently of the rename stage, if fusion haopens after rename as well.
That's a good point.
If some RISC-V μarch was going to invest the extra gates for a complex fusion setup, the search isn't actually going to slow anything down, as it can run in parallel with other frontend operations (like renaming).
I always just assumed fusion was done as early as possible, only considering instructions that are right next to each-other (that's certainly the intent of the RISC-V spec), and then resolved immediately after decode.
But maybe it's better to do it right at the end of the front end; After renaming, during insertion into the scheduler.
Despite my curiosity, I explicitly refused to agree to Apples terms for accessing those documents, because they were very draconian. The terms absolutely forbids using the information for anything other than optimising software for apple devices.
Discussing the design tradeoffs of RISC-V μarches couldn't be further from "optimising software for apple's devices".
Ah, I suppose.
Such as? I can't think of anything it does better for high performance cores.
With RISC-V, multiple instruction workarounds are needed for all of the above, and those sequences are usually sequentially dependent ones so they can't be run in parallel. i.e. the insanity of loading a 64-bit value through repeated 12-bit immediates with shifts, using multiple instructions to compute branch offsets, and RVV needing setvli instructions everywhere due to not having opcode space to encode vector length/type.
AArch64 is better, but still has problems with limited opcode space when it comes to future extensions. They've had to make "start mode" and "end mode" for SME to save on opcode space, and future compromises will likely be necessary.