PolarFire® SoC Applications - CorePWM
Introduction
Pulse-width modulation (PWM) is a technique that uses a digital signal's on and off states to control analog behavior. Although PolarFire® Field-Programmable Gate Array (FPGA) devices do not provide a dedicated PWM peripheral, Libero® System-on-Chip (SoC) includes the CorePWM IP core, which enables PWM functionality in the FPGA fabric.
This article focuses on using the CorePWM from the Microprocessor Subsystem (MSS). It guides you through the required setup, programming the reference design, creating a bare-metal SoftConsole project, adding a simple UART-driven application that controls the PWM duty cycle, and verifying the output on the serial console. The article also covers the Yocto Project® workflow and demonstrates PWM use in embedded Linux®.
Prerequisites
Hardware Setup
This application applies to the following PolarFire SoC-based boards:
- Three Micro-USB cables
- Logic analyzer (optional, used to capture signals)
- Linux host PC for the Yocto Project build environment
Software Setup
- SoftConsole Integrated Development Environment (IDE)
- Libero SoC Design Suite 2024.2+
- Yocto Project Host Environment
- Logic analyzer software, if a logic analyzer is used
- MobaXterm® for interactive serial-console access
Additional Resources
- PolarFire System-on-Chip (SoC) Applications - Microprocessor Subsystem (MSS) and Libero SoC Design Suite
- PolarFire SoC Applications - SoftConsole Integration
- Discovery Kit Reference Design
- Icicle Kit Reference Design
- PolarFire SoC - SoC Video Kit Reference Design
Programming the Reference Design
To use PWM from the MSS at the application level, the CorePWM peripheral must be routed over the APB bus to the MSS APB initiator interface, and the software must know the address range assigned to CorePWM. In the referenced design, CorePWM is already enabled, connected, and routed to the MSS at a known address range.
The reference design also supports running an embedded Linux distribution created with the Yocto Project. When using any peripheral from the reference design, it is important to confirm how that peripheral is connected in the design documentation.

The memory-mapping information is available in the reference design documentation and the Libero SoC Design Suite memory map.

This article uses the PolarFire Icicle Kit reference design.
Download the reference design programming files from the latest release of the Icicle Kit reference design repository.
Program the FPGA with the prebuilt MPFS_ICICLE_KIT_BASE_DESIGN_{VERSION} design by using FlashPro® Express, which is included with Libero SoC.
Open FlashPro Express.
Create a new project.
Select the appropriate job file from the downloaded reference design folder:
Click Run to start programming the device.
PWM in Linux® Environment
We need to set up and configure the PWM peripheral in the Linux configurations. That's why we need a Linux build system to configure it. For this article, we will use the Yocto Project.
Hart Software Services (HSS) Configurations
Hart Software Services (HSS) is the bootloader for PolarFire SoC. It runs first, sets up hardware, launches Linux or other apps, and is essential for multi-core and secure boot.
Objectives:
- Download and import HSS to SoftConsole.
- Update references and build HSS.
- Deploy HSS to PolarFire Icicle Kit
First, download HSS from GitHub®.
Import HSS project to SoftConsole by going to File > Import > Import Existing Project Into Workspace.

Browse the HSS folder and import project into workspace by clicking Finish.
Copy the MSS XML file into the HSS project.
Copy the XML file to hart-software-services/boards/mpfs-icicle-kit-es/soc_fpga_design/xml/<your xml>.xml.
Copy and rename configurations for HSS.
Copy hart-software-services/boards/mpfs-icicle-kit-es/def_config to hart-software-services/.
Rename def_config to .config.
Edit the .config file and update the XML-file path by changing the following line.
Build HSS and deploy.
Right-click on the project name.
Click Build Project.
Select PolarFire SoC program non-secure boot mode 1 run option and deploy the project to the SoC.
Yocto Project® Configurations
In this section, you will create a Linux image and program it onto the PolarFire SoC Icicle Kit.
Objectives:
- Setting up the Yocto Project building environment.
- Enabling the PWM peripheral and including the necessary packages in the build.
- Building a Linux image and deploying it into the SoC.
Creating Environment
To create the Linux build environment, follow the README.md guide.
Configuring PWM Peripheral
Prepare the Microchip Linux kernel (linux-mchp) source tree for local development:
To include the required libraries and applications in the build, open the yocto-dev/meta-mchp/meta-mchp-polarfire-soc/meta-mchp-polarfire-soc-bsp/conf/local.conf file and add the following variable at the end of the file.
CORE_IMAGE_EXTRA_INSTALL += "packagegroup-core-buildessential vim"
Save the file and exit.
Open the Linux kernel configuration menu and enable the PWM framework and the CorePWM controller driver.
MACHINE=mpfs-icicle-kit bitbake linux-mchp -c menuconfig
In the configuration menu, navigate to Device Drivers.

