diff --git a/.gitignore b/.gitignore index e6daddd..741d4b9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ tests/*.o tests/*.bin rust/target rust/Cargo.lock +_codeql_* diff --git a/Makefile b/Makefile index 1ee3278..4d042f6 100644 --- a/Makefile +++ b/Makefile @@ -14,13 +14,13 @@ rust: cd rust && cargo build %.o: %.s - riscv64-unknown-elf-as -march=rv32ima -o $@ $< + riscv64-unknown-elf-as -march=rv32imafd -o $@ $< %.bin: %.o riscv64-unknown-elf-objcopy -O binary $< $@ c/main: c/main.c - gcc -O2 -fno-strict-aliasing -o c/main c/main.c + gcc -O2 -fno-strict-aliasing -o c/main c/main.c -lm clean: rm -f $(OBJECTS) $(BINARIES) c/main diff --git a/README.md b/README.md index 74a9a08..47b91ff 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,12 @@ I wanted to do this for a long time, but never had the time to do it. Now I stil ## Supported Extensions -This CPU implements the **RV32I** base instruction set, the **RV32M** extension, and the **RV32A** extension: +This CPU implements the **RV32G** instruction set, which includes: +- **RV32I**: Base instruction set +- **RV32M**: Integer multiplication and division +- **RV32A**: Atomic instructions +- **RV32F**: Single-precision floating-point +- **RV32D**: Double-precision floating-point ### RV32I Base Instructions - **Load Instructions**: `lb`, `lh`, `lw`, `lbu`, `lhu` @@ -32,6 +37,26 @@ This CPU implements the **RV32I** base instruction set, the **RV32M** extension, - **Atomic Memory Operations**: `amoadd.w`, `amoswap.w`, `amoxor.w`, `amoand.w`, `amoor.w` - **Atomic Min/Max**: `amomin.w`, `amomax.w`, `amominu.w`, `amomaxu.w` +### RV32F Extension (Single-Precision Floating-Point) +- **Load/Store**: `flw`, `fsw` +- **Arithmetic**: `fadd.s`, `fsub.s`, `fmul.s`, `fdiv.s`, `fsqrt.s` +- **Fused Multiply-Add**: `fmadd.s`, `fmsub.s`, `fnmsub.s`, `fnmadd.s` +- **Conversion**: `fcvt.w.s`, `fcvt.wu.s`, `fcvt.s.w`, `fcvt.s.wu`, `fmv.x.w`, `fmv.w.x` +- **Comparison**: `feq.s`, `flt.s`, `fle.s` +- **Sign Injection**: `fsgnj.s`, `fsgnjn.s`, `fsgnjx.s` +- **Min/Max**: `fmin.s`, `fmax.s` +- **Classification**: `fclass.s` + +### RV32D Extension (Double-Precision Floating-Point) +- **Load/Store**: `fld`, `fsd` +- **Arithmetic**: `fadd.d`, `fsub.d`, `fmul.d`, `fdiv.d`, `fsqrt.d` +- **Fused Multiply-Add**: `fmadd.d`, `fmsub.d`, `fnmsub.d`, `fnmadd.d` +- **Conversion**: `fcvt.w.d`, `fcvt.wu.d`, `fcvt.d.w`, `fcvt.d.wu`, `fcvt.s.d`, `fcvt.d.s` +- **Comparison**: `feq.d`, `flt.d`, `fle.d` +- **Sign Injection**: `fsgnj.d`, `fsgnjn.d`, `fsgnjx.d` +- **Min/Max**: `fmin.d`, `fmax.d` +- **Classification**: `fclass.d` + ## How to use To compile the test programs, run `make` or `make tests`. diff --git a/c/main.c b/c/main.c index 0bf2727..1f3ddff 100644 --- a/c/main.c +++ b/c/main.c @@ -4,6 +4,7 @@ #include #include #include +#include #define MEMORY_SIZE 64 * 1024 @@ -13,6 +14,7 @@ #define OOB_BITS_16 (~(MEMORY_SIZE - 1 - 1)) // 0xFFFF0000 - halfword access #define OOB_BITS_32 (~(MEMORY_SIZE - 1 - 3)) // 0xFFFF0000 - word access #define OOB_BITS_PC (~(MEMORY_SIZE / 4 - 1)) // 0xFFFFC000 - program counter (word index) +#define OOB_BITS_64 (~(MEMORY_SIZE - 1 - 7)) // 0xFFFF0000 - double word access // Branch prediction hint for unlikely conditions #define unlikely(x) __builtin_expect(!!(x), 0) @@ -24,6 +26,14 @@ int32_t *memory32 = (int32_t *) memory32_unsigned; uint16_t *memory16 = (uint16_t *) memory32_unsigned; uint8_t *memory8 = (uint8_t *) memory32_unsigned; +// Floating-point registers (64-bit for D extension compatibility) +union { + float f32[64]; // 32 registers * 2 (for 64-bit storage) + double f64[32]; // 32 registers + uint32_t u32[64]; // 32 registers * 2 + int32_t i32[64]; // 32 registers * 2 +} fp_registers; + // index for 32 bit! uint32_t program_counter = 0; @@ -80,6 +90,22 @@ void tick() { registers[register_destination] = memory16[addr >> 1]; break; } + // floating-point load + case 0b00001010: { // flw + int32_t addr = registers[register_source1] + ((int32_t)instruction >> 20); + if (unlikely(addr & OOB_BITS_32)) goto error_oob; + fp_registers.u32[register_destination * 2] = memory32_unsigned[addr >> 2]; + fp_registers.u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + break; + } + case 0b00001011: { // fld + int32_t addr = registers[register_source1] + ((int32_t)instruction >> 20); + if (unlikely(addr & OOB_BITS_64)) goto error_oob; + uint32_t word_index = addr >> 2; + fp_registers.u32[register_destination * 2] = memory32_unsigned[word_index]; + fp_registers.u32[register_destination * 2 + 1] = memory32_unsigned[word_index + 1]; + break; + } // fence // register+immediate case 0b00100000: // addi @@ -142,6 +168,21 @@ void tick() { memory32[addr >> 2] = registers[register_source2]; break; } + // floating-point store + case 0b01001010: { // fsw + int32_t addr = registers[register_source1] + (((int32_t)instruction >> 25) << 5 | register_destination); + if (unlikely(addr & OOB_BITS_32)) goto error_oob; + memory32_unsigned[addr >> 2] = fp_registers.u32[register_source2 * 2]; + break; + } + case 0b01001011: { // fsd + int32_t addr = registers[register_source1] + (((int32_t)instruction >> 25) << 5 | register_destination); + if (unlikely(addr & OOB_BITS_64)) goto error_oob; + uint32_t word_index = addr >> 2; + memory32_unsigned[word_index] = fp_registers.u32[register_source2 * 2]; + memory32_unsigned[word_index + 1] = fp_registers.u32[register_source2 * 2 + 1]; + break; + } // atomic case 0b01011010: { int32_t addr = registers[register_source1]; @@ -320,6 +361,301 @@ void tick() { case 0b01101111: registers[register_destination] = instruction & 0xfffff000; break; + // fused multiply-add (F and D extensions) + case 0b10000010: // fmadd.s + case 0b10000011: { // fmadd.d + uint32_t register_source3 = instruction >> 27; + bool is_double = funct3 & 1; + if (is_double) { + fp_registers.f64[register_destination] = fp_registers.f64[register_source1] * fp_registers.f64[register_source2] + fp_registers.f64[register_source3]; + } + else { + fp_registers.f32[register_destination * 2] = fp_registers.f32[register_source1 * 2] * fp_registers.f32[register_source2 * 2] + fp_registers.f32[register_source3 * 2]; + fp_registers.u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + } + case 0b10001010: // fmsub.s + case 0b10001011: { // fmsub.d + uint32_t register_source3 = instruction >> 27; + bool is_double = funct3 & 1; + if (is_double) { + fp_registers.f64[register_destination] = fp_registers.f64[register_source1] * fp_registers.f64[register_source2] - fp_registers.f64[register_source3]; + } + else { + fp_registers.f32[register_destination * 2] = fp_registers.f32[register_source1 * 2] * fp_registers.f32[register_source2 * 2] - fp_registers.f32[register_source3 * 2]; + fp_registers.u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + } + case 0b10010010: // fnmsub.s + case 0b10010011: { // fnmsub.d + uint32_t register_source3 = instruction >> 27; + bool is_double = funct3 & 1; + if (is_double) { + fp_registers.f64[register_destination] = -(fp_registers.f64[register_source1] * fp_registers.f64[register_source2]) + fp_registers.f64[register_source3]; + } + else { + fp_registers.f32[register_destination * 2] = -(fp_registers.f32[register_source1 * 2] * fp_registers.f32[register_source2 * 2]) + fp_registers.f32[register_source3 * 2]; + fp_registers.u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + } + case 0b10011010: // fnmadd.s + case 0b10011011: { // fnmadd.d + uint32_t register_source3 = instruction >> 27; + bool is_double = funct3 & 1; + if (is_double) { + fp_registers.f64[register_destination] = -(fp_registers.f64[register_source1] * fp_registers.f64[register_source2] + fp_registers.f64[register_source3]); + } + else { + fp_registers.f32[register_destination * 2] = -(fp_registers.f32[register_source1 * 2] * fp_registers.f32[register_source2 * 2] + fp_registers.f32[register_source3 * 2]); + fp_registers.u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + } + // floating-point operations + case 0b10100000: // fadd.s/fadd.d + case 0b10100001: + case 0b10100010: + case 0b10100011: + case 0b10100100: + case 0b10100101: + case 0b10100110: + case 0b10100111: { + uint32_t funct7 = instruction >> 25; + uint32_t funct5 = funct7 >> 2; + bool is_double = funct7 & 1; + + switch (funct5) { + case 0b00000: // fadd + if (is_double) { + fp_registers.f64[register_destination] = fp_registers.f64[register_source1] + fp_registers.f64[register_source2]; + } + else { + fp_registers.f32[register_destination * 2] = fp_registers.f32[register_source1 * 2] + fp_registers.f32[register_source2 * 2]; + fp_registers.u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + case 0b00001: // fsub + if (is_double) { + fp_registers.f64[register_destination] = fp_registers.f64[register_source1] - fp_registers.f64[register_source2]; + } + else { + fp_registers.f32[register_destination * 2] = fp_registers.f32[register_source1 * 2] - fp_registers.f32[register_source2 * 2]; + fp_registers.u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + case 0b00010: // fmul + if (is_double) { + fp_registers.f64[register_destination] = fp_registers.f64[register_source1] * fp_registers.f64[register_source2]; + } + else { + fp_registers.f32[register_destination * 2] = fp_registers.f32[register_source1 * 2] * fp_registers.f32[register_source2 * 2]; + fp_registers.u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + case 0b00011: // fdiv + if (is_double) { + fp_registers.f64[register_destination] = fp_registers.f64[register_source1] / fp_registers.f64[register_source2]; + } + else { + fp_registers.f32[register_destination * 2] = fp_registers.f32[register_source1 * 2] / fp_registers.f32[register_source2 * 2]; + fp_registers.u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + case 0b01011: // fsqrt + if (is_double) { + fp_registers.f64[register_destination] = sqrt(fp_registers.f64[register_source1]); + } + else { + fp_registers.f32[register_destination * 2] = sqrtf(fp_registers.f32[register_source1 * 2]); + fp_registers.u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + case 0b00100: // fsgnj/fsgnjn/fsgnjx + if (is_double) { + uint32_t sign1 = fp_registers.u32[register_source1 * 2 + 1] >> 31; + uint32_t sign2 = fp_registers.u32[register_source2 * 2 + 1] >> 31; + fp_registers.u32[register_destination * 2] = fp_registers.u32[register_source1 * 2]; + fp_registers.u32[register_destination * 2 + 1] = ( + funct3 == 0b000 // fsgnj + ? (fp_registers.u32[register_source1 * 2 + 1] & 0x7fffffff) | (sign2 << 31) + : funct3 == 0b001 // fsgnjn + ? (fp_registers.u32[register_source1 * 2 + 1] & 0x7fffffff) | ((sign2 ^ 1) << 31) + : (fp_registers.u32[register_source1 * 2 + 1] & 0x7fffffff) | ((sign1 ^ sign2) << 31) // fsgnjx + ); + } + else { + uint32_t sign1 = fp_registers.u32[register_source1 * 2] >> 31; + uint32_t sign2 = fp_registers.u32[register_source2 * 2] >> 31; + fp_registers.u32[register_destination * 2] = ( + funct3 == 0b000 // fsgnj + ? (fp_registers.u32[register_source1 * 2] & 0x7fffffff) | (sign2 << 31) + : funct3 == 0b001 // fsgnjn + ? (fp_registers.u32[register_source1 * 2] & 0x7fffffff) | ((sign2 ^ 1) << 31) + : (fp_registers.u32[register_source1 * 2] & 0x7fffffff) | ((sign1 ^ sign2) << 31) // fsgnjx + ); + fp_registers.u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + case 0b00101: { // fmin/fmax + if (is_double) { + double val1 = fp_registers.f64[register_source1]; + double val2 = fp_registers.f64[register_source2]; + fp_registers.f64[register_destination] = ( + funct3 == 0b000 // fmin + ? (val1 < val2 ? val1 : val2) + : (val1 > val2 ? val1 : val2) // fmax + ); + } + else { + float val1 = fp_registers.f32[register_source1 * 2]; + float val2 = fp_registers.f32[register_source2 * 2]; + fp_registers.f32[register_destination * 2] = ( + funct3 == 0b000 // fmin + ? (val1 < val2 ? val1 : val2) + : (val1 > val2 ? val1 : val2) // fmax + ); + fp_registers.u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + } + case 0b01000: // fcvt.s.d/fcvt.d.s + if (is_double) { // fcvt.d.s + fp_registers.f64[register_destination] = fp_registers.f32[register_source1 * 2]; + } + else { // fcvt.s.d + fp_registers.f32[register_destination * 2] = fp_registers.f64[register_source1]; + fp_registers.u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + case 0b10100: // fcmp (feq/flt/fle) + if (is_double) { + double val1 = fp_registers.f64[register_source1]; + double val2 = fp_registers.f64[register_source2]; + registers[register_destination] = ( + funct3 == 0b010 // feq + ? (val1 == val2 ? 1 : 0) + : funct3 == 0b001 // flt + ? (val1 < val2 ? 1 : 0) + : (val1 <= val2 ? 1 : 0) // fle + ); + } + else { + float val1 = fp_registers.f32[register_source1 * 2]; + float val2 = fp_registers.f32[register_source2 * 2]; + registers[register_destination] = ( + funct3 == 0b010 // feq + ? (val1 == val2 ? 1 : 0) + : funct3 == 0b001 // flt + ? (val1 < val2 ? 1 : 0) + : (val1 <= val2 ? 1 : 0) // fle + ); + } + break; + case 0b11000: // fcvt.w.s/fcvt.w.d/fcvt.wu.s/fcvt.wu.d + if (is_double) { + double val = fp_registers.f64[register_source1]; + if (register_source2 == 0b00000) { // fcvt.w.d + registers[register_destination] = (int32_t)val; + } + else if (register_source2 == 0b00001) { // fcvt.wu.d + registers_unsigned[register_destination] = (uint32_t)val; + } + } + else { + float val = fp_registers.f32[register_source1 * 2]; + if (register_source2 == 0b00000) { // fcvt.w.s + registers[register_destination] = (int32_t)val; + } + else if (register_source2 == 0b00001) { // fcvt.wu.s + registers_unsigned[register_destination] = (uint32_t)val; + } + } + break; + case 0b11010: // fcvt.s.w/fcvt.d.w/fcvt.s.wu/fcvt.d.wu + if (is_double) { + if (register_source2 == 0b00000) { // fcvt.d.w + fp_registers.f64[register_destination] = (double)registers[register_source1]; + } + else if (register_source2 == 0b00001) { // fcvt.d.wu + fp_registers.f64[register_destination] = (double)registers_unsigned[register_source1]; + } + } + else { + if (register_source2 == 0b00000) { // fcvt.s.w + fp_registers.f32[register_destination * 2] = (float)registers[register_source1]; + } + else if (register_source2 == 0b00001) { // fcvt.s.wu + fp_registers.f32[register_destination * 2] = (float)registers_unsigned[register_source1]; + } + fp_registers.u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + case 0b11100: // fmv.x.w/fmv.x.d/fclass + if (funct3 == 0b000) { // fmv.x.w/fmv.x.d + if (is_double) { // fmv.x.d (RV64 only, not implemented) + error_message = "illegal instruction"; return; + } + else { // fmv.x.w + registers[register_destination] = fp_registers.i32[register_source1 * 2]; + } + } + else if (funct3 == 0b001) { // fclass + if (is_double) { + uint32_t bits_hi = fp_registers.u32[register_source1 * 2 + 1]; + uint32_t bits_lo = fp_registers.u32[register_source1 * 2]; + uint32_t sign = bits_hi >> 31; + uint32_t exp = (bits_hi >> 20) & 0x7ff; + uint64_t mantissa = ((uint64_t)(bits_hi & 0xfffff) << 32) | bits_lo; + + registers[register_destination] = ( + exp == 0 && mantissa == 0 + ? (sign ? 0x008 : 0x001) // -0 or +0 + : exp == 0 + ? (sign ? 0x010 : 0x002) // -subnormal or +subnormal + : exp == 0x7ff && mantissa == 0 + ? (sign ? 0x080 : 0x004) // -inf or +inf + : exp == 0x7ff + ? 0x200 // qNaN or sNaN + : (sign ? 0x040 : 0x020) // -normal or +normal + ); + } + else { + uint32_t bits = fp_registers.u32[register_source1 * 2]; + uint32_t sign = bits >> 31; + uint32_t exp = (bits >> 23) & 0xff; + uint32_t mantissa = bits & 0x7fffff; + + registers[register_destination] = ( + exp == 0 && mantissa == 0 + ? (sign ? 0x008 : 0x001) // -0 or +0 + : exp == 0 + ? (sign ? 0x010 : 0x002) // -subnormal or +subnormal + : exp == 0xff && mantissa == 0 + ? (sign ? 0x080 : 0x004) // -inf or +inf + : exp == 0xff + ? 0x200 // qNaN or sNaN + : (sign ? 0x040 : 0x020) // -normal or +normal + ); + } + } + break; + case 0b11110: // fmv.w.x + if (is_double) { // fmv.d.x (RV64 only, not implemented) + error_message = "illegal instruction"; return; + } + else { // fmv.w.x + fp_registers.i32[register_destination * 2] = registers[register_source1]; + fp_registers.u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + default: + error_message = "illegal instruction"; return; + } + break; + } case 0b11000000: // branch case 0b11000001: case 0b11000100: diff --git a/js/main.js b/js/main.js index b73075c..00c0b9e 100644 --- a/js/main.js +++ b/js/main.js @@ -8,6 +8,7 @@ const OOB_BITS_8 = ~(MEMORY_SIZE - 1); // byte access const OOB_BITS_16 = ~(MEMORY_SIZE - 1 - 1); // halfword access const OOB_BITS_32 = ~(MEMORY_SIZE - 1 - 3); // word access const OOB_BITS_PC = ~(MEMORY_SIZE / 4 - 1); // program counter (word index) +const OOB_BITS_64 = ~(MEMORY_SIZE - 1 - 7); // double word access const registers_unsigned = new Uint32Array(32); const registers = new Int32Array(registers_unsigned.buffer); @@ -16,6 +17,12 @@ const memory32 = new Int32Array(memory32_unsigned.buffer); const memory16 = new Uint16Array(memory32_unsigned.buffer); const memory8 = new Uint8Array(memory32_unsigned.buffer); +// floating-point registers (64-bit for D extension compatibility) +const fp_registers_buffer = new ArrayBuffer(32 * 8); +const fp_registers_f32 = new Float32Array(fp_registers_buffer); +const fp_registers_f64 = new Float64Array(fp_registers_buffer); +const fp_registers_u32 = new Uint32Array(fp_registers_buffer); +const fp_registers_i32 = new Int32Array(fp_registers_buffer); // index for 32 bit! let program_counter = 0; @@ -70,6 +77,22 @@ function tick() { registers[register_destination] = memory16[addr >>> 1]; break; } + // floating-point load + case 0b00001010: { // flw + const addr = registers[register_source1] + (instruction >> 20) | 0; + if (addr & OOB_BITS_32) throw 'out of bounds'; + fp_registers_u32[register_destination * 2] = memory32_unsigned[addr >>> 2]; + fp_registers_u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + break; + } + case 0b00001011: { // fld + const addr = registers[register_source1] + (instruction >> 20) | 0; + if (addr & OOB_BITS_64) throw 'out of bounds'; + const word_index = addr >>> 2; + fp_registers_u32[register_destination * 2] = memory32_unsigned[word_index]; + fp_registers_u32[register_destination * 2 + 1] = memory32_unsigned[word_index + 1]; + break; + } // fence // register+immediate case 0b00100000: // addi @@ -132,6 +155,21 @@ function tick() { memory32[addr >>> 2] = registers[register_source2]; break; } + // floating-point store + case 0b01001010: { // fsw + const addr = registers[register_source1] + (instruction >> 25 << 5 | register_destination) | 0; + if (addr & OOB_BITS_32) throw 'out of bounds'; + memory32_unsigned[addr >>> 2] = fp_registers_u32[register_source2 * 2]; + break; + } + case 0b01001011: { // fsd + const addr = registers[register_source1] + (instruction >> 25 << 5 | register_destination) | 0; + if (addr & OOB_BITS_64) throw 'out of bounds'; + const word_index = addr >>> 2; + memory32_unsigned[word_index] = fp_registers_u32[register_source2 * 2]; + memory32_unsigned[word_index + 1] = fp_registers_u32[register_source2 * 2 + 1]; + break; + } // atomic case 0b01011010: { const addr = registers[register_source1]; @@ -309,6 +347,300 @@ function tick() { case 0b01101111: registers[register_destination] = instruction & 0xfffff000; break; + // fused multiply-add (F and D extensions) + case 0b10000010: // fmadd.s + case 0b10000011: { // fmadd.d + const register_source3 = instruction >>> 27; + const is_double = funct3 & 1; + if (is_double) { + fp_registers_f64[register_destination] = fp_registers_f64[register_source1] * fp_registers_f64[register_source2] + fp_registers_f64[register_source3]; + } + else { + fp_registers_f32[register_destination * 2] = fp_registers_f32[register_source1 * 2] * fp_registers_f32[register_source2 * 2] + fp_registers_f32[register_source3 * 2]; + fp_registers_u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + } + case 0b10001010: // fmsub.s + case 0b10001011: { // fmsub.d + const register_source3 = instruction >>> 27; + const is_double = funct3 & 1; + if (is_double) { + fp_registers_f64[register_destination] = fp_registers_f64[register_source1] * fp_registers_f64[register_source2] - fp_registers_f64[register_source3]; + } + else { + fp_registers_f32[register_destination * 2] = fp_registers_f32[register_source1 * 2] * fp_registers_f32[register_source2 * 2] - fp_registers_f32[register_source3 * 2]; + fp_registers_u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + } + case 0b10010010: // fnmsub.s + case 0b10010011: { // fnmsub.d + const register_source3 = instruction >>> 27; + const is_double = funct3 & 1; + if (is_double) { + fp_registers_f64[register_destination] = -(fp_registers_f64[register_source1] * fp_registers_f64[register_source2]) + fp_registers_f64[register_source3]; + } + else { + fp_registers_f32[register_destination * 2] = -(fp_registers_f32[register_source1 * 2] * fp_registers_f32[register_source2 * 2]) + fp_registers_f32[register_source3 * 2]; + fp_registers_u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + } + case 0b10011010: // fnmadd.s + case 0b10011011: { // fnmadd.d + const register_source3 = instruction >>> 27; + const is_double = funct3 & 1; + if (is_double) { + fp_registers_f64[register_destination] = -(fp_registers_f64[register_source1] * fp_registers_f64[register_source2] + fp_registers_f64[register_source3]); + } + else { + fp_registers_f32[register_destination * 2] = -(fp_registers_f32[register_source1 * 2] * fp_registers_f32[register_source2 * 2] + fp_registers_f32[register_source3 * 2]); + fp_registers_u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + } + // floating-point operations + case 0b10100000: // fadd.s/fadd.d + case 0b10100001: + case 0b10100010: + case 0b10100011: + case 0b10100100: + case 0b10100101: + case 0b10100110: + case 0b10100111: { + const funct7 = instruction >>> 25; + const funct5 = funct7 >>> 2; + const is_double = (funct7 & 1); + + switch (funct5) { + case 0b00000: // fadd + if (is_double) { + fp_registers_f64[register_destination] = fp_registers_f64[register_source1] + fp_registers_f64[register_source2]; + } + else { + fp_registers_f32[register_destination * 2] = fp_registers_f32[register_source1 * 2] + fp_registers_f32[register_source2 * 2]; + fp_registers_u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + case 0b00001: // fsub + if (is_double) { + fp_registers_f64[register_destination] = fp_registers_f64[register_source1] - fp_registers_f64[register_source2]; + } + else { + fp_registers_f32[register_destination * 2] = fp_registers_f32[register_source1 * 2] - fp_registers_f32[register_source2 * 2]; + fp_registers_u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + case 0b00010: // fmul + if (is_double) { + fp_registers_f64[register_destination] = fp_registers_f64[register_source1] * fp_registers_f64[register_source2]; + } + else { + fp_registers_f32[register_destination * 2] = fp_registers_f32[register_source1 * 2] * fp_registers_f32[register_source2 * 2]; + fp_registers_u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + case 0b00011: // fdiv + if (is_double) { + fp_registers_f64[register_destination] = fp_registers_f64[register_source1] / fp_registers_f64[register_source2]; + } + else { + fp_registers_f32[register_destination * 2] = fp_registers_f32[register_source1 * 2] / fp_registers_f32[register_source2 * 2]; + fp_registers_u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + case 0b01011: // fsqrt + if (is_double) { + fp_registers_f64[register_destination] = Math.sqrt(fp_registers_f64[register_source1]); + } + else { + fp_registers_f32[register_destination * 2] = Math.sqrt(fp_registers_f32[register_source1 * 2]); + fp_registers_u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + case 0b00100: // fsgnj/fsgnjn/fsgnjx + if (is_double) { + const sign1 = fp_registers_u32[register_source1 * 2 + 1] >>> 31; + const sign2 = fp_registers_u32[register_source2 * 2 + 1] >>> 31; + fp_registers_u32[register_destination * 2] = fp_registers_u32[register_source1 * 2]; + fp_registers_u32[register_destination * 2 + 1] = ( + funct3 === 0b000 // fsgnj + ? (fp_registers_u32[register_source1 * 2 + 1] & 0x7fffffff) | (sign2 << 31) + : funct3 === 0b001 // fsgnjn + ? (fp_registers_u32[register_source1 * 2 + 1] & 0x7fffffff) | ((sign2 ^ 1) << 31) + : (fp_registers_u32[register_source1 * 2 + 1] & 0x7fffffff) | ((sign1 ^ sign2) << 31) // fsgnjx + ); + } + else { + const sign1 = fp_registers_u32[register_source1 * 2] >>> 31; + const sign2 = fp_registers_u32[register_source2 * 2] >>> 31; + fp_registers_u32[register_destination * 2] = ( + funct3 === 0b000 // fsgnj + ? (fp_registers_u32[register_source1 * 2] & 0x7fffffff) | (sign2 << 31) + : funct3 === 0b001 // fsgnjn + ? (fp_registers_u32[register_source1 * 2] & 0x7fffffff) | ((sign2 ^ 1) << 31) + : (fp_registers_u32[register_source1 * 2] & 0x7fffffff) | ((sign1 ^ sign2) << 31) // fsgnjx + ); + fp_registers_u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + case 0b00101: // fmin/fmax + if (is_double) { + const val1 = fp_registers_f64[register_source1]; + const val2 = fp_registers_f64[register_source2]; + fp_registers_f64[register_destination] = ( + funct3 === 0b000 // fmin + ? Math.min(val1, val2) + : Math.max(val1, val2) // fmax + ); + } + else { + const val1 = fp_registers_f32[register_source1 * 2]; + const val2 = fp_registers_f32[register_source2 * 2]; + fp_registers_f32[register_destination * 2] = ( + funct3 === 0b000 // fmin + ? Math.min(val1, val2) + : Math.max(val1, val2) // fmax + ); + fp_registers_u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + case 0b01000: // fcvt.s.d/fcvt.d.s + if (is_double) { // fcvt.d.s + fp_registers_f64[register_destination] = fp_registers_f32[register_source1 * 2]; + } + else { // fcvt.s.d + fp_registers_f32[register_destination * 2] = fp_registers_f64[register_source1]; + fp_registers_u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + case 0b10100: // fcmp (feq/flt/fle) + if (is_double) { + const val1 = fp_registers_f64[register_source1]; + const val2 = fp_registers_f64[register_source2]; + registers[register_destination] = ( + funct3 === 0b010 // feq + ? (val1 === val2 ? 1 : 0) + : funct3 === 0b001 // flt + ? (val1 < val2 ? 1 : 0) + : (val1 <= val2 ? 1 : 0) // fle + ); + } + else { + const val1 = fp_registers_f32[register_source1 * 2]; + const val2 = fp_registers_f32[register_source2 * 2]; + registers[register_destination] = ( + funct3 === 0b010 // feq + ? (val1 === val2 ? 1 : 0) + : funct3 === 0b001 // flt + ? (val1 < val2 ? 1 : 0) + : (val1 <= val2 ? 1 : 0) // fle + ); + } + break; + case 0b11000: // fcvt.w.s/fcvt.w.d/fcvt.wu.s/fcvt.wu.d + if (is_double) { + const val = fp_registers_f64[register_source1]; + if (register_source2 === 0b00000) { // fcvt.w.d + registers[register_destination] = val; + } + else if (register_source2 === 0b00001) { // fcvt.wu.d + registers_unsigned[register_destination] = val >>> 0; + } + } + else { + const val = fp_registers_f32[register_source1 * 2]; + if (register_source2 === 0b00000) { // fcvt.w.s + registers[register_destination] = val; + } + else if (register_source2 === 0b00001) { // fcvt.wu.s + registers_unsigned[register_destination] = val >>> 0; + } + } + break; + case 0b11010: // fcvt.s.w/fcvt.d.w/fcvt.s.wu/fcvt.d.wu + if (is_double) { + if (register_source2 === 0b00000) { // fcvt.d.w + fp_registers_f64[register_destination] = registers[register_source1]; + } + else if (register_source2 === 0b00001) { // fcvt.d.wu + fp_registers_f64[register_destination] = registers_unsigned[register_source1]; + } + } + else { + if (register_source2 === 0b00000) { // fcvt.s.w + fp_registers_f32[register_destination * 2] = registers[register_source1]; + } + else if (register_source2 === 0b00001) { // fcvt.s.wu + fp_registers_f32[register_destination * 2] = registers_unsigned[register_source1]; + } + fp_registers_u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + case 0b11100: // fmv.x.w/fmv.x.d/fclass + if (funct3 === 0b000) { // fmv.x.w/fmv.x.d + if (is_double) { // fmv.x.d (RV64 only, not implemented) + throw 'illegal instruction'; + } + else { // fmv.x.w + registers[register_destination] = fp_registers_i32[register_source1 * 2]; + } + } + else if (funct3 === 0b001) { // fclass + if (is_double) { + const bits_hi = fp_registers_u32[register_source1 * 2 + 1]; + const bits_lo = fp_registers_u32[register_source1 * 2]; + const sign = bits_hi >>> 31; + const exp = (bits_hi >>> 20) & 0x7ff; + const mantissa = ((bits_hi & 0xfffff) << 32) | bits_lo; + + registers[register_destination] = ( + exp === 0 && mantissa === 0 + ? (sign ? 0x008 : 0x001) // -0 or +0 + : exp === 0 + ? (sign ? 0x010 : 0x002) // -subnormal or +subnormal + : exp === 0x7ff && mantissa === 0 + ? (sign ? 0x080 : 0x004) // -inf or +inf + : exp === 0x7ff + ? 0x200 // qNaN or sNaN + : (sign ? 0x040 : 0x020) // -normal or +normal + ); + } + else { + const bits = fp_registers_u32[register_source1 * 2]; + const sign = bits >>> 31; + const exp = (bits >>> 23) & 0xff; + const mantissa = bits & 0x7fffff; + + registers[register_destination] = ( + exp === 0 && mantissa === 0 + ? (sign ? 0x008 : 0x001) // -0 or +0 + : exp === 0 + ? (sign ? 0x010 : 0x002) // -subnormal or +subnormal + : exp === 0xff && mantissa === 0 + ? (sign ? 0x080 : 0x004) // -inf or +inf + : exp === 0xff + ? 0x200 // qNaN or sNaN + : (sign ? 0x040 : 0x020) // -normal or +normal + ); + } + } + break; + case 0b11110: // fmv.w.x + if (is_double) { // fmv.d.x (RV64 only, not implemented) + throw 'illegal instruction'; + } + else { // fmv.w.x + fp_registers_i32[register_destination * 2] = registers[register_source1]; + fp_registers_u32[register_destination * 2 + 1] = 0xffffffff; // NaN-box + } + break; + default: + throw 'illegal floating-point instruction'; + } + break; + } case 0b11000000: // branch case 0b11000001: case 0b11000100: diff --git a/js/package.json b/js/package.json index a93ce87..290aa55 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "cpu", - "version": "0.1.0", + "version": "0.2.0", "description": "risc-v emulator in js", "scripts": { "start": "node main.js" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index eab1a40..967858c 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cpu" -version = "0.1.0" +version = "0.2.0" edition = "2021" authors = ["L3P3 "] license = "Zlib" diff --git a/rust/main.rs b/rust/main.rs index 7110366..fe47163 100644 --- a/rust/main.rs +++ b/rust/main.rs @@ -10,6 +10,7 @@ const MEMORY_SIZE: usize = 64 * 1024; const OOB_BITS_8: i32 = !(MEMORY_SIZE as i32 - 1); // byte access const OOB_BITS_16: i32 = !(MEMORY_SIZE as i32 - 1 - 1); // halfword access const OOB_BITS_32: i32 = !(MEMORY_SIZE as i32 - 1 - 3); // word access +const OOB_BITS_64: i32 = !(MEMORY_SIZE as i32 - 1 - 7); // double word access const OOB_BITS_PC: u32 = !((MEMORY_SIZE / 4) as u32 - 1); // program counter (word index) struct CPU { @@ -19,6 +20,7 @@ struct CPU { program_ended: bool, error_message: Option<&'static str>, reservation_address: i32, + fp_registers: [u64; 32], // 64-bit storage for F and D extensions } impl CPU { @@ -30,6 +32,7 @@ impl CPU { program_ended: false, error_message: None, reservation_address: -1, + fp_registers: [0; 32], } } @@ -98,6 +101,46 @@ impl CPU { self.registers[index] as u32 } + #[inline(always)] + fn fp_f32(&self, index: usize) -> f32 { + f32::from_bits((self.fp_registers[index] & 0xffffffff) as u32) + } + + #[inline(always)] + fn fp_f32_set(&mut self, index: usize, value: f32) { + self.fp_registers[index] = (value.to_bits() as u64) | 0xffffffff00000000; // NaN-box + } + + #[inline(always)] + fn fp_f64(&self, index: usize) -> f64 { + f64::from_bits(self.fp_registers[index]) + } + + #[inline(always)] + fn fp_f64_set(&mut self, index: usize, value: f64) { + self.fp_registers[index] = value.to_bits(); + } + + #[inline(always)] + fn fp_u32(&self, index: usize) -> u32 { + (self.fp_registers[index] & 0xffffffff) as u32 + } + + #[inline(always)] + fn fp_u32_set(&mut self, index: usize, value: u32) { + self.fp_registers[index] = (value as u64) | 0xffffffff00000000; // NaN-box + } + + #[inline(always)] + fn fp_i32(&self, index: usize) -> i32 { + (self.fp_registers[index] & 0xffffffff) as i32 + } + + #[inline(always)] + fn fp_i32_set(&mut self, index: usize, value: i32) { + self.fp_registers[index] = (value as u32 as u64) | 0xffffffff00000000; // NaN-box + } + fn tick(&mut self) { // make it constant self.registers[0] = 0; @@ -160,6 +203,29 @@ impl CPU { } self.registers[register_destination] = self.memory16()[(addr >> 1) as usize] as i32; } + // floating-point load + 0b00001010 => { // flw + let addr = self.registers[register_source1] + .wrapping_add((instruction as i32) >> 20); + if addr & OOB_BITS_32 != 0 { + self.error_message = Some("out of bounds"); + return; + } + let value = self.memory32[(addr >> 2) as usize]; + self.fp_u32_set(register_destination, value); + } + 0b00001011 => { // fld + let addr = self.registers[register_source1] + .wrapping_add((instruction as i32) >> 20); + if addr & OOB_BITS_64 != 0 { + self.error_message = Some("out of bounds"); + return; + } + let word_index = (addr >> 2) as usize; + let lo = self.memory32[word_index] as u64; + let hi = self.memory32[word_index + 1] as u64; + self.fp_registers[register_destination] = lo | (hi << 32); + } // fence // register+immediate 0b00100000 => { // addi @@ -249,6 +315,30 @@ impl CPU { } self.memory32_signed_mut()[(addr >> 2) as usize] = self.registers[register_source2]; } + // floating-point store + 0b01001010 => { // fsw + let addr = self.registers[register_source1].wrapping_add( + (((instruction as i32) >> 25) << 5) | (register_destination as i32), + ); + if addr & OOB_BITS_32 != 0 { + self.error_message = Some("out of bounds"); + return; + } + self.memory32[(addr >> 2) as usize] = self.fp_u32(register_source2); + } + 0b01001011 => { // fsd + let addr = self.registers[register_source1].wrapping_add( + (((instruction as i32) >> 25) << 5) | (register_destination as i32), + ); + if addr & OOB_BITS_64 != 0 { + self.error_message = Some("out of bounds"); + return; + } + let word_index = (addr >> 2) as usize; + let value = self.fp_registers[register_source2]; + self.memory32[word_index] = (value & 0xffffffff) as u32; + self.memory32[word_index + 1] = (value >> 32) as u32; + } // atomic 0b01011010 => 'atomic: { let addr = self.registers[register_source1]; @@ -445,6 +535,273 @@ impl CPU { 0b01101111 => { // lui ;) self.registers[register_destination] = (instruction & 0xfffff000) as i32; } + // fused multiply-add (F and D extensions) + 0b10000010 | 0b10000011 => { // fmadd.s/fmadd.d + let register_source3 = (instruction >> 27) as usize; + let is_double = (funct3 & 1) != 0; + if is_double { + let result = self.fp_f64(register_source1) * self.fp_f64(register_source2) + self.fp_f64(register_source3); + self.fp_f64_set(register_destination, result); + } + else { + let result = self.fp_f32(register_source1) * self.fp_f32(register_source2) + self.fp_f32(register_source3); + self.fp_f32_set(register_destination, result); + } + } + 0b10001010 | 0b10001011 => { // fmsub.s/fmsub.d + let register_source3 = (instruction >> 27) as usize; + let is_double = (funct3 & 1) != 0; + if is_double { + let result = self.fp_f64(register_source1) * self.fp_f64(register_source2) - self.fp_f64(register_source3); + self.fp_f64_set(register_destination, result); + } + else { + let result = self.fp_f32(register_source1) * self.fp_f32(register_source2) - self.fp_f32(register_source3); + self.fp_f32_set(register_destination, result); + } + } + 0b10010010 | 0b10010011 => { // fnmsub.s/fnmsub.d + let register_source3 = (instruction >> 27) as usize; + let is_double = (funct3 & 1) != 0; + if is_double { + let result = -(self.fp_f64(register_source1) * self.fp_f64(register_source2)) + self.fp_f64(register_source3); + self.fp_f64_set(register_destination, result); + } + else { + let result = -(self.fp_f32(register_source1) * self.fp_f32(register_source2)) + self.fp_f32(register_source3); + self.fp_f32_set(register_destination, result); + } + } + 0b10011010 | 0b10011011 => { // fnmadd.s/fnmadd.d + let register_source3 = (instruction >> 27) as usize; + let is_double = (funct3 & 1) != 0; + if is_double { + let result = -(self.fp_f64(register_source1) * self.fp_f64(register_source2) + self.fp_f64(register_source3)); + self.fp_f64_set(register_destination, result); + } + else { + let result = -(self.fp_f32(register_source1) * self.fp_f32(register_source2) + self.fp_f32(register_source3)); + self.fp_f32_set(register_destination, result); + } + } + // floating-point operations + 0b10100000 | 0b10100001 | 0b10100010 | 0b10100011 | + 0b10100100 | 0b10100101 | 0b10100110 | 0b10100111 => { + let funct7 = instruction >> 25; + let funct5 = funct7 >> 2; + let is_double = (funct7 & 1) != 0; + + match funct5 { + 0b00000 => { // fadd + if is_double { + self.fp_f64_set(register_destination, self.fp_f64(register_source1) + self.fp_f64(register_source2)); + } + else { + self.fp_f32_set(register_destination, self.fp_f32(register_source1) + self.fp_f32(register_source2)); + } + } + 0b00001 => { // fsub + if is_double { + self.fp_f64_set(register_destination, self.fp_f64(register_source1) - self.fp_f64(register_source2)); + } + else { + self.fp_f32_set(register_destination, self.fp_f32(register_source1) - self.fp_f32(register_source2)); + } + } + 0b00010 => { // fmul + if is_double { + self.fp_f64_set(register_destination, self.fp_f64(register_source1) * self.fp_f64(register_source2)); + } + else { + self.fp_f32_set(register_destination, self.fp_f32(register_source1) * self.fp_f32(register_source2)); + } + } + 0b00011 => { // fdiv + if is_double { + self.fp_f64_set(register_destination, self.fp_f64(register_source1) / self.fp_f64(register_source2)); + } + else { + self.fp_f32_set(register_destination, self.fp_f32(register_source1) / self.fp_f32(register_source2)); + } + } + 0b01011 => { // fsqrt + if is_double { + self.fp_f64_set(register_destination, self.fp_f64(register_source1).sqrt()); + } + else { + self.fp_f32_set(register_destination, self.fp_f32(register_source1).sqrt()); + } + } + 0b00100 => { // fsgnj/fsgnjn/fsgnjx + if is_double { + let val1 = self.fp_registers[register_source1]; + let val2 = self.fp_registers[register_source2]; + let sign1 = (val1 >> 63) as u32; + let sign2 = (val2 >> 63) as u32; + let result = match funct3 { + 0b000 => (val1 & 0x7fffffffffffffff) | ((sign2 as u64) << 63), // fsgnj + 0b001 => (val1 & 0x7fffffffffffffff) | (((sign2 ^ 1) as u64) << 63), // fsgnjn + _ => (val1 & 0x7fffffffffffffff) | (((sign1 ^ sign2) as u64) << 63), // fsgnjx + }; + self.fp_registers[register_destination] = result; + } + else { + let val1 = self.fp_u32(register_source1); + let val2 = self.fp_u32(register_source2); + let sign1 = val1 >> 31; + let sign2 = val2 >> 31; + let result = match funct3 { + 0b000 => (val1 & 0x7fffffff) | (sign2 << 31), // fsgnj + 0b001 => (val1 & 0x7fffffff) | ((sign2 ^ 1) << 31), // fsgnjn + _ => (val1 & 0x7fffffff) | ((sign1 ^ sign2) << 31), // fsgnjx + }; + self.fp_u32_set(register_destination, result); + } + } + 0b00101 => { // fmin/fmax + if is_double { + let val1 = self.fp_f64(register_source1); + let val2 = self.fp_f64(register_source2); + let result = if funct3 == 0b000 { val1.min(val2) } else { val1.max(val2) }; + self.fp_f64_set(register_destination, result); + } + else { + let val1 = self.fp_f32(register_source1); + let val2 = self.fp_f32(register_source2); + let result = if funct3 == 0b000 { val1.min(val2) } else { val1.max(val2) }; + self.fp_f32_set(register_destination, result); + } + } + 0b01000 => { // fcvt.s.d/fcvt.d.s + if is_double { // fcvt.d.s + self.fp_f64_set(register_destination, self.fp_f32(register_source1) as f64); + } + else { // fcvt.s.d + self.fp_f32_set(register_destination, self.fp_f64(register_source1) as f32); + } + } + 0b10100 => { // fcmp (feq/flt/fle) + if is_double { + let val1 = self.fp_f64(register_source1); + let val2 = self.fp_f64(register_source2); + self.registers[register_destination] = match funct3 { + 0b010 => (val1 == val2) as i32, // feq + 0b001 => (val1 < val2) as i32, // flt + _ => (val1 <= val2) as i32, // fle + }; + } + else { + let val1 = self.fp_f32(register_source1); + let val2 = self.fp_f32(register_source2); + self.registers[register_destination] = match funct3 { + 0b010 => (val1 == val2) as i32, // feq + 0b001 => (val1 < val2) as i32, // flt + _ => (val1 <= val2) as i32, // fle + }; + } + } + 0b11000 => { // fcvt.w.s/fcvt.w.d/fcvt.wu.s/fcvt.wu.d + if is_double { + let val = self.fp_f64(register_source1); + if register_source2 == 0b00000 { // fcvt.w.d + self.registers[register_destination] = val as i32; + } + else if register_source2 == 0b00001 { // fcvt.wu.d + self.registers[register_destination] = (val as u32) as i32; + } + } + else { + let val = self.fp_f32(register_source1); + if register_source2 == 0b00000 { // fcvt.w.s + self.registers[register_destination] = val as i32; + } + else if register_source2 == 0b00001 { // fcvt.wu.s + self.registers[register_destination] = (val as u32) as i32; + } + } + } + 0b11010 => { // fcvt.s.w/fcvt.d.w/fcvt.s.wu/fcvt.d.wu + if is_double { + if register_source2 == 0b00000 { // fcvt.d.w + self.fp_f64_set(register_destination, self.registers[register_source1] as f64); + } + else if register_source2 == 0b00001 { // fcvt.d.wu + self.fp_f64_set(register_destination, self.register_unsigned(register_source1) as f64); + } + } + else { + if register_source2 == 0b00000 { // fcvt.s.w + self.fp_f32_set(register_destination, self.registers[register_source1] as f32); + } + else if register_source2 == 0b00001 { // fcvt.s.wu + self.fp_f32_set(register_destination, self.register_unsigned(register_source1) as f32); + } + } + } + 0b11100 => { // fmv.x.w/fmv.x.d/fclass + if funct3 == 0b000 { // fmv.x.w/fmv.x.d + if is_double { // fmv.x.d (RV64 only, not implemented) + self.error_message = Some("illegal instruction"); + return; + } + else { // fmv.x.w + self.registers[register_destination] = self.fp_i32(register_source1); + } + } + else if funct3 == 0b001 { // fclass + if is_double { + let bits = self.fp_registers[register_source1]; + let sign = (bits >> 63) as u32; + let exp = ((bits >> 52) & 0x7ff) as u32; + let mantissa = bits & 0xfffffffffffff; + + self.registers[register_destination] = if exp == 0 && mantissa == 0 { + if sign != 0 { 0x008 } else { 0x001 } // -0 or +0 + } else if exp == 0 { + if sign != 0 { 0x010 } else { 0x002 } // -subnormal or +subnormal + } else if exp == 0x7ff && mantissa == 0 { + if sign != 0 { 0x080 } else { 0x004 } // -inf or +inf + } else if exp == 0x7ff { + 0x200 // qNaN or sNaN + } else { + if sign != 0 { 0x040 } else { 0x020 } // -normal or +normal + }; + } + else { + let bits = self.fp_u32(register_source1); + let sign = bits >> 31; + let exp = (bits >> 23) & 0xff; + let mantissa = bits & 0x7fffff; + + self.registers[register_destination] = if exp == 0 && mantissa == 0 { + if sign != 0 { 0x008 } else { 0x001 } // -0 or +0 + } else if exp == 0 { + if sign != 0 { 0x010 } else { 0x002 } // -subnormal or +subnormal + } else if exp == 0xff && mantissa == 0 { + if sign != 0 { 0x080 } else { 0x004 } // -inf or +inf + } else if exp == 0xff { + 0x200 // qNaN or sNaN + } else { + if sign != 0 { 0x040 } else { 0x020 } // -normal or +normal + }; + } + } + } + 0b11110 => { // fmv.w.x + if is_double { // fmv.d.x (RV64 only, not implemented) + self.error_message = Some("illegal instruction"); + return; + } + else { // fmv.w.x + self.fp_i32_set(register_destination, self.registers[register_source1]); + } + } + _ => { + self.error_message = Some("illegal instruction"); + return; + } + } + } 0b11000000 | 0b11000001 | 0b11000100 | diff --git a/tests/double_basic.s b/tests/double_basic.s new file mode 100644 index 0000000..f1ca6c2 --- /dev/null +++ b/tests/double_basic.s @@ -0,0 +1,48 @@ +# Basic double-precision floating-point tests (D extension) +# Test FLD, FSD, FADD.D, FSUB.D, FMUL.D, FDIV.D + +.text +.globl _start + +_start: + # Initialize some double-precision values in memory + # 2.0 in double: 0x4000000000000000 + lui x1, 0x40000 + sw x1, 4(x0) # store high word at address 4 + sw x0, 0(x0) # store low word at address 0 + + # 3.0 in double: 0x4008000000000000 + lui x2, 0x40080 + sw x2, 12(x0) # store high word at address 12 + sw x0, 8(x0) # store low word at address 8 + + # Load double-precision values + fld f0, 0(x0) # f0 = 2.0 + fld f1, 8(x0) # f1 = 3.0 + + # Test FADD.D: 2.0 + 3.0 = 5.0 + fadd.d f2, f0, f1 + + # Test FSUB.D: 3.0 - 2.0 = 1.0 + fsub.d f3, f1, f0 + + # Test FMUL.D: 2.0 * 3.0 = 6.0 + fmul.d f4, f0, f1 + + # Test FDIV.D: 6.0 / 2.0 = 3.0 + fdiv.d f5, f4, f0 + + # Store results back to memory + fsd f2, 16(x0) # store 5.0 at address 16 + fsd f3, 24(x0) # store 1.0 at address 24 + fsd f4, 32(x0) # store 6.0 at address 32 + fsd f5, 40(x0) # store 3.0 at address 40 + + # Load results into integer registers for verification + lw x3, 20(x0) # x3 should be 0x40140000 (high word of 5.0) + lw x4, 28(x0) # x4 should be 0x3ff00000 (high word of 1.0) + lw x5, 36(x0) # x5 should be 0x40180000 (high word of 6.0) + lw x6, 44(x0) # x6 should be 0x40080000 (high word of 3.0) + + # Exit + jal x0, . diff --git a/tests/float_basic.s b/tests/float_basic.s new file mode 100644 index 0000000..7c8d800 --- /dev/null +++ b/tests/float_basic.s @@ -0,0 +1,44 @@ +# Basic floating-point tests (F extension) +# Test FLW, FSW, FADD.S, FSUB.S, FMUL.S, FDIV.S + +.text +.globl _start + +_start: + # Initialize some floating-point values in memory + lui x1, 0x40000 # x1 = 0x40000000 (2.0 in float) + sw x1, 0(x0) # store at address 0 + + lui x2, 0x40400 # x2 = 0x40400000 (3.0 in float) + sw x2, 4(x0) # store at address 4 + + # Load floating-point values + flw f0, 0(x0) # f0 = 2.0 + flw f1, 4(x0) # f1 = 3.0 + + # Test FADD.S: 2.0 + 3.0 = 5.0 + fadd.s f2, f0, f1 + + # Test FSUB.S: 3.0 - 2.0 = 1.0 + fsub.s f3, f1, f0 + + # Test FMUL.S: 2.0 * 3.0 = 6.0 + fmul.s f4, f0, f1 + + # Test FDIV.S: 6.0 / 2.0 = 3.0 + fdiv.s f5, f4, f0 + + # Store results back to memory + fsw f2, 8(x0) # store 5.0 at address 8 + fsw f3, 12(x0) # store 1.0 at address 12 + fsw f4, 16(x0) # store 6.0 at address 16 + fsw f5, 20(x0) # store 3.0 at address 20 + + # Load results into integer registers for verification + lw x3, 8(x0) # x3 should be 0x40a00000 (5.0) + lw x4, 12(x0) # x4 should be 0x3f800000 (1.0) + lw x5, 16(x0) # x5 should be 0x40c00000 (6.0) + lw x6, 20(x0) # x6 should be 0x40400000 (3.0) + + # Exit + jal x0, . diff --git a/tests/float_convert.s b/tests/float_convert.s new file mode 100644 index 0000000..a25daf2 --- /dev/null +++ b/tests/float_convert.s @@ -0,0 +1,35 @@ +# Test conversion and comparison instructions +# Test FCVT, FEQ, FLT, FLE + +.text +.globl _start + +_start: + # Test integer to float conversion + addi x1, x0, 42 # x1 = 42 + fcvt.s.w f0, x1 # f0 = 42.0 (float) + + # Test float to integer conversion + fcvt.w.s x2, f0 # x2 = 42 + + # Test comparison + lui x3, 0x42280 # x3 = 0x42280000 (42.0 in float) + sw x3, 0(x0) + flw f1, 0(x0) # f1 = 42.0 + + # Test FEQ: f0 == f1 (should be 1) + feq.s x4, f0, f1 + + # Test with different value + lui x5, 0x41200 # x5 = 0x41200000 (10.0 in float) + sw x5, 4(x0) + flw f2, 4(x0) # f2 = 10.0 + + # Test FLT: f2 < f0 (should be 1, 10 < 42) + flt.s x6, f2, f0 + + # Test FLE: f2 <= f0 (should be 1, 10 <= 42) + fle.s x7, f2, f0 + + # Exit + jal x0, .