Skip to content

Commit

Permalink
⚠️ Rework XIRQ - remove "pending" register (#1071)
Browse files Browse the repository at this point in the history
  • Loading branch information
stnolting authored Oct 21, 2024
2 parents 9243491 + e190ebe commit ac2dc5e
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 162 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ mimpid = 0x01040312 -> Version 01.04.03.12 -> v1.4.3.12

| Date | Version | Comment | Ticket |
|:----:|:-------:|:--------|:------:|
| 20.10.2024 | 1.10.5.9 | :warning: rework XIRQ controller; remove "interrupt pending" register `EIP` | [#1071](https://github.com/stnolting/neorv32/pull/1071) |
| 18.10.2024 | 1.10.5.8 | minor RTL code cleanups | [#1068](https://github.com/stnolting/neorv32/pull/1068) |
| 18.10.2024 | 1.10.5.7 | use individual/new module for XBUS-to-AXI4-Lite bridge | [#1063](https://github.com/stnolting/neorv32/pull/1063) |
| 12.10.2024 | 1.10.5.6 | :warning: remove legacy support for on-chip debugger DM version v0.13; now only supporting DM v1.0 (removing `OCD_DM_LEGACY_MODE` generic) | [#1056](https://github.com/stnolting/neorv32/pull/1056) |
Expand Down
48 changes: 18 additions & 30 deletions docs/datasheet/soc_xirq.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,17 @@ The XIRQ provides up to 32 external interrupt channels configured via the `XIRQ_
`xirq_i` input signal vector represents one interrupt channel. If less than 32 channels are configured, only the
LSB-aligned channels are used while the remaining ones are left unconnected internally.

The external interrupt controller features five interface registers:
The external interrupt controller features four interface registers:

[start=1]
. external interrupt channel enable (`EIE`)
. external interrupt channel pending (`EIP`)
. external interrupt source (`ESC`)
. trigger type configuration (`TTYP`)
. trigger polarity configuration (`TPOL`)

[TIP]
From a functional point of view, the `EIE`, `EIP` and `ESC` registers follow the behavior
of the RISC-V <<_mie>>, <<_mip>> and <<_mcause>> CSRs.

The actual interrupt trigger type can be configured individually for each channel using the `TTYP` and `TPOL`
registers. `TTYP` defines the actual trigger type (level-triggered or edge-triggered), while `TPOL` defines
the trigger's polarity (low-level/falling-edge or high-level_/rising-edge). The position of each bit in these
the trigger's polarity (low-level/falling-edge or high-level/rising-edge). The position of each bit in these
registers corresponds the according XIRQ channel.

.XIRQ Trigger Configuration
Expand All @@ -57,24 +52,19 @@ registers corresponds the according XIRQ channel.
| `1` | `1` | rising-edge
|=======================

When the configured trigger of an interrupt channel fires the according interrupt channel becomes _pending_
which is indicated by the according channel bit being set in the `EIP` register. This pending interrupt can
be manually cleared at any time by writing zero to the according `EIP` bit.

A pending interrupt can only generate a CPU interrupt if the according channel is enabled by the `EIE`
register. Once triggered, disabled channels that **were already triggered** remain pending until explicitly
(= manually) cleared. The channels are prioritized in a static order, i.e. channel 0 (`xirq_i(0)`) has the
highest priority and channel 31 (`xirq_i(31)`) has the lowest priority. If **any** pending interrupt channel is
also enabled, an interrupt request is sent to the CPU.
Each interrupt channel can be enabled or disabled individually using the `EIE` register. If the trigger of a
disabled channel fires the interrupt request is entirely ignored.

The CPU can determine the most prioritized external interrupt request either by checking the bits in the `EIP`
register or by reading the interrupt source register `ESC`. This register provides a 5-bit wide ID (0..31)
identifying the currently firing external interrupt source channel. Writing _any_ value to this register will
acknowledge and clear the _current_ CPU interrupt (so the XIRQ controller can issue a new CPU interrupt).
If the configured trigger of an _enabled_ channels fires, the according interrupt request is buffered internally
and an interrupt request is sent to the CPU. If more than one trigger fires at one a prioritization is used:
the channels are prioritized in a static order, i.e. channel 0 (`xirq_i(0)`) has the highest priority and channel
31 (`xirq_i(31)`) has the lowest priority.

In order to acknowledge an XIRQ interrupt, the interrupt handler has to...
* clear the pending XIRQ channel by clearing the according `EIP` bit
* writing _any_ value to `ESC` to acknowledge the XIRQ CPU interrupt
The CPU can determine the most prioritized external interrupt request by reading the interrupt source register `ESC`.
This register provides a 5-bit wide ID (0..31) identifying the currently firing external interrupt source channel as
well as a single bit (the MSB) that
Writing _any_ value to this register will acknowledge and clear the _current_ CPU interrupt (so the XIRQ controller
can issue a new CPU interrupt).


**Register Map**
Expand All @@ -85,11 +75,9 @@ In order to acknowledge an XIRQ interrupt, the interrupt handler has to...
|=======================
| Address | Name [C] | Bit(s) | R/W | Description
| `0xfffff300` | `EIE` | `31:0` | r/w | External interrupt enable register (one bit per channel, LSB-aligned)
| `0xfffff304` | `EIP` | `31:0` | r/w | External interrupt pending register (one bit per channel, LSB-aligned); writing 0 to a bit clears the according pending interrupt
| `0xfffff308` | `ESC` | `4:0` | r/w | Interrupt source ID (0..31) of firing IRQ (prioritized!); writing _any_ value will acknowledge the current XIRQ CPU interrupt
| `0xfffff30c` | `TTYP` | `31:0` | r/w | Trigger type select (`0` = level trigger, `1` = edge trigger); each bit corresponds to the according channel number
| `0xfffff310` | `TPOL` | `31:0` | r/w | Trigger polarity select (`0` = low-level/falling-edge, `1` = high-level/rising-edge); each bit corresponds to the according channel number
| `0xfffff314` | - | `31:0` | r/- | _reserved_, read as zero
| `0xfffff318` | - | `31:0` | r/- | _reserved_, read as zero
| `0xfffff31c` | - | `31:0` | r/- | _reserved_, read as zero
.3+^| `0xfffff304` .3+<| `ESC` ^| `31` ^| r/c <| XIRQ interrupt when set; write any value to this register to acknowledge the current XIRQ interrupt
^| `30:5` ^| r/- <| _reserved_, read as zero
^| `4:0` ^| r/c <| Interrupt source ID (0..31) of firing IRQ (prioritized!)
| `0xfffff308` | `TTYP` | `31:0` | r/w | Trigger type select (`0` = level trigger, `1` = edge trigger); each bit corresponds to the according channel number
| `0xfffff30c` | `TPOL` | `31:0` | r/w | Trigger polarity select (`0` = low-level/falling-edge, `1` = high-level/rising-edge); each bit corresponds to the according channel number
|=======================
2 changes: 1 addition & 1 deletion rtl/core/neorv32_package.vhd
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ package neorv32_package is

-- Architecture Constants -----------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
constant hw_version_c : std_ulogic_vector(31 downto 0) := x"01100508"; -- hardware version
constant hw_version_c : std_ulogic_vector(31 downto 0) := x"01100509"; -- hardware version
constant archid_c : natural := 19; -- official RISC-V architecture ID
constant XLEN : natural := 32; -- native data path width

Expand Down
124 changes: 66 additions & 58 deletions rtl/core/neorv32_xirq.vhd
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
-- NEORV32 SoC - External Interrupt Controller (XIRQ) --
-- -------------------------------------------------------------------------------- --
-- Simple interrupt controller for platform (processor-external) interrupts. Up to --
-- 32 channels are supported that get (optionally) prioritized into a single CPU --
-- interrupt. Trigger type is programmable per channel by configuration registers. --
-- 32 channels are supported that get prioritized into a single CPU interrupt. --
-- Trigger type is programmable per-channel by configuration registers. --
-- -------------------------------------------------------------------------------- --
-- The NEORV32 RISC-V Processor - https://github.com/stnolting/neorv32 --
-- Copyright (c) NEORV32 contributors. --
Expand Down Expand Up @@ -36,27 +36,29 @@ end neorv32_xirq;
architecture neorv32_xirq_rtl of neorv32_xirq is

-- register addresses --
constant addr_enable_c : std_ulogic_vector(2 downto 0) := "000"; -- r/w: channel enable
constant addr_pending_c : std_ulogic_vector(2 downto 0) := "001"; -- r/w: pending IRQs
constant addr_source_c : std_ulogic_vector(2 downto 0) := "010"; -- r/w: source IRQ, ACK on write
constant addr_ttype_c : std_ulogic_vector(2 downto 0) := "011"; -- r/w: trigger type (level/edge)
constant addr_tpolarity_c : std_ulogic_vector(2 downto 0) := "100"; -- r/w: trigger polarity (high/low or rising/falling)

-- interface registers --
signal irq_enable, nclr_pending, irq_type, irq_polarity : std_ulogic_vector(XIRQ_NUM_CH-1 downto 0);
signal irq_source : std_ulogic_vector(4 downto 0);
constant addr_eie_c : std_ulogic_vector(1 downto 0) := "00"; -- r/w: channel enable
constant addr_esc_c : std_ulogic_vector(1 downto 0) := "01"; -- r/w: source IRQ, ACK on write
constant addr_ttyp_c : std_ulogic_vector(1 downto 0) := "10"; -- r/w: trigger type (level/edge)
constant addr_tpol_c : std_ulogic_vector(1 downto 0) := "11"; -- r/w: trigger polarity (high/low or rising/falling)

-- configuration registers --
signal irq_enable, irq_type, irq_polarity : std_ulogic_vector(XIRQ_NUM_CH-1 downto 0);

-- interrupt trigger --
signal irq_sync, irq_sync2, irq_trig : std_ulogic_vector(XIRQ_NUM_CH-1 downto 0);
signal irq_sync1, irq_sync2, irq_trig : std_ulogic_vector(XIRQ_NUM_CH-1 downto 0);

-- interrupt buffer --
signal irq_pending, irq_raw : std_ulogic_vector(XIRQ_NUM_CH-1 downto 0);
signal irq_fire, irq_active : std_ulogic;
-- pending interrupt(s) --
signal irq_pending : std_ulogic_vector(XIRQ_NUM_CH-1 downto 0);

-- priority encoder --
type prio_enc_t is array (0 to XIRQ_NUM_CH-1) of std_ulogic_vector(4 downto 0);
signal prio_enc : prio_enc_t;

-- interrupt arbiter --
signal irq_state : std_ulogic_vector(1 downto 0);
signal irq_source : std_ulogic_vector(4 downto 0);
signal irq_clear : std_ulogic_vector(31 downto 0);

begin

-- Bus Access -----------------------------------------------------------------------------
Expand All @@ -65,7 +67,6 @@ begin
begin
if (rstn_i = '0') then
bus_rsp_o <= rsp_terminate_c;
nclr_pending <= (others => '0');
irq_type <= (others => '0');
irq_polarity <= (others => '0');
irq_enable <= (others => '0');
Expand All @@ -74,29 +75,29 @@ begin
bus_rsp_o.ack <= bus_req_i.stb;
bus_rsp_o.err <= '0';
bus_rsp_o.data <= (others => '0');
nclr_pending <= (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(4 downto 2) = addr_enable_c) then -- channel-enable
if (bus_req_i.addr(3 downto 2) = addr_eie_c) then -- channel-enable
irq_enable <= bus_req_i.data(XIRQ_NUM_CH-1 downto 0);
end if;
if (bus_req_i.addr(4 downto 2) = addr_pending_c) then -- clear pending IRQs
nclr_pending <= bus_req_i.data(XIRQ_NUM_CH-1 downto 0); -- set zero to clear pending IRQ
end if;
if (bus_req_i.addr(4 downto 2) = addr_ttype_c) then -- trigger type
if (bus_req_i.addr(3 downto 2) = addr_ttyp_c) then -- trigger type
irq_type <= bus_req_i.data(XIRQ_NUM_CH-1 downto 0);
end if;
if (bus_req_i.addr(4 downto 2) = addr_tpolarity_c) then -- trigger polarity
if (bus_req_i.addr(3 downto 2) = addr_tpol_c) then -- trigger polarity
irq_polarity <= bus_req_i.data(XIRQ_NUM_CH-1 downto 0);
end if;
else -- read access
case bus_req_i.addr(4 downto 2) is
when addr_enable_c => bus_rsp_o.data(XIRQ_NUM_CH-1 downto 0) <= irq_enable; -- channel-enable
when addr_pending_c => bus_rsp_o.data(XIRQ_NUM_CH-1 downto 0) <= irq_pending; -- pending IRQs
when addr_source_c => bus_rsp_o.data(4 downto 0) <= irq_source; -- IRQ source
when addr_ttype_c => bus_rsp_o.data(XIRQ_NUM_CH-1 downto 0) <= irq_type; -- trigger type
when others => bus_rsp_o.data(XIRQ_NUM_CH-1 downto 0) <= irq_polarity; -- trigger polarity
case bus_req_i.addr(3 downto 2) is
when addr_eie_c => -- channel-enable
bus_rsp_o.data(XIRQ_NUM_CH-1 downto 0) <= irq_enable;
when addr_esc_c =>
bus_rsp_o.data(31) <= irq_state(1); -- active interrupt waiting for ACK
bus_rsp_o.data(4 downto 0) <= irq_source; -- interrupt source (channel number)
when addr_ttyp_c => -- trigger type
bus_rsp_o.data(XIRQ_NUM_CH-1 downto 0) <= irq_type;
when others => -- trigger polarity
bus_rsp_o.data(XIRQ_NUM_CH-1 downto 0) <= irq_polarity;
end case;
end if;
end if;
Expand All @@ -109,55 +110,51 @@ begin
synchronizer: process(rstn_i, clk_i)
begin
if (rstn_i = '0') then
irq_sync <= (others => '0');
irq_sync1 <= (others => '0');
irq_sync2 <= (others => '0');
elsif rising_edge(clk_i) then
irq_sync <= xirq_i(XIRQ_NUM_CH-1 downto 0);
irq_sync2 <= irq_sync;
irq_sync1 <= xirq_i(XIRQ_NUM_CH-1 downto 0);
irq_sync2 <= irq_sync1;
end if;
end process synchronizer;

-- trigger type select --
irq_trigger_gen:
for i in 0 to XIRQ_NUM_CH-1 generate
irq_trigger: process(irq_sync, irq_sync2, irq_type, irq_polarity)
irq_trigger: process(irq_sync1, irq_sync2, irq_type, irq_polarity)
variable sel_v : std_ulogic_vector(1 downto 0);
begin
sel_v := irq_type(i) & irq_polarity(i);
case sel_v is
when "00" => irq_trig(i) <= not irq_sync(i); -- low-level
when "01" => irq_trig(i) <= irq_sync(i); -- high-level
when "10" => irq_trig(i) <= (not irq_sync(i)) and irq_sync2(i); -- falling-edge
when "11" => irq_trig(i) <= irq_sync(i) and (not irq_sync2(i)); -- rising-edge
when "00" => irq_trig(i) <= not irq_sync1(i); -- low-level
when "01" => irq_trig(i) <= irq_sync1(i); -- high-level
when "10" => irq_trig(i) <= (not irq_sync1(i)) and irq_sync2(i); -- falling-edge
when "11" => irq_trig(i) <= irq_sync1(i) and (not irq_sync2(i)); -- rising-edge
when others => irq_trig(i) <= '0';
end case;
end process irq_trigger;
end generate;


-- IRQ Buffer ---------------------------------------------------------------
-- Interrupt-Pending Buffer -------------------------------------------------
-- -----------------------------------------------------------------------------
irq_buffer: process(rstn_i, clk_i)
begin
if (rstn_i = '0') then
irq_pending <= (others => '0');
elsif rising_edge(clk_i) then
irq_pending <= (irq_pending and nclr_pending) or irq_trig;
irq_pending <= irq_enable and ((irq_pending and (not irq_clear(XIRQ_NUM_CH-1 downto 0))) or irq_trig);
end if;
end process irq_buffer;

-- filter enabled channels --
irq_raw <= irq_pending and irq_enable;

-- anyone firing? --
irq_fire <= or_reduce_f(irq_raw);

-- encode highest-priority source (structural code: mux-chain) --
-- Priority Encoder (structural code: mux-chain) ----------------------------
-- -----------------------------------------------------------------------------
priority_encoder_gen:
for i in 0 to XIRQ_NUM_CH-1 generate -- start with highest priority
for i in 0 to XIRQ_NUM_CH-1 generate -- start with highest priority (=0)
priority_encoder_gen_chain: -- inside chain
if i < XIRQ_NUM_CH-1 generate
prio_enc(i) <= std_ulogic_vector(to_unsigned(i, 5)) when (irq_raw(i) = '1') else prio_enc(i+1);
prio_enc(i) <= std_ulogic_vector(to_unsigned(i, 5)) when (irq_pending(i) = '1') else prio_enc(i+1);
end generate;
priority_encoder_gen_last: -- end of chain
if i = XIRQ_NUM_CH-1 generate
Expand All @@ -171,23 +168,34 @@ begin
irq_arbiter: process(rstn_i, clk_i)
begin
if (rstn_i = '0') then
irq_active <= '0';
irq_clear <= (others => '0');
irq_source <= (others => '0');
irq_state <= (others => '0');
elsif rising_edge(clk_i) then
if (irq_active = '0') then -- no active IRQ
irq_source <= prio_enc(0); -- get IRQ source
if (irq_fire = '1') then
irq_active <= '1';
end if;
elsif (bus_req_i.stb = '1') and (bus_req_i.rw = '1') and
(bus_req_i.addr(4 downto 2) = addr_source_c) then -- acknowledge on write access
irq_active <= '0';
end if;
irq_clear <= (others => '0'); -- default
case irq_state is

when "00" => -- wait for pending interrupt
irq_source <= prio_enc(0); -- highest-priority channel
if (or_reduce_f(irq_pending) = '1') then
irq_state <= "01";
end if;

when "01" => -- clear triggering channel
irq_clear(to_integer(unsigned(irq_source))) <= '1'; -- ACK/clear according pending bit
irq_state <= "11";

when others => -- wait for CPU acknowledge
if (bus_req_i.stb = '1') and (bus_req_i.rw = '1') and (bus_req_i.addr(3 downto 2) = addr_esc_c) then -- acknowledge on write access
irq_state <= "00";
end if;

end case;
end if;
end process irq_arbiter;

-- CPU interrupt --
cpu_irq_o <= irq_active;
cpu_irq_o <= irq_state(0);


end neorv32_xirq_rtl;
7 changes: 1 addition & 6 deletions sw/example/demo_xirq/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,6 @@ int main() {
// All incoming XIRQ interrupt requests are "prioritized" in this example. The XIRQ FIRQ handler
// reads the ID of the interrupt with the highest priority from the XIRQ controller ("source" register) and calls the according
// handler function (installed via neorv32_xirq_install();).
// Non-prioritized handling of interrupts (or custom prioritization) can be implemented by manually reading the
// XIRQ controller's "pending" register. Then it is up to the software to define which pending IRQ should be serviced first.

asm volatile ("nop");
asm volatile ("nop");
Expand All @@ -165,15 +163,12 @@ int main() {


// just as an example: to disable certain XIRQ interrupt channels, we can
// un-install the according handler. this will also clear a pending interrupt for that channel
// un-install the according handler. this will also disable the according channel.
neorv32_xirq_uninstall(0); // disable XIRQ channel 0 and remove associated handler
neorv32_xirq_uninstall(1); // disable XIRQ channel 1 and remove associated handler
neorv32_xirq_uninstall(2); // disable XIRQ channel 2 and remove associated handler
neorv32_xirq_uninstall(3); // disable XIRQ channel 3 and remove associated handler

// you can also manually clear pending interrupts
neorv32_xirq_clear_pending(0); // clear pending interrupt of channel 0

// manually enable and disable XIRQ channels
neorv32_xirq_channel_enable(0); // enable channel 0
neorv32_xirq_channel_disable(0); // disable channel 0
Expand Down
2 changes: 0 additions & 2 deletions sw/example/processor_check/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1324,8 +1324,6 @@ int main() {
xirq_err_cnt += neorv32_xirq_install(1, xirq_trap_handler1); // install XIRQ IRQ handler channel 1
neorv32_xirq_setup_trigger(0, XIRQ_TRIGGER_EDGE_RISING); // configure channel 0 as rising-edge trigger
neorv32_xirq_setup_trigger(1, XIRQ_TRIGGER_EDGE_RISING); // configure channel 1 as rising-edge trigger
neorv32_xirq_clear_pending(0); // clear any pending request
neorv32_xirq_clear_pending(1); // clear any pending request
neorv32_xirq_channel_enable(0); // enable XIRQ channel 0
neorv32_xirq_channel_enable(1); // enable XIRQ channel 1

Expand Down
Loading

0 comments on commit ac2dc5e

Please sign in to comment.