§ 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¶
| 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 |
|---|---|---|---|---|---|---|---|---|---|---|
#0reset | 15 | 0 |
0x00 |
0 |
0x00 |
0x01 |
1 |
0x00000000 |
0x00000000 |
0x00000000 |
#1ignore-x0-write | 33 | 0 |
0x00 |
1 |
0x00 |
0x01 |
0 |
0xFFFFFFFF |
0x00000000 |
0x00000000 |
#2write-x1 | 77 | 0 |
0x01 |
1 |
0x01 |
0x00 |
0 |
0x11010101 |
0x11010101 |
0x00000000 |
#3write-x2 | 117 | 0 |
0x02 |
1 |
0x02 |
0x01 |
0 |
0x12020202 |
0x12020202 |
0x11010101 |
#4write-x3 | 158 | 0 |
0x03 |
1 |
0x03 |
0x02 |
0 |
0x13030303 |
0x13030303 |
0x12020202 |
#5write-x4 | 198 | 0 |
0x04 |
1 |
0x04 |
0x03 |
0 |
0x14040404 |
0x14040404 |
0x13030303 |
#6write-x5 | 239 | 0 |
0x05 |
1 |
0x05 |
0x04 |
0 |
0x15050505 |
0x15050505 |
0x14040404 |
#7write-x6 | 279 | 0 |
0x06 |
1 |
0x06 |
0x05 |
0 |
0x16060606 |
0x16060606 |
0x15050505 |
#8write-x7 | 320 | 0 |
0x07 |
1 |
0x07 |
0x06 |
0 |
0x17070707 |
0x17070707 |
0x16060606 |
#9write-x8 | 360 | 0 |
0x08 |
1 |
0x08 |
0x07 |
0 |
0x18080808 |
0x18080808 |
0x17070707 |
#10write-x9 | 401 | 0 |
0x09 |
1 |
0x09 |
0x08 |
0 |
0x19090909 |
0x19090909 |
0x18080808 |
#11write-x10 | 441 | 0 |
0x0A |
1 |
0x0A |
0x09 |
0 |
0x1A0A0A0A |
0x1A0A0A0A |
0x19090909 |
#12write-x11 | 482 | 0 |
0x0B |
1 |
0x0B |
0x0A |
0 |
0x1B0B0B0B |
0x1B0B0B0B |
0x1A0A0A0A |
#13write-x12 | 524 | 0 |
0x0C |
1 |
0x0C |
0x0B |
0 |
0x1C0C0C0C |
0x1C0C0C0C |
0x1B0B0B0B |
#14write-x13 | 565 | 0 |
0x0D |
1 |
0x0D |
0x0C |
0 |
0x1D0D0D0D |
0x1D0D0D0D |
0x1C0C0C0C |
#15write-x14 | 605 | 0 |
0x0E |
1 |
0x0E |
0x0D |
0 |
0x1E0E0E0E |
0x1E0E0E0E |
0x1D0D0D0D |
#16write-x15 | 646 | 0 |
0x0F |
1 |
0x0F |
0x0E |
0 |
0x1F0F0F0F |
0x1F0F0F0F |
0x1E0E0E0E |
#17write-x16 | 686 | 0 |
0x10 |
1 |
0x10 |
0x0F |
0 |
0x10101010 |
0x10101010 |
0x1F0F0F0F |
#18write-x17 | 727 | 0 |
0x11 |
1 |
0x11 |
0x10 |
0 |
0x11111111 |
0x11111111 |
0x10101010 |
#19write-x18 | 767 | 0 |
0x12 |
1 |
0x12 |
0x11 |
0 |
0x12121212 |
0x12121212 |
0x11111111 |
#20write-x19 | 808 | 0 |
0x13 |
1 |
0x13 |
0x12 |
0 |
0x13131313 |
0x13131313 |
0x12121212 |
#21write-x20 | 850 | 0 |
0x14 |
1 |
0x14 |
0x13 |
0 |
0x14141414 |
0x14141414 |
0x13131313 |
#22write-x21 | 891 | 0 |
0x15 |
1 |
0x15 |
0x14 |
0 |
0x15151515 |
0x15151515 |
0x14141414 |
#23write-x22 | 931 | 0 |
0x16 |
1 |
0x16 |
0x15 |
0 |
0x16161616 |
0x16161616 |
0x15151515 |
#24write-x23 | 972 | 0 |
0x17 |
1 |
0x17 |
0x16 |
0 |
0x17171717 |
0x17171717 |
0x16161616 |
#25write-x24 | 1012 | 0 |
0x18 |
1 |
0x18 |
0x17 |
0 |
0x18181818 |
0x18181818 |
0x17171717 |
#26write-x25 | 1053 | 0 |
0x19 |
1 |
0x19 |
0x18 |
0 |
0x19191919 |
0x19191919 |
0x18181818 |
#27write-x26 | 1093 | 0 |
0x1A |
1 |
0x1A |
0x19 |
0 |
0x1A1A1A1A |
0x1A1A1A1A |
0x19191919 |
#28write-x27 | 1134 | 0 |
0x1B |
1 |
0x1B |
0x1A |
0 |
0x1B1B1B1B |
0x1B1B1B1B |
0x1A1A1A1A |
#29write-x28 | 1176 | 0 |
0x1C |
1 |
0x1C |
0x1B |
0 |
0x1C1C1C1C |
0x1C1C1C1C |
0x1B1B1B1B |
#30write-x29 | 1217 | 0 |
0x1D |
1 |
0x1D |
0x1C |
0 |
0x1D1D1D1D |
0x1D1D1D1D |
0x1C1C1C1C |
#31write-x30 | 1257 | 0 |
0x1E |
1 |
0x1E |
0x1D |
0 |
0x1E1E1E1E |
0x1E1E1E1E |
0x1D1D1D1D |
#32write-x31 | 1298 | 0 |
0x1F |
1 |
0x1F |
0x1E |
0 |
0x1F1F1F1F |
0x1F1F1F1F |
0x1E1E1E1E |
#33hold-disabled | 1330 | 0 |
0x05 |
0 |
0x05 |
0x1F |
0 |
0xDEADBEEF |
0x15050505 |
0x1F1F1F1F |
#34ignore-x0-again | 1366 | 0 |
0x00 |
1 |
0x00 |
0x1F |
0 |
0xAAAAAAAA |
0x00000000 |
0x1F1F1F1F |
#35unknown-data-lane | 1409 | 0 |
0x0D |
1 |
0x0D |
0x0C |
0 |
0b110010101111111010111010X0111110 |
0b110010101111111010111010X0111110 |
0x1C0C0C0C |
#36high-z-data-lane-normalizes-to-unknown | 1452 | 0 |
0x0E |
1 |
0x0E |
0x00 |
0 |
0b0001001101010111100110Z111011111 |
0b0001001101010111100110X111011111 |
0x00000000 |
#37reset-before-unknown-policy | 1468 | 0 |
0x0E |
1 |
0x03 |
0x07 |
1 |
0b0001001101010111100110Z111011111 |
0x00000000 |
0x00000000 |
#38write-x3-for-unknown-policy | 1509 | 0 |
0x03 |
1 |
0x03 |
0x07 |
0 |
0xAAAAAAAA |
0xAAAAAAAA |
not checked |
#39ambiguous-read-agrees | 1553 | 0 |
0x07 |
1 |
0b00X11 |
0x07 |
0 |
0xAAAAAAAA |
0bX0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0 |
not checked |
#40high-z-read-address-matches-unknown-policy | 1556 | 0 |
0x07 |
1 |
0b00Z11 |
0x07 |
0 |
0xAAAAAAAA |
0bX0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0 |
not checked |
#41ambiguous-read-differs | 1591 | 0 |
0x07 |
1 |
0b00X11 |
0x07 |
0 |
0x55555555 |
0bXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
not checked |
#42ambiguous-write | 1637 | 0 |
0b00X11 |
1 |
0x03 |
0x07 |
0 |
0x12345678 |
0bX0XXX0X0X0XXXXX0XXXXXXX0XXXXX0X0 |
0b0X0X0XXX0XXX0X0X0X0X0XXX0XXXXX0X |
#43reset-clears-ambiguous-write | 1653 | 0 |
0b00X11 |
1 |
0x03 |
0x07 |
1 |
0x12345678 |
0x00000000 |
0x00000000 |
#44unknown-register-write | 1698 | 0 |
0x09 |
X |
0x09 |
0x00 |
0 |
0xFFFFFFFF |
0bXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
0x00000000 |
#45unknown-reset | 1704 | 0 |
0x09 |
X |
0x01 |
0x00 |
X |
0xFFFFFFFF |
0x00000000 |
0x00000000 |
#46high-z-reset-matches-unknown-policy | 1706 | 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.
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.