News: 11 March 2016 - Forum Rules
Current Moderators - DarkSol, KingMike, MathOnNapkins, Azkadellia, Danke

Author Topic: Emulate 16-bit ADC on a modern system  (Read 5401 times)

Sinthet

  • Jr. Member
  • **
  • Posts: 44
  • A line of code a day keeps insanity away.
    • View Profile
    • I write stuff here, sometimes.
Emulate 16-bit ADC on a modern system
« on: December 27, 2012, 01:27:38 am »
So, I was wondering if anyone knew a good method of emulating the behaviour the SNES CPU instruction ADC. The concept is simple enough, add 1 to compensate for overflows. However, the details of implementing this are escaping me.

Basically, I'm adding a ton of 2 byte words together and need a method to emulate ADC. I'm using two short ints (One to store the answer, one to read in the value to add). I'm doing this in C/C++, but any general info, code, etc is also much appreciated.

Its one of those "its 1 in the morning and this is driving me freaking mad" moments.  :banghead:

Ryusui

  • Hero Member
  • *****
  • Posts: 4989
  • It's the greatest day.
    • View Profile
    • Tumblr
Re: Emulate 16-bit ADC on a modern system
« Reply #1 on: December 27, 2012, 01:52:54 am »
ADC means "add with carry." If the carry flag is set, you add 1 to the result and reset the carry flag.
In the event of a firestorm, the salad bar will remain open.

BRPXQZME

  • Hero Member
  • *****
  • Posts: 4572
  • じー
    • View Profile
    • The BRPXQZME Network
Re: Emulate 16-bit ADC on a modern system
« Reply #2 on: December 27, 2012, 02:01:40 am »
The human-readable version is on page 327 (or for those of us who prefer to search instead of finding the page: "Add the data located at the effective address"):
http://www.westerndesigncenter.com/wdc/datasheets/Programmanual.pdf

This is implemented (for 16-bit words) in bsnes v091 (bsnes/processor/r65816/algorithms.cpp) like so:
Code: [Select]
inline void R65816::op_adc_w() {
  int result;

  if(!regs.p.d) {
    result = regs.a.w + rd.w + regs.p.c;
  } else {
    result = (regs.a.w & 0x000f) + (rd.w & 0x000f) + (regs.p.c <<  0);
    if(result > 0x0009) result += 0x0006;
    regs.p.c = result > 0x000f;
    result = (regs.a.w & 0x00f0) + (rd.w & 0x00f0) + (regs.p.c <<  4) + (result & 0x000f);
    if(result > 0x009f) result += 0x0060;
    regs.p.c = result > 0x00ff;
    result = (regs.a.w & 0x0f00) + (rd.w & 0x0f00) + (regs.p.c <<  8) + (result & 0x00ff);
    if(result > 0x09ff) result += 0x0600;
    regs.p.c = result > 0x0fff;
    result = (regs.a.w & 0xf000) + (rd.w & 0xf000) + (regs.p.c << 12) + (result & 0x0fff);
  }

  regs.p.v = ~(regs.a.w ^ rd.w) & (regs.a.w ^ result) & 0x8000;
  if(regs.p.d && result > 0x9fff) result += 0x6000;
  regs.p.c = result > 0xffff;
  regs.p.n = result & 0x8000;
  regs.p.z = (uint16_t)result == 0;

  regs.a.w = result;
}
we are in a horrible and deadly danger

LostTemplar

  • Hero Member
  • *****
  • Posts: 906
    • View Profile
    • au-ro-ra.net
Re: Emulate 16-bit ADC on a modern system
« Reply #3 on: December 27, 2012, 02:28:44 am »
The stuff in the else branch is executed when the CPU's decimal flag is set, which I've never seen used by SNES games. So unless you're actually working on a real emulator you can probably ignore it and the three most important lines are

Code: [Select]
result = regs.a.w + rd.w + regs.p.c;
regs.p.c = result > 0xffff;
regs.a.w = result; // implicit cast to 16-bit integer here

Sinthet

  • Jr. Member
  • **
  • Posts: 44
  • A line of code a day keeps insanity away.
    • View Profile
    • I write stuff here, sometimes.
