Hello, welcome.
In this short video, I am going to answer the question: why should we learn assembly
language?
Here is the list of the top 10 programming languages for embedded systems.
The ranking was published by IEEE Spectrum in 2019.
Overall, for embedded systems, assembly language was ranked top 6 among 52 programming languages.
In 1950s and 60s, many applications were written entirely in assembly.
As processors become more powerful and compilers become more effective in optimizing compiled
code, high-level languages, such as C, C++, and Python, have been widely used to enhance
programming productivity.
However, assembly language is not obsolete.
Here is the list of the most popular programming languages based on jobs.
Among all jobs related to embedded system, assembly is among the top 5.
Today, almost no employers are going to pay you to develop a large and complex application
entirely in assembly.
Most embedded system applications are writen in high level languages.
The main disadvantages of assembly language are that it is difficult to develop, read
and maintain, and it is bug prone.
In addition, assembly language is not portable from one type of processors to another.
An assembly program might need to be completely rewritten for it to run on another type of
processors.
High-level languages, on the contrary, are portable, and much easier to design and maintain.
However, assembly language isn't "just another language".
It is the interface between the software and the hardware.
Assembly language is very close to machine language.
Thus, assembly language helps us understand what happen under the hood when a processor
executes a program.
Simultaneously, via implementing high-level control and structures, assembly language
helps us understand high-level languages better.
Therefore, the first reason of learning assembly is that it helps us learn how a processor
work.
Assembly language describes what is going on "under the hood" of the processor.
It provides required background knowledge for some courses that you might have to take
later in your curriculum, such as computer architecture, operating systems, and compiler.
The second reason is that generally an assembly program not only runs faster but also is smaller
than its corresponding program written in a high-level language.
In embedded systems, we often use profiling tools to identify the performance bottleneck,
and then rewrite, often in assembly, the subroutines where computation speed is extremely critical.
In assembly, we can manually select the best instructions and use the minimum required
instructions to accomplish a task.
Accordingly, in some scenarios, hand-written assembly may significantly outperform compilers.
In addition, assembly programs consume less memory, which is very helpful for cost-sensitive
applications.
The third reason is that sometimes we have to use assembly.
Here I am going to show three examples.
First, some special processor-dependent instructions are not supported by standard compilers.
These instructions are outside the standard instruction set used by compilers.
Here are five example instructions.
To support these instructions, we can insert these special instructions into C.
This is called inline assembly and here are two examples.
These two C functions calls the CPSIE and CPSID instruction to enable and disable interrupts.
The second example is startup code.
Startup code is used to define the heap, the stack, the initial values of the interrupt
vector table, as well as the default implementation of interrupt service routines.
It is possible to write the startup code in C. However, the startup code in C is not independent
from the tool chain because the C code has to import compiler-specific symbols and directives.
The third example is that, sometimes we have to use assembly when we develop device drivers.
Typically, we use assembly or inline assembly to access machine-dependent registers and
I/O, or to control the exact code behavior in critical sections that might cause deadlock.
The last reason of learning assembly, but not the least, is that it helps us write better
software in high-level languages.
It is often that the best applications are written by those who've mastered assembly
language or fully understand the low-level implementation of the high-level language
statements they're choosing.
For example, the low-level data representation and low-level translation of C pointers can
help us better understand pointer reference and deference.
For a function, the assembly translation of parameters passed by reference and passed
by value can help us understand their difference.
Assembly can also help us understand why we need to declare a variable as volatile or
static.
In addition, at the assembly level, we can observe the stacking and unstacking process
when a function is called.
As a result, it is not surprising to know that a recursive function has a larger memory
overhead.
Let me give you two detailed examples.
When building this simple C program, the compiler in Keil reports no error and no warning.
However, when we run this program, the output is totally unexpected and it prints "something
is wrong."
Variable x is 1 and variable y is -1.
Thus, the comparison statement should be true, and the string "of course" should be printed.
However, when comparing signed with unsigned, the compiler implicitly converts the signed
value to an unsigned one without changing any bit patterns, and then performs the comparison
assuming both operands are non-negative.
Since -1 is represented in two's complement in modern computers, the value of y, stored
in register r1, is 0xFFFFFFFF.
The compiler translates the C comparison to this assembly code.
Here, the BLS instruction is to branch if unsigned lower than or same.
As a result, the program prints "something is wrong".
You may ask: why doesn't the compiler convert the unsigned to signed and then perform signed
comparison?
This is because the rank of 32-bit unsigned is higher than the rank of 32-bit sign.
Before the arithmetic happens, operands are converted to the data type with a higher rank
Some compiler does give a warning.
We should always fix the warning.
First of all, we need to ask ourselves why we have to compare variables with different
signs.
Maybe it was a software bug and we should have declared them as the same data type.
If we do have to make such type of comparisons, we should explicitly cast one of them to the
other data type.
Let me give you another example to show that assembly helps us understand high level languages
better.
Suppose the initial value of the global variable x is 1.
We have two tasks, A and B, which run concurrently.
These two tasks might be two interrupt handlers, or two threads if we have OS or RTOS.
Here is the question: What is the final value of x?
The answer is not that simple.
At the end, x might be 4, 6, or 9.
We know that these two C operations are not atomic.
It is obvious if we translate them into assembly.
In the assembly code, each task reads the x value out from the memory, then adds 3 or
5 to x, and finally writes the result back to the memory.
Basically, a sequence of load, modify, store operations are performed at the low level
to change a value in the memory.
If the execution timeline is this, the final result of x is 9.
In this case, we are lucky that the sequence of load-modify-store is not interfered between
these two tasks.
However, the execution timeline may look like this.
Before the store operation of task A is executed, the processor pauses the execution of task
A, and starts to serve task B. After B completes, the processor resumes the execution of A and
store the value 4 into the memory.
Accordingly, the final value of x is 4.
In addition, the execution timeline may look like this.
As a result, the final value of X is 6.
In this example, the assembly code helps us understand why x can have different values
at the end.
To avoid such issue, one solution is to use a lock to synchronize entry to regions of
code that access shared resources.
When one thread has entered this region, no other thread can enter.
Here is the summary of this short video.
As compilers have become very smart, it is true that you might not need to write any
assembly programs in the future.
So, why should we learn assembly language?
First, assembly language provides insights about what is running under the hood of a
processor, and it provides important background knowledge for other courses, such as computer
architecture and operating systems.
Second, carefully hand-tuned assembly program runs fast and can be used for performance
critical sessions to alleviate performance bottleneck.
Third, we have to use assembly to access processor-dependent instructions, which have no equivalent statements
in high-level languages.
Last, but not the least, understanding assembly helps us write better programs in high-level
languages.
Thank you for watching.
Please visit the book website for more information.
See you next time.