Although assembly language is not as prevalent as a high-level language, such as C or an object-oriented language like C++, it is the predominant language used in embedded microprocessors. A course in a high-level language, such as C, usually precedes a course in assembly language.
Assembly language programming requires a knowledge of number representations, such as fixed-point, decimal, and floating-point; also digital logic, registers, and stacks. In order to thoroughly understand assembly language, it is necessary to be familiar with the architecture of the computer on which the language is being used. For the X86 assembly language, this implies the Intel and Intel-like microprocessors. Programs written in assembly language are usually faster and more compact than programs written in a high-level language and provide greater control over the program application. Assembly language is machine dependent; that is, it is used only with a specific type of processor. A high-level language, however, is usually machine independent; that is, it can be used with any processor.
Assembly language programs use an assembler to convert the assembly language code to the machine language of 1s and 0s. This is in contrast to high-level languages which use compilers to accomplish the transformation.
Assembly languages consist of mnemonic codes, which are similar to English words, making the program easy to read. For example, the MOV instruction moves data from a source location to a destination location; the XCHG instruction exchanges the contents of a source location and a destination location; and the logical AND instruction performs the bitwise AND operation of two operands.
The programs in this book are written using X86 assembly language only , the C programming language only, or by embedding an in-line assembly language module in a C program by using the _asm command. The assembly language code immediately follows the _asm command and is bracketed by left and right braces, as shown below.
#include "stdafx.h"
int main (void)
{
define variables
_asm switch to assembly language
{
assembly language code goes here
}
print results
return 0;
}
Assembly languages also have input/output (I/O) instructions to access I/O devices on the computer. Input/output instructions are usually not available for highlevel languages. Also, assembly languages can access the stack, general-purpose registers, base pointer registers, segment registers, and execute PUSH and POP operations.
The book presents the binary, octal, decimal, and hexadecimal number systems, as well as the basic X86 processor architecture. The architecture includes the general-purpose registers, the segment registers, the flags register, the instruction pointer, and the floating-point registers. The following topics are also presented: different addressing modes, data transfer instructions, branching and looping operations, stack operations, logic, shift, and rotate instructions. Computer arithmetic topics are presented in detail, including fixed-point, binary-coded decimal, and floating-point instructions. There are additional chapters on procedures, string operations, arrays, macros, and input/output operations. The fundamentals of C programming are covered in a separate chapter.
The book is intended to be tutorial, and as such, is comprehensive and self contained. All program examples are carried through to completion — nothing is left unfinished or partially designed. Also, all programs provide the outputs that result from program execution. Each chapter includes numerous problems of varying complexity to be designed by the reader.
Chapter 1 covers the number systems of different radices, such as binary , octal, binary-coded octal, decimal, binary-coded decimal, hexadecimal, and binary-coded hexadecimal. The chapter also presents the number representations of sign magnitude, diminished-radix complement, and radix complement.
Chapter 2 presents the generic architecture of processors and how the architecture corresponds more appropriately to the X86 architecture execution environment, including the different sets of registers. The chapter also covers the arithmetic and logic unit (ALU), the control unit, and memory, including main memory and cache memory. Error detection and correction is also discussed using the Hamming code developed by Richard W. Hamming. A brief introduction to tape drives and disk drives is also presented. The X86 register set is covered, which includes the generalpurpose registers (GPRs), the segment registers, the EFLAGS register containing status flags, system flags, and a control flag. Other registers include the instruction pointer and the floating-point registers. The translation lookaside buffer (TLB) and the assembler are also briefly discussed.
Chapter 3 presents the various addressing modes of the X86 assembly language. The instruction set provides various methods to address operands. The main methods are: register, immediate, direct, register indirect, base, index, and base combined with index. A displacement may also be present. These and other addressing methods are presented in this chapter together with examples. The processor selects the applicable default segment as a function of the instruction: instruction fetching assumes the code segment; accessing data in main memory references the data segment; and instructions that pertain to the stack reference the stack segment. However, a segment override prefix can be used to change the default data segment to another segment; that is, to explicitly specify any segment register to be used as the current segment.
Chapter 4 presents a brief introduction to the C programming language, which will be used in most chapters — typically to contain an embedded assembly language module. The main purpose of this chapter is to provide sufficient information regarding C programming in order to demonstrate how a C program can be linked to an assembly language program. The various constants and variables of the C language are presented, plus the input/output functions. The following operators are introduced: arithmetic, relational, if and else statements, and the logical operators of AND, OR, and NOT. Also included are the conditional operator, the increment and decrement operators, and the bitwise operators. There are two looping statements. The while loop executes a statement or block of statements as long as a test expression is true (nonzero). The second looping statement is the for loop. The for loop repeats a statement or block of statements a specific number of times. Arrays and strings are also covered in this chapter.
Chapter 5 presents the basic data transfer instructions as they apply to the X86 processors. Other data transfer instructions, such as instructions that pertain to stack operations and string operations, are presented in later chapters. This chapter also contains the various data types used in the X86 processors, which include signed binary integers, unsigned binary integers, unpacked binary-coded decimal (BCD) integers, packed BCD integers, and floating-point numbers.
Some of the basic move instructions involving data transfer are also presented. These include register-to-register, immediate-data-to-register, immediate-data-to-memory, memory-to-register, and register-to-memory. This chapter also covers moves with sign extension, moves with zero extension, and conditional moves that move data to a destination depending on the state of a flag. Different types of exchange instructions are discussed, which exchange the contents of a source and destination location. The chapter also presents translate instructions, which change an operand into a different operand in order to translate from one code to another code.
Chapter 6 presents branching and looping instructions as used in the X86 assembly language. These instructions transfer control to a section of the program that does not immediately follow the current instruction. The transfer may be a backward transfer to a section of code that was previously executed or a forward transfer to a section of code that follows the current instruction. The unconditional jump instruction advances the instruction pointer register forward or backward a specific number of instructions. It transfers control to a destination address and provides no return address. The conditional jump instruction transfers control to a destination instruction in the same code segment if certain condition codes are met — as determined by a compare instruction. If the condition is not met, then program execution continues with the next instruction that follows the conditional jump instruction. Implementing WHILE and FOR loops in assembly is also presented.
Chapter 7 presents stack operations in the X86 processor. The stack is a one-dimensional data structure located in contiguous locations of memory that is used for the temporary storage of data. It is one of the segments in a segmented memory model and is called the stack segment, in which the base address of the stack is contained in the stack segment register. A data element is placed on top of the stack by a PUSH instruction; a data element is removed from the top of the stack by a POP instruction. A stack builds toward lower addresses. Additional PUSH and POP instructions are also covered in this chapter. The PUSH instruction decrements an explicit number of bytes before the operation is executed, depending on the size of the operand being pushed onto the stack.
Chapter 8 presents the logical operations of AND, OR, exclusive-OR, NOT, and NEG. These instructions execute the Boolean equivalent of the corresponding operations in digital logic circuits. The NOT instruction performs a bitwise 1s complement operation on the destination operand and stores the result in the destination operand. The NEG instruction performs a 2s complement operation on the destination operand and stores the result in the destination operand. There are also bit test instructions that operate on a single bit and are used to scan the bits in an operand and then perform an operation on the selected bit. These instructions include: the bit test, bit test and set, bit test and reset, bit test and complement, bit scan forward, and bit scan reverse operations.
Shift instructions are also presented that perform logical or arithmetic left or right shifts on bytes, words, or doublewords. The number of bits shifted can be specified by an immediate value of 1, an immediate value stipulated in a byte, or a count in generalpurpose register CL or CX. The shift instructions are shift arithmetic left, shift logical left, shift arithmetic right, shift logical right, double precision shift left, and double precision shift right. There are also rotate instructions that rotate the operand a number of bits specified by an immediate value of 1, an immediate value stipulated in a byte, or a count in general-purpose register CL or CX. The rotate instructions are rotate left, rotate right, rotate through carry left, and rotate through carry right . Also covered is the set byte on condition instruction.
There are two bit scan instructions: bit scan forward and bit scan reverse. These instructions scan the contents of a register or memory location to determine the location of the first 1 bit in the operand.
Chapter 9 covers the four operations of addition, subtraction, multiplication, and division for fixed-point arithmetic. In fixed-point operations, the radix point is in a fixed location in the operand. The operands can be expressed by any of the following number representations: unsigned, sign-magnitude, diminished-radix complement, or radix complement. Addition operations include the add, add with carry, and the increment by 1 instructions. Subtraction operations include the subtract, subtract with borrow, decrement by 1, and twos complement negation instructions. Multiplication operations include the unsigned multiply and signed multiply instructions. Division operations include the unsigned divide and signed divide instructions.
Chapter 10 presents the binary-coded decimal (BCD) operations of addition, subtraction, multiplication, and division. BCD instructions operate on decimal numbers that are encoded as 4-bit binary numbers in the 8421 code. The BCD instructions include the ASCII adjust after addition instruction, which adjusts the result of an addition operation of two unpacked BCD operands in which the high-order four bits of a byte contain zeroes; the low-order four bits contain a numerical value; the decimal adjust AL after addition instruction, which adjusts the sum of two packed BCD integers to generate a packed BCD result; and the ASCII adjust AL after subtraction instruction, which adjusts the result of a subtraction of two unpacked BCD operands.
Other BCD operations include the decimal adjust AL after subtraction instruction, which adjusts the result of a subtraction of two packed BCD operands; the ASCII adjust AX after multiplication instruction, which adjusts the product in register AX resulting from multiplying two valid unpacked BCD operands; and the ASCII adjust AX before division instruction, which converts two unpacked digits in register AX to an equivalent binary value, then divides AX by an unpacked BCD value.
Chapter 11 presents floating-point arithmetic instructions. Floating-point numbers consist of the following three fields: a sign bit, s; an exponent, e; and a fraction, f. These parts represent a number that is obtained by multiplying the fraction, f,by a radix, r, raised to the power of the exponent, e, where f and e are signed fixed-point numbers, and r is the radix (or base). As the exponents are being formed, a bias constant is added to the exponents, making all exponents positive, thus allowing exponent comparison to be simplified.
Floating-point operations utilize an 8-register stack in which each register contains 80 bits. There are three main rounding methods used in floating-point operations: truncation rounding, adder-based rounding, and von Neumann rounding. Rounding deletes one or more low-order bits of the fraction and adjusts the retained bits according to a particular rounding technique.
There are several different load instructions that push different types of data onto the register stack. These include pushing a value of +1.0, pushing logarithmic values, pushing the value of π, and pushing the value of +0.0. There are also several different store instructions that pop values off the stack. These include storing operands in the BCD format or as rounded integers, storing an integer then popping the stack, and storing a truncated integer then popping the stack. Operands can also be popped off the stack and stored as floating-point values or stored as floating-point values and then pop the stack.
There are different versions of the add instruction. These include adding a stack register and a memory location then storing the sum in the register stack, or adding two stack registers and storing the sum in the register stack. There are also instructions that add, store, then pop the stack. There are similar versions of the subtract instructions. These include subtracting a memory operand from a stack register and storing the difference in the register stack, or subtracting two stack registers and storing the difference in the register stack. There are also instructions that subtract, store, then pop the stack.
There are different versions of the multiply instruction. These include multiplying a stack register and a memory location then storing the product in the register stack, or multiplying two stack registers and storing the product in the register stack. There are also instructions that multiply, store, then pop the stack. There are similar versions of the divide instructions. These include dividing a stack register by a memory operand and storing the result in the register stack, or dividing two stack registers and storing the result in the register stack. There are also instructions that divide, store, then pop the stack.
There are also several different versions of floating-point instructions that compare different types of data. One version compares an operand in the register stack with an operand in memory and sets condition code flags. Another version compares two operands in the register stack and sets the condition codes. Both versions can also compare operands, set the condition codes, then pop the stack. Another version compares operands, sets the condition codes, then pops the stack twice. There are also different versions that compare integer operands.
This chapter also contains instructions that operate on trigonometric functions, such as sine, cosine, and combined sine and cosine, which calculates both functions. There is also a partial tangent instruction, which calculates the tangent of the source operand in a stack register, then pushes a value of +1.0 onto the stack. The partial arctangent instruction is also included, which is the inverse tangent function.
There are several additional floating-point instructions that perform basic arithmetic operations and have only one syntax. Most of the previous instructions presented above have more than one syntax.
Chapter 12 provides a brief discussion on procedures. A procedure is a set of instructions that perform a specific task. They are invoked from another procedure and provide results to the calling program at the end of execution. Procedures (also called subroutines) are utilized primarily for routines that are called frequently by other procedures. The procedure routine is written only once, but used repeatedly, thereby saving storage space. Procedures permit a program to be coded in modules, thus making the program easier to code and test.
Chapter 13 discusses string instructions. A string is a sequence of bytes, words, or doublewords that are stored in contiguous locations in memory as a one-dimensional array. Strings can be processed from low addresses to high addresses or from high addresses to low addresses, depending on the state of the direction flag. If the direction flag is set, then the direction of processing is from high addresses to low addresses (auto-decrement). If the direction flag is reset, then the direction of processing is from low addresses to high addresses (auto-increment).
There are several repeat prefixes, which can be placed before the string instruction, that specify the condition for which the instruction is to be executed. The general-purpose register (E)CX specifies the number of times that the string instruction is to be executed.
The move string instructions transfer a string element — byte, word, or doubleword — from one memory location to another memory location. The load string instructions transfer a string element from a memory location to general-purpose register AL, AX, or EAX. The store string instructions transfer a string element from register AL, AX, or EAX to a destination memory location.
The compare strings instructions compare a string element in the first source operand with an equivalent size operand in the second source operand. The status flags reflect the result of the comparison. Both operands are unaltered by the comparison. The compare strings instructions are usually followed by a jump on condition instruction.
The scan strings instructions contain only one operand, which is in a generalpurpose register. The instructions compare a string element in general-purpose register AL, AX, or EAX with an equivalent size operand in a memory location. The status flags reflect the result of the comparison. Both the operand in the general-purpose register and the memory location are unaltered by the comparison.
Chapter 14 introduces arrays, which are data structures that contain a list of elements of the same data type (homogeneous) with a common name whose elements can be accessed individually. Array elements are usually stored in contiguous locations in memory, allowing easier access to the array elements. There are two main types of arrays: one-dimensional arrays and multi-dimensional arrays. A one-dimensional array — also called a linear array — is an array that is accessed by a single index. A two-dimensional array — also called a multi-dimensional array — is an array that is accessed by two indexes. One index accesses a row, the other index accesses a column. The two different types of arrays are written in assembly language only, the C programming language only, or an assembly language module embedded in a C program.
Chapter 15 introduces macros, which are segments of code that are written only once, but can be executed many times in the main program. When the macro is invoked, the assembler replaces the macro call with the macro code. The macro code is then placed in-line with the main program. Macros generally make the program more readable. Macros and procedures are similar because they both call a sequence of instructions to be executed by the main program; however, there is no CALL or RET instruction in a macro as there is in a procedure.
Chapter 16 discusses interrupts and input/output operations. When an interrupt occurs, the processor suspends operation of the current program and pushes the contents of specific registers onto the stack. Return from an interrupt is generated by the interrupt return instruction, which is similar to the procedure far return instruction.
Direct memory access is also covered, which allows an I/O device control unit to transfer data directly to or from main memory without CPU intervention. This is a much faster data transfer operation, allowing both the processor and the I/O device to operate concurrently in most cases.
Memory-mapped I/O is also discussed. For single bus machines, the same bus can be utilized for both memory and I/O devices. Therefore, I/O devices may be assigned a unique address within main memory, which is partitioned into separate areas for memory and I/O devices. Using the memory-mapped technique, I/O devices are accessed in the same way as memory locations, providing significant flexibility in managing I/O operations. Thus, there are no separate I/O instructions and the I/O devices can be accessed utilizing any of the memory read or write instructions and their addressing modes.
There are several instructions used to transfer data between an I/O device and the processor. There are two instructions that transfer data between an I/O port and general-purpose registers: IN and OUT. The IN instruction transfers data from an I/O port to register AL, AX, or EAX. The OUT instruction transfers data from register AL, AX, or EAX to an I/O port. These are referred to as register I/O instructions.
There are also two types of instructions that transfer string data between memory and an I/O port: INS and OUTS. The INS instructions transfer bytes, words, or dou-blewords of string data from an I/O port to memory. The OUTS instructions transfer bytes, words, or doublewords of string data from memory to an I/O port. These are referred to as string (block) I/O instructions. The repeat prefix may also be used to specify the condition for which the instructions are to be executed.
Chapter 17 presents additional programming examples to provide additional exposure for the reader. The examples include programs written in assembly language only, the C programming language only, and assembly language modules embedded in a C program. The various topics that are covered in the examples include logic instructions, bit test instructions, compare instructions, unconditional and conditional jump instructions, unconditional and conditional loop instructions, fixed-point instructions, floating-point instructions, string instructions, and arrays.
Appendix A lists the American Standard Code for Information Interchange (ASCII) codes for hexadecimal characters 20H through 7FH. These are provided as a reference to be used in certain chapters. Appendix B provides solutions to select problems in each chapter.
The outputs obtained from executing the programs in this book are the actual outputs obtained directly from the flat assembler or from the C compiler.
Since there are more than 330 instructions in the X86 Assembly Language, not all instructions are presented in this book — only the most commonly used instructions. For a complete listing of all the X86 assembly language instructions, refer to the following manuals: Intel 64 and IA-32 Architectures Software Developer's Manual, Volumes 2A and 2B.
It is assumed that the reader has an adequate background in C programming, digital logic design, and computer architecture. The book is designed for undergraduate students in electrical engineering, computer engineering, computer science, and software engineering; also for graduate students who require a noncredit course in X86 assembly language to supplement their program of studies.
Although this book does not utilize Verilog HDL, I would like to express my thanks to Dr. Ivan Pesic, CEO of Silvaco International, for allowing use of the SILOS Simulation Environment software for all of my books that use Verilog HDL and for his continued support.
I would like to express my appreciation and thanks to the following people who gave generously of their time and expertise to review the manuscript and submit comments: Professor Daniel W. Lewis, Department of Computer Engineering, Santa Clara University who supported me in all my endeavors; Geri Lamble; and Steve Midford. Thanks also to Nora Konopka and the staff at Taylor & Francis for their support.
X86 Assembly Language code for the figures can be downloaded at: http://www.crcpress.com/product/isbn/9781466568242
Joseph Cavanagh