|
20 | 20 | // Tests also cover the following features:
|
21 | 21 | // - mul/div and mflo/mfhi interlocks
|
22 | 22 | // - mtlo/mthi and mul/div interlocks
|
| 23 | +// - mfhi/mthi interlocks (MIPS I/II/III bug) |
23 | 24 |
|
24 | 25 | #define FILL_ERR(errmsg, v, ve) \
|
25 | 26 | { \
|
@@ -550,6 +551,148 @@ int check_allegrex_insts(struct check_error_info *errs, exception_control_block
|
550 | 551 | }
|
551 | 552 | }
|
552 | 553 |
|
| 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 | + |
553 | 696 | if (ecb) {
|
554 | 697 | // Test the lack of trap instructions (MIPS 2).
|
555 | 698 | _check_illegal_inst("teq $0, $0");
|
|
0 commit comments