📄 cpuperipherals.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 + -