With Big Endian ordering, the address of a multiple-byte data type is of its most significant byte (its "big end"), whereas with Little Endian ordering, the address is of its least significant byte (its "little end"). This is shown in Figure A.14. For structures declared in a high-level language, the order of bytes in memory will differ depending on the byte ordering and the particular data type, as shown for a C structure in Figure A.15.
Figure A.15 Big and Little Endian Byte-Ordering Example
Code Big Endian Little Endian Memory Contents Memory Contents
struct { long a; short b[2]; char c[4]; } S1 = { /* Big Endian Little Endian */ 0x12345678, /* 12 34 56 78 78 56 34 12 */ 0x1234,0x5678, /* 12 34 56 78 34 12 78 56 */ "ABC" /* 41 42 43 00 41 42 43 00 */ };
Note that Endianness only affects the operation of the load and store instructions, which means when data is moved between memory or peripheral devices and registers.
Except in cases where the host and target have the same byte-ordering convention, you must explicitly define the required Endianness of the target code using the appropriate command-line option (EB or EL). This will override the host's default byte ordering. For example,
pmcc -EB -o prog prog.c pmcc -EL -o prog prog.c
To write code that can be compiled/assembled to execute correctly in either a Big or a Little Endian target environment, the user must follow one basic rule: either eliminate Endian-specific code or enclose it with #ifdef/else statements, using the appropriate preprocessor flag (MIPSEB or MIPSEL). Endian-specific code is produced whenever the data type implicit in the instruction (e.g., "byte" in load byte) differs from the data type of the accessed data, such as using four load-byte instructions to read a single word or using a store-word instruction to store 4 bytes. I/O addresses are also Endian-specific, because a peripheral device is hardwired to a specific part of the data bus (typically D0-D7).
The example programs main.c and asm.s print Endian-sensitive values returned from the functions end1 and end2 and the contents of two Endian-sensitive I/O locations, SIOCNTL and SIODATA. For purposes of illustration, the functions return correct results when passed the value 0 and incorrect results when passed the value 1. The programs are shown in their entirety in the final section of this appendix and are discussed below.
The C program defines two base addresses for the peripheral device (SIOBASE) and uses the preprocessor variable MIPSEB (Big Endian) to select between the two byte-ordering conventions.
1 #if ((defined MIPSEB) || (defined PPCEB)) 2 #define SIOBASE 0xbe000003 /* Big Endian base address */ 3 #else 4 #define SIOBASE 0xbe000000 /* Little Endian base address */ 5 #endif 6 #define SIOCNTL *((volatile unsigned char *)SIOBASE+4) 7 #define SIODATA *((volatile unsigned char *)SIOBASE+12)
The function main prints the results of the functions end1 and end2. Incorrect results are produced by end1 because when the function is passed a 1, it reads the word as a series of 4 bytes, rather than as a single word. Starting at the word address, each byte is read into the variable v, such that the lowest addressed byte ends up in the most significant byte of the variable. This is the correct byte ordering for Big Endian but incorrect for Little Endian:
19 for (i=0;i<4;i++) { 20 v <<="8;" 21 v |="p[i];" 22 } 23 }
For a MIPS CPU, incorrect results are produced by end2 because the lwl and lwr instructions are used to access unaligned data; the non-Endian-sensitive solution uses the ulw instruction: (PowerPC results are similar)
8 end2: la t0,dat2 9 beq a0,zero,1f 10 nop 11 lwl v0,0(t0) 12 lwr v0,3(t0) 13 b 2f 14 nop 15 1: ulw v0,(t0) 16 2: jr ra
For Big Endian the program prints:
12345678 12345678 12345678 12345678 00 3b
and for Little Endian it prints:
12345678 78563412 12345678 78345612 00 3b
Keep in mind that a program's binary cannot be converted from one byte-ordering convention to another by simply swapping all the bytes. This would produce correct results only if all of the program's data were of the same type (size) and the same size as the instructions. To change the byte ordering, programs must be recompiled using the appropriate compiler option.
A.7 PROGRAM LISTINGS
main.c: 1 #if ((defined MIPSEB) || (defined PPCEB)) 2 #define SIOBASE 0xbe000003 /* Big Endian base address */ 3 #else 4 #define SIOBASE 0xbe000000 /* Little Endian base address */ 5 #endif 6 #define SIOCNTL *((volatile unsigned char *)SIOBASE+4) 7 #define SIODATA *((volatile unsigned char *)SIOBASE+12) 8 int dat1 = 0x12345678; 9 end1(n) 10 int n; 11 { 12 int i,v; 13 unsigned char *p; 14 /* set up a pointer into the data */ 15 p = (unsigned char *)&dat1; 16 if (n==0) v = dat1; 17 else { 18 v = 0; 19 for (i=0;i<4;i++) { 20 v <<= 8; 21 v |= p[i]; 22 } 23 } 24 return(v); 25 } 26 main() 27 { 28 printf("%08x %08x\n"); 29 printf("%08x %08x\n"); 30 printf("%02x %02x\n"); 31 } asm.s: 1 #include "mips.h" 2 .data 3 dat2: .word 0x12345678 4 .text 5 .globl end2 6 .ent end2 7 .set noreorder 8 end2: la t0,dat2 9 beq a0,zero,1f 10 nop 11 lwl v0,0(t0) 12 lwr v0,3(t0) 13 b 2f 14 nop 15 1: ulw v0,(t0) 16 2: jr ra 17 nop 18 .end end2[EGG] Danny Cohen, "On Holy Wars and a Plea for Peace," IEEE Computer, Oct. 1981, pp. 48-54.
Navigation: Document Home | Document Contents | Document Index