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