mirror of https://github.com/michaelcmartin/Ophis.git synced 2024-09-16 23:56:09 +00:00

#### 186 lines 6.6 KiB Plaintext Raw Permalink Normal View History

 2012-06-09 08:06:25 +00:00 `` `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.` ` ` `
` `
`