How to Migrate from IAR Embedded Workbench to Keil MDK

IAR to Keil MDK migration guide by the Pertech Embedded Solutions engineering team. Last updated: October 2026. Applies to: IAR Embedded Workbench for Arm (EWARM), Keil MDK v5 and v6, Arm Compiler 6 (armclang), Arm Cortex-M devices.

Quick answer: IAR to Keil MDK migration

To migrate an Arm Cortex-M project from IAR Embedded Workbench for Arm to Keil MDK, create a new Keil MDK project for the same device, add your source files, and replace the IAR-specific parts: the startup file (use the CMSIS device pack instead), IAR pragmas and extended keywords (use armclang attributes and Clang pragmas), IAR intrinsics (use CMSIS-Core functions) and the IAR linker configuration file .icf (convert it to an Arm scatter file .sct). The application C code does not change. The effort depends mostly on how many pragmas, fixed-address placements and assembly files the project has.

In this guide

  1. What changes and what stays the same
  2. Before you start: make the code portable inside IAR
  3. Step by step: the mechanical migration
  4. How to convert IAR pragmas
  5. How to convert IAR extended keywords and intrinsics
  6. How to convert the IAR .icf linker file to a scatter file
  7. Checklist
  8. Frequently asked questions

What changes and what stays the same?

Part of the projectChanges?What to do
Application C and C++ codeNoKeep as is
Device headers and HAL / SDK driversNoUse the same vendor HAL, from the device’s CMSIS pack
Startup file and vector tableYesReplace with the CMSIS pack startup file
#pragma directivesYesConvert using the pragma tables
IAR extended keywords (__root, __no_init, __ramfunc, @)YesConvert to __attribute__
IAR intrinsics (intrinsics.h)YesUse CMSIS-Core functions
Assembly files (.s)YesConvert IAR assembler syntax to GNU syntax
Linker file (.icf)YesConvert to a scatter file (.sct)
Compiler and linker optionsYesMap using the options table
Debugger and flash setupYesSelect the probe and flash algorithm in Keil MDK

Before you start: make the code portable inside IAR

Do the first round of changes while you are still building with IAR. Each step keeps the project working and shrinks the final switch.

  1. Replace IAR intrinsics with CMSIS-Core functions. __disable_irq(), __NOP(), __get_PRIMASK() and the rest work with both compilers.
  2. Wrap compiler-specific syntax in macros. CMSIS already provides __WEAK, __PACKED, __ALIGNED(x), __USED, __NO_RETURN, __STATIC_FORCEINLINE and __RAM_FUNC style macros in cmsis_compiler.h. Use them instead of __weak, __packed or #pragma inline=forced.
  3. Guard what remains with the compiler’s predefined macros:
#if defined(__ICCARM__)                 /* IAR */
  /* IAR-specific code */
#elif defined(__ARMCC_VERSION) && (__ARMCC_VERSION >= 6000000)  /* Arm Compiler 6 */
  /* armclang-specific code */
#endif

Step by step: the mechanical migration

Step 1. Take an inventory of the IAR project

The IAR project file (.ewp) is XML. Open it in a text editor and copy out what the new project needs:

What you needWhere it is in the .ewp file
Deviceoption OGChipSelectEditMenu
Source files and groups<group> and <file> elements
Include pathsoption CCIncludePath2
Preprocessor definesoption CCDefines
Pre-include fileoption PreInclude
Linker fileoption IlinkIcfFile
Extra linker symbolsoption IlinkConfigDefines

Keep the build configurations (for example Debug and Release) separate: each has its own option block.

Step 2. Find every IAR-specific construct in the code

Run a search across the source tree and save the result as your work list.

grep -rnE "#pragma|_Pragma\(|__root|__no_init|__ramfunc|__weak|__packed|__interrupt|__task|__stackless|__section_begin|__section_end|__ICCARM__|intrinsics\.h|@ *\"|@ *0x" --include=*.c --include=*.h --include=*.cpp .

On Windows PowerShell:

Get-ChildItem -Recurse -Include *.c,*.h,*.cpp | Select-String -Pattern '#pragma|_Pragma\(|__root|__no_init|__ramfunc|__weak|__packed|__interrupt|__task|__stackless|__section_begin|__section_end|__ICCARM__|intrinsics\.h|@ *"|@ *0x'

Each hit is converted with the tables later in this guide.

Step 3. Install Keil MDK and the device pack

  1. Install Keil MDK.
  2. Install the CMSIS pack for your device: in µVision use Pack Installer; with Keil MDK v6 in VS Code, the pack is installed when you select the device, or with cpackget add <Vendor>::<DeviceFamilyPack> from the command line.

The pack brings the device header, startup file, system file, flash algorithm and, for most vendors, the HAL or SDK drivers.

Step 4. Create the Keil MDK project

In µVision (Keil MDK v5 or v6):

  1. Project > New µVision Project, save it next to the IAR project.
  2. Select the same device as in the IAR project.
  3. In Manage Run-Time Environment, select CMSIS > CORE and Device > Startup. Add the vendor HAL or SDK components if you use them.
  4. Create groups that mirror the IAR groups and add the same source files.
  5. Do not add the IAR startup file (startup_<device>.s from the IAR tree) or IAR-only assembly files. The CMSIS pack startup file replaces them.

In Keil MDK v6 with VS Code (CMSIS csolution project): create a solution for the device and list the same files, defines and include paths in the .cproject.yml file:

project:
  groups:
    - group: App
      files:
        - file: src/main.c
        - file: src/app.c
  components:
    - component: CMSIS:CORE
    - component: Device:Startup
  define:
    - USE_HAL_DRIVER
  add-path:
    - inc
  linker:
    - script: app.sct

Set compiler: AC6 in the .csolution.yml file.

Step 5. Copy the compiler settings

In µVision: Options for Target > C/C++ (AC6).

  1. Paste the preprocessor defines into Define. Remove __ICCARM__-style defines that only made sense for IAR.
  2. Paste the include paths into Include Paths.
  3. Set Language C to gnu11 (or the C standard the project uses) and Language C++ if needed.
  4. Set the optimization level (see the table below).
  5. Tick One ELF Section per Function, so the linker can remove unused code the way IAR does.
  6. Put any remaining options into Misc Controls.
IAR compiler optionArm Compiler 6 equivalent
--cpu, --fpuSet automatically from the selected device (-mcpu, -mfpu, -mfloat-abi)
-On (none)-O0
-Ol (low)-O1
-Om (medium)-O2
-Oh / -Ohs (high, speed)-O3 (or -Ofast if strict IEEE floating point is not required)
-Ohz (high, size)-Oz
--debug-g
-e (extended language)Not needed. GNU extensions are on with gnu11 / gnu++17
--preinclude file.h-include file.h
--diag_suppress PeXXX-Wno-<warning-name>
--diag_error PeXXX-Werror=<warning-name>
--warnings_are_errors-Werror
--endian=littleDefault. -mbig-endian only for big-endian targets
--no_wrap_diagnostics, --silentRemove
--dlib_configChoose the Arm C library or tick Use MicroLib in the Target tab

Step 6. Set up the linker

In Options for Target > Linker:

  1. Untick Use Memory Layout from Target Dialog.
  2. Point Scatter File to the .sct file you converted from the .icf (see the linker section below).
  3. Add --keep= options for symbols that were __root in IAR.
  4. Add --map --info=sizes,totals,unused to Misc controls to get a map file you can compare with the IAR one.

For a simple single-flash, single-RAM project you can leave Use Memory Layout from Target Dialog ticked and let Keil MDK generate the scatter file.

Step 7. Convert the code

Work through the list from Step 2 using the tables in this guide: pragmas, keywords, intrinsics and assembly. Build after each group of changes. Treat every unknown pragma and unknown attribute warning as a migration item.

Step 8. Set up the debugger and flash

In Options for Target > Debug, select your probe (ULINK, CMSIS-DAP or the probe on the evaluation board). In Utilities > Settings > Flash Download, check that the device’s flash algorithm is listed. It comes from the CMSIS pack.

Step 9. Compare and test

  1. Compare the IAR and Arm map files: code size, RAM use, stack size, and the address of every named section and fixed-address variable.
  2. Run the full test suite on hardware. Give extra attention to interrupt handling, packed structures, anything at a fixed address and anything timing-sensitive.
  3. Keep the IAR project building until the Keil MDK build passes the same tests.

How to convert IAR pragmas

Arm Compiler 6 (armclang) is based on LLVM/Clang. It accepts GCC-style __attribute__ syntax and Clang pragmas, and ignores IAR pragmas it does not know, usually with a warning. Search the code base for #pragma and _Pragma( and work through the list below. Treat every “unknown pragma” warning as a migration item, not noise.

Placement and alignment

IARArm Compiler 6Notes
#pragma location = ".mysection"__attribute__((section(".mysection")))Then place .mysection in the scatter file
#pragma location = 0x20001000 or @ 0x20001000__attribute__((section(".ARM.__at_0x20001000")))armlink places .ARM.__at_<address> sections at that address. For many fixed addresses, a scatter-file region is cleaner
int x @ ".mysection";int x __attribute__((section(".mysection")));The @ operator has no armclang equivalent
#pragma data_alignment = 8__attribute__((aligned(8)))Or CMSIS __ALIGNED(8)
#pragma default_function_attributes = @ ".ramcode" … #pragma default_function_attributes =#pragma clang section text=".ramcode" … #pragma clang section text=""Same idea for data=, bss= and rodata=
#pragma default_variable_attributes = @ ".sram2"#pragma clang section data=".sram2" bss=".sram2"Initialized and zero-initialized data are separate in armclang

Packing

IARArm Compiler 6Notes
#pragma pack(1) / #pragma pack()Same, #pragma pack(1) / #pragma pack()Supported as is. push and pop also work
__packed struct s { ... };struct __attribute__((packed)) s { ... };Or CMSIS __PACKED_STRUCT
#pragma type_attribute = __packed__attribute__((packed)) on the type

Check every packed structure that is shared with another processor, a file format or a protocol. Layout should match, but this is the place to confirm it with a static_assert(sizeof(...) == N).

Inlining and optimization

IARArm Compiler 6Notes
#pragma inlineinline (or static inline)
#pragma inline = forced__attribute__((always_inline)) inlineOr CMSIS __STATIC_FORCEINLINE
#pragma inline = never__attribute__((noinline))
#pragma optimize = none__attribute__((optnone)) on the function, or #pragma clang optimize off … #pragma clang optimize on
#pragma optimize = size / speed / high on one functionNo per-function equivalentSet the optimization level for that source file in the project options (per-file settings are supported)
#pragma unroll = 4#pragma unroll 4 or #pragma clang loop unroll_count(4)Place directly before the loop

Keeping and initializing symbols

IARArm Compiler 6Notes
__root or #pragma required = sym__attribute__((used)) plus linker option --keep=sym (or --keep=*(.section))used stops the compiler from dropping the symbol. armlink still removes unreferenced sections unless told to keep them
__no_init int x;int x __attribute__((section(".bss.noinit"))); and an UNINIT execution region in the scatter fileSee the scatter example below
#pragma object_attribute = __root__attribute__((used)) + --keep

Diagnostics

IARArm Compiler 6Notes
#pragma diag_suppress = Pe177#pragma clang diagnostic ignored "-Wunused-variable"IAR diagnostics are numbered, Clang’s are named. Map each one; don’t carry over blanket suppressions
#pragma diag_default / diag_warning / diag_error#pragma clang diagnostic warning "-W..." / error "-W..."Wrap in #pragma clang diagnostic push / pop
#pragma message("text")#pragma message("text")Supported as is
#pragma once#pragma onceSupported as is

Pragmas with no direct equivalent

IARWhat to do
#pragma language = extendedRemove. armclang accepts GNU extensions by default (-std=gnu11 / gnu++17)
#pragma calls = ... (stack usage hints)Remove. Use the linker’s static call graph and stack usage report (--callgraph, --info=stack)
#pragma bitfields = reversedNo equivalent. Rewrite the bit-field as explicit masks and shifts, which also makes it portable
#pragma vector = ...Not used on Cortex-M. Interrupt handlers are plain C functions named in the vector table
#pragma segmentReplace with sections and linker symbols (see below)

How to convert IAR extended keywords and intrinsics

Extended keywords

IARArm Compiler 6Notes
__weak__attribute__((weak))Or CMSIS __WEAK
__ramfunc__attribute__((section(".ramfunc"))) and a RAM execution region in the scatter filearmlink copies it to RAM at startup when the load and execution addresses differ
__noreturn__attribute__((noreturn))Or _Noreturn (C11)
__interruptRemove on Cortex-MCortex-M handlers are ordinary AAPCS functions
__irq, __fiq (Cortex-A/R)__attribute__((interrupt("IRQ"))) / ("FIQ")
__taskRemoveCheck the function is never expected to return
__stackless__attribute__((naked))Only inline assembly is allowed in a naked function. Review each use
__nounwind, __intrinsicRemove

