[rtl] rework GPIO controller

add interrupt capabilities to input pins
This commit is contained in:
stnolting 2025-01-15 20:05:08 +01:00
parent b4cbc3b6cf
commit cdba1022c4

View file

@ -1,5 +1,5 @@
-- ================================================================================ --
-- NEORV32 SoC - General Purpose Parallel Input/Output Port (GPIO) --
-- NEORV32 SoC - Interrupt-Capable General Purpose Input/Output Port (GPIO) Module --
-- -------------------------------------------------------------------------------- --
-- The NEORV32 RISC-V Processor - https://github.com/stnolting/neorv32 --
-- Copyright (c) NEORV32 contributors. --
@ -16,21 +16,37 @@ use neorv32.neorv32_package.all;
entity neorv32_gpio is
generic (
GPIO_NUM : natural range 0 to 64 -- number of GPIO input/output pairs (0..64)
GPIO_NUM : natural range 0 to 32 -- number of GPIO input/output pairs (0..32)
);
port (
clk_i : in std_ulogic; -- global clock line
rstn_i : in std_ulogic; -- global reset line, low-active, async
bus_req_i : in bus_req_t; -- bus request
bus_rsp_o : out bus_rsp_t; -- bus response
gpio_o : out std_ulogic_vector(63 downto 0); -- parallel output
gpio_i : in std_ulogic_vector(63 downto 0) -- parallel input
gpio_o : out std_ulogic_vector(31 downto 0); -- general purpose input port
gpio_i : in std_ulogic_vector(31 downto 0); -- general purpose output port
cpu_irq_o : out std_ulogic -- CPU interrupt
);
end neorv32_gpio;
architecture neorv32_gpio_rtl of neorv32_gpio is
signal din, din_rd, dout, dout_rd : std_ulogic_vector(63 downto 0);
-- register addresses --
constant addr_in_c : std_ulogic_vector(2 downto 0) := "000"; -- r/-: input port
constant addr_out_c : std_ulogic_vector(2 downto 0) := "001"; -- r/w: output port
--
constant addr_tt_c : std_ulogic_vector(2 downto 0) := "100"; -- r/w: trigger type (level/edge)
constant addr_tp_c : std_ulogic_vector(2 downto 0) := "101"; -- r/w: trigger polarity (high/low or rising/falling)
constant addr_ie_c : std_ulogic_vector(2 downto 0) := "110"; -- r/w: interrupt enable
constant addr_ip_c : std_ulogic_vector(2 downto 0) := "111"; -- r/c: interrupt pending
-- interface registers --
signal port_in, port_out : std_ulogic_vector(GPIO_NUM-1 downto 0);
signal irq_typ, irq_pol : std_ulogic_vector(GPIO_NUM-1 downto 0);
signal irq_en, irq_clrn : std_ulogic_vector(GPIO_NUM-1 downto 0);
-- interrupt generator --
signal port_in2, irq_trig, irq_pend : std_ulogic_vector(GPIO_NUM-1 downto 0);
begin
@ -40,58 +56,92 @@ begin
begin
if (rstn_i = '0') then
bus_rsp_o <= rsp_terminate_c;
dout <= (others => '0');
port_out <= (others => '0');
irq_typ <= (others => '0');
irq_pol <= (others => '0');
irq_en <= (others => '0');
irq_clrn <= (others => '0');
elsif rising_edge(clk_i) then
-- bus handshake --
-- defaults --
bus_rsp_o.ack <= bus_req_i.stb;
bus_rsp_o.err <= '0';
bus_rsp_o.data <= (others => '0');
irq_clrn <= (others => '1');
-- bus access --
if (bus_req_i.stb = '1') then
if (bus_req_i.rw = '1') then -- write access
if (bus_req_i.addr(3 downto 2) = "10") then
dout(31 downto 00) <= bus_req_i.data;
end if;
if (bus_req_i.addr(3 downto 2) = "11") then
dout(63 downto 32) <= bus_req_i.data;
end if;
case bus_req_i.addr(4 downto 2) is
when addr_out_c => port_out <= bus_req_i.data(GPIO_NUM-1 downto 0); -- output port
when addr_tt_c => irq_typ <= bus_req_i.data(GPIO_NUM-1 downto 0); -- trigger type
when addr_tp_c => irq_pol <= bus_req_i.data(GPIO_NUM-1 downto 0); -- trigger polarity
when addr_ie_c => irq_en <= bus_req_i.data(GPIO_NUM-1 downto 0); -- interrupt enable
when addr_ip_c => irq_clrn <= bus_req_i.data(GPIO_NUM-1 downto 0); -- interrupt pending (clear-only)
when others => NULL;
end case;
else -- read access
case bus_req_i.addr(3 downto 2) is
when "00" => bus_rsp_o.data <= din_rd(31 downto 00);
when "01" => bus_rsp_o.data <= din_rd(63 downto 32);
when "10" => bus_rsp_o.data <= dout_rd(31 downto 00);
when others => bus_rsp_o.data <= dout_rd(63 downto 32);
case bus_req_i.addr(4 downto 2) is
when addr_in_c => bus_rsp_o.data(GPIO_NUM-1 downto 0) <= port_in; -- input port
when addr_out_c => bus_rsp_o.data(GPIO_NUM-1 downto 0) <= port_out; -- output port
when addr_tt_c => bus_rsp_o.data(GPIO_NUM-1 downto 0) <= irq_typ; -- trigger type
when addr_tp_c => bus_rsp_o.data(GPIO_NUM-1 downto 0) <= irq_pol; -- trigger polarity
when addr_ie_c => bus_rsp_o.data(GPIO_NUM-1 downto 0) <= irq_en; -- interrupt enable
when addr_ip_c => bus_rsp_o.data(GPIO_NUM-1 downto 0) <= irq_pend; -- interrupt pending
when others => NULL;
end case;
end if;
end if;
end if;
end process bus_access;
-- Physical Pin Mapping -------------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
pin_mapping: process(din, dout)
begin
din_rd <= (others => '0');
dout_rd <= (others => '0');
for i in 0 to GPIO_NUM-1 loop
din_rd(i) <= din(i);
dout_rd(i) <= dout(i);
end loop;
end process pin_mapping;
-- output --
gpio_o <= dout_rd;
-- synchronize input --
input_sync: process(rstn_i, clk_i)
-- input sampling --
input_stage: process(rstn_i, clk_i)
begin
if (rstn_i = '0') then
din <= (others => '0');
port_in <= (others => '0');
port_in2 <= (others => '0');
elsif rising_edge(clk_i) then
din <= gpio_i;
port_in <= gpio_i(GPIO_NUM-1 downto 0);
port_in2 <= port_in;
end if;
end process input_sync;
end process input_stage;
-- direct output --
output_stage: process(port_out)
begin
gpio_o <= (others => '0');
gpio_o(GPIO_NUM-1 downto 0) <= port_out;
end process output_stage;
end neorv32_gpio_rtl;
-- IRQ Generator --------------------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
irq_trigger_gen:
for i in 0 to GPIO_NUM-1 generate
irq_trigger: process(port_in, port_in2, irq_typ, irq_pol)
variable sel_v : std_ulogic_vector(1 downto 0);
begin
sel_v := irq_typ(i) & irq_pol(i);
case sel_v is
when "00" => irq_trig(i) <= not port_in(i); -- low-level
when "01" => irq_trig(i) <= port_in(i); -- high-level
when "10" => irq_trig(i) <= (not port_in(i)) and port_in2(i); -- falling-edge
when "11" => irq_trig(i) <= port_in(i) and (not port_in2(i)); -- rising-edge
when others => irq_trig(i) <= '0';
end case;
end process irq_trigger;
end generate;
-- buffer pending interrupts until manually cleared --
irq_buffer: process(rstn_i, clk_i)
begin
if (rstn_i = '0') then
irq_pend <= (others => '0');
cpu_irq_o <= '0';
elsif rising_edge(clk_i) then
irq_pend <= irq_en and ((irq_pend and irq_clrn) or irq_trig);
cpu_irq_o <= or_reduce_f(irq_pend);
end if;
end process irq_buffer;
end neorv32_gpio_rtl;