1

Given the following assembly code for a 16-bit PRNG function,

$80/8111 E2 20       SEP #$20   ; set 8-bit mode accumulator
$80/8113 AD E5 05    LDA $05E5  ; load low byte of last random number
$80/8116 8D 02 42    STA $4202
$80/8119 A9 05       LDA #$05   ; multiply it by 5
$80/811B 8D 03 42    STA $4203
$80/811E EA          NOP
$80/811F C2 20       REP #$20   ; set 16-bit mode accumulator
$80/8121 AD 16 42    LDA $4216  ; load the resultant product
$80/8124 48          PHA        ; push it onto the stack
$80/8125 E2 20       SEP #$20   ; 8-bit
$80/8127 AD E6 05    LDA $05E6  ; load high byte of last random number
$80/812A 8D 02 42    STA $4202
$80/812D A9 05       LDA #$05   ; multiply by 5
$80/812F 8D 03 42    STA $4203
$80/8132 EB          XBA        ; exchange high and low bytes of accumulator
$80/8133 EA          NOP
$80/8134 AD 16 42    LDA $4216  ; load low byte of product
$80/8137 38          SEC
$80/8138 63 02       ADC $02,s  ; add to it the high byte of the original product
$80/813A 83 02       STA $02,s  ; save it to the high byte of the original product
$80/813C C2 20       REP #$20   ; 16-bit
$80/813E 68          PLA        ; pull it from the stack
$80/813F 69 11 00    ADC #$0011 ; add 11
$80/8142 8D E5 05    STA $05E5  ; save as new random number
$80/8145 6B          RTL

a user by the name of @sagara translated the code to C:

#define LOW(exp)  ((exp) & 0x00FF)
#define HIGH(exp) (((exp) & 0xFF00) >> 8)

uint16_t prng(uint16_t v) {

    uint16_t low  = LOW(v);
    uint16_t high = HIGH(v);

    uint16_t mul_low  = low  * 5;
    uint16_t mul_high = high * 5;

    // need to check for overflow, since final addition is adc as well
    uint16_t v1    = LOW(mul_high) + HIGH(mul_low) + 1;
    uint8_t  carry = HIGH(v1) ? 1 : 0;

    uint16_t v2 = (LOW(v1) << 8) + LOW(mul_low);

    return (v2 + 0x11 + carry);
}

I'm confused by two things.

  1. In this line...

    uint16_t v1    = LOW(mul_high) + HIGH(mul_low) + 1;
    

    Why is there a + 1? I think it's because of the ADC operation, but how can we be sure that the carry flag is set to 1? What previous operation would guarantee this? The XBC? I read a few posts such as Assembly ADC (Add with carry) to C++ and Overflow and Carry flags on Z80 but it's not clear to me because the instruction set appears to be different I'm not familiar with 65C816 assembly. (This is from a popular 1994 SNES game whose NA release anniversary recently passed; free upvote to the correct guess :-)

  2. In the next line...

    uint8_t  carry = HIGH(v1) ? 1 : 0;
    

    Why would it work this way? I read this as, "Set the carry flag if and only if the high byte is non-zero." But wouldn't the indication of an overflow be only if the high byte is zero? (I'm probably misinterpreting what the line is doing.)

Thanks in advance for any insights.

Community
  • 1
  • 1
Andrew Cheong
  • 29,362
  • 15
  • 90
  • 145
  • 2
    What assembly language is this? The posts you linked are for different instruction sets. Please tag this appropriately. – interjay Apr 20 '16 at 13:40
  • @interjay - As mentioned in my post, it's from a game cartridge from 1994, but I have no idea what language—I was hoping someone could identify it, because today is literally my first day looking at assembly. I've already tried Googling extensively, _e.g._ " assembly instruction set". – Andrew Cheong Apr 20 '16 at 13:42
  • Looks like 6502 assembly, or some sort of modification of it. – Leandros Apr 20 '16 at 13:45
  • It just hit me that the instruction set is obviously related to the CPU, not the game cartridge. I found [here](https://en.wikibooks.org/wiki/Super_NES_Programming/Introduction_to_assembly) that it might be the "65816." Ah, [here's the full instruction set](https://en.wikibooks.org/wiki/Super_NES_Programming/65c816_reference#Addressing_modes). – Andrew Cheong Apr 20 '16 at 13:46
  • 1
    Now I know what that looked familiar. Has been a while since I've worked with NES assembly. – Leandros Apr 20 '16 at 13:48
  • 1
    Yep, that's 65C816 assembly, which is the CPU that the SNES's 5A22 was based on. – Michael Apr 20 '16 at 13:50
  • 1
    All the Motorola assemblers look pretty much like this. – Lundin Apr 20 '16 at 13:52
  • 2
    The line `$80/8137 38 SEC` is explicity setting the carry just before an `ADC` add-with-carry instruction, that's why the `+1` in the C code. – Weather Vane Apr 20 '16 at 13:57

2 Answers2

5
  1. but how can we be sure that the carry flag is set to 1? What previous operation would guarantee this?
$80/8137 38          SEC   ; SEt Carry flag

  1. uint8_t carry = HIGH(v1) ? 1 : 0;
    Why would it work this way? I read this as, "Set the carry flag if and only if the high byte is non-zero." But wouldn't the indication of an overflow be only if the high byte is zero?

The addition ADC #$0011 is using the carry from ADC $02,s. When ADC $02,s is performed, the accumulator is set to 8-bit (because of SEP #$20), so the carry flag will be set if the result of ADC $02,s would've exceeded 8 bits (i.e. if you would've got something >= $100 in 16-bit mode).
In the C version you've got a 16-bit variable (v1) to hold the result, so your carry will be in bit 8 of v1, which you can test with HIGH(v1) == 1, or simply HIGH(v1) since it will either be 1 or 0.

Michael
  • 57,169
  • 9
  • 80
  • 125
  • Ah, I've realized why (2) was throwing me off. I mistakenly read the `LOW(mul_high) + HIGH(mul_low)` as concatenating two 8-bit strings (as if one of them were shifted) instead of simply being added. So I was under the impression that the `HIGH(v1)` could be any range of numbers, hence my whole "non-zero" thing. But your explanation cleared it up: only 1 bit can be set in this scenario. Thanks! – Andrew Cheong Apr 20 '16 at 14:14
  • @Andrew Cheong it was done that way because C does not have a "carry". – Weather Vane Apr 20 '16 at 14:17
3

1) The line

$80/8137 38     SEC

is explicity setting the carry just before an ADC add-with-carry instruction, that's why the +1 in the C code.

2) The processor has an 8-bit accumulator, and the addition will overflow to the carry, ready for the next ADC instruction. However, the C code is using a 16-bit variable v1, and the carry remains in the upper 8 bits. Hence the test of those upper 8 bits to extract the so-called "carry".

Weather Vane
  • 33,872
  • 7
  • 36
  • 56