BIT instruction?

Programming the 6502 microprocessor and its relatives in assembly and other languages.
Post Reply
Meterman58761
Posts: 19
Joined: 05 Jun 2021

BIT instruction?

Post by Meterman58761 »

So, I've never really been clear on the BIT instruction, as I so seldom encountered this opcode when poking around in assembly language back in my C64 days.

Are there any real-world code examples that would illustrate how this works?

I have at hand some code that could stand a slight rewrite, and I am wondering if a BIT instruction would be useful for that scenario.

Right now, in several places, a number is tested bit by bit, by doing an ASL or LSR followed by a BCC or BCS, based on whether a 0 or 1 is desired, then repeating the process for the next bit until all the bits have been shifted through the carry.

Seeing as the carry bit doesn't translate so well into modern high-level languages, I want to see if there are other usable approaches that don't take up so many bytes in assembly language, but readily translate into newer languages.

Now, I could just use AND to test the individual bits, but that means I have to reload the accumulator each time before testing a given bit, and that just adds to code length.

So, is BIT just a non-destructive AND?

In other words, if I do BIT #$08, and comes back 0, and I want to check for 0, I use BEQ? Then I can follow with a BIT #$04 and continue evaluating that way?
User avatar
BigDumbDinosaur
Posts: 9426
Joined: 28 May 2009
Location: Midwestern USA (JB Pritzker’s dystopia)
Contact:

Re: BIT instruction?

Post by BigDumbDinosaur »

The BIT instruction performs a logical AND between the accumulator and memory, setting or clearing status register flags, but not storing the result of the logical AND.

Code: Select all

          LDA #%01010000
          AND uart_isr
          BEQ is_zero

is logically equivalent to:

Code: Select all

          LDA #%01010000
          BIT uart_isr
          BEQ is_zero

the difference being the second example doesn't modify the accumulator the way the first example does. The branch to is_zero will only be taken if ACCUMULATOR & MEMORY would result in $00.

BIT on the NMOS 6502 has only two addressing modes. On the 65C02 and 65C816, new addressing modes are <zp>,X and <abs>,X, plus immediate mode:

Code: Select all

          LDA uart_isr
          BIT #%01010000
          BEQ is_zero

which may be more convenient at times.

When BIT is used on memory, as in the second example above, bits 6 and 7 of the memory location being tested are copied to the status register's V and N flags, respectively. The Z flag will always reflect the result of the logical AND operation.
x86?  We ain't got no x86.  We don't NEED no stinking x86!
User avatar
BigEd
Posts: 11464
Joined: 11 Dec 2008
Location: England
Contact:

Re: BIT instruction?

Post by BigEd »

Meterman58761 wrote:
So, is BIT just a non-destructive AND?
Very very nearly! (The difference being, as BDD notes, that two handy flags are set directly... except when used with an immediate.)
User avatar
GARTHWILSON
Forum Moderator
Posts: 8773
Joined: 30 Aug 2002
Location: Southern California
Contact:

Re: BIT instruction?

Post by GARTHWILSON »

BIT is really nice for testing port bits. If you will need to quickly test an I/O line like for the data line when you're bit-banging a synchronous-serial connection, put it on bit 6 or bit 7 of a parallel port, so you can just do:

Code: Select all

        BIT  PORT_A
        BMI  ____        ; (or BPL or BVC or BVS, as appropriate)

without regard for what's in the accumulator, nor affecting the accumulator.
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
User avatar
BigEd
Posts: 11464
Joined: 11 Dec 2008
Location: England
Contact:

Re: BIT instruction?

Post by BigEd »

This is, surely, the reason why the peripheral chips we use often present a useful overall status bit in bit 7.
Meterman58761
Posts: 19
Joined: 05 Jun 2021

Re: BIT instruction?

Post by Meterman58761 »

Here's a snippet showing what I mean about using the carry bit to evaluate a number and if I understand BIT based on what you guys say:

Code: Select all

Relay status nybble: 0 = contacts closed, 1 = contacts open

