1
0
mirror of https://github.com/j-core/j-core-ice40.git synced 2026-03-06 19:21:14 +00:00
Files
j-core.j-core-ice40/register_file.vhd

179 lines
5.7 KiB
VHDL

-- A Register File with 2 write ports and 2 read ports built out of
-- 2 RAM blocks with 1 read and 1 independant write port. Register 0
-- also has independant ouput. 32 regs x 32 bits by default, one write clock.
--
-- Both write ports actually write to the same RAM blocks by delaying EX stage
-- writes to the WB stage and assuming that the decoder will never schedule
-- register writes for both Z and W busses in same ID stage.
--
-- To delay EX stage writes, a pipeline of 2 pending writes is kept. To service
-- a read, either the data in the EX pipeline or the current WB write value
-- may be returned instead of the data in the RAM block. Servicing reads from
-- the current WB write value implements W bus forwarding.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity register_file is
generic (
ADDR_WIDTH : integer := 5;
NUM_REGS : integer := 32;
REG_WIDTH : integer := 32);
port (
clk : in std_logic;
rst : in std_logic;
ce : in std_logic;
addr_ra : in std_logic_vector(ADDR_WIDTH-1 downto 0);
dout_a : out std_logic_vector(REG_WIDTH-1 downto 0);
addr_rb : in std_logic_vector(ADDR_WIDTH-1 downto 0);
dout_b : out std_logic_vector(REG_WIDTH-1 downto 0);
dout_0 : out std_logic_vector(REG_WIDTH-1 downto 0);
we_wb : in std_logic;
w_addr_wb : in std_logic_vector(ADDR_WIDTH-1 downto 0);
din_wb : in std_logic_vector(REG_WIDTH-1 downto 0);
we_ex : in std_logic;
w_addr_ex : in std_logic_vector(ADDR_WIDTH-1 downto 0);
din_ex : in std_logic_vector(REG_WIDTH-1 downto 0);
-- wr_data_o exposes the data about to be written to the
-- register memories
wr_data_o : out std_logic_vector(REG_WIDTH-1 downto 0)
);
subtype addr_t is std_logic_vector(ADDR_WIDTH-1 downto 0);
subtype data_t is std_logic_vector(REG_WIDTH-1 downto 0);
type reg_pipe_t is
record
en : std_logic;
data : data_t;
addr : addr_t;
end record;
type ex_pipeline_t is array(0 to 2) of reg_pipe_t;
function pipe_matches(pipe : reg_pipe_t; addr : addr_t)
return boolean is
begin
return pipe.en = '1' and pipe.addr = addr;
end;
function read_with_forwarding(addr : addr_t; bank_data : data_t;
wb_pipe : reg_pipe_t;
ex_pipes : ex_pipeline_t)
return std_logic_vector is
begin
-- The goal here is to read the most recent value for a register.
-- (I believe the order of the wb_pipe and ex_pipes(1) checks doesn't
-- matter and can be reversed because they cannot both be writing to the
-- same register. Register conflict detection prevents that.)
-- forward from W bus writes occuring this cycle
if (pipe_matches(wb_pipe, addr)) then
return wb_pipe.data;
-- ex_pipes(1) and ex_pipes(2) are "already written" values that should be
-- returned before the bank data. Check ex_pipes(1) first as it is the more
-- recent write.
elsif (pipe_matches(ex_pipes(1), addr)) then
return ex_pipes(1).data;
elsif (pipe_matches(ex_pipes(2), addr)) then
return ex_pipes(2).data;
else
-- no matching pending writes in the pipeline, return bank data
return bank_data;
end if;
end;
function to_reg_index(addr : addr_t)
return integer is
variable ret : integer range 0 to 31;
begin
ret := to_integer(unsigned(addr));
if ret >= NUM_REGS then
report "Register out of range";
ret := 0;
end if;
return ret;
end;
end register_file;
architecture two_bank of register_file is
constant ZERO_ADDR : addr_t := (others => '0');
type ram_type is array(0 to NUM_REGS - 1) of data_t;
signal bank_a, bank_b : ram_type := (others => (others => '0'));
signal reg0 : data_t;
signal da : std_logic_vector(REG_WIDTH-1 downto 0);
signal db : std_logic_vector(REG_WIDTH-1 downto 0);
signal ex_pipes : ex_pipeline_t;
signal wb_pipe : reg_pipe_t;
begin
wb_pipe.en <= we_wb;
wb_pipe.addr <= w_addr_wb;
wb_pipe.data <= din_wb;
ex_pipes(0).en <= we_ex;
ex_pipes(0).addr <= w_addr_ex;
ex_pipes(0).data <= din_ex;
dout_a <= read_with_forwarding(addr_ra, da, wb_pipe, ex_pipes);
dout_b <= read_with_forwarding(addr_rb, db, wb_pipe, ex_pipes);
dout_0 <= read_with_forwarding(ZERO_ADDR, reg0, wb_pipe, ex_pipes);
process (clk, rst, ce, wb_pipe, ex_pipes)
variable addr : integer;
variable data : data_t;
begin
if (falling_edge(clk)) then
da <= bank_a(to_reg_index(addr_ra));
db <= bank_b(to_reg_index(addr_rb));
end if;
if rst = '1' then
addr := 0;
data := (others => '0');
wr_data_o <= (others => '0');
reg0 <= (others => '0');
ex_pipes(1) <= (
en => '0',
data => (others => '0'),
addr => (others => '0'));
ex_pipes(2) <= (
en => '0',
data => (others => '0'),
addr => (others => '0'));
elsif (rising_edge(clk) and ce = '1') then
-- the decoder should never schedule a write to a register for both Z and
-- W bus at the same time
assert (wb_pipe.en and ex_pipes(2).en) = '0'
report "Write clash detected" severity warning;
addr := to_reg_index(wb_pipe.addr);
data := wb_pipe.data;
if (ex_pipes(2).en = '1') then
addr := to_reg_index(ex_pipes(2).addr);
data := ex_pipes(2).data;
end if;
wr_data_o <= (others => '0');
if ((wb_pipe.en or ex_pipes(2).en) = '1') then
wr_data_o <= data;
bank_a(addr) <= data;
bank_b(addr) <= data;
if (addr = 0) then
reg0 <= data;
end if;
end if;
ex_pipes(2) <= ex_pipes(1);
ex_pipes(1) <= ex_pipes(0);
end if;
end process;
end architecture;