Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

M-Extension

The RISC-V M-Extension (Standard Extension for Integer Multiplication and Division) provides native hardware support for multiplication and division operations. The original DOOM engine does not use floating-point arithmetic but relies heavily on 16.16 fixed-point arithmetic for its 3D rendering pipeline, BSP tree traversal, and physics calculations. Without the M-Extension, these operations would be implemented with software emulation routines (__mulsi3 and __divsi3), which would cost hundreds of clock cycles per operation and catastrophically reduce performance.

Supported Operations

CategoryInstructionsUse in DOOM
MultiplyMUL, MULH, MULHSU, MULHUFixed-point multiplication, angle/trig lookup scaling
DivideDIV, DIVUAspect ratio correction, column/span calculation
RemainderREM, REMUModulo operations, buffer index wrapping

Architecture

The M-Extension is implemented as two separate execution units instantiated within the Execute stage, running in parallel with the standard ALU:

flowchart LR
    ALU["ALU (Single-Cycle)"]
    MUL["Multiplier<br/>(3-Stage Pipelined)"]
    DIV["Divider<br/>(Radix-2 State Machine)"]
    MUX["Result MUX"]

    ALU --> MUX
    MUL --> MUX
    DIV --> MUX

Both units are independent and can operate concurrently. The Execute stage monitors their busy/valid signals and only stalls the pipeline when at least one unit is actively computing without a valid result available.

Pipeline Integration

Stall Logic: The Execute stage asserts stall_req_exe when either the Multiplier or Divider is busy but does not yet have a valid_out signal. This completely freezes the Fetch and Decode stages, ensuring no instructions are dropped:

assign stall_req_exe = ((mul_start || mul_busy) && !mul_valid_out) ||
                       ((div_start || div_busy) && !div_valid_out);

Forwarding: The forwarding unit treats MUL and DIV identically to standard ALU R-type instructions. Once the multi-cycle math finishes, the Execute stage outputs valid = 1 and the resulting data is instantly forwarded to subsequent instructions, preventing data hazards.

Busy Tracking: Each unit tracks its own busy state with separate start/busy/valid_out signals. A unit is started by asserting its start signal; it remains busy until it produces a valid output and the pipeline is not stalled.

Verification

The M-Extension was verified at three levels:

  • Unit-level testbenches (tb_multiplier.sv, tb_divider.sv) using Verilator with a dynamic scoreboard queue to handle multi-cycle latency
  • Assembly tests exercising hazard chains (MUL feeding DIV, feeding MUL) and edge cases (divide-by-zero, signed overflow)
  • Hardware acceptance tests (test_acc.c) that run on the FPGA, printing results over UART and setting LED patterns for quick visual pass/fail confirmation

The Multiplier and Divider are each documented in detail in their own subchapters.