⚠️ rework PWM module (#1049)

This commit is contained in:
stnolting 2024-10-06 21:07:55 +02:00 committed by GitHub
commit e688424752
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 333 additions and 265 deletions

View file

@ -29,6 +29,7 @@ mimpid = 0x01040312 -> Version 01.04.03.12 -> v1.4.3.12
| Date | Version | Comment | Ticket |
|:----:|:-------:|:--------|:------:|
| 06.10.2024 | 1.10.5.4 | :warning: rework PWM module | [#1049](https://github.com/stnolting/neorv32/pull/1049) |
| 05.10.2024 | 1.10.5.3 | upgrade neoTRNG to version 3.2 | [#1048](https://github.com/stnolting/neorv32/pull/1048) |
| 03.10.2024 | 1.10.5.2 | :warning: remove `A` ISA extensions; replaced by new `Zalrsc` ISA extension | [#1047](https://github.com/stnolting/neorv32/pull/1047) |
| 02.10.2024 | 1.10.5.1 | :warning: rework CFU interface; reduce minimal latency of CFU instructions from 4 cycles to 3 cycles | [#1046](https://github.com/stnolting/neorv32/pull/1046) |
@ -57,7 +58,7 @@ mimpid = 0x01040312 -> Version 01.04.03.12 -> v1.4.3.12
| 05.09.2024 | 1.10.3.1 | minor CPU RTL cleanups and optimizations | [#1004](https://github.com/stnolting/neorv32/pull/1004) |
| 03.09.2024 | [**:rocket:1.10.3**](https://github.com/stnolting/neorv32/releases/tag/v1.10.3) | **New release** | |
| 30.08.2024 | 1.10.2.9 | :bug: fix PC reset bug (introduced in v1.10.2.8); minor RTL optimizations (size and critical path) | [#998](https://github.com/stnolting/neorv32/pull/998) |
| 25.08.2024 | 1.10.2.8 | :warning: remove user-mode HPM counters; add individual `mocuntern` bits (`CY` and `IR`) rework Vivado IP module; minor RTL cleanups and optimization | [#996](https://github.com/stnolting/neorv32/pull/996) |
| 25.08.2024 | 1.10.2.8 | :warning: remove user-mode HPM counters; add individual `mocunteren` bits (`CY` and `IR`) rework Vivado IP module; minor RTL cleanups and optimization | [#996](https://github.com/stnolting/neorv32/pull/996) |
| 16.08.2024 | 1.10.2.7 | minor CPU area and critical path optimizations; minor code cleanups | [#990](https://github.com/stnolting/neorv32/pull/990) |
| 09.08.2024 | 1.10.2.6 | :warning: re-organize RTL files; all core files are now located in `rtl/core`; remove `mem` sub-folder | [#985](https://github.com/stnolting/neorv32/pull/985) |
| 09.08.2024 | 1.10.2.5 | minor HDL edits | [#984](https://github.com/stnolting/neorv32/pull/984) |

View file

@ -20,7 +20,8 @@ image::neorv32_processor.png[align=center]
**Key Features**
* _optional_ processor-internal data and instruction memories (<<_data_memory_dmem,**DMEM**>>/<<_instruction_memory_imem,**IMEM**>>)
* _optional_ caches (<<_processor_internal_instruction_cache_icache,**iCACHE**>>, <<_processor_internal_data_cache_dcache,**dCACHE**>>, <<_execute_in_place_module_xip,**xipCACHE**>>, <<_processor_external_bus_interface_xbus,**xCACHE**>>)
* _optional_ caches (<<_processor_internal_instruction_cache_icache,**I-CACHE**>>, <<_processor_internal_data_cache_dcache,**D-CACHE**>>,
<<_execute_in_place_module_xip,**XIP-CACHE**>>, <<_processor_external_bus_interface_xbus,**XBUS-CACHE**>>)
* _optional_ internal bootloader (<<_bootloader_rom_bootrom,**BOOTROM**>>) with UART console & SPI flash boot option
* _optional_ machine system timer (<<_machine_system_timer_mtime,**MTIME**>>), RISC-V-compatible
* _optional_ two independent universal asynchronous receivers and transmitters (<<_primary_universal_asynchronous_receiver_and_transmitter_uart0,**UART0**>>,
@ -31,11 +32,11 @@ image::neorv32_processor.png[align=center]
* _optional_ general purpose parallel IO port (<<_general_purpose_input_and_output_port_gpio,**GPIO**>>), 64xOut, 64xIn
* _optional_ 32-bit external bus interface, Wishbone b4 / AXI4-Lite compatible (<<_processor_external_bus_interface_xbus,**XBUS**>>)
* _optional_ watchdog timer (<<_watchdog_timer_wdt,**WDT**>>)
* _optional_ PWM controller with up to 12 channels & 8-bit duty cycle resolution (<<_pulse_width_modulation_controller_pwm,**PWM**>>)
* _optional_ PWM controller with up to 16 individual channels (<<_pulse_width_modulation_controller_pwm,**PWM**>>)
* _optional_ ring-oscillator-based true random number generator (<<_true_random_number_generator_trng,**TRNG**>>)
* _optional_ custom functions subsystem for custom co-processor extensions (<<_custom_functions_subsystem_cfs,**CFS**>>)
* _optional_ NeoPixel(TM)/WS2812-compatible smart LED interface (<<_smart_led_interface_neoled,**NEOLED**>>)
* _optional_ external interrupt controller with up to 32 channels (<<_external_interrupt_controller_xirq,**XIRQ**>>)
* _optional_ external interrupt controller with up to 32 channels and programmable interrupt triggers (<<_external_interrupt_controller_xirq,**XIRQ**>>)
* _optional_ general purpose 32-bit timer (<<_general_purpose_timer_gptmr,**GPTMR**>>)
* _optional_ execute in-place module (<<_execute_in_place_module_xip,**XIP**>>)
* _optional_ 1-wire serial interface controller (<<_one_wire_serial_interface_controller_onewire,**ONEWIRE**>>), compatible to the 1-wire standard
@ -43,7 +44,7 @@ image::neorv32_processor.png[align=center]
* _optional_ stream link interface (<<_stream_link_interface_slink,**SLINK**>>), AXI4-Stream compatible
* _optional_ cyclic redundancy check unit (<<_cyclic_redundancy_check_crc,**CRC**>>)
* _optional_ on-chip debugger with JTAG TAP (<<_on_chip_debugger_ocd,**OCD**>>)
* (optional) system configuration information memory to check HW configuration via software (<<_system_configuration_information_memory_sysinfo,**SYSINFO**>>)
* _optional_ system configuration information memory to determine hardware configuration via software (<<_system_configuration_information_memory_sysinfo,**SYSINFO**>>)
<<<
@ -72,7 +73,7 @@ bits/channels are hardwired to zero.
Some interfaces (like the TWI and the 1-Wire bus) require explicit tri-state drivers in the final top module.
.Input/Output Registers
[NOTE]
[NOTE]
By default all output signals are driven by register and all input signals are synchronized into the processor's
clock domain also using registers. However, for ASIC implementations it is recommended to add another register state
to all inputs and output so the synthesis tool can insert an explicit IO (boundary) scan chain.
@ -149,7 +150,7 @@ to all inputs and output so the synthesis tool can insert an explicit IO (bounda
| `onewire_i` | 1 | in | `'H'` | 1-wire bus sense input
| `onewire_o` | 1 | out | - | 1-wire bus output (pull low only)
5+^| **<<_pulse_width_modulation_controller_pwm>>**
| `pwm_o` | 12 | out | - | pulse-width modulated channels
| `pwm_o` | 16 | out | - | pulse-width modulated channels
5+^| **<<_custom_functions_subsystem_cfs>>**
| `cfs_in_i` | 32 | in | `'L'` | custom CFS input signal conduit
| `cfs_out_o` | 32 | out | - | custom CFS output signal conduit
@ -289,7 +290,7 @@ The generic type "`suv(x:y)`" is an abbreviation for "`std_ulogic_vector(x downt
| `IO_SDI_FIFO` | natural | 1 | Depth of the <<_serial_data_interface_controller_sdi>> FIFO. Has to be a power of two, min 1, max 32768.
| `IO_TWI_EN` | boolean | false | Implement the <<_two_wire_serial_interface_controller_twi>>.
| `IO_TWI_FIFO` | natural | 1 | Depth of the <<_two_wire_serial_interface_controller_twi>> FIFO. Has to be a power of two, min 1, max 32768.
| `IO_PWM_NUM_CH` | natural | 0 | Number of channels of the <<_pulse_width_modulation_controller_pwm>> to implement (0..12).
| `IO_PWM_NUM_CH` | natural | 0 | Number of channels of the <<_pulse_width_modulation_controller_pwm>> to implement (0..16).
| `IO_WDT_EN` | boolean | false | Implement the <<_watchdog_timer_wdt>>.
| `IO_TRNG_EN` | boolean | false | Implement the <<_true_random_number_generator_trng>>.
| `IO_TRNG_FIFO` | natural | 1 | Depth of the TRNG data FIFO. Has to be a power of two, min 1, max 32768.
@ -585,7 +586,7 @@ explicit specific processor generic. See section <<_processor_external_bus_inter
==== Reservation Set Controller
The reservation set controller is responsible for handling the load-reservate and store-conditional bus transaction that
are triggered by the `lr.w` (LR) and `sc.w` (SC) instructions from the CPU's <<_a_isa_extension>>.
are triggered by the `lr.w` (LR) and `sc.w` (SC) instructions from the CPU's <<_zalrsc_isa_extension>>.
A "reservation" defines an address or address range that provides a guarding mechanism to support atomic accesses. A new
reservation is registered by the LR instruction. The address provided by this instruction defines the memory location

View file

@ -17,47 +17,45 @@
**Overview**
The PWM module implements a pulse-width modulation controller with up to 12 independent channels providing
8-bit resolution per channel. The actual number of implemented channels is defined by the `IO_PWM_NUM_CH` generic.
Setting this generic to zero will completely remove the PWM controller from the design.
[NOTE]
The `pwm_o` has a static size of 12-bit. If less than 12 PWM channels are configured, only the LSB-aligned channel
bits are used while the remaining bits are hardwired to zero.
The PWM module implements a pulse-width modulation controller with up to 16 independent channels. Duty cycle and
carrier frequency can be programmed individually for each channel.The total number of implemented channels is
defined by the `IO_PWM_NUM_CH` generic. The PWM output signal `pwm_o` has a static size of 16-bit. Channel 0
corresponds to bit 0, channel 1 to bit 1 and so on. If less than 16 channels are configured, only the LSB-aligned
channel bits are connected while the remaining ones are hardwired to zero.
**Theory of Operation**
The PWM controller is activated by setting the `PWM_CTRL_EN` bit in the module's control register `CTRL`. When this
bit is cleared, the unit is reset and all PWM output channels are set to zero. The module
provides three duty cycle registers `DC[0]` to `DC[2]`. Each register contains the duty cycle configuration for four
consecutive channels. For example, the duty cycle of channel 0 is defined via bits 7:0 in `DC[0]`. The duty cycle of
channel 2 is defined via bits 15:0 in `DC[0]` and so on.
Depending on the configured number channels, the PWM module provides 16 configuration registers `CHANNEL_CFG[0]` to
`CHANNEL_CFG[15]` - one for each channel. Regardless of the configuration of `IO_PWM_NUM_CH` all channel registers can
be accessed without raising an exception. However, registers above `IO_PWM_NUM_CH-1` are read-only and hardwired to
all-zero.
[NOTE]
Regardless of the configuration of `IO_PWM_NUM_CH` all module registers can be accessed without raising an exception.
Software can discover the number of available channels by writing 0xff to all duty cycle configuration bytes and
reading those values back. The duty-cycle of channels that were not implemented always reads as zero.
Each configuration provides a 1-bit enable flag to enable/disable the according channel, an 8-bit register for setting
the duty cycle and a 3-bit clock prescaler select as well as a 10-bit clock diver for _coarse_ and _fine_ tuning of the
carrier frequency, respectively.
Based on the configured duty cycle the according intensity of the channel can be computed by the following formula:
A channel is enabled by setting the `PWM_CFG_EN` bit. If this bit is cleared the according PWM output is set to zero.
The duty cycle is programmed via the 8 `PWM_CFG_DUTY` bits. Based on the value programmed to this bits the duty cycle
the resulting duty cycle of the according channel can be computed by the following formula:
_**Intensity~x~**_ = `DC[y](i*8+7 downto i*8)` / (2^8^)
_Duty Cycle_[%] = `PWM_CFG_DUTY` / 2^8^
The base frequency of the generated PWM signals is defined by the PWM core clock. This clock is derived
from the main processor clock and divided by a prescaler via the 3-bit `PWM_CTRL_PRSCx` in the unit's control
register.
The PWM period (carrier frequency) is derived from the processor's main clock (_f~main~_). The `PWM_CFG_PRSC` register
bits allow to select one out of eight pre-defined clock prescalers for a coarse clock scaling. The 10 `PWM_CFG_CDIV` register
bits can be used to apply another fine clock scaling.
.PWM prescaler configuration
[cols="<4,^1,^1,^1,^1,^1,^1,^1,^1"]
[options="header",grid="rows"]
|=======================
| **`PWM_CTRL_PRSCx`** | `0b000` | `0b001` | `0b010` | `0b011` | `0b100` | `0b101` | `0b110` | `0b111`
| **`PWM_CFG_PRSC`** | `0b000` | `0b001` | `0b010` | `0b011` | `0b100` | `0b101` | `0b110` | `0b111`
| Resulting `clock_prescaler` | 2 | 4 | 8 | 64 | 128 | 1024 | 2048 | 4096
|=======================
The resulting PWM carrier frequency is defined by:
_**f~PWM~**_ = _f~main~[Hz]_ / (2^8^ * `clock_prescaler`)
_f~PWM~_[Hz] = _f~main~_[Hz] / (2^8^ * `clock_prescaler` * (1 + `PWM_CFG_CDIV`))
**Register Map**
@ -67,19 +65,15 @@ _**f~PWM~**_ = _f~main~[Hz]_ / (2^8^ * `clock_prescaler`)
[options="header",grid="all"]
|=======================
| Address | Name [C] | Bit(s), Name [C] | R/W | Function
.3+<| `0xfffff000` .3+<| `CTRL` <|`0` `PWM_CTRL_EN` ^| r/w <| PWM enable
<|`3:1` `PWM_CTRL_PRSC2 : PWM_CTRL_PRSC0` ^| r/w <| 3-bit clock prescaler select
<|`31:4` - ^| r/- <| _reserved_, read as zero
.4+<| `0xfffff004` .4+<| `DC[0]` <|`7:0` ^| r/w <| 8-bit duty cycle for channel 0
<|`15:8` ^| r/w <| 8-bit duty cycle for channel 1
<|`23:16` ^| r/w <| 8-bit duty cycle for channel 2
<|`31:24` ^| r/w <| 8-bit duty cycle for channel 3
.4+<| `0xfffff008` .4+<| `DC[1]` <|`7:0` ^| r/w <| 8-bit duty cycle for channel 4
<|`15:8` ^| r/w <| 8-bit duty cycle for channel 5
<|`23:16` ^| r/w <| 8-bit duty cycle for channel 6
<|`31:24` ^| r/w <| 8-bit duty cycle for channel 7
.4+<| `0xfffff00c` .4+<| `DC[2]` <|`7:0` ^| r/w <| 8-bit duty cycle for channel 8
<|`15:8` ^| r/w <| 8-bit duty cycle for channel 9
<|`23:16` ^| r/w <| 8-bit duty cycle for channel 10
<|`31:24` ^| r/w <| 8-bit duty cycle for channel 11
.5+<| `0xfffff000` .5+<| `CHANNEL_CFG[0]` <|`31` - `PWM_CFG_EN` ^| r/w <| Channel 0: channel enabled when set
<|`30:28` - `PWM_CFG_PRSC_MSB:PWM_CFG_PRSC_LSB` ^| r/w <| Channel 0: 3-bit clock prescaler select
<|`27:18` ^| r/- <| Channel 0: _reserved_, hardwired to zero
<|`17:8` - `PWM_CFG_CDIV_MSB:PWM_CFG_CDIV_LSB` ^| r/w <| Channel 0: 10-bit clock divider
<|`7:0` - `PWM_CFG_DUTY_MSB:PWM_CFG_DUTY_LSB` ^| r/w <| Channel 0: 8-bit duty cycle
| `0xfffff004` ... `0xfffff038` | `CHANNEL_CFG[1]` ... `CHANNEL_CFG[14]` | ... | r/w <| Channels 1 to 14
.5+<| `0xfffff03C` .5+<| `CHANNEL_CFG[15]` <|`31` - `PWM_CFG_EN` ^| r/w <| Channel 15: channel enabled when set
<|`30:28` - `PWM_CFG_PRSC_MSB:PWM_CFG_PRSC_LSB` ^| r/w <| Channel 15: 3-bit clock prescaler select
<|`27:18` ^| r/- <| Channel 15: _reserved_, hardwired to zero
<|`17:8` - `PWM_CFG_CDIV_MSB:PWM_CFG_CDIV_LSB` ^| r/w <| Channel 15: 10-bit clock divider
<|`7:0` - `PWM_CFG_DUTY_MSB:PWM_CFG_DUTY_LSB` ^| r/w <| Channel 15: 8-bit duty cycle
|=======================

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Before After
Before After

View file

@ -29,7 +29,7 @@ package neorv32_package is
-- Architecture Constants -----------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
constant hw_version_c : std_ulogic_vector(31 downto 0) := x"01100503"; -- hardware version
constant hw_version_c : std_ulogic_vector(31 downto 0) := x"01100504"; -- hardware version
constant archid_c : natural := 19; -- official RISC-V architecture ID
constant XLEN : natural := 32; -- native data path width
@ -755,7 +755,7 @@ package neorv32_package is
IO_SDI_FIFO : natural range 1 to 2**15 := 1;
IO_TWI_EN : boolean := false;
IO_TWI_FIFO : natural range 1 to 2**15 := 1;
IO_PWM_NUM_CH : natural range 0 to 12 := 0;
IO_PWM_NUM_CH : natural range 0 to 16 := 0;
IO_WDT_EN : boolean := false;
IO_TRNG_EN : boolean := false;
IO_TRNG_FIFO : natural range 1 to 2**15 := 1;
@ -841,7 +841,7 @@ package neorv32_package is
onewire_i : in std_ulogic := 'H';
onewire_o : out std_ulogic;
-- PWM (available if IO_PWM_NUM_CH > 0) --
pwm_o : out std_ulogic_vector(11 downto 0); -- pwm channels
pwm_o : out std_ulogic_vector(15 downto 0); -- pwm channels
-- Custom Functions Subsystem IO --
cfs_in_i : in std_ulogic_vector(IO_CFS_IN_SIZE-1 downto 0) := (others => 'L');
cfs_out_o : out std_ulogic_vector(IO_CFS_OUT_SIZE-1 downto 0);

View file

@ -1,6 +1,10 @@
-- ================================================================================ --
-- NEORV32 SoC - Pulse Width Modulation Controller (PWM) --
-- -------------------------------------------------------------------------------- --
-- Providing up to 16 individual PWM channels; each channel features an individual --
-- enable flag, an 8-bit duty-cycle configuration, a 3-bit prescaler (for coarse --
-- clock configuration) and a 16-bit clock divider (for fine clock configuration). --
-- -------------------------------------------------------------------------------- --
-- The NEORV32 RISC-V Processor - https://github.com/stnolting/neorv32 --
-- Copyright (c) NEORV32 contributors. --
-- Copyright (c) 2020 - 2024 Stephan Nolting. All rights reserved. --
@ -17,7 +21,7 @@ use neorv32.neorv32_package.all;
entity neorv32_pwm is
generic (
NUM_CHANNELS : natural range 0 to 12 -- number of PWM channels (0..12)
NUM_CHANNELS : natural range 0 to 16 -- number of PWM channels (0..16)
);
port (
clk_i : in std_ulogic; -- global clock line
@ -26,32 +30,32 @@ entity neorv32_pwm is
bus_rsp_o : out bus_rsp_t; -- bus response
clkgen_en_o : out std_ulogic; -- enable clock generator
clkgen_i : in std_ulogic_vector(7 downto 0); -- clock divider input
pwm_o : out std_ulogic_vector(11 downto 0) -- PWM output
pwm_o : out std_ulogic_vector(15 downto 0) -- PWM output
);
end neorv32_pwm;
architecture neorv32_pwm_rtl of neorv32_pwm is
-- Control register bits --
constant ctrl_enable_c : natural := 0; -- r/w: PWM enable
constant ctrl_prsc0_bit_c : natural := 1; -- r/w: prescaler select bit 0
constant ctrl_prsc1_bit_c : natural := 2; -- r/w: prescaler select bit 1
constant ctrl_prsc2_bit_c : natural := 3; -- r/w: prescaler select bit 2
-- pwm channel controller --
component neorv32_pwm_channel
port (
clk_i : in std_ulogic; -- global clock line
rstn_i : in std_ulogic; -- global reset line, low-active, async
we_i : in std_ulogic; -- write enable
re_i : in std_ulogic; -- read enable
wdata_i : in std_ulogic_vector(31 downto 0); -- write data
rdata_o : out std_ulogic_vector(31 downto 0); -- read data
clkgen_i : in std_ulogic_vector(7 downto 0); -- clock divider input
clkgen_en_o : out std_ulogic; -- enable clock generator
pwm_o : out std_ulogic -- PWM output
);
end component;
-- accessible regs --
type pwm_ch_t is array (0 to 11) of std_ulogic_vector(7 downto 0);
signal pwm_ch : pwm_ch_t; -- duty cycle (r/w)
signal enable : std_ulogic; -- enable unit (r/w)
signal prsc : std_ulogic_vector(2 downto 0); -- clock prescaler (r/w)
type pwm_ch_rd_t is array (0 to 11) of std_ulogic_vector(7 downto 0);
signal pwm_ch_rd : pwm_ch_rd_t; -- duty cycle read-back
-- prescaler clock generator --
signal prsc_tick : std_ulogic;
-- pwm core counter --
signal pwm_cnt : std_ulogic_vector(7 downto 0);
-- wiring --
type rdata_t is array (0 to NUM_CHANNELS-1) of std_ulogic_vector(31 downto 0);
signal rdata : rdata_t;
signal rdata_sum : std_ulogic_vector(31 downto 0);
signal sel, we, re, ce, pwm : std_ulogic_vector(NUM_CHANNELS-1 downto 0);
begin
@ -61,99 +65,173 @@ begin
begin
if (rstn_i = '0') then
bus_rsp_o <= rsp_terminate_c;
enable <= '0';
prsc <= (others => '0');
pwm_ch <= (others => (others => '0'));
elsif rising_edge(clk_i) then
-- bus handshake --
bus_rsp_o.ack <= bus_req_i.stb;
bus_rsp_o.err <= '0';
bus_rsp_o.data <= (others => '0');
if (bus_req_i.stb = '1') then
-- write access --
if (bus_req_i.rw = '1') then
-- control register --
if (bus_req_i.addr(3 downto 2) = "00") then
enable <= bus_req_i.data(ctrl_enable_c);
prsc <= bus_req_i.data(ctrl_prsc2_bit_c downto ctrl_prsc0_bit_c);
end if;
-- duty cycle register 0 --
if (bus_req_i.addr(3 downto 2) = "01") then
pwm_ch(00) <= bus_req_i.data(07 downto 00);
pwm_ch(01) <= bus_req_i.data(15 downto 08);
pwm_ch(02) <= bus_req_i.data(23 downto 16);
pwm_ch(03) <= bus_req_i.data(31 downto 24);
end if;
-- duty cycle register 1 --
if (bus_req_i.addr(3 downto 2) = "10") then
pwm_ch(04) <= bus_req_i.data(07 downto 00);
pwm_ch(05) <= bus_req_i.data(15 downto 08);
pwm_ch(06) <= bus_req_i.data(23 downto 16);
pwm_ch(07) <= bus_req_i.data(31 downto 24);
end if;
-- duty cycle register 2 --
if (bus_req_i.addr(3 downto 2) = "11") then
pwm_ch(08) <= bus_req_i.data(07 downto 00);
pwm_ch(09) <= bus_req_i.data(15 downto 08);
pwm_ch(10) <= bus_req_i.data(23 downto 16);
pwm_ch(11) <= bus_req_i.data(31 downto 24);
end if;
-- read access --
else
case bus_req_i.addr(3 downto 2) is
when "00" => bus_rsp_o.data(ctrl_enable_c) <= enable; bus_rsp_o.data(ctrl_prsc2_bit_c downto ctrl_prsc0_bit_c) <= prsc;
when "01" => bus_rsp_o.data <= pwm_ch_rd(03) & pwm_ch_rd(02) & pwm_ch_rd(01) & pwm_ch_rd(00);
when "10" => bus_rsp_o.data <= pwm_ch_rd(07) & pwm_ch_rd(06) & pwm_ch_rd(05) & pwm_ch_rd(04);
when "11" => bus_rsp_o.data <= pwm_ch_rd(11) & pwm_ch_rd(10) & pwm_ch_rd(09) & pwm_ch_rd(08);
when others => bus_rsp_o.data <= (others => '0');
end case;
end if;
bus_rsp_o.data <= rdata_sum;
bus_rsp_o.ack <= '1';
else
bus_rsp_o.data <= (others => '0');
bus_rsp_o.ack <= '0';
end if;
bus_rsp_o.err <= '0'; -- no errors
end if;
end process bus_access;
-- duty cycle read-back --
pwm_dc_rd_gen: process(pwm_ch)
-- data read-back (large OR) --
read_back: process(rdata)
variable tmp_v : std_ulogic_vector(31 downto 0);
begin
pwm_ch_rd <= (others => (others => '0'));
for i in 0 to NUM_CHANNELS-1 loop -- only implement the actually configured number of channel register
pwm_ch_rd(i) <= pwm_ch(i);
tmp_v := (others => '0');
for i in 0 to NUM_CHANNELS-1 loop
tmp_v := tmp_v or rdata(i);
end loop;
end process pwm_dc_rd_gen;
rdata_sum <= tmp_v;
end process read_back;
-- Channel Controllers --------------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
pwm_channel_gen:
for i in 0 to NUM_CHANNELS-1 generate
neorv32_pwm_channel_inst: neorv32_pwm_channel
port map (
clk_i => clk_i,
rstn_i => rstn_i,
we_i => we(i),
re_i => re(i),
wdata_i => bus_req_i.data,
rdata_o => rdata(i),
clkgen_i => clkgen_i,
clkgen_en_o => ce(i),
pwm_o => pwm(i)
);
-- access enable --
sel(i) <= '1' when (bus_req_i.addr(5 downto 2) = std_ulogic_vector(to_unsigned(i, 4))) else '0';
we(i) <= sel(i) and bus_req_i.stb and ( bus_req_i.rw);
re(i) <= sel(i) and bus_req_i.stb and (not bus_req_i.rw);
end generate;
pwm_channel_connect: process(pwm)
begin
pwm_o <= (others => '0');
pwm_o(pwm'range) <= pwm(pwm'range);
end process pwm_channel_connect;
-- any clock requests? --
clkgen_en_o <= or_reduce_f(ce);
end neorv32_pwm_rtl;
-- ================================================================================ --
-- NEORV32 SoC - PWM - Channel Controller --
-- -------------------------------------------------------------------------------- --
-- The NEORV32 RISC-V Processor - https://github.com/stnolting/neorv32 --
-- Copyright (c) NEORV32 contributors. --
-- Copyright (c) 2020 - 2024 Stephan Nolting. All rights reserved. --
-- Licensed under the BSD-3-Clause license, see LICENSE for details. --
-- SPDX-License-Identifier: BSD-3-Clause --
-- ================================================================================ --
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
library neorv32;
use neorv32.neorv32_package.all;
entity neorv32_pwm_channel is
port (
clk_i : in std_ulogic; -- global clock line
rstn_i : in std_ulogic; -- global reset line, low-active, async
we_i : in std_ulogic; -- write enable
re_i : in std_ulogic; -- read enable
wdata_i : in std_ulogic_vector(31 downto 0); -- write data
rdata_o : out std_ulogic_vector(31 downto 0); -- read data
clkgen_i : in std_ulogic_vector(7 downto 0); -- clock divider input
clkgen_en_o : out std_ulogic; -- enable clock generator
pwm_o : out std_ulogic -- PWM output
);
end neorv32_pwm_channel;
architecture neorv32_pwm_channel_rtl of neorv32_pwm_channel is
-- configuration register --
signal cfg_en : std_ulogic; -- channel enable
signal cfg_prsc : std_ulogic_vector(2 downto 0); -- (course) clock prescaler select
signal cfg_cdiv : std_ulogic_vector(9 downto 0); -- (fine) clock divider
signal cfg_duty : std_ulogic_vector(7 downto 0); -- duty cycle
-- pwm core --
signal cnt_cdiv : std_ulogic_vector(9 downto 0);
signal cnt_tick : std_ulogic;
signal cnt_duty : std_ulogic_vector(7 downto 0);
begin
-- Configuration --------------------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
config_write: process(rstn_i, clk_i)
begin
if (rstn_i = '0') then
cfg_en <= '0';
cfg_prsc <= (others => '0');
cfg_cdiv <= (others => '0');
cfg_duty <= (others => '0');
elsif rising_edge(clk_i) then
if (we_i = '1') then
cfg_en <= wdata_i(31);
cfg_prsc <= wdata_i(30 downto 28);
cfg_cdiv <= wdata_i(17 downto 8);
cfg_duty <= wdata_i(7 downto 0);
end if;
end if;
end process config_write;
-- read access --
rdata_o <= cfg_en & cfg_prsc & "0000000000" & cfg_cdiv & cfg_duty when (re_i = '1') else (others => '0');
-- enable global clock generator --
clkgen_en_o <= cfg_en;
-- PWM Core -------------------------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
pwm_core: process(rstn_i, clk_i)
begin
if (rstn_i = '0') then
pwm_cnt <= (others => '0');
pwm_o <= (others => '0');
cnt_cdiv <= (others => '0');
cnt_duty <= (others => '0');
pwm_o <= '0';
elsif rising_edge(clk_i) then
-- pwm base counter --
if (enable = '0') then
pwm_cnt <= (others => '0');
elsif (prsc_tick = '1') then
pwm_cnt <= std_ulogic_vector(unsigned(pwm_cnt) + 1);
end if;
-- channels --
pwm_o <= (others => '0');
for i in 0 to NUM_CHANNELS-1 loop
if (unsigned(pwm_cnt) >= unsigned(pwm_ch(i))) or (enable = '0') then
pwm_o(i) <= '0';
-- clock divider --
if (cfg_en = '0') then
cnt_cdiv <= (others => '0');
elsif (clkgen_i(to_integer(unsigned(cfg_prsc))) = '1') then -- pre-scaled clock (coarse)
if (cnt_tick = '1') then -- fine-tuned clock
cnt_cdiv <= (others => '0');
else
pwm_o(i) <= '1';
cnt_cdiv <= std_ulogic_vector(unsigned(cnt_cdiv) + 1);
end if;
end loop;
end if;
-- duty cycle counter --
if (cfg_en = '0') then
cnt_duty <= (others => '0');
elsif (cnt_tick = '1') then
cnt_duty <= std_ulogic_vector(unsigned(cnt_duty) + 1);
end if;
-- pwm output --
if (cfg_en = '0') or (unsigned(cnt_duty) >= unsigned(cfg_duty)) then
pwm_o <= '0';
else
pwm_o <= '1';
end if;
end if;
end process pwm_core;
-- PWM clock select --
clkgen_en_o <= enable; -- enable clock generator
prsc_tick <= clkgen_i(to_integer(unsigned(prsc)));
-- fine-tuned clock tick --
cnt_tick <= '1' when (cnt_cdiv = cfg_cdiv) else '0';
end neorv32_pwm_rtl;
end neorv32_pwm_channel_rtl;

View file

@ -122,7 +122,7 @@ entity neorv32_top is
IO_SDI_FIFO : natural range 1 to 2**15 := 1; -- RTX fifo depth, has to be zero or a power of two, min 1
IO_TWI_EN : boolean := false; -- implement two-wire interface (TWI)?
IO_TWI_FIFO : natural range 1 to 2**15 := 1; -- RTX fifo depth, has to be zero or a power of two, min 1
IO_PWM_NUM_CH : natural range 0 to 12 := 0; -- number of PWM channels to implement (0..12); 0 = disabled
IO_PWM_NUM_CH : natural range 0 to 16 := 0; -- number of PWM channels to implement (0..16)
IO_WDT_EN : boolean := false; -- implement watch dog timer (WDT)?
IO_TRNG_EN : boolean := false; -- implement true random number generator (TRNG)?
IO_TRNG_FIFO : natural range 1 to 2**15 := 1; -- data fifo depth, has to be a power of two, min 1
@ -220,7 +220,7 @@ entity neorv32_top is
onewire_o : out std_ulogic; -- 1-wire bus output (pull low only)
-- PWM (available if IO_PWM_NUM_CH > 0) --
pwm_o : out std_ulogic_vector(11 downto 0); -- pwm channels
pwm_o : out std_ulogic_vector(15 downto 0); -- pwm channels
-- Custom Functions Subsystem IO (available if IO_CFS_EN = true) --
cfs_in_i : in std_ulogic_vector(IO_CFS_IN_SIZE-1 downto 0) := (others => 'L'); -- custom CFS inputs conduit

View file

@ -25,7 +25,7 @@ entity neorv32_ProcessorTop_Minimal is
MEM_INT_DMEM_EN : boolean := true; -- implement processor-internal data memory
MEM_INT_DMEM_SIZE : natural := 64*1024; -- size of processor-internal data memory in bytes
-- Processor peripherals --
IO_PWM_NUM_CH : natural := 3 -- number of PWM channels to implement (0..12); 0 = disabled
IO_PWM_NUM_CH : natural := 3 -- number of PWM channels to implement (0..16)
);
port (
-- Global control --
@ -39,7 +39,7 @@ end entity;
architecture neorv32_ProcessorTop_Minimal_rtl of neorv32_ProcessorTop_Minimal is
-- internal IO connection --
signal con_pwm_o : std_ulogic_vector(11 downto 0);
signal con_pwm_o : std_ulogic_vector(15 downto 0);
begin

View file

@ -26,7 +26,7 @@ entity neorv32_ProcessorTop_MinimalBoot is
MEM_INT_DMEM_SIZE : natural := 64*1024; -- size of processor-internal data memory in bytes
-- Processor peripherals --
IO_GPIO_NUM : natural := 0; -- number of GPIO input/output pairs (0..64)
IO_PWM_NUM_CH : natural := 3 -- number of PWM channels to implement (0..12); 0 = disabled
IO_PWM_NUM_CH : natural := 3 -- number of PWM channels to implement (0..16)
);
port (
-- Global control --
@ -46,7 +46,7 @@ architecture neorv32_ProcessorTop_MinimalBoot_rtl of neorv32_ProcessorTop_Minima
-- internal IO connection --
signal con_gpio_o : std_ulogic_vector(63 downto 0);
signal con_pwm_o : std_ulogic_vector(11 downto 0);
signal con_pwm_o : std_ulogic_vector(15 downto 0);
begin

View file

@ -26,7 +26,7 @@ entity neorv32_ProcessorTop_UP5KDemo is
MEM_INT_DMEM_SIZE : natural := 64*1024; -- size of processor-internal data memory in bytes
-- Processor peripherals --
IO_GPIO_NUM : natural := 64; -- number of GPIO input/output pairs (0..64)
IO_PWM_NUM_CH : natural := 3 -- number of PWM channels to implement (0..12); 0 = disabled
IO_PWM_NUM_CH : natural := 3 -- number of PWM channels to implement (0..16)
);
port (
-- Global control --
@ -61,7 +61,7 @@ architecture neorv32_ProcessorTop_UP5KDemo_rtl of neorv32_ProcessorTop_UP5KDemo
-- internal IO connection --
signal con_gpio_o : std_ulogic_vector(63 downto 0);
signal con_gpio_i : std_ulogic_vector(63 downto 0);
signal con_pwm_o : std_ulogic_vector(11 downto 0);
signal con_pwm_o : std_ulogic_vector(15 downto 0);
signal con_spi_sck : std_ulogic;
signal con_spi_sdi : std_ulogic;
signal con_spi_sdo : std_ulogic;

View file

@ -114,7 +114,7 @@ entity neorv32_vivado_ip is
IO_TWI_EN : boolean := false;
IO_TWI_FIFO : natural range 1 to 2**15 := 1;
IO_PWM_EN : boolean := false;
IO_PWM_NUM_CH : natural range 1 to 12 := 1; -- variable-sized ports must be at least 0 downto 0; #974
IO_PWM_NUM_CH : natural range 1 to 16 := 1; -- variable-sized ports must be at least 0 downto 0; #974
IO_WDT_EN : boolean := false;
IO_TRNG_EN : boolean := false;
IO_TRNG_FIFO : natural range 1 to 2**15 := 1;
@ -274,7 +274,7 @@ architecture neorv32_vivado_ip_rtl of neorv32_vivado_ip is
-- constrained size ports --
signal gpio_o_aux : std_ulogic_vector(63 downto 0);
signal gpio_i_aux : std_ulogic_vector(63 downto 0);
signal pwm_o_aux : std_ulogic_vector(11 downto 0);
signal pwm_o_aux : std_ulogic_vector(15 downto 0);
signal xirq_i_aux : std_ulogic_vector(31 downto 0);
-- internal wishbone bus --

View file

@ -273,7 +273,7 @@ begin
IO_SDI_FIFO => 4, -- SDI RTX fifo depth, has to be zero or a power of two
IO_TWI_EN => true, -- implement two-wire interface (TWI)?
IO_TWI_FIFO => 4, -- RTX fifo depth, has to be zero or a power of two, min 1
IO_PWM_NUM_CH => 12, -- number of PWM channels to implement (0..12); 0 = disabled
IO_PWM_NUM_CH => 8, -- number of PWM channels to implement (0..16)
IO_WDT_EN => true, -- implement watch dog timer (WDT)?
IO_TRNG_EN => true, -- implement true random number generator (TRNG)?
IO_TRNG_FIFO => 4, -- TRNG fifo depth, has to be a power of two, min 1

View file

@ -249,7 +249,7 @@ begin
IO_SDI_FIFO => 4, -- SDI RTX fifo depth, has to be zero or a power of two
IO_TWI_EN => true, -- implement two-wire interface (TWI)?
IO_TWI_FIFO => 4, -- RTX fifo depth, has to be zero or a power of two, min 1
IO_PWM_NUM_CH => 12, -- number of PWM channels to implement (0..12); 0 = disabled
IO_PWM_NUM_CH => 8, -- number of PWM channels to implement (0..16)
IO_WDT_EN => true, -- implement watch dog timer (WDT)?
IO_TRNG_EN => true, -- implement true random number generator (TRNG)?
IO_TRNG_FIFO => 4, -- TRNG fifo depth, has to be a power of two, min 1

View file

@ -22,8 +22,8 @@
/**@{*/
/** UART BAUD rate */
#define BAUD_RATE 19200
/** Maximum PWM output intensity (8-bit) */
#define PWM_MAX 200
/** Maximum PWM output intensity (8-bit duty cycle) */
#define MAX_DUTY 200
/**@}*/
@ -61,49 +61,61 @@ int main() {
int num_pwm_channels = neorv32_pmw_get_num_channels();
// check number of PWM channels
// get number of implemented PWM channels
if (neorv32_uart0_available()) {
neorv32_uart0_printf("Implemented PWM channels: %i\n\n", num_pwm_channels);
}
// deactivate all PWM channels
// deactivate/clear all available channels
int i;
for (i=0; i<num_pwm_channels; i++) {
neorv32_pwm_set(i, 0);
neorv32_pwm_ch_disable(i);
neorv32_pwm_ch_set_clock(i, 0, 0);
neorv32_pwm_ch_set_duty(i, 0);
}
// configure and enable PWM
neorv32_pwm_setup(CLK_PRSC_64);
// configure all available channels
for (i=0; i<num_pwm_channels; i++) {
neorv32_pwm_ch_set_clock(i, CLK_PRSC_64, 0);
neorv32_pwm_ch_enable(i);
}
uint8_t pwm = 0;
uint8_t up = 1;
uint8_t ch = 0;
// simple animation: "pulse" channels one by one
neorv32_uart0_printf("Starting animation...\n");
// animate!
while(1) {
int dc = 0; // current duty cycle
int up = 1; // up/down (increase/decrease)
int ch = 0; // current channel
while (1) {
// update duty cycle
if (up) {
if (pwm == PWM_MAX) {
if (dc >= (int)(MAX_DUTY)) { // maximum intensity reached?
up = 0;
}
else {
pwm++;
dc++;
}
}
else {
if (pwm == 0) {
ch = (ch + 1) & 3; // goto next channel
if (dc == 0) {
// goto next channel
if ((ch + 1) >= num_pwm_channels) {
ch = 0;
}
else {
ch++;
}
up = 1;
}
else {
pwm--;
dc--;
}
}
neorv32_pwm_set(ch, pwm); // output new duty cycle
neorv32_cpu_delay_ms(3); // wait ~3ms
neorv32_pwm_ch_set_duty(ch, dc); // set new duty cycle for channel
neorv32_cpu_delay_ms(3); // wait ~3ms using busy-wait
}
return 0;

View file

@ -27,19 +27,22 @@
/**@{*/
/** PWM module prototype */
typedef volatile struct __attribute__((packed,aligned(4))) {
uint32_t CTRL; /**< offset 0: control register (#NEORV32_PWM_CTRL_enum) */
uint32_t DC[3]; /**< offset 4..12: duty cycle register 0..2 */
uint32_t CHANNEL_CFG[16]; /**< offset 0..64: channel configuration 0..15 (#CHANNEL_CFG_enum) */
} neorv32_pwm_t;
/** PWM module hardware access (#neorv32_pwm_t) */
#define NEORV32_PWM ((neorv32_pwm_t*) (NEORV32_PWM_BASE))
/** PWM control register bits */
enum NEORV32_PWM_CTRL_enum {
PWM_CTRL_EN = 0, /**< PWM control register(0) (r/w): PWM controller enable */
PWM_CTRL_PRSC0 = 1, /**< PWM control register(1) (r/w): Clock prescaler select bit 0 */
PWM_CTRL_PRSC1 = 2, /**< PWM control register(2) (r/w): Clock prescaler select bit 1 */
PWM_CTRL_PRSC2 = 3 /**< PWM control register(3) (r/w): Clock prescaler select bit 2 */
/** PWM channel configuration bits */
enum CHANNEL_CFG_enum {
PWM_CFG_DUTY_LSB = 0, /**< PWM configuration register(0) (r/w): Duty cycle (8-bit), LSB */
PWM_CFG_DUTY_MSB = 7, /**< PWM configuration register(7) (r/w): Duty cycle (8-bit), MSB */
PWM_CFG_CDIV_LSB = 8, /**< PWM configuration register(8) (r/w): Clock divider (10-bit), LSB */
PWM_CFG_CDIV_MSB = 17, /**< PWM configuration register(17) (r/w): Clock divider (10-bit), MSB */
PWM_CFG_PRSC_LSB = 28, /**< PWM configuration register(28) (r/w): Clock prescaler select (3-bit), LSB */
PWM_CFG_PRSC_MSB = 30, /**< PWM configuration register(30) (r/w): Clock prescaler select (3-bit), MSB */
PWM_CFG_EN = 31 /**< PWM configuration register(31) (r/w): channel enable */
};
/**@}*/
@ -48,13 +51,12 @@ enum NEORV32_PWM_CTRL_enum {
* @name Prototypes
**************************************************************************/
/**@{*/
int neorv32_pwm_available(void);
void neorv32_pwm_setup(int prsc);
void neorv32_pwm_disable(void);
void neorv32_pwm_enable(void);
int neorv32_pmw_get_num_channels(void);
void neorv32_pwm_set(int channel, uint8_t dc);
uint8_t neorv32_pwm_get(int channel);
int neorv32_pwm_available(void);
int neorv32_pmw_get_num_channels(void);
void neorv32_pwm_ch_enable(int channel);
void neorv32_pwm_ch_disable(int channel);
void neorv32_pwm_ch_set_clock(int channel, int prsc, int cdiv);
void neorv32_pwm_ch_set_duty(int channel, int duty);
/**@}*/
#endif // neorv32_pwm_h

View file

@ -15,7 +15,7 @@
* @see https://stnolting.github.io/neorv32/sw/files.html
*/
#include "neorv32.h"
#include <neorv32.h>
/**********************************************************************//**
@ -35,59 +35,19 @@ int neorv32_pwm_available(void) {
/**********************************************************************//**
* Enable and configure pulse width modulation controller.
* The PWM control register bits are listed in #NEORV32_PWM_CTRL_enum.
* Get number of implemented PWM channels.
* @warning This function will override all channel configuration registers.
*
* @param[in] prsc Clock prescaler select (0..7). See #NEORV32_CLOCK_PRSC_enum.
**************************************************************************/
void neorv32_pwm_setup(int prsc) {
NEORV32_PWM->CTRL = 0; // reset
uint32_t ct_enable = 1;
ct_enable = ct_enable << PWM_CTRL_EN;
uint32_t ct_prsc = (uint32_t)(prsc & 0x07);
ct_prsc = ct_prsc << PWM_CTRL_PRSC0;
NEORV32_PWM->CTRL = ct_enable | ct_prsc;
}
/**********************************************************************//**
* Disable pulse width modulation controller.
**************************************************************************/
void neorv32_pwm_disable(void) {
NEORV32_PWM->CTRL &= ~((uint32_t)(1 << PWM_CTRL_EN));
}
/**********************************************************************//**
* Enable pulse width modulation controller.
**************************************************************************/
void neorv32_pwm_enable(void) {
NEORV32_PWM->CTRL |= ((uint32_t)(1 << PWM_CTRL_EN));
}
/**********************************************************************//**
* Get number of implemented channels.
* @warning This function will override all duty cycle configuration registers.
*
* @return Number of implemented channels.
* @return Number of implemented PWM channels.
**************************************************************************/
int neorv32_pmw_get_num_channels(void) {
neorv32_pwm_disable();
int i = 0;
uint32_t cnt = 0;
for (i=0; i<12; i++) {
neorv32_pwm_set(i, 1);
cnt += neorv32_pwm_get(i);
for (i=0; i<16; i++) {
NEORV32_PWM->CHANNEL_CFG[i] = 1;
cnt += NEORV32_PWM->CHANNEL_CFG[i];
}
return (int)cnt;
@ -95,42 +55,62 @@ int neorv32_pmw_get_num_channels(void) {
/**********************************************************************//**
* Set duty cycle for channel.
* Enable PWM channel.
*
* @param[in] channel Channel select (0..11).
* @param[in] dc Duty cycle (8-bit, LSB-aligned).
* @param[in] channel Channel select (0..15).
**************************************************************************/
void neorv32_pwm_set(int channel, uint8_t dc) {
void neorv32_pwm_ch_enable(int channel) {
if (channel > 11) {
return; // out-of-range
}
channel &= 0xf; // constrain range
const uint32_t dc_mask = 0xff;
uint32_t dc_new = (uint32_t)dc;
uint32_t tmp = NEORV32_PWM->DC[channel/4];
tmp &= ~(dc_mask << ((channel % 4) * 8)); // clear previous duty cycle
tmp |= dc_new << ((channel % 4) * 8); // set new duty cycle
NEORV32_PWM->DC[channel/4] = tmp;
NEORV32_PWM->CHANNEL_CFG[channel] |= ((uint32_t)(1 << PWM_CFG_EN));
}
/**********************************************************************//**
* Get duty cycle from channel.
* Disable PWM channel.
*
* @param[in] channel Channel select (0..11).
* @return Duty cycle (8-bit, LSB-aligned) of channel 'channel'.
* @param[in] channel Channel select (0..15).
**************************************************************************/
uint8_t neorv32_pwm_get(int channel) {
void neorv32_pwm_ch_disable(int channel) {
if (channel > 11) {
return 0; // out of range
}
channel &= 0xf; // constrain range
uint32_t rd = NEORV32_PWM->DC[channel/4] >> (((channel % 4) * 8));
return (uint8_t)rd;
NEORV32_PWM->CHANNEL_CFG[channel] &= ~((uint32_t)(1 << PWM_CFG_EN));
}
/**********************************************************************//**
* Set PWM channel's clock configuration.
*
* @param[in] channel Channel select (0..15).
* @param[in] prsc Coarse clock prescaler select (3-bit, LSB-aligned). See #NEORV32_CLOCK_PRSC_enum.
* @param[in] cdiv Fine clock divider value (10-bit, LSB-aligned).
**************************************************************************/
void neorv32_pwm_ch_set_clock(int channel, int prsc, int cdiv) {
channel &= 0xf; // constrain range
uint32_t tmp = NEORV32_PWM->CHANNEL_CFG[channel];
tmp &= 0x800000ffU; // clear current prsc and cdiv, keep enable and duty
tmp |= ((uint32_t)(prsc & 0x7U)) << PWM_CFG_PRSC_LSB;
tmp |= ((uint32_t)(cdiv & 0x3ffU)) << PWM_CFG_CDIV_LSB;
NEORV32_PWM->CHANNEL_CFG[channel] = tmp;
}
/**********************************************************************//**
* Set PWM channel's duty cycle.
*
* @param[in] channel Channel select (0..15).
* @param[in] duty Duty cycle (8-bit, LSB-aligned).
**************************************************************************/
void neorv32_pwm_ch_set_duty(int channel, int duty) {
channel &= 0xf; // constrain range
uint32_t tmp = NEORV32_PWM->CHANNEL_CFG[channel];
tmp &= 0xffffff00U; // clear current duty cycle configuration
tmp |= (uint32_t)(duty & 0x000000ffU); // set new configuration
NEORV32_PWM->CHANNEL_CFG[channel] = tmp;
}