Enable the Pulse-Width Modulation (PWM) Support option.

Save the config by selecting Save > Exit.
Locate the PolarFire Icicle Kit-related Device Tree Source (DTS) files for modification.
yocto-dev/build/workspace/sources/linux-mchp/arch/riscv/boot/dts/microchip/mpfs-icicle-kit-common.dtsi
Make sure that the core_pwm0 is enabled in the DTS (in mpfs-icicle-kit-common.dtsi file).
2
3
status = "okay";
};
Compile a customized Yocto Project Linux kernel recipe in a developer-friendly way, producing kernel binaries for PolarFire SoC.
Building Linux Image and Deploying
Execute the following command to build the Linux image:
MACHINE=mpfs-icicle-kit bitbake mchp-base-image
Here's the list of names of supporting machines.
| MACHINE | Board Name | Description |
|---|---|---|
| MACHINE=mpfs-icicle-kit | MPFS-ICICLE-KIT-ES, MPFS-ICICLE-KIT | PolarFire SoC Icicle Kit |
| MACHINE=mpfs-disco-kit | MPFS-DISCO-KIT | PolarFire SoC Discovery Kit |
| MACHINE=mpfs-video-kit | MPFS250-VIDEO-KIT | PolarFire SoC Video Kit |
After the build completes, you can locate your Linux image at:
yocto-dev/build/tmp-glibc/deploy/images/<board_name>/<image-name>.rootfs-***.wic
Follow the GitHub instructions to deploy the built image to eMMC or an SD card.
After booting Linux on the PolarFire SoC Icicle Board, log in as root and verify that the PWM device appears under /sys/class/pwm.
total 0
lrwxrwxrwx 1 root root 0 May 29 18:48 pwmchip0 -> ../../devices/platform/fabric-bus@40000000/40000000.pwm/pwm/pwmchip0
If nothing returns, that means the PWM device is not enabled and you have to double-check the configuration, DTS, and driver modifications needed to be done.
Software
User-Space Application Development in C
We can write a C program that runs from user-space and interacts with PWM devices. The following C program performs a gradual PWM breathing test.
Boot Linux on your PolarFire SoC Icicle kit. Navigate to /media and create main.c using the vim editor:
Copy the following C code into main.c:
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#define PWM_PATH "/sys/class/pwm/pwmchip0"
#define PERIOD_NS 1000000 // 1ms period = 1 kHz frequency
#define STEP_NS 20000 // Change duty cycle by 20,000ns per step
#define DELAY_US 15000 // 15ms delay between steps for smooth transition
// Helper function to write a string to a sysfs file
int sysfs_write(const char *path, const char *value) {
int fd = open(path, O_WRONLY);
if (fd < 0) {
perror("Error opening sysfs file");
return -1;
}
if (write(fd, value, strlen(value)) < 0) {
perror("Error writing to sysfs file");
close(fd);
return -1;
}
close(fd);
return 0;
}
int main() {
char path[128];
char value[32];
// 1. Export PWM channel 0 if not already done
if (access(PWM_PATH "/pwm0", F_OK) == -1) {
if (sysfs_write(PWM_PATH "/export", "0") < 0) return 1;
usleep(100000); // 100ms delay to let sysfs populate files
}
// 2. Set the initial period (1 kHz)
snprintf(path, sizeof(path), "%s/pwm0/period", PWM_PATH);
if (sysfs_write(path, "1000000") < 0) return 1;
// 3. Set initial duty cycle to 0
snprintf(path, sizeof(path), "%s/pwm0/duty_cycle", PWM_PATH);
if (sysfs_write(path, "0") < 0) return 1;
// 4. Enable the PWM output
snprintf(path, sizeof(path), "%s/pwm0/enable", PWM_PATH);
if (sysfs_write(path, "1") < 0) return 1;
printf("PWM breathing application started. Press Ctrl+C to stop.\n");
// Cache the duty_cycle path for maximum loop performance
snprintf(path, sizeof(path), "%s/pwm0/duty_cycle", PWM_PATH);
int current_duty = 0;
int direction = 1; // 1 for fading in, -1 for fading out
// 5. Smooth Breathing Loop
while (1) {
// Update duty cycle value
current_duty += (STEP_NS * direction);
// Reverse direction at boundaries
if (current_duty >= PERIOD_NS) {
current_duty = PERIOD_NS;
direction = -1; // Start fading out
} else if (current_duty <= 0) {
current_duty = 0;
direction = 1; // Start fading in
}
// Apply new duty cycle
snprintf(value, sizeof(value), "%d", current_duty);
sysfs_write(path, value);
// Wait a bit to make the transition viewable to the human eye
usleep(DELAY_US);
}
return 0;
}
After saving the modification, compile the C code on target:
Run the main executable:
Driving PWM in Bare-Metal Applications
Building and Programming the SoftConsole Project
To use CorePWM in a bare-metal application, use SoftConsole to develop, build, compile, and deploy the application.
For this application, let us take one of the GitHub bare-metal reference examples and change it to use CorePWM and blink the LED on the Tester 2 Click board™.
Download the mpfs-blank-baremetal bare-metal application project from the official GitHub repository and import it into the SoftConsole.
Replace the MSS configuration XML file in the bare-metal project with the XML file used in your Libero SoC design suite project. The path to the file that SoftConsole will use to generate header files, which are then used by the MPFS HAL, is:
Replace the contents of u54_1.c with the following application code:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include "drivers/mss/mss_mmuart/mss_uart.h"
#include "drivers/fpga_ip/CorePWM/core_pwm.h"
#define PWM_PRESCALE 64
#define PWM_PERIOD 100
#define COREPWM_BASE_ADDR 0x40000000
uint8_t g_welcome_message[] =
" \r\n\r\n **** Welcome to PF SoC Icicle Kit PWM Lab !! ****\r\n";
uint8_t g_menu_text[] = ""
"\r\nMenu:"
"\r\n 0) 00% of power"
"\r\n 1) 10% of power"
"\r\n 2) 20% of power"
"\r\n 3) 30% of power"
"\r\n 4) 40% of power"
"\r\n 5) 50% of power"
"\r\n 6) 60% of power"
"\r\n 7) 70% of power"
"\r\n 8) 80% of power"
"\r\n 9) 90% of power"
"\r\nChoose option: ";
pwm_instance_t the_pwm;
uint8_t rx_buffer[1] = {0};
uint8_t rx_size = 0;
void u54_1(void)
{
PLIC_init();
__enable_irq();
(void)mss_config_clk_rst(MSS_PERIPH_MMUART2, (uint8_t)1, PERIPHERAL_ON);
(void)mss_config_clk_rst(MSS_PERIPH_FIC3 , (uint8_t)1, PERIPHERAL_ON);
mss_enable_fabric();
MSS_UART_init(&g_mss_uart2_lo,
MSS_UART_115200_BAUD,
(MSS_UART_DATA_8_BITS | MSS_UART_NO_PARITY | MSS_UART_ONE_STOP_BIT));
PWM_init( &the_pwm, COREPWM_BASE_ADDR, PWM_PRESCALE, PWM_PERIOD ) ;
MSS_UART_polled_tx_string(&g_mss_uart2_lo, g_welcome_message);
MSS_UART_polled_tx_string(&g_mss_uart2_lo, g_menu_text);
while(1u){
rx_size = MSS_UART_get_rx(&g_mss_uart2_lo,
(uint8_t*)rx_buffer,
(uint32_t)sizeof(rx_buffer));
if(rx_size == 0){
continue;
}
if(rx_buffer[0] > 57 || rx_buffer[0] < 48){
continue;
}
PWM_set_duty_cycle( &the_pwm, PWM_1, rx_buffer[0] - 48);
MSS_UART_polled_tx_string(&g_mss_uart2_lo, rx_buffer);
MSS_UART_polled_tx_string(&g_mss_uart2_lo, "\r\n");
MSS_UART_polled_tx_string(&g_mss_uart2_lo, g_menu_text);
}
}
Lines 1–6: Include the PolarFire SoC HAL, MSS UART driver, and CorePWM driver, and define the PWM configuration constants.
Lines 10–25: Define the messages displayed on the UART console.
Lines 27–29: Define the CorePWM instance and UART receive buffers.
Line 31: The u54_1() function is the application entry point for this hart.
Line 33-34: Enable platform interrupts.
Line 36-37: Enable MMUART2 and FIC3, then initialize the UART and fabric interface.
Line 39-45: Initializing controllers and peripherals.
Line 46-48: Display the menu options.
Line 51-60: Receiving and verifying user input.
Line 62-65: Update the PWM duty cycle and show the menu again.
This example assumes that the reference design uses MMUART2 for the application console, connects CorePWM through FIC3, and maps CorePWM at 0x40000000. If the Libero design uses different peripherals or an address, update the corresponding software configuration.
Build the project and deploy it either in LIM for Debug mode or eNVM for Release mode.
Checking for the Results
The following steps describe how to verify the application after programming the device.
Open the UART port connected to MMUART2 in MobaXterm. The required port depends on the board reference design and USB-UART connection.
Use the terminal menu to send digit selections and observe the PWM-related behavior.

Use a logic analyzer to observe the pulse width increase as the selected duty cycle changes from a lower value to a higher value.
Summary
By following this article, you programmed a PolarFire SoC reference design that exposes CorePWM to the MSS, created a bare-metal SoftConsole project, enabled the required CorePWM driver support, and added a UART-driven application that updates the PWM duty cycle based on user input. You also reviewed the key parts of the application code and checked the results through a serial terminal connection.

