Page 1 of 1
BIT instruction?
Posted: Sat Jul 24, 2021 4:59 am
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?
Re: BIT instruction?
Posted: Sat Jul 24, 2021 5:33 am
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.
Re: BIT instruction?
Posted: Sat Jul 24, 2021 5:40 am
by BigEd
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.)
Re: BIT instruction?
Posted: Sat Jul 24, 2021 5:54 am
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.
Re: BIT instruction?
Posted: Sat Jul 24, 2021 6:00 am
by BigEd
This is, surely, the reason why the peripheral chips we use often present a useful overall status bit in bit 7.
Re: BIT instruction?
Posted: Sat Jul 24, 2021 2:30 pm
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
Re: BIT instruction?
Posted: Sat Jul 24, 2021 2:48 pm
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.
Re: BIT instruction?
Posted: Sat Jul 24, 2021 3:15 pm
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.
Re: BIT instruction?
Posted: Sat Jul 24, 2021 3:31 pm
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.
Re: BIT instruction?
Posted: Sat Jul 24, 2021 4:01 pm
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:
Re: BIT instruction?
Posted: Sat Jul 24, 2021 5:44 pm
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...
Re: BIT instruction?
Posted: Sat Jul 24, 2021 8:41 pm
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.
Re: BIT instruction?
Posted: Sun Jul 25, 2021 4:44 am
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.
Re: BIT instruction?
Posted: Thu Jul 29, 2021 6:16 am
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.