Magma has an experimental feature introducing a register file primitive.
To use this, first install the magma branch:
git clone https://github.com/phanrahan/magma
cd magma
git checkout regfile-primitive
pip 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):
wireHere's a basic example and test
In [1]:
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)
In [ ]:
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'])
Here's an example and test that demonstrates the "last connect/write" semantics
In [ ]:
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'])
In [ ]: