@@ -68,40 +68,56 @@ SpikeCosim::SpikeCosim(const std::string &isa_string, uint32_t start_pc,
68
68
char *SpikeCosim::addr_to_mem (reg_t addr) { return nullptr ; }
69
69
70
70
bool SpikeCosim::mmio_load (reg_t addr, size_t len, uint8_t *bytes) {
71
+ // Load from the ISS memory model, while checking if the access is
72
+ // consistent with the observed DUT memory acceses.
73
+ // Return false if any errors occur, or the checking fails.
74
+ // Within Spike (mmu_t::load_slow_path), this causes a load_access_fault.
75
+
76
+ // Spike does not differentiate between iside and dside memory accesses
77
+ // (as ibex does) so we make an educated guess which is which, and
78
+ // then after fetching the data, check against the recorded DUT access
79
+ // that should be queued as pending.
80
+ //
81
+ // Spike may attempt to access up to 8-bytes from the PC when fetching, so
82
+ // assume as a dside access when it falls outside that range.
83
+ // N.B. Heuristic
84
+ uint32_t pc = processor->get_state ()->pc ;
85
+ bool is_probably_dmem_access = (addr < pc || addr >= (pc + 8 ));
86
+ // Check we don't have a heuristic mismatch (this may be a false-positive but
87
+ // worth failing explicity if it is seen)
88
+ assert (!(is_probably_dmem_access && pending_iside_error));
89
+
90
+ // Attempt to make an identical load from the bus memory device.
91
+ // This can fail e.g. if accessing an unmapped address (see SpikeCosim::add_memory)
71
92
bool bus_error = !bus.load (addr, len, bytes);
72
93
73
- bool dut_error = false ;
94
+ // If the DUT encountered a bus error for its access, or the checking failed,
95
+ // this is a dut_error.
96
+ bool dut_error = is_probably_dmem_access ? (check_mem_access (false , addr, len, bytes) != kCheckMemOk ) : false ;
74
97
75
- // Incoming access may be an iside or dside access. Use PC to help determine
76
- // which.
77
- uint32_t pc = processor->get_state ()->pc ;
78
98
uint32_t aligned_addr = addr & 0xfffffffc ;
79
-
80
- if (pending_iside_error && (aligned_addr == pending_iside_err_addr)) {
81
- // Check if the incoming access is subject to an iside error, in which case
82
- // assume it's an iside access and produce an error.
99
+ if ( pending_iside_error &&
100
+ (pending_iside_err_addr == aligned_addr)) {
101
+ // Check if the aligned PC matches with the aligned address or an
102
+ // incremented aligned PC (to capture the unaligned 4-byte instruction
103
+ // case).
104
+ // If the pending_iside_error has been set, produce a dut_error.
83
105
pending_iside_error = false ;
84
106
dut_error = true ;
85
- } else if (addr < pc || addr >= (pc + 8 )) {
86
- // Spike may attempt to access up to 8-bytes from the PC when fetching, so
87
- // only check as a dside access when it falls outside that range.
88
-
89
- // Otherwise check if the aligned PC matches with the aligned address or an
90
- // incremented aligned PC (to capture the unaligned 4-byte instruction
91
- // case). Assume a successful iside access if either of these checks are
92
- // true, otherwise assume a dside access and check against DUT dside
93
- // accesses. If the RTL produced a bus error for the access, or the
94
- // checking failed produce a memory fault in spike.
95
- dut_error = (check_mem_access (false , addr, len, bytes) != kCheckMemOk );
96
- }
107
+ };
97
108
98
109
return !(bus_error || dut_error);
99
110
}
100
111
101
112
bool SpikeCosim::mmio_store (reg_t addr, size_t len, const uint8_t *bytes) {
113
+ // Store to the ISS memory model, while checking if the access is
114
+ // consistent with the observed DUT memory acceses.
115
+ // Return false if any errors occur, or the checking fails.
116
+ // Within Spike (mmu_t::store_slow_path), this causes a store_access_fault.
117
+
102
118
bool bus_error = !bus.store (addr, len, bytes);
103
- // If the RTL produced a bus error for the access, or the checking failed
104
- // produce a memory fault in spike .
119
+ // If the DUT encountered a bus error for its access, or the checking failed,
120
+ // this is a dut_error .
105
121
bool dut_error = (check_mem_access (true , addr, len, bytes) != kCheckMemOk );
106
122
107
123
return !(bus_error || dut_error);
@@ -516,12 +532,76 @@ void SpikeCosim::fixup_csr(int csr_num, uint32_t csr_val) {
516
532
517
533
SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access (
518
534
bool store, uint32_t addr, size_t len, const uint8_t *bytes) {
535
+ // Because Spike is only fed instructions that have been made available
536
+ // on the RVFI by IBEX (retired and synchronously trapping), spike will
537
+ // always run slightly-behind the ibex, and hence we need to store memory
538
+ // accesses and errors when they are seen by the testbench, and then pop
539
+ // them from the queues when spike actually executes them.
540
+ // This delay may be a few cycles in general, but may never happen if
541
+ // the access was iside-speculative.
542
+ //
543
+ // Whenever a load/store is attempted by spike, we check the following
544
+ // conditions....
545
+ //
546
+ // 0) First, if an iside error has been detected and
547
+ // pending_iside_error=True, a dut_error is generated within mmio_load(), and
548
+ // this function is not called. This function is called if the heuristics
549
+ // believe the access is a dmem access. (dmem may be accessed with stores or
550
+ // loads, while imem is only loads.)
551
+ //
552
+ // As well as checking for correspondence between the spike access and the
553
+ // queued DUT dmem access, we also want to pass along the error if the dmem
554
+ // access response came back with an error from the DUT memory system bus. So,
555
+ // if all the below checks pass, the final step is to check for a marked error
556
+ // in the queued dmem access, and pass it along to Spike if so.
557
+ //
558
+ // The correspondence checks are as follows:
559
+ //
560
+ // 1) Check there is actually a dmem access in the queue at all.
561
+ // Pop the top dmem access from the queue, and then...
562
+ // 2) Check the addr of spike access matches that of the queued dmem access.
563
+ // 3) Check the type of spike access (load/store) matches that of the queued
564
+ // dmem access.
565
+ //
566
+ // If the dut dmem access was misaligned :
567
+ // (Because ibex dmem can make misaligned and byte-enabled accesses, and spike
568
+ // will not make accesses in this way, we need to check the final result of
569
+ // the two systems is the same. Spike will make multiple single-byte accesses
570
+ // for the same end-result). Therefore, this function (and mmio_load/store)
571
+ // may be called multiple times for a single dut dmem access.
572
+ // By checking the byte-enables of the memory accesses, we can check that..
573
+ // 4) The spike-accesses do not repeat accessing any bytes the dut accesses.
574
+ // 5) The spike-access does not touch any bytes not accessed by the dut.
575
+ // ..and while doing this, record the bytes accessed so next time around we
576
+ // can make the checks 4) and 5).
577
+ // When all of the bytes have been accessed matching the queued access,
578
+ // discard this recorded data in preparation for the next access.
579
+ //
580
+ // If the dut dmem access is aligned :
581
+ // 6) Check that the spike-accessed bytes match the dut dmem byte-enables in
582
+ // one go.
583
+ //
584
+ // Until this point, we have not even checked the data of the accesses.
585
+ // For accesses not tagged with a dut error:
586
+ // 7) Check the data of the spike access matches that of the ibex dmem access,
587
+ // for all stores and for loads without errors.
588
+ //
589
+ // For dut dmem accesses with errors, we can't check the data, but one more
590
+ // possible sanity test is that for misaligned accesses...
591
+ // 8) Check the second access exists in the pending queue
592
+ // 9) Check the second (next) access has the correct address
593
+ // After 9), pop both dmem accesses from pending. Only return a single error if needed.
594
+ //
595
+ // Finally, pass along the error to spike if the dut access at the top of the
596
+ // queue was marked with one (DSideAccessInfo.error)
597
+
519
598
assert (len >= 1 && len <= 4 );
520
599
// Expect that no spike memory accesses cross a 32-bit boundary
521
600
assert (((addr + (len - 1 )) & 0xfffffffc ) == (addr & 0xfffffffc ));
522
601
523
602
std::string iss_action = store ? " store" : " load" ;
524
603
604
+ // 1)
525
605
// Check if there are any pending DUT accesses to check against
526
606
if (pending_dside_accesses.size () == 0 ) {
527
607
std::stringstream err_str;
@@ -537,6 +617,7 @@ SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
537
617
538
618
std::string dut_action = top_pending_access_info.store ? " store" : " load" ;
539
619
620
+ // 2)
540
621
// Check for an address match
541
622
uint32_t aligned_addr = addr & 0xfffffffc ;
542
623
if (aligned_addr != top_pending_access_info.addr ) {
@@ -549,6 +630,7 @@ SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
549
630
return kCheckMemCheckFailed ;
550
631
}
551
632
633
+ // 3)
552
634
// Check access type match
553
635
if (store != top_pending_access_info.store ) {
554
636
std::stringstream err_str;
@@ -572,6 +654,7 @@ SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
572
654
// accesses where the DUT will generate an access covering all bytes within
573
655
// an aligned 32-bit word.
574
656
657
+ // 4)
575
658
// Check bytes accessed this time haven't already been been seen for the DUT
576
659
// access we are trying to match against
577
660
if ((expected_be & top_pending_access.be_spike ) != 0 ) {
@@ -587,6 +670,7 @@ SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
587
670
return kCheckMemCheckFailed ;
588
671
}
589
672
673
+ // 5)
590
674
// Check expected access isn't trying to access bytes that the DUT access
591
675
// didn't access.
592
676
if ((expected_be & ~top_pending_access_info.be ) != 0 ) {
@@ -607,6 +691,7 @@ SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
607
691
pending_access_done = true ;
608
692
}
609
693
} else {
694
+ // 6)
610
695
// For aligned accesses bytes from spike access must precisely match bytes
611
696
// from DUT access in one go
612
697
if (expected_be != top_pending_access_info.be ) {
@@ -623,9 +708,10 @@ SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
623
708
pending_access_done = true ;
624
709
}
625
710
711
+ // 7)
626
712
// Check data from expected access matches pending DUT access.
627
- // Data is ignored on error responses to loads so don't check it. Always check
628
- // store data.
713
+ // Always check store data.
714
+ // (Data is ignored on error responses to loads so don't check it.)
629
715
if (store || !top_pending_access_info.error ) {
630
716
// Combine bytes into a single word
631
717
uint32_t expected_data = 0 ;
@@ -644,10 +730,10 @@ SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
644
730
645
731
if (expected_data != masked_dut_data) {
646
732
std::stringstream err_str;
647
- err_str << " DUT generated " << iss_action << " at address " << std::hex
648
- << top_pending_access_info.addr << " with data "
649
- << masked_dut_data << " but data " << expected_data
650
- << " was expected with byte mask " << expected_be;
733
+ err_str << " DUT generated " << iss_action << " dmem access at address "
734
+ << std::hex << top_pending_access_info.addr << " with data "
735
+ << masked_dut_data << " but ISS access(es) got data " << std::hex << expected_data
736
+ << " with equivalent byte mask of " << std::hex << expected_be;
651
737
652
738
errors.emplace_back (err_str.str ());
653
739
@@ -656,15 +742,24 @@ SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
656
742
}
657
743
658
744
bool pending_access_error = top_pending_access_info.error ;
745
+ // If the pending dside access has an error, then we don't need to check to data
746
+ // of the accesses. However, we can still check if the correct number of accesses
747
+ // took place, and then immediately pop the erroneous dmem access(es) off the top
748
+ // of the queue.
659
749
660
750
if (pending_access_error && misaligned) {
661
- // When misaligned accesses see an error, if they have crossed a 32-bit
662
- // boundary DUT will generate two accesses. If the top pending access from
663
- // the DUT was the first half of a misaligned access which accesses the top
664
- // byte, it must have crossed the 32-bit boundary and generated a second
665
- // access
666
- if (top_pending_access_info.misaligned_first &&
751
+ // If requested to make a misaligned access that crosses a 32-bit
752
+ // boundary (this could be a word or halfword access), the DUT
753
+ // LSU generates two word-aligned accesses with appropriate
754
+ // byte-enables set.
755
+ //
756
+ // If the top pending access from the DUT was the first half of a misaligned
757
+ // access which accesses the top byte, it must have crossed the 32-bit
758
+ // boundary and generated a second access
759
+ if ( top_pending_access_info.misaligned_first &&
667
760
((top_pending_access_info.be & 0x8 ) != 0 )) {
761
+
762
+ // 8)
668
763
// Check the second access DUT exists
669
764
if ((pending_dside_accesses.size () < 2 ) ||
670
765
!pending_dside_accesses[1 ].dut_access_info .misaligned_second ) {
@@ -678,6 +773,7 @@ SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
678
773
return kCheckMemCheckFailed ;
679
774
}
680
775
776
+ // 9)
681
777
// Check the second access had the expected address
682
778
if (pending_dside_accesses[1 ].dut_access_info .addr !=
683
779
(top_pending_access_info.addr + 4 )) {
@@ -709,6 +805,10 @@ SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
709
805
pending_dside_accesses.erase (pending_dside_accesses.begin ());
710
806
}
711
807
808
+ // If we got this far, just check if the DUT access was marked as
809
+ // an error, and pass this on to Spike if so as a dut_error.
810
+ // (If we just erased the top dmem access from the pending queue, this
811
+ // returned error refers to that.)
712
812
return pending_access_error ? kCheckMemBusError : kCheckMemOk ;
713
813
}
714
814
0 commit comments