Skip to content

Commit 1949a40

Browse files
Improve comments around check_mem_access and mmio_load/store
1 parent 83ac7a9 commit 1949a40

File tree

2 files changed

+143
-40
lines changed

2 files changed

+143
-40
lines changed

dv/cosim/cosim.h

+5-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
// Information about a dside transaction observed on the DUT memory interface
1212
struct DSideAccessInfo {
13-
// set when the access is a store, in which case `data` is the store data from
13+
// Set when the access is a store, in which case `data` is the store data from
1414
// the DUT. Otherwise `data` is the load data provided to the DUT.
1515
bool store;
1616
// `addr` is the address and must be 32-bit aligned. `data` and `be` are
@@ -20,7 +20,7 @@ struct DSideAccessInfo {
2020
uint32_t addr;
2121
uint32_t be;
2222

23-
// set if an error response to the transaction is seen.
23+
// Set to indicate an error response to the attempted DUT memory access.
2424
bool error;
2525

2626
// `misaligned_first` and `misaligned_second` are set when the transaction is
@@ -30,9 +30,9 @@ struct DSideAccessInfo {
3030
//
3131
// For example an unaligned 32-bit load to 0x3 produces a transaction with
3232
// `addr` as 0x0 and `misaligned_first` set to true, then a transaction with
33-
// `addr` as 0x4 and `misaligned_second` set to true. An unaligned 16-bit load
34-
// to 0x01 only produces a transaction with `addr` as 0x0 and
35-
// `misaligned_first` set to true, there is no second half.
33+
// `addr` as 0x4 and `misaligned_second` set to true.
34+
// An unaligned 16-bit load to 0x01 only produces a transaction with `addr`
35+
// as 0x0 and `misaligned_first` set to true, there is no second half.
3636
bool misaligned_first;
3737
bool misaligned_second;
3838
};

dv/cosim/spike_cosim.cc

+138-35
Original file line numberDiff line numberDiff line change
@@ -68,40 +68,59 @@ SpikeCosim::SpikeCosim(const std::string &isa_string, uint32_t start_pc,
6868
char *SpikeCosim::addr_to_mem(reg_t addr) { return nullptr; }
6969

7070
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));
7286

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);
7490

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;
7994

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.
8897

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
9099
// 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+
};
97111

98112
return !(bus_error || dut_error);
99113
}
100114

101115
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+
102121
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.
105124
bool dut_error = (check_mem_access(true, addr, len, bytes) != kCheckMemOk);
106125

107126
return !(bus_error || dut_error);
@@ -516,12 +535,76 @@ void SpikeCosim::fixup_csr(int csr_num, uint32_t csr_val) {
516535

517536
SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
518537
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+
519601
assert(len >= 1 && len <= 4);
520602
// Expect that no spike memory accesses cross a 32-bit boundary
521603
assert(((addr + (len - 1)) & 0xfffffffc) == (addr & 0xfffffffc));
522604

523605
std::string iss_action = store ? "store" : "load";
524606

607+
// 1)
525608
// Check if there are any pending DUT accesses to check against
526609
if (pending_dside_accesses.size() == 0) {
527610
std::stringstream err_str;
@@ -537,6 +620,7 @@ SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
537620

538621
std::string dut_action = top_pending_access_info.store ? "store" : "load";
539622

623+
// 2)
540624
// Check for an address match
541625
uint32_t aligned_addr = addr & 0xfffffffc;
542626
if (aligned_addr != top_pending_access_info.addr) {
@@ -549,6 +633,7 @@ SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
549633
return kCheckMemCheckFailed;
550634
}
551635

636+
// 3)
552637
// Check access type match
553638
if (store != top_pending_access_info.store) {
554639
std::stringstream err_str;
@@ -572,6 +657,7 @@ SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
572657
// accesses where the DUT will generate an access covering all bytes within
573658
// an aligned 32-bit word.
574659

660+
// 4)
575661
// Check bytes accessed this time haven't already been been seen for the DUT
576662
// access we are trying to match against
577663
if ((expected_be & top_pending_access.be_spike) != 0) {
@@ -587,6 +673,7 @@ SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
587673
return kCheckMemCheckFailed;
588674
}
589675

676+
// 5)
590677
// Check expected access isn't trying to access bytes that the DUT access
591678
// didn't access.
592679
if ((expected_be & ~top_pending_access_info.be) != 0) {
@@ -607,6 +694,7 @@ SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
607694
pending_access_done = true;
608695
}
609696
} else {
697+
// 6)
610698
// For aligned accesses bytes from spike access must precisely match bytes
611699
// from DUT access in one go
612700
if (expected_be != top_pending_access_info.be) {
@@ -623,9 +711,10 @@ SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
623711
pending_access_done = true;
624712
}
625713

714+
// 7)
626715
// 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.)
629718
if (store || !top_pending_access_info.error) {
630719
// Combine bytes into a single word
631720
uint32_t expected_data = 0;
@@ -644,10 +733,10 @@ SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
644733

645734
if (expected_data != masked_dut_data) {
646735
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;
651740

652741
errors.emplace_back(err_str.str());
653742

@@ -656,15 +745,24 @@ SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
656745
}
657746

658747
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.
659752

660753
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 &&
667763
((top_pending_access_info.be & 0x8) != 0)) {
764+
765+
// 8)
668766
// Check the second access DUT exists
669767
if ((pending_dside_accesses.size() < 2) ||
670768
!pending_dside_accesses[1].dut_access_info.misaligned_second) {
@@ -678,6 +776,7 @@ SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
678776
return kCheckMemCheckFailed;
679777
}
680778

779+
// 9)
681780
// Check the second access had the expected address
682781
if (pending_dside_accesses[1].dut_access_info.addr !=
683782
(top_pending_access_info.addr + 4)) {
@@ -709,6 +808,10 @@ SpikeCosim::check_mem_result_e SpikeCosim::check_mem_access(
709808
pending_dside_accesses.erase(pending_dside_accesses.begin());
710809
}
711810

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.)
712815
return pending_access_error ? kCheckMemBusError : kCheckMemOk;
713816
}
714817

0 commit comments

Comments
 (0)