/* V2MemTest - A CLI Tool to test & fix Voodoo² TMU System
* Copyright (C) 2026 ChaCha
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include "cvg.h"
#include
#include "sst1init.h"
#include "fxpci.h"
#include "Utils.h"
#include "V2MemTest.h"
#include "FaultSources.h"
#include "Utils.h"
#include "Draw.h"
#include "Test_Address.h"
typedef struct _def_sTestAddress {
FxU32 u32Addr;
unsigned char nBank; /* RAS0 and RAS1 (Front/Back) */
unsigned char nColBit;
unsigned char nRowBit;
}def_sTestAddress;
unsigned long
RenderTestAddress( sst1DeviceInfoStruct * const devInfo,
FxU32 * const sst,
SstRegs * const sstregs,
const char ucNumTMU,
const unsigned char RamSizeMB,
def_sFaultSourceScoreRec* const pFaultSrcCtx)
{
unsigned long NbErr=0;
sst1InitIdle(sst);
unsigned long _trexInit0 = IGET(SST_TREX(sstregs,ucNumTMU)->trexInit0);
devInfo->tmuInit0[(int)ucNumTMU] = SST_TREXINIT0_DEFAULT ;
ISET(SST_TREX(sstregs,ucNumTMU)->trexInit0, devInfo->tmuInit0[(int)ucNumTMU]);
sst1InitIdle(sst);
/* set downstream TMUs to passthrough */
for (int i=0; itextureMode, SST_TC_PASS | SST_TCA_PASS);
const def_sTestAddress add_list[] =
{
/* Bank0 */
{0x000000,0,0,0},
/* not addressable because its a 64bit bus
{0x000001,0,0,0,0}
{0x000002,0,0,0,0}
{0x000004,0,0,0,0}
*/
{0x000008,0,1,0},
/* {target pin} */
{0x000010,0,2,0},
{0x000020,0,3,0},
{0x000040,0,4,0},
{0x000080,0,5,0},
{0x000100,0,6,0},
{0x000200,0,7,0},
{0x000400,0,8,0},
{0x000800,0,9,0},
{0x001000,0,0,1},
{0x002000,0,0,2},
{0x004000,0,0,3},
{0x008000,0,0,4},
{0x010000,0,0,5},
{0x020000,0,0,6},
{0x040000,0,0,7},
{0x080000,0,0,8},
{0x100000,0,0,9},
/* Bank1 */
{0x200000,1,0,0},
/* not addressable because its a 64bit bus
{0x200001,1,0,0,0}
{0x200002,1,0,0,0}
{0x200004,1,0,0,0}
*/
{0x200008,1,1,0},
/* {target pin} */
{0x200010,1,2,0},
{0x200020,1,3,0},
{0x200040,1,4,0},
{0x200080,1,5,0},
{0x200100,1,6,0},
{0x200200,1,7,0},
{0x200400,1,8,0},
{0x200800,1,9,0},
{0x201000,1,0,1},
{0x202000,1,0,2},
{0x204000,1,0,3},
{0x208000,1,0,4},
{0x210000,1,0,5},
{0x220000,1,0,6},
{0x240000,1,0,7},
{0x280000,1,0,8},
{0x300000,1,0,9},
};
logT("testing %d MB of memory\n",RamSizeMB);
for(unsigned char idx=0;
idx < (sizeof(add_list)/sizeof(def_sTestAddress));
idx++)
{
/* Skipping unsupported addresses */
if(RamSizeMB<4 && add_list[idx].u32Addr >= 0x300000) continue;
if(RamSizeMB<3 && add_list[idx].u32Addr >= 0x200000) continue;
if(RamSizeMB<2 && add_list[idx].u32Addr >= 0x100000) continue;
logT("idx = %d\n",idx);
logT("u32Addr = 0x%08X\n",add_list[idx].u32Addr);
const uint32_t TestVal1 = get_notnull_random_balanced_mByte();
logT("TestVal1 = %08X\n",TestVal1);
uint32_t TestVal2 = 0;
do
{
TestVal2 = get_notnull_random_balanced_mByte();
}
while((count_bit32((TestVal2 ^ TestVal1)) < 12));
logT("TestVal2 = %08X\n",TestVal2);
uint32_t TestValBlank1 = 0;
do
{
TestValBlank1 = get_notnull_random_balanced_mByte();
}
while( (count_bit32((TestValBlank1 ^ TestVal1) & 0x0000FFFF) < 6)
|| (count_bit32((TestValBlank1 ^ TestVal1) & 0xFFFF0000) < 6)
|| (count_bit32((TestValBlank1 ^ TestVal2)) < 12));
logT("TestValBlank1 = %08X\n",TestValBlank1);
uint32_t TestValBlank2 = 0;
do
{
TestValBlank2 = get_notnull_random_balanced_mByte();
}
while( (count_bit32((TestValBlank2 ^ TestVal2) & 0x0000FFFF) < 6)
|| (count_bit32((TestValBlank2 ^ TestVal2) & 0xFFFF0000) < 6)
|| (count_bit32((TestValBlank2 ^ TestValBlank1)) < 12)
|| (count_bit32((TestValBlank2 ^ TestVal1)) < 12));
logT("TestValBlank2 = %08X\n",TestValBlank2);
/* Clearing memory targets */
for(unsigned char idxclr=0;
idxclr < sizeof(add_list)/sizeof(def_sTestAddress);
idxclr++)
{
/* Skipping unsupported addresses */
if(RamSizeMB<4 && add_list[idxclr].u32Addr >= 0x300000) continue;
if(RamSizeMB<3 && add_list[idxclr].u32Addr >= 0x200000) continue;
if(RamSizeMB<2 && add_list[idxclr].u32Addr >= 0x100000) continue;
/* set base mem @ */
ISET(SST_TREX(sstregs,ucNumTMU)->texBaseAddr, (add_list[idxclr].u32Addr>>3));
/* set @ to first line, using bits 00..31*/
volatile const FxU32 *texAddrBlank
= (ucNumTMU<<(21-2))
+ (((FxU32)0)<<(17-2)) /*LOD0*/
+ (FxU32 *)SST_TEX_ADDRESS(sst);
/* write the value */
ISET(texAddrBlank[0], TestValBlank1);
/* set @ to second line, to use bits 32..63*/
volatile const FxU32 *texAddrBlank2
= (ucNumTMU<<(21-2))
+ (((FxU32)0)<<(17-2)) /*LOD0*/
+ (1<<(9-2))
+ (FxU32 *)SST_TEX_ADDRESS(sst);
/* write the value */
ISET(texAddrBlank2[0], TestValBlank2);
}
/* set base mem @ */
ISET(SST_TREX(sstregs,ucNumTMU)->texBaseAddr, (add_list[idx].u32Addr>>3));
/* set @ to first line, using bits 00..31*/
volatile const FxU32 *texAddr
= (ucNumTMU<<(21-2))
+ (((FxU32)0)<<(17-2)) /*LOD0*/
+ (FxU32 *)SST_TEX_ADDRESS(sst);
/* write the value */
ISET(texAddr[0], TestVal1);
/* set @ to second line, to use bits 32..63*/
volatile const FxU32 *texAddr2
= (ucNumTMU<<(21-2))
+ (((FxU32)0)<<(17-2)) /*LOD0*/
+ (1<<(9-2))
+ (FxU32 *)SST_TEX_ADDRESS(sst);
/* write the value */
ISET(texAddr2[0], TestVal2);
/* Checking expected memory map is there */
for(unsigned char idxdraw=0;
idxdraw < sizeof(add_list) / sizeof(def_sTestAddress);
idxdraw++)
{
if(RamSizeMB<4 && add_list[idxdraw].u32Addr >= 0x300000) continue;
if(RamSizeMB<3 && add_list[idxdraw].u32Addr >= 0x200000) continue;
if(RamSizeMB<2 && add_list[idxdraw].u32Addr >= 0x100000) continue;
logT("idxdraw = %d, row = %d; col = %d\n",idxdraw,add_list[idxdraw].nRowBit,add_list[idxdraw].nColBit);
//if(idxdraw > idx) break;
clearScreen(sstregs,0x00000000,2,2);
/* set to mem addr */
ISET(SST_TREX(sstregs,ucNumTMU)->texBaseAddr, (add_list[idxdraw].u32Addr >> 3));
/* draw a 2x2 square */
drawSquare(sstregs, ucNumTMU, 0, 0, 2);
sst1InitIdle(sst);
/* first line, to use bits 00..31 */
const uint32_t L1 = IGET(sst[(SST_LFB_ADDR>>2) + 0]);
/* second line, to use bits 32..63 */
const uint32_t L2 = IGET(sst[(SST_LFB_ADDR>>2) + (2048>>2) + 0]);
const uint32_t ErrorMark_L1 = (idxdraw == idx) ? (L1 ^ TestVal1) : (L1 ^ TestValBlank1);
const uint32_t ErrorMark_L2 = (idxdraw == idx) ? (L2 ^ TestVal2) : (L2 ^ TestValBlank2);
const uint32_t ErrorMarkOther_L1 = (idxdraw == idx) ? (L1 ^ TestValBlank1) : (L1 ^ TestVal1);
const uint32_t ErrorMarkOther_L2 = (idxdraw == idx) ? (L2 ^ TestValBlank2) : (L2 ^ TestVal2);
if(ErrorMark_L1 || ErrorMark_L2)
{
const def_eFaultSource TMUTexADDR_0_0 = (ucNumTMU == 0) ? U9_TMU0_TEX_ADDR_0_0 : U8_TMU1_TEX_ADDR_0_0;
const def_eFaultSource TMUTexADDR_1_0 = (ucNumTMU == 0) ? U9_TMU0_TEX_ADDR_1_0 : U8_TMU1_TEX_ADDR_1_0;
const def_eFaultSource TMUTexADDR_2_0 = (ucNumTMU == 0) ? U9_TMU0_TEX_ADDR_2_0 : U8_TMU1_TEX_ADDR_2_0;
const def_eFaultSource TMUTexADDR_3_0 = (ucNumTMU == 0) ? U9_TMU0_TEX_ADDR_3_0 : U8_TMU1_TEX_ADDR_3_0;
const def_eFaultSource _MEMChip_B0_0_A0 = (ucNumTMU == 0) ? U14_A0 : U13_A0;
const def_eFaultSource _MEMChip_B0_1_A0 = (ucNumTMU == 0) ? U12_A0 : U11_A0;
const def_eFaultSource _MEMChip_B0_2_A0 = (ucNumTMU == 0) ? U18_A0 : U16_A0;
const def_eFaultSource _MEMChip_B0_3_A0 = (ucNumTMU == 0) ? U17_A0 : U15_A0;
const def_eFaultSource _MEMChip_B1_0_A0 = (ucNumTMU == 0) ? U23_A0 : U27_A0;
const def_eFaultSource _MEMChip_B1_1_A0 = (ucNumTMU == 0) ? U24_A0 : U28_A0;
const def_eFaultSource _MEMChip_B1_2_A0 = (ucNumTMU == 0) ? U25_A0 : U29_A0;
const def_eFaultSource _MEMChip_B1_3_A0 = (ucNumTMU == 0) ? U26_A0 : U30_A0;
const def_eFaultSource MEMChip_0_A0 = (add_list[idxdraw].nBank == 0) ? _MEMChip_B0_0_A0 : _MEMChip_B1_0_A0;
const def_eFaultSource MEMChip_1_A0 = (add_list[idxdraw].nBank == 0) ? _MEMChip_B0_1_A0 : _MEMChip_B1_1_A0;
const def_eFaultSource MEMChip_2_A0 = (add_list[idxdraw].nBank == 0) ? _MEMChip_B0_2_A0 : _MEMChip_B1_2_A0;
const def_eFaultSource MEMChip_3_A0 = (add_list[idxdraw].nBank == 0) ? _MEMChip_B0_3_A0 : _MEMChip_B1_3_A0;
const def_eFaultSource MEMChip_0_A0_Other = (add_list[idxdraw].nBank == 1) ? _MEMChip_B0_0_A0 : _MEMChip_B1_0_A0;
const def_eFaultSource MEMChip_1_A0_Other = (add_list[idxdraw].nBank == 1) ? _MEMChip_B0_1_A0 : _MEMChip_B1_1_A0;
const def_eFaultSource MEMChip_2_A0_Other = (add_list[idxdraw].nBank == 1) ? _MEMChip_B0_2_A0 : _MEMChip_B1_2_A0;
const def_eFaultSource MEMChip_3_A0_Other = (add_list[idxdraw].nBank == 1) ? _MEMChip_B0_3_A0 : _MEMChip_B1_3_A0;
const def_eFaultSource RES_TEXADDR_0_L = (ucNumTMU == 0) ? RA35 : RA31;
const def_eFaultSource RES_TEXADDR_0_H = (ucNumTMU == 0) ? RA34 : RA30;
const def_eFaultSource RES_TEXADDR_0_8 = (ucNumTMU == 0) ? R116 : R113;
const def_eFaultSource RES_TEXADDR_1_L = (ucNumTMU == 0) ? RA26 : RA25;
const def_eFaultSource RES_TEXADDR_1_H = (ucNumTMU == 0) ? RA24 : RA23;
const def_eFaultSource RES_TEXADDR_1_8 = (ucNumTMU == 0) ? R102 : R101;
const def_eFaultSource RES_TEXADDR_2_L = (ucNumTMU == 0) ? RA33 : RA29;
const def_eFaultSource RES_TEXADDR_2_H = (ucNumTMU == 0) ? RA28 : RA27;
const def_eFaultSource RES_TEXADDR_2_8 = (ucNumTMU == 0) ? R112 : R111;
const def_eFaultSource RES_TEXADDR_3_L = (ucNumTMU == 0) ? RA22 : RA20;
const def_eFaultSource RES_TEXADDR_3_H = (ucNumTMU == 0) ? RA21 : RA19;
const def_eFaultSource RES_TEXADDR_3_8 = (ucNumTMU == 0) ? R98 : R97;
/* This test is simpler than I wanteed it to be. It focuses
* only on address lines. I thought that reading a totally
* unknown value can mean we have a control line issue but it
* is more complex. In case of 2 addresses lines shorted,
* the value we will get wont necessary be the one we wrote,
* at any place. I am not 100% sure why, but it might be
* because of EDO Ram Page mode... Or maybe lines are also used
* internally by the TMU.. or maybe I just missed something !
*
* Anyway, this test should do the job for @ lines (including
* shorted). And the Control lines will be tested in a different
* Module where @ lines wont change.
*/
/* Considering error only if more than 3 over 16 bits
* are wrong, because we are only testing addresses
* lines here */
if(count_bit32(ErrorMark_L1 & 0x0000FFFF) > 3)
{
NbErr++;
/* If it matches the Other value with less than
* 3 error bits */
if(count_bit32(ErrorMarkOther_L1 & 0x0000FFFF) < 3)
{
if(add_list[idx].nColBit!=0)
{
FaultSource_addScore(pFaultSrcCtx, MEMChip_0_A0 + add_list[idx].nColBit - 1, 1.0 / 1);
if(RamSizeMB>=2)
FaultSource_addScore(pFaultSrcCtx, MEMChip_0_A0_Other + add_list[idx].nColBit - 1, 1.0 / 2);
FaultSource_addScore(pFaultSrcCtx, TMUTexADDR_0_0 + add_list[idx].nColBit - 1, 1.0 / 2);
if(add_list[idx].nColBit < 4)
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_0_L + add_list[idx].nColBit - 2, 1.0 / 2);
else if(add_list[idx].nColBit < 8)
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_0_H + add_list[idx].nColBit - 6, 1.0 / 2);
else
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_0_8, 1.0 / 2);
}
if(add_list[idx].nRowBit!=0)
{
FaultSource_addScore(pFaultSrcCtx, MEMChip_0_A0 + add_list[idx].nRowBit - 1, 1.0 / 1);
if(RamSizeMB>=2)
FaultSource_addScore(pFaultSrcCtx, MEMChip_0_A0_Other + add_list[idx].nRowBit - 1, 1.0 / 2);
FaultSource_addScore(pFaultSrcCtx, TMUTexADDR_0_0 + add_list[idx].nRowBit - 1, 1.0 / 2);
if(add_list[idx].nRowBit < 4)
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_0_L + add_list[idx].nRowBit - 2, 1.0 / 2);
else if(add_list[idx].nRowBit < 8)
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_0_H + add_list[idx].nRowBit - 6, 1.0 / 2);
else
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_0_8, 1.0 / 2);
}
}
}
if(count_bit32(ErrorMark_L1 & 0xFFFF0000) > 3)
{
NbErr++;
if(count_bit32(ErrorMarkOther_L1 & 0xFFFF0000) < 3)
{
if(add_list[idx].nColBit!=0)
{
FaultSource_addScore(pFaultSrcCtx, MEMChip_1_A0 + add_list[idx].nColBit - 1, 1.0 / 1);
if(RamSizeMB>=2)
FaultSource_addScore(pFaultSrcCtx, MEMChip_1_A0_Other + add_list[idx].nColBit - 1, 1.0 / 2);
FaultSource_addScore(pFaultSrcCtx, TMUTexADDR_1_0 + add_list[idx].nColBit - 1, 1.0 / 2);
if(add_list[idx].nColBit < 4)
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_1_L + add_list[idx].nColBit - 2, 1.0 / 2);
else if(add_list[idx].nColBit < 8)
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_1_H + add_list[idx].nColBit - 6, 1.0 / 2);
else
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_1_8, 1.0 / 2);
}
if(add_list[idx].nRowBit!=0)
{
FaultSource_addScore(pFaultSrcCtx, MEMChip_1_A0 + add_list[idx].nRowBit - 1, 1.0 / 1);
if(RamSizeMB>=2)
FaultSource_addScore(pFaultSrcCtx, MEMChip_1_A0_Other + add_list[idx].nRowBit - 1, 1.0 / 2);
FaultSource_addScore(pFaultSrcCtx, TMUTexADDR_1_0 + add_list[idx].nRowBit - 1, 1.0 / 2);
if(add_list[idx].nRowBit < 4)
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_1_L + add_list[idx].nRowBit - 2, 1.0 / 2);
else if(add_list[idx].nRowBit < 8)
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_1_H + add_list[idx].nRowBit - 6, 1.0 / 2);
else
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_1_8, 1.0 / 2);
}
}
}
if(count_bit32(ErrorMark_L2 & 0x0000FFFF) > 3)
{
NbErr++;
if(count_bit32(ErrorMarkOther_L2 & 0x0000FFFF) < 3)
{
if(add_list[idx].nColBit!=0)
{
FaultSource_addScore(pFaultSrcCtx, MEMChip_2_A0 + add_list[idx].nColBit - 1, 1.0 / 1);
if(RamSizeMB>=2)
FaultSource_addScore(pFaultSrcCtx, MEMChip_2_A0_Other + add_list[idx].nColBit - 1, 1.0 / 2);
FaultSource_addScore(pFaultSrcCtx, TMUTexADDR_2_0 + add_list[idx].nColBit - 1, 1.0 / 2);
if(add_list[idx].nColBit < 4)
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_2_L + add_list[idx].nColBit - 2, 1.0 / 2);
else if(add_list[idx].nColBit < 8)
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_2_H + add_list[idx].nColBit - 6, 1.0 / 2);
else
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_2_8, 1.0 / 2);
}
if(add_list[idx].nRowBit!=0)
{
FaultSource_addScore(pFaultSrcCtx, MEMChip_2_A0 + add_list[idx].nRowBit - 1, 1.0 / 1);
if(RamSizeMB>=2)
FaultSource_addScore(pFaultSrcCtx, MEMChip_2_A0_Other + add_list[idx].nRowBit - 1, 1.0 / 2);
FaultSource_addScore(pFaultSrcCtx, TMUTexADDR_2_0 + add_list[idx].nRowBit - 1, 1.0 / 2);
if(add_list[idx].nRowBit < 4)
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_2_L + add_list[idx].nRowBit - 2, 1.0 / 2);
else if(add_list[idx].nRowBit < 8)
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_2_H + add_list[idx].nRowBit - 6, 1.0 / 2);
else
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_2_8, 1.0 / 2);
}
}
}
if(count_bit32(ErrorMark_L2 & 0xFFFF0000) > 3)
{
NbErr++;
if(count_bit32(ErrorMarkOther_L2 & 0xFFFF0000) < 3)
{
if(add_list[idx].nColBit!=0)
{
FaultSource_addScore(pFaultSrcCtx, MEMChip_3_A0 + add_list[idx].nColBit - 1, 1.0 / 1);
if(RamSizeMB>=2)
FaultSource_addScore(pFaultSrcCtx, MEMChip_3_A0_Other + add_list[idx].nColBit - 1, 1.0 / 2);
FaultSource_addScore(pFaultSrcCtx, TMUTexADDR_3_0 + add_list[idx].nColBit - 1, 1.0 / 2);
if(add_list[idx].nColBit < 4)
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_3_L + add_list[idx].nColBit - 2, 1.0 / 2);
else if(add_list[idx].nColBit < 8)
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_3_H + add_list[idx].nColBit - 6, 1.0 / 2);
else
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_3_8, 1.0 / 2);
}
if(add_list[idx].nRowBit!=0)
{
FaultSource_addScore(pFaultSrcCtx, MEMChip_3_A0 + add_list[idx].nRowBit - 1, 1.0 / 1);
if(RamSizeMB>=2)
FaultSource_addScore(pFaultSrcCtx, MEMChip_3_A0_Other + add_list[idx].nRowBit - 1, 1.0 / 2);
FaultSource_addScore(pFaultSrcCtx, TMUTexADDR_3_0 + add_list[idx].nRowBit - 1, 1.0 / 2);
if(add_list[idx].nRowBit < 4)
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_3_L + add_list[idx].nRowBit - 2, 1.0 / 2);
else if(add_list[idx].nRowBit < 8)
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_3_H + add_list[idx].nRowBit - 6, 1.0 / 2);
else
FaultSource_addScore(pFaultSrcCtx, RES_TEXADDR_3_8, 1.0 / 2);
}
}
}
}
}
}
clearScreen(sstregs,0x00000000,2,2);
sst1InitIdle(sst);
devInfo->tmuInit0[(int)ucNumTMU] = _trexInit0;
ISET(SST_TREX(sst,ucNumTMU)->trexInit0, devInfo->tmuInit0[(int)ucNumTMU]);
sst1InitIdle(sst);
return NbErr;
}