mirror of
https://github.com/antonblanchard/microwatt.git
synced 2026-01-18 17:07:12 +00:00
The goal is to have the icache fit in BRAM by latching the output into a register. In order to avoid timing issues , we need to give the BRAM a full cycle on reads, and thus we souce the BRAM address directly from fetch1 latched NIA. (Note: This will be problematic if/when we want to hash the address, we'll probably be better off having fetch1 latch a fully hashed address along with the normal one, so the icache can use the former to address the BRAM and pass the latter along) One difficulty is that we cannot really stall the icache without adding more combo logic that would break the "one full cycle" BRAM model. This means that on stalls from decode, by the time we stall fetch1, it has already gone to the next address, which the icache is already latching. We work around this by having a "stash" buffer in fetch2 that will stash away the icache output on a stall, and override the output of the icache with the content of the stash buffer when unstalling. This requires a rewrite of the stop/step debug logic as well. We now do most of the hard work in fetch1 which makes more sense. Note: Vivado is still not inferring an built-in output register for the BRAMs. I don't want to add another cycle... I don't fully understand why it wouldn't be able to treat current_row as such but clearly it won't. At least the timing seems good enough now for 100Mhz, possibly more. Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
128 lines
3.5 KiB
VHDL
128 lines
3.5 KiB
VHDL
library ieee;
|
|
use ieee.std_logic_1164.all;
|
|
use ieee.numeric_std.all;
|
|
|
|
library work;
|
|
use work.common.all;
|
|
|
|
entity fetch1 is
|
|
generic(
|
|
RESET_ADDRESS : std_logic_vector(63 downto 0) := (others => '0')
|
|
);
|
|
port(
|
|
clk : in std_ulogic;
|
|
rst : in std_ulogic;
|
|
|
|
-- Control inputs:
|
|
stall_in : in std_ulogic;
|
|
flush_in : in std_ulogic;
|
|
stop_in : in std_ulogic;
|
|
|
|
-- redirect from execution unit
|
|
e_in : in Execute1ToFetch1Type;
|
|
|
|
-- Request to icache
|
|
i_out : out Fetch1ToIcacheType
|
|
);
|
|
end entity fetch1;
|
|
|
|
architecture behaviour of fetch1 is
|
|
type stop_state_t is (RUNNING, STOPPED, RESTARTING);
|
|
type reg_internal_t is record
|
|
stop_state: stop_state_t;
|
|
end record;
|
|
signal r, r_next : Fetch1ToIcacheType;
|
|
signal r_int, r_next_int : reg_internal_t;
|
|
begin
|
|
|
|
regs : process(clk)
|
|
begin
|
|
if rising_edge(clk) then
|
|
if r /= r_next then
|
|
report "fetch1 rst:" & std_ulogic'image(rst) &
|
|
" R:" & std_ulogic'image(e_in.redirect) &
|
|
" S:" & std_ulogic'image(stall_in) &
|
|
" T:" & std_ulogic'image(stop_in) &
|
|
" nia:" & to_hstring(r_next.nia) &
|
|
" SM:" & std_ulogic'image(r_next.stop_mark);
|
|
end if;
|
|
r <= r_next;
|
|
r_int <= r_next_int;
|
|
end if;
|
|
end process;
|
|
|
|
comb : process(all)
|
|
variable v : Fetch1ToIcacheType;
|
|
variable v_int : reg_internal_t;
|
|
variable increment : boolean;
|
|
begin
|
|
v := r;
|
|
v_int := r_int;
|
|
|
|
if rst = '1' then
|
|
v.nia := RESET_ADDRESS;
|
|
v_int.stop_state := RUNNING;
|
|
elsif e_in.redirect = '1' then
|
|
v.nia := e_in.redirect_nia;
|
|
elsif stall_in = '0' then
|
|
|
|
-- For debug stop/step to work properly we need a little bit of
|
|
-- trickery here. If we just stop incrementing and send stop marks
|
|
-- when stop_in is set, then we'll increment on the cycle it clears
|
|
-- and end up never executing the instruction we were stopped on.
|
|
--
|
|
-- Avoid this along with the opposite issue when stepping (stop is
|
|
-- cleared for only one cycle) is handled by the state machine below
|
|
--
|
|
-- By default, increment addresses
|
|
increment := true;
|
|
case v_int.stop_state is
|
|
when RUNNING =>
|
|
-- If we are running and stop_in is set, then stop incrementing,
|
|
-- we are now stopped.
|
|
if stop_in = '1' then
|
|
increment := false;
|
|
v_int.stop_state := STOPPED;
|
|
end if;
|
|
when STOPPED =>
|
|
-- When stopped, never increment. If stop is cleared, go to state
|
|
-- "restarting" but still don't increment that cycle. stop_in is
|
|
-- now 0 so we'll send the NIA down without a stop mark.
|
|
increment := false;
|
|
if stop_in = '0' then
|
|
v_int.stop_state := RESTARTING;
|
|
end if;
|
|
when RESTARTING =>
|
|
-- We have just sent the NIA down, we can start incrementing again.
|
|
-- If stop_in is still not set, go back to running normally.
|
|
-- If stop_in is set again (that was a one-cycle "step"), go
|
|
-- back to "stopped" state which means we'll stop incrementing
|
|
-- on the next cycle. This ensures we increment the PC once after
|
|
-- sending one instruction without a stop mark. Since stop_in is
|
|
-- now set, the new PC will be sent with a stop mark and thus not
|
|
-- executed.
|
|
if stop_in = '0' then
|
|
v_int.stop_state := RUNNING;
|
|
else
|
|
v_int.stop_state := STOPPED;
|
|
end if;
|
|
end case;
|
|
|
|
if increment then
|
|
v.nia := std_logic_vector(unsigned(v.nia) + 4);
|
|
end if;
|
|
end if;
|
|
|
|
v.req := not rst;
|
|
v.stop_mark := stop_in;
|
|
|
|
r_next <= v;
|
|
r_next_int <= v_int;
|
|
|
|
-- Update outputs to the icache
|
|
i_out <= r;
|
|
|
|
end process;
|
|
|
|
end architecture behaviour;
|