Easy RISC-V

8 comments

Having learned assembly with the book "Computer Organization And Design" from Patterson and Hennessy, it really shows how much RISC-V takes from MIPS. After all they share some of the people involved in both ISAs and they have learned from the MIPS mistakes (no delay slots!). Basically if you come from a MIPS the assembly is very very similar, as it was my case.

Now that book is also available with a RISC-V edition, which has a very interesting chapter comparing all different RISC ISAs and what they do differently (SH, Alpha, SPARC, PA-RISC, POWER, ARM, ...),...

However I've been exploring AArch64 for some time and I think it has some very interesting ideas too. Maybe not as clean as RISC-V but with very pragmatic design and some choices that make me question if RISC-V was too conservative in its design.

I get the same impression w.r.t. RISC-V v. MIPS similarities, just from my (limited) exposure to Nintendo 64 homebrew development. Pretty striking how often I was thinking to myself “huh, that looks exactly like what I was fiddling with in Ares+Godbolt, just without the delay slots”.

Instructions are more easily added than taken away. RISC-V started with a minimum viable set of instructions to efficiently run standard C/C++ code. More instructions are being added over time, but the burden of proof is on someone proposing a new instruction to demonstrate what adding the instruction costs and how much benefit it brings and in what real-world applications.

> Instructions are more easily added than taken away.

That's not saying much, it's basically impossible to remove an instruction. Just because something is easier than impossible doesn't mean that it's easy.

And sure, from a technical perspective, it's quite easy to add new instructions to RISC-V. Anyone can draft up a spec and implement it in their core.

But if you actually want wide-spread adoption of a new instruction, to the point where compilers can actually emit it by default and expect it to run everywhere, that's really, really hard. First you have to prove that this instruction is worthwhile standardizing, then debate the details and actually agree on a spec. Then you have to repeat the process and argue the extension is worth including in the next RVA profile, which is highly contentious.

Then you have to wait. Not just for the first CPUs to support that profile. You have to wait for every single processor that doesn't support that profile to become irrelevant. It might be over a decade before a compiler can safely switch on that instruction by default.

> it's basically impossible to remove an instruction

laughs and/or cries in one of the myriad OISC ISAs

My memory is a bit fuzzy but I think Patterson and Hennessy‘s “Computer Architecture: A Quantitative Approach” had some bits that were explicitly about RISC-V, and similarities to MIPS. Unfortunately my copy is buried in a box somewhere so I can’t get you any page numbers, but maybe someone else remembers…

Henessey and Patterson "Computer Architecture: A Quantitative Approach" has 6 published editions (1990, 1996?, 2003, 2006, 2011, 2019) with the 7th due November 2025. Each edition would have a varying set of CPUs as examples for each chapter. For example, the various chapters in the 2nd edition has sections on the MIPS R4000 and the PowerPC 620, while the 3rd edition has sections on the Trimedia TM32, Intel P6, Intel IA-64, Alpha 21264, Sony PS2 Emotion Engine, Sun Wildfire, MIPS R4000, and MIPS R4300. From what I could figure out via web searches, the 6th edition has RISC-V in the appendix, but the 3rd through 5th editions has the MIPS R4000.

Patterson and Hennessy "Computer Organization and Design: The Hardware/Software Interface" has had 6 editions (1998, 2003, 2005, 2012, 2014, 2020) but various editions have had ARM, MIPS, and RISC-V specific editions.

My copy sure doesn't ... it was published in 1992, almost 20 years before anyone got an idea to make a new ISA called "RISC-V".

> However I've been exploring AArch64 for some time and I think it has some very interesting ideas too. Maybe not as clean as RISC-V but with very pragmatic design and some choices that make me question if RISC-V was too conservative in its design.

Not enough people reflect on this, or the fact that it's remarkably hazy where exactly AArch64 came from and what guided the design of it.

AArch64 came from AArch32. That's why it keeps things like condition codes, which are a big mistake for large out-of-order implementations. RISC-V sensibly avoid this by having condition-and-branch instructions instead. Otherwise, RISC-V is conservative because it tries to avoid possibly encumbered techniques. But other than that it's remarkably simple and elegant.

Yeah the problem with having flags is demonstrated by multiple very high performance implementations of arm64 and x86, while risc-v has exactly zero.

Afair, AArch64 was basically designed by Apple for their A-series iPhone processors, and pushed to be the official ARM standard. Those guys really knew what they were doing and it shows.

> That's why it keeps things like condition codes, which are a big mistake for large out-of-order implementations. RISC-V sensibly avoid this by having condition-and-branch instructions instead.