Existing code:

LB_ECC0	LDA A	$C3		//	fetch status nybble
		LSR A				//	shift Relay A bit into carry
		BCS		LB_ECCA	//	contact opened like it was supposed to
		JMP		LB_EE31	//	jump to test fail: Relay A
LB_ECCA	LDA B	#$07	//	Write pass value to Relay A register
		STA B	$0CEA
		LSR A			//	shift Relay B bit into carry
		BCS		LB_ECD5		//	contact opened like it was supposed to
		JMP		LB_EE67	//	jump to test fail: Relay B
LB_ECD5	LDA B	#$07	//	Write pass value to Relay B register
		STA B	$0CEB
		LSR A			//	shift Relay C bit into carry
		BCS		LB_ECE0		//	contact opened like it was supposed to
		JMP		LB_EE6F	//	jump to test fail: Relay C
LB_ECE0	LDA B	#$07	//	Write pass value to Relay C register
		STA B	$0CEC
		LSR A			//	shift Relay D bit into carry
		BCS		LB_ECEB		//	contact opened like it was supposed to
		JMP		LB_EE77	//	jump to test fail: Relay D
LB_ECEB	LDA B	#$07	//	Write pass value to Relay D register
		STA B	$0CED


If I understand BIT:

LB_ECC0	LDA A	$C3		//	fetch status nybble
		BIT A	#$01		//	Test bit 0 - Relay A
		BNE		LB_ECCA	//	contact opened like it was supposed to
		JMP		LB_EE31	//	jump to test fail: Relay A
LB_ECCA	LDA B	#$07	//	Write pass value to Relay A register
		STA B	$0CEA
		BIT A	#$02	//	Test bit 1 - Relay B
		BNE		LB_ECD5		//	contact opened like it was supposed to
		JMP		LB_EE67	//	jump to test fail: Relay B
LB_ECD5	LDA B	#$07	//	Write pass value to Relay B register
		STA B	$0CEB
		BIT A	#$04	//	Test bit 2 - Relay C
		BNE		LB_ECE0		//	contact opened like it was supposed to
		JMP		LB_EE6F	//	jump to test fail: Relay C
LB_ECE0	LDA B	#$07	//	Write pass value to Relay C register
		STA B	$0CEC
		BIT A	#$08	//	Test bit 3 - Relay D
		BNE		LB_ECEB		//	contact opened like it was supposed to
		JMP		LB_EE77	//	jump to test fail: Relay D
LB_ECEB	LDA B	#$07	//	Write pass value to Relay D register
		STA B	$0CED
User avatar
BigEd
Posts: 11464
Joined: 11 Dec 2008
Location: England
Contact:

Re: BIT instruction?

Post by BigEd »

Umm, do you have two accumulators? Is this 6800, or 6809 family, perhaps, rather than 6502? I'm not very familiar but it's possible those micros have different behaviour.

On the 6502, if you needed to update some other address without disturbing A, you would typically use X or Y, which are both single-byte registers.
Meterman58761
Posts: 19
Joined: 05 Jun 2021

Re: BIT instruction?

Post by Meterman58761 »

Currently working in 6801 assembly, yes, so if / when this gets translated over to 6502, the B accumulator in this example would end up being substituted with the Y index.
User avatar
BigEd
Posts: 11464
Joined: 11 Dec 2008
Location: England
Contact:

Re: BIT instruction?

Post by BigEd »

I think you have the right idea, anyway.

There's a choice, whether to load a mask into A and BIT against a location, or load A from the location and then BIT against a constant. Sometimes it will be right to read the location just once.

If you have just 2 bits of interest and they are at the top of the byte, BIT makes more sense, I think. If your bits of interest are at the bottom of the byte, shifting and checking C makes good sense. It's not the case that BIT is always the best choice, but sometimes it is. So it's worth understanding it so you can use it as appropriate.

Sometimes a mix of tactics will be best, for example if bits 7 6 and 0 are in use.
User avatar
BB8
Posts: 57
Joined: 01 Nov 2020
Location: Tatooine

