Skip to content

Commit cd98376

Browse files
committed
Adding some tests for HI/LO register hazards
This is still a bit WIP, still haven't fully figured out the issues with exceptions and mthi/mtlo. It seems pretty buggy. Might need to try some MIPS32 CPU and compare :)
1 parent b4b15ef commit cd98376

File tree

2 files changed

+151
-4
lines changed

2 files changed

+151
-4
lines changed

psp-tests/manual/exception-module/exception_info.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,14 @@
3434

3535
#define GET_EX_CAUSE(cause) (((cause) >> 2) & 31)
3636

37-
#define EX_MEM_ADD_LD_ERR 4
38-
#define EX_MEM_ADD_ST_ERR 5
39-
#define EX_MEM_BUS_ERR 7
40-
#define EX_ILLEGAL_INST 10 // An instruction that's not supported
37+
// From SMIPS Processor Specification
38+
#define EX_MEM_ADD_LD_ERR 4 // Address error/misaligned on load
39+
#define EX_MEM_ADD_ST_ERR 5 // Address error/misaligned on store
40+
#define EX_MEM_BUS_ERR 7 // Bus error exception
41+
#define EX_DEBUG_BREAK 9 // Breakpoint
42+
#define EX_ILLEGAL_INST 10 // Reserved instruction exception
43+
#define EX_COP_ILLEGAL 11 // Coprocessor Unusable
44+
#define EX_ARITH_OVERFLOW 12 // Arithmetic overflow
4145

