diff --git a/Source/Core/Core/DSP/Interpreter/DSPInterpreter.cpp b/Source/Core/Core/DSP/Interpreter/DSPInterpreter.cpp index 8ca18e2a36..c4cc0e5601 100644 --- a/Source/Core/Core/DSP/Interpreter/DSPInterpreter.cpp +++ b/Source/Core/Core/DSP/Interpreter/DSPInterpreter.cpp @@ -16,8 +16,23 @@ namespace DSP::Interpreter { -// Not needed for game ucodes (it slows down interpreter + easier to compare int VS -// dspjit64 without it) +// Correctly handle instructions such as `INC'L $ac0 : $ac0.l, @$ar0` (encoded as 0x7660) where both +// the main opcode and the extension opcode modify the same register. See the "Extended opcodes" +// section in the manual for more details. No official uCode writes to the same register twice like +// this, so we don't emulate it by default (and also don't support it in the recompiler). +// +// Dolphin only supports this behavior in the interpreter when PRECISE_BACKLOG is defined. +// In ExecuteInstruction, if an extended opcode is in use, the extended opcode's behavior is +// executed first, followed by the main opcode's behavior. The extended opcode does not directly +// write to registers, but instead records the writes into a backlog (WriteToBackLog). The main +// opcode calls ZeroWriteBackLog after it is done reading the register values; this directly +// writes zero to all registers that have pending writes in the backlog. The main opcode then is +// free to write directly to registers it changes. Afterwards, ApplyWriteBackLog bitwise-ors the +// value of the register and the value in the backlog; if the main opcode didn't write to the +// register then ZeroWriteBackLog means that the pending value is being or'd with zero, so it's +// used without changes. When PRECISE_BACKLOG is not defined, ZeroWriteBackLog does nothing and +// ApplyWriteBackLog overwrites the register value with the value from the backlog (so writes from +// extended opcodes "win" over the main opcode). //#define PRECISE_BACKLOG Interpreter::Interpreter(DSPCore& dsp) : m_dsp_core{dsp} @@ -809,7 +824,7 @@ void Interpreter::ConditionalExtendAccum(int reg) void Interpreter::ApplyWriteBackLog() { // Always make sure to have an extra entry at the end w/ -1 to avoid - // infinitive loops + // infinite loops for (int i = 0; m_write_back_log_idx[i] != -1; i++) { u16 value = m_write_back_log[i]; @@ -823,6 +838,11 @@ void Interpreter::ApplyWriteBackLog() } } +// The ext ops are calculated in parallel with the actual op. That means that +// both the main op and the ext op see the same register state as input. The +// output is simple as long as the main and ext ops don't change the same +// register. If they do the output is the bitwise OR of the result of both the +// main and ext ops. void Interpreter::WriteToBackLog(int i, int idx, u16 value) { m_write_back_log[i] = value; @@ -840,7 +860,7 @@ void Interpreter::ZeroWriteBackLog() { #ifdef PRECISE_BACKLOG // always make sure to have an extra entry at the end w/ -1 to avoid - // infinitive loops + // infinite loops for (int i = 0; m_write_back_log_idx[i] != -1; i++) { OpWriteRegister(m_write_back_log_idx[i], 0); diff --git a/Source/Core/Core/DSP/Interpreter/DSPInterpreter.h b/Source/Core/Core/DSP/Interpreter/DSPInterpreter.h index 6d9e3e2709..e7cd266680 100644 --- a/Source/Core/Core/DSP/Interpreter/DSPInterpreter.h +++ b/Source/Core/Core/DSP/Interpreter/DSPInterpreter.h @@ -235,11 +235,6 @@ private: void ConditionalExtendAccum(int reg); - // The ext ops are calculated in parallel with the actual op. That means that - // both the main op and the ext op see the same register state as input. The - // output is simple as long as the main and ext ops don't change the same - // register. If they do the output is the bitwise OR of the result of both the - // main and ext ops. void WriteToBackLog(int i, int idx, u16 value); void ZeroWriteBackLog(); void ZeroWriteBackLogPreserveAcc(u8 acc); diff --git a/docs/DSP/GameCube_DSP_Users_Manual/GameCube_DSP_Users_Manual.tex b/docs/DSP/GameCube_DSP_Users_Manual/GameCube_DSP_Users_Manual.tex index 8ed85c004b..87c61b167a 100644 --- a/docs/DSP/GameCube_DSP_Users_Manual/GameCube_DSP_Users_Manual.tex +++ b/docs/DSP/GameCube_DSP_Users_Manual/GameCube_DSP_Users_Manual.tex @@ -4339,9 +4339,16 @@ Extended opcodes do not exist on their own. These opcodes can only be attached t Specifically, opcodes where the first nybble is 0, 1, or 2 cannot be extended. Opcodes where the first nybble is 4 or higher can be extended, using the 8 lower bits. Opcodes where the first nybble is 3 can also be extended, but the main opcode is 9 bits and the extension opcode is 7 bits. For these instructions, the extension opcode is treated as if the first bit were 0 (i.e. \texttt{0xxxxxxx}). +(\Opcode{NX} has no behavior of its own, so it can be used to get an extended opcode's behavior on its own.) Extended opcodes do not modify the program counter (\Register{\$pc} register). +Extended opcodes are run \textit{in parallel} with the main opcode; they see the same register state as the input. (For instance, \texttt{\Opcode{MOVR}\Opcode{'MV} \Register{\$ac1}, \Register{\$ax0.l} : \Register{\$ax0.l}, \Register{\$ac1.m}} (encoded as \Value{0x6113}) \textit{swaps} the values of \Register{\$ac1.m} and \Register{\$ax0.l} (and also extends the new value of \Register{\$ac1.m} into \Register{\$ac1.l} and \Register{\$ac1.h}).) + +Since they are executed in parallel, the main and extension opcodes could theoretically write to the same registers. All opcodes that support extension only modify a main accumulator \Register{\$acD}, as well as \Register{\$prod}, \Register{\$sr}, and/or \Register{\$pc}, while the extension opcodes themselves generally only modify an additional accumulator \Register{\$axD} and addressing registers \Register{\$arS}. The exception is \Opcode{'L} and \Opcode{'LN}, which has the option of writing to \Register{\$acD}. Thus, \texttt{\Opcode{INC}\Opcode{'L} \Register{\$ac0} : \Register{\$ac0.l}, @\Register{\$ar0}} (encoded as \Value{0x7660}) increments \Register{\$ac0} (and thus \Register{\$ac0.l}), but also sets \Register{\$ac0.l} to the value in data memory at address \Register{\$ar0} and increments \Register{\$ar0}. + +When the main and extension opcodes write to the same register, the register is set to the two values bitwise-or'd together. For the above example, \Register{\$ar0.l} would be set to \InlineExpression{(\Register{\$ar0.l} + 1) | MEM[\Register{\$ar0}]}. \textbf{Note that no official uCode writes to the same register twice like this.} + \pagebreak{} \section{Alphabetical list of extended opcodes}