RISC-V is a simple ISA with lots of coding space left for extensions (new instructions). That was intentional.
Having fixed-width instructions is pretty central to the original RISC idea of being simple to pipeline and decode in parallel. In a classic RISC the fetch stage is incrementing the program counter by 4 to fetch the next instruction in parallel with the last one being decoded.
With a good format for 16-bit compressed instructions like RV32C or ARM Thumb, pipelining a mix of 16 and 32-bit instructions isn't a problem; you fetch in wide contiguous chunks and pull instructions out of a buffer. It's still easy and fast to find instruction boundaries. Modern transistor budgets, even for microcontrollers, are larger than when MIPS was first being designed, and above that even an in-order superscalar CPU has variable instructions-per-cycle, so having a fetch buffer isn't a problem. See also Modern Microprocessors
A 90-Minute Guide! for some pipelining and superscalar (more than 1 instruction per cycle) basics.
Having each instruction be a different number of bits, like a bitstream that wasn't aligned even to byte boundaries, would be very inconvenient, especially for branching. It's no surprise nothing does that. But even with byte alignment, x86's variable-length instructions are a huge pain to decode, and cost significant power. (And are so troublesome that modern CPUs cache the decode results.)
RISC-V was designed by the same lead designer as MIPS, where all R-type instructions have the same "opcode" field, using the "funct" field to control the ALU. (I-type and J-type instructions, and RISC-V U-type, don't have room elsewhere, so the opcode field is actually the opcode in the normal sense, determining which instructions.)
RISC-V funct3
and funct7
aren't really separate, they're a 10-bit field that just happens not to be contiguous. The RISC-V manual has an explanation for why it spreads around immediates and other things into separate fields: This allows other fields to always be in the same place in all formats when they're present at all, saving some muxes in the decoders. Other fields get spaced out around them. RISC-V: Immediate Encoding Variants has a few answers, including one which quotes those docs.
RISC-V I-type instructions only have a funct3
field, no funct7
, so in that case you only have a 3-bit field. It probably makes sense to use opcode + funct3
bits to specify which operation the ALU should do, so addi
vs. add
can decode as similarly as possible. (I haven't looked at exactly how RISC-V lays out its opcodes).