33 changes: 19 additions & 14 deletions Source/Core/Core/DSP/Jit/x64/DSPJitExtOps.cpp
Expand Up @@ -9,7 +9,7 @@
using namespace Gen;

/* It is safe to directly write to the address registers as they are
neither read not written by any extendable opcode. The same is true
neither read nor written by any extendable opcode. The same is true
for memory accesses.
It probably even is safe to write to all registers except for
SR, ACx.x, AXx.x and PROD, which may be modified by the main op.
Expand Down Expand Up @@ -131,7 +131,7 @@ void DSPEmitter::l(const UDSPInstruction opc)
}

// LN $axD.D, @$arS
// xxxx xxxx 01dd d0ss
// xxxx xxxx 01dd d1ss
// Load $axD.D/$acD.D with value from memory pointed by register $arS.
// Add indexing register $ixS to register $arS.
void DSPEmitter::ln(const UDSPInstruction opc)
Expand Down Expand Up @@ -354,16 +354,18 @@ void DSPEmitter::slnm(const UDSPInstruction opc)
increase_addr_reg(DSP_REG_AR0, DSP_REG_AR0);
}

// LD $ax0.d, $ax1.r, @$arS
// LD $ax0.D, $ax1.R, @$arS
// xxxx xxxx 11dr 00ss
// example for "nx'ld $AX0.L, $AX1.L, @$AR3"
// Loads the word pointed by AR0 to AX0.H, then loads the word pointed by AR3
// to AX0.L. Increments AR0 and AR3. If AR0 and AR3 point into the same
// memory page (upper 6 bits of addr are the same -> games are not doing that!)
// then the value pointed by AR0 is loaded to BOTH AX0.H and AX0.L. If AR0
// points into an invalid memory page (ie 0x2000), then AX0.H keeps its old
// value. (not implemented yet) If AR3 points into an invalid memory page, then
// AX0.L gets the same value as AX0.H. (not implemented yet)
// Load register $ax0.D (either $ax0.l or $ax0.h) with value from memory pointed by register $arS.
// Load register $ax1.R (either $ax1.l or $ax1.h) with value from memory pointed by register $ar3.
// Increment both $arS and $ar3.
// S cannot be 3, as that encodes LDAX. Thus $arS and $ar3 are known to be distinct.
// If $ar0 and $ar3 point into the same memory page (upper 6 bits of addr are the same -> games are
// not doing that!) then the value pointed by $ar0 is loaded to BOTH $ax0.D and $ax1.R.
// If $ar0 points into an invalid memory page (ie 0x2000), then $ax0.D keeps its old value. (not
// implemented yet)
// If $ar3 points into an invalid memory page, then $ax1.R gets the same value as $ax0.D. (not
// implemented yet)
void DSPEmitter::ld(const UDSPInstruction opc)
{
u8 dreg = (opc >> 5) & 0x1;
Expand Down Expand Up @@ -396,6 +398,9 @@ void DSPEmitter::ld(const UDSPInstruction opc)

// LDAX $axR, @$arS
// xxxx xxxx 11sr 0011
// Load register $axR.h with value from memory pointed by register $arS.
// Load register $axR.l with value from memory pointed by register $ar3.
// Increment both $arS and $ar3.
void DSPEmitter::ldax(const UDSPInstruction opc)
{
u8 sreg = (opc >> 5) & 0x1;
Expand Down Expand Up @@ -425,7 +430,7 @@ void DSPEmitter::ldax(const UDSPInstruction opc)
increment_addr_reg(DSP_REG_AR3);
}

// LDN $ax0.d, $ax1.r, @$arS
// LDN $ax0.D, $ax1.R, @$arS
// xxxx xxxx 11dr 01ss
void DSPEmitter::ldn(const UDSPInstruction opc)
{
Expand Down Expand Up @@ -488,7 +493,7 @@ void DSPEmitter::ldaxn(const UDSPInstruction opc)
increment_addr_reg(DSP_REG_AR3);
}

// LDM $ax0.d, $ax1.r, @$arS
// LDM $ax0.D, $ax1.R, @$arS
// xxxx xxxx 11dr 10ss
void DSPEmitter::ldm(const UDSPInstruction opc)
{
Expand Down Expand Up @@ -551,7 +556,7 @@ void DSPEmitter::ldaxm(const UDSPInstruction opc)
increase_addr_reg(DSP_REG_AR3, DSP_REG_AR3);
}

// LDNM $ax0.d, $ax1.r, @$arS
// LDNM $ax0.D, $ax1.R, @$arS
// xxxx xxxx 11dr 11ss
void DSPEmitter::ldnm(const UDSPInstruction opc)
{
Expand Down
39 changes: 19 additions & 20 deletions Source/Core/Core/DSP/Jit/x64/DSPJitLoadStore.cpp
Expand Up @@ -17,7 +17,6 @@ namespace DSP::JIT::x64
// Move value from register $(0x18+S) to data memory pointed by address
// CR[0-7] | M. That is, the upper 8 bits of the address are the
// bottom 8 bits from CR, and the lower 8 bits are from the 8-bit immediate.
// Note: pc+=2 in duddie's doc seems wrong
void DSPEmitter::srs(const UDSPInstruction opc)
{
u8 reg = ((opc >> 8) & 0x7) + 0x18;
Expand Down Expand Up @@ -105,9 +104,9 @@ void DSPEmitter::si(const UDSPInstruction opc)
m_gpr.PutXReg(tmp1);
}

// LRR $D, @$S
// LRR $D, @$arS
// 0001 1000 0ssd dddd
// Move value from data memory pointed by addressing register $S to register $D.
// Move value from data memory pointed by addressing register $arS to register $D.
void DSPEmitter::lrr(const UDSPInstruction opc)
{
u8 sreg = (opc >> 5) & 0x3;
Expand All @@ -124,10 +123,10 @@ void DSPEmitter::lrr(const UDSPInstruction opc)
dsp_conditional_extend_accum(dreg);
}

// LRRD $D, @$S
// LRRD $D, @$arS
// 0001 1000 1ssd dddd
// Move value from data memory pointed by addressing register $S to register $D.
// Decrement register $S.
// Move value from data memory pointed by addressing register $arS to register $D.
// Decrement register $arS.
void DSPEmitter::lrrd(const UDSPInstruction opc)
{
u8 sreg = (opc >> 5) & 0x3;
Expand All @@ -145,10 +144,10 @@ void DSPEmitter::lrrd(const UDSPInstruction opc)
decrement_addr_reg(sreg);
}

// LRRI $D, @$S
// LRRI $D, @$arS
// 0001 1001 0ssd dddd
// Move value from data memory pointed by addressing register $S to register $D.
// Increment register $S.
// Move value from data memory pointed by addressing register $arS to register $D.
// Increment register $arS.
void DSPEmitter::lrri(const UDSPInstruction opc)
{
u8 sreg = (opc >> 5) & 0x3;
Expand All @@ -166,10 +165,10 @@ void DSPEmitter::lrri(const UDSPInstruction opc)
increment_addr_reg(sreg);
}

// LRRN $D, @$S
// LRRN $D, @$arS
// 0001 1001 1ssd dddd
// Move value from data memory pointed by addressing register $S to register $D.
// Add indexing register $(0x4+S) to register $S.
// Move value from data memory pointed by addressing register $arS to register $D.
// Add indexing register $ixS to register $arS.
void DSPEmitter::lrrn(const UDSPInstruction opc)
{
u8 sreg = (opc >> 5) & 0x3;
Expand All @@ -187,10 +186,10 @@ void DSPEmitter::lrrn(const UDSPInstruction opc)
increase_addr_reg(sreg, sreg);
}

// SRR @$D, $S
// SRR @$arD, $S
// 0001 1010 0dds ssss
// Store value from source register $S to a memory location pointed by
// addressing register $D.
// addressing register $arD.
void DSPEmitter::srr(const UDSPInstruction opc)
{
u8 dreg = (opc >> 5) & 0x3;
Expand All @@ -205,10 +204,10 @@ void DSPEmitter::srr(const UDSPInstruction opc)
m_gpr.PutXReg(tmp1);
}

// SRRD @$D, $S
// SRRD @$arD, $S
// 0001 1010 1dds ssss
// Store value from source register $S to a memory location pointed by
// addressing register $D. Decrement register $D.
// addressing register $arD. Decrement register $arD.
void DSPEmitter::srrd(const UDSPInstruction opc)
{
u8 dreg = (opc >> 5) & 0x3;
Expand All @@ -225,10 +224,10 @@ void DSPEmitter::srrd(const UDSPInstruction opc)
decrement_addr_reg(dreg);
}

// SRRI @$D, $S
// SRRI @$arD, $S
// 0001 1011 0dds ssss
// Store value from source register $S to a memory location pointed by
// addressing register $D. Increment register $D.
// addressing register $arD. Increment register $arD.
void DSPEmitter::srri(const UDSPInstruction opc)
{
u8 dreg = (opc >> 5) & 0x3;
Expand All @@ -245,10 +244,10 @@ void DSPEmitter::srri(const UDSPInstruction opc)
increment_addr_reg(dreg);
}

// SRRN @$D, $S
// SRRN @$arD, $S
// 0001 1011 1dds ssss
// Store value from source register $S to a memory location pointed by
// addressing register $D. Add DSP_REG_IX0 register to register $D.
// addressing register $arD. Add corresponding indexing register $ixD to register $arD.
void DSPEmitter::srrn(const UDSPInstruction opc)
{
u8 dreg = (opc >> 5) & 0x3;
Expand Down
8 changes: 4 additions & 4 deletions Source/Core/Core/DSP/Jit/x64/DSPJitMisc.cpp
Expand Up @@ -130,8 +130,8 @@ void DSPEmitter::clrCompileSR(u16 bit)
}
// SBCLR #I
// 0001 0011 aaaa aiii
// bit of status register $sr. Bit number is calculated by adding 6 to
// immediate value I.
// Clear bit of status register $sr. Bit number is calculated by adding 6 to immediate value I;
// thus, bits 6 through 13 (LZ through AM) can be cleared with this instruction.
void DSPEmitter::sbclr(const UDSPInstruction opc)
{
u8 bit = (opc & 0x7) + 6;
Expand All @@ -141,8 +141,8 @@ void DSPEmitter::sbclr(const UDSPInstruction opc)

// SBSET #I
// 0001 0010 aaaa aiii
// Set bit of status register $sr. Bit number is calculated by adding 6 to
// immediate value I.
// Set bit of status register $sr. Bit number is calculated by adding 6 to immediate value I;
// thus, bits 6 through 13 (LZ through AM) can be set with this instruction.
void DSPEmitter::sbset(const UDSPInstruction opc)
{
u8 bit = (opc & 0x7) + 6;
Expand Down
10 changes: 4 additions & 6 deletions Source/Core/Core/DSP/Jit/x64/DSPJitMultiplier.cpp
Expand Up @@ -416,7 +416,7 @@ void DSPEmitter::mulx(const UDSPInstruction opc)
}

// MULXAC $ax0.S, $ax1.T, $acR
// 101s t01r xxxx xxxx
// 101s t10r xxxx xxxx
// Add product register to accumulator register $acR. Multiply one part
// $ax0 by one part $ax1. Part is selected by S and
// T bits. Zero selects low part, one selects high part.
Expand Down Expand Up @@ -487,7 +487,7 @@ void DSPEmitter::mulxmv(const UDSPInstruction opc)
m_gpr.PutXReg(tmp1);
}

// MULXMV $ax0.S, $ax1.T, $acR
// MULXMVZ $ax0.S, $ax1.T, $acR
// 101s t01r xxxx xxxx
// Move product register to accumulator register $acR and clear (round) low part
// of accumulator register $acR.l. Multiply one part $ax0 by one part $ax1
Expand Down Expand Up @@ -545,7 +545,7 @@ void DSPEmitter::mulc(const UDSPInstruction opc)
}

// MULCAC $acS.m, $axT.h, $acR
// 110s t10r xxxx xxxx
// 110s t10r xxxx xxxx
// Multiply mid part of accumulator register $acS.m by high part $axS.h of
// secondary accumulator $axS (treat them both as signed). Add product
// register before multiplication to accumulator $acR.
Expand Down Expand Up @@ -586,7 +586,6 @@ void DSPEmitter::mulcac(const UDSPInstruction opc)
// Multiply mid part of accumulator register $acS.m by high part $axT.h of
// secondary accumulator $axT (treat them both as signed). Move product
// register before multiplication to accumulator $acR.
// possible mistake in duddie's doc axT.h rather than axS.h

// flags out: --xx xx0x
void DSPEmitter::mulcmv(const UDSPInstruction opc)
Expand Down Expand Up @@ -617,8 +616,7 @@ void DSPEmitter::mulcmv(const UDSPInstruction opc)
}

// MULCMVZ $acS.m, $axT.h, $acR
// 110s t01r xxxx xxxx
// (fixed possible bug in duddie's description, s->t)
// 110s t01r xxxx xxxx
// Multiply mid part of accumulator register $acS.m by high part $axT.h of
// secondary accumulator $axT (treat them both as signed). Move product
// register before multiplication to accumulator $acR, set (round) low part of
Expand Down
4 changes: 4 additions & 0 deletions Source/DSPSpy/.gitignore
@@ -0,0 +1,4 @@
dsp_code.h
build/
*.dol
*.elf
4 changes: 0 additions & 4 deletions Source/DSPSpy/Config.h

This file was deleted.

2 changes: 1 addition & 1 deletion Source/DSPSpy/Makefile
Expand Up @@ -23,7 +23,7 @@ include $(DEVKITPPC)/$(HW_TYPE)_rules
#---------------------------------------------------------------------------------
TARGET := $(notdir $(CURDIR))_$(HW_TYPE)
BUILD := build
SOURCES := . emu
SOURCES := .
DATA := data
INCLUDES := include ../Core/Common .

Expand Down
39 changes: 39 additions & 0 deletions Source/DSPSpy/README.md
@@ -0,0 +1,39 @@
# DSPSpy

DSPSpy is a homebrew tool for experimenting with the GameCube/Wii DSP. It can also be used to dump the DSP ROMs.

## Building

DSPSpy is built using [devkitPPC](https://wiibrew.org/wiki/DevkitPPC); see the [devkitPro getting started page](https://devkitpro.org/wiki/Getting_Started) for more information. DSPSpy also requires DSPTool to be built.

First, run DSPTool to generate `dsp_code.h`, for instance from `tests/dsp_test.ds`. The following commands assume an x64 Windows setup running in the DSPSpy directory:

```
../../Binary/x64/DSPTool.exe -h dsp_code tests/dsp_test.ds
```

To use the ROM-dumping code, run this:

```
../../Binary/x64/DSPTool.exe -h dsp_code util/dump_roms.ds
```

DSPTool can also generate a header for multiple DSP programs at the same time. First, create a file (in this example, it was named `file_list.txt`) with the following contents:

```
tests/dsp_test.ds
tests/mul_test.ds
tests/neg_test.ds
```

Then run:

```
../../Binary/x64/DSPTool.exe -h dsp_code -m file_list.txt
```

After `dsp_code.h` has been generated, simply run `make` to generate `dspspy_wii.dol`, which can be loaded by normal means.

## Dumping DSP ROMs

Build DSPSpy with `util/dump_roms.ds`. When launched, DSPSpy will automatically create files `dsp_rom.bin` and `dsp_coef.bin` on the SD card (only SD cards are supported); DSPSpy can be exited immediately afterwards.
6 changes: 0 additions & 6 deletions Source/DSPSpy/build.sh

This file was deleted.

4 changes: 0 additions & 4 deletions Source/DSPSpy/sbuild.sh

This file was deleted.

81 changes: 81 additions & 0 deletions Source/DSPSpy/tests/cr_test.ds
@@ -0,0 +1,81 @@
incdir "tests"
include "dsp_base.inc"

; Tests the behavior of SI, SRS, and LRS when CR is changed

; Register that is writable but with contents that doesn't matter (COEF_A1_0)
TEST_REG: equ 0xFFA0 ; 0xFF00 (not writable)
; This is separate because SRS and SI currently require value 0..7f or ff80..ffff,
; though the actual behavior doesn't match that
TEST_ADDR: equ 0xFFA0 ; 0x0000
; Memory addresses
TEST_MEM: equ 0x00A0 ; 0x0000
TEST_MEM_2: equ 0x01A0 ; 0x0100

LRI $AC0.L, #0xf00f
SR @TEST_REG, $AC0.L
SR @TEST_MEM, $AC0.L
SR @TEST_MEM_2, $AC0.L
CALL send_regs

; Observed: writes to TEST_REG
SI @TEST_ADDR, #0xf11f
CALL send_regs

LRI $AC0.L, #0xf22f
; Observed: writes to TEST_REG
SRS @TEST_ADDR, $AC0.L
CALL send_regs

LRI $CR, #0x0000
; Observed: still writes to TEST_REG
SI @TEST_ADDR, #0xf33f
CALL send_regs

LRI $AC0.L, #0xf44f
; Observed: writes to TEST_MEM
SRS @TEST_ADDR, $AC0.L
CALL send_regs

LRI $CR, #0x0001
; Observed: still writes to TEST_REG
SI @TEST_ADDR, #0xf55f
CALL send_regs

LRI $AC0.L, #0xf66f
; Observed: writes to TEST_MEM_2
SRS @TEST_ADDR, $AC0.L
CALL send_regs

; At this point, TEST_REG should be f55f, TEST_MEM should be f44f,
; and TEST_MEM_2 should be f66f. Test the behavior of LRS.
; Changes to prod.l are for display only.
LRI $CR, #0x00ff
LRI $prod.l, #0xf55f
LRS $AC0.L, @TEST_ADDR
CALL send_regs

LRI $CR, #0x0000
LRI $prod.l, #0xf44f
LRS $AC0.L, @TEST_ADDR
CALL send_regs

LRI $CR, #0x0001
LRI $prod.l, #0xf66f
LRS $AC0.L, @TEST_ADDR
CALL send_regs

; We're done, DO NOT DELETE THIS LINE
JMP end_of_test

send_regs:
; For display purposes only
LRI $prod.m1, #TEST_REG
LRI $prod.h, #TEST_MEM
LRI $prod.m2, #TEST_MEM_2
; Actual registers
LR $AC1.L, @TEST_REG
LR $AC0.M, @TEST_MEM
LR $AC1.M, @TEST_MEM_2
CALL send_back
RET
290 changes: 290 additions & 0 deletions docs/DSP/GameCube_DSP_Users_Manual/.gitignore
@@ -0,0 +1,290 @@
GameCube_DSP_Users_Manual.pdf

## Core latex/pdflatex auxiliary files:
*.aux
*.lof
*.log
*.lot
*.fls
*.out
*.toc
*.fmt
*.fot
*.cb
*.cb2
.*.lb

## Intermediate documents:
*.dvi
*.xdv
*-converted-to.*
# these rules might exclude image files for figures etc.
# *.ps
# *.eps
# *.pdf

## Generated if empty string is given at "Please type another file name for output:"
.pdf

## Bibliography auxiliary files (bibtex/biblatex/biber):
*.bbl
*.bcf
*.blg
*-blx.aux
*-blx.bib
*.run.xml

## Build tool auxiliary files:
*.fdb_latexmk
*.synctex
*.synctex(busy)
*.synctex.gz
*.synctex.gz(busy)
*.pdfsync

## Build tool directories for auxiliary files
# latexrun
latex.out/

## Auxiliary and intermediate files from other packages:
# algorithms
*.alg
*.loa

# achemso
acs-*.bib

# amsthm
*.thm

# beamer
*.nav
*.pre
*.snm
*.vrb

# changes
*.soc

# comment
*.cut

# cprotect
*.cpt

# elsarticle (documentclass of Elsevier journals)
*.spl

# endnotes
*.ent

# fixme
*.lox

# feynmf/feynmp
*.mf
*.mp
*.t[1-9]
*.t[1-9][0-9]
*.tfm

#(r)(e)ledmac/(r)(e)ledpar
*.end
*.?end
*.[1-9]
*.[1-9][0-9]
*.[1-9][0-9][0-9]
*.[1-9]R
*.[1-9][0-9]R
*.[1-9][0-9][0-9]R
*.eledsec[1-9]
*.eledsec[1-9]R
*.eledsec[1-9][0-9]
*.eledsec[1-9][0-9]R
*.eledsec[1-9][0-9][0-9]
*.eledsec[1-9][0-9][0-9]R

# glossaries
*.acn
*.acr
*.glg
*.glo
*.gls
*.glsdefs
*.lzo
*.lzs

# uncomment this for glossaries-extra (will ignore makeindex's style files!)
# *.ist

# gnuplottex
*-gnuplottex-*

# gregoriotex
*.gaux
*.glog
*.gtex

# htlatex
*.4ct
*.4tc
*.idv
*.lg
*.trc
*.xref

# hyperref
*.brf

# knitr
*-concordance.tex
# TODO Uncomment the next line if you use knitr and want to ignore its generated tikz files
# *.tikz
*-tikzDictionary

# listings
*.lol

# luatexja-ruby
*.ltjruby

# makeidx
*.idx
*.ilg
*.ind

# minitoc
*.maf
*.mlf
*.mlt
*.mtc[0-9]*
*.slf[0-9]*
*.slt[0-9]*
*.stc[0-9]*

# minted
_minted*
*.pyg

# morewrites
*.mw

# newpax
*.newpax

# nomencl
*.nlg
*.nlo
*.nls

# pax
*.pax

# pdfpcnotes
*.pdfpc

# sagetex
*.sagetex.sage
*.sagetex.py
*.sagetex.scmd

# scrwfile
*.wrt

# sympy
*.sout
*.sympy
sympy-plots-for-*.tex/

# pdfcomment
*.upa
*.upb

# pythontex
*.pytxcode
pythontex-files-*/

# tcolorbox
*.listing

# thmtools
*.loe

# TikZ & PGF
*.dpth
*.md5
*.auxlock

# todonotes
*.tdo

# vhistory
*.hst
*.ver

# easy-todo
*.lod

# xcolor
*.xcp

# xmpincl
*.xmpi

# xindy
*.xdy

# xypic precompiled matrices and outlines
*.xyc
*.xyd

# endfloat
*.ttt
*.fff

# Latexian
TSWLatexianTemp*

## Editors:
# WinEdt
*.bak
*.sav

# Texpad
.texpadtmp

# LyX
*.lyx~

# Kile
*.backup

# gummi
.*.swp

# KBibTeX
*~[0-9]*

# TeXnicCenter
*.tps

# auto folder when using emacs and auctex
./auto/*
*.el

# expex forward references with \gathertags
*-tags.tex

# standalone packages
*.sta

# Makeindex log files
*.lpz

# xwatermark package
*.xwm

# REVTeX puts footnotes in the bibliography by default, unless the nofootinbib
# option is specified. Footnotes are the stored in a file with suffix Notes.bib.
# Uncomment the next line to have this generated file ignored.
#*Notes.bib
2,141 changes: 1,758 additions & 383 deletions docs/DSP/GameCube_DSP_Users_Manual/GameCube_DSP_Users_Manual.tex

Large diffs are not rendered by default.