Respectfully, the statement in question is partially erroneous and, in far greater measure, profoundly misleading. A distortion draped in fragments of truth remains a falsehood nonetheless.

Whilst AArch64 does retain condition flags, it is not simply because of «AArch32 stretched to 64-bit», and condition codes are not a «big mistake» for large out-of-order (OoO) cores. AArch64 also provides compare-and-branch forms similar to RISC-V, so the contrast given is a false dichotomy.

Namely:

  – «AArch64 came from AArch32» – historically AArch64 was a fresh ARMv8-A ISA design that removed many AArch32 features. It has kept flags, but discarded pervasive per-instruction predication and redesigned much of the encoding and register model;

  – «Flags are a big mistake for large OoO» – global flags do create extra dependencies, yet modern cores (x86 and ARM) eliminate most of the cost with techniques such as flag renaming, out-of-order flag generation and using instruction forms that avoid setting flags when unnecessary. As implemented in high-IPC x86 and ARM cores, it shows that flags are not an inherent limiter;

  – «RISC-V avoids this by having condition-and-branch» – AArch64 also has condition-and-branch style forms that do not use flags, for example:

  1) CBZ/CBNZ xN, label – compare register to zero and branch;

  2) TBZ/TBNZ xN, #bit, label – test bit and branch.
Compilers freely choose between these and flag-based sequences, depending on what is already available and the code/data flow. Also, many arithmetic operations do not set flags unless explicitly requested, which reduces false flag dependencies.

Lastly, but not least importantly, Apple’s big cores are among the widest, deepest out-of-order designs in production, with very high IPC and excellent branch handling. Their microarchitectures and toolchains make effective use of:

  – Flag-free branches where convenient – CBZ/CBNZ, TBZ/TBNZ (see above);

  – Flag-setting only when it is free or beneficial – ADDS/SUBS feeding a conditional branch or CSEL;

  – Advanced renaming – including flag renaming – which removes most practical downsides of a global NZCV.

Thanks LLM

You are, of course, most welcome to offer your contributions — whether in debate or in contestation of the points I have raised – beyond the hollow reverberations of yet another LLM echo chamber.

The information I used to contest the original statement comes from the AArch64 ISA documentation as well as from the infamous «M1 Explainer (070)» publication, namely sections titled «Theory of a modern OoO machine» and «How Do “set flags” Instructions, Like ADDS, Modify the History File?».

How are you defining "large"? Apple seems to do pretty well with the M-series.

For the uninitiated in AArch64, are there specific parts of it you're referring to here? Mostly what I find is that it lets you stitch common instruction combinations together, like shift + add and fancier adressing. Since the whole point of RISC-V was a RISC instruction set, these things are superfluous.

RISC-V has shift+add instructions as part of the Zba extension. Zba is part of B, so it's included in many recent RISC-V profiles.

I have to praise the interactive style of this content.

As a C/C++ dev, I've always thought assembly was much harder. But this interactive content makes assembly clearer.

Great guide! I think the first "My first RISC-V assembly program" emulator plane should be right at the beginning of the guide. Otherwise, casual readers might think that this is a text-only introduction (despite the word "interactive" in the title).

Will spend more time on it in the coming days. I am quite interested in RISC-V and I think that it might have a bright future ahead.

If any AI expert is reading this now, please use Replit or Lovable or something like that to re-create "Core War" [0] with RISC-V assembly. It would be GREAT.

[0]: https://en.wikipedia.org/wiki/Core_War

Why wouldn't someone just do this with their brain?

I think there's an error in the Position Independence section:

    start:
        auipc a0, 3
        addi a0, a0, 4
The text says that this should result in 0x3004; was this example intended to be

    start:
        lui a0, 3
        addi a0, a0, 4

No, because `lui` results in an absolute address, not a position independent one.

The `auipc/addi` sequence results in 0x3004 + whatever the address of the `auipc` instruction itself is. If the `auipc` is at address 0 then the result will be the same.

Exactly, but the text has the same instruction sequence twice and the parent correctly indicated that the first copy should have used "lui" to illustrate the problem you mentioned and the second copy does use "auipc" to illustrate the fix you mentioned.

> Subtracting from zero is negation. What’s the negative of 0x123?

It is 0xfffffedd.

> Hmm, we get 0xfffffccd.

No, we didn't. The emulator shows 0xfffffedd, and I've checked it manually. The emulator is right.

Within the basic "123" ASM demo, I get that x10 - Becomes 0x00000123 as we are taking the integer of x0 and applying 123 to end of it but why is the sp (x2) register at 0x40100000?

What is that sp? Is it important? Why isn't that at 0x000000? Why isn't that explained? That's when I get lost.

[deleted]