diff --git a/Source/Core/Core/DSP/DSPCodeUtil.cpp b/Source/Core/Core/DSP/DSPCodeUtil.cpp index 9a33edc9ba..074dd86c3a 100644 --- a/Source/Core/Core/DSP/DSPCodeUtil.cpp +++ b/Source/Core/Core/DSP/DSPCodeUtil.cpp @@ -57,10 +57,12 @@ bool Disassemble(const std::vector& code, bool line_numbers, std::string& t return success; } +// NOTE: This code is called from DSPTool and UnitTests, which do not use the logging system. +// Thus, fmt::print is used instead of the log system. bool Compare(const std::vector& code1, const std::vector& code2) { if (code1.size() != code2.size()) - WARN_LOG_FMT(AUDIO, "Size difference! 1={} 2={}\n", code1.size(), code2.size()); + fmt::print("Size difference! 1={} 2={}\n", code1.size(), code2.size()); u32 count_equal = 0; const u16 min_size = static_cast(std::min(code1.size(), code2.size())); @@ -76,26 +78,47 @@ bool Compare(const std::vector& code1, const std::vector& code2) { std::string line1, line2; u16 pc = i; - disassembler.DisassembleOpcode(&code1[0], &pc, line1); + disassembler.DisassembleOpcode(code1, &pc, line1); pc = i; - disassembler.DisassembleOpcode(&code2[0], &pc, line2); - WARN_LOG_FMT(AUDIO, "!! {:04x} : {:04x} vs {:04x} - {} vs {}\n", i, code1[i], code2[i], - line1, line2); + disassembler.DisassembleOpcode(code2, &pc, line2); + fmt::print("!! {:04x} : {:04x} vs {:04x} - {} vs {}\n", i, code1[i], code2[i], line1, + line2); + + // Also do a comparison one word back if the previous word corresponded to an instruction with + // a large immediate. (Compare operates on individual words, so both the main word and the + // immediate following it are compared separately; we don't use DisassembleOpcode's ability to + // increment pc by 2 for two-word instructions because code1 may have a 1-word instruction + // where code2 has a 2-word instruction.) + if (i >= 1 && code1[i - 1] == code2[i - 1]) + { + const DSPOPCTemplate* opc = FindOpInfoByOpcode(code1[i - 1]); + if (opc != nullptr && opc->size == 2) + { + line1.clear(); + line2.clear(); + pc = i - 1; + disassembler.DisassembleOpcode(code1, &pc, line1); + pc = i - 1; + disassembler.DisassembleOpcode(code2, &pc, line2); + fmt::print(" (or {:04x} : {:04x} {:04x} vs {:04x} {:04x} - {} vs {})\n", i - 1, + code1[i - 1], code1[i], code2[i - 1], code2[i], line1, line2); + } + } } } if (code2.size() != code1.size()) { - DEBUG_LOG_FMT(AUDIO, "Extra code words:\n"); + fmt::print("Extra code words:\n"); const std::vector& longest = code1.size() > code2.size() ? code1 : code2; for (u16 i = min_size; i < longest.size(); i++) { u16 pc = i; std::string line; - disassembler.DisassembleOpcode(&longest[0], &pc, line); - DEBUG_LOG_FMT(AUDIO, "!! {}\n", line); + disassembler.DisassembleOpcode(longest, &pc, line); + fmt::print("!! {:04x} : {:04x} - {}\n", i, longest[i], line); } } - DEBUG_LOG_FMT(AUDIO, "Equal instruction words: {} / {}\n", count_equal, min_size); + fmt::print("Equal instruction words: {} / {}\n", count_equal, min_size); return code1.size() == code2.size() && code1.size() == count_equal; } diff --git a/Source/Core/Core/DSP/DSPDisassembler.cpp b/Source/Core/Core/DSP/DSPDisassembler.cpp index 01817c0780..6e833cdf16 100644 --- a/Source/Core/Core/DSP/DSPDisassembler.cpp +++ b/Source/Core/Core/DSP/DSPDisassembler.cpp @@ -33,9 +33,10 @@ bool DSPDisassembler::Disassemble(const std::vector& code, std::string& tex for (u16 pc = 0; pc < code.size();) { - if (!DisassembleOpcode(code.data(), &pc, text)) - return false; + bool failed = !DisassembleOpcode(code, &pc, text); text.append("\n"); + if (failed) + return false; } return true; } @@ -107,7 +108,7 @@ std::string DSPDisassembler::DisassembleParameters(const DSPOPCTemplate& opc, u1 { // Left and right shifts function essentially as a single shift by a 7-bit signed value, // but are split into two intructions for clarity. - buf += fmt::format("#{}", (val & 0x20) != 0 ? (64 - val) : val); + buf += fmt::format("#{}", (val & 0x20) != 0 ? (int(val) - 64) : int(val)); } else { @@ -139,16 +140,23 @@ std::string DSPDisassembler::DisassembleParameters(const DSPOPCTemplate& opc, u1 return buf; } -bool DSPDisassembler::DisassembleOpcode(const u16* binbuf, u16* pc, std::string& dest) +bool DSPDisassembler::DisassembleOpcode(const std::vector& code, u16* pc, std::string& dest) { - if ((*pc & 0x7fff) >= 0x1000) + return DisassembleOpcode(code.data(), code.size(), pc, dest); +} + +bool DSPDisassembler::DisassembleOpcode(const u16* binbuf, size_t binbuf_size, u16* pc, + std::string& dest) +{ + const u16 wrapped_pc = (*pc & 0x7fff); + if (wrapped_pc >= binbuf_size) { ++pc; dest.append("; outside memory"); return false; } - const u16 op1 = binbuf[*pc & 0x0fff]; + const u16 op1 = binbuf[wrapped_pc]; // Find main opcode const DSPOPCTemplate* opc = FindOpInfoByOpcode(op1); @@ -179,14 +187,23 @@ bool DSPDisassembler::DisassembleOpcode(const u16* binbuf, u16* pc, std::string& // printing if (settings_.show_pc) - dest += fmt::format("{:04x} ", *pc); + dest += fmt::format("{:04x} ", wrapped_pc); u16 op2; // Size 2 - the op has a large immediate. if (opc->size == 2) { - op2 = binbuf[(*pc + 1) & 0x0fff]; + if (wrapped_pc + 1 >= binbuf_size) + { + if (settings_.show_hex) + dest += fmt::format("{:04x} ???? ", op1); + dest += fmt::format("; Insufficient data for large immediate"); + *pc += opc->size; + return false; + } + + op2 = binbuf[wrapped_pc + 1]; if (settings_.show_hex) dest += fmt::format("{:04x} {:04x} ", op1, op2); } diff --git a/Source/Core/Core/DSP/DSPDisassembler.h b/Source/Core/Core/DSP/DSPDisassembler.h index 37a556050c..294cc8df52 100644 --- a/Source/Core/Core/DSP/DSPDisassembler.h +++ b/Source/Core/Core/DSP/DSPDisassembler.h @@ -34,8 +34,10 @@ public: bool Disassemble(const std::vector& code, std::string& text); - // Warning - this one is trickier to use right. - bool DisassembleOpcode(const u16* binbuf, u16* pc, std::string& dest); + // Disassembles the given opcode at pc and increases pc by the opcode's size. + // The PC is wrapped such that 0x0000 and 0x8000 both point to the start of the buffer. + bool DisassembleOpcode(const std::vector& code, u16* pc, std::string& dest); + bool DisassembleOpcode(const u16* binbuf, size_t binbuf_size, u16* pc, std::string& dest); private: std::string DisassembleParameters(const DSPOPCTemplate& opc, u16 op1, u16 op2); diff --git a/Source/Core/Core/HW/DSPLLE/DSPSymbols.cpp b/Source/Core/Core/HW/DSPLLE/DSPSymbols.cpp index ab1ff3ce0b..8f190f1ebe 100644 --- a/Source/Core/Core/HW/DSPLLE/DSPSymbols.cpp +++ b/Source/Core/Core/HW/DSPLLE/DSPSymbols.cpp @@ -77,13 +77,15 @@ void AutoDisassembly(const SDSP& dsp, u16 start_addr, u16 end_addr) u16 addr = start_addr; const u16* ptr = (start_addr >> 15) != 0 ? dsp.irom : dsp.iram; + constexpr size_t size = DSP_IROM_SIZE; + static_assert(size == DSP_IRAM_SIZE); while (addr < end_addr) { line_to_addr[line_counter] = addr; addr_to_line[addr] = line_counter; std::string buf; - if (!disasm.DisassembleOpcode(ptr, &addr, buf)) + if (!disasm.DisassembleOpcode(ptr, size, &addr, buf)) { ERROR_LOG_FMT(DSPLLE, "disasm failed at {:04x}", addr); break; diff --git a/Source/DSPTool/DSPTool.cpp b/Source/DSPTool/DSPTool.cpp index 3c4b350479..41299771ce 100644 --- a/Source/DSPTool/DSPTool.cpp +++ b/Source/DSPTool/DSPTool.cpp @@ -128,7 +128,7 @@ static std::string CodesToHeader(const std::vector>& codes, return header; } -static void PerformBinaryComparison(const std::string& lhs, const std::string& rhs) +static bool PerformBinaryComparison(const std::string& lhs, const std::string& rhs) { std::string binary_code; @@ -138,7 +138,7 @@ static void PerformBinaryComparison(const std::string& lhs, const std::string& r File::ReadFileToString(rhs, binary_code); const std::vector code2 = DSP::BinaryStringBEToCode(binary_code); - DSP::Compare(code1, code2); + return DSP::Compare(code1, code2); } static void PrintResults(const std::string& input_name, const std::string& output_name, @@ -482,8 +482,7 @@ int main(int argc, const char* argv[]) if (compare) { - PerformBinaryComparison(input_name, output_name); - return 0; + return PerformBinaryComparison(input_name, output_name) ? 0 : 1; } if (print_results) diff --git a/Source/DSPTool/DSPTool.vcxproj b/Source/DSPTool/DSPTool.vcxproj index 846377f990..20401a4ddb 100644 --- a/Source/DSPTool/DSPTool.vcxproj +++ b/Source/DSPTool/DSPTool.vcxproj @@ -21,12 +21,6 @@ Console - - - - - - diff --git a/Source/UnitTests/Core/CMakeLists.txt b/Source/UnitTests/Core/CMakeLists.txt index 134f7da78b..1e24489239 100644 --- a/Source/UnitTests/Core/CMakeLists.txt +++ b/Source/UnitTests/Core/CMakeLists.txt @@ -8,6 +8,7 @@ add_dolphin_test(DSPAssemblyTest DSP/DSPTestBinary.cpp DSP/DSPTestText.cpp DSP/HermesBinary.cpp + DSP/HermesText.cpp ) add_dolphin_test(ESFormatsTest IOS/ES/FormatsTest.cpp) diff --git a/Source/UnitTests/Core/DSP/DSPAssemblyTest.cpp b/Source/UnitTests/Core/DSP/DSPAssemblyTest.cpp index dd3c96e194..44e4cde30e 100644 --- a/Source/UnitTests/Core/DSP/DSPAssemblyTest.cpp +++ b/Source/UnitTests/Core/DSP/DSPAssemblyTest.cpp @@ -1,17 +1,19 @@ // Copyright 2017 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "Common/FileUtil.h" +#include + #include "Core/DSP/DSPCodeUtil.h" #include "Core/DSP/DSPDisassembler.h" #include "DSPTestBinary.h" #include "DSPTestText.h" #include "HermesBinary.h" +#include "HermesText.h" #include -static bool RoundTrippableDissassemble(const std::vector& code, std::string& text) +static bool RoundTrippableDisassemble(const std::vector& code, std::string& text) { DSP::AssemblerSettings settings; settings.ext_separator = '\''; @@ -31,20 +33,20 @@ static bool RoundTrip(const std::vector& code1) { std::vector code2; std::string text; - if (!RoundTrippableDissassemble(code1, text)) + if (!RoundTrippableDisassemble(code1, text)) { - printf("RoundTrip: Disassembly failed.\n"); + fmt::print("RoundTrip: Disassembly failed.\n"); return false; } if (!DSP::Assemble(text, code2)) { - printf("RoundTrip: Assembly failed.\n"); + fmt::print("RoundTrip: Assembly failed.\n"); return false; } if (!DSP::Compare(code1, code2)) { - DSP::Disassemble(code1, true, text); - printf("%s", text.c_str()); + fmt::print("RoundTrip: Assembled code does not match input code\n"); + return false; } return true; } @@ -57,25 +59,54 @@ static bool SuperTrip(const char* asm_code) std::string text; if (!DSP::Assemble(asm_code, code1)) { - printf("SuperTrip: First assembly failed\n"); + fmt::print("SuperTrip: First assembly failed\n"); return false; } - printf("First assembly: %i words\n", (int)code1.size()); + fmt::print("First assembly: {} words\n", code1.size()); - if (!RoundTrippableDissassemble(code1, text)) + if (!RoundTrippableDisassemble(code1, text)) { - printf("SuperTrip: Disassembly failed\n"); + fmt::print("SuperTrip: Disassembly failed\n"); return false; } else { - printf("Disassembly:\n"); - printf("%s", text.c_str()); + fmt::print("Disassembly:\n"); + fmt::print("{}", text); } if (!DSP::Assemble(text, code2)) { - printf("SuperTrip: Second assembly failed\n"); + fmt::print("SuperTrip: Second assembly failed\n"); + return false; + } + + if (!DSP::Compare(code1, code2)) + { + fmt::print("SuperTrip: Assembled code does not match between passes\n"); + return false; + } + return true; +} + +// Assembles asm_code, and verifies that it matches code1. +static bool AssembleAndCompare(const char* asm_code, const std::vector& code1) +{ + std::vector code2; + if (!DSP::Assemble(asm_code, code2)) + { + fmt::print("AssembleAndCompare: Assembly failed\n"); + return false; + } + + fmt::print("AssembleAndCompare: Produced {} words; padding to {} words\n", code2.size(), + code1.size()); + while (code2.size() < code1.size()) + code2.push_back(0); + + if (!DSP::Compare(code1, code2)) + { + fmt::print("AssembleAndCompare: Assembled code does not match expected code\n"); return false; } return true; @@ -129,11 +160,21 @@ TEST(DSPAssembly, ExtendedInstructions) " ADDAXL'MV $ACC1, $AX1.L : $AX1.H, $AC1.M\n")); } +TEST(DSPAssembly, HermesText) +{ + ASSERT_TRUE(SuperTrip(s_hermes_text)); +} + TEST(DSPAssembly, HermesBinary) { ASSERT_TRUE(RoundTrip(s_hermes_bin)); } +TEST(DSPAssembly, HermesAssemble) +{ + ASSERT_TRUE(AssembleAndCompare(s_hermes_text, s_hermes_bin)); +} + TEST(DSPAssembly, DSPTestText) { ASSERT_TRUE(SuperTrip(s_dsp_test_text)); @@ -144,11 +185,7 @@ TEST(DSPAssembly, DSPTestBinary) ASSERT_TRUE(RoundTrip(s_dsp_test_bin)); } -/* - -if (File::ReadFileToString("C:/devkitPro/examples/wii/asndlib/dsptest/dsp_test.ds", &dsp_test)) - SuperTrip(dsp_test.c_str()); - -//.File::ReadFileToString("C:/devkitPro/trunk/libogc/libasnd/dsp_mixer/dsp_mixer.s", &dsp_test); -// This is CLOSE to working. Sorry about the local path btw. This is preliminary code. -*/ +TEST(DSPAssembly, DSPTestAssemble) +{ + ASSERT_TRUE(AssembleAndCompare(s_dsp_test_text, s_dsp_test_bin)); +} diff --git a/Source/UnitTests/Core/DSP/DSPTestText.cpp b/Source/UnitTests/Core/DSP/DSPTestText.cpp index 2e1f60a450..bc02ef1ba2 100644 --- a/Source/UnitTests/Core/DSP/DSPTestText.cpp +++ b/Source/UnitTests/Core/DSP/DSPTestText.cpp @@ -89,7 +89,7 @@ MEM_LO: equ 0x0f7F CW 0x1305 CW 0x1306 - s40 + s16 lri $r12, #0x00ff main: @@ -469,7 +469,7 @@ irq4: jmp irq irq5: ; jmp finale - s40 + s16 mrr $r0d, $r1c mrr $r0d, $r1e clr $acc0 @@ -609,7 +609,7 @@ dma_copy: ret -send_back_16: +send_back_40: cw 0x8e00 call send_back diff --git a/Source/UnitTests/Core/DSP/HermesBinary.cpp b/Source/UnitTests/Core/DSP/HermesBinary.cpp index 299f50e16c..a7f485d0a0 100644 --- a/Source/UnitTests/Core/DSP/HermesBinary.cpp +++ b/Source/UnitTests/Core/DSP/HermesBinary.cpp @@ -1,5 +1,13 @@ -// Copyright 2017 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +/* DSP_MIXER -> PCM VOICE SOFTWARE PROCESSOR (8-16 Bits Mono/Stereo Voices) + +// Thanks to Duddie for you hard work and documentation + +Copyright (c) 2008 Hermes +All rights reserved. + +SPDX-License-Identifier: BSD-3-Clause + +*/ #include "HermesBinary.h" diff --git a/Source/UnitTests/Core/DSP/hermes.s b/Source/UnitTests/Core/DSP/HermesText.cpp similarity index 97% rename from Source/UnitTests/Core/DSP/hermes.s rename to Source/UnitTests/Core/DSP/HermesText.cpp index d5418a578a..f7f83a9737 100644 --- a/Source/UnitTests/Core/DSP/hermes.s +++ b/Source/UnitTests/Core/DSP/HermesText.cpp @@ -9,7 +9,9 @@ SPDX-License-Identifier: BSD-3-Clause */ +#include "HermesText.h" +const char s_hermes_text[21370] = R"( /********************************/ /** REGISTER NAMES **/ /********************************/ @@ -173,7 +175,7 @@ MEM_SND: equ data_end ; it need 2048 words (4096 bytes) lri $CONFIG, #0xff lri $SR,#0 - s40 + s16 clr15 m0 @@ -254,7 +256,7 @@ sys_command: jmp recv_cmd run_nexttask: - s40 + s16 call wait_for_cpu_mail lrs $29,@CMBL call wait_for_cpu_mail @@ -542,7 +544,11 @@ no_delay: ///////////////////////////////////// // end of delay time section ///////////////////////////////////// - +)" // Work around C2026 on MSVC, which allows at most 16380 single-byte characters in a single + // non-concatenated string literal (but you can concatenate multiple shorter string literals to + // produce a longer string just fine). (This comment is not part of the actual test program, + // and instead there is a single blank line at this location.) + R"( /* bucle de generacion de samples */ @@ -655,7 +661,7 @@ left_skip2: cmp - jrl $AR0 //get_sample or get_sample2 method + jrnc $AR0 //get_sample or get_sample2 method sr @COUNTERH_SMP, $ACH1 sr @COUNTERL_SMP, $ACM1 @@ -711,7 +717,7 @@ get_sample2: // slow method // if addr>addr end get a new buffer (if you uses double buffer) - jge get_new_buffer + jc get_new_buffer // load samples from dma, return $ar2 with the addr to get the samples and return using $ar0 to the routine to process 8-16bits Mono/Stereo @@ -741,7 +747,7 @@ get_sample: // fast method // compares if the current address is >= end address to change the buffer or stops cmp - jge get_new_buffer + jc get_new_buffer // load the new sample from the buffer @@ -961,8 +967,8 @@ wait_dma: wait_for_dsp_mail: lrs $ACM1, @DMBH - andf $ACM1, #0x8000 - jnz wait_for_dsp_mail + andcf $ACM1, #0x8000 + jlz wait_for_dsp_mail ret wait_for_cpu_mail: @@ -1077,4 +1083,4 @@ polla_loca: clr $ACC0 jmp recv_cmd - +)"; diff --git a/Source/UnitTests/Core/DSP/HermesText.h b/Source/UnitTests/Core/DSP/HermesText.h new file mode 100644 index 0000000000..99c16686e2 --- /dev/null +++ b/Source/UnitTests/Core/DSP/HermesText.h @@ -0,0 +1,8 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +extern const char s_hermes_text[21370]; diff --git a/Source/UnitTests/Core/PageFaultTest.cpp b/Source/UnitTests/Core/PageFaultTest.cpp index e340ca7646..e873a28e73 100644 --- a/Source/UnitTests/Core/PageFaultTest.cpp +++ b/Source/UnitTests/Core/PageFaultTest.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include "Common/CommonTypes.h" #include "Common/Timer.h" @@ -79,16 +80,19 @@ TEST(PageFault, PageFault) perform_invalid_access(data); auto end = std::chrono::high_resolution_clock::now(); -#define AS_NS(diff) \ - ((unsigned long long)std::chrono::duration_cast(diff).count()) + auto difference_in_nanoseconds = [](auto start, auto end) { + return std::chrono::duration_cast(end - start).count(); + }; EMM::UninstallExceptionHandler(); JitInterface::SetJit(nullptr); - printf("page fault timing:\n"); - printf("start->HandleFault %llu ns\n", AS_NS(pfjit.m_pre_unprotect_time - start)); - printf("UnWriteProtectMemory %llu ns\n", - AS_NS(pfjit.m_post_unprotect_time - pfjit.m_pre_unprotect_time)); - printf("HandleFault->end %llu ns\n", AS_NS(end - pfjit.m_post_unprotect_time)); - printf("total %llu ns\n", AS_NS(end - start)); + fmt::print("page fault timing:\n"); + fmt::print("start->HandleFault {} ns\n", + difference_in_nanoseconds(start, pfjit.m_pre_unprotect_time)); + fmt::print("UnWriteProtectMemory {} ns\n", + difference_in_nanoseconds(pfjit.m_pre_unprotect_time, pfjit.m_post_unprotect_time)); + fmt::print("HandleFault->end {} ns\n", + difference_in_nanoseconds(pfjit.m_post_unprotect_time, end)); + fmt::print("total {} ns\n", difference_in_nanoseconds(start, end)); } diff --git a/Source/UnitTests/UnitTests.vcxproj b/Source/UnitTests/UnitTests.vcxproj index b13cab4d0d..da461087e9 100644 --- a/Source/UnitTests/UnitTests.vcxproj +++ b/Source/UnitTests/UnitTests.vcxproj @@ -28,6 +28,7 @@ + @@ -60,6 +61,7 @@ +