⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 cpuperipherals.pas

📁 一个不出名的GBA模拟器
💻 PAS
字号:
//////////////////////////////////////////////////////////////////////
//                                                                  //
// cpuPeripherals.pas: CPU - Peripheral devices                     //
//   Code for DMA, timers, and the joypad                           //
//                                                                  //
// The contents of this file are subject to the Bottled Light       //
// Public License Version 1.0 (the "License"); you may not use this //
// file except in compliance with the License. You may obtain a     //
// copy of the License at http://www.bottledlight.com/BLPL/         //
//                                                                  //
// Software distributed under the License is distributed on an      //
// "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or   //
// implied. See the License for the specific language governing     //
// rights and limitations under the License.                        //
//                                                                  //
// The Original Code is the Mappy VM Core, released April 1st, 2003 //
// The Initial Developer of the Original Code is Bottled Light,     //
// Inc. Portions created by Bottled Light, Inc. are Copyright       //
// (C) 2001 - 2003 Bottled Light, Inc. All Rights Reserved.         //
//                                                                  //
// Author(s):                                                       //
//   Michael Noland (joat), michael@bottledlight.com                //
//                                                                  //
// Changelog:                                                       //
//   1.0: First public release (April 1st, 2003)                    //
//                                                                  //
// Notes:                                                           //
//   DMA transfers with start mode 3 are a touchy area.  Its used   //
//   for FIFO refills in conjunction with channels 1 and 2, but I'm //
//   not certain if the fields forced to a specific value by the    //
//   sound hardware are all accounted for, if that makes any sense. //
//   For example, the length/width fields are certainly ignored,    //
//   FIFO refills always transfer 4 words.  In addition, start mode //
//   3 seems to be invalid for channel 0, but it may behave         //
//   specially for channel 3, I'm not certain.                      //
//                                                                  //
//   The timer code is a touchy subject with me, this is either the //
//   7th or 9th complete rewrite, I'm not even certain anymore.     //
//   It finally seems to work perfectly, and its actually faster    //
//   than a few of the older versions.  If there are any more       //
//   timer related problems, don't even THINK about telling me,     //
//   just fix it and send me the revised code.                      //
//                                                                  //
//////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////
unit cpuPeripherals; /////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////
interface ////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

uses
  nexus, AddressSpace;

//////////////////////////////////////////////////////////////////////

// FlushTimers() updates all of the timers to their current states.
procedure FlushTimers;

// WriteTimerCR() is called when a timer control register is modified.
procedure WriteTimerCR(timer: byte);

// InitiateDMATransfer() is called to initiate a DMA transfer.
procedure InitiateDMATransfer(channel: uint32);

//////////////////////////////////////////////////////////////////////
// Exported functions ////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

// vmKeyInput() is how the UI notifies the core when a key is pressed.
procedure vmKeyInput(mask: integer);

//////////////////////////////////////////////////////////////////////

exports
  vmKeyInput;

//////////////////////////////////////////////////////////////////////
implementation ///////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

uses
  SysUtils, cpuMemory, cpuMisc, cpuARMCore, cpuSound;

//////////////////////////////////////////////////////////////////////

procedure AdvanceTimer(timer: byte; cycles: integer);
var
  cr, speed: byte;
begin
  // Calculate the new value
  cr := registers[TIMER0_CR + timer shl 2];
  Inc(Puint32(@(registers[TIMER0_CYCLES + timer shl 2]))^, cycles);

  speed := registers[TIMER0_SPEED + timer];
  if Puint32(@(registers[TIMER0_CYCLES + timer shl 2]))^ shr speed >= $10000 then begin
    // Trigger an interrupt if requested
    if cr and TIMER_IRQ <> 0 then TriggerIRQ(IRQ_TIMER0 shl timer);

    // Try to trigger a FIFO increment and possible reload
    if registers[SOUND_ENABLED] and (1 shl 7) <> 0 then begin
      soundChanging;

      if (timer = soundA.timer) and soundA.enabled then begin
        DequeueTDSoundRecord(soundA);
        if soundA.writeIndex <= 16 then begin
          if ((Puint16(@(registers[DMA1_CR]))^ shr 12) and 3 = 3) then
            InitiateDMATransfer(1)
          else begin
            FillChar(soundA.fifo[soundA.writeIndex], 16, 0);
            Inc(soundA.writeIndex, 16);
          end;
        end;
      end;
      if (timer = soundB.timer) and soundB.enabled then begin
        DequeueTDSoundRecord(soundB);

        if soundB.writeIndex <= 16 then begin
          if ((Puint16(@(registers[DMA2_CR]))^ shr 12) and 3 = 3) then
            InitiateDMATransfer(2)
          else begin
            FillChar(soundB.fifo[soundB.writeIndex], 16, 0);
            Inc(soundB.writeIndex, 16);
          end;
        end;

      end;
    end;

    // Cascade into the other timers if they're set to cascade mode
    if timer < 3 then begin
      cr := registers[TIMER0_CR + (timer+1) shl 2];
      if (cr and TIMER_ENABLED <> 0) and (cr and TIMER_CASCADE <> 0) then
        AdvanceTimer(timer+1, Puint32(@(registers[TIMER0_CYCLES + timer shl 2]))^ shr (16 + speed));
    end;

    // Reload the timer
    Puint32(@(registers[TIMER0_CYCLES + timer shl 2]))^ := Puint16(@(registers[TIMER0_LATCH + timer shl 1]))^ shl speed + Puint32(@(registers[TIMER0_CYCLES + timer shl 2]))^ and not ($FFFFFFFF shl (16 + speed));
  end;
