mirror of
https://github.com/antonblanchard/microwatt.git
synced 2026-02-27 00:59:41 +00:00
This implements a simple branch predictor in the decode1 stage. If it sees that the instruction is b or bc and the branch is predicted to be taken, it sends a flush and redirect upstream (to icache and fetch1) to redirect fetching to the branch target. The prediction is sent downstream with the branch instruction, and execute1 now only sends a flush/redirect upstream if the prediction was wrong. Unconditional branches are always predicted to be taken, and conditional branches are predicted to be taken if and only if the offset is negative. Branches that take the branch address from a register (bclr, bcctr) are predicted not taken, as we don't have any way to predict the branch address. Since we can now have a mflr being executed immediately after a bl or bcl, we now track the update to LR in the hazard tracker, using the second write register field that is used to track RA updates for update-form loads and stores. For those branches that update LR but don't write any other result (i.e. that don't decrementer CTR), we now write back LR in the same cycle as the instruction rather than taking a second cycle for the LR writeback. Signed-off-by: Paul Mackerras <paulus@ozlabs.org>
153 lines
4.4 KiB
VHDL
153 lines
4.4 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');
|
|
ALT_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;
|
|
alt_reset_in : in std_ulogic;
|
|
|
|
-- redirect from execution unit
|
|
e_in : in Execute1ToFetch1Type;
|
|
|
|
-- redirect from decode1
|
|
d_in : in Decode1ToFetch1Type;
|
|
|
|
-- Request to icache
|
|
i_out : out Fetch1ToIcacheType;
|
|
|
|
-- outputs to logger
|
|
log_out : out std_ulogic_vector(42 downto 0)
|
|
);
|
|
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;
|
|
signal log_nia : std_ulogic_vector(42 downto 0);
|
|
begin
|
|
|
|
regs : process(clk)
|
|
begin
|
|
if rising_edge(clk) then
|
|
log_nia <= r.nia(63) & r.nia(43 downto 2);
|
|
if r /= r_next then
|
|
report "fetch1 rst:" & std_ulogic'image(rst) &
|
|
" IR:" & std_ulogic'image(e_in.virt_mode) &
|
|
" P:" & std_ulogic'image(e_in.priv_mode) &
|
|
" R:" & std_ulogic'image(e_in.redirect) & std_ulogic'image(d_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;
|
|
log_out <= log_nia;
|
|
|
|
comb : process(all)
|
|
variable v : Fetch1ToIcacheType;
|
|
variable v_int : reg_internal_t;
|
|
variable increment : boolean;
|
|
begin
|
|
v := r;
|
|
v_int := r_int;
|
|
v.sequential := '0';
|
|
|
|
if rst = '1' then
|
|
if alt_reset_in = '1' then
|
|
v.nia := ALT_RESET_ADDRESS;
|
|
else
|
|
v.nia := RESET_ADDRESS;
|
|
end if;
|
|
v.virt_mode := '0';
|
|
v.priv_mode := '1';
|
|
v_int.stop_state := RUNNING;
|
|
elsif e_in.redirect = '1' then
|
|
v.nia := e_in.redirect_nia;
|
|
v.virt_mode := e_in.virt_mode;
|
|
v.priv_mode := e_in.priv_mode;
|
|
elsif d_in.redirect = '1' then
|
|
v.nia := d_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);
|
|
v.sequential := '1';
|
|
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;
|