<jupyter_start><jupyter_text>Register FileMagma has an experimental feature introducing a register file primitive.To use this, first install the magma branch:```shellgit clone https://github.com/phanrahan/magmacd magmagit checkout regfile-primitivepip install -e .```The `RegisterFile` primitive is a `Generator` that takes in arguments `height, data_width`.  This will create `height` registers each storing a `Bits[data_width]`.  To read from a register, you can use the `__getitem__` syntax, such as `reg_file[addr]` where `addr` is a magma value of type `Bits[clog2(height)]`.  This will add a read port to the generated register file.  Simililarly, to write to a register, you can use the `__setitem__` syntax, such as `reg_file[addr] = data` where `addr` is a magma value of type `Bits[clog2(height)]` and `data` is a magma value of type `Bits[data_width]`.  This will add a write port to the generated register file.  **NOTE** that this uses `=` (assignment) instead of `@=` which is normally used for wiring.The `RegisterFile` uses last connect (write) semantics.  If two statements write to the register file, and their dynamic address values match, the value written by the last executed statement will take priority.The `RegisterFile` forwards writes within the same cycle (combinational writes), so if a read statement reads from the same dynamic address as a write statement, the read value will equal the write value.Planned features (feedback welcome):* Support imatmul (@=) syntax to be consistent with `wire`* Optional write forwarding (writes have a one cycle delay)* Integration with sequential syntax* Convenient syntax for enable logic (when)* Generate verilog always blocksHere's a basic example and test<jupyter_code>import magma as m
from magma.primitives.register_file import RegisterFile

height = 4
data_width = 4
addr_width = m.bitutils.clog2(height)

class Main(m.Circuit):
    io = m.IO(
        write_addr=m.In(m.Bits[addr_width]),
        write_data=m.In(m.Bits[data_width]),
        read_addr=m.In(m.Bits[addr_width]),
        read_data=m.Out(m.Bits[data_width])
    ) + m.ClockIO(has_async_reset=True)
    
    reg_file = RegisterFile(height, data_width)
    reg_file[io.write_addr] = io.write_data
    io.read_data @= reg_file[io.read_addr]

m.compile("build/test_register_file_primitive_basic", Main, inline=True)
import fault
import tempfile
tester = fault.Tester(Main, Main.CLK)
tester.circuit.CLK = 0
for i in range(4):
    tester.circuit.write_addr = i
    tester.circuit.write_data = i
    tester.step(2)
for i in range(4):
    tester.circuit.read_addr = i
    tester.eval()
    tester.circuit.read_data.expect(i)
    
# Test combinational write
tester.circuit.read_addr = 1
tester.eval()
tester.circuit.read_data.expect(1)
tester.circuit.write_addr = 1
tester.circuit.write_data = 2
tester.eval()
tester.circuit.read_data.expect(2)


with tempfile.TemporaryDirectory() as dir_:
    tester.compile_and_run("verilator", directory=dir_, flags=['-Wno-unused'])<jupyter_output><empty_output><jupyter_text>Here's an example and test that demonstrates the "last connect/write" semantics<jupyter_code>import magma as m
from magma.primitives.register_file import RegisterFile

height = 4
data_width = 4
addr_width = m.bitutils.clog2(height)

class Main2(m.Circuit):
    io = m.IO(
        write_addr0=m.In(m.Bits[addr_width]),
        write_data0=m.In(m.Bits[data_width]),
        write_addr1=m.In(m.Bits[addr_width]),
        write_data1=m.In(m.Bits[data_width]),
        read_addr0=m.In(m.Bits[addr_width]),
        read_data0=m.Out(m.Bits[data_width]),
        read_addr1=m.In(m.Bits[addr_width]),
        read_data1=m.Out(m.Bits[data_width])
    ) + m.ClockIO(has_async_reset=True)
    reg_file = RegisterFile(height, data_width)
    reg_file[io.write_addr0] = io.write_data0
    io.read_data0 @= reg_file[io.read_addr0]
    reg_file[io.write_addr1] = io.write_data1
    io.read_data1 @= reg_file[io.read_addr1]

m.compile("build/test_register_file_primitive_two", Main2, inline=True)

import fault
import tempfile
tester = fault.Tester(Main2, Main2.CLK)
tester.circuit.CLK = 0
for i in range(4):
    tester.circuit.write_addr0 = i
    tester.circuit.write_data0 = 3 - i
    tester.circuit.write_addr1 = 3 - i
    tester.circuit.write_data1 = i
    tester.step(2)
for i in range(4):
    tester.circuit.read_addr0 = i
    tester.circuit.read_addr1 = 3 - i
    tester.eval()
    tester.circuit.read_data0.expect(3 - i)
    tester.circuit.read_data1.expect(i)

# Test priority
tester.circuit.write_addr0 = 3
tester.circuit.write_data0 = 3
tester.circuit.write_addr1 = 3
tester.circuit.write_data1 = 4
tester.step(2)
tester.circuit.read_addr0 = 3
tester.eval()
tester.circuit.read_data0.expect(4)
with tempfile.TemporaryDirectory() as dir_:
    tester.compile_and_run("verilator", directory=dir_, flags=['-Wno-unused'])<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10712822613682917, 'fraction_numerical': 0.013519049569848422, 'mean_word_length': 4.580571428571429, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 9, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '2', 'dir': 'jupyter-structured-clean-dedup', 'id': '608228', 'license': 'MIT', 'n_tokens_mistral': 1715, 'n_tokens_neox': 1645, 'n_words': 427, 'path': 'notebooks/intermediate/Register File.ipynb', 'repo_name': 'phanrahan/magmathon', 'size': '4882'}