Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make XIRQ trigger configuration *programmable* #911

Merged
merged 9 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 |
|:----:|:-------:|:--------|:------:|
| 27.05.2024 | 1.9.9.3 | removed `XIRQ_TRIGGER_*` generics; XIRQ trigger type is now _programmable_ by dedicated configuration registers | [#911](https://github.com/stnolting/neorv32/pull/911) |
| 21.05.2024 | 1.9.9.2 | :sparkles: add SLINK routing information ports (compatible to AXI-stream's `TID` and `TDEST` signals) | [#908](https://github.com/stnolting/neorv32/pull/908) |
| 04.05.2024 | 1.9.9.1 | :sparkles: add NEORV32 as Vivado IP block | [#894](https://github.com/stnolting/neorv32/pull/894) |
| 03.05.2024 | [**:rocket:1.9.9**](https://github.com/stnolting/neorv32/releases/tag/v1.9.9) | **New release** | |
Expand Down
2 changes: 0 additions & 2 deletions docs/datasheet/soc.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,6 @@ The generic type "`suv(x:y)`" is an abbreviation for "`std_ulogic_vector(x downt
| `XIP_CACHE_BLOCK_SIZE` | natural | 256 | Number of bytes per XIP cache block. Has to be a power of two, min 4.
4+^| **<<_external_interrupt_controller_xirq>>**
| `XIRQ_NUM_CH` | natural | 0 | Number of channels of the external interrupt controller. Valid values are 0..32.
| `XIRQ_TRIGGER_TYPE` | suv(31:0) | 0xFFFFFFFF | Trigger type (one bit per channel): `0` = level-triggered, '1' = edge triggered.
| `XIRQ_TRIGGER_POLARITY` | suv(31:0) | 0xFFFFFFFF | Trigger polarity (one bit per channel): `0` = low-level/falling-edge, '1' = high-level/rising-edge.
4+^| **Peripheral/IO Modules**
| `IO_GPIO_NUM` | natural | 0 | Number of general purpose input/output pairs of the <<_general_purpose_input_and_output_port_gpio>>.
| `IO_MTIME_EN` | boolean | false | Implement the <<_machine_system_timer_mtime>>.
Expand Down
95 changes: 56 additions & 39 deletions docs/datasheet/soc_xirq.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -5,62 +5,75 @@
[cols="<3,<3,<4"]
[frame="topbot",grid="none"]
|=======================
| Hardware source file(s): | neorv32_xirq.vhd |
| Software driver file(s): | neorv32_xirq.c |
| | neorv32_xirq.h |
| Top entity port: | `xirq_i` | External interrupts input (32-bit)
| Configuration generics: | `XIRQ_NUM_CH` | Number of external IRQ channels to implement (0..32)
| | `XIRQ_TRIGGER_TYPE` | IRQ trigger type configuration
| | `XIRQ_TRIGGER_POLARITY` | IRQ trigger polarity configuration
| CPU interrupts: | fast IRQ channel 8 | XIRQ (see <<_processor_interrupts>>)
| Hardware source file(s): | neorv32_xirq.vhd |
| Software driver file(s): | neorv32_xirq.c |
| | neorv32_xirq.h |
| Top entity port: | `xirq_i` | External interrupts input (32-bit)
| Configuration generic(s): | `XIRQ_NUM_CH` | Number of external IRQ channels to implement (0..32)
| CPU interrupts: | fast IRQ channel 8 | XIRQ (see <<_processor_interrupts>>)
|=======================


**Overview**

The external interrupt controller provides a simple mechanism to implement up to 32 processor-external interrupt
request signals. The external IRQ requests are prioritized, queued and signaled to the CPU via a
_single_ CPU fast interrupt request.
The external interrupt controller provides a simple mechanism to implement up to 32 platform-level / processor-external
interrupt request signals. The external IRQ requests are prioritized, queued and signaled to the CPU via a
_single_ CPU fast interrupt request channel.


**Theory of Operation**

The XIRQ provides up to 32 external interrupt channels configured via the `XIRQ_NUM_CH` generic. Each bit in the `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 actual interrupt trigger type is configured before
synthesis using the `XIRQ_TRIGGER_TYPE` and `XIRQ_TRIGGER_POLARITY` generics (see table below).
The XIRQ provides up to 32 external interrupt channels configured via the `XIRQ_NUM_CH` generic. Each bit in the
`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:

[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
registers corresponds the according XIRQ channel.

.XIRQ Trigger Configuration
[cols="^2,^2,<3"]
[options="header",grid="all"]
|=======================
| `XIRQ_TRIGGER_TYPE(i)` | `XIRQ_TRIGGER_POLARITY(i)` | Resulting Trigger of `xirq_i(i)`
| `0` | `0` | low-level
| `0` | `1` | high-level
| `1` | `0` | falling-edge
| `1` | `1` | rising-edge
| `TTYP(i)` | `TPOL(i)` | Resulting trigger of `xirq_i(i)`
| `0` | `0` | low-level
| `0` | `1` | high-level
| `1` | `0` | falling-edge
| `1` | `1` | rising-edge
|=======================

The interrupt controller features three interface registers: external interrupt channel enable (`EIE`), external interrupt
channel pending (`EIP`) and external interrupt source (`ESC`). From a functional point of view, the functionality of these
registers follow the one of the RISC-V <<_mie>>, <<_mip>> and <<_mcause>> CSRs.

If the configured trigger of an interrupt channel fires (e.g. a rising edge) 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 cleared at any time
by writing zero to the according `EIP` bit.
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 trigger a CPU interrupt if the according is enabled via the `EIE` register. Once triggered, disabled
channels that were triggered remain pending until explicitly 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
actually enabled, an interrupt request is sent to the CPU.
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.

The CPU can determine the most prioritized external interrupt request either by checking the bits in the `IPR` 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.
Writing _any_ value to this register will acknowledge the _current_ XIRQ interrupt (so the XIRQ controller can issue a new CPU interrupt).
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).

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 interrupt
* writing _any_ value to `ESC` to acknowledge the XIRQ CPU interrupt


**Register Map**
Expand All @@ -70,8 +83,12 @@ In order to acknowledge an XIRQ interrupt, the interrupt handler has to...
[options="header",grid="all"]
|=======================
| 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 interrupt
| `0xfffff30c` | - | `31:0` | r/- | _reserved_, read as zero
| `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
|=======================
4 changes: 1 addition & 3 deletions 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"01090902"; -- hardware version
constant hw_version_c : std_ulogic_vector(31 downto 0) := x"01090903"; -- hardware version
constant archid_c : natural := 19; -- official RISC-V architecture ID
constant XLEN : natural := 32; -- native data path width

Expand Down Expand Up @@ -769,8 +769,6 @@ package neorv32_package is
XIP_CACHE_BLOCK_SIZE : natural range 1 to 2**16 := 256;
-- External Interrupts Controller (XIRQ) --
XIRQ_NUM_CH : natural range 0 to 32 := 0;
XIRQ_TRIGGER_TYPE : std_ulogic_vector(31 downto 0) := x"ffffffff";
XIRQ_TRIGGER_POLARITY : std_ulogic_vector(31 downto 0) := x"ffffffff";
-- Processor peripherals --
IO_GPIO_NUM : natural range 0 to 64 := 0;
IO_MTIME_EN : boolean := false;
Expand Down
6 changes: 1 addition & 5 deletions rtl/core/neorv32_top.vhd
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,6 @@ entity neorv32_top is

-- External Interrupts Controller (XIRQ) --
XIRQ_NUM_CH : natural range 0 to 32 := 0; -- number of external IRQ channels (0..32)
XIRQ_TRIGGER_TYPE : std_ulogic_vector(31 downto 0) := x"ffffffff"; -- trigger type: 0=level, 1=edge
XIRQ_TRIGGER_POLARITY : std_ulogic_vector(31 downto 0) := x"ffffffff"; -- trigger polarity: 0=low-level/falling-edge, 1=high-level/rising-edge

-- Processor peripherals --
IO_GPIO_NUM : natural range 0 to 64 := 0; -- number of GPIO input/output pairs (0..64)
Expand Down Expand Up @@ -1434,9 +1432,7 @@ begin
if io_xirq_en_c generate
neorv32_xirq_inst: entity neorv32.neorv32_xirq
generic map (
XIRQ_NUM_CH => XIRQ_NUM_CH,
XIRQ_TRIGGER_TYPE => XIRQ_TRIGGER_TYPE,
XIRQ_TRIGGER_POLARITY => XIRQ_TRIGGER_POLARITY
XIRQ_NUM_CH => XIRQ_NUM_CH
)
port map (
clk_i => clk_i,
Expand Down
51 changes: 31 additions & 20 deletions rtl/core/neorv32_xirq.vhd
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
-- -------------------------------------------------------------------------------- --
-- Simple interrupt controller for platform (processor-external) interrupts. Up to --
-- 32 channels are supported that get (optionally) prioritized into a single CPU --
-- interrupt. --
-- The actual trigger configuration has to be done BEFORE synthesis using the --
-- XIRQ_TRIGGER_TYPE and XIRQ_TRIGGER_POLARITY generics. These allow to configure --
-- channel-independent low-/high-level, falling-/rising-edge triggers. --
-- 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 All @@ -24,9 +21,7 @@ use neorv32.neorv32_package.all;

entity neorv32_xirq is
generic (
XIRQ_NUM_CH : natural range 0 to 32; -- number of IRQ channels
XIRQ_TRIGGER_TYPE : std_ulogic_vector(31 downto 0); -- trigger type: 0=level, 1=edge
XIRQ_TRIGGER_POLARITY : std_ulogic_vector(31 downto 0) -- trigger polarity: 0=low-level/falling-edge, 1=high-level/rising-edge
XIRQ_NUM_CH : natural range 0 to 32 -- number of IRQ channels
);
port (
clk_i : in std_ulogic; -- global clock line
Expand All @@ -40,10 +35,16 @@ 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 : std_ulogic_vector(XIRQ_NUM_CH-1 downto 0); -- r/w: channel enable
signal nclr_pending : std_ulogic_vector(XIRQ_NUM_CH-1 downto 0); -- r/w: pending IRQs
signal irq_source : std_ulogic_vector(4 downto 0); -- r/w: source IRQ, ACK on write
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);

-- interrupt trigger --
signal irq_sync, irq_sync2, irq_trig : std_ulogic_vector(XIRQ_NUM_CH-1 downto 0);
Expand All @@ -67,28 +68,37 @@ begin
bus_rsp_o.err <= '0';
bus_rsp_o.data <= (others => '0');
nclr_pending <= (others => '0');
irq_type <= (others => '0');
irq_polarity <= (others => '0');
irq_enable <= (others => '0');
elsif rising_edge(clk_i) then
-- defaults --
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(3 downto 2) = "00") then -- channel-enable
if (bus_req_i.addr(4 downto 2) = addr_enable_c) then -- channel-enable
irq_enable <= bus_req_i.data(XIRQ_NUM_CH-1 downto 0);
end if;
if (bus_req_i.addr(3 downto 2) = "01") then -- clear pending IRQs
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
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
irq_polarity <= bus_req_i.data(XIRQ_NUM_CH-1 downto 0);
end if;
else -- read access
case bus_req_i.addr(3 downto 2) is
when "00" => bus_rsp_o.data(XIRQ_NUM_CH-1 downto 0) <= irq_enable; -- channel-enable
when "01" => bus_rsp_o.data(XIRQ_NUM_CH-1 downto 0) <= irq_pending; -- pending IRQs
when others => bus_rsp_o.data(4 downto 0) <= irq_source; -- IRQ source
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
end case;
end if;
end if;
Expand All @@ -112,10 +122,10 @@ begin
-- trigger type select --
irq_trigger_gen:
for i in 0 to XIRQ_NUM_CH-1 generate
irq_trigger: process(irq_sync, irq_sync2)
irq_trigger: process(irq_sync, irq_sync2, irq_type, irq_polarity)
variable sel_v : std_ulogic_vector(1 downto 0);
begin
sel_v := XIRQ_TRIGGER_TYPE(i) & XIRQ_TRIGGER_POLARITY(i);
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
Expand Down Expand Up @@ -165,7 +175,8 @@ begin
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(3 downto 2) = "10") then -- acknowledge on write access
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;
end if;
Expand Down
Loading