SIM : boolean := false;
-- Line size in bytes
LINE_SIZE : positive := 64;
+ -- BRAM organisation: We never access more than wishbone_data_bits at
+ -- a time so to save resources we make the array only that wide, and
+ -- use consecutive indices for to make a cache "line"
+ --
+ -- ROW_SIZE is the width in bytes of the BRAM (based on WB, so 64-bits)
+ ROW_SIZE : positive := wishbone_data_bits / 8;
-- Number of lines in a set
NUM_LINES : positive := 32;
-- Number of ways
-- L1 ITLB log_2(page_size)
TLB_LG_PGSZ : positive := 12;
-- Number of real address bits that we store
- REAL_ADDR_BITS : positive := 56
+ REAL_ADDR_BITS : positive := 56;
+ -- Non-zero to enable log data collection
+ LOG_LENGTH : natural := 0
);
port (
clk : in std_ulogic;
rst : in std_ulogic;
i_in : in Fetch1ToIcacheType;
- i_out : out IcacheToFetch2Type;
+ i_out : out IcacheToDecode1Type;
m_in : in MmuToIcacheType;
+ stall_in : in std_ulogic;
stall_out : out std_ulogic;
flush_in : in std_ulogic;
inval_in : in std_ulogic;
wishbone_out : out wishbone_master_out;
- wishbone_in : in wishbone_slave_out
+ wishbone_in : in wishbone_slave_out;
+
+ log_out : out std_ulogic_vector(53 downto 0)
);
end entity icache;
architecture rtl of icache is
- -- BRAM organisation: We never access more than wishbone_data_bits at
- -- a time so to save resources we make the array only that wide, and
- -- use consecutive indices for to make a cache "line"
- --
- -- ROW_SIZE is the width in bytes of the BRAM (based on WB, so 64-bits)
- constant ROW_SIZE : natural := wishbone_data_bits / 8;
+ constant ROW_SIZE_BITS : natural := ROW_SIZE*8;
-- ROW_PER_LINE is the number of row (wishbone transactions) in a line
constant ROW_PER_LINE : natural := LINE_SIZE / ROW_SIZE;
-- BRAM_ROWS is the number of rows in BRAM needed to represent the full
-- icache
constant BRAM_ROWS : natural := NUM_LINES * ROW_PER_LINE;
-- INSN_PER_ROW is the number of 32bit instructions per BRAM row
- constant INSN_PER_ROW : natural := wishbone_data_bits / 32;
+ constant INSN_PER_ROW : natural := ROW_SIZE_BITS / 32;
-- Bit fields counts in the address
-- INSN_BITS is the number of bits to select an instruction in a row
-- SET_SIZE_BITS is the log base 2 of the set size
constant SET_SIZE_BITS : natural := LINE_OFF_BITS + INDEX_BITS;
-- TAG_BITS is the number of bits of the tag part of the address
- constant TAG_BITS : natural := REAL_ADDR_BITS - SET_SIZE_BITS;
+ -- the +1 is to allow the endianness to be stored in the tag
+ constant TAG_BITS : natural := REAL_ADDR_BITS - SET_SIZE_BITS + 1;
-- WAY_BITS is the number of bits to select a way
constant WAY_BITS : natural := log2(NUM_WAYS);
subtype row_t is integer range 0 to BRAM_ROWS-1;
subtype index_t is integer range 0 to NUM_LINES-1;
subtype way_t is integer range 0 to NUM_WAYS-1;
+ subtype row_in_line_t is unsigned(ROW_LINEBITS-1 downto 0);
-- The cache data BRAM organized as described above for each way
- subtype cache_row_t is std_ulogic_vector(wishbone_data_bits-1 downto 0);
+ subtype cache_row_t is std_ulogic_vector(ROW_SIZE_BITS-1 downto 0);
-- The cache tags LUTRAM has a row per set. Vivado is a pain and will
-- not handle a clean (commented) definition of the cache tags as a 3d
-- The cache valid bits
subtype cache_way_valids_t is std_ulogic_vector(NUM_WAYS-1 downto 0);
type cache_valids_t is array(index_t) of cache_way_valids_t;
+ type row_per_line_valid_t is array(0 to ROW_PER_LINE - 1) of std_ulogic;
-- Storage. Hopefully "cache_rows" is a BRAM, the rest is LUTs
signal cache_tags : cache_tags_array_t;
signal eaa_priv : std_ulogic;
-- Cache reload state machine
- type state_t is (IDLE, WAIT_ACK);
+ type state_t is (IDLE, CLR_TAG, WAIT_ACK);
type reg_internal_t is record
-- Cache hit state (Latches for 1 cycle BRAM access)
hit_nia : std_ulogic_vector(63 downto 0);
hit_smark : std_ulogic;
hit_valid : std_ulogic;
+ big_endian: std_ulogic;
-- Cache miss state (reload state machine)
state : state_t;
store_way : way_t;
store_index : index_t;
store_row : row_t;
+ store_tag : cache_tag_t;
store_valid : std_ulogic;
+ end_row_ix : row_in_line_t;
+ rows_valid : row_per_line_valid_t;
-- TLB miss state
fetch_failed : std_ulogic;
signal ra_valid : std_ulogic;
signal priv_fault : std_ulogic;
signal access_ok : std_ulogic;
+ signal use_previous : std_ulogic;
-- Cache RAM interface
type cache_ram_out_t is array(way_t) of cache_row_t;
return to_integer(unsigned(addr(SET_SIZE_BITS - 1 downto ROW_OFF_BITS)));
end;
+ -- Return the index of a row within a line
+ function get_row_of_line(row: row_t) return row_in_line_t is
+ variable row_v : unsigned(ROW_BITS-1 downto 0);
+ begin
+ row_v := to_unsigned(row, ROW_BITS);
+ return row_v(ROW_LINEBITS-1 downto 0);
+ end;
+
-- Returns whether this is the last row of a line
- function is_last_row_addr(addr: wishbone_addr_type) return boolean is
- constant ones : std_ulogic_vector(ROW_LINEBITS-1 downto 0) := (others => '1');
+ function is_last_row_addr(addr: wishbone_addr_type; last: row_in_line_t) return boolean is
begin
- return addr(LINE_OFF_BITS-1 downto ROW_OFF_BITS) = ones;
+ return unsigned(addr(LINE_OFF_BITS-1 downto ROW_OFF_BITS)) = last;
end;
-- Returns whether this is the last row of a line
- function is_last_row(row: row_t) return boolean is
- variable row_v : std_ulogic_vector(ROW_BITS-1 downto 0);
- constant ones : std_ulogic_vector(ROW_LINEBITS-1 downto 0) := (others => '1');
+ function is_last_row(row: row_t; last: row_in_line_t) return boolean is
begin
- row_v := std_ulogic_vector(to_unsigned(row, ROW_BITS));
- return row_v(ROW_LINEBITS-1 downto 0) = ones;
+ return get_row_of_line(row) = last;
end;
-- Return the address of the next row in the current cache line
end;
-- Get the tag value from the address
- function get_tag(addr: std_ulogic_vector(REAL_ADDR_BITS - 1 downto 0)) return cache_tag_t is
+ function get_tag(addr: std_ulogic_vector(REAL_ADDR_BITS - 1 downto 0);
+ endian: std_ulogic) return cache_tag_t is
begin
- return addr(REAL_ADDR_BITS - 1 downto SET_SIZE_BITS);
+ return endian & addr(REAL_ADDR_BITS - 1 downto SET_SIZE_BITS);
end;
-- Read a tag from a tag memory row
report "geometry bits don't add up" severity FAILURE;
assert (LINE_OFF_BITS = ROW_OFF_BITS + ROW_LINEBITS)
report "geometry bits don't add up" severity FAILURE;
- assert (REAL_ADDR_BITS = TAG_BITS + INDEX_BITS + LINE_OFF_BITS)
+ assert (REAL_ADDR_BITS + 1 = TAG_BITS + INDEX_BITS + LINE_OFF_BITS)
report "geometry bits don't add up" severity FAILURE;
- assert (REAL_ADDR_BITS = TAG_BITS + ROW_BITS + ROW_OFF_BITS)
+ assert (REAL_ADDR_BITS + 1 = TAG_BITS + ROW_BITS + ROW_OFF_BITS)
report "geometry bits don't add up" severity FAILURE;
sim_debug: if SIM generate
signal wr_addr : std_ulogic_vector(ROW_BITS-1 downto 0);
signal dout : cache_row_t;
signal wr_sel : std_ulogic_vector(ROW_SIZE-1 downto 0);
+ signal wr_dat : std_ulogic_vector(wishbone_in.dat'left downto 0);
begin
way: entity work.cache_ram
generic map (
ROW_BITS => ROW_BITS,
- WIDTH => wishbone_data_bits
+ WIDTH => ROW_SIZE_BITS
)
port map (
clk => clk,
rd_data => dout,
wr_sel => wr_sel,
wr_addr => wr_addr,
- wr_data => wishbone_in.dat
+ wr_data => wr_dat
);
process(all)
+ variable j: integer;
begin
- do_read <= '1';
+ -- byte-swap read data if big endian
+ if r.store_tag(TAG_BITS - 1) = '0' then
+ wr_dat <= wishbone_in.dat;
+ else
+ for ii in 0 to (wishbone_in.dat'length / 8) - 1 loop
+ j := ((ii / 4) * 4) + (3 - (ii mod 4));
+ wr_dat(ii * 8 + 7 downto ii * 8) <= wishbone_in.dat(j * 8 + 7 downto j * 8);
+ end loop;
+ end if;
+ do_read <= not (stall_in or use_previous);
do_write <= '0';
- if wishbone_in.ack = '1' and r.store_way = i then
+ if wishbone_in.ack = '1' and replace_way = i then
do_write <= '1';
end if;
cache_out(i) <= dout;
rd_addr <= std_ulogic_vector(to_unsigned(req_row, ROW_BITS));
wr_addr <= std_ulogic_vector(to_unsigned(r.store_row, ROW_BITS));
- for i in 0 to ROW_SIZE-1 loop
- wr_sel(i) <= do_write;
+ for ii in 0 to ROW_SIZE-1 loop
+ wr_sel(ii) <= do_write;
end loop;
end process;
end generate;
lru => plru_out
);
- process(req_index, req_is_hit, req_hit_way, req_is_hit, plru_out)
+ process(all)
begin
-- PLRU interface
- if req_is_hit = '1' and req_index = i then
- plru_acc_en <= req_is_hit;
+ if get_index(r.hit_nia) = i then
+ plru_acc_en <= r.hit_valid;
else
plru_acc_en <= '0';
end if;
- plru_acc <= std_ulogic_vector(to_unsigned(req_hit_way, WAY_BITS));
+ plru_acc <= std_ulogic_vector(to_unsigned(r.hit_way, WAY_BITS));
plru_victim(i) <= plru_out;
end process;
end generate;
variable is_hit : std_ulogic;
variable hit_way : way_t;
begin
+ -- i_in.sequential means that i_in.nia this cycle is 4 more than
+ -- last cycle. If we read more than 32 bits at a time, had a cache hit
+ -- last cycle, and we don't want the first 32-bit chunk, then we can
+ -- keep the data we read last cycle and just use that.
+ if unsigned(i_in.nia(INSN_BITS+2-1 downto 2)) /= 0 then
+ use_previous <= i_in.req and i_in.sequential and r.hit_valid;
+ else
+ use_previous <= '0';
+ end if;
+
-- Extract line, row and tag from request
req_index <= get_index(i_in.nia);
req_row <= get_row(i_in.nia);
- req_tag <= get_tag(real_addr);
+ req_tag <= get_tag(real_addr, i_in.big_endian);
- -- Calculate address of beginning of cache line, will be
+ -- Calculate address of beginning of cache row, will be
-- used for cache miss processing if needed
--
req_laddr <= (63 downto REAL_ADDR_BITS => '0') &
- real_addr(REAL_ADDR_BITS - 1 downto LINE_OFF_BITS) &
- (LINE_OFF_BITS-1 downto 0 => '0');
+ real_addr(REAL_ADDR_BITS - 1 downto ROW_OFF_BITS) &
+ (ROW_OFF_BITS-1 downto 0 => '0');
-- Test if pending request is a hit on any way
hit_way := 0;
is_hit := '0';
for i in way_t loop
- if i_in.req = '1' and cache_valids(req_index)(i) = '1' then
+ if i_in.req = '1' and
+ (cache_valids(req_index)(i) = '1' or
+ (r.state = WAIT_ACK and
+ req_index = r.store_index and
+ i = r.store_way and
+ r.rows_valid(req_row mod ROW_PER_LINE) = '1')) then
if read_tag(i, cache_tags(req_index)) = req_tag then
hit_way := i;
is_hit := '1';
end if;
req_hit_way <= hit_way;
- -- The way to replace on a miss
- replace_way <= to_integer(unsigned(plru_victim(req_index)));
+ -- The way to replace on a miss
+ if r.state = CLR_TAG then
+ replace_way <= to_integer(unsigned(plru_victim(r.store_index)));
+ else
+ replace_way <= r.store_way;
+ end if;
-- Output instruction from current cache row
--
i_out.nia <= r.hit_nia;
i_out.stop_mark <= r.hit_smark;
i_out.fetch_failed <= r.fetch_failed;
+ i_out.big_endian <= r.big_endian;
+ i_out.next_predicted <= i_in.predicted;
-- Stall fetch1 if we have a miss on cache or TLB or a protection fault
stall_out <= not (is_hit and access_ok);
icache_hit : process(clk)
begin
if rising_edge(clk) then
- -- On a hit, latch the request for the next cycle, when the BRAM data
- -- will be available on the cache_out output of the corresponding way
- --
- r.hit_valid <= req_is_hit;
- -- Send stop marks and NIA down regardless of validity
- r.hit_smark <= i_in.stop_mark;
- r.hit_nia <= i_in.nia;
- if req_is_hit = '1' then
- r.hit_way <= req_hit_way;
- r.hit_smark <= i_in.stop_mark;
-
- report "cache hit nia:" & to_hstring(i_in.nia) &
- " IR:" & std_ulogic'image(i_in.virt_mode) &
- " SM:" & std_ulogic'image(i_in.stop_mark) &
- " idx:" & integer'image(req_index) &
- " tag:" & to_hstring(req_tag) &
- " way:" & integer'image(req_hit_way) &
- " RA:" & to_hstring(real_addr);
+ -- keep outputs to fetch2 unchanged on a stall
+ -- except that flush or reset sets valid to 0
+ -- If use_previous, keep the same data as last cycle and use the second half
+ if stall_in = '1' or use_previous = '1' then
+ if rst = '1' or flush_in = '1' then
+ r.hit_valid <= '0';
+ end if;
+ else
+ -- On a hit, latch the request for the next cycle, when the BRAM data
+ -- will be available on the cache_out output of the corresponding way
+ --
+ r.hit_valid <= req_is_hit;
+ if req_is_hit = '1' then
+ r.hit_way <= req_hit_way;
+
+ report "cache hit nia:" & to_hstring(i_in.nia) &
+ " IR:" & std_ulogic'image(i_in.virt_mode) &
+ " SM:" & std_ulogic'image(i_in.stop_mark) &
+ " idx:" & integer'image(req_index) &
+ " tag:" & to_hstring(req_tag) &
+ " way:" & integer'image(req_hit_way) &
+ " RA:" & to_hstring(real_addr);
+ end if;
end if;
+ if stall_in = '0' then
+ -- Send stop marks and NIA down regardless of validity
+ r.hit_smark <= i_in.stop_mark;
+ r.hit_nia <= i_in.nia;
+ r.big_endian <= i_in.big_endian;
+ end if;
end if;
end process;
-- Main state machine
case r.state is
when IDLE =>
+ -- Reset per-row valid flags, only used in WAIT_ACK
+ for i in 0 to ROW_PER_LINE - 1 loop
+ r.rows_valid(i) <= '0';
+ end loop;
+
-- We need to read a cache line
if req_is_miss = '1' then
report "cache miss nia:" & to_hstring(i_in.nia) &
" tag:" & to_hstring(req_tag) &
" RA:" & to_hstring(real_addr);
- -- Force misses on that way while reloading that line
- cache_valids(req_index)(replace_way) <= '0';
-
- -- Store new tag in selected way
- for i in 0 to NUM_WAYS-1 loop
- if i = replace_way then
- tagset := cache_tags(req_index);
- write_tag(i, tagset, req_tag);
- cache_tags(req_index) <= tagset;
- end if;
- end loop;
-
-- Keep track of our index and way for subsequent stores
r.store_index <= req_index;
- r.store_way <= replace_way;
r.store_row <= get_row(req_laddr);
+ r.store_tag <= req_tag;
r.store_valid <= '1';
+ r.end_row_ix <= get_row_of_line(get_row(req_laddr)) - 1;
-- Prep for first wishbone read. We calculate the address of
-- the start of the cache line and start the WB cycle.
r.wb.stb <= '1';
-- Track that we had one request sent
- r.state <= WAIT_ACK;
+ r.state <= CLR_TAG;
end if;
- when WAIT_ACK =>
+ when CLR_TAG | WAIT_ACK =>
+ if r.state = CLR_TAG then
+ -- Get victim way from plru
+ r.store_way <= replace_way;
+
+ -- Force misses on that way while reloading that line
+ cache_valids(req_index)(replace_way) <= '0';
+
+ -- Store new tag in selected way
+ for i in 0 to NUM_WAYS-1 loop
+ if i = replace_way then
+ tagset := cache_tags(r.store_index);
+ write_tag(i, tagset, r.store_tag);
+ cache_tags(r.store_index) <= tagset;
+ end if;
+ end loop;
+
+ r.state <= WAIT_ACK;
+ end if;
-- Requests are all sent if stb is 0
stbs_done := r.wb.stb = '0';
-- stb and set stbs_done so we can handle an eventual last
-- ack on the same cycle.
--
- if is_last_row_addr(r.wb.adr) then
+ if is_last_row_addr(r.wb.adr, r.end_row_ix) then
r.wb.stb <= '0';
stbs_done := true;
end if;
-- Incoming acks processing
if wishbone_in.ack = '1' then
+ r.rows_valid(r.store_row mod ROW_PER_LINE) <= '1';
-- Check for completion
- if stbs_done and is_last_row(r.store_row) then
+ if stbs_done and is_last_row(r.store_row, r.end_row_ix) then
-- Complete wishbone cycle
r.wb.cyc <= '0';
-- Cache line is now valid
- cache_valids(r.store_index)(r.store_way) <= r.store_valid and not inval_in;
+ cache_valids(r.store_index)(replace_way) <= r.store_valid and not inval_in;
-- We are done
r.state <= IDLE;
-- TLB miss and protection fault processing
if rst = '1' or flush_in = '1' or m_in.tlbld = '1' then
r.fetch_failed <= '0';
- elsif i_in.req = '1' and access_ok = '0' then
+ elsif i_in.req = '1' and access_ok = '0' and stall_in = '0' then
r.fetch_failed <= '1';
end if;
end if;
end process;
+
+ icache_log: if LOG_LENGTH > 0 generate
+ -- Output data to logger
+ signal log_data : std_ulogic_vector(53 downto 0);
+ begin
+ data_log: process(clk)
+ variable lway: way_t;
+ variable wstate: std_ulogic;
+ begin
+ if rising_edge(clk) then
+ lway := req_hit_way;
+ wstate := '0';
+ if r.state /= IDLE then
+ wstate := '1';
+ end if;
+ log_data <= i_out.valid &
+ i_out.insn &
+ wishbone_in.ack &
+ r.wb.adr(5 downto 3) &
+ r.wb.stb & r.wb.cyc &
+ wishbone_in.stall &
+ stall_out &
+ r.fetch_failed &
+ r.hit_nia(5 downto 2) &
+ wstate &
+ std_ulogic_vector(to_unsigned(lway, 3)) &
+ req_is_hit & req_is_miss &
+ access_ok &
+ ra_valid;
+ end if;
+ end process;
+ log_out <= log_data;
+ end generate;
end;