diff --git a/rtl/core_modules/alu.v b/rtl/core_modules/alu.v index 58f5aa7..6f6d53d 100644 --- a/rtl/core_modules/alu.v +++ b/rtl/core_modules/alu.v @@ -9,28 +9,81 @@ module alu ( output reg [31:0] ALUoutput ); +wire signed [31:0] rs1_signed = $signed(rs1); +wire signed [31:0] rs2_signed = $signed(rs2); +wire signed [63:0] rs1_signed_ext = {{32{rs1[31]}}, rs1}; +wire signed [63:0] rs2_signed_ext = {{32{rs2[31]}}, rs2}; +wire [63:0] rs1_unsigned_ext = {32'b0, rs1}; +wire [63:0] rs2_unsigned_ext = {32'b0, rs2}; +wire signed [63:0] mul_signed = rs1_signed_ext * rs2_signed_ext; +wire signed [63:0] mul_mixed = rs1_signed_ext * $signed(rs2_unsigned_ext); +wire [63:0] mul_unsigned = rs1_unsigned_ext * rs2_unsigned_ext; +wire div_by_zero = (rs2 == 0); +wire div_overflow = (rs1 == 32'h80000000) && (rs2 == 32'hFFFFFFFF); + +`ifdef FORMAL + // Simplified ALU for formal verification - only basic operations + always @(*) begin + case (instr_id) + INSTR_ADD: ALUoutput = rs1 + rs2; + INSTR_SUB: ALUoutput = rs1 - rs2; + INSTR_XOR: ALUoutput = rs1 ^ rs2; + INSTR_OR: ALUoutput = rs1 | rs2; + INSTR_AND: ALUoutput = rs1 & rs2; + INSTR_ADDI: ALUoutput = rs1 + imm; + INSTR_XORI: ALUoutput = rs1 ^ imm; + INSTR_ORI: ALUoutput = rs1 | imm; + INSTR_ANDI: ALUoutput = rs1 & imm; + default: ALUoutput = 32'h0; + endcase + end +`else + // Full ALU implementation for synthesis always @(*) begin case (instr_id) - INSTR_ADD: ALUoutput = $signed(rs1) + $signed(rs2); // Addition - INSTR_SUB: ALUoutput = $signed(rs1) - $signed(rs2); // Subtraction - INSTR_XOR: ALUoutput = rs1 ^ rs2; // Bitwise XOR - INSTR_OR: ALUoutput = rs1 | rs2; // Bitwise OR - INSTR_AND: ALUoutput = rs1 & rs2; // Bitwise AND - INSTR_SLL: ALUoutput = rs1 << rs2[4:0]; // Logical left shift - INSTR_SRL: ALUoutput = rs1 >> rs2[4:0]; // Logical right shift - INSTR_SRA: ALUoutput = $signed(rs1) >>> rs2[4:0]; // Arithmetic right shift - INSTR_SLT: ALUoutput = {31'b0, $signed(rs1) < $signed(rs2)}; // Set less than (signed comparison) - INSTR_SLTU: ALUoutput = {31'b0, rs1 < rs2}; // Set less than (unsigned comparison) - INSTR_ADDI: ALUoutput = $signed(rs1) + $signed(imm); // Add immediate - INSTR_XORI: ALUoutput = rs1 ^ imm; // Bitwise XOR with immediate - INSTR_ORI: ALUoutput = rs1 | imm; // Bitwise OR with immediate - INSTR_ANDI: ALUoutput = rs1 & imm; // Bitwise AND with immediate - INSTR_SLLI: ALUoutput = rs1 << imm[4:0]; // Logical left shift with immediate - INSTR_SRLI: ALUoutput = rs1 >> imm[4:0]; // Logical right shift with immediate - INSTR_SRAI: ALUoutput = $signed(rs1) >>> imm[4:0]; // Arithmetic right shift with immediate - INSTR_SLTI: ALUoutput = {31'b0, $signed(rs1) < $signed(imm)}; // Set less than immediate (signed comparison) - INSTR_SLTIU: ALUoutput = {31'b0, rs1 < imm}; // Set less than immediate (unsigned comparison) - default: ALUoutput = 0; // Default case: output zero + INSTR_ADD: ALUoutput = $signed(rs1) + $signed(rs2); + INSTR_SUB: ALUoutput = $signed(rs1) - $signed(rs2); + INSTR_XOR: ALUoutput = rs1 ^ rs2; + INSTR_OR: ALUoutput = rs1 | rs2; + INSTR_AND: ALUoutput = rs1 & rs2; + INSTR_SLL: ALUoutput = rs1 << rs2[4:0]; + INSTR_SRL: ALUoutput = rs1 >> rs2[4:0]; + INSTR_SRA: ALUoutput = $signed(rs1) >>> rs2[4:0]; + INSTR_SLT: ALUoutput = {31'b0, $signed(rs1) < $signed(rs2)}; + INSTR_SLTU: ALUoutput = {31'b0, rs1 < rs2}; + INSTR_ADDI: ALUoutput = $signed(rs1) + $signed(imm); + INSTR_XORI: ALUoutput = rs1 ^ imm; + INSTR_ORI: ALUoutput = rs1 | imm; + INSTR_ANDI: ALUoutput = rs1 & imm; + INSTR_SLLI: ALUoutput = rs1 << imm[4:0]; + INSTR_SRLI: ALUoutput = rs1 >> imm[4:0]; + INSTR_SRAI: ALUoutput = $signed(rs1) >>> imm[4:0]; + INSTR_SLTI: ALUoutput = {31'b0, $signed(rs1) < $signed(imm)}; // bugfix: returns 1 or 0 + INSTR_SLTIU: ALUoutput = {31'b0, rs1 < imm}; // bugfix: returns 1 or 0 + INSTR_MUL: ALUoutput = mul_signed[31:0]; + INSTR_MULH: ALUoutput = mul_signed[63:32]; + INSTR_MULHSU: ALUoutput = mul_mixed[63:32]; + INSTR_MULHU: ALUoutput = mul_unsigned[63:32]; + INSTR_DIV: begin + if (div_by_zero) ALUoutput = 32'hFFFFFFFF; + else if (div_overflow) ALUoutput = 32'h80000000; + else ALUoutput = rs1_signed / rs2_signed; + end + INSTR_DIVU: begin + if (div_by_zero) ALUoutput = 32'hFFFFFFFF; + else ALUoutput = rs1 / rs2; + end + INSTR_REM: begin + if (div_by_zero) ALUoutput = rs1; + else if (div_overflow) ALUoutput = 32'h00000000; + else ALUoutput = rs1_signed % rs2_signed; + end + INSTR_REMU: begin + if (div_by_zero) ALUoutput = rs1; + else ALUoutput = rs1 % rs2; + end + default: ALUoutput = 0; endcase end +`endif endmodule diff --git a/rtl/core_modules/decoder.v b/rtl/core_modules/decoder.v index 4fb1f89..5f28bd2 100644 --- a/rtl/core_modules/decoder.v +++ b/rtl/core_modules/decoder.v @@ -60,6 +60,10 @@ module decoder ( }) {7'h00, 3'h0} : instr_id = INSTR_ADD; {7'h20, 3'h0} : instr_id = INSTR_SUB; + {7'h01, 3'h0} : instr_id = INSTR_MUL; + {7'h01, 3'h1} : instr_id = INSTR_MULH; + {7'h01, 3'h2} : instr_id = INSTR_MULHSU; + {7'h01, 3'h3} : instr_id = INSTR_MULHU; {7'h00, 3'h4} : instr_id = INSTR_XOR; {7'h00, 3'h6} : instr_id = INSTR_OR; {7'h00, 3'h7} : instr_id = INSTR_AND; @@ -68,6 +72,10 @@ module decoder ( {7'h20, 3'h5} : instr_id = INSTR_SRA; {7'h00, 3'h2} : instr_id = INSTR_SLT; {7'h00, 3'h3} : instr_id = INSTR_SLTU; + {7'h01, 3'h4} : instr_id = INSTR_DIV; + {7'h01, 3'h5} : instr_id = INSTR_DIVU; + {7'h01, 3'h6} : instr_id = INSTR_REM; + {7'h01, 3'h7} : instr_id = INSTR_REMU; default: instr_id = INSTR_INVALID; endcase end diff --git a/rtl/include/instr_defines.vh b/rtl/include/instr_defines.vh index a748974..39433a0 100644 --- a/rtl/include/instr_defines.vh +++ b/rtl/include/instr_defines.vh @@ -15,6 +15,16 @@ localparam [5:0] INSTR_SRA = 6'h08; localparam [5:0] INSTR_SLT = 6'h09; localparam [5:0] INSTR_SLTU = 6'h0A; +// R-type M extension +localparam [5:0] INSTR_MUL = 6'h30; +localparam [5:0] INSTR_MULH = 6'h31; +localparam [5:0] INSTR_MULHSU = 6'h32; +localparam [5:0] INSTR_MULHU = 6'h33; +localparam [5:0] INSTR_DIV = 6'h34; +localparam [5:0] INSTR_DIVU = 6'h35; +localparam [5:0] INSTR_REM = 6'h36; +localparam [5:0] INSTR_REMU = 6'h37; + // I-type arithmetic localparam [5:0] INSTR_ADDI = 6'h0B; localparam [5:0] INSTR_XORI = 6'h0C; diff --git a/sim/factorial.c b/sim/factorial.c new file mode 100644 index 0000000..f9422f4 --- /dev/null +++ b/sim/factorial.c @@ -0,0 +1,161 @@ +// RISC-V factorial program to exercise all M extension instructions +#include +#include + +#if defined(__linux__) || defined(__APPLE__) +#define HOST +#include +#endif + +#define N 6 // Compute factorial of 6 (6! = 720) +#define DATA_MEM_BASE 0x10000000 +#define CPU_DONE_ADDR (DATA_MEM_BASE + 0xFF) +#define FACTORIAL_ADDR (DATA_MEM_BASE + 0x20) + +int main() { +#ifdef HOST + uint32_t CPU_DONE = 0; +#else + #define CPU_DONE (* (volatile uint8_t *) CPU_DONE_ADDR) +#endif + + uint32_t result = 1; + uint32_t i; + volatile int32_t sink32 = 0; + volatile uint32_t sinku32 = 0; + + // MUL: result = result * i + for (i = 1; i <= N; i++) { + result = result * i; + } + + // MULH: high 32 bits of signed * signed + volatile int32_t mulh_a = (int32_t)0x80000000; + volatile int32_t mulh_b = -2; + int32_t mulh_res = ((int64_t)mulh_a * (int64_t)mulh_b) >> 32; +#ifdef HOST + printf("MULH: high(0x%08x * %d) = %d\n", (uint32_t)mulh_a, mulh_b, mulh_res); +#endif + sink32 = mulh_res; + + mulh_a = 0x7FFFFFFF; + mulh_b = 0x7FFFFFFF; + mulh_res = ((int64_t)mulh_a * (int64_t)mulh_b) >> 32; +#ifdef HOST + printf("MULH: high(%d * %d) = %d\n", mulh_a, mulh_b, mulh_res); +#endif + sink32 = mulh_res; + + // MULHSU: high 32 bits of signed * unsigned + volatile int32_t mulhsu_a = -1; + volatile uint32_t mulhsu_b = 2; + int32_t mulhsu_res = ((int64_t)mulhsu_a * (uint64_t)mulhsu_b) >> 32; +#ifdef HOST + printf("MULHSU: high(%d * %u) = %d\n", mulhsu_a, mulhsu_b, mulhsu_res); +#endif + sink32 = mulhsu_res; + + mulhsu_a = 0x80000000; + mulhsu_b = 2; + mulhsu_res = ((int64_t)mulhsu_a * (uint64_t)mulhsu_b) >> 32; +#ifdef HOST + printf("MULHSU: high(%d * %u) = %d\n", mulhsu_a, mulhsu_b, mulhsu_res); +#endif + sink32 = mulhsu_res; + + // MULHU: high 32 bits of unsigned * unsigned + volatile uint32_t mulhu_a = 0xFFFFFFFF; + volatile uint32_t mulhu_b = 0xFFFFFFFF; + uint32_t mulhu_res = ((uint64_t)mulhu_a * (uint64_t)mulhu_b) >> 32; +#ifdef HOST + printf("MULHU: high(%u * %u) = %u\n", mulhu_a, mulhu_b, mulhu_res); +#endif + sinku32 = mulhu_res; + + mulhu_a = 0x12345678; + mulhu_b = 0x9ABCDEF0; + mulhu_res = ((uint64_t)mulhu_a * (uint64_t)mulhu_b) >> 32; +#ifdef HOST + printf("MULHU: high(%u * %u) = %u\n", mulhu_a, mulhu_b, mulhu_res); +#endif + sinku32 = mulhu_res; + + // DIV: signed division + volatile int32_t div_a = -2; + volatile int32_t div_b = 2; + int32_t div_res = div_a / div_b; +#ifdef HOST + printf("DIV: %d / %d = %d\n", div_a, div_b, div_res); +#endif + sink32 = div_res; + + div_a = 10; + div_b = 0; + div_res = (div_b == 0) ? -1 : div_a / div_b; +#ifdef HOST + printf("DIV: %d / %d = %d\n", div_a, div_b, div_res); +#endif + sink32 = div_res; + + // DIVU: unsigned division + volatile uint32_t divu_a = 10; + volatile uint32_t divu_b = 2; + uint32_t divu_res = divu_a / divu_b; +#ifdef HOST + printf("DIVU: %u / %u = %u\n", divu_a, divu_b, divu_res); +#endif + sinku32 = divu_res; + + divu_a = 10; + divu_b = 0; + divu_res = (divu_b == 0) ? 0xFFFFFFFF : divu_a / divu_b; +#ifdef HOST + printf("DIVU: %u / %u = %u\n", divu_a, divu_b, divu_res); +#endif + sinku32 = divu_res; + + // REM: signed remainder + volatile int32_t rem_a = -2; + volatile int32_t rem_b = 3; + int32_t rem_res = rem_a % rem_b; +#ifdef HOST + printf("REM: %d %% %d = %d\n", rem_a, rem_b, rem_res); +#endif + sink32 = rem_res; + + rem_a = 10; + rem_b = 0; + rem_res = (rem_b == 0) ? rem_a : rem_a % rem_b; +#ifdef HOST + printf("REM: %d %% %d = %d\n", rem_a, rem_b, rem_res); +#endif + sink32 = rem_res; + + // REMU: unsigned remainder + volatile uint32_t remu_a = 10; + volatile uint32_t remu_b = 3; + uint32_t remu_res = remu_a % remu_b; +#ifdef HOST + printf("REMU: %u %% %u = %u\n", remu_a, remu_b, remu_res); +#endif + sinku32 = remu_res; + + remu_a = 10; + remu_b = 0; + remu_res = (remu_b == 0) ? remu_a : remu_a % remu_b; +#ifdef HOST + printf("REMU: %u %% %u = %u\n", remu_a, remu_b, remu_res); +#endif + sinku32 = remu_res; + +#ifdef HOST + printf("Factorial(%d) = %u\n", N, result); +#else + // Store result to memory + volatile uint32_t *mem_ptr = (volatile uint32_t *)FACTORIAL_ADDR; + *mem_ptr = result; +#endif + + CPU_DONE = 1; + return 0; +} diff --git a/tests/system_tests/test_factorial.py b/tests/system_tests/test_factorial.py new file mode 100644 index 0000000..564646c --- /dev/null +++ b/tests/system_tests/test_factorial.py @@ -0,0 +1,203 @@ +import cocotb +from cocotb.triggers import RisingEdge, ClockCycles +from cocotb.clock import Clock +import logging +from pathlib import Path +import subprocess +import os + +data_mem_base = 0x10000000 +cpu_done_addr = data_mem_base + 0xFF +factorial_addr = data_mem_base + 0x20 + +# Configure logging +logging.basicConfig(level=logging.DEBUG) +log = logging.getLogger(__name__) + +def compile_factorial(): + """Compile factorial.c to RISC-V binary and prepare hex file for instruction memory""" + log.info("Compiling factorial.c to RISC-V binary...") + root_dir = os.getcwd() + while not os.path.exists(os.path.join(root_dir, "rtl")): + if os.path.dirname(root_dir) == root_dir: + raise FileNotFoundError("rtl directory not found in the current or parent directories.") + root_dir = os.path.dirname(root_dir) + print(f"Using RTL directory: {root_dir}/rtl") + rtl_dir = os.path.join(root_dir, "rtl") + sim_dir = os.path.join(root_dir, "sim") + curr_dir = Path.cwd() + build_dir = curr_dir / "build" + build_dir.mkdir(exist_ok=True) + sim_dir = Path(sim_dir).resolve() + factorial_c = sim_dir / "factorial.c" + start_s = sim_dir / "start.S" + link_ld = sim_dir / "link.ld" + elf_file = build_dir / "factorial.elf" + bin_file = build_dir / "factorial.bin" + hex_file = build_dir / "instr_mem.hex" + try: + subprocess.run([ + "riscv64-unknown-elf-gcc", + "-march=rv32im", + "-mabi=ilp32", + "-nostdlib", + "-ffreestanding", + "-O1", + "-g3", + "-Wall", + "-c", + str(factorial_c), + "-o", str(build_dir / "factorial.o") + ], check=True) + log.info("Compiled factorial.c to object file.") + subprocess.run([ + "riscv64-unknown-elf-gcc", + "-march=rv32im", + "-mabi=ilp32", + "-nostdlib", + "-ffreestanding", + "-O3", + "-g3", + "-Wall", + "-c", + str(start_s), + "-o", str(build_dir / "start.o") + ], check=True) + log.info("Compiled start.S to object file.") + subprocess.run([ + "riscv64-unknown-elf-gcc", + "-march=rv32im", + "-mabi=ilp32", + "-nostdlib", + "-Wl,--no-relax", + "-Wl,-m,elf32lriscv", + "-T", str(link_ld), + str(build_dir / "factorial.o"), + str(build_dir / "start.o"), + "-o", str(elf_file) + ], check=True) + log.info("Linked object files to create ELF binary.") + subprocess.run([ + "riscv64-unknown-elf-objcopy", + "-O", "binary", + str(elf_file), + str(bin_file) + ], check=True) + log.info("Converted ELF binary to raw binary format.") + subprocess.run([ + "truncate", + "-s", "16384", + str(bin_file) + ], check=True) + subprocess.run([ + "riscv64-unknown-elf-objcopy", + "-I", "binary", + "-O", "verilog", + "--verilog-data-width=4", + "--reverse-bytes=4", + str(bin_file), + str(hex_file) + ], check=True) + subprocess.run([ + "riscv64-unknown-elf-objdump", + "-D", + "--visualize-jumps", + "-t", + "-S", + "--source-comment=//", + "-M", "no-aliases,numeric", + str(elf_file) + ], stdout=open(build_dir / "factorial.lss", "w"), check=True) + return hex_file + except subprocess.CalledProcessError as e: + log.error(f"Compilation failed: {e}") + raise + +@cocotb.test() +async def test_factorial_program(dut): + """Test the Factorial program execution and M extension ops on the RISC-V CPU""" + cocotb.log.info("Starting cocotb test: test_factorial_program") + clock = Clock(dut.clk, 10, units="ns") + cocotb.start_soon(clock.start()) + cocotb.log.info("Clock started") + dut.rst.value = 1 + cocotb.log.info("Reset asserted") + await ClockCycles(dut.clk, 5) + dut.rst.value = 0 + cocotb.log.info("Reset deasserted") + max_cycles = 10000 + cpu_done = False + result = None + dummy = None + mem_accesses = {} + cocotb.log.info("Entering simulation loop") + for cycle in range(max_cycles): + await RisingEdge(dut.clk) + if cycle % 1000 == 0: + cocotb.log.info(f"Cycle {cycle}...") + if dut.cpu_mem_write_en.value: + addr = int(dut.cpu_mem_write_addr.value) + data = int(dut.cpu_mem_write_data.value) + mem_accesses[addr] = data + cocotb.log.info(f"Cycle {cycle}: Memory write: addr=0x{addr:08x}, data=0x{data:08x}") + if addr == cpu_done_addr and (data & 0xFF) == 1: + cpu_done = True + cocotb.log.info("CPU_DONE flag set - program finished execution") + if addr == factorial_addr: + result = data + cocotb.log.info(f"Factorial result written: {result}") + if addr == factorial_addr + 4: + dummy = data + cocotb.log.info(f"Dummy value written: {dummy}") + # Print out each step of the M extension tests + cocotb.log.info("--- M Extension Step Results ---") + cocotb.log.info(f"Factorial (MUL) result: {result}") + cocotb.log.info(f"Dummy (MULH, MULHSU, MULHU, DIV, DIVU, REM, REMU sum): {dummy}") + if cpu_done and result is not None and dummy is not None: + cocotb.log.info(f"Exiting simulation loop at cycle {cycle}") + break + cocotb.log.info(f"Program execution complete after {cycle+1} cycles") + cocotb.log.info(f"Factorial result: {result}") + cocotb.log.info(f"Dummy value: {dummy}") + print("Memory accesses:") + for addr, data in sorted(mem_accesses.items()): + print(f" 0x{addr:08x}: 0x{data:08x}") + assert cpu_done, "CPU_DONE flag was not set - program did not complete" + assert result == 720, f"Factorial result mismatch: got {result}, expected 720" + cocotb.log.info("Factorial and M extension test successful!") + +def runCocotbTests(): + from cocotb_test.simulator import run + import os + hex_file = compile_factorial() + curr_dir = os.getcwd() + root_dir = curr_dir + while not os.path.exists(os.path.join(root_dir, "rtl")): + if os.path.dirname(root_dir) == root_dir: + raise FileNotFoundError("rtl directory not found in the current or parent directories.") + root_dir = os.path.dirname(root_dir) + print(f"Using RTL directory: {root_dir}/rtl") + rtl_dir = os.path.join(root_dir, "rtl") + incl_dir = os.path.join(rtl_dir, "include") + sources = [] + rtl_dir = Path(rtl_dir) + for file in rtl_dir.glob("**/*.v"): + sources.append(str(file)) + curr_dir = Path(curr_dir) + waveform_dir = curr_dir / "waveforms" + waveform_dir.mkdir(exist_ok=True) + waveform_path = waveform_dir / "factorial_test.vcd" + run( + verilog_sources=sources, + toplevel="top", + module="test_factorial", + testcase="test_factorial_program", + includes=[str(incl_dir)], + simulator="verilator", + timescale="1ns/1ps", + plus_args=[f"+dumpfile={waveform_path}"], + defines=[f"INSTR_HEX_FILE=\"{hex_file}\""] + ) + +if __name__ == "__main__": + runCocotbTests() diff --git a/tests/system_tests/test_fibonacci.py b/tests/system_tests/test_fibonacci.py index 1984b67..5f21f68 100644 --- a/tests/system_tests/test_fibonacci.py +++ b/tests/system_tests/test_fibonacci.py @@ -146,41 +146,40 @@ def compile_fibonacci(): async def test_fibonacci_program(dut): """Test the Fibonacci program execution on the RISC-V CPU""" + log.info("Starting cocotb test: test_fibonacci_program") # Start clock (10ns period) clock = Clock(dut.clk, 10, units="ns") cocotb.start_soon(clock.start()) - + log.info("Clock started") # Reset the design dut.rst.value = 1 + log.info("Reset asserted") await ClockCycles(dut.clk, 5) dut.rst.value = 0 - + log.info("Reset deasserted") # Expected Fibonacci sequence for N=10 expected_sequence = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] - # Monitor for CPU_DONE signal max_cycles = 10000 # Maximum cycles to run before timeout cpu_done = False data_values = [] - # Track memory accesses mem_accesses = {} - + log.info("Entering simulation loop") for cycle in range(max_cycles): await RisingEdge(dut.clk) - + if cycle % 1000 == 0: + log.info(f"Cycle {cycle}...") # Check for memory writes if dut.cpu_mem_write_en.value: addr = int(dut.cpu_mem_write_addr.value) data = int(dut.cpu_mem_write_data.value) mem_accesses[addr] = data log.info(f"Cycle {cycle}: Memory write: addr=0x{addr:08x}, data=0x{data:08x}") - # Check if CPU_DONE flag was set if addr == CPU_DONE_ADDR and (data & 0xFF) == 1: cpu_done = True log.info("CPU_DONE flag set - program finished execution") - # Collect Fibonacci sequence values (byte writes) if FIBONACCI_START_ADDR <= addr < FIBONACCI_START_ADDR + 10: index = addr - FIBONACCI_START_ADDR @@ -193,9 +192,9 @@ async def test_fibonacci_program(dut): data_values.append(0) data_values[index] = value log.info(f"Fibonacci[{index}] = {value}") - # Exit simulation once CPU_DONE is set and we've collected all values if cpu_done and len([x for x in data_values if x != 0]) >= 10: + log.info(f"Exiting simulation loop at cycle {cycle}") break # Verify results diff --git a/tests/unit_tests/test_alu.py b/tests/unit_tests/test_alu.py index b6dfc74..79181c9 100644 --- a/tests/unit_tests/test_alu.py +++ b/tests/unit_tests/test_alu.py @@ -2,7 +2,57 @@ from cocotb.triggers import Timer import random import os -from pathlib import Path +import sys +from contextlib import contextmanager + +MASK32 = 0xFFFFFFFF +MASK64 = 0xFFFFFFFFFFFFFFFF + + +def to_signed32(value: int) -> int: + """Interpret value as signed 32-bit integer.""" + return value if value < 0x80000000 else value - 0x100000000 + + +def to_twos_complement(value: int, bits: int) -> int: + """Convert possibly negative integer to two's complement representation.""" + return value & ((1 << bits) - 1) + + +def signed_truncating_div(dividend: int, divisor: int) -> int: + """RISC-V DIV semantics: truncates toward zero.""" + assert divisor != 0 + negative = (dividend < 0) ^ (divisor < 0) + quotient = abs(dividend) // abs(divisor) + return -quotient if negative else quotient + + +def signed_truncating_rem(dividend: int, divisor: int) -> int: + """RISC-V REM semantics: matches dividend sign.""" + assert divisor != 0 + quotient = signed_truncating_div(dividend, divisor) + return dividend - quotient * divisor + + +@contextmanager +def prepend_to_path(*path_entries: str): + """Temporarily prepend directories to PATH for simulator subprocesses.""" + entries = [entry for entry in path_entries if entry] + if not entries: + yield + return + + original_path = os.environ.get("PATH") + prefix = os.pathsep.join(entries) + new_path = prefix if not original_path else f"{prefix}{os.pathsep}{original_path}" + os.environ["PATH"] = new_path + try: + yield + finally: + if original_path is None: + os.environ.pop("PATH", None) + else: + os.environ["PATH"] = original_path # Helper function to verify ALU operation async def verify_alu_operation(dut, rs1, rs2, imm, instruction, pc_input, expected_output, operation_name): @@ -143,6 +193,61 @@ async def test_immediate_operations(dut): await verify_alu_operation(dut, 20, 0, 10, 0x13, 0, 0, "SLTIU false") await verify_alu_operation(dut, 0x80000000, 0, 1, 0x13, 0, 0, "SLTIU MSB high > low (unsigned)") +@cocotb.test() +async def test_m_extension_operations(dut): + """Test multiplication and division operations from the M extension.""" + # MUL: signed * signed low word + await verify_alu_operation( + dut, 0x0000FFFF, 0x00001000, 0, 0x30, 0, (0x0000FFFF * 0x1000) & MASK32, "MUL simple") + await verify_alu_operation( + dut, 0xFFFFFFFF, 0x00000002, 0, 0x30, 0, (to_signed32(0xFFFFFFFF) * 2) & MASK32, "MUL negative operand") + + # Prepare operands for high-word checks + rs1 = 0x80000000 # -2147483648 + rs2 = 0x00000003 + mul_signed = to_twos_complement(to_signed32(rs1) * to_signed32(rs2), 64) + mul_mixed = to_twos_complement(to_signed32(rs1) * rs2, 64) + mul_unsigned = to_twos_complement(rs1 * rs2, 64) + + await verify_alu_operation( + dut, rs1, rs2, 0, 0x31, 0, (mul_signed >> 32) & MASK32, "MULH signed high") + await verify_alu_operation( + dut, rs1, rs2, 0, 0x32, 0, (mul_mixed >> 32) & MASK32, "MULHSU signed*unsigned high") + await verify_alu_operation( + dut, rs1, rs2, 0, 0x33, 0, (mul_unsigned >> 32) & MASK32, "MULHU unsigned high") + + # DIV signed + await verify_alu_operation( + dut, 0xFFFFFFFE, 0x00000002, 0, 0x34, 0, + to_twos_complement(signed_truncating_div(to_signed32(0xFFFFFFFE), to_signed32(0x2)), 32), + "DIV negative dividend") + await verify_alu_operation( + dut, 0x80000000, 0xFFFFFFFF, 0, 0x34, 0, 0x80000000, "DIV overflow INT_MIN / -1") + await verify_alu_operation( + dut, 0x12345678, 0, 0, 0x34, 0, 0xFFFFFFFF, "DIV divide by zero") + + # DIVU unsigned + await verify_alu_operation( + dut, 0x80000000, 0x00000002, 0, 0x35, 0, (0x80000000 // 2) & MASK32, "DIVU simple") + await verify_alu_operation( + dut, 0x80000000, 0x0, 0, 0x35, 0, 0xFFFFFFFF, "DIVU divide by zero") + + # REM signed + await verify_alu_operation( + dut, 0xFFFFFFFE, 0x00000003, 0, 0x36, 0, + to_twos_complement(signed_truncating_rem(to_signed32(0xFFFFFFFE), to_signed32(0x3)), 32), + "REM negative dividend") + await verify_alu_operation( + dut, 0x80000000, 0xFFFFFFFF, 0, 0x36, 0, 0x00000000, "REM overflow INT_MIN % -1") + await verify_alu_operation( + dut, 0x89ABCDEF, 0x0, 0, 0x36, 0, 0x89ABCDEF, "REM divide by zero") + + # REMU unsigned + await verify_alu_operation( + dut, 0x12345678, 0x00000010, 0, 0x37, 0, (0x12345678 % 0x10) & MASK32, "REMU simple") + await verify_alu_operation( + dut, 0x12345678, 0x0, 0, 0x37, 0, 0x12345678, "REMU divide by zero") + @cocotb.test() async def test_default(dut): """Test default operation (should output zero)""" @@ -226,10 +331,14 @@ def runCocotbTests(): incl_dir = os.path.join(rtl_dir, "include") verilog_file = os.path.join(rtl_dir, "core_modules", "alu.v") - run( - verilog_sources=[verilog_file], - toplevel="alu", - module="test_alu", - simulator="verilator", - includes=[incl_dir] - ) + tools_dir = os.path.join(root_dir, "tests", "tools") + python_dir = os.path.dirname(sys.executable) + with prepend_to_path(tools_dir, python_dir): + run( + verilog_sources=[verilog_file], + toplevel="alu", + module="test_alu", + simulator="verilator", + includes=[incl_dir], + extra_env={"PYTHON3": sys.executable}, + ) diff --git a/tests/unit_tests/test_decoder_gcc.py b/tests/unit_tests/test_decoder_gcc.py index c5ca1d1..b909e3f 100644 --- a/tests/unit_tests/test_decoder_gcc.py +++ b/tests/unit_tests/test_decoder_gcc.py @@ -2,13 +2,36 @@ from cocotb.triggers import Timer import subprocess import os +import sys +from contextlib import contextmanager + + +@contextmanager +def prepend_to_path(*path_entries: str): + """Temporarily prepend directories to PATH for simulator subprocesses.""" + entries = [entry for entry in path_entries if entry] + if not entries: + yield + return + + original_path = os.environ.get("PATH") + prefix = os.pathsep.join(entries) + new_path = prefix if not original_path else f"{prefix}{os.pathsep}{original_path}" + os.environ["PATH"] = new_path + try: + yield + finally: + if original_path is None: + os.environ.pop("PATH", None) + else: + os.environ["PATH"] = original_path def assemble_riscv_instruction(assembly_code, bin_file="temp.bin"): with open("temp.s", "w") as f: f.write(assembly_code) subprocess.run([ - "riscv64-unknown-elf-as", "-march=rv32i_zifencei", "-mabi=ilp32", "-o", "temp.o", "temp.s" + "riscv64-unknown-elf-as", "-march=rv32im_zifencei", "-mabi=ilp32", "-o", "temp.o", "temp.s" ], check=True) subprocess.run([ @@ -97,6 +120,14 @@ async def test_decoder_exhaustive(dut): ("lui x16, 0x12345", {"opcode": 0b0110111, "rs1": 0, "rs2": 0, "rd": 16, "instr_id": 0x24, "imm": 0x12345000}), # Add fence.i test ("fence.i", {"opcode": 0b0001111, "rs1": 0, "rs2": 0, "rd": 0, "instr_id": 0x26, "imm": 0}), + ("mul x1, x2, x3", {"opcode": 0b0110011, "rs1": 2, "rs2": 3, "rd": 1, "instr_id": 0x30}), + ("mulh x4, x5, x6", {"opcode": 0b0110011, "rs1": 5, "rs2": 6, "rd": 4, "instr_id": 0x31}), + ("mulhsu x7, x8, x9", {"opcode": 0b0110011, "rs1": 8, "rs2": 9, "rd": 7, "instr_id": 0x32}), + ("mulhu x10, x11, x12", {"opcode": 0b0110011, "rs1": 11, "rs2": 12, "rd": 10, "instr_id": 0x33}), + ("div x13, x14, x15", {"opcode": 0b0110011, "rs1": 14, "rs2": 15, "rd": 13, "instr_id": 0x34}), + ("divu x16, x17, x18", {"opcode": 0b0110011, "rs1": 17, "rs2": 18, "rd": 16, "instr_id": 0x35}), + ("rem x19, x20, x21", {"opcode": 0b0110011, "rs1": 20, "rs2": 21, "rd": 19, "instr_id": 0x36}), + ("remu x22, x23, x24", {"opcode": 0b0110011, "rs1": 23, "rs2": 24, "rd": 22, "instr_id": 0x37}), ] for instr, expected in instructions: @@ -129,12 +160,16 @@ def runCocotbTests(): instr_defines_file = os.path.join(rtl_dir, "instr_defines.vh") decoder_file = os.path.join(rtl_dir, "core_modules", "decoder.v") - run( - verilog_sources=[ - decoder_file - ], - toplevel="decoder", - module="test_decoder_gcc", - simulator="verilator", - includes=[str(incl_dir)], - ) \ No newline at end of file + tools_dir = os.path.join(root_dir, "tests", "tools") + python_dir = os.path.dirname(sys.executable) + with prepend_to_path(tools_dir, python_dir): + run( + verilog_sources=[ + decoder_file + ], + toplevel="decoder", + module="test_decoder_gcc", + simulator="verilator", + includes=[str(incl_dir)], + extra_env={"PYTHON3": sys.executable}, + )