mirror of
https://github.com/stnolting/neorv32.git
synced 2025-04-24 22:27:21 -04:00
⚠️ [dma] remove firq-triggered auto mode
This commit is contained in:
parent
ecb185b9b6
commit
35d0fefbf0
4 changed files with 123 additions and 223 deletions
|
@ -16,17 +16,20 @@
|
|||
|
||||
**Overview**
|
||||
|
||||
The NEORV32 DMA provides a small-scale scatter/gather direct memory access controller that allows to transfer and
|
||||
modify data independently of the CPU. A single read/write transfer channel is implemented that is configured via
|
||||
memory-mapped registers. a configured transfer can either be triggered manually or by a programmable CPU FIRQ interrupt
|
||||
(see <<_neorv32_specific_fast_interrupt_requests>>).
|
||||
The NEORV32 DMA provides a lightweight direct memory access controller that allows to transfer and
|
||||
modify data independently of the CPU. A single read/write channel is implemented that is configured via
|
||||
memory-mapped registers.
|
||||
|
||||
The DMA is connected to the central processor-internal bus system (see section <<_address_space>>) and can access the same
|
||||
address space as the CPU core. It uses _interleaving mode_ accessing the central processor bus only if the CPU does not
|
||||
currently request and bus access.
|
||||
currently request and bus access. The controller can handle different data quantities (e.g. read bytes and write them
|
||||
back as sign-extend words) and can also change the Endianness of data while transferring.
|
||||
|
||||
The controller can handle different data quantities (e.g. read bytes and write them back as sign-extend words) and can
|
||||
also change the Endianness of data while transferring.
|
||||
|
||||
.DMA Access Privilege Level
|
||||
[WARNING]
|
||||
Transactions performed by the DMA are executed as bus transactions with elevated **machine-mode** privilege level.
|
||||
Note that any physical memory protection rules (<<_smpmp_isa_extension>>) are not applied to DMA transfers.
|
||||
|
||||
.DMA Demo Program
|
||||
[TIP]
|
||||
|
@ -40,39 +43,32 @@ configuring the actual DMA transfer. The base address of the source data is prog
|
|||
Vice versa, the base address of the destination data is programmed via the `DST_BASE`. The third configuration register
|
||||
`TTYPE` is use to configure the actual transfer type and the number of elements to transfer.
|
||||
|
||||
The DMA is enabled by setting the `DMA_CTRL_EN` bit of the control register. Manual trigger mode (i.e. the DMA transfer is
|
||||
triggered by writing to the `TTYPE` register) is selected if `DMA_CTRL_AUTO` is cleared. Alternatively, the DMA transfer can
|
||||
be triggered by a processor internal FIRQ signal if `DMA_CTRL_AUTO` is set (see section below).
|
||||
The DMA is enabled by setting the `DMA_CTRL_EN` bit of the control register. A programmed DMA transfer is initiated
|
||||
by setting the control register's `DMA_CTRL_START` bit.
|
||||
|
||||
The DMA uses a load-modify-write data transfer process. Data is read from the bus system, internally modified and then written
|
||||
back to the bus system. This combination is implemented as an atomic progress, so canceling the current transfer by clearing the
|
||||
`DMA_CTRL_EN` bit will stop the DMA right after the current load-modify-write operation.
|
||||
`DMA_CTRL_EN` bit will stop the DMA after the current load-modify-write operation.
|
||||
|
||||
If the DMA controller detects a bus error during operation, it will set either the `DMA_CTRL_ERROR_RD` (error during
|
||||
last read access) or `DMA_CTRL_ERROR_WR` (error during last write access) and will terminate the current transfer.
|
||||
Software can read the `SRC_BASE` or `DST_BASE` register to retrieve the address that caused the according error.
|
||||
Alternatively, software can read back the `NUM` bits of the control register to determine the index of the element
|
||||
that caused the error. The error bits are automatically cleared when starting a new transfer.
|
||||
The error bits are automatically cleared when starting a new transfer. The error flags auto-clear when starting a new
|
||||
DMA transfer.
|
||||
|
||||
When the `DMA_CTRL_DONE` flag is set the DMA has actually executed a transfer. However, the `DMA_CTRL_ERROR_*` flags
|
||||
should also be checked to verify that the executed transfer completed without errors. The `DMA_CTRL_DONE` flag is
|
||||
automatically cleared when writing the `CTRL` register.
|
||||
|
||||
.DMA Access Privilege Level
|
||||
[WARNING]
|
||||
Transactions performed by the DMA are executed as bus transactions with elevated **machine-mode** privilege level.
|
||||
Note that any physical memory protection rules (<<_smpmp_isa_extension>>) are not applied to DMA transfers.
|
||||
|
||||
|
||||
**Transfer Configuration**
|
||||
|
||||
If the DMA is set to **manual trigger mode** (`DMA_CTRL_AUTO` = 0) writing the `TTRIG` register will start the
|
||||
programmed DMA transfer. Once started, the DMA will read one data quantity from the source address, processes it internally
|
||||
Once started, the DMA will read one data quantity from the source address, processes it internally
|
||||
and then will write it back to the destination address. The `DMA_TTYPE_NUM` bits of the `TTYPE` register define how many
|
||||
times this process is repeated by specifying the number of elements to transfer.
|
||||
|
||||
Optionally, the source and/or destination addresses can be increments according to the data quantities
|
||||
automatically by setting the according `DMA_TTYPE_SRC_INC` and/or `DMA_TTYPE_DST_INC` bit.
|
||||
Optionally, the source and/or destination addresses can be automatically increments according to the data quantities
|
||||
by setting the according `DMA_TTYPE_SRC_INC` and/or `DMA_TTYPE_DST_INC` bit.
|
||||
|
||||
Four different transfer quantities are available, which are configured via the `DMA_TTYPE_QSEL` bits:
|
||||
|
||||
|
@ -89,34 +85,11 @@ bit is set.
|
|||
Make sure to align the source and destination base addresses to the according transfer data quantities. For instance,
|
||||
word-to-word transfers require that the two LSB of `SRC_BASE` and `DST_BASE` are cleared.
|
||||
|
||||
.Writing to IO Device
|
||||
.Accessing IO Device
|
||||
[IMPORTANT]
|
||||
When writing data to IO / peripheral devices (for example to the <<_cyclic_redundancy_check_crc>>) the destination
|
||||
data quantity has to be set to **word** (32-bit) since all IO registers can only be written in full 32-bit word mode.
|
||||
|
||||
|
||||
**Automatic Trigger**
|
||||
|
||||
As an alternative to the manual trigger mode, the DMA can be set to **automatic trigger mode** starting a pre-configured
|
||||
transfer if a specific processor-internal peripheral issues a FIRQ interrupt request. The automatic trigger mode is enabled by
|
||||
setting the `CTRL` register's `DMA_CTRL_AUTO` bit. In this configuration _no_ transfer is started when writing to the DMA's
|
||||
`TTYPE` register.
|
||||
|
||||
The actually triggering FIRQ channel is configured via the control register's `DMA_CTRL_FIRQ_SEL` bits. Writing a 0 will
|
||||
select FIRQ channel 0, writing a 1 will select FIRQ channel 1, and so on. See section <<_processor_interrupts>>
|
||||
for a list of all FIRQ channels and their according sources.
|
||||
|
||||
The FIRQ trigger can operate in two trigger mode configured via the `DMA_CTRL_FIRQ_TYPE` flag:
|
||||
|
||||
* `DMA_CTRL_FIRQ_TYPE = 0`: trigger the automatic DMA transfer on a rising-edge of the selected FIRQ channel (e.g. trigger
|
||||
DMA transfer only once)
|
||||
* `DMA_CTRL_FIRQ_TYPE = 1`: trigger the automatic DMA transfer when the selected FIRQ channel is active (e.g. trigger
|
||||
DMA transfer again and again)
|
||||
|
||||
.FIRQ Trigger
|
||||
[NOTE]
|
||||
The DMA transfer will start if a **rising edge** is detected on the configured FIRQ channel. Hence, the DMA is triggered only
|
||||
once even if the selected FIRQ channel keeps pending.
|
||||
In contrast, read accesses to IO / peripheral devices can also be executed on a byte granule.
|
||||
|
||||
|
||||
**DMA Interrupt**
|
||||
|
@ -134,23 +107,19 @@ register).
|
|||
[options="header",grid="all"]
|
||||
|=======================
|
||||
| Address | Name [C] | Bit(s), Name [C] | R/W | Function
|
||||
.11+<| `0xffed0000` .11+<| `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
|
||||
<|`14:12` _reserved_ ^| r/- <| reserved, read as zero
|
||||
<|`15` `DMA_CTRL_FIRQ_TYPE` ^| r/w <| Trigger on rising-edge (`0`) or high-level (`1`) or selected FIRQ channel
|
||||
<|`19:16` `DMA_CTRL_FIRQ_SEL_MSB : DMA_CTRL_FIRQ_SEL_LSB` ^| r/w <| FIRQ trigger select (FIRQ0=0 ... FIRQ15=15)
|
||||
<|`31:20` _reserved_ ^| r/- <| reserved, read as zero
|
||||
.7+<| `0xffed0000` .7+<| `CTRL` <|`0` `DMA_CTRL_EN` ^| r/w <| DMA module enable
|
||||
<|`1` `DMA_CTRL_START` ^| r/s <| Start programmed DMA transfer (reads as zero)
|
||||
<|`7:27` _reserved_ ^| r/- <| reserved, read as zero
|
||||
<|`28` `DMA_CTRL_ERROR_RD` ^| r/- <| Error during read access, clears when starting a new transfer
|
||||
<|`29` `DMA_CTRL_ERROR_WR` ^| r/- <| Error during write access, clears when starting a new transfer
|
||||
<|`30` `DMA_CTRL_DONE` ^| r/c <| Set if a transfer was executed; auto-clears on write-access
|
||||
<|`31` `DMA_CTRL_BUSY` ^| r/- <| DMA transfer in progress
|
||||
| `0xffed0004` | `SRC_BASE` |`31:0` | r/w | Source base address (shows the last-accessed source address when read)
|
||||
| `0xffed0008` | `DST_BASE` |`31:0` | r/w | Destination base address (shows the last-accessed destination address when read)
|
||||
.6+<| `0xffed000c` .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)
|
||||
<|`26:24` _reserved_ ^| r/- <| reserved, read as zero
|
||||
<|`28:27` `DMA_TTYPE_QSEL_MSB : DMA_TTYPE_QSEL_LSB` ^| r/w <| Quantity select (`00` = byte -> byte, `01` = byte -> zero-extended-word, `10` = byte -> sign-extended-word, `11` = word -> word)
|
||||
<|`28:27` `DMA_TTYPE_QSEL_MSB : DMA_TTYPE_QSEL_LSB` ^| r/w <| Transfer type (`00` = byte -> byte, `01` = byte -> zero-extended-word, `10` = byte -> sign-extended-word, `11` = word -> word)
|
||||
<|`29` `DMA_TTYPE_SRC_INC` ^| r/w <| Constant (`0`) or incrementing (`1`) source address
|
||||
<|`30` `DMA_TTYPE_DST_INC` ^| r/w <| Constant (`0`) or incrementing (`1`) destination address
|
||||
<|`31` `DMA_TTYPE_ENDIAN` ^| r/w <| Swap Endianness when set
|
||||
<|`31` `DMA_TTYPE_ENDIAN` ^| r/w <| Convert Endianness when set
|
||||
|=======================
|
||||
|
|
|
@ -23,7 +23,6 @@ entity neorv32_dma is
|
|||
bus_rsp_o : out bus_rsp_t; -- bus response
|
||||
dma_req_o : out bus_req_t; -- DMA request
|
||||
dma_rsp_i : in bus_rsp_t; -- DMA response
|
||||
firq_i : in std_ulogic_vector(15 downto 0); -- CPU FIRQ channels
|
||||
irq_o : out std_ulogic -- transfer done interrupt
|
||||
);
|
||||
end neorv32_dma;
|
||||
|
@ -41,17 +40,13 @@ architecture neorv32_dma_rtl of neorv32_dma is
|
|||
constant type_endian_c : natural := 31; -- r/w: Convert Endianness when set
|
||||
|
||||
-- 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_en_c : natural := 0; -- r/w: DMA enable
|
||||
constant ctrl_start_c : natural := 1; -- -/s: start DMA operation
|
||||
--
|
||||
constant ctrl_error_rd_c : natural := 8; -- r/-: error during read transfer
|
||||
constant ctrl_error_wr_c : natural := 9; -- r/-: error during write transfer
|
||||
constant ctrl_busy_c : natural := 10; -- r/-: DMA transfer in progress
|
||||
constant ctrl_done_c : natural := 11; -- r/c: a DMA transfer was executed/attempted
|
||||
--
|
||||
constant ctrl_firq_type_c : natural := 15; -- r/w: trigger on FIRQ rising-edge or on high-level
|
||||
constant ctrl_firq_sel_lsb_c : natural := 16; -- r/w: FIRQ trigger select LSB
|
||||
constant ctrl_firq_sel_msb_c : natural := 19; -- r/w: FIRQ trigger select MSB
|
||||
constant ctrl_error_rd_c : natural := 28; -- r/-: error during read transfer
|
||||
constant ctrl_error_wr_c : natural := 29; -- r/-: error during write transfer
|
||||
constant ctrl_done_c : natural := 30; -- r/c: transfer has completed
|
||||
constant ctrl_busy_c : natural := 31; -- r/-: DMA transfer in progress
|
||||
|
||||
-- transfer quantities --
|
||||
constant qsel_b2b_c : std_ulogic_vector(1 downto 0) := "00"; -- byte to byte
|
||||
|
@ -61,19 +56,16 @@ architecture neorv32_dma_rtl of neorv32_dma is
|
|||
|
||||
-- configuration registers --
|
||||
type cfg_t is record
|
||||
enable : std_ulogic; -- DMA enabled when set
|
||||
auto : std_ulogic; -- FIRQ-driven auto transfer
|
||||
firq_sel : std_ulogic_vector(3 downto 0); -- FIRQ trigger select
|
||||
firq_type : std_ulogic; -- trigger on FIRQ rising-edge (0) or high-level (1)
|
||||
src_base : std_ulogic_vector(31 downto 0); -- source base address
|
||||
dst_base : std_ulogic_vector(31 downto 0); -- destination base address
|
||||
num : std_ulogic_vector(23 downto 0); -- number of elements
|
||||
qsel : std_ulogic_vector(1 downto 0); -- data quantity select
|
||||
src_inc : std_ulogic; -- constant (0) or incrementing (1) source address
|
||||
dst_inc : std_ulogic; -- constant (0) or incrementing (1) destination address
|
||||
endian : std_ulogic; -- convert endianness when set
|
||||
start : std_ulogic; -- transfer start trigger
|
||||
done : std_ulogic; -- transfer was executed (but might have failed)
|
||||
enable : std_ulogic; -- DMA enabled when set
|
||||
start : std_ulogic; -- transfer start trigger
|
||||
done : std_ulogic; -- transfer was executed (but might have failed)
|
||||
src_base : std_ulogic_vector(31 downto 0); -- source base address
|
||||
dst_base : std_ulogic_vector(31 downto 0); -- destination base address
|
||||
num : std_ulogic_vector(23 downto 0); -- number of elements
|
||||
qsel : std_ulogic_vector(1 downto 0); -- data quantity select
|
||||
src_inc : std_ulogic; -- constant (0) or incrementing (1) source address
|
||||
dst_inc : std_ulogic; -- constant (0) or incrementing (1) destination address
|
||||
endian : std_ulogic; -- convert endianness when set
|
||||
end record;
|
||||
signal cfg : cfg_t;
|
||||
|
||||
|
@ -81,6 +73,8 @@ architecture neorv32_dma_rtl of neorv32_dma is
|
|||
type state_t is (S_IDLE, S_READ, S_WRITE, S_NEXT);
|
||||
type engine_t is record
|
||||
state : state_t;
|
||||
stb : std_ulogic;
|
||||
rw : std_ulogic;
|
||||
src_addr : std_ulogic_vector(31 downto 0);
|
||||
dst_addr : std_ulogic_vector(31 downto 0);
|
||||
num : std_ulogic_vector(23 downto 0);
|
||||
|
@ -94,14 +88,7 @@ architecture neorv32_dma_rtl of neorv32_dma is
|
|||
signal engine : engine_t;
|
||||
|
||||
-- data alignment --
|
||||
signal align_buf : std_ulogic_vector(31 downto 0);
|
||||
signal align_end : std_ulogic_vector(31 downto 0);
|
||||
|
||||
-- FIRQ trigger --
|
||||
signal firq_buf : std_ulogic_vector(15 downto 0);
|
||||
signal match : std_ulogic;
|
||||
signal match_ff : std_ulogic;
|
||||
signal atrigger : std_ulogic;
|
||||
signal align_buf, align_end : std_ulogic_vector(31 downto 0);
|
||||
|
||||
begin
|
||||
|
||||
|
@ -110,20 +97,17 @@ begin
|
|||
bus_access: process(rstn_i, clk_i)
|
||||
begin
|
||||
if (rstn_i = '0') then
|
||||
bus_rsp_o <= rsp_terminate_c;
|
||||
cfg.enable <= '0';
|
||||
cfg.auto <= '0';
|
||||
cfg.firq_sel <= (others => '0');
|
||||
cfg.firq_type <= '0';
|
||||
cfg.src_base <= (others => '0');
|
||||
cfg.dst_base <= (others => '0');
|
||||
cfg.num <= (others => '0');
|
||||
cfg.qsel <= (others => '0');
|
||||
cfg.src_inc <= '0';
|
||||
cfg.dst_inc <= '0';
|
||||
cfg.endian <= '0';
|
||||
cfg.start <= '0';
|
||||
cfg.done <= '0';
|
||||
bus_rsp_o <= rsp_terminate_c;
|
||||
cfg.enable <= '0';
|
||||
cfg.start <= '0';
|
||||
cfg.done <= '0';
|
||||
cfg.src_base <= (others => '0');
|
||||
cfg.dst_base <= (others => '0');
|
||||
cfg.num <= (others => '0');
|
||||
cfg.qsel <= (others => '0');
|
||||
cfg.src_inc <= '0';
|
||||
cfg.dst_inc <= '0';
|
||||
cfg.endian <= '0';
|
||||
elsif rising_edge(clk_i) then
|
||||
-- bus handshake --
|
||||
bus_rsp_o.ack <= bus_req_i.stb;
|
||||
|
@ -134,14 +118,13 @@ begin
|
|||
cfg.start <= '0'; -- default
|
||||
cfg.done <= cfg.enable and (cfg.done or engine.done); -- set if enabled and transfer done
|
||||
|
||||
-- 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 -- control and status register
|
||||
cfg.enable <= bus_req_i.data(ctrl_en_c);
|
||||
cfg.auto <= bus_req_i.data(ctrl_auto_c);
|
||||
cfg.done <= '0'; -- clear on write access
|
||||
cfg.firq_type <= bus_req_i.data(ctrl_firq_type_c);
|
||||
cfg.firq_sel <= bus_req_i.data(ctrl_firq_sel_msb_c downto ctrl_firq_sel_lsb_c);
|
||||
cfg.enable <= bus_req_i.data(ctrl_en_c);
|
||||
cfg.start <= bus_req_i.data(ctrl_start_c); -- start transfer
|
||||
cfg.done <= '0'; -- clear on write access
|
||||
end if;
|
||||
if (bus_req_i.addr(3 downto 2) = "01") then -- source base address
|
||||
cfg.src_base <= bus_req_i.data;
|
||||
|
@ -155,19 +138,15 @@ begin
|
|||
cfg.src_inc <= bus_req_i.data(type_src_inc_c);
|
||||
cfg.dst_inc <= bus_req_i.data(type_dst_inc_c);
|
||||
cfg.endian <= bus_req_i.data(type_endian_c);
|
||||
cfg.start <= '1'; -- trigger DMA operation
|
||||
end if;
|
||||
else -- read access
|
||||
case bus_req_i.addr(3 downto 2) is
|
||||
when "00" => -- control and status register
|
||||
bus_rsp_o.data(ctrl_en_c) <= cfg.enable;
|
||||
bus_rsp_o.data(ctrl_auto_c) <= cfg.auto;
|
||||
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;
|
||||
bus_rsp_o.data(ctrl_done_c) <= cfg.done;
|
||||
bus_rsp_o.data(ctrl_firq_type_c) <= cfg.firq_type;
|
||||
bus_rsp_o.data(ctrl_firq_sel_msb_c downto ctrl_firq_sel_lsb_c) <= cfg.firq_sel;
|
||||
bus_rsp_o.data(ctrl_en_c) <= cfg.enable;
|
||||
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_done_c) <= cfg.done;
|
||||
bus_rsp_o.data(ctrl_busy_c) <= engine.busy;
|
||||
when "01" => -- address of last read access
|
||||
bus_rsp_o.data <= engine.src_addr;
|
||||
when "10" => -- address of last write access
|
||||
|
@ -188,63 +167,40 @@ begin
|
|||
irq_o <= cfg.done;
|
||||
|
||||
|
||||
-- Automatic Trigger ----------------------------------------------------------------------
|
||||
-- -------------------------------------------------------------------------------------------
|
||||
automatic_trigger: process(rstn_i, clk_i)
|
||||
begin
|
||||
if (rstn_i = '0') then
|
||||
firq_buf <= (others => '0');
|
||||
match_ff <= '0';
|
||||
atrigger <= '0';
|
||||
elsif rising_edge(clk_i) then
|
||||
firq_buf <= firq_i;
|
||||
match_ff <= match;
|
||||
if (cfg.firq_type = '0') then -- auto-trigger on rising-edge of FIRQ
|
||||
atrigger <= match and (not match_ff);
|
||||
else -- auto-trigger on high-level of FIRQ
|
||||
atrigger <= match;
|
||||
end if;
|
||||
end if;
|
||||
end process automatic_trigger;
|
||||
|
||||
-- select a single FIRQ --
|
||||
match <= firq_buf(to_integer(unsigned(cfg.firq_sel)));
|
||||
|
||||
|
||||
-- Bus Access Engine ----------------------------------------------------------------------
|
||||
-- -------------------------------------------------------------------------------------------
|
||||
bus_engine: process(rstn_i, clk_i)
|
||||
begin
|
||||
if (rstn_i = '0') then
|
||||
engine.state <= S_IDLE;
|
||||
engine.stb <= '0';
|
||||
engine.rw <= '0';
|
||||
engine.src_addr <= (others => '0');
|
||||
engine.dst_addr <= (others => '0');
|
||||
engine.num <= (others => '0');
|
||||
engine.err_rd <= '0';
|
||||
engine.err_wr <= '0';
|
||||
engine.done <= '0';
|
||||
dma_req_o.rw <= '0';
|
||||
dma_req_o.stb <= '0';
|
||||
elsif rising_edge(clk_i) then
|
||||
-- defaults --
|
||||
engine.done <= '0';
|
||||
dma_req_o.stb <= '0';
|
||||
engine.done <= '0';
|
||||
engine.stb <= '0';
|
||||
|
||||
-- state machine --
|
||||
case engine.state is
|
||||
|
||||
when S_IDLE => -- idle, waiting for start trigger
|
||||
when S_IDLE => -- idle, waiting for trigger
|
||||
-- ------------------------------------------------------------
|
||||
engine.src_addr <= cfg.src_base;
|
||||
engine.dst_addr <= cfg.dst_base;
|
||||
engine.num <= cfg.num;
|
||||
if (cfg.enable = '1') and
|
||||
(((cfg.auto = '0') and (cfg.start = '1')) or -- manual trigger
|
||||
((cfg.auto = '1') and (atrigger = '1'))) then -- automatic trigger
|
||||
engine.rw <= '0';
|
||||
if (cfg.enable = '0') and (cfg.start = '1') then -- disabled or start
|
||||
engine.err_rd <= '0';
|
||||
engine.err_wr <= '0';
|
||||
dma_req_o.rw <= '0'; -- read
|
||||
dma_req_o.stb <= '1'; -- issue read request
|
||||
end if;
|
||||
if (cfg.enable = '1') and (cfg.start = '1') then -- start
|
||||
engine.stb <= '1';
|
||||
engine.state <= S_READ;
|
||||
end if;
|
||||
|
||||
|
@ -255,9 +211,9 @@ begin
|
|||
engine.err_rd <= '1';
|
||||
engine.state <= S_IDLE;
|
||||
elsif (dma_rsp_i.ack = '1') then
|
||||
dma_req_o.rw <= '1'; -- write
|
||||
dma_req_o.stb <= '1'; -- issue write request
|
||||
engine.state <= S_WRITE;
|
||||
engine.rw <= '1'; -- write
|
||||
engine.stb <= '1'; -- issue write request
|
||||
engine.state <= S_WRITE;
|
||||
end if;
|
||||
|
||||
when S_WRITE => -- pending write access
|
||||
|
@ -283,9 +239,9 @@ begin
|
|||
if (cfg.dst_inc = '1') then -- incrementing destination address
|
||||
engine.dst_addr <= std_ulogic_vector(unsigned(engine.dst_addr) + engine.dst_add);
|
||||
end if;
|
||||
dma_req_o.rw <= '0'; -- read
|
||||
dma_req_o.stb <= '1'; -- issue read request
|
||||
engine.state <= S_READ;
|
||||
engine.rw <= '0';
|
||||
engine.stb <= '1'; -- issue read request
|
||||
engine.state <= S_READ;
|
||||
end if;
|
||||
|
||||
when others => -- undefined
|
||||
|
@ -300,7 +256,9 @@ begin
|
|||
engine.busy <= '0' when (engine.state = S_IDLE) else '1';
|
||||
|
||||
-- bus output --
|
||||
dma_req_o.addr <= engine.src_addr when (engine.state = S_READ) else engine.dst_addr;
|
||||
dma_req_o.stb <= engine.stb;
|
||||
dma_req_o.rw <= engine.rw;
|
||||
dma_req_o.addr <= engine.dst_addr when (engine.state = S_WRITE) else engine.src_addr;
|
||||
dma_req_o.src <= '0'; -- source = data access
|
||||
dma_req_o.priv <= priv_mode_m_c; -- DMA accesses are always privileged
|
||||
dma_req_o.debug <= '0'; -- can never ever be in debug mode
|
||||
|
@ -312,9 +270,15 @@ begin
|
|||
address_inc: process(cfg.qsel)
|
||||
begin
|
||||
case cfg.qsel is
|
||||
when qsel_b2b_c => engine.src_add <= to_unsigned(1, 32); engine.dst_add <= to_unsigned(1, 32); -- byte -> byte
|
||||
when qsel_w2w_c => engine.src_add <= to_unsigned(4, 32); engine.dst_add <= to_unsigned(4, 32); -- word -> word
|
||||
when others => engine.src_add <= to_unsigned(1, 32); engine.dst_add <= to_unsigned(4, 32); -- byte -> word
|
||||
when qsel_b2b_c => -- byte -> byte
|
||||
engine.src_add <= to_unsigned(1, 32);
|
||||
engine.dst_add <= to_unsigned(1, 32);
|
||||
when qsel_w2w_c => -- word -> word
|
||||
engine.src_add <= to_unsigned(4, 32);
|
||||
engine.dst_add <= to_unsigned(4, 32);
|
||||
when others => -- byte -> word
|
||||
engine.src_add <= to_unsigned(1, 32);
|
||||
engine.dst_add <= to_unsigned(4, 32);
|
||||
end case;
|
||||
end process address_inc;
|
||||
|
||||
|
|
|
@ -35,16 +35,12 @@ typedef volatile struct __attribute__((packed,aligned(4))) {
|
|||
/** DMA control and status register bits */
|
||||
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_START = 1, /**< DMA control register(1) (-/s): Start configured DMA transfer */
|
||||
|
||||
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 */
|
||||
DMA_CTRL_BUSY = 10, /**< DMA control register(10) (r/-): DMA busy / transfer in progress */
|
||||
DMA_CTRL_DONE = 11, /**< DMA control register(11) (r/c): A transfer was executed when set */
|
||||
|
||||
DMA_CTRL_FIRQ_TYPE = 15, /**< DMA control register(15) (r/w): Trigger on FIRQ rising-edge (0) or high-level (1) */
|
||||
DMA_CTRL_FIRQ_SEL_LSB = 16, /**< DMA control register(16) (r/w): FIRQ trigger select LSB */
|
||||
DMA_CTRL_FIRQ_SEL_MSB = 19 /**< DMA control register(19) (r/w): FIRQ trigger select MSB */
|
||||
DMA_CTRL_ERROR_RD = 28, /**< DMA control register(28) (r/-): Error during read access; SRC_BASE shows the faulting address */
|
||||
DMA_CTRL_ERROR_WR = 29, /**< DMA control register(29) (r/-): Error during write access; DST_BASE shows the faulting address */
|
||||
DMA_CTRL_DONE = 30, /**< DMA control register(30) (r/c): A transfer has been executed when set */
|
||||
DMA_CTRL_BUSY = 31 /**< DMA control register(32) (r/-): DMA busy / transfer in progress */
|
||||
};
|
||||
|
||||
/** DMA transfer type bits */
|
||||
|
@ -87,10 +83,22 @@ enum NEORV32_DMA_STATUS_enum {
|
|||
DMA_STATUS_ERR_WR = -2, /**< write access error during last transfer (-2) */
|
||||
DMA_STATUS_ERR_RD = -1, /**< read access error during last transfer (-1) */
|
||||
DMA_STATUS_IDLE = 0, /**< DMA idle (0) */
|
||||
DMA_STATUS_BUSY = 1 /**< DMA busy (1) */
|
||||
DMA_STATUS_BUSY = 1, /**< DMA busy (1) */
|
||||
DMA_STATUS_DONE = 2 /**< transfer done (2) */
|
||||
};
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* DMA transfer descriptor
|
||||
**************************************************************************/
|
||||
typedef struct __attribute__((packed,aligned(4))) {
|
||||
uint32_t src; /**< 32-bit source base address */
|
||||
uint32_t dst; /**< 32-bit destination base address */
|
||||
uint32_t num; /**< 24-bit (LSB-aligned) number of elements to transfer */
|
||||
uint32_t cmd; /**< transfer type */
|
||||
} neorv32_dma_desc_t;
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* @name Prototypes
|
||||
**************************************************************************/
|
||||
|
@ -98,10 +106,8 @@ enum NEORV32_DMA_STATUS_enum {
|
|||
int neorv32_dma_available(void);
|
||||
void neorv32_dma_enable(void);
|
||||
void neorv32_dma_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, int firq_sel, int firq_type);
|
||||
void neorv32_dma_transfer(neorv32_dma_desc_t *desc);
|
||||
int neorv32_dma_status(void);
|
||||
int neorv32_dma_done(void);
|
||||
/**@}*/
|
||||
|
||||
|
||||
|
|
|
@ -56,37 +56,12 @@ void neorv32_dma_disable(void) {
|
|||
* @param[in] num Number of elements to transfer (24-bit).
|
||||
* @param[in] config Transfer type configuration/commands.
|
||||
**************************************************************************/
|
||||
void neorv32_dma_transfer(uint32_t base_src, uint32_t base_dst, uint32_t num, uint32_t config) {
|
||||
void neorv32_dma_transfer(neorv32_dma_desc_t *desc) {
|
||||
|
||||
NEORV32_DMA->CTRL &= ~((uint32_t)(1 << DMA_CTRL_AUTO)); // manual transfer trigger
|
||||
NEORV32_DMA->SRC_BASE = base_src;
|
||||
NEORV32_DMA->DST_BASE = base_dst;
|
||||
NEORV32_DMA->TTYPE = (num & 0x00ffffffUL) | (config & 0xff000000UL); // trigger transfer
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Configure automatic DMA transfer (triggered by CPU FIRQ).
|
||||
*
|
||||
* @param[in] base_src Source base address (has to be aligned to source data type!).
|
||||
* @param[in] base_dst Destination base address (has to be aligned to destination data type!).
|
||||
* @param[in] num Number of elements to transfer (24-bit).
|
||||
* @param[in] config Transfer type configuration/commands.
|
||||
* @param[in] firq_sel FIRQ trigger select (#NEORV32_CSR_MIP_enum); only FIRQ0..FIRQ15 = 16..31.
|
||||
* @param[in] firq_type Trigger on rising-edge (0) or high-level (1) of FIRQ channel.
|
||||
**************************************************************************/
|
||||
void neorv32_dma_transfer_auto(uint32_t base_src, uint32_t base_dst, uint32_t num, uint32_t config, int firq_sel, int firq_type) {
|
||||
|
||||
uint32_t tmp = NEORV32_DMA->CTRL;
|
||||
tmp |= (uint32_t)(1 << DMA_CTRL_AUTO); // automatic transfer trigger
|
||||
tmp &= ~(0xf << DMA_CTRL_FIRQ_SEL_LSB); // clear current FIRQ select
|
||||
tmp |= (uint32_t)((firq_sel & 0xf) << DMA_CTRL_FIRQ_SEL_LSB); // set new FIRQ select
|
||||
tmp |= (uint32_t)((firq_type & 1) << DMA_CTRL_FIRQ_TYPE); // FIRQ trigger type
|
||||
NEORV32_DMA->CTRL = tmp;
|
||||
|
||||
NEORV32_DMA->SRC_BASE = base_src;
|
||||
NEORV32_DMA->DST_BASE = base_dst;
|
||||
NEORV32_DMA->TTYPE = (num & 0x00ffffffUL) | (config & 0xff000000UL);
|
||||
NEORV32_DMA->SRC_BASE = desc->src;
|
||||
NEORV32_DMA->DST_BASE = desc->dst;
|
||||
NEORV32_DMA->TTYPE = (desc->num & 0x00ffffffUL) | (desc->cmd & 0xff000000UL);
|
||||
NEORV32_DMA->CTRL |= 1<<DMA_CTRL_START;
|
||||
}
|
||||
|
||||
|
||||
|
@ -108,24 +83,10 @@ int neorv32_dma_status(void) {
|
|||
else if (tmp & (1 << DMA_CTRL_BUSY)) {
|
||||
return DMA_STATUS_BUSY; // transfer in progress
|
||||
}
|
||||
else if (tmp & (1 << DMA_CTRL_DONE)) {
|
||||
return DMA_STATUS_DONE; // transfer done
|
||||
}
|
||||
else {
|
||||
return DMA_STATUS_IDLE; // idle
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************//**
|
||||
* Check if a transfer has actually been executed.
|
||||
*
|
||||
* @return 0 if no transfer was executed, 1 if a transfer has actually been executed.
|
||||
* Use neorv32_dma_status(void) to check if there was an error during that transfer.
|
||||
**************************************************************************/
|
||||
int neorv32_dma_done(void) {
|
||||
|
||||
if (NEORV32_DMA->CTRL & (1 << DMA_CTRL_DONE)) {
|
||||
return 1; // there was a transfer
|
||||
}
|
||||
else {
|
||||
return 0; // no transfer executed
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue