Author Topic: Coldfire BYTEREV instruction  (Read 8169 times)

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3236
    • View Profile
    • uTasker
Coldfire BYTEREV instruction
« on: March 21, 2009, 10:06:01 PM »
Hi All

The Coldfire has a few interesting instructions which are not seen when programming exclusively in C. To make use of them it is sometimes necessary to write some assembler code.

The BYTEREV is such a command. It reverses the bytes in a register:

Dx[31:24] -> D[7:0]
Dx[23:16] -> D[15:8]
Dx[15:8] -> D[23:16]
Dx[7:0] -> D[31:24]

where x is one of the data registers (eg. D0).

This is effectively the same as the following C-code does:

x = ((x >> 24) | ((x >> 8 ) & 0x0000ff00) | ((x << 8 ) & 0x00ff0000) | ((x << 24) & 0xff000000));

This code can be used to swap addresses between little and big-endian format and looks like this when compiled on the Coldfire, where the original address (32 bit word) is in d5 and the final converted address is in d0:

00001F38: 2605            move.l   d5,d3
00001F3A: E08B            lsr.l    #8,d3
00001F3C: 7E18            moveq    #24,d7
00001F3E: 2405            move.l   d5,d2
00001F40: EEAA            lsr.l    d7,d2
00001F42: 02830000FF00    andi.l   #0xff00,d3
00001F48: 8483            or.l     d3,d2
00001F4A: 2605            move.l   d5,d3
00001F4C: E18B            lsl.l    #8,d3
00001F4E: 028300FF0000    andi.l   #0xff0000,d3
00001F54: EFAD            lsl.l    d7,d5
00001F56: 8483            or.l     d3,d2
00001F58: 0285FF000000    andi.l   #0xff000000,d5
00001F5E: 8485            or.l     d5,d2


Compared to the single instruction byterev.l d0 it is obviously rather less efficient. But compilers generally will not detect that this can be simplified and the longer form results in real code.

Now it is interesting to note that the USB controller in the Coldfire operates in little-endian mode, whereas the Coldfire CPU itself operates in big-endian mode [more details about this can be found in the USB user's guide in appendix A at http://www.utasker.com/docs/uTasker/USB_User_Guide.PDF]. This requires the driver code to convert between the two when collecting USB messages from the input buffers (OUT tokens) or preparing new transmit messages (IN tokens) and so the BYTEREV instruction was an opportunity to make this interface a bit more efficient.

Here's how it was done (this will be included in the next service pack or can be tried yourselves by making the quite small changes below).

1) In Startup.s add a new routine called byte_rev()

byte_rev:                                                                // sub routine for reversing bytes in a long word
_byte_rev:
    byterev.l d0   
    rts


2) In M5223x.c change the routine fnLE_add() to call this assembler sub-routine instead of doing the 'long-winded' C-version:

    ulLE_long_word = ((long_word >> 24) | ((long_word >> 8 ) & 0x0000ff00) | ((long_word << 8 ) & 0x00ff0000) | ((long_word << 24) & 0xff000000));
    return (void *)ulLE_long_word;


becomes
    return (void *)byte_rev(long_word);

where the prototype
extern unsigned long byte_rev(unsigned long); will also be required.

Even with the sub-routine call, this reduces each conversion from originally about 14 instructions down to just 4 instructions and makes the USB driver operation that bit more efficient!

Regards

Mark




« Last Edit: March 21, 2009, 10:51:12 PM by mark »