diff --git a/CHANGELOG.md b/CHANGELOG.md
index a48785498..22be90b62 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -30,6 +30,7 @@ mimpid = 0x01040312 -> Version 01.04.03.12 -> v1.4.3.12
| Date | Version | Comment | Link |
|:----:|:-------:|:--------|:----:|
+| 15.01.2023 | 1.9.4.13 | allow the DMA to issue a FENCE operation | [#807](https://github.com/stnolting/neorv32/pull/807) |
| 14.02.2024 | 1.9.4.12 | :bug: close another illegal compressed instruction encoding loophole | [#806](https://github.com/stnolting/neorv32/pull/806) |
| 11.02.2024 | 1.9.4.11 | :bug: fix several FPU bugs and design flaws | [#794](https://github.com/stnolting/neorv32/pull/794) |
| 11.02.2024 | 1.9.4.10 | minor additions to previous version (1.9.4.9): fix HPM configuration read-back | [#804](https://github.com/stnolting/neorv32/pull/804) |
diff --git a/docs/datasheet/soc_dma.adoc b/docs/datasheet/soc_dma.adoc
index 2cf6559f5..da608484d 100644
--- a/docs/datasheet/soc_dma.adoc
+++ b/docs/datasheet/soc_dma.adoc
@@ -113,6 +113,13 @@ sources are logically OR-ed).
The DMA transfer will start if a **rising edge** is detected on _any_ of the enabled FIRQ source channels.
+**Memory Barrier / Fence Operation**
+
+Optionally, the DMA can issue a FENCE request to the downstream memory system when a transfer has been completed
+without errors. This can be used to re-sync caches (flush and reload) and buffers to maintain data coherency.
+This automatic fencing is enabled by the setting the control register's `DMA_CTRL_FENCE` bit.
+
+
**DMA Interrupt**
The DMA features a single CPU interrupt that is triggered when the programmed transfer has completed. This
@@ -127,15 +134,16 @@ explicitly cleared again by writing zero to the according <<_mip>> CSR bit.
[options="header",grid="all"]
|=======================
| Address | Name [C] | Bit(s), Name [C] | R/W | Function
-.9+<| `0xffffed00` .9+<| `CTRL` <|`0` `DMA_CTRL_EN` ^| r/w <| DMA module enable
- <|`1` `DMA_CTRL_AUTO` ^| r/w <| Enable automatic mode (FIRQ-triggered)
- <|`7:2` _reserved_ ^| r/- <| reserved, read as zero
- <|`8` `DMA_CTRL_ERROR_RD` ^| r/- <| Error during read access, clears when starting a new transfer
- <|`9` `DMA_CTRL_ERROR_WR` ^| r/- <| Error during write access, clears when starting a new transfer
- <|`10` `DMA_CTRL_BUSY` ^| r/- <| DMA transfer in progress
- <|`11` `DMA_CTRL_DONE` ^| r/c <| Set if a transfer was executed; auto-clears on write-access
- <|`15:12` _reserved_ ^| r/- <| reserved, read as zero
- <|`31:16` `DMA_CTRL_FIRQ_MASK_MSB : DMA_CTRL_FIRQ_MASK_LSB` ^| r/w <| FIRQ trigger mask (same bits as in <<_mip>>)
+.10+<| `0xffffed00` .10+<| `CTRL` <|`0` `DMA_CTRL_EN` ^| r/w <| DMA module enable
+ <|`1` `DMA_CTRL_AUTO` ^| r/w <| Enable automatic mode (FIRQ-triggered)
+ <|`2` `DMA_CTRL_FENCE` ^| r/w <| Issue a downstream FENCE operation when DMA transfer completes (without errors)
+ <|`7:3` _reserved_ ^| r/- <| reserved, read as zero
+ <|`8` `DMA_CTRL_ERROR_RD` ^| r/- <| Error during read access, clears when starting a new transfer
+ <|`9` `DMA_CTRL_ERROR_WR` ^| r/- <| Error during write access, clears when starting a new transfer
+ <|`10` `DMA_CTRL_BUSY` ^| r/- <| DMA transfer in progress
+ <|`11` `DMA_CTRL_DONE` ^| r/c <| Set if a transfer was executed; auto-clears on write-access
+ <|`15:12` _reserved_ ^| r/- <| reserved, read as zero
+ <|`31:16` `DMA_CTRL_FIRQ_MASK_MSB : DMA_CTRL_FIRQ_MASK_LSB` ^| r/w <| FIRQ trigger mask (same bits as in <<_mip>>)
| `0xffffed04` | `SRC_BASE` |`31:0` | r/w | Source base address (shows the last-accessed source address when read)
| `0xffffed08` | `DST_BASE` |`31:0` | r/w | Destination base address (shows the last-accessed destination address when read)
.6+<| `0xffffed0c` .6+<| `TTYPE` <|`23:0` `DMA_TTYPE_NUM_MSB : DMA_TTYPE_NUM_LSB` ^| r/w <| Number of elements to transfer (shows the last-transferred element index when read)
diff --git a/rtl/core/neorv32_dma.vhd b/rtl/core/neorv32_dma.vhd
index b96217ee7..70a26bd28 100644
--- a/rtl/core/neorv32_dma.vhd
+++ b/rtl/core/neorv32_dma.vhd
@@ -69,6 +69,7 @@ architecture neorv32_dma_rtl of neorv32_dma is
-- control and status register bits --
constant ctrl_en_c : natural := 0; -- r/w: DMA enable
constant ctrl_auto_c : natural := 1; -- r/w: enable FIRQ-triggered transfer
+ constant ctrl_fence_c : natural := 3; -- r/w: issue FENCE operation when DMA is done
--
constant ctrl_error_rd_c : natural := 8; -- r/-: error during read transfer
constant ctrl_error_wr_c : natural := 9; -- r/-: error during write transfer
@@ -88,6 +89,7 @@ architecture neorv32_dma_rtl of neorv32_dma is
type config_t is record
enable : std_ulogic; -- DMA enabled when set
auto : std_ulogic; -- FIRQ-driven auto transfer
+ fence : std_ulogic; -- issue FENCE operation when DMA is done
firq_mask : std_ulogic_vector(15 downto 0); -- FIRQ trigger mask
src_base : std_ulogic_vector(31 downto 0); -- source base address
dst_base : std_ulogic_vector(31 downto 0); -- destination base address
@@ -139,6 +141,7 @@ begin
bus_rsp_o.data <= (others => '0');
config.enable <= '0';
config.auto <= '0';
+ config.fence <= '0';
config.firq_mask <= (others => '0');
config.src_base <= (others => '0');
config.dst_base <= (others => '0');
@@ -166,6 +169,7 @@ begin
if (bus_req_i.addr(3 downto 2) = "00") then -- control and status register
config.enable <= bus_req_i.data(ctrl_en_c);
config.auto <= bus_req_i.data(ctrl_auto_c);
+ config.fence <= bus_req_i.data(ctrl_fence_c);
config.done <= '0'; -- clear on write access
config.firq_mask <= bus_req_i.data(ctrl_firq_mask_msb_c downto ctrl_firq_mask_lsb_c);
end if;
@@ -190,6 +194,7 @@ begin
when "00" => -- control and status register
bus_rsp_o.data(ctrl_en_c) <= config.enable;
bus_rsp_o.data(ctrl_auto_c) <= config.auto;
+ bus_rsp_o.data(ctrl_fence_c) <= config.fence;
bus_rsp_o.data(ctrl_error_rd_c) <= engine.err_rd;
bus_rsp_o.data(ctrl_error_wr_c) <= engine.err_wr;
bus_rsp_o.data(ctrl_busy_c) <= engine.busy;
@@ -328,7 +333,7 @@ begin
dma_req_o.src <= '0'; -- source = data access
dma_req_o.addr <= engine.src_addr when (engine.state = S_READ) else engine.dst_addr;
dma_req_o.rvso <= '0'; -- no reservation set operation possible
- dma_req_o.fence <= '0'; -- DMA cannot trigger a fence
+ dma_req_o.fence <= config.enable and config.fence and engine.done; -- issue FENCE operation when transfer is done
-- address increment --
address_inc: process(config.qsel)
diff --git a/rtl/core/neorv32_package.vhd b/rtl/core/neorv32_package.vhd
index 5a90692e7..961963d62 100644
--- a/rtl/core/neorv32_package.vhd
+++ b/rtl/core/neorv32_package.vhd
@@ -53,7 +53,7 @@ package neorv32_package is
-- Architecture Constants -----------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
- constant hw_version_c : std_ulogic_vector(31 downto 0) := x"01090412"; -- hardware version
+ constant hw_version_c : std_ulogic_vector(31 downto 0) := x"01090413"; -- hardware version
constant archid_c : natural := 19; -- official RISC-V architecture ID
constant XLEN : natural := 32; -- native data path width
diff --git a/sw/example/demo_dma/main.c b/sw/example/demo_dma/main.c
index a8f8b017d..f6376cc80 100644
--- a/sw/example/demo_dma/main.c
+++ b/sw/example/demo_dma/main.c
@@ -96,6 +96,10 @@ int main() {
// enable DMA
neorv32_dma_enable();
+ // issue a FENCE operation when the DMA transfer completes (without errors); this
+ // will re-sync /flush and reload) all downstream caches
+ neorv32_dma_fence_enable();
+
// initialize and data arrays
dma_src[0] = 0x66778899UL;
dma_src[1] = 0x22334455UL;
@@ -107,7 +111,7 @@ int main() {
dma_dst[2] = 0;
dma_dst[3] = 0;
- asm volatile ("fence"); // make sure main memory is sync with d-cache
+ asm volatile ("fence"); // re-sync caches
// ----------------------------------------------------------
@@ -264,7 +268,7 @@ int main() {
**************************************************************************/
void show_arrays(void) {
- asm volatile ("fence"); // make sure main memory is sync with d-cache
+ asm volatile ("fence"); // re-sync caches
neorv32_uart0_printf("---------------------------\n");
neorv32_uart0_printf(" SRC DST\n");
neorv32_uart0_printf("[0] 0x%x 0x%x\n", dma_src[0], dma_dst[0]);
diff --git a/sw/example/processor_check/main.c b/sw/example/processor_check/main.c
index 682057018..038a8ebe5 100644
--- a/sw/example/processor_check/main.c
+++ b/sw/example/processor_check/main.c
@@ -1470,8 +1470,9 @@ int main() {
if (NEORV32_SYSINFO->SOC & (1 << SYSINFO_SOC_IO_DMA)) {
cnt_test++;
- // enable DMA and according FIRQ channel
+ // enable DMA, auto-fencing and according FIRQ channel
neorv32_dma_enable();
+ neorv32_dma_fence_enable();
neorv32_cpu_irq_enable(DMA_FIRQ_ENABLE);
// setup source data
diff --git a/sw/lib/include/neorv32_dma.h b/sw/lib/include/neorv32_dma.h
index c35f428e3..cdb4bfa6d 100644
--- a/sw/lib/include/neorv32_dma.h
+++ b/sw/lib/include/neorv32_dma.h
@@ -3,7 +3,7 @@
// # ********************************************************************************************* #
// # BSD 3-Clause License #
// # #
-// # Copyright (c) 2023, Stephan Nolting. All rights reserved. #
+// # Copyright (c) 2024, Stephan Nolting. All rights reserved. #
// # #
// # Redistribution and use in source and binary forms, with or without modification, are #
// # permitted provided that the following conditions are met: #
@@ -62,6 +62,7 @@ typedef volatile struct __attribute__((packed,aligned(4))) {
enum NEORV32_DMA_CTRL_enum {
DMA_CTRL_EN = 0, /**< DMA control register(0) (r/w): DMA enable */
DMA_CTRL_AUTO = 1, /**< DMA control register(1) (r/w): Automatic trigger mode enable */
+ DMA_CTRL_FENCE = 2, /**< DMA control register(2) (r/w): Issue FENCE downstream operation when DMA transfer is completed */
DMA_CTRL_ERROR_RD = 8, /**< DMA control register(8) (r/-): Error during read access; SRC_BASE shows the faulting address */
DMA_CTRL_ERROR_WR = 9, /**< DMA control register(9) (r/-): Error during write access; DST_BASE shows the faulting address */
@@ -123,6 +124,8 @@ enum NEORV32_DMA_STATUS_enum {
int neorv32_dma_available(void);
void neorv32_dma_enable(void);
void neorv32_dma_disable(void);
+void neorv32_dma_fence_enable(void);
+void neorv32_dma_fence_disable(void);
void neorv32_dma_transfer(uint32_t base_src, uint32_t base_dst, uint32_t num, uint32_t config);
void neorv32_dma_transfer_auto(uint32_t base_src, uint32_t base_dst, uint32_t num, uint32_t config, uint32_t firq_mask);
int neorv32_dma_status(void);
diff --git a/sw/lib/source/neorv32_dma.c b/sw/lib/source/neorv32_dma.c
index 84b3ed2a6..67e900483 100644
--- a/sw/lib/source/neorv32_dma.c
+++ b/sw/lib/source/neorv32_dma.c
@@ -3,7 +3,7 @@
// # ********************************************************************************************* #
// # BSD 3-Clause License #
// # #
-// # Copyright (c) 2023, Stephan Nolting. All rights reserved. #
+// # Copyright (c) 2024, Stephan Nolting. All rights reserved. #
// # #
// # Redistribution and use in source and binary forms, with or without modification, are #
// # permitted provided that the following conditions are met: #
@@ -78,6 +78,25 @@ void neorv32_dma_disable(void) {
}
+/**********************************************************************//**
+ * Enable memory barrier (fence): issue a FENCE operation when DMA transfer
+ * completes without errors.
+ **************************************************************************/
+void neorv32_dma_fence_enable(void) {
+
+ NEORV32_DMA->CTRL |= (uint32_t)(1 << DMA_CTRL_FENCE);
+}
+
+
+/**********************************************************************//**
+ * Disable memory barrier (fence).
+ **************************************************************************/
+void neorv32_dma_fence_disable(void) {
+
+ NEORV32_DMA->CTRL &= ~((uint32_t)(1 << DMA_CTRL_FENCE));
+}
+
+
/**********************************************************************//**
* Trigger manual DMA transfer.
*
diff --git a/sw/svd/neorv32.svd b/sw/svd/neorv32.svd
index 1f6f5d928..8260dd808 100644
--- a/sw/svd/neorv32.svd
+++ b/sw/svd/neorv32.svd
@@ -371,6 +371,11 @@
[1:1]
Enable automatic transfer trigger (FIRQ-triggered)
+
+ DMA_CTRL_FENCE
+ [2:2]
+ Issue a downstream FENCE operation when DMA transfer completes (without errors)
+
DMA_CTRL_ERROR_RD
[8:8]