Skip to content

Comment on ULX3S: Hackable FPGA that runs Linux on RISC-V

Comments

Is there any published rationale for the RISC-V instruction encoding?

A few months back I set out to write a software emulator of RISC-V for fun. I expected the instruction set encoding to lend itself well to a very simple implementation, eg. something you could decode in 5-10 lines of C plus some tables.

But the instruction encoding is much more irregular than I expected: https://github.com/ucb-bar/riscv-sodor/blob/master/src/commo...

In particular:

    - The bit patterns allocated to the simplest instructions
      (eg. rv32i) seem random. Why not allocate starting from zero
      to allow dense jump tables?
    - I can't make any sense of the groupings. A bunch of instructions
      have 0b1100011 in the lowest bits, do these instructions have
      something in common?
I assume there is some rhyme and reason to all this? Where is this explained?

I'm not sure about publications that try to answer "why RISC-V?", but the specification often describes the rationales behind many of these kinds of decisions.

The bit patterns are often designed to make it easier to write efficient hardware designs to implement them. For example, if an instruction encodes an "immediate" value, the most significant bit of the instruction will always hold that value's sign-extension bit. And if you compare the immediate values in I-/S-/B-/J-type instructions, you can see that bits [5:10] are always in the same place. It's not exactly intuitive, but there is some rhyme and reason.

In the base instruction set, the 0b1100011 opcode encodes a set of conditional branch instructions. They perform relative jumps if rs1 == rs2, or if rs1 < rs2, etc.

https://riscv.org/specifications/isa-spec-pdf/

Take a look at the "RV32/64G Instruction Set Listings" table to get a feel for the encodings.

Also, to the specific "0b1100011 in the lowest bits" question, page 8 of the spec shows that, for an instruction to be 32 bits, the lowest two bits must be 11 (non-11 is used for 16-bit) and the next three bits must not all be 111 (111 is used for 48-bit and larger instructions).

Being "simple" for a C program to decode is not the point; generating the smallest hardware to decode instructions and to reduce the critical path for things like deducing the instruction operands are.

For example, the operands all stay in the same position, which prevents putting decode on the critical path before you can figure out which registers you need to read (which is doubly important for superscalar designs which need to compute the register dependence graph). Likewise, the sign-extend bit for immediates is always in the same place, providing relief for another potential critical path.

Exactly this. We had a hardware design where the difference between a load and store was a single bit, we also had a register file that wasn't afraid of bit flips, and part of the decode was on the critical path so it wasn't sufficiently parity protected. You can imagine what happened and how fun that was to debug.

Is there any published rationale for the RISC-V instruction encoding?

Computer Organization and Design, RISC-V edition, goes into these. R-types have opcode = 0b0110011, SB (branch) types have opcode = 0b1100011, etc.

The lower bits also have extra meanings (16 bit, 48 bit, etc. instruction).

This allows hardware to just branch on these bits in sub-groups. The hardware pipeline for 16 bit is different than that of 48 bit, and the 48-bit is probably even optional.

I spent some time defining an alternate encoding of the base instruction set. I dont think they did a good job, but it's also not easy to do better. I started from an assumption of variable length - 16 bit or 32 bit opcodes plus immediate data in 16 bit chunks. It's not easy. Remember all those strange bit positions are irrelevant in hardware. But they are a bitch for things like linkers.

It has to encode 32 GPR's and three-operand instructions so instruction encoding is a bit cramped, even in 32 bits. 16-bit encodings are reserved for "compressed" special-case forms of existing 32-bit instructions.

Right, so I made 16bit opcodes only have 4 bits for register indexes. 32bit ones would provide the 5th bit for each register. That means a compiler should prefer the first 16 registers in order to reduce code size, but they are all still available. It also meant a reduced size chip with half the registers would not need different instruction encodings.

Compressed was an afterthought on Risc-V. A well thought out afterthought, but you do things differently when you know in advance.

Compressed was very explicitly made optional so that implementers would have the choice of recycling that encoding space for something else. It was not just an "afterthought".

Remember all those strange bit positions are irrelevant in hardware.

What, they are absolutely relevant in hardware and selected to optimize the resulting generated hardware.

I was super unclear there. The strange routing is largely irrelevant. In software it's a pain to do all that reshuffling, in hardware it's just wires from here to there.

The ISA was likely designed for efficient implementation in Silicon rather than being easy to grok by a human.

To see what I mean, have a gander at this:

https://github.com/ulx3s/apple2fpga/blob/master/cpu6502.vhd

I agree. I had to write a dissembler for RISC-V in college and it was surprisingly annoying.

AboutSource Built by g1lg1l

Hackerly is an independent reader for Hacker News, built on the public HN API. Not affiliated with Y Combinator.