Intrinsics

IAR (intrinsics.h)CMSIS-Core (both compilers)
__disable_interrupt()__disable_irq()
__enable_interrupt()__enable_irq()
__get_interrupt_state() / __set_interrupt_state(s)__get_PRIMASK() / __set_PRIMASK(s)
__no_operation()__NOP()
__WFI(), __WFE(), __SEV(), __DSB(), __ISB(), __DMB()Same names in CMSIS
__CLZ(x), __REV(x), __RBIT(x)Same names in CMSIS
__section_begin("CSTACK") / __section_end / __section_sizeLinker symbols: Image$$ARM_LIB_STACK$$ZI$$Limit, Image$$<region>$$Base, Image$$<region>$$Limit, Load$$<region>$$Base

Inline and stand-alone assembly

  • Inline assembly in IAR uses GNU-style extended asm("..." : outputs : inputs : clobbers). armclang accepts the same syntax, so most blocks move as is. Check register constraints and labels.
  • Assembly files written for the IAR assembler (SECTION, PUBLIC, EXTERN, DC32, END) need converting to GNU syntax (.section, .global, .extern, .word), which armclang’s integrated assembler uses.
  • Startup code: don’t convert the IAR startup file. Use the startup and system files from the device’s CMSIS pack, which Keil MDK adds automatically.

How to convert the IAR .icf linker file to a scatter file

The IAR .icf file and the Arm scatter file (.sct) describe the same thing: where code and data load and where they run. A typical single-flash, single-RAM Cortex-M layout:

IAR (.icf)

define memory mem with size = 4G;
define region ROM_region = mem:[from 0x08000000 to 0x080FFFFF];
define region RAM_region = mem:[from 0x20000000 to 0x2001FFFF];

define block CSTACK with alignment = 8, size = 0x1000 { };
define block HEAP   with alignment = 8, size = 0x800  { };

initialize by copy { readwrite };
do not initialize  { section .noinit };

place at address mem:0x08000000 { readonly section .intvec };
place in ROM_region { readonly };
place in RAM_region { readwrite, block CSTACK, block HEAP };

Arm (.sct)

LR_IROM1 0x08000000 0x00100000 {
  ER_IROM1 0x08000000 0x00100000 {
    *.o (RESET, +First)
    *(InRoot$$Sections)
    .ANY (+RO)
    .ANY (+XO)
  }
  RW_IRAM1 0x20000000 0x0001E800 {
    .ANY (+RW +ZI)
  }
  RW_NOINIT +0 UNINIT {
    *(.bss.noinit)
  }
  ARM_LIB_HEAP  0x2001E800 EMPTY 0x00000800 { }
  ARM_LIB_STACK 0x2001F000 EMPTY 0x00001000 { }
}

Mapping:

IAR .icfArm .sct
define regionLoad region (LR_...) and execution regions (ER_..., RW_...)
place at address ... { section .intvec }*.o (RESET, +First) (the CMSIS startup file names its vector table section RESET)
place in ROM_region { readonly }.ANY (+RO)
place in RAM_region { readwrite }.ANY (+RW +ZI)
initialize by copy { readwrite }Automatic. RW data in a RAM region is copied from flash at startup
do not initialize { section .noinit }An execution region with the UNINIT attribute
block CSTACK, block HEAPARM_LIB_STACK and ARM_LIB_HEAP regions
keep { section ... }--keep= linker option

For a simple layout you can skip the hand-written file: Keil MDK can generate the scatter file from the memory layout in the project’s target options. Write your own when you have several RAM blocks, RAM functions, no-init areas or a bootloader split.

C library and retargeting

  • IAR’s DLIB and the Arm C library both provide the standard C library. Arm also offers MicroLib, a smaller library for size-constrained devices.
  • Low-level I/O moves from IAR’s __write / __read to Arm’s retargeting functions (fputc, _sys_write and related) or the CMSIS-Compiler retarget component.
  • If the target must never use semihosting, add __asm(".global __use_no_semihosting"); in one source file and provide the retarget functions the linker asks for.

