RPU/vhdl/control_unit.vhd
Colin Riley 4b16f9bf6f RPU 1.0
Updated ISA support to RV32IMZcsr - Passes riscv-compliance.
Integer divide/rem in 34 cycles.
Integer multiply in 2 cycles (when using xilinx dsp blocks!)
Saved multiple cycles from fetch/memory load stages by short-cutting the start of memory requests.
Compliant misaligned exceptions for jumps,loads and stores. Addrs starting 0xFxxxxxxx ignore alignment requests (assumes mmio space).
Added CSRs for riscv-compliance requirements.
Source ran through a formatter for ease of use.
2020-09-11 00:06:01 +01:00

300 lines
No EOL
12 KiB
VHDL

----------------------------------------------------------------------------------
-- Project Name: RPU
-- Description: control unit
--
----------------------------------------------------------------------------------
-- Copyright 2016,2018,2019,2020 Colin Riley
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.all;
library work;
use work.constants.all;
entity control_unit is
port (
I_clk : in STD_LOGIC;
I_reset : in STD_LOGIC;
I_halt : in STD_LOGIC;
I_aluop : in STD_LOGIC_VECTOR (6 downto 0);
-- interrupts
I_int_enabled : in std_logic;
I_int : in STD_LOGIC;
O_int_ack : out STD_LOGIC;
I_int_mem_data : in STD_LOGIC_VECTOR(XLENM1 downto 0);
O_idata : out STD_LOGIC_VECTOR(XLENM1 downto 0);
O_set_idata : out STD_LOGIC;
O_set_ipc : out STD_LOGIC;
O_set_irpc : out STD_LOGIC;
O_instTick : out STD_LOGIC;
-- mem controller state and control
I_misalignment : in STD_LOGIC;
I_ready : in STD_LOGIC;
O_execute : out STD_LOGIC;
I_dataReady : in STD_LOGIC;
-- alu stall input
I_aluWait : in STD_LOGIC;
I_aluMultiCy : in STD_LOGIC;
O_state : out STD_LOGIC_VECTOR (6 downto 0)
);
end control_unit;
architecture Behavioral of control_unit is
signal s_state : STD_LOGIC_VECTOR(6 downto 0) := "0000001";
signal mem_ready : std_logic;
signal mem_execute : std_logic := '0';
signal mem_dataReady : std_logic;
signal mem_cycles : integer := 0;
signal next_s_state : STD_LOGIC_VECTOR(6 downto 0) := "0000001";
signal interrupt_state : STD_LOGIC_VECTOR(2 downto 0) := "000";
signal interrupt_ack : STD_LOGIC := '0';
signal interrupt_was_inactive : STD_LOGIC := '1';
signal set_idata : STD_LOGIC := '0';
signal set_ipc : STD_LOGIC := '0';
signal instTick : STD_LOGIC := '0';
signal s_hasWaited : STD_LOGIC := '0';
signal s_check_alignint : integer := 0;
begin
O_execute <= mem_execute;
mem_ready <= I_ready;
mem_dataReady <= I_dataReady;
O_int_ack <= interrupt_ack;
O_set_idata <= set_idata;
O_set_irpc <= set_idata;
O_set_ipc <= set_ipc;
O_instTick <= instTick;
process (I_clk)
begin
if rising_edge(I_clk) and I_halt = '0' then
if I_reset = '1' then
s_state <= "0000001";
next_s_state <= "0000001";
mem_cycles <= 0;
mem_execute <= '0';
interrupt_was_inactive <= '1';
interrupt_ack <= '0';
interrupt_state <= "000";
set_ipc <= '0';
O_idata <= X"00000000";
set_idata <= '0';
instTick <= '0';
else
case s_state is
---------------------------
-- FETCH
when "0000001" => -- fetch
if s_check_alignint /= 0 then
-- If we've seen an alignment hint we need to stall here for s_check_alignint
-- cycles, checking for an interrupt each time. When it's 0 we give up.
if I_int_enabled = '1' and interrupt_was_inactive = '1' and I_int = '1' then
interrupt_ack <= '1';
interrupt_was_inactive <= '0';
interrupt_state <= "001";
next_s_state <= "0000001"; --F
s_state <= "1000000"; --S
s_check_alignint <= 0;
else
s_check_alignint <= s_check_alignint - 1;
end if;
else
if I_int = '0' then
interrupt_was_inactive <= '1';
end if;
instTick <= '0';
if mem_cycles = 0 and mem_ready = '1' then
mem_execute <= '1';
mem_cycles <= 1;
elsif mem_cycles = 1 then
mem_execute <= '0';
mem_cycles <= 2;
elsif mem_cycles = 2 then
mem_execute <= '0';
if mem_dataReady = '1' then
mem_cycles <= 0;
s_state <= "0000010";
end if;
end if;
end if;
---------------------------
-- DECODE
when "0000010" => --- decode
if I_int = '0' then
interrupt_was_inactive <= '1';
end if;
s_hasWaited <= '0';
s_state <= "0001000"; --E "0000100"; --R
---------------------------
-- EXECUTE
when "0001000" => -- execute
if I_int = '0' then
interrupt_was_inactive <= '1';
end if;
--MEM/WB
-- if it's not a memory alu op, goto writeback
if (I_aluop(6 downto 2) = OPCODE_LOAD or
I_aluop(6 downto 2) = OPCODE_STORE) then
s_state <= "0010000"; -- MEM
-- -- mem load short cut
-- ISSUE - this fails to take into account the type of request, sizing, address correctly
-- and therefore needs removed for compliance to pass.
-- if I_misalignment = '0' and mem_cycles = 0 and mem_ready = '1' then
-- mem_execute <= '1';
-- mem_cycles <= 1;
-- end if;
else
if I_aluWait = '0' then
if I_aluMultiCy = '1' then
if s_hasWaited = '1' then
s_state <= "0100000"; -- WB
end if;
else
s_state <= "0100000"; -- WB
end if;
s_hasWaited <= '1';
end if;
end if;
---------------------------
-- MEMORY
when "0010000" => -- mem
if I_int = '0' then
interrupt_was_inactive <= '1';
end if;
-- alignment traps here are tricky.
-- if we see the misalignment hint, wait 6 cycles and then test interrupt stall.
-- if no interrupt we need to re-run the stage.
if I_misalignment = '1' and s_check_alignint = 0 then
s_check_alignint <= 6;
elsif s_check_alignint > 0 then
if I_int_enabled = '1' and interrupt_was_inactive = '1' and I_int = '1' then
interrupt_ack <= '1';
interrupt_was_inactive <= '0';
interrupt_state <= "001";
next_s_state <= "0000001"; --F
s_state <= "1000000"; --F
s_check_alignint <= 0;
else
s_check_alignint <= s_check_alignint - 1;
end if;
else
if mem_cycles = 0 and mem_ready = '1' then
mem_execute <= '1';
mem_cycles <= 1;
elsif mem_cycles = 1 then
mem_execute <= '0';
-- if it's a write, go through
if I_aluop(6 downto 2) = OPCODE_STORE then
mem_cycles <= 0;
s_state <= "0100000"; -- WB
elsif mem_dataReady = '1' then
-- if read, wait for data
mem_cycles <= 0;
s_state <= "0100000"; -- WB
end if;
end if;
end if;
---------------------------
-- WRITEBACK
when "0100000" => -- writeback
-- check interrupt?
if I_int_enabled = '1' and interrupt_was_inactive = '1' and I_int = '1' then
interrupt_ack <= '1';
interrupt_was_inactive <= '0';
interrupt_state <= "001";
next_s_state <= "0000001"; --F
s_state <= "1000000"; --F
else
if I_int = '0' then
interrupt_was_inactive <= '1';
end if;
if I_misalignment = '1' and s_check_alignint = 0 then
s_check_alignint <= 3;
end if;
-- misalign interrupts take a while to propagate
-- this signal short cuts to ensure we can catch any misalignments before fetch.
s_state <= "0000001"; --F
-- if the mem system is ready, shortcut the fetch
-- at this point, the next PC/Branch should be set.
-- need to ensure the sizing is correct.
if I_misalignment = '0' and mem_cycles = 0 and mem_ready = '1' then -- shortcut
mem_execute <= '1'; -- shortcut
mem_cycles <= 2; -- shortcut
end if; -- shortcut
end if;
instTick <= '1';
when "1000000" => -- stalls
if I_int = '0' then
interrupt_was_inactive <= '1';
end if;
instTick <= '0';
-- interrupt stall
if interrupt_state = "001" then
-- give a cycle of latency
-- set PC to interrupt vector.
set_ipc <= '1';
interrupt_state <= "101";
elsif interrupt_state = "101" then
set_ipc <= '0';
interrupt_ack <= '0';
interrupt_state <= "111";
elsif interrupt_state = "111" then
interrupt_state <= "000";
s_state <= "0000001"; --F
end if;
when "1001000" =>
-- alu 1 cycle stall
s_state <= "0100000"; -- WB
when others =>
s_state <= "0000001";
end case;
end if;
end if;
end process;
O_state <= s_state;
end Behavioral;