A DSP chip from Capcom; it is actually a Hitachi HG51B169, as confirmed by decapping. It has up to 1024 words of 24-bit instructions, running at 20.000MHz. and is used in 2 games:
The test program is located at $029C00
.
CX4 Interface Summary
The CX4 has 16 multi-purpose triple-byte registers in the memory range $7F80
through $7FAF
.
Mode | Size | Bank | RAM | Registers |
---|---|---|---|---|
0x20H | 2Mbit - 32Mbit | $00 - $3F |
$6000 - $6BFF |
$7F40 - $7FAF |
CX4 Registers
Registers | Function |
---|---|
$7F40 - $7F47 |
DMA Transfer (?) |
$7F49 - $7F4B |
ROM Offset |
$7F4D - $7F4E |
Page Select |
$7F4F |
Instruction Pointer |
Start Address = ((Page_Select * 256) + Instruction Pointer) * 2) + ROM_Offset
I ran some tests on a Mashmods flash programmer and MMX2 cart and these two cases give the same results.
Page Select = $000E and ROM_Offset = $028000
Page Select = $010E and ROM_Offset = $008000
CX4 Memory Layout
Program ROM is obviously 256x16-bit pages at a time (taken from the SNES ROM). Program RAM is 2x256x16-bit (two banks). Data ROM is 1024x24-bit (only ROM internal to the Cx4). Data RAM is 4x384x16-bit. Call stack is 8-levels deep, at least 16-bits wide.
Memory Type | Size | Location |
---|---|---|
Program ROM | 256 x 16-bit | SNES ROM |
Program RAM | 2 x 256 x 16-bit (2 Banks) | |
Data ROM | 1024 x 24-bit | CX4 Internal |
Data RAM | 4 x 384 x 16-bit |
CX4 Data ROM Layout
Location | Table Data |
---|---|
000 - 0FF | Inverse |
100 - 1FF | Square Root (sqrt) |
200 - 27F | First Quadrant Sine (sin) |
280 - 2FF | First Quadrant Arcsine (asin) |
300 - 37F | First Quadrant Tangent (tan) |
380 - 3FF | First Quadrant Cosine (cos) |
CX4 Command Summary
Commands are executed on the CX4 by writing the command to $7F4F
while bit 6 of $7F5E
is clear. Bit 6 of $7F5E
will stay set until the command has completed, at which time output data will be available. See individual commands for input and output parameter addresses.
Command | Function Name |
---|---|
$00 |
Sprite Functions |
$01 |
Wireframe |
$05 |
Propulsion |
$0D |
Set Vector Length |
$10 |
Triangle |
$13 |
Triangle |
$15 |
Pythagorean |
$1F |
Arc-Tan |
$22 |
Trapezoid |
$25 |
Multiply |
$2D |
Transform Coordinates |
Test Functions
Command | Function Name |
---|---|
$00 - $3F |
Command Shift |
`$40 | Sum |
`$54 | Square |
`$5C | Immediate Register |
$5E - $7E |
Immediate Register (Multiple) |
`$89 | Immediate ROM |
The following steps must be completed when accessing these functions:
- Wait until bit 6 of
$xx7F5e
is clear - Write
$0E
to$xx7F4D
- Write
$00
to$xx7F4E
- Write
$01
to$xx7F48
- Wait until bit 6 of
$xx7F5E
is clear - Write command to
$xx7F4F
- Wait until bit 6 of
$xx7F5E
is clear
CX4 Source Code Contributors
Reversed engineered by zsknight and documented by anomie. Source code and additional information by Overload.
unsigned char CX4_Ram[0x0C00];
unsigned char CX4_Reg[0x0100];
#define uint24 unsigned int
// 24 Bit Work Registers
uint24 R0, R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R15;
const uint24 CX4_SinTable[256] = {
0x000000, 0x000324, 0x000648, 0x00096c, 0x000c8f, 0x000fb2, 0x0012d5, 0x0015f6,
0x001917, 0x001c37, 0x001f56, 0x002273, 0x002590, 0x0028aa, 0x002bc4, 0x002edb,
0x0031f1, 0x003505, 0x003817, 0x003b26, 0x003e33, 0x00413e, 0x004447, 0x00474d,
0x004a50, 0x004d50, 0x00504d, 0x005347, 0x00563e, 0x005931, 0x005c22, 0x005f0e,
0x0061f7, 0x0064dc, 0x0067bd, 0x006a9b, 0x006d74, 0x007049, 0x007319, 0x0075e5,
0x0078ad, 0x007b70, 0x007e2e, 0x0080e7, 0x00839c, 0x00864b, 0x0088f5, 0x008b9a,
0x008e39, 0x0090d3, 0x009368, 0x0095f6, 0x00987f, 0x009b02, 0x009d7f, 0x009ff6,
0x00a267, 0x00a4d2, 0x00a736, 0x00a994, 0x00abeb, 0x00ae3b, 0x00b085, 0x00b2c8,
0x00b504, 0x00b73a, 0x00b968, 0x00bb8f, 0x00bdae, 0x00bfc7, 0x00c1d8, 0x00c3e2,
0x00c5e4, 0x00c7de, 0x00c9d1, 0x00cbbb, 0x00cd9f, 0x00cf7a, 0x00d14d, 0x00d318,
0x00d4db, 0x00d695, 0x00d848, 0x00d9f2, 0x00db94, 0x00dd2d, 0x00debe, 0x00e046,
0x00e1c5, 0x00e33c, 0x00e4aa, 0x00e60f, 0x00e76b, 0x00e8bf, 0x00ea09, 0x00eb4b,
0x00ec83, 0x00edb2, 0x00eed8, 0x00eff5, 0x00f109, 0x00f213, 0x00f314, 0x00f40b,
0x00f4fa, 0x00f5de, 0x00f6ba, 0x00f78b, 0x00f853, 0x00f912, 0x00f9c7, 0x00fa73,
0x00fb14, 0x00fbac, 0x00fc3b, 0x00fcbf, 0x00fd3a, 0x00fdab, 0x00fe13, 0x00fe70,
0x00fec4, 0x00ff0e, 0x00ff4e, 0x00ff84, 0x00ffb1, 0x00ffd3, 0x00ffec, 0x00fffb,
0x000000, 0xfffcdb, 0xfff9b7, 0xfff693, 0xfff370, 0xfff04d, 0xffed2a, 0xffea09,
0xffe6e8, 0xffe3c8, 0xffe0a9, 0xffdd8c, 0xffda6f, 0xffd755, 0xffd43b, 0xffd124,
0xffce0e, 0xffcafa, 0xffc7e8, 0xffc4d9, 0xffc1cc, 0xffbec1, 0xffbbb8, 0xffb8b2,
0xffb5af, 0xffb2af, 0xffafb2, 0xffacb8, 0xffa9c1, 0xffa6ce, 0xffa3dd, 0xffa0f1,
0xff9e08, 0xff9b23, 0xff9842, 0xff9564, 0xff928b, 0xff8fb6, 0xff8ce6, 0xff8a1a,
0xff8752, 0xff848f, 0xff81d1, 0xff7f18, 0xff7c63, 0xff79b4, 0xff770a, 0xff7465,
0xff71c6, 0xff6f2c, 0xff6c97, 0xff6a09, 0xff6780, 0xff64fd, 0xff6280, 0xff6009,
0xff5d98, 0xff5b2d, 0xff58c9, 0xff566b, 0xff5414, 0xff51c4, 0xff4f7a, 0xff4d37,
0xff4afb, 0xff48c5, 0xff4697, 0xff4470, 0xff4251, 0xff4038, 0xff3e27, 0xff3c1e,
0xff3a1b, 0xff3821, 0xff362e, 0xff3444, 0xff3260, 0xff3085, 0xff2eb2, 0xff2ce7,
0xff2b24, 0xff296a, 0xff27b7, 0xff260d, 0xff246b, 0xff22d2, 0xff2141, 0xff1fb9,
0xff1e3a, 0xff1cc3, 0xff1b55, 0xff19f0, 0xff1894, 0xff1740, 0xff15f6, 0xff14b4,
0xff137c, 0xff124d, 0xff1127, 0xff100a, 0xff0ef6, 0xff0dec, 0xff0ceb, 0xff0bf4,
0xff0b05, 0xff0a21, 0xff0945, 0xff0874, 0xff07ac, 0xff06ed, 0xff0638, 0xff058d,
0xff04eb, 0xff0453, 0xff03c4, 0xff0340, 0xff02c5, 0xff0254, 0xff01ec, 0xff018f,
0xff013b, 0xff00f1, 0xff00b1, 0xff007b, 0xff004e, 0xff002c, 0xff0013, 0xff0004
}
The resolution of an Angle is 9 bits, 8 bits data plus a sign bit. Angles range from -180° ~ +180°.
uint24 CX4_Sin(uint24 Rx){
R0 = Rx & 0x1ff;
if (R0 & 0x100) R0 ^= 0x1ff;
if (R0 & 0x080) R0 ^= 0x0ff;
if (Rx & 0x100)
return CX4_SinTable[R0 + 0x80];
else
return CX4_SinTable[R0];
}
uint24 CX4_Cos(uint24 Rx){
return CX4_Sin(Rx + 0x080);
}
bool CX4_Adc24(uint24 &A, uint24 B, bool Carry){
uint32 C = (A & 0xffffff) + (B & 0xffffff);
if (Carry) C++;
A = (uint24) C;
return (C & 0x1000000);
}
void CX4_Mul24(uint24 A, uint24 B, uint24 &CL, uint24 &CH){
if (B & 0x800000){
A = -A;
B = -B;
}
CL = CH = 0;
uint24 AdderL = A;
uint24 AdderH = 0;
if (A & 0x800000) AdderH--;
B &= 0xffffff;
while (B){
if (B & 1)
CX4_Adc24(CH, AdderH, CX4_Adc24(CL, AdderL, false));
AdderH <<= 1;
if (AdderL & 0x800000) AdderH++;
AdderL <<= 1;
B >>= 1;
}
}
uint24 CX4_Ldr24(uint32 ofs){
return (CX4_Reg[ofs + 0] | (CX4_Reg[ofs + 1] << 8) | (CX4_Reg[ofs + 2] << 16));
}
void CX4_Str24(uint32 ofs, uint24 Rx){
CX4_Reg[ofs + 0] = (uint8) (Rx);
CX4_Reg[ofs + 1] = (uint8) (Rx >> 8);
CX4_Reg[ofs + 2] = (uint8) (Rx >> 16);
}
0x01 - Wireframe
N/A
0x05 - Propulsion
N/A
0x0D - Set Vector Length
N/A
0x10 - Triangle
I/O | Address | Name / Variable |
---|---|---|
Input | $7F80 - $7F82 |
Angle (R0) |
$7F83 - $7F85 |
Radius (R1) | |
Output | $7F86 - $7F88 |
X (R2) |
$7F89 - $7F8B |
Y (R3) |
void CX4_Triangle16(){
R0 = CX4_Ldr24(0x80);
R1 = CX4_Ldr24(0x83);
R4 = R0 & 0x1ff;
if (R1 & 0x8000) R1 |= 0xff0000;
CX4_Mul24(CX4_Cos(R4), R1, R5, R2);
R5 = (R5 >> 16) & 0xff;
R2 = (R2 << 8) + R5;
CX4_Mul24(CX4_Sin(R4), R1, R5, R3);
R5 = (R5 >> 16) & 0xff;
R3 = (R3 << 8) + R5;
CX4_Str24(0x80, R0);
CX4_Str24(0x83, R1);
CX4_Str24(0x86, R2);
CX4_Str24(0x89, R3);
CX4_Str24(0x8c, R4);
CX4_Str24(0x8f, R5);
}
0x13 - Triangle
I/O | Address | Name / Variable |
---|---|---|
Input | $7F80 - $7F82 |
Angle (R0) |
$7F83 - $7F85 |
Radius (R1) | |
Output | $7F86 - $7F88 |
X (R2) |
$7F89 - $7F8B |
Y (R3) |
void CX4_Triangle24(){
R0 = CX4_Ldr24(0x80);
R1 = CX4_Ldr24(0x83);
R4 = R0 & 0x1ff;
CX4_Mul24(CX4_Cos(R4), R1, R5, R2);
R5 = (R5 >> 8) & 0xffff;
R2 = (R2 << 16) + R5;
CX4_Mul24(CX4_Sin(R4), R1, R5, R3);
R5 = (R5 >> 8) & 0xffff;
R3 = (R3 << 16) + R5;
CX4_Str24(0x80, R0);
CX4_Str24(0x83, R1);
CX4_Str24(0x86, R2);
CX4_Str24(0x89, R3);
CX4_Str24(0x8c, R4);
CX4_Str24(0x8f, R5);
}
0x15 - Pythagorean
N/A
0x1F - Arc-Tan
N/A
0x22 - Trapezoid
N/A
0x25 - Multiply
I/O | Address | Name / Variable |
---|---|---|
Input | $7F80 - $7F82 |
Multiplicand (R0) |
$7F83 - $7F85 |
Multiplier (R1) | |
Output | $7F80 - $7F85 |
Product (R1:R0) |
void CX4_Multiply(){
R0 = CX4_Ldr24(0x80);
R1 = CX4_Ldr24(0x83);
CX4_Mul24(R0, R1, R0, R1);
CX4_Str24(0x80, R0);
CX4_Str24(0x83, R1);
}
0x2D - Transform Coordinates
N/A
0x40 - Sum
I/O | Address | Name / Variable |
---|---|---|
Input | $6000 - $67FF |
|
Output | $7F80 - $7F82 |
(R0) |
void CX4_Sum(){
R0 = 0;
for (uint32 i = 0; i < 0x800; i++) R0 += CX4_Ram[i];
CX4_Str24(0x80, R0);
}
0x54 - Square
I/O | Address | Name / Variable |
---|---|---|
Input | $7F80 - $7F82 |
(R0) |
Output | $7F83 - $7F88 |
(R2:R1) |
void CX4_Square(){
R0 = CX4_Ldr24(0x80);
CX4_Mul24(R0, R0, R1, R2);
CX4_Str24(0x83, R1);
CX4_Str24(0x86, R2);
}
0x5C - Immediate Register
I/O | Address | Name / Variable |
---|---|---|
Input | None | |
Output | $6000 - $6030 |
|
$7F80 - $7F82 |
(R0) |
const unsigned char ImmediateData[48] = {
0x00, 0xfe, 0xff, 0x00, 0x01, 0x00, 0xfe, 0xff, 0xff, 0x01, 0x00, 0x00,
0xff, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x00, 0x7f, 0xff, 0x00, 0x80, 0x00,
0x7f, 0xff, 0xff, 0x80, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00
};
void CX4_ImmediateReg(){
R0 = 0;
for (uint32 i = 48; i > 0; --i){
CX4_Ram[R0] = ImmediateData[i];
R0++;
}
CX4_Str24(0x80, R0);
}
0x5E - 0x7E - Immediate Register (Multiple)
I/O | Address | Name / Variable |
---|---|---|
Input | $7F80 - $7F82 |
(R0) |
Output | $6XXX - $6XXX |
|
$7F80 - $7F82 |
(R0) |
This command transfers a preset triple-byte pattern into the memory offset specified in R0
.
Command | Number of Bytes |
---|---|
$5E |
48 |
$60 |
45 |
$62 |
42 |
$64 |
39 |
$66 |
36 |
$68 |
33 |
$6A |
30 |
$6C |
27 |
$6E |
24 |
$70 |
21 |
$72 |
18 |
$74 |
15 |
$76 |
12 |
$78 |
9 |
$7A |
6 |
$7C |
3 |
void CX4_ImmediateReg(uint32 NumberOfBytes){
R0 = CX4_Ldr24(0x80);
for (uint32 i = NumberOfBytes; i > 0; --i){
CX4_Ram[R0 & 0xfff] = ImmediateData[i];
R0++;
}
CX4_Str24(0x80, R0);
}
0x89 - Immediate ROM
I/O | Address | Name / Variable |
---|---|---|
Input | None | |
Output | $7F80 - $7F85 |
(R1:R0) |
void CX4_ImmediateROM(){
R0 = 0x054336;
R1 = 0xffffff;
CX4_Str24(0x80, R0);
CX4_Str24(0x83, R1);
}
CX4 Data ROM Extraction
ROM_Offset = 028000
Page Select = 0006
PC = 04
04: 606b lda r11
05: 7000 sta rp
06: 6008 lda rom
07: e06c sta r12
08: 3c00 ret
Patents
5740404, 5513374, 5426600, 5440747, 5535417, 5381360, 5832258
Pinout
A0..23, D0..7, RD, WR, RST, etc. are connected to the cart edge
RA0..20, RD0..7, ROE, RWE, RCE, etc. are connected to the ROM
RCE1 is CE on the first ROM if 2x8Mbit ROMs are used, or the only ROM if a 16Mbit ROM is used
RCE2 is CE on the second ROM if 2x8Mbit ROMs are used
SRCE is CE on the SRAM chip (never used on official games)
1 A3 21 A15 41 RA8 61 /IRQ
2 A4 22 A14 42 RA7 62 D7
3 A5 23 A13 43 RA6 63 D6
4 A6 24 A12 44 RA5 64 D5
5 A7 25 SRCE 45 RA4 65 D4
6 A8 26 RCE1 46 RA3 66 Vcc
7 A9 27 RCE2 47 RA2 67 D3
8 A10 28 RA19 48 RA1 68 D2
9 A11 29 RA18 49 RA0 69 D1
10 GND 30 RA17 50 GND 70 D0
11 XIN 31 Vcc 51 RWE 71 Vcc
12 XOUT 32 RA16 52 ROE 72 RST
13 A23 33 RA15 53 RD7 73 GND
14 A22 34 RA20 54 RD6 74 ???
15 A21 35 RA14 55 RD5 75 ???
16 A20 36 RA13 56 RD4 76 RD
17 A19 37 RA12 57 RD3 77 WR
18 A18 38 RA11 58 RD2 78 A0
19 A17 39 RA10 59 RD1 79 A1
20 A16 40 RA9 60 RD0 80 A2
Pins 74 and 75 are not internally connected to GND, but are tied to GND on the MMX2 and MMX3 PCB's. I have no idea what they're for, but they're probably inputs of some kind. Possibly one of them could be a Hi/LoROM switch like pin 10 on the MAD-1, but that is purely speculation at this point.
Pin 51 (RWE) is asserted low for writes to both the ROM and SRAM address space, so it can be used to write to reprogrammable ROM chips in-circuit, as well as writing to SRAM.
Information provided by Overload (codeviolation@hotmail.com) / Dr. Decapitator / byuu. Jonas Quinn - Program ROM discovery. Overload - Data ROM extraction. Segher - Instruction set reverse-engineering. qwertymodo - Pinout.