Re: Emulate 16-bit ADC on a modern system
« Reply #4 on: December 27, 2012, 11:21:41 am »
So, I've tried to simply re-implement what bsnes is doing, and I'm getting the same, incorrect values that I was getting with my original approach (Before I started throwing in a few hackish partial fixes which didn't actually work in the long run...).

Here is my relevant code.
Code: [Select]
  int checksum;
  int16_t aw;
  int16_t rw;
  bool carry = false;
 
  for (int i=0;i < 300; i++) {
    fread(&rw,2,1,sram)
    checksum = aw + rw + carry;
    std::cout << "Checksum: " << std::hex << checksum << "\n";
    carry = checksum > 0xffff;
    aw = checksum;
  }

Code: [Select]
carry = checksum > 0xffffNever seems to return true, however. If anyone is wondering, I'm revisiting my toy Final Fantasy 5 SRAM checksum fixer that I posted a question about almost a year ago (!). Here's a bit of a relevant trace.

Code: [Select]
$C2/F59B 7D 00 00    ADC $0000,x[$30:6028]   A:F290 X:6028 Y:05D8 P:envmxdIzc
$C2/F59E E8          INX                     A:0BAC X:6028 Y:05D8 P:envmxdIzC
$C2/F59F E8          INX                     A:0BAC X:6029 Y:05D8 P:envmxdIzC
$C2/F5A0 88          DEY                     A:0BAC X:602A Y:05D8 P:envmxdIzC
$C2/F5A1 88          DEY                     A:0BAC X:602A Y:05D7 P:envmxdIzC
$C2/F5A2 D0 F7       BNE $F7    [$F59B]      A:0BAC X:602A Y:05D6 P:envmxdIzC
$C2/F59B 7D 00 00    ADC $0000,x[$30:602A]   A:0BAC X:602A Y:05D6 P:envmxdIzC
$C2/F59E E8          INX                     A:24C8 X:602A Y:05D6 P:envmxdIzc
$C2/F59F E8          INX                     A:24C8 X:602B Y:05D6 P:envmxdIzc
$C2/F5A0 88          DEY                     A:24C8 X:602C Y:05D6 P:envmxdIzc
$C2/F5A1 88          DEY                     A:24C8 X:602C Y:05D5 P:envmxdIzc
$C2/F5A2 D0 F7       BNE $F7    [$F59B]      A:24C8 X:602C Y:05D4 P:envmxdIzc

My for loop runs 300 times because "Y" starts at 600, and counts down to 0, with "Y" being decremented twice every time. Each time I read 2 bytes, and everything seems to be fine there. LostTemplar seems to be correct that I can ignore all the code in the "else" clause (in the bsnes code), since the only Processor flag ever raised here is Carry.

I'm guessing I'm messing up a type somewhere, or else overlooking a small detail. Anyone have any ideas?

Just btw...
Code: [Select]
uname -p -i -o -v
#55-Ubuntu SMP Wed Dec 5 17:42:16 UTC 2012 x86_64 x86_64 GNU/Linux

Pyriel

  • Jr. Member
  • **
  • Posts: 23
    • View Profile
Re: Emulate 16-bit ADC on a modern system
« Reply #5 on: December 27, 2012, 12:03:22 pm »
Edit:  actually, I misread the code there.

LostTemplar

  • Hero Member
  • *****
  • Posts: 906
    • View Profile
    • au-ro-ra.net
Re: Emulate 16-bit ADC on a modern system
« Reply #6 on: December 27, 2012, 12:54:03 pm »
It will never be true because aw and rw are signed 16-bit values, so the maximum value would be 0x7fff+0x7fff=0xfffe (everything greater than 0x8000 will be treated as negative). Try using uint16_t instead.

FAST6191

  • Hero Member
  • *****
  • Posts: 2965
    • View Profile
Re: Emulate 16-bit ADC on a modern system
« Reply #7 on: December 27, 2012, 02:36:48 pm »
Utterly irrelevant I know but I had a "you know you have been pondering electronics too much" moment when I clicked on the topic and thought it might be a discussion about analogue to digital conversion.

KingMike

  • Forum Moderator
  • Hero Member
  • *****
  • Posts: 7038
  • *sigh* A changed avatar. Big deal.
    • View Profile
Re: Emulate 16-bit ADC on a modern system
« Reply #8 on: December 27, 2012, 07:38:43 pm »
It will never be true because aw and rw are signed 16-bit values, so the maximum value would be 0x7fff+0x7fff=0xfffe (everything greater than 0x8000 will be treated as negative). Try using uint16_t instead.
Wouldn't checksum have to be a long int (at least 17 bits) for checksum > 0xFFFF to be true?
"My watch says 30 chickens" Google, 2018

BRPXQZME

  • Hero Member
  • *****
  • Posts: 4572
  • じー
    • View Profile
    • The BRPXQZME Network
Re: Emulate 16-bit ADC on a modern system
« Reply #9 on: December 27, 2012, 08:04:42 pm »
Under x64 Linux, an int is 32 bits.
we are in a horrible and deadly danger

Sinthet

  • Jr. Member
  • **
  • Posts: 44
  • A line of code a day keeps insanity away.
    • View Profile
    • I write stuff here, sometimes.
Re: Emulate 16-bit ADC on a modern system
« Reply #10 on: December 28, 2012, 09:55:37 am »
So... turns out, I did miss a small detail. We count down from 600 to 0 in hex, not decimal. So, my loop should actually run 767 times, not just 300 times.
I also decided to explicitly define stuff instead of relying on int/short, since they can indeed vary between systems and compilers.

Thanks for the help and suggestions everyone!  Much appreciated  :beer: