visrealm / pico-56

The HBC-56 (65C02/TMS9918A/AY-3-8910 retro computer) fully emulated on a Raspberry Pi Pico
https://youtube.com/@TroySchrapel
MIT License
120 stars 10 forks source link

Is the AY-3-8910 Pico emulation code available? #1

Closed cbmeeks closed 10 months ago

cbmeeks commented 11 months ago

I was wondering if you have released the source for your AY-3-8910 emulation on the Pico. I am working on a similar project and would really love some information on that. :-)

Thanks!

visrealm commented 11 months ago

Hey CB. I'm working on the consumable version now. Here's some snippets from my existing dual AY-3-8910 emulation to get you started. I'm using my fork of emu2149.

Globals / magic numbers:

#define PSG_CLOCK 2000000
#define PSG_GPIO 20
#define PSG_PWM_WRAP 1024

PSG* psg0 = NULL;
PSG* psg1 = NULL;

uint audioSlice = 0;

Initialization (called once)

void configureAudio()
{
  // init the two PSGs
  int sampleRate = vgaParams.hSyncParams.freqHz;   // I'm updating the audio in my VGA hsync blanking ~37.87KHz
  psg0 = PSG_new(PSG_CLOCK, sampleRate);
  psg1 = PSG_new(PSG_CLOCK, sampleRate);
  PSG_setVolumeMode(psg0, EMU2149_VOL_AY_3_8910);  // AY-3-8910 mode
  PSG_setVolumeMode(psg1, EMU2149_VOL_AY_3_8910);  // AY-3-8910 mode
  PSG_reset(psg0);
  PSG_reset(psg1);

  // init the GPIO pins for PWM
  gpio_set_function(PSG_AUDIO_GPIO , GPIO_FUNC_PWM);
  gpio_set_function(PSG_AUDIO_GPIO + 1, GPIO_FUNC_PWM);

  audioSlice = pwm_gpio_to_slice_num(PSG_AUDIO_GPIO );

  pwm_set_clkdiv_int_frac(audioSlice, 1, 0);
  pwm_set_wrap(audioSlice, PSG_PWM_WRAP);
  pwm_set_both_levels(audioSlice, 0, 0);
  pwm_set_enabled(audioSlice, true);
}

Updates (called in my hsync event handler at ~37.87KHz:

PSG_calc(psg0);
PSG_calc(psg1);

// mix the various channels from the PSGs how you see fit
uint16_t leftAudio = abs((((int32_t)psg0->ch_out[0] + (int32_t)psg1->ch_out[1] + (int32_t)psg0->ch_out[2] + (int32_t)psg1->ch_out[2]))) * PWM_WRAP >> 14;
uint16_t rightAudio = abs((((int32_t)psg1->ch_out[0] + (int32_t)psg0->ch_out[1] + (int32_t)psg0->ch_out[2] + (int32_t)psg1->ch_out[2]))) * PWM_WRAP >> 14;

pwm_set_both_levels(audioSlice, leftAudio, rightAudio);
cbmeeks commented 10 months ago

Thanks for the response. I definitely have a lot to learn on this one. My main goal is to have some sort of PSG from the Pico...all being controlled by a 6502.

visrealm commented 10 months ago

Oh, so you're using a physical 6502? And the PICO is acting as the RAM/peripherals? One step I failed to mention was reading/writing from/to the virtual PSG(s). Each PSG will need 3-4 addresses mapped. For example, all of my I/O is mapped at 0x7fxx and my PSGs are mapped at 0x7f40 and 0x7f44. The way my PSGs are wired on my actual HBC-56, writing to 0x7f40 will latch a register to write to, 0x7f41 will write a register value and reading from 0x7f42 will read a value from the latched register. The address mappings don't matter, but you will need a similar process.

Here's some snippets from the PICO-56 memory read/write functions which show how the various addresses ling to the PSG_readreg/PSG_writereg functions:

#define PSG0_BASE_PORT 0x40
#define PSG1_BASE_PORT 0x44

void cpuMemWrite(uint16_t addr, uint8_t val)
{
  if (addr < 0x7f00)  // RAM
  {
    ram[addr] = val;
  }
  else if (addr < 0x8000) // IO
  {
      switch (addr & 0xff) 
      {
        case 0x10:
          vrEmuTms9918WriteData(tms9918, val);
          break;

        case 0x11:
          vrEmuTms9918WriteAddr(tms9918, val);
          break;

        case PSG0_BASE_PORT:
          psg0Reg = val;
          break;

        case PSG0_BASE_PORT | 0x01:
          PSG_writeReg(psg0, psg0Reg, val);
          break;

        case PSG1_BASE_PORT:
          psg1Reg = val;
          break;

        case PSG1_BASE_PORT | 0x01:
          PSG_writeReg(psg1, psg1Reg, val);
          break;

        default:
          // unknown device
          break;
    }
  }
}

uint8_t cpuMemRead(uint16_t addr)
{
  if (addr & 0x8000)     // ROM
  {
    return ROM[addr & 0x7fff];
  }
  else if (addr < 0x7f00)  // RAM
  {
    return ram[addr];
  }
  else    // IO
  {
      switch (addr & 0xff)
      {
        case 0x10:
          {
            return vrEmuTms9918ReadData(tms9918);
          }

        case 0x11:
          {
            uint8_t value = vrEmuTms9918ReadStatus(tms9918);
            releaseInterrupt(TMS_INT);
            return value;
          }

          // <snip> handle keyboard, nes, interrupt controller, etc.

        case PSG0_BASE_PORT | 0x02:
          {
            return PSG_readReg(psg0, psg0Reg);
          }

        case PSG1_BASE_PORT | 0x02:
          {
            return PSG_readReg(psg1, psg1Reg);
          }

        default:
          return 0x00;
      }
    }
  }
}
cbmeeks commented 10 months ago

Thank you for this information!

Yeah, I am designing a new SBC with an actual 6502. The Pico(s) will act as video, audio, etc.

I will dig into this more when I get back. Headed out on a nice vacation tomorrow.

Thanks!