Skip to content

§ 4.3.2. Register File

RegisterFile32x32 provides the RV32I core's two source-register reads and one destination-register write. It uses the whole-word FF register, with register-address decoding and two read selectors around a shared bank. Its words are registers, not byte-masked memory words, because a register is only ever written whole (storage and memory). It is not a transaction memory endpoint: register operands are read directly, without requests, responses, or a memory controller.

Its fixed register count, two-read/one-write interface, and hardwired x0 make it an RV32I-oriented building block. The FF cells are described in 3.1.1, and the whole-word register family in 4.3.1; this page defines the CPU-facing organization built around them.

The single-cycle Decode stage owns this component at CORE.DECODE.REGISTER_FILE. Writeback only selects its input value; it does not own a second register file. Execution status qualifies the actual write enable.

Component Description Structural Behavioral
RegisterFile32x32 32 architectural registers, two asynchronous reads, one clocked write; x0 is always zero Yes Yes
RegisterFileStorageBank Parallel bank of 31 writable whole-word registers Yes No
Register<32> One resettable, whole-word register per writable architectural register Yes Yes
FlipFlopCell Reused write-enabled, resettable storage bit Yes Yes

The bank fixes its depth at 31 because x0 is a constant. Register<32> is reusable independently of this RV32I-specific decoder and read-port arrangement.

4.3.2.1. RegisterFile32x32

IO pins

Pin Direction Width Meaning
RS1_ADDR Input 5 First source register, x0–x31
RS2_ADDR Input 5 Second source register, x0–x31
RD_ADDR Input 5 Destination register, x0–x31
WRITE_DATA Input 32 Full replacement value for the destination
REG_WRITE Input 1 Enable destination write
CLK Input 1 Capture on a known low-to-high transition
RST Input 1 Active-high asynchronous reset; clears x1–x31
RS1_DATA Output 32 Value selected by RS1_ADDR
RS2_DATA Output 32 Value selected by RS2_ADDR

Addresses are register indices, not byte addresses. There is no external byte mask: every enabled write replaces all 32 bits.

Behavior

Condition Effect
Power-up, before reset x1–x31 are unknown; x0 is zero
RST = 1 Clear every writable register, without waiting for a clock edge
RST = 0, rising CLK, REG_WRITE = 1, RD_ADDR != 0 Capture WRITE_DATA in the selected register
Write to x0 Ignore the write; both read ports still return zero when selecting x0
REG_WRITE = 0, or no rising edge Retain storage
Source address changes Update the corresponding read output after propagation; no clock edge required
Read and write the same register Read the old value before capture and the new value after capture and propagation

Reset has priority over writing. There is no separate write-through bypass or same-cycle forwarding circuit inside this component; pipeline forwarding belongs to the CPU. Both read ports can select the same register or two different registers independently.

C++ example

This standalone reference shows the stored state, clock detection, reset priority, decoder, and both read-mux trees, including X/Z. Bits and address bits are least-significant first. Call evaluate after inputs change; prepare data and controls before a rising edge. This example returns settled values immediately: it omits simulator events, propagation delays, and history recording, not the state-transition rules.

#include <array>
#include <cstddef>
#include <utility>

namespace register_file_example {
enum class Bit { L, H, X, Z };
using Word = std::array<Bit, 32>;
using Address = std::array<Bit, 5>;

Bit known(Bit v) { return v == Bit::Z ? Bit::X : v; }
Bit invert(Bit v) {
    return v == Bit::L ? Bit::H : v == Bit::H ? Bit::L : Bit::X;
}
Bit both(Bit a, Bit b) {
    if (a == Bit::L || b == Bit::L) return Bit::L;
    return a == Bit::H && b == Bit::H ? Bit::H : Bit::X;
}
Bit either(Bit a, Bit b) {
    if (a == Bit::H || b == Bit::H) return Bit::H;
    return a == Bit::L && b == Bit::L ? Bit::L : Bit::X;
}
Bit mux(Bit a, Bit b, Bit select) {
    return either(both(a, invert(select)), both(b, select));
}
Bit matches(const Address& address, std::size_t index) {
    Bit result = Bit::H;
    for (std::size_t b = 0; b < 5; ++b)
        result = both(result, (index >> b) & 1 ? known(address[b]) : invert(address[b]));
    return result;
}

class RegisterFile {
    std::array<Word, 32> registers;
    Bit previous_clock = Bit::X;

