Implement all RV64F and RV64D instructions.

Specifics

You need to be careful about the registers in RV64FD. Most of the time, rd, rs1, rs2, and rs3 are used to specify a floating point register. However, some instructions, such as FCVT and FMV use both x registers and f registers.

There is also a couple more fields not present with the integer instructions. One is the rounding-mode, or rm for short, and the other is the format, or fmt for short. The rm field is in the funct3 section of the instruction, and the format field is two bits of the funct7 section of the instruction.

Valid options for the rounding mode field.

You are not required to implement rounding modes.

Valid options for the format field.

You are only required to implement S and D.

You are NOT required to implement the floating point control and status register (fcsr).

You are not required to implement the FCLASS instructions.


Instruction Types

There are only four instruction types for RV64FD.

Instruction types for RV64F and RV64D

RV64F, 32-bit floating-point

You will have two sets of registers. One for RV64F and one for RV64D. Some machines have two banks of registers for different data sizes, and the instruction below allow for that. The other way is to mask the upper 32-bits of a 64-bit register for the RV64F or use the entire 64-bits for the RV64D.

RV64F full instruction set.

The RV64D instruction set is native to your machine. There is no typecasting or copying necessary for these instructions.

RV64D instruction set.

Floating point MOVE Instructions

The floating point move instructions (FMV) move a value untranslated to/from an integer register. There is no way to do this 100% in C++, so we have to cheat a little bit by using memory as a central point between the ALU and the FPU.

float value = 2.5;
int value_as_int = *reinterpret_cast<int*>(&value);
printf("%f 0x%08x\n", value, value_as_int);

0x40200000 = 0100 0000 0010 0000 0000 0000 0000 0000

Sign = 0 (+)
Exponent = 0b1000_0000 = 128 – 127 = 1
Fraction = 1.01

\(1.01\times2^1 = 10.1_2 = 2.5_{10}\)

The code above will print 2.5 and 0x4020_0000, which is the IEEE-754 representation of 2.5. We use memory and just trick C++ into thinking that the memory location &value is an integer instead of a float. We then dereference that memory spot and reinterpret those bytes as an integer.

Regular casting using static_cast will actually convert a float into an integer and vice versa. This mimics the functionality of FCVT.


Sign Injection Instructions

The fsgn instructions are used to “inject” information into an IEEE-754 register. These instructions have special meanings as documented below.


Floating Point Branch Instructions

There are no branch instructions that compare floating-point registers. Instead, feq, flt, fle, will take an integer destination register and two floating point registers. It will compare the registers, and if the registers match the comparison, then the integer destination register is set to 1, otherwise it is set to 0. This means, you can compare by chaining an feq, flt, or fle with a beqz or bnez pseudo-instruction.

flt t0, ft0, ft1
bnez t0, is_less_than

Fused Multiply and Add

The fused multiply and add instructions fuse multiply and add (go figure?) into one instruction. To implement them, a new instruction type format was created called R4. This new instruction type takes four registers: rd, rs1, rs2, and rs3.

Keep an eye on what these do. As the manual states, it might seem counterintuitive what the instructions do. So, follow the manual closely when implementing these instructions.


Final Thoughts

Notice that you do not need to decode the 32-bit and 64-bit separately. The difference is the fmt 2-bit field. So, you can instead narrow down to the instruction, then determine which bank of registers (32-bit floats or 64-bit doubles) to use based on the format field.