4246
typedef struct {
4347
uint32_t cause;

psp-tests/manual/mips-allegrex.c

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
// Tests also cover the following features:
2121
// - mul/div and mflo/mfhi interlocks
2222
// - mtlo/mthi and mul/div interlocks
23+
// - mfhi/mthi interlocks (MIPS I/II/III bug)
2324

2425
#define FILL_ERR(errmsg, v, ve) \
2526
{ \
@@ -550,6 +551,148 @@ int check_allegrex_insts(struct check_error_info *errs, exception_control_block
550551
}
551552
}
552553

554+
// Validate that mthi/mfhi have proper interlocks, as per manual:
555+
// As per manual:
556+
// Historical Information:
557+
// In MIPS I-III, if either of the two preceding instructions is MFHI,
558+
// the result of that MFHI is UNPREDICTABLE. Reads of the HI or LO
559+
// special register must be separated from any subsequent instructions
560+
// that write to them by two or more instructions. In MIPS IV and
561+
// later, including MIPS32, this restriction does not exist.
562+
{
563+
const uint32_t test_data[][3] = {
564+
// slo, shi, shi-interference
565+
{0x00001234, 0x5678abcd, 0xbeefbeef},
566+
{0x01010202, 0x03030404, 0xdeadc0de},
567+
{0x00111111, 0x88888888, 0xc0ffeba2},
568+
{0xffffffff, 0xffffeeee, 0xf00ba5f0},
569+
};
570+
// We repeat a bunch of times to prove we didn't "get lucky"
571+
for (unsigned rep = 0; rep < 128; rep++) {
572+
for (unsigned i = 0; i < 4; i++) {
573+
uint32_t reshi;
574+
asm volatile(
575+
".set push\n"
576+
".set noreorder\n"
577+
"mthi $0; mtlo $0;\n" // Clear HILO
578+
"nop; nop; nop; nop; nop; nop; nop; nop;\n"
579+
"nop; nop; nop; nop; nop; nop; nop; nop;\n"
580+
581+
"mthi %2\n" // Write some well known data
582+
"mtlo %1\n"
583+
// Add some pipeline bubble to ensure all previous
584+
// instructions are out of the pipeline.
585+
"nop; nop; nop; nop; nop; nop; nop; nop;\n"
586+
"nop; nop; nop; nop; nop; nop; nop; nop;\n"
587+
588+
"mfhi %0\n" // Instruction under test!
589+
"mthi %3\n" // Write some other data, see if it fails.
590+
".set pop\n"
591+
: "=r"(reshi)
592+
: "r"(test_data[i][0]), "r"(test_data[i][1]), "r"(test_data[i][2]));
593+
594+
if (reshi != test_data[i][1]) {
595+
FILL_ERR("mfhi -> mthi interlock failed for HI!", reshi, test_data[i][1]);
596+
break;
597+
}
598+
}
599+
for (unsigned i = 0; i < 4; i++) {
600+
uint32_t reslo;
601+
asm volatile(
602+
".set push\n"
603+
".set noreorder\n"
604+
"mthi $0; mtlo $0;\n" // Clear HILO
605+
"nop; nop; nop; nop; nop; nop; nop; nop;\n"
606+
"nop; nop; nop; nop; nop; nop; nop; nop;\n"
607+
608+
"mthi %2\n" // Write some well known data
609+
"mtlo %1\n"
610+
// Add some pipeline bubble to ensure all previous
611+
// instructions are out of the pipeline.
612+
"nop; nop; nop; nop; nop; nop; nop; nop;\n"
613+
"nop; nop; nop; nop; nop; nop; nop; nop;\n"
614+
"mflo %0\n" // Instruction under test!
615+
"mtlo %3\n" // Write some other data, see if it fails.
616+
".set pop\n"
617+
: "=r"(reslo)
618+
: "r"(test_data[i][0]), "r"(test_data[i][1]), "r"(test_data[i][2]));
619+
620+
if (reslo != test_data[i][0]) {
621+
FILL_ERR("mflo -> mtlo interlock failed for LO!", reslo, test_data[i][0]);
622+
break;
623+
}
624+
}
625+
}
626+
}
627+
628+
// Validate that exceptions can mess the HI/LO interlocks
629+
// Some exception-generating instructions seem to be problematic and cannot
630+
// prevent certain instructions from writing HI/LO registers. This seems
631+
// to only work for mtlo/mthi, and doesn't work for mul/div, probably due
632+
// to their longer latency. Other exception causing instructions seem to
633+
// be fine and not trigger this issue!
634+
// eret seems to properly flush the pipeline (which is expected)
635+
if (ecb) {
636+
#define _TEST_HILO_BUG(expect_bug, exception_instr, update_instr, \
637+
read_inst, field, expected_cause) { \
638+
uint32_t result = 0; \
639+
memset(ecb, 0, sizeof(*ecb)); \
640+
ecb->magic[0] = MAGIC_VAL_1; \
641+
ecb->magic[1] = MAGIC_VAL_2; \
642+
ecb->magic[2] = MAGIC_VAL_3; \
643+
ecb->magic[3] = MAGIC_VAL_4; \
644+
ecb->armed = 1; \
645+
asm volatile( \
646+
".set push\n" \
647+
".set noreorder\n" \
648+
"la $v1, 0x7FFFFFFF\n" /* Load overflow value */ \
649+
"la $v0, 1f\n" /* Load expected EPC */ \
650+
"sw $v0, 0(%1)\n" \
651+
"mthi $0; mtlo $0\n" /* Clear HI/LO */ \
652+
"nop; nop; nop; nop; nop; nop\n" /* Pipeline flush */ \
653+
"li $v0, 0xc0fe\n" \
654+
update_instr " $v0\n" \
655+
"li $v0, 0xbaad\n" \
656+
"nop; nop; nop; nop; nop; nop\n" \
657+
"1:\n " exception_instr "\n" \
658+
update_instr " $v0\n" /* Not aborted instruction */ \
659+
"nop; nop; nop; nop; nop; nop; nop; nop\n" \
660+
read_inst " %0\n" \
661+
".set pop\n" \
662+
:"=r"(result) \
663+
: "r"(&ecb->expected_epc) \
664+
: "$v0", "$v1", "memory"); \
665+
\
666+
unsigned extype = GET_EX_CAUSE(ecb->state.cause); \
667+
if (ecb->exception_count != 1) \
668+
FILL_ERR("No exception caught!", ecb->exception_count, 1) \
669+
else if (extype != expected_cause) \
670+
FILL_ERR("Unexpected cause type!", extype, expected_cause) \
671+
else if (result != 0xbaad) \
672+
FILL_ERR("Unexpected final Hi/Lo value!", result, 0xbaad) \
673+
else if (expect_bug && ecb->state.field != 0xbaad) \
674+
FILL_ERR("Unexpected (not buggy) captured Hi/Lo value!" \
675+
exception_instr, ecb->state.field, 0xbaad) \
676+
else if (!expect_bug && ecb->state.field != 0xc0fe) \
677+
FILL_ERR("Unexpected (buggy) captured Hi/Lo value at " \
678+
exception_instr, ecb->state.field, 0xc0fe) \
679+
}
680+
681+
// Test buggy exceptions first
682+
_TEST_HILO_BUG(1, "break", "mthi", "mfhi", mhi, EX_DEBUG_BREAK); // Regular breakpoint
683+
_TEST_HILO_BUG(1, "break", "mtlo", "mflo", mlo, EX_DEBUG_BREAK);
684+
_TEST_HILO_BUG(1, "lw $0, 1($0)", "mthi", "mfhi", mhi, EX_MEM_ADD_LD_ERR); // Invalid alignment
685+
_TEST_HILO_BUG(1, "lw $0, 1($0)", "mtlo", "mflo", mlo, EX_MEM_ADD_LD_ERR);
686+
_TEST_HILO_BUG(1, "teq $0, $0", "mthi", "mfhi", mhi, EX_ILLEGAL_INST); // Invalid inst
687+
_TEST_HILO_BUG(1, "teq $0, $0", "mtlo", "mflo", mlo, EX_ILLEGAL_INST);
688+
_TEST_HILO_BUG(1, "add $v1, $v0", "mthi", "mfhi", mhi, EX_ARITH_OVERFLOW); // Overflow
689+
_TEST_HILO_BUG(1, "add $v1, $v0", "mtlo", "mflo", mlo, EX_ARITH_OVERFLOW);
690+
691+
// Now other exceptions that do *not* cause the bug
692+
_TEST_HILO_BUG(0, "eret", "mthi", "mfhi", mhi, EX_COP_ILLEGAL); // Cannot run in user mode!
693+
_TEST_HILO_BUG(0, "eret", "mtlo", "mflo", mlo, EX_COP_ILLEGAL);
694+
}
695+
553696
if (ecb) {
554697
// Test the lack of trap instructions (MIPS 2).
555698
_check_illegal_inst("teq $0, $0");

0 commit comments

Comments
 (0)