This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Bitwise logical AND stores in a bit ....

Hi,

I get a curious result when compiling such a following code :

typedef union  {

  unsigned char                cCtrlFullByte;

  struct {
    unsigned char  bEnable          : 1;
    unsigned char  cUnused          : 7;

  }  cCtrlStruct;
} CtrlUnion;

void main (void)  {

    unsigned char  dummy = 0x55;
    CtrlUnion  xdata    bitUnion;

    bitUnion.cCtrlStruct.bEnable = dummy & 0x40;

    return;
}


It results in :
MOV A,#0x55
ANL A,#0x00
MOV R7,A
MOV DPTR, #0x0000
MOVX A,@DPTR
ANL A,#0xFE
ORL A,R7
MOVX @DPTR, A 

I thought that the bit result of bitwise logical AND is 1 if result is not 0, else 0.
It seems that I didn't understand ANSI the same way than Keil compiler ? Am I wrong ?

Arnaud DELEULE

Parents
  • Graham,

    Those are all good ideas. Bit variables, are unfortunately, and oddity of the 8051 (and C16x) architecture. They are only included in the compiler because SETB and CLR instructions are faster than the read-modify-write equivalents.

    As for using the CLR and SETB instructions on bitfields, I'm not sure that this would actually improve the performance. If you consider that the unsigned char/int of the bitfield must be read and written back, that is 2 instructions. A set (LOGICAL OR) operation only requires 1 instruction to set up to 8 bits in a byte. And that would be faster than using several SETB instructions. For the case of setting a single bit, the code generated would be the same.

    A clear operation (LOGICAL AND) is the same--only one instruction is required to clear 1-8 bits.

    A clear and set operation (LOGICAL AND plus LOGICAL OR) requires 2 instructions, the AND mask and the OR mask--but only for multi-bit bitfields.

    Anyway, we'll keep looking at this to see if we can improve code generation for these types of operations.

    As for the bit typing, you can use the BDATA memory space to place typed variables in the bit-addressable area. But I'm not sure this is what you want.

    Jon

Reply
  • Graham,

    Those are all good ideas. Bit variables, are unfortunately, and oddity of the 8051 (and C16x) architecture. They are only included in the compiler because SETB and CLR instructions are faster than the read-modify-write equivalents.

    As for using the CLR and SETB instructions on bitfields, I'm not sure that this would actually improve the performance. If you consider that the unsigned char/int of the bitfield must be read and written back, that is 2 instructions. A set (LOGICAL OR) operation only requires 1 instruction to set up to 8 bits in a byte. And that would be faster than using several SETB instructions. For the case of setting a single bit, the code generated would be the same.

    A clear operation (LOGICAL AND) is the same--only one instruction is required to clear 1-8 bits.

    A clear and set operation (LOGICAL AND plus LOGICAL OR) requires 2 instructions, the AND mask and the OR mask--but only for multi-bit bitfields.

    Anyway, we'll keep looking at this to see if we can improve code generation for these types of operations.

    As for the bit typing, you can use the BDATA memory space to place typed variables in the bit-addressable area. But I'm not sure this is what you want.

    Jon

Children
  • Jon, thanks for your comments.

    My apologies to Arnaud for high jacking his thread.

    At the risk of becoming tedious, I decided to do some experiments which have concluded with the following item of test code.

    The experiment has convinced me that bit fields are not accessed efficiently.

    I have been able to go and look at our latest application, it is about 60K bytes long and contains a number of modules doing a wide variety of things and written by a number of different programmers. I searched for the simple case of testing a 1-bit bit field in a simple if statement, I found just under 500 instances. An average saving of just 3 instructions in each case would give a code reduction of 2.5% which I think is significant.

    There are in the 60K application, at least as many logical operations as arithmetic operations – quite possibly more. Many of them involve 1-bit bit fields. Many things in the same application are achieved by means of masks whereas using bit fields would be more elegant, but currently will generate slower code.

    typedef enum { FALSE, TRUE } boolean;
    
    typedef struct
    {
    	boolean		bit0	: 1;
    	boolean		bit1	: 1;
    	boolean		bit2	: 1;
    	boolean		bit3	: 1;
    	boolean		bit4	: 1;
    	boolean		bit5	: 1;
    	boolean		bit6	: 1;
    	boolean		bit7	: 1;
    } bit_field_type;
    
    extern bit do_stuff();
    
    main( void )
    {
    	bit 	/*boolean*/ 	r, s, t;
    	data	boolean			x;
    
    	bit_field_type 			byte;
    
    	r = s | t;
    	r = s || t;
    
    	byte.bit0 = TRUE;
    	byte.bit1 = r;
    
    	s = byte.bit2;
    
    	byte.bit2 = s | !t;
    	byte.bit7 = byte.bit5 && byte.bit6;
    
    	if( byte.bit3 )
    	{
    		r = TRUE;
    	}
    }
    
    This:
    	r = s | t;
    
    Gave me this
                                               ; SOURCE LINE # 25
    0000 A200        R     MOV     C,t
    0002 7200        R     ORL     C,s
    0004 9200        R     MOV     r,C
     
    But this:
    	r = s || t;
    
    Gave me this:
    0006 200003      R     JB      s,?C0003
    0009 300003      R     JNB     t,?C0001
    000C         ?C0003:
    000C D3                SETB    C
    000D 8001              SJMP    ?C0002
    000F         ?C0001:
    000F C3                CLR     C
    0010         ?C0002:
    0010 9200        R     MOV     r,C
    I was surprised because I expected that the logical operator would not only be the correct choice when a logical result was required, but also the most efficient. I always use the logical operator in my code where it is appropriate – so there is an opportunity for more compact code here.

    This:
    	byte.bit0 = TRUE;
    
    Gave me this:
                                               ; SOURCE LINE # 28
    0012 E500        R     MOV     A,byte
    0014 4401              ORL     A,#01H
    0016 F500        R     MOV     byte,A
    
    That is a read-modify-write. Where the bit field is in data memory this could be reduced to ORL byte,#01H.

    This:
    	byte.bit1 = r;
    
    Gave me this:
                                              ; SOURCE LINE # 29
    0018 A200        R     MOV     C,r
    001A E4                CLR     A
    001B 33                RLC     A
    001C 5401              ANL     A,#01H
    001E FF                MOV     R7,A
    001F 25E0              ADD     A,ACC
    0021 FF                MOV     R7,A
    0022 E500        R     MOV     A,byte
    0024 54FD              ANL     A,#0FDH
    0026 4F                ORL     A,R7
    0027 F500        R     MOV     byte,A
    
    But the same could have been done with less than half as much code:
    0018 A200        R     MOV     C,r
    001A E500        R     MOV     A,byte
    001C 54FD              MOV     Acc.1,C
    001E F500        R     MOV     byte,A
    
    This:
    	if( byte.bit3 )
    
    Gave me this:
                                               ; SOURCE LINE # 36
    006F FF                MOV     R7,A
    0070 13                RRC     A
    0071 13                RRC     A
    0072 13                RRC     A
    0073 541F              ANL     A,#01FH
    0075 30E002            JNB     ACC.0,?C0007
    
    But all that is needed is something like this:
    006F E500        R     MOV     A,byte
    0071 30E002            JNB     ACC.3,?C0007