PolarFire® SoC Applications - CorePWM

Last modified by Microchip on 2026/10/05 15:45

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

Software Setup

Additional Resources

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.

PolarFire SoC Icicle Kit

Information

In the Icicle Kit and Discovery Kit reference designs, CorePWM is mapped to 0x4000_0000 -> 0x4000_00FF.

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

CorePWM mapped

This article uses the PolarFire Icicle Kit reference design.

Information

For more information on board-level design connections, refer to the reference design repositories for the Discovery Kit, Icicle Kit, and Video Kit SoC.

Download the reference design programming files from the latest release of the Icicle Kit reference design repository.

https://github.com/polarfire-soc/icicle-kit-reference-design/releases

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:

MPFS_ICICLE_KIT_ES_*\MPFS_ICICLE_KIT_ES_BASE_DESIGN_*\MPFS_ICICLE_KIT_ES_*.job

Click Run to start programming the device.

Success

At this point, the FPGA fabric is programmed with the reference design.

Warning

You have two choices for programming the FPGA:

  1. Use the prebuilt reference programming files only for a quick start.
  2. Build or modify the Libero SoC project and regenerate the programming file by following the "PolarFire® System-on-Chip (SoC) Applications - Microprocessor Subsystem (MSS) and Libero® SoC Design Suite" guide.

Back to Top

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
Information

Note: This application article is verified on HSS 2025.07.

First, download HSS from GitHub®.


Import HSS project to SoftConsole by going to File > Import > Import Existing Project Into Workspace.

Import Projects

Warning

Make sure that Copy projects into workspace is selected.

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.

Information

Note: If your board is production board (not ES): use boards/mpfs-icicle-kit/... instead of boards/mpfs-icicle-kit-es/...


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. 

CONFIG_SOC_FPGA_DESIGN_XML="boards/mpfs-icicle-kit-es/soc_fpga_design/xml/<your xml>.xml"

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. 

Warning

Make sure that the run configuration is correct. If necessary, open External Tools > External Tools Configurations and select the die and package that match your board. For the Icicle Kit ES, use die MPFS250T_ES and package FCVG484

Back to Top

Yocto Project® Configurations

In this section, you will create a Linux image and program it onto the PolarFire SoC Icicle Kit.

Objectives:

  1. Setting up the Yocto Project building environment.
  2. Enabling the PWM peripheral and including the necessary packages in the build.
  3. 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:

MACHINE=mpfs-icicle-kit devtool modify linux-mchp
Success

We will get the following log:

Recipe linux-mchp now set up to build from /build/workspace/sources/linux-mchp


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"

Information
  • packagegroup-core-buildessential: A Yocto Project meta-package that pulls in all the essential build tools needed for compiling software on your embedded system.

    • Includes:

      • gcc → C compiler

      • make → build automation

      • binutils → linker, assembler, etc.

      • pkgconfig → helps locate libraries and headers

      • libc-dev → standard C library headers

      • autoconf, automake, libtool → for building autotools-based projects

  • vim - A powerful text editor used in terminal environments

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.

Device Drivers


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

SPI Support


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.dts
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).

1
2
3
&core_pwm0 {
        status = "okay";
};

Compile a customized Yocto Project Linux kernel recipe in a developer-friendly way, producing kernel binaries for PolarFire SoC.

MACHINE=mpfs-icicle-kit devtool build linux-mchp
Warning

To create a bbappend layer, execute the following command:
> devtool finish linux-mchp custom-layer

​​​​Your bbappend layer will be saved at: custom-layer/recipes-kernel/linux/linux-mchp_%.bbappend

Building Linux Image and Deploying

Execute the following command to build the Linux image:

MACHINE=mpfs-icicle-kit bitbake mchp-base-image

Information

Note: Ensure that the initial Linux image has been built without error, so we can start making the modifications.

Here's the list of names of supporting machines. 

MACHINEBoard NameDescription
MACHINE=mpfs-icicle-kitMPFS-ICICLE-KIT-ES, MPFS-ICICLE-KITPolarFire SoC Icicle Kit
MACHINE=mpfs-disco-kitMPFS-DISCO-KITPolarFire SoC Discovery Kit
MACHINE=mpfs-video-kitMPFS250-VIDEO-KITPolarFire 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.

Information

For additional information, refer to the "OpenEmbedded/Yocto Project BSP layer for Microchip's SoCs" GitHub page.


After booting Linux on the PolarFire SoC Icicle Board, log in as root and verify that the PWM device appears under /sys/class/pwm.

root@mpfs-icicle-kit:~# ls -l /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.

Back to Top

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:

cd /media && vim main.c

Copy the following C code into main.c:

#include <stdio.h>
#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:

gcc main.c -o main

Run the main executable:

./main
Success

Upon running the application, you will get the following response:

root@mpfs-icicle-kit:~# ./main
PWM breathing application started. Press Ctrl+C to stop.

Back to Top

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.

Note: If this is your first time importing a project in SoftConsole, watch the "Clone and Import a Project from the Bare Metal Library" video:

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:

mpfs-blank-baremetal > "your_board" > fpga_design > design_description > xx.xml

Replace the contents of u54_1.c with the following application code:

1
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 "mpfs_hal/mss_hal.h"
#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.

Information

The application accepts only ASCII digit characters from 0 through 9. Non-digit input is ignored.

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.

Note: Refer to this video on building and debugging bare-metal applications in SoftConsole.

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.

Terminal interaction after choosing a PWM power level

Success

Connect the logic analyzer or the appropriate Click board input to the CorePWM output pin identified in the board reference design. If using an LED, follow the board user guide and use the required current-limiting resistor.


Use a logic analyzer to observe the pulse width increase as the selected duty cycle changes from a lower value to a higher value.

  1. duty cycle changes

  2. duty cycle changes

Back to Top

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.

Back to Top