4 Uses for Assembly Language

An embedded tip that I often advocate is that developers should avoid using assembly language. The reason for avoiding assembly language is that it is specific to the processor architecture being used, it is difficult to read, understand and maintain. Today, we are going to look at the few areas where I believe it is still appropriate to use assembly language and how that code looks.

Before we look at each area that assembly language can still be used, please keep in mind that how these assembly instructions are integrated into a code base will vary based on the development environment that is used. There is usually some custom compiler function that is used to let the compiler language know that assembly instructions are being used such as _asm(). The code snippets we examine may require modification before use.

 

Assembly Usage #1 – To Execute a Breakpoint Instruction

Whenever I start on a new project, one of the first things I do during implementation is to put assembly instructions for breakpoints in interrupt service routines for CPU faults, watchdog time-outs and within special RTOS events such as malloc failed (even though I avoid using malloc). The reason that I do this is that I want to be notified when I reach one of these functions unexpectedly, but I do not want to manage those breakpoints. Many modern processors have a breakpoint assembly instruction and using assembly code to execute that instruction is completely appropriate.

For an Arm Cortex-M processor, using an IDE similar to STM32 CubeIDE, such an assembly implementation might look like the following:

__asm(“bkpt”);

When this instruction is reached, the processor will halt execution.

 

Assembly Usage #2 – Transition from Bootloader to Application

A second area where developers should be looking to use assembly language is at the transition point from bootloader to application code. In many systems, the bootloader is the first application that executes. It sets up the processor, validates memory and potentially performs several other functions. At some point though, the bootloader gives way and jumps to the main application. In order to make that jump successfully, many components within the processor need to be set to their initial condition such as the system stack and then the function pointer for reset vector of the application needs to be loaded.

When I integrate assembly language into an application, I often make use of the IDE’s assembly functions. These functions can vary from one IDE to the next. For example, below is a function that accepts the start address location for where the reset vector for an application is located. This code was put together for an NXP Kinetis-L processor using NXP specific toolchains:

void Flash_StartApplication(uint32_t startAddress)
{
    // Set up stack pointer
    asm("LDR     r1, [r0]");
    asm("mov     r13, r1");

    // Jump to application reset vector
    asm("ADDS     r0,r0,#0x04 ");
    asm("LDR      r0, [r0]");
    asm("BX       r0");
}

Another example for the same function but in this case the code is written for a Texas Instruments C2000 processor:

void Flash_StartApplication(uint32_t startAddress)
{
    asm ("      C28OBJ"); //Select C28x object mode
    asm ("      C28ADDR") ; //Select C27x/C28x addressing

    asm ("      SETC INTM");
    asm ("      ZAPA");
    asm ("      MOV @SP,#0");
    asm ("      PUSH ACC");
    asm ("      PUSH AL");
    asm ("      MOV AL, #0x0a08");
    asm ("      PUSH AL");
    asm ("      MOVL XAR7, #0x003F3FFE");
    asm ("      PUSH XAR7");

    asm ("      POP RPC");
    asm ("      POP ST1");
    asm ("      POP ST0");
    asm ("      POP IER");
    asm ("      POP DBGIER");
    asm ("      LRETR");
}

Again, this provides low-level control over the processor to ensure that everything gets put back to square one and that the application can then execute from a clean slate.

 

Assembly Usage #3 – Code Optimization in a Control Loop

There are still instances where it may be necessary to optimize the code in a high frequency control loop that requires the use of assembly. Hand coding a fast control loop in assembly used to be quite common. While this may still be appropriate at times, in today’s development environments with ultra-fast processors and compilers that use sophisticated optimization techniques, more and more I feel that this last case for assembly language usage is disappearing.

 

Assembly Usage #4 – To Teach Microcontroller Fundamentals

When I first started out in embedded systems, I was essentially forced to develop my applications in assembly. The microcontroller vendors back then did not offer free C compilers. That was something that you had to pay for; However, their assembly language tools were completely free. While many of us today want to start at the highest level of abstraction possible, for embedded systems it’s critical that developers understand what is going on under the hood and there is no better way to learn that then to write software in assembly.

I don’t think that a lot of time should be spent writing in assembly, but I think developers new to embedded systems should write some very basic applications like a “Hello World” application and a blinky LED application to understand how to initialize the processor, control registers and get a little familiar with the underlying instruction set. These details can help developers write far more efficient code in C/C++ if they understand the underlying architecture and the only way to truly understand it, is to work with it in its own natural language.

 