end;

//////////////////////////////////////////////////////////////////////

// This updates all of the timers to their current states.
procedure flushTimers;
var
  timer: byte;
  cycles: integer;
begin
  cycles := timerQuotaAtLastFlush - quota;
  timerQuotaAtLastFlush := quota;

  if cycles < 1 then Exit;
  for timer := 0 to 3 do begin
    if (registers[TIMER0_CR + timer shl 2] and TIMER_CASCADE = 0) and
       (registers[TIMER0_CR + timer shl 2] and TIMER_ENABLED <> 0) then
      AdvanceTimer(timer, cycles);

    // Write the value back to the register
    Puint16(@(registers[TIMER0 + timer shl 2]))^ := Puint32(@(registers[TIMER0_CYCLES + timer shl 2]))^ shr registers[TIMER0_SPEED + timer];
  end;
end;

//////////////////////////////////////////////////////////////////////

// WriteTimerCR() is called when a timer control register is modified.
procedure WriteTimerCR(timer: byte);
const
  timerDividerTable: array[0..3] of byte = (0, 6, 8, 10);
var
  cr: byte;
begin
  cr := registers[TIMER0_CR + timer shl 2];
  if cr and TIMER_ENABLED <> 0 then begin
    if cr and TIMER_CASCADE = 0 then
      registers[TIMER0_SPEED + timer] := timerDividerTable[cr and 3]
    else
      registers[TIMER0_SPEED + timer] := 0;
    Puint32(@(registers[TIMER0_CYCLES + timer shl 2]))^ := Puint16(@(registers[TIMER0_LATCH + timer shl 1]))^ shl registers[TIMER0_SPEED + timer];
  end;
end;

//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

// Performs a DMA transfer
procedure DmaTransfer(channel: byte; src, dest, count: uint32; var cr: uint16);
var
  srcDelta, destDelta, srcMode, destMode, startMode, delta: uint32;
  words: boolean;
  data: uint32;
  i: uint32;
  preQuota: integer;
begin
  if cr and DMA_ENABLED = 0 then Exit;

  words := cr and (1 shl 10) <> 0;
  if words then delta := 4 else delta := 2;
  startMode := (cr shr 12) and 3;

  // Set up the source mode
  srcMode := (cr shr 7) and $3;
  if srcMode = 0 then begin
    srcDelta := delta;
  end else if srcMode = 1 then begin
    srcDelta := -delta;
  end else
    srcDelta := 0;

  // Set up the destination mode
  destMode := (cr shr 5) and $3;
  if (destMode = 0) or (destMode = 3) then begin
    destDelta := delta;
  end else if destMode = 1 then begin
    destDelta := -delta;
  end else
    destDelta := 0;

  if startMode = 3 then begin
    if channel in [1,2] then begin
//      srcDelta := 4;
      destDelta := 0;
      count := 4;
      words := true;
    end else if channel = 0 then begin
      logWriteLn('Error, illegal startup mode 3 for DMA channel 0');
      Exit;
    end;
  end;

  // Write the xfer to the log
  preQuota := quota;
  if logDMATransfers then begin
    LogWrite(Format('DMA%d $%x bytes from $%8.8x to $%8.8x, CR=$%4.4x', [channel, count*delta, src, dest, cr]));
  end;

  if src shr 28 = dest shr 28 then begin
    // Same bus, all N cycles
    if words then begin
      for i := 1 to count do begin
        data := memReadWordUnc(src);
        memWriteWordUnc(dest, data);
        Inc(src, srcDelta);
        Inc(dest, destDelta);
      end;
    end else begin
      for i := 1 to count do begin
        data := memReadHalfWordUnc(src);
        memWriteHalfWordUnc(dest, data);
        Inc(src, srcDelta);
        Inc(dest, destDelta);
      end;
    end;
  end else begin
    // Different buses, all S cycles after the 1st access
    if words then begin
      if count > 0 then begin
        data := memReadWordUnc(src);
        memWriteWordUnc(dest, data);
        Inc(src, srcDelta);
        Inc(dest, destDelta);
      end;

      for i := 2 to count do begin
        lastAddress := src;
        data := memReadWordUnc(src);
        Inc(src, srcDelta);

        lastAddress := dest;
        memWriteWordUnc(dest, data);
        Inc(dest, destDelta);
      end;
    end else begin
      if count > 0 then begin
        data := memReadHalfWordUnc(src);
        memWriteHalfWordUnc(dest, data);
        Inc(src, srcDelta);
        Inc(dest, destDelta);
      end;

      for i := 2 to count do begin
        lastAddress := src;
        data := memReadHalfWordUnc(src);
        Inc(src, srcDelta);

        lastAddress := dest;
        memWriteHalfWordUnc(dest, data);
        Inc(dest, destDelta);
      end;
    end;
  end;

  // If not in repeat mode, clear the enabled bit
  if cr and DMA_REPEAT = 0 then
    Puint16(@(registers[channel*$C + DMA0_CR]))^ := Puint16(@(registers[channel*$C + DMA0_CR]))^ and not DMA_ENABLED;

  Puint32(@(registers[channel*$C + DMA0_SRC]))^ := src;
  if destMode <> 3 then Puint32(@(registers[channel*$C + DMA0_DEST]))^ := dest;

  if logDMATransfers then begin
    LogWriteLn(Format(' in %d cycles', [preQuota-quota]));
  end;

  // Attempt to trigger an IRQ if the IRQ bit is set
  if cr and DMA_IRQ_REQ <> 0 then TriggerIRQ(IRQ_DMA0 shl channel);
end;

//////////////////////////////////////////////////////////////////////

// InitiateDMATransfer() is called to initiate a DMA transfer.
procedure InitiateDMATransfer(channel: uint32);
var
  count, src, dest: uint32;
begin
  case channel of
    0: begin
      // Channel 0 has 27-bit source and destination ranges, and a 14-bit count
      src := Puint32(@(registers[DMA0_SRC]))^ and $07FFFFFF;
      dest := Puint32(@(registers[DMA0_DEST]))^ and $07FFFFFF;
      count := Puint16(@(registers[DMA0_COUNT]))^ and $3FFF;
      if count = 0 then count := $4000;
        DmaTransfer(0, src, dest, count, Puint16(@(registers[DMA0_CR]))^);
    end;
    1: begin
      // Channel 1 has a 28-bit source, 27-bit destination, and a 14-bit count
      src := Puint32(@(registers[DMA1_SRC]))^ and $0FFFFFFF;
      dest := Puint32(@(registers[DMA1_DEST]))^ and $07FFFFFF;
      count := Puint16(@(registers[DMA1_COUNT]))^ and $3FFF;
      if count = 0 then count := $4000;
      DmaTransfer(1, src, dest, count, Puint16(@(registers[DMA1_CR]))^);
    end;
    2: begin
      // Channel 2 has a 28-bit source, 27-bit destination, and a 14-bit count
      src := Puint32(@(registers[DMA2_SRC]))^ and $0FFFFFFF;
      dest := Puint32(@(registers[DMA2_DEST]))^ and $07FFFFFF;
      count := Puint16(@(registers[DMA2_COUNT]))^ and $3FFF;
      if count = 0 then count := $4000;
      DmaTransfer(2, src, dest, count, Puint16(@(registers[DMA2_CR]))^);
    end;
    3: begin
      // Channel 3 has a 28-bit source and destination range, and a 16-bit count
      src := Puint32(@(registers[DMA3_SRC]))^ and $0FFFFFFF;
      dest := Puint32(@(registers[DMA3_DEST]))^ and $0FFFFFFF;
      count := Puint16(@(registers[DMA3_COUNT]))^;
      if count = 0 then count := $10000;
      DmaTransfer(3, src, dest, count, Puint16(@(registers[DMA3_CR]))^);
    end;
  end;
end;

//////////////////////////////////////////////////////////////////////
// Input control (joystick or keyboard, whatever you want to call it)
//////////////////////////////////////////////////////////////////////

// vmKeyInput() is how the UI notifies the core when a key is pressed.
procedure vmKeyInput(mask: integer);
var
  k, cr: uint16;
begin
  Puint16(@(registers[KEYS]))^ := mask;
  if mask <> downkeys then begin
    // Attempt to trigger a key press IRQ
    k := (not Puint16(@(registers[KEYS]))^) and $3FF;
    cr := Puint16(@(registers[KEY_IRQS]))^;
    if cr and KEY_IRQ_ENABLED <> 0 then
      if cr and KEY_IRQ_AND <> 0 then begin
        cr := cr and $3FF;
        if k and cr = cr then TriggerIRQ(IRQ_KEY);
      end else
        if k and (cr and $3FF) <> 0 then TriggerIRQ(IRQ_KEY);
  end;
  downkeys := mask;
end;

//////////////////////////////////////////////////////////////////////

end.

//////////////////////////////////////////////////////////////////////

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -