Byte Accesses with the C28x CPU

From Texas Instruments Wiki
Jump to: navigation, search


The C28x is a 16-bit addressable CPU. That is, each unique address identifies 16-bits of data. 16-bit and 32-bit reads as well as writes are easily achieved via MOV and MOVL instructions. To access 8-bit data, special MOVB instructions are used. These instructions can access either the LSB or the MSB of the 16-bit word.

This article describes how these special instructions can be accessed when writing code in C or C++.

Other Resources

Reference Guides
Application Notes

Byte Accesses With the C28x Compiler

The TMS320C28x byte is 16 Bits. By ANSI/ISO C definition, the sizeof operator yields the number of bytes required to store an object. ANSI/ISO further stipulates that when sizeof is applied to char, the result is 1. Since the TMS320C28x char is 16 bits (to make it separately addressable), a byte is also 16 bits.

This yields results you may not expect; for example, size of (int) = = 1 (not 2). It should also be noted that contrary to common usage, a byte is not defined as 8 bits. A byte is defined as the unit of data capable of holding a single character on any given machine. Hence, if the term "byte" is used on C2000 it refers to 16 bits. In summary: TMS320C28x bytes and words are equivalent (16 bits).

To access data in increments of 8 bits, use the __byte() intrinsic described in the TMS320C28x Optimizing C/C++ Compiler User's Guide (spru514)

Using the __byte Intrinsic

What are Intrinsics?

The C28x compiler recognizes a number of intrinsic operators. Intrinsics allow you to express the meaning of certain assembly statements that would otherwise be cumbersome or inexpressible in C/C++. Intrinsics are used like functions; you can use C/C++ variables with these intrinsics, just as you would with any normal function.

The intrinsics are specified with a leading underscore, and are accessed by calling them as you do a function. Available intrinsics are documented in the TMS320C28x Optimizing C/C++ Compiler User's Guide (spru514). In addition, consult the release notes for your compiler to see if new intrinsics have been added since the documentation was last updated.

The __byte() Intrinsic

Prototype int __&byte(int *array, unsigned int byte_index);
Generated Assembly:
  • If the operation is a write: MOVB array[byte_index].LSB,
  • If the operation is a read: MOVB dst, array[byte_index].LSB

Example 1

The following example shows how the compiler will translate the __byte intrinsic to a MOVB.LSB operation.

// Example code to show the use of the __byte C28x Compiler intrinsic
int16 MyArray[20];
int16 Val;
int16 Read1;
int16 Read2;
int16 Read3;
int16 Read4;
void main(void)
                                  // Note: - means value was unchanged
                                  //         from the previous state
   __byte(MyArray,0) = 0x00;      // MyArray[0] = 0x--00
   __byte(MyArray,1) = 0x11;      // MyArray[0] = 0x1100
   __byte(MyArray,2) = 0x22;      // MyArray[1] = 0x--22
   __byte(MyArray,3) = 0x33;      // MyArray[1] = 0x3322
   __byte(MyArray,4) = 0x44;      // MyArray[2] = 0x--44
   __byte(MyArray,5) = 0x55;      // MyArray[2] = 0x5544
   __byte(&Val,0) = 0x66;         // Val1 = 0x--66
   __byte(&Val,1) = 0x77;         // Val1 = 0x7766
   Read1 = __byte(MyArray,2);     // Read1 = 0x0022 (note: clears upper byte)
   Read2 = __byte(MyArray,5);     // Read2 = 0x0055 (note: clears upper byte)
   Read3 = __mov_byte(MyArray,4); // Read3 = 0x0044 (note: clears upper byte)
   Read4 = __mov_byte(MyArray,5); // Read4 = 0x0055 (note: clears upper byte)

Execution of this code will change memory contents as shown below:


Example 2

The following example illustrates how __byte can be used on the left-hand side to set values. This is the reason it returns a reference.

#include <stdio.h>
int array[3] = { 0xaaaa, 0xbbbb, 0xcccc };
void main()
    int i;
    __byte(array, 0) = 0;
    for (i=0;i<3;i++)
        printf("%x\n", array[i]);
    for (i=0;i<6;i++)
        printf("%x\n", __mov_byte(array, i));

Byte Addressing Modes

For the most part you do not need to worry about the addressing modes when using the intrinsics. It is useful to understand if you are looking at the generated assembly code or writing assembly code from scratch.

Example 1 shown in the previous section generates the following assembly code:


The instructions generated by the compiler follow the byte addressing mode rules shown below. More detail and additional forms of MOVB can be found in the TMS320C28x CPU and Instruction Set Reference Guide (spru430).

Instruction: MOVB AX.LSB, loc16
Offset: AR0, AR1 or 3-bit value
if( loc16 == *+XARn[offset] )
    if( offset == even )
        AX.LSB = [loc16].LSB;
        AX.MSB = 0x00;
    if( offset == odd )
        AX.LSB = [loc16].MSB;
        AX.MSB = 0x00;
    AX.LSB = [loc16].LSB;
    AX.MSB = 0x00;
Instruction: MOVB loc16,AX.LSB
Offset: AR0, AR1 or 3-bit value
if( loc16 == *+XARn[offset] )
    if( offset == even )
        [loc16].LSB = AX.LSB
        [loc16].MSB = untouched;
    if( offset == odd )
        [loc16].LSB = untouched;
        [loc16].MSB = AX.LSB;
    [loc16].LSB = AX.LSB;
    [loc16].MSB = untouched;

Frequently Asked Questions

Q: The prototype for __byte is &__byte(int *array, unsigned int byte_index). Is the "&" a typo?

A: It's not a typo, the __byte intrinsic returns a C++-style reference, even in C mode.

Q: What is the difference between __byte() and __mov_byte()

A: The __byte() intrinsic is preferred over __mov_byte.
__mov_byte is much older, and was implemented before the compiler could support an intrinsic that returns a reference. To maintain backward compatibility, __mov_byte was left as-is, and a new intrinsic (__byte) was added which returns the reference. There is nothing you can do with __mov_byte that you can't do with __byte.