Skip to content

Commit

Permalink
Add "out-of-band" signals to internal bus interface (#1131)
Browse files Browse the repository at this point in the history
  • Loading branch information
stnolting authored Dec 28, 2024
2 parents 208ad4e + eb89d7e commit a4935d2
Show file tree
Hide file tree
Showing 17 changed files with 222 additions and 144 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 |
|:----:|:-------:|:--------|:------:|
| 27.12.2024 | 1.10.8.2 | add out-of-band signals to internal request bus | [#1131](https://github.com/stnolting/neorv32/pull/1131) |
| 27.12.2024 | 1.10.8.1 | :warning: replace MTIME by CLINT; :warning: remove `HART_ID` generic | [#1130](https://github.com/stnolting/neorv32/pull/1130) |
| 26.12.2024 | [**:rocket:1.10.8**](https://github.com/stnolting/neorv32/releases/tag/v1.10.8) | **New release** | |
| 23.12.2024 | 1.10.7.9 | :warning: rework IO/peripheral address space; :sparkles: increase device size from 256 bytes to 64kB | [#1126](https://github.com/stnolting/neorv32/pull/1126) |
Expand Down
26 changes: 17 additions & 9 deletions docs/datasheet/cpu.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,6 @@ direction as seen from the CPU.
4+^| **Global Signals**
| `clk_i` | 1 | in | Global clock line, all registers triggering on rising edge.
| `rstn_i` | 1 | in | Global reset, low-active.
| `sleep_o` | 1 | out | CPU is in <<_sleep_mode>> when set.
| `debug_o` | 1 | out | CPU is in <<_cpu_debug_mode,debug mode>> when set.
4+^| **Interrupts (<<_traps_exceptions_and_interrupts>>)**
| `msi_i` | 1 | in | RISC-V machine software interrupt.
| `mei_i` | 1 | in | RISC-V machine external interrupt.
Expand Down Expand Up @@ -342,7 +340,7 @@ The `wfi` instruction will raise an illegal instruction exception when executed
if `TW` in <<_mstatus>> is set. When executed in debug-mode or during single-stepping `wfi` will behave as
simple `nop` without entering sleep mode.

After executing the `wfi` instruction the CPU's `sleep_o` signal (<<_cpu_top_entity_signals>>) will become set
After executing the `wfi` instruction the `sleep` signal of the CPU's request buses (<<_bus_interface>> will become set
as soon as the CPU has fully halted:

[start=1]
Expand Down Expand Up @@ -393,15 +391,22 @@ the instruction fetch interface (`i_bus_*` signals) is used for fetching instruc
(`d_bus_*` signals) is used to access data via load and store operations. Each of these interfaces can access an address
space of up to 2^32^ bytes (4GB).

The bus interface uses two custom interface types: `bus_req_t` is used to propagate the bus access **requests**. These
signals are driven by the _accessing_ device (i.e. the CPU core). `bus_rsp_t` is used to return the bus **response** and
is driven by the _accessed_ device or bus system (i.e. a processor-internal memory or IO device).
The bus interface uses two custom interface types: `bus_req_t` is used to propagate the bus access requests downstream
from a host to a device. These signals are driven by the request-issuing device (i.e. the CPU core). Vice versa, `bus_rsp_t`
is used to return the bus response upstream from a device back to the host and is driven by the accessed device or bus system
(i.e. a processor-internal memory or IO device).

The signals of the request bus are split in to two categories: _in-band_ signals and _out-of-band_ signals. In-band
signals always belong to a certain bus transaction and are only valid between `stb` being set and the according response
(`err` or `ack`). being set. In contrast, the out-of-band signals are not associated with any bus transaction and are
always valid when set.

.Bus Interface - Request Bus (`bus_req_t`)
[cols="^1,^1,<6"]
[options="header",grid="rows"]
|=======================
| Signal | Width | Description
3+^| **In-Band Signals**
| `addr` | 32 | Access address (byte addressing)
| `data` | 32 | Write data
| `ben` | 4 | Byte-enable for each byte in `data`
Expand All @@ -410,7 +415,10 @@ is driven by the _accessed_ device or bus system (i.e. a processor-internal memo
| `src` | 1 | Access source (`0` = instruction fetch, `1` = load/store)
| `priv` | 1 | Set if privileged (M-mode) access
| `rvso` | 1 | Set if current access is a reservation-set operation (`lr` or `sc` instruction, <<_zalrsc_isa_extension>>)
| `fence` | 1 | Data/instruction fence operation; valid without `stb` being set
3+^| **Out-Of-Band Signals**
| `fence` | 1 | Data/instruction fence request; single-shot
| `sleep` | 1 | Set if ALL upstream devices are in <<_sleep_mode>>
| `debug` | 1 | Set if the upstream device is in debug-mode
|=======================

.Bus Interface - Response Bus (`bus_rsp_t`)
Expand Down Expand Up @@ -444,7 +452,7 @@ The figure below shows three exemplary bus accesses:
. A write access to address `B_addr` writing `wdata` (fastest response; `ACK` arrives right in the next cycle).
. A failing read access to address `C_addr` (slow response; `ERR` arrives after several cycles).

.Three Exemplary Bus Transactions
.Three Exemplary Bus Transactions (showing only in-band signals)
image::bus_interface.png[700]

.Adding Register Stages
Expand Down Expand Up @@ -478,7 +486,7 @@ and also registers a reservation for the address `addr` (`rvs_valid` becomes set
invalidated (`rvs_valid` is `0`) the store access fails, so `wdata2` is **not** written to address `addr` at all. The failed
operation is indicated by a **1** being returned via `rsp.data` together with `ack`.

.Three Exemplary LR/SC Bus Transactions
.Three Exemplary LR/SC Bus Transactions (showing only in-band signals)
image::bus_interface_atomic.png[700]

.Store-Conditional Status
Expand Down
Binary file modified docs/figures/bus_interface.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/figures/bus_interface_atomic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion docs/sources/bus_interface.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
{name: 'src', wave: 'x0.|.x0.x..|..'},
{name: 'priv', wave: 'x0.|.x0.x..|..'},
{name: 'rvso', wave: 'x0.|.x0.x..|..'},
{name: 'fence', wave: '0....|.....|..'},
],
{},
[
Expand Down
1 change: 0 additions & 1 deletion docs/sources/bus_interface_atomic.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
{name: 'src', wave: '0....|.....|.....'},
{name: 'priv', wave: '0....|.....|.....'},
{name: 'rvso', wave: '01..0|.1..0|.1..0', node: '.b.......e....'},
{name: 'fence', wave: '0....|.....|.....'},
],
{},
[
Expand Down
216 changes: 141 additions & 75 deletions rtl/core/neorv32_bus.vhd
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ use neorv32.neorv32_package.all;

entity neorv32_bus_switch is
generic (
PORT_A_READ_ONLY : boolean; -- set if port A is read-only
PORT_B_READ_ONLY : boolean -- set if port B is read-only
ROUND_ROBIN_EN : boolean := false; -- enable round-robing scheduling
PORT_A_READ_ONLY : boolean := false; -- set if port A is read-only
PORT_B_READ_ONLY : boolean := false -- set if port B is read-only
);
port (
clk_i : in std_ulogic; -- global clock, rising edge
rstn_i : in std_ulogic; -- global reset, low-active, async
a_lock_i : in std_ulogic; -- exclusive access for port A while set
a_req_i : in bus_req_t; -- host port A request bus (PRIORITIZED)
a_req_i : in bus_req_t; -- host port A request bus
a_rsp_o : out bus_rsp_t; -- host port A response bus
b_req_i : in bus_req_t; -- host port B request bus
b_rsp_o : out bus_rsp_t; -- host port B response bus
Expand All @@ -38,17 +39,10 @@ end neorv32_bus_switch;
architecture neorv32_bus_switch_rtl of neorv32_bus_switch is

-- access arbiter --
type arbiter_t is record
state, state_nxt : std_ulogic_vector(1 downto 0);
a_req, b_req : std_ulogic;
sel, stb : std_ulogic;
end record;
signal arbiter : arbiter_t;

-- FSM states --
constant IDLE : std_ulogic_vector(1 downto 0) := "00";
constant BUSY_A : std_ulogic_vector(1 downto 0) := "01";
constant BUSY_B : std_ulogic_vector(1 downto 0) := "10";
type state_t is (S_CHECK_A, S_BUSY_A, S_CHECK_B, S_BUSY_B);
signal state, state_nxt : state_t;
signal a_req, b_req : std_ulogic;
signal sel, stb : std_ulogic;

begin

Expand All @@ -57,86 +51,158 @@ begin
arbiter_sync: process(rstn_i, clk_i)
begin
if (rstn_i = '0') then
arbiter.state <= IDLE;
arbiter.a_req <= '0';
arbiter.b_req <= '0';
state <= S_CHECK_A;
a_req <= '0';
b_req <= '0';
elsif rising_edge(clk_i) then
arbiter.state <= arbiter.state_nxt;
arbiter.a_req <= (arbiter.a_req or a_req_i.stb) and (not arbiter.state(0)); -- clear STB buffer in BUSY_A
arbiter.b_req <= (arbiter.b_req or b_req_i.stb) and (not arbiter.state(1)); -- clear STB buffer in BUSY_B
state <= state_nxt;
if (state = S_BUSY_A) then -- clear request
a_req <= '0';
else -- buffer request
a_req <= a_req or a_req_i.stb;
end if;
if (state = S_BUSY_B) then -- clear request
b_req <= '0';
else -- buffer request
b_req <= b_req or b_req_i.stb;
end if;
end if;
end process arbiter_sync;

-- fsm --
arbiter_comb: process(arbiter, a_lock_i, a_req_i, b_req_i, x_rsp_i)
begin
-- defaults --
arbiter.state_nxt <= arbiter.state;
arbiter.sel <= '0';
arbiter.stb <= '0';

-- state machine --
case arbiter.state is

when BUSY_A => -- port A access in progress
-- ------------------------------------------------------------
arbiter.sel <= '0';
if (x_rsp_i.err = '1') or (x_rsp_i.ack = '1') then
arbiter.state_nxt <= IDLE;
end if;

when BUSY_B => -- port B access in progress
-- ------------------------------------------------------------
arbiter.sel <= '1';
if (x_rsp_i.err = '1') or (x_rsp_i.ack = '1') then
arbiter.state_nxt <= IDLE;
end if;
-- Prioritizing Bus Switch ----------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
arbiter_prioritized:
if not ROUND_ROBIN_EN generate
arbiter_fsm: process(state, a_req, b_req, a_lock_i, a_req_i, b_req_i, x_rsp_i)
begin
-- defaults --
state_nxt <= state;
sel <= '0';
stb <= '0';

-- state machine --
case state is

when S_BUSY_A => -- port A access in progress
-- ------------------------------------------------------------
sel <= '0';
if (x_rsp_i.err = '1') or (x_rsp_i.ack = '1') then
state_nxt <= S_CHECK_A;
end if;

when others => -- IDLE: wait for requests
-- ------------------------------------------------------------
if (a_req_i.stb = '1') or (arbiter.a_req = '1') then -- request from port A (prioritized)?
arbiter.sel <= '0';
arbiter.stb <= '1';
arbiter.state_nxt <= BUSY_A;
elsif ((b_req_i.stb = '1') or (arbiter.b_req = '1')) and (a_lock_i = '0') then -- request from port B?
arbiter.sel <= '1';
arbiter.stb <= '1';
arbiter.state_nxt <= BUSY_B;
end if;
when S_BUSY_B => -- port B access in progress
-- ------------------------------------------------------------
sel <= '1';
if (x_rsp_i.err = '1') or (x_rsp_i.ack = '1') then
state_nxt <= S_CHECK_A;
end if;

end case;
end process arbiter_comb;
when others => -- wait for requests
-- ------------------------------------------------------------
if (a_req_i.stb = '1') or (a_req = '1') then -- request from port A (prioritized)?
sel <= '0';
stb <= '1';
state_nxt <= S_BUSY_A;
elsif ((b_req_i.stb = '1') or (b_req = '1')) and (a_lock_i = '0') then -- request from port B?
sel <= '1';
stb <= '1';
state_nxt <= S_BUSY_B;
end if;

end case;
end process arbiter_fsm;
end generate;


-- Round-Robin Arbiter --------------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
arbiter_round_robin:
if ROUND_ROBIN_EN generate
arbiter_fsm: process(state, a_req, b_req, a_req_i, b_req_i, x_rsp_i)
begin
-- defaults --
state_nxt <= state;
sel <= '0';
stb <= '0';

-- state machine --
case state is

when S_CHECK_A => -- check if access from port A
-- ------------------------------------------------------------
sel <= '0';
if (a_req_i.stb = '1') or (a_req = '1') then
stb <= '1';
state_nxt <= S_BUSY_A;
else
state_nxt <= S_CHECK_B;
end if;

when S_BUSY_A => -- port B access in progress
-- ------------------------------------------------------------
sel <= '0';
if (x_rsp_i.err = '1') or (x_rsp_i.ack = '1') then
state_nxt <= S_CHECK_B;
end if;

when S_CHECK_B => -- check if access from port B
-- ------------------------------------------------------------
sel <= '1';
if (b_req_i.stb = '1') or (b_req = '1') then
stb <= '1';
state_nxt <= S_BUSY_B;
else
state_nxt <= S_CHECK_A;
end if;

when S_BUSY_B => -- port B access in progress
-- ------------------------------------------------------------
sel <= '1';
if (x_rsp_i.err = '1') or (x_rsp_i.ack = '1') then
state_nxt <= S_CHECK_A;
end if;

when others => -- undefined
-- ------------------------------------------------------------
state_nxt <= S_CHECK_A;

end case;
end process arbiter_fsm;
end generate;


-- Request Switch -------------------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
x_req_o.addr <= a_req_i.addr when (arbiter.sel = '0') else b_req_i.addr;
x_req_o.rvso <= a_req_i.rvso when (arbiter.sel = '0') else b_req_i.rvso;
x_req_o.priv <= a_req_i.priv when (arbiter.sel = '0') else b_req_i.priv;
x_req_o.src <= a_req_i.src when (arbiter.sel = '0') else b_req_i.src;
x_req_o.rw <= a_req_i.rw when (arbiter.sel = '0') else b_req_i.rw;
x_req_o.fence <= a_req_i.fence or b_req_i.fence; -- propagate any fence operations
x_req_o.addr <= a_req_i.addr when (sel = '0') else b_req_i.addr;
x_req_o.rvso <= a_req_i.rvso when (sel = '0') else b_req_i.rvso;
x_req_o.priv <= a_req_i.priv when (sel = '0') else b_req_i.priv;
x_req_o.src <= a_req_i.src when (sel = '0') else b_req_i.src;
x_req_o.rw <= a_req_i.rw when (sel = '0') else b_req_i.rw;
x_req_o.fence <= a_req_i.fence or b_req_i.fence; -- propagate any fence request
x_req_o.sleep <= a_req_i.sleep and b_req_i.sleep; -- set if ALL upstream devices are in sleep mode
x_req_o.debug <= a_req_i.debug when (sel = '0') else b_req_i.debug;

x_req_o.data <= b_req_i.data when PORT_A_READ_ONLY else
a_req_i.data when PORT_B_READ_ONLY else
a_req_i.data when (arbiter.sel = '0') else b_req_i.data;
x_req_o.data <= b_req_i.data when PORT_A_READ_ONLY else
a_req_i.data when PORT_B_READ_ONLY else
a_req_i.data when (sel = '0') else b_req_i.data;

x_req_o.ben <= b_req_i.ben when PORT_A_READ_ONLY else
a_req_i.ben when PORT_B_READ_ONLY else
a_req_i.ben when (arbiter.sel = '0') else b_req_i.ben;
x_req_o.ben <= b_req_i.ben when PORT_A_READ_ONLY else
a_req_i.ben when PORT_B_READ_ONLY else
a_req_i.ben when (sel = '0') else b_req_i.ben;

x_req_o.stb <= arbiter.stb;
x_req_o.stb <= stb;


-- Response Switch ------------------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
a_rsp_o.data <= x_rsp_i.data;
a_rsp_o.ack <= x_rsp_i.ack when (arbiter.sel = '0') else '0';
a_rsp_o.err <= x_rsp_i.err when (arbiter.sel = '0') else '0';
a_rsp_o.ack <= x_rsp_i.ack when (sel = '0') else '0';
a_rsp_o.err <= x_rsp_i.err when (sel = '0') else '0';

b_rsp_o.data <= x_rsp_i.data;
b_rsp_o.ack <= x_rsp_i.ack when (arbiter.sel = '1') else '0';
b_rsp_o.err <= x_rsp_i.err when (arbiter.sel = '1') else '0';
b_rsp_o.ack <= x_rsp_i.ack when (sel = '1') else '0';
b_rsp_o.err <= x_rsp_i.err when (sel = '1') else '0';


end neorv32_bus_switch_rtl;
Expand Down Expand Up @@ -703,10 +769,10 @@ entity neorv32_bus_reservation_set is
rvs_addr_o : out std_ulogic_vector(31 downto 0);
rvs_valid_o : out std_ulogic;
rvs_clear_i : in std_ulogic;
-- core/cpu port --
-- core port --
core_req_i : in bus_req_t;
core_rsp_o : out bus_rsp_t;
-- system ports --
-- system port --
sys_req_o : out bus_req_t;
sys_rsp_i : in bus_rsp_t
);
Expand Down
Loading

0 comments on commit a4935d2

Please sign in to comment.