Checklist

  1. Build with CMSIS intrinsics and macros in IAR first.
  2. Grep for #pragma, _Pragma(, @ , __root, __no_init, __ramfunc, __weak, __packed, __section_begin.
  3. Convert pragmas and keywords using the tables above.
  4. Replace startup code with the CMSIS pack files.
  5. Convert the .icf to a scatter file, or generate one.
  6. Retarget I/O.
  7. Build with warnings on and clear every “unknown pragma” and “unknown attribute” warning.
  8. Compare the map files: code size, RAM use, stack, and the placement of every named section.
  9. Run the full test suite on hardware, with extra attention to anything timing-sensitive, packed or placed at fixed addresses.

For safety-critical projects

If the product is developed to IEC 61508, ISO 26262, IEC 62304 or EN 50128, changing compilers also changes your tool qualification evidence. Arm Compiler for Embedded FuSa is TÜV SÜD certified and comes with a qualification kit (safety manual, development process and defect reports). Arm FuSa RTS adds a certified RTOS and C library for the same standards. Both are included in Keil MDK-Professional, not in MDK-Essential. Plan the qualification update as part of the migration, not after it.

Frequently asked questions

Can Keil MDK open an IAR Embedded Workbench project directly?

No. Create a new Keil MDK project for the same device and add the source files. The IAR .ewp file is XML, so the file list, include paths and defines can be copied from it (see Step 1).

Do I have to rewrite my C code to move from IAR to Keil MDK?

No. Standard C and C++ code compiles unchanged with Arm Compiler 6. Only compiler-specific parts change: pragmas, extended keywords, intrinsics, assembly files, startup code and the linker file.

What is the Arm Compiler 6 equivalent of IAR #pragma location?

__attribute__((section(".name"))) for a named section. For a fixed address, use __attribute__((section(".ARM.__at_0x20001000"))) or place the section at that address in the scatter file.

What replaces IAR __no_init in Keil MDK?

Put the variable in a named section with __attribute__((section(".bss.noinit"))) and place that section in a scatter-file execution region marked UNINIT. The startup code then leaves it untouched.

What replaces IAR __root?

__attribute__((used)) keeps the symbol in the compiler output. Add the linker option --keep=<symbol> so the linker doesn’t remove it either.

What replaces IAR #pragma optimize?

For no optimization on one function, use __attribute__((optnone)) or #pragma clang optimize off and on. For a different speed or size level, set the optimization for that source file in the project options.

Does #pragma pack work in Arm Compiler 6?

Yes. #pragma pack(n), push and pop are supported as in IAR. The IAR __packed keyword becomes __attribute__((packed)).

How do I convert an IAR .icf file to a Keil scatter file?

Map each IAR region to a scatter load or execution region, readonly to .ANY (+RO), readwrite to .ANY (+RW +ZI), the CSTACK and HEAP blocks to ARM_LIB_STACK and ARM_LIB_HEAP, and do not initialize to an UNINIT region. For a simple memory layout, Keil MDK can generate the scatter file for you.

Which IAR compiler optimization level matches Arm Compiler 6 -Oz?

IAR high optimization for size (-Ohz) is closest to -Oz. IAR high optimization for speed (-Ohs) is closest to -O3.

Can I use the same debug probe after moving to Keil MDK?

Usually yes. Keil MDK supports Arm ULINK probes, CMSIS-DAP probes, including most on-board evaluation probes, and several third-party probes. Select it in Options for Target > Debug.

Is Keil MDK certified for functional safety?

Keil MDK-Professional includes Arm Compiler for Embedded FuSa, certified by TÜV SÜD, and Arm FuSa RTS, a certified RTOS and C library. Changing compilers in a certified project means updating your tool qualification evidence.

Can both compilers build the same code during the transition?

Yes. Use CMSIS-Core intrinsics and the CMSIS compiler macros, and guard what remains with #if defined(__ICCARM__) and #elif defined(__ARMCC_VERSION). Keep the IAR build running until the Keil MDK build passes the same tests.

Pertech can help

Pertech Embedded Solutions is the official Arm Keil partner in Israel. We provide Keil MDK evaluation licences, help with migrating real projects, and local support in Hebrew and English. Contact us to plan a migration.

upbtn
Skip to content