Conclusions

Assembly language should be avoided in most circumstances today, but when it is used it can provide a mechanism for very fine control over how an application behaves. We explored three areas where a developer might consider using assembly language. In my mind, these are the only areas where it may still be appropriate to use assembly language and even then, there may be alternatives available. If you do decide to use some assembly language, make sure that you heavily document that area of code so that it will be easier for yourself and other developers to maintain that code in the future.

10 thoughts on “4 Uses for Assembly Language”

  1. One place I’ve used it is in the distant past while debugging with an ICE. It was to change an opcode to set an LED, a NOP (no operation) or a BNE (branch not equal) to jump over a section of code easily, rather than building and loading a new version on the target. The toolset generated a map and listing file, showing the assembler code and C code side by side, for context.

  2. I don’t recommend breakpoint instructions. I’ve come across multiple code bases where developers used them but forgot to remove them for release. For ARM Cortex-M, when you run standalone (without JTAG), you’ll get a hard fault that is very difficult to diagnose as the problem will go away when you attempt to fix it by attaching JTAG.

    Like any tool, it is not evil in itself, but it depends how you use it. You can wrap it with the NDEBUG macro. But that’s not a panacea. Many developers forget (or don’t bother) to switch from debug to release.

    I put this in the same camp as MISRA rules. You can violate every MISRA rule in the book and still write code that functions correctly. But its also much more likely to be incorrect than correct.

    Not worth the risk IMO.

    1. Thanks for the comment. You’re definitely right on the potential issues. I didn’t mention it but I generally wrap these in debug builds only and am then very careful to make sure they don’t find their way into a production build. It definitely requires some extra process to use correctly and as you mention, it becomes a trade-off between having code that can potentially cause issues.

  3. I would also add,

    Assembly Usage #5: Any function that is sensitive to the stack.

    For example, in Cortex-M the hard fault handler should be written in assembly. You can get to the hard fault handler via a stack overflow. Your hard fault handler should be written in assembly so you can guarantee it doesn’t use the stack (otherwise you can get latchup).

    Assembly Usage #6: Any operation not supported by the language or architecture.

    For example, on Cortex-M, the SVC handler should also be in assembly so that you can pick the immediate from the SVC instruction to know what handler to call.

    Another example would be math routines that are not supported by the hardware or are inefficient to do using library routines. Although, as you mentioned, this is going away. I still have to do this somewhat on Cortex-M0, but on higher end processors we have hardware support for floating point and multiply-accumulate.

  4. I don’t write much assembly code anymore, but I use assembly language all the time.

    Knowing assembly language is a very helpful skill when debugging. You can learn a lot by looking at what’s happening in the disassembly pane and the registers. A bug that was not obvious at all in a C++ template was crystal clear in the generated code.

    I also use the compiler’s assembly listing to help me learn how to help the compiler generate better code. Understanding the compiler’s optimization rules can make a world of difference in the performance of the resulting object code. That can help keep the “inner loop” code in a higher-level language, while still getting (almost) the same performance as using assembly language.

  5. Sorry, english grammar police.
    vs
    Idea(function parameter)

    Is there a method in assembly that would contain a feature subset to measure a pipeline stall?

    How can an operation such as function(return from sleep) behave deterministically?

    1. Good question. I think this is going to be architecture dependent. If the architecture contains an instruction for this then it would be possible.

  6. Great article, as always. I’ll add my #7, #8, and #9.
    These are similar to #4 “To Teach Microcontroller Fundamentals”,
    but going in slightly different directions:

    #7 To Teach Computer Architecture
    #8 To Teach Compiler Code Generation
    #9 To Teach Programming Language Runtime Environments

    I’ve found that colleagues who did not get to experience the joy (agony?)
    of programming in assembly language are much more likely to have
    a hard time dealing with porting code, understanding the runtime
    cost of various high-level language constructs, and knowing how to
    navigate around runtime behavior such as garbage collection.
    You might say, “well only a small number of engineers write low-level
    code any more”, but it does help to have a working familiarity with the
    concepts even if day-to-day you work in Python etc.

    Thanks for another thought-provoking article!
    Glenn

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.