mirror of https://github.com/michaelcmartin/Ophis.git synced 2024-08-03 01:29:11 +00:00
Michael Martin ffd96a8c2f
2012-06-09 03:21:33 -07:00

#### 186 lines 6.6 KiB Plaintext Raw Permalink Blame History

 ``` ``` ```The Second Step ``` ``` ``` ``` ``` ``` This essay discusses how to do 16-or-more bit addition and ``` ``` subtraction on the 6502, and how to do unsigned comparisons ``` ``` properly, thus making 16-bit arithmetic less necessary. ``` ``` ``` ``` ``` ```
``` ``` The problem ``` ``` ``` ``` The ADC, SBC, INX, ``` ``` and INY instructions are the only real ``` ``` arithmetic instructions the 6502 chip has. In and of themselves, ``` ``` they aren't too useful for general applications: the accumulator ``` ``` can only hold 8 bits, and thus can't store any value over 255. ``` ``` Matters get even worse when we're branching based on ``` ``` values; BMI and BPL hinge on ``` ``` the seventh (sign) bit of the result, so we can't represent any ``` ``` value above 127. ``` ``` ``` ```
``` ``` ``` ```
``` ``` The solution ``` ``` ``` ``` ``` ``` We have two solutions available to us. First, we can use ``` ``` the unsigned discipline, which involves checking ``` ``` different flags, but lets us deal with values between 0 and 255 ``` ``` instead of -128 to 127. Second, we can trade speed and register ``` ``` persistence for multiple precision arithmetic, using 16-bit ``` ``` integers (-32768 to 32767, or 0-65535), 24-bit, or more. ``` ``` ``` ``` ``` ``` ``` ``` Multiplication, division, and floating point arithmetic are beyond ``` ``` the scope of this essay. The best way to deal with those is to ``` ``` find a math library on the web (I ``` ``` recommend ) and use the ``` ``` routines there. ``` ``` ``` ```
``` ``` ``` ```
``` ``` Unsigned arithmetic ``` ``` ``` ``` When writing control code that hinges on numbers, we should always ``` ``` strive to have our comparison be with zero; that way, no explicit ``` ``` compare is necessary, and we can branch simply ``` ``` with BEQ/BNE, which test the zero flag. ``` ``` Otherwise, we use CMP. ``` ``` The CMP command subtracts its argument from the ``` ``` accumulator (without borrow), updates the flags, but throws away ``` ``` the result. If the value is equal, the result is zero. ``` ``` (CMP followed by BEQ ``` ``` branches if the argument is equal to the accumulator; this is ``` ``` probably why it's called BEQ and not something ``` ``` like BZS.) ``` ``` ``` ``` ``` ``` Intuitively, then, to check if the accumulator is less ``` ``` than some value, we CMP against that ``` ``` value and BMI. The BMI ``` ``` command branches based on the Negative Flag, which is equal to the ``` ``` seventh bit of CMP's subtract. That's exactly ``` ``` what we need, for signed arithmetic. However, this produces ``` ``` problems if you're writing a boundary detector on your screen or ``` ``` something and find that 192 < 4. 192 is outside of a signed ``` ``` byte's range, and is interpreted as if it were -64. This will not ``` ``` do for most graphics applications, where your values will be ``` ``` ranging from 0-319 or 0-199 or 0-255. ``` ``` ``` ``` ``` ``` ``` ``` Instead, we take advantage of the implied subtraction ``` ``` that CMP does. When subtracting, the result's ``` ``` carry bit starts at 1, and gets borrowed from if necessary. Let ``` ``` us consider some four-bit subtractions. ``` ``` ``` ``` ``` ``` ``` ```C|3210 C|3210 ``` ```------ ------ ``` ```1|1001 9 1|1001 9 ``` ``` |0100 - 4 |1100 -12 ``` ```------ --- ------ --- ``` ```1|0101 5 0|1101 -3 ``` ``` ``` ``` ``` ``` ``` ``` The CMP command properly modifies the carry bit ``` ``` to reflect this. When computing A-B, the carry bit is set if A ``` ``` >= B, and it's clear if A < B. Consider the following two ``` ``` code sequences. ``` ``` ``` ``` ``` ``` ``` ``` (1) (2) ``` ``` CMP #\$C0 CMP #\$C0 ``` ``` BMI label BCC label ``` ``` ``` ``` ``` ``` ``` ``` The code in the first column treats the value in the accumulator ``` ``` as a signed value, and branches if the value is less than -64. ``` ``` (Because of overflow issues, it will actually branch for ``` ``` accumulator values between \$40 and \$BF, even though it *should* ``` ``` only be doing it for values between \$80 and \$BF. To see why, ``` ``` compare \$40 to \$C0 and look at the result.) The second column ``` ``` code treats the accumulator as holding an unsigned value, and ``` ``` branches if the value is less than 192. It will branch for ``` ``` accumulator values \$00-\$BF. ``` ``` ``` ```
``` ``` ``` ```
``` ``` 16-bit addition and subtraction ``` ``` ``` ``` ``` ``` Time to use the carry bit for what it was meant to do. Adding two ``` ``` 8 bit numbers can produce a 9-bit result. That 9th bit is stored ``` ``` in the carry flag. The ADC command adds the ``` ``` carry value to its result, as well. Thus, carries work just as ``` ``` we'd expect them to. Suppose we're storing two 16-bit values, low ``` ``` byte first, in \$C100-1 and \$C102-3. To add them together and ``` ``` store them in \$C104-5, this is very easy: ``` ``` ``` ``` ``` ``` ``` ``` CLC ``` ``` LDA \$C100 ``` ``` ADC \$C102 ``` ``` STA \$C104 ``` ``` LDA \$C101 ``` ``` ADC \$C103 ``` ``` STA \$C105 ``` ``` ``` ``` ``` ``` ``` ``` Subtraction is identical, but you set the carry bit first ``` ``` with SEC (because borrow is the complement of ``` ``` carry—think about how the unsigned compare works if this ``` ``` puzzles you) and, of course, using the SBC ``` ``` instruction instead of ADC. ``` ``` ``` ``` ``` ``` ``` ``` The carry/borrow bit is set appropriately to let you continue, ``` ``` too. As long as you just keep working your way up to bytes of ``` ``` ever-higher significance, this generalizes to 24 (do it three ``` ``` times instead of two) or 32 (four, etc.) bit integers. ``` ``` ``` ```
``` ``` ``` ```
``` ``` 16-bit comparisons ``` ``` ``` ``` ``` ``` Doing comparisons on extended precision values is about the same ``` ``` as doing them on 8-bit values, but you have to have the value you ``` ``` test in memory, since it won't fit in the accumulator all at once. ``` ``` You don't have to store the values back anywhere, either, since ``` ``` all you care about is the final state of the flags. For example, ``` ``` here's a signed comparison, branching to label ``` ``` if the value in \$C100-1 is less than 1000 (\$03E8): ``` ``` ``` ``` ``` ``` ``` ``` SEC ``` ``` LDA \$C100 ``` ``` SBC #\$E8 ``` ``` LDA \$C101 ; We only need the carry bit from that subtract ``` ``` SBC #\$03 ``` ``` BMI label ``` ``` ``` ``` ``` ``` ``` ``` All the commentary on signed and unsigned compares holds for ``` ``` 16-bit (or higher) integers just as it does for the 8-bit ``` ``` ones. ``` ``` ``` ```
``` ```
```