    Word read(const Address& address) const {
        auto level = registers;
        std::size_t count = 32;
        for (std::size_t a = 0; a < 5; ++a) {
            for (std::size_t pair = 0; pair < count / 2; ++pair)
                for (std::size_t b = 0; b < 32; ++b)
                    level[pair][b] = mux(level[2 * pair][b], level[2 * pair + 1][b], address[a]);
            count /= 2;
        }
        return level[0];
    }
public:
    RegisterFile() {
        for (auto& word : registers) word.fill(Bit::X);
        registers[0].fill(Bit::L);
    }
    std::pair<Word, Word> evaluate(const Address& rs1, const Address& rs2,
                                  const Address& rd, const Word& data,
                                  Bit write, Bit clock, Bit reset) {
        clock = known(clock);
        reset = known(reset);
        for (std::size_t reg = 1; reg < 32; ++reg) {
            auto& word = registers[reg];
            if (reset == Bit::H) word.fill(Bit::L);
            else if (reset != Bit::L) {
                for (auto& b : word) if (b != Bit::L) b = Bit::X;
            } else if (previous_clock == Bit::L && clock == Bit::H) {
                const Bit enable = both(write, matches(rd, reg));
                if (enable != Bit::L)
                    for (std::size_t b = 0; b < 32; ++b)
                        word[b] = mux(word[b], known(data[b]), enable);
            }
        }
        previous_clock = clock;
        return {read(rs1), read(rs2)};
    }
};
} // namespace register_file_example

Unknown inputs and timing

Captured Z data becomes X. An uncertain reset preserves stored zeros and makes other bits unknown. An uncertain destination address can affect multiple candidate registers; known address bits exclude nonmatching registers. A definitely disabled write does not modify any register.

An uncertain write enable follows the gate-built feedback mux: old zero/new zero stays zero; every other pair becomes X, including old one/new one. Read selection likewise follows five layers of AND/OR muxes, not an optimistic merge of possible register values. Consequently, selecting between identical ones with an unknown selector can produce X; identical zeros remain zero.

The behavioral implementation detects only known CLK = 0 → 1 transitions. Ambiguous clocks and simultaneous clock/data/control changes are not a setup/hold or metastability model. Structural and behavioral implementations are compared at settled checkpoints, not at every intermediate gate transition. Absolute checkpoint times differ by fidelity.

Implementation

Structural implementation

31 stored words, two read ports The register file writes one of 31 whole-word registers for x1 to x31 and has two independent read selectors; x0 comes from a constant, not storage cells. RegisterFile32x32 RD_DECODER one word ONE WRITE PORT STORAGE 31 × Register<32> WORD_0 x1 WORD_1 x2 WORD_30 x31 … TWO READ PORTS RS1_MUX RS1_VALUE RS2_MUX RS2_VALUE X0_ZERO = 0 · no cells
x0 is not stored. The two read selectors choose independently from x0 or the 31 stored words. The x0-to-selector wiring is omitted here; the connection table below specifies it.
Part Connection / responsibility
RD_DECODER Decoder5to32: ADDR receives RD_ADDR; ENABLE receives REG_WRITE
STORAGE.WRITE_n Connected to decoder OUT(n+1); OUT0 is intentionally unused
STORAGE.WORD_n Holds x(n+1); fans out to input (n+1) of both read muxes
Shared storage pins WRITE_DATA, CLK, RST fan out to the 31 whole-word registers
X0_ZERO Drives input 0 of both read muxes
RS1_MUX, RS2_MUX Mux32to1_32bit: independent selectors drive the two data outputs

The bank contains 992 writable bits, not 1,024: x0 does not need flip-flops. The bank supplies parallel word outputs, so two read selectors share the stored values. Each physical word is one whole-word Register<32>, whose write enable comes directly from the decoder rather than a byte mask.

Behavioral implementation

RegisterFile32x32Behavioral stores 32 logic-vector words, keeps x0 zero, and implements the same decoder/write/read rules without child gates. Catalog-selected read outputs use the register file’s gate-derived per-path delay table. The register file uses that table without a delay twin because its large gate list added simulation cost without improving its measured timing shares. Register snapshots support the visualizer's register table and timeline scrubbing.

The structural register file can select behavioral Register<32> words or their FlipFlopCell children. The external pins and register numbering do not change. Its register-state inspector reads the bank's WORD outputs after the simulator restores the requested time.

Examples

The table contains every checkpoint of RegisterFile32x32Test, including its actual simulation time and checkpoint number. Preparatory clock/control changes occur between checkpoints; the table is not a list of independent combinational inputs. Scroll horizontally to inspect all pins. Times describe the embedded simulation's default profile, not universal register-file latency.

All 47 recorded rows of RegisterFile32x32Test, in checkpoint order. Outputs are test expectations verified against the simulation. Time is the absolute settled checkpoint time in simulator units, not milliseconds or CPU cycles.

Default scenario profile: canonical-default-preview / structural (253fb5cb7d5a89bd). Changing fidelity or the circuit can change these times.

Checkpointzero-based number · timeline label Timesimulator units CLKInput · 1 bit RD_ADDRInput · 5 bit REG_WRITEInput · 1 bit RS1_ADDRInput · 5 bit RS2_ADDRInput · 5 bit RSTInput · 1 bit WRITE_DATAInput · 32 bit RS1_DATAExpected output · 32 bit RS2_DATAExpected output · 32 bit
#0reset15 0 0x00 0 0x00 0x01 1 0x00000000 0x00000000 0x00000000
#1ignore-x0-write33 0 0x00 1 0x00 0x01 0 0xFFFFFFFF 0x00000000 0x00000000
#2write-x177 0 0x01 1 0x01 0x00 0 0x11010101 0x11010101 0x00000000
#3write-x2117 0 0x02 1 0x02 0x01 0 0x12020202 0x12020202 0x11010101
#4write-x3158 0 0x03 1 0x03 0x02 0 0x13030303 0x13030303 0x12020202
#5write-x4198 0 0x04 1 0x04 0x03 0 0x14040404 0x14040404 0x13030303
#6write-x5239 0 0x05 1 0x05 0x04 0 0x15050505 0x15050505 0x14040404
#7write-x6279 0 0x06 1 0x06 0x05 0 0x16060606 0x16060606 0x15050505
#8write-x7320 0 0x07 1 0x07 0x06 0 0x17070707 0x17070707 0x16060606
#9write-x8360 0 0x08 1 0x08 0x07 0 0x18080808 0x18080808 0x17070707
#10write-x9401 0 0x09 1 0x09 0x08 0 0x19090909 0x19090909 0x18080808
#11write-x10441 0 0x0A 1 0x0A 0x09 0 0x1A0A0A0A 0x1A0A0A0A 0x19090909
#12write-x11482 0 0x0B 1 0x0B 0x0A 0 0x1B0B0B0B 0x1B0B0B0B 0x1A0A0A0A
#13write-x12524 0 0x0C 1 0x0C 0x0B 0 0x1C0C0C0C 0x1C0C0C0C 0x1B0B0B0B
#14write-x13565 0 0x0D 1 0x0D 0x0C 0 0x1D0D0D0D 0x1D0D0D0D 0x1C0C0C0C
#15write-x14605 0 0x0E 1 0x0E 0x0D 0 0x1E0E0E0E 0x1E0E0E0E 0x1D0D0D0D
#16write-x15646 0 0x0F 1 0x0F 0x0E 0 0x1F0F0F0F 0x1F0F0F0F 0x1E0E0E0E
#17write-x16686 0 0x10 1 0x10 0x0F 0 0x10101010 0x10101010 0x1F0F0F0F
#18write-x17727 0 0x11 1 0x11 0x10 0 0x11111111 0x11111111 0x10101010
#19write-x18767 0 0x12 1 0x12 0x11 0 0x12121212 0x12121212 0x11111111
#20write-x19808 0 0x13 1 0x13 0x12 0 0x13131313 0x13131313 0x12121212
#21write-x20850 0 0x14 1 0x14 0x13 0 0x14141414 0x14141414 0x13131313
#22write-x21891 0 0x15 1 0x15 0x14 0 0x15151515 0x15151515 0x14141414
#23write-x22931 0 0x16 1 0x16 0x15 0 0x16161616 0x16161616 0x15151515
#24write-x23972 0 0x17 1 0x17 0x16 0 0x17171717 0x17171717 0x16161616
#25write-x241012 0 0x18 1 0x18 0x17 0 0x18181818 0x18181818 0x17171717
#26write-x251053 0 0x19 1 0x19 0x18 0 0x19191919 0x19191919 0x18181818
#27write-x261093 0 0x1A 1 0x1A 0x19 0 0x1A1A1A1A 0x1A1A1A1A 0x19191919
#28write-x271134 0 0x1B 1 0x1B 0x1A 0 0x1B1B1B1B 0x1B1B1B1B 0x1A1A1A1A
#29write-x281176 0 0x1C 1 0x1C 0x1B 0 0x1C1C1C1C 0x1C1C1C1C 0x1B1B1B1B
#30write-x291217 0 0x1D 1 0x1D 0x1C 0 0x1D1D1D1D 0x1D1D1D1D 0x1C1C1C1C
#31write-x301257 0 0x1E 1 0x1E 0x1D 0 0x1E1E1E1E 0x1E1E1E1E 0x1D1D1D1D
#32write-x311298 0 0x1F 1 0x1F 0x1E 0 0x1F1F1F1F 0x1F1F1F1F 0x1E1E1E1E
#33hold-disabled1330 0 0x05 0 0x05 0x1F 0 0xDEADBEEF 0x15050505 0x1F1F1F1F
#34ignore-x0-again1366 0 0x00 1 0x00 0x1F 0 0xAAAAAAAA 0x00000000 0x1F1F1F1F
#35unknown-data-lane1409 0 0x0D 1 0x0D 0x0C 0 0b110010101111111010111010X0111110 0b110010101111111010111010X0111110 0x1C0C0C0C
#36high-z-data-lane-normalizes-to-unknown1452 0 0x0E 1 0x0E 0x00 0 0b0001001101010111100110Z111011111 0b0001001101010111100110X111011111 0x00000000
#37reset-before-unknown-policy1468 0 0x0E 1 0x03 0x07 1 0b0001001101010111100110Z111011111 0x00000000 0x00000000
#38write-x3-for-unknown-policy1509 0 0x03 1 0x03 0x07 0 0xAAAAAAAA 0xAAAAAAAA not checked
#39ambiguous-read-agrees1553 0 0x07 1 0b00X11 0x07 0 0xAAAAAAAA 0bX0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0 not checked
#40high-z-read-address-matches-unknown-policy1556 0 0x07 1 0b00Z11 0x07 0 0xAAAAAAAA 0bX0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0 not checked
#41ambiguous-read-differs1591 0 0x07 1 0b00X11 0x07 0 0x55555555 0bXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX not checked
#42ambiguous-write1637 0 0b00X11 1 0x03 0x07 0 0x12345678 0bX0XXX0X0X0XXXXX0XXXXXXX0XXXXX0X0 0b0X0X0XXX0XXX0X0X0X0X0XXX0XXXXX0X
#43reset-clears-ambiguous-write1653 0 0b00X11 1 0x03 0x07 1 0x12345678 0x00000000 0x00000000
#44unknown-register-write1698 0 0x09 X 0x09 0x00 0 0xFFFFFFFF 0bXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 0x00000000
#45unknown-reset1704 0 0x09 X 0x01 0x00 X 0xFFFFFFFF 0x00000000 0x00000000
#46high-z-reset-matches-unknown-policy1706 0 0x09 X 0x01 0x00 Z 0xFFFFFFFF 0x00000000 0x00000000

Scroll within the table for all rows and pins. Buses use hexadecimal; values containing X/Z use MSB-first binary. Inputs show the declared values in effect at each checkpoint, including retained inputs. Sequential tests also replay preparation changes between checkpoints, such as lowering the clock. Use the matching checkpoint label in the simulation below.

Embedded simulation

The sequence resets the file, attempts writes to x0, writes and reads every writable register, checks disabled writes, captures unknown/high-impedance data, and exercises uncertain read/write addresses and controls. Expand STORAGE, then WORD_n, to inspect its BIT_0–BIT_31 cells. Physical WORD_0 is x1, and WORD_30 is x31. The bank layout orders words numerically, left to right and top to bottom.

Open the full visualizer

Tests

Test Coverage
RegisterFile32x32Test Registered structural/behavioral scenario; all 31 writable registers, both reads, x0 protection, reset, write disable, selected X/Z cases
RegisterFileFFIntegrationTest Bank/register/cell hierarchy, context-free construction, cross-fidelity scenario, uncertain-enable regression, reverse history, and program-03 in both CPU cores with the structural register file
RegisterFileDocumentationTest Compiles this exact C++ example and runs every declared scenario action against its expected outputs; checks numeric bank layout order
RegisterTest, RegisterWidthsTest, FlipFlopCellTest Reused lower-level storage contracts

The register file retains the existing rv32i.register-file family, public pins, and register-state capability. No memory-bus adapter or CPU instruction-interface change is needed.