ECP4166: Advanced Microprocessors AM2

AM2: Protected-Mode Programming

Duration: 3 hours

Components: Lab exercises and report.

Objectives:

(a)  To examine how protected-mode programming is done on x86 based computer.

(b)  To evaluate the advantages of protected-mode programming compared to real-mode programming in x86 microprocessor.

(c)  To develop protected-mode assembly language programs which utilize the floating-point unit (FPU) on x86 based computer.

(d)  To develop Win32 Console programs based on x86 assembly language.

1. Floating-Point Unit

The Intel 8086 processor was designed to handle only integer arithmetic. This turned out to be a problem for graphics and calculation-intensive software using floating-point calculations. It was possible to emulate floating-point arithmetic purely through software, but the performance penalty was severe. Programs such as AutoCad (by Autodesk) demanded a more powerful way to perform floating-point math. Intel sold a separate floating-point coprocessor chip named the 8087, and upgraded it along with each processor generation. Beginning from Intel 80486, the floating-point hardware was integrated into the processor and known as the floating-point unit (FPU).

1.1 FPU Register Stack

The FPU does not use the general-purpose registers (EAX, EBX, etc.). Instead, it has its own set of registers called a register stack. It loads values from memory into the register stack, performs calculations, and stores stack values into memory. FPU instructions evaluate mathematical expressions in postfix format. The following, for example, is called an infix expression: (5 * 6) + 4. The postfix equivalent is

5 6 * 4 +

The infix expression (A + B) * C requires parenthesis to override the default precedence rules (multiplication before addition). The parenthesis is not required in the equivalent postfix expression:

A B + C *

1.1.1 Expression Stack

A stack holds intermediate values during the evaluation of postfix expressions. Figure 1.1 shows the steps required to evaluate the postfix expression 5 6 * 4 –. The stack entries are labelled ST(0) and ST(1), with ST(0) indicating where the stack pointer would normally be pointing.

Figure 1.1: Evaluating the Postfix Expression 5 6 * 4 – .

Table 1.1 contains some examples of translating infix to equivalent postfix expressions.

Table 1.1 Infix to Postfix Examples

Infix / Postfix
A + B / A B +
(A - B) / D / A B - D /
(A + B) * (C + D) / A B + C D + *
((A + B) / C) * (E - F) / AB + C / EF - *

1.2 FPU Data Registers

The FPU has eight individually addressable 80-bit data registers named R0 through R7 (see Figure 2). Together, they are called a register stack. A three-bit field named TOP in the FPU status word identifies the register number that is currently the top of the stack. In Figure 1.2, for example, TOP equals binary 011, identifying R3 as the top of the stack. This stack location is also known as ST(0) (or simply ST) when writing floating-point instructions. The last register is ST(7).

Figure 1.2: Floating-Point Data Register Stack.

As we might expect, a push operation (also called load) decrements TOP by 1 and copies an operand into the register identified as ST(0). If TOP equals 0 before a push, TOP wraps around to register R7. A pop operation (also called store) copies the data at ST(0) into an operand, then adds 1 to TOP. If TOP equals 7 before the pop, it wraps around to register R0. If loading a value into the stack would result in overwriting existing data in the register stack, a floating-point exception is generated. Figure 1.3 shows the same stack after 1.0 and 2.0 have been pushed (loaded) on the stack.

Figure 1.3: FPU Stack after Pushing 1.0 and 2.0.

Although it is interesting to understand how the FPU implements the stack using a limited set of registers, we need only focus on the ST(n) notation, where ST(0) is always the top of stack. From this point forward, we refer to stack registers as ST(0), ST(1), and so on. Instruction operands cannot refer directly to register numbers.

Floating-point values in registers use the IEEE 10-byte extended real format (also known as temporary real). When the FPU stores the result of an arithmetic operation in memory, it translates the result into one of the following formats: integer, long integer, single precision (short real), double precision (long real), or packed binary-coded decimal (BCD).

1.3 Floating-Point Instruction Set

The FPU instruction set is somewhat complex, so we will attempt here to give you an overview of its capabilities, along with specific examples that demonstrate code typically generated by compilers. In addition, we will see how you can exercise control over the FPU by changing its rounding mode. The instruction set contains the following basic categories of instructions:

• Data transfer

• Basic arithmetic

• Comparison

• Transcendental

• Load constants (specialized predefined constants only)

• x87 FPU control

• x87 FPU and SIMD state management

Floating-point instruction names begin with the letter F to distinguish them from CPU instructions. The second letter of the instruction mnemonic (often B or I) indicates how a memory operand is to be interpreted: B indicates a BCD operand, and I indicates a binary integer operand. If neither is specified, the memory operand is assumed to be in real-number format. For example, FBLD operates on BCD numbers, FILD operates on integers, and FLD operates on real numbers.

1.3.1 Operands

A floating-point instruction can have zero operands, one operand, or two operands. If there are two operands, one must be a floating-point register. There are no immediate operands, but certain predefined constants (such as 0.0, _, and log2 10) can be loaded into the stack. General-purpose registers such as EAX, EBX, ECX, and EDX cannot be operands. (The only exception is FSTSW, which stores the FPU status word in AX.) Memory-to-memory operations are not permitted. Integer operands must be loaded into the FPU from memory (never from CPU registers); they are automatically converted to floating-point format. Similarly, when storing floating-point values into integer memory operands, the values are automatically truncated or rounded into integers.

1.3.2 Initialization (FINIT)

The FINIT instruction initializes the FPU. It sets the FPU control word to 037Fh, which masks (hides) all floating-point exceptions, sets rounding to nearest even, and sets the calculation precision to 64 bits. We recommend calling FINIT at the beginning of your programs, so you know the starting state of the processor.

1.3.3 Floating-Point Data Types

Let’s quickly review the floating-point data types supported by MASM (QWORD, TBYTE, REAL4, REAL8, and REAL10), listed in Table 1.2. You will need to use these types when defining memory operands for FPU instructions. For example, when loading a floating-point variable into the FPU stack, the variable is defined as REAL4, REAL8, or REAL10:

.data

bigVal REAL10 1.212342342234234243E+864

.code

fld bigVal ; load variable into stack

Table 1.2: Intrinsic Data Types.

Type / Usage
QWORD / 64-bit integer
TBYTE / 80-bit (10-byte) integer
REAL4 / 32-bit (4-byte) IEEE short real
REAL8 / 64-bit (8-byte) IEEE long real
REAL10 / 80-bit (10-byte) IEEE extended real

1.3.4 Reading and Writing Floating-Point Values

In this experiment, the following two procedures for floating-point input-output (created by William Barrett of San Jose State University) are used:

ReadFloat: Reads a floating-point value from the keyboard and pushes it on the floating-point stack. It accepts a wide variety of floating-point formats. Some examples are shown below:

35 / 3.5E005
+35. / -3.5E+5
-3.5 / 3.5E-4
.35 / +3.5E-4
3.5E5

WriteFloat: Writes the floating-point value at ST(0) to the console window in exponential format.

ShowFPUStack: Another useful procedure, written by James Brink of Pacific Lutheran University, displays the FPU stack. It is called with no parameters:

call ShowFPUStack

Example 1.1

The following example program pushes two floating-point values on the FPU stack, displays it, inputs two values from the user, multiplies them, and displays their product:

TITLE Example 1.1 (ex11.asm)
INCLUDE Irvine32.inc
INCLUDE macros.inc
.data
first REAL8 123.456
second REAL8 10.0
third REAL8 ?
.code
main PROC
finit ; initialize FPU
; Push two floats and display the FPU stack.
fld first
fld second
call ShowFPUStack
; Input two floats and display their product.
mWrite "Please enter a real number: "
call ReadFloat
mWrite "Please enter a real number: "
call ReadFloat
fmul ST(0),ST(1) ; multiply
mWrite "Their product is: "
call WriteFloat
call Crlf
exit
main ENDP
END main

Note: Please refer to Appendix A on how to build and run this program.

Sample input/output (user input shown in bold type):

------FPU Stack ------
ST(0): +1.0000000E+001
ST(1): +1.2345600E+002
Please enter a real number: 3.5
Please enter a real number: 4.2
Their product is: +1.4700000E+001

Example 1.2

Let’s code the expression valD = -valA + (valB * valC). A possible step-by-step solution is: Load valA on the stack and negate it. Load valB into ST(0), moving valA down to ST(1). Multiply ST(0) by valC, leaving the product in ST(0). Add ST(1) and ST(0) and store the sum in valD:

TITLE Example 1.2 (ex12.asm)
INCLUDE Irvine32.inc
INCLUDE macros.inc
.data
valA REAL8 1.5
valB REAL8 2.5
valC REAL8 3.0
valD REAL8 ? ; +6.0
.code
main PROC
finit
fld valA ; ST(0) = valA
fchs ; change sign of ST(0)
fld valB ; load valB into ST(0)
fmul valC ; ST(0) *= valC
fadd ; ST(0) += ST(1)
fstp valD ; store ST(0) to valD
exit
main ENDP
END main

Please refer to Appendix B on how to debug this program.

Exercise 1.1

Write a program that prompts the user for the radius of a circle. Calculate and display the circle’s circumference and area. Use the ReadFloat and WriteFloat procedures. Use the FLDPI instruction to load p onto the register stack. Please refer to Appendix A on how to create, build and run your program.

Exercise 1.2

Write a program that asks the user to enter the X and Y coordinates of two points on a straight line. Calculate and display the gradient of the straight line. Use the ReadFloat and WriteFloat procedures in your program.

2. Win32 Console Programming

On the surface, 32-bit console mode programs look and behave like 16-bit MS-DOS programs running in text mode. There are differences, however: The former runs in 32-bit protected mode, whereas MS-DOS programs run in real-address mode. They use different function libraries. Win32 programs call functions from the same library used by graphical Windows applications. MS-DOS programs use BIOS and MS-DOS interrupts that have existed since the introduction of the IBM-PC.

2.1 Application Programming Interface

An Application Programming Interface (API) is a collection of types, constants, and functions that provide a way to directly manipulate objects through programming. Therefore, the Win32 API lets you tap into the functions in the 32-bit version of MS-Windows. The Irvine32 link library used in this experiment is completely built on Win32 console functions. It is compatible with Win32 API functions and can be used for basic input output, simulations, timing, and other useful operations. Table 2.1 gives a complete list of procedures in the Irvine32 link library.

Table 2.1: Procedures in the Irvine32 link library

Procedure / Description
CloseFile / Closes a disk file that was previously opened.
Clrscr / Clears the console window and locates the cursor at the upper left corner.
CreateOutputFile / Creates a new disk file for writing in output mode.
Crlf / Writes an end-of-line sequence to the console window.
Delay / Pauses the program execution for a specified n -millisecond interval.
DumpMem / Writes a block of memory to the console window in hexadecimal.
DumpRegs / Displays the EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP, EFLAGS, and EIP registers in hexadecimal. Also displays the most common CPU status flags.
GetCommandTail / Copies the program’s command-line arguments (called the command tail) into an array of bytes.
GetDateTime / Gets the current date and time from the system.
GetMaxXY / Gets the number of columns and rows in the console window’s buffer.
GetMseconds / Returns the number of milliseconds elapsed since midnight.
GetTextColor / Returns the active foreground and background text colors in the console window.
Gotoxy / Locates the cursor at a specific row and column in the console window.
IsDigit / Sets the Zero flag if the AL register contains the ASCII code for a decimal digit (0–9).
MsgBox / Displays a popup message box.
MsgBoxAsk / Display a yes/no question in a popup message box.
OpenInputFile / Opens an existing disk file for input.
ParseDecimal32 / Converts an unsigned decimal integer string to 32-bit binary.
ParseInteger32 / Converts a signed decimal integer string to 32-bit binary.
Random32 / Generates a 32-bit pseudorandom integer in the range 0 to FFFFFFFFh.
Randomize / Seeds the random number generator with a unique value.
RandomRange / Generates a pseudorandom integer within a specified range.
ReadChar / Waits for a single character to be typed at the keyboard and returns the character.
ReadDec / Reads an unsigned 32-bit decimal integer from the keyboard, terminated by the Enter key.
ReadFromFile / Reads an input disk file into a buffer.
ReadHex / Reads a 32-bit hexadecimal integer from the keyboard, terminated by the Enter key.
ReadInt / Reads a 32-bit signed decimal integer from the keyboard, terminated by the Enter key.
ReadKey / Reads a character from the keyboard’s input buffer without waiting for input.
ReadString / Reads a string from the keyboard, terminated by the Enter key.
SetTextColor / Sets the foreground and background colors of all subsequent text output to the console.
Str_compare / Compares two strings.
Str_copy / Copies a source string to a destination string.
Str_length / Returns the length of a string in EAX.
Str_trim / Removes unwanted characters from a string.
Str_ucase / Converts a string to uppercase letters.
WaitMsg / Displays a message and waits for a key to be pressed.
WriteBin / Writes an unsigned 32-bit integer to the console window in ASCII binary format.
WriteBinB / Writes a binary integer to the console window in byte, word, or doubleword format.
WriteChar / Writes a single character to the console window.
WriteDec / Writes an unsigned 32-bit integer to the console window in decimal format.
WriteHex / Writes a 32-bit integer to the console window in hexadecimal format.
WriteHexB / Writes a byte, word, or doubleword integer to the console window in hexadecimal format
WriteInt / Writes a signed 32-bit integer to the console window in decimal format.
WriteStackFrame / Writes the current procedure’s stack frame to the console.
WriteStackFrameName / Writes the current procedure’s name and stack frame to the console.
WriteString / Writes a null-terminated string to the console window.
WriteToFile / Writes a buffer to an output file.
WriteWindowsMsg / Displays a string containing the most recent error generated by MS-Windows.

Some of the procedures that are used in this experiment are described in the following sections.