Re: BIT instruction?

Post by BB8 »

If you want to check all the bits of a location, one by one, you could do something like:

Code: Select all

LDA #$01
BIT mem
BNE bit0set
ASL
BIT mem
BNE bit1set
ASL
BIT mem
BNE bit2set
...
I think that maybe you're describing a byte of flags (where each bit is a flag). You may prefer to use a whole byte as a flag and just focus on the bit 7 of the byte and check it with the BIT instruction and the BPL/BMI branches.

Code: Select all

true = $FF
false = $00

LDA #false
;LDA #true
STA memflag
[...]
BIT memflag
BPL flagisfalse
Similarly another useful opportunity is to check if a signed number in memory is positive or negative, without compromising the accumulator:

Code: Select all

BIT num
BMI isneg
Meterman58761
Posts: 19
Joined: 05 Jun 2021

Re: BIT instruction?

Post by Meterman58761 »

This successive bit-shifting is done in several places in the code.

The most common use is to interpret the relay status nybble to determine whether the four relays opened or closed as directed, and to set the pass / fail conditions as appropriate.

It is also used within the main loop; at various points in the code, a 'configuration byte' is set and program flow is then directed back to the main loop as a 'do this step now but not that' (this section uses ASL and BCC to run through bits 7-3, 1, and 0; when the carry bit is 0, the program drops down to the next bit to be evaluated).

My goal is to end up with something that works in assembly, whether 6801 or 6502, yet intuitively translates to higher level languages (i.e. C++).

This code was riddled with shortcuts and odd programming quirks and I've pried out as many as I could find.
Perhaps I should post the initial configuration check code just to show you what I'm working with...
User avatar
GARTHWILSON
Forum Moderator
Posts: 8773
Joined: 30 Aug 2002
Location: Southern California
Contact:

Re: BIT instruction?

Post by GARTHWILSON »

I probably wouldn't use BIT here. Instead, I might do something like:

Code: Select all

        LDY  #7

        LDA  $C3              ; fetch status nybble
        LSR                   ; shift Relay A bit into carry
        BCC  REL_A_TEST_FAIL  ; branch to test fail: Relay A

        STY  $0CEA            ; Write pass value to Relay A register
        LSR                   ; shift Relay B bit into carry
        BCC  REL_B_TEST_FAIL  ; Branch to test fail: Relay B

        STY  $0CEB            ; Write pass value to Relay B register
        LSR                   ; shift Relay C bit into carry
        BCC  REL_C_TEST_FAIL  ; Branch to test fail: Relay C

        STY  $0CEC            ; Write pass value to Relay C register
        LSR                   ; shift Relay D bit into carry
        BCC  REL_D_TEST_FAIL  ; jump to test fail: Relay D

        STY  $0CED            ; Write pass value to Relay D register

using the BCC instead of BCS around a JMP, and put them all within branch range. It looks like the various places to jump to are all short enough to put them together within branch range.
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
teamtempest
Posts: 443
Joined: 08 Nov 2009
Location: Minnesota
Contact:

Re: BIT instruction?

Post by teamtempest »

BIT is quite handy for waiting until a status line goes to a desired state:

Code: Select all


      lda #$10    ; status bit is bit #4 in this example
@1    bit ioport
      beq @1       ; wait until set (or bne if waiting until clear)

You might see something like this in bit-banged serial i/o, for instance.
Meterman58761
Posts: 19
Joined: 05 Jun 2021

Re: BIT instruction?

Post by Meterman58761 »

As for using BIT to wait for one line to change state, that's actually not a bad idea. Will have to see if that would work better than the existing approach.

Anyway, coming back to the original part of my post, for the post-test relay check routine I did end up using BIT rather than LSR paired with BCC / BCS, as the routine which it replaced was several layers deep and a mass of shifts, carries, branches, and even jumps that was scattered out into several sections. 500+ bytes (in 5-6 places) replaced by a ~100-byte subroutine and two JSRs.
Post Reply