3
3
// SPDX-License-Identifier: Apache-2.0
4
4
5
5
#include " spike_cosim.h"
6
+
7
+ #include < cassert>
8
+ #include < iostream>
9
+ #include < sstream>
10
+
6
11
#include " riscv/config.h"
7
12
#include " riscv/decode.h"
8
13
#include " riscv/devices.h"
9
14
#include " riscv/log_file.h"
15
+ #include " riscv/mmu.h"
10
16
#include " riscv/processor.h"
11
17
#include " riscv/simif.h"
12
18
13
- #include < cassert>
14
- #include < iostream>
15
- #include < sstream>
16
-
17
19
// For a short time, we're going to support building against version
18
20
// ibex-cosim-v0.2 (20a886c) and also ibex-cosim-v0.3 (9af9730). Unfortunately,
19
21
// they've got different APIs and spike doesn't expose a version string.
@@ -83,26 +85,24 @@ bool SpikeCosim::mmio_load(reg_t addr, size_t len, uint8_t *bytes) {
83
85
bool dut_error = false ;
84
86
85
87
// Incoming access may be an iside or dside access. Use PC to help determine
86
- // which.
87
- uint32_t pc = processor->get_state ()->pc ;
88
+ // which. PC is 64 bits in spike, we only care about the bottom 32-bit so mask
89
+ // off the top bits.
90
+ uint64_t pc = processor->get_state ()->pc & 0xffffffff ;
88
91
uint32_t aligned_addr = addr & 0xfffffffc ;
89
92
90
93
if (pending_iside_error && (aligned_addr == pending_iside_err_addr)) {
91
94
// Check if the incoming access is subject to an iside error, in which case
92
95
// assume it's an iside access and produce an error.
93
96
pending_iside_error = false ;
94
97
dut_error = true ;
95
- } else if (addr < pc || addr >= (pc + 8 )) {
98
+ } else {
96
99
// Spike may attempt to access up to 8-bytes from the PC when fetching, so
97
- // only check as a dside access when it falls outside that range.
98
-
99
- // Otherwise check if the aligned PC matches with the aligned address or an
100
- // incremented aligned PC (to capture the unaligned 4-byte instruction
101
- // case). Assume a successful iside access if either of these checks are
102
- // true, otherwise assume a dside access and check against DUT dside
103
- // accesses. If the RTL produced a bus error for the access, or the
104
- // checking failed produce a memory fault in spike.
105
- dut_error = (check_mem_access (false , addr, len, bytes) != kCheckMemOk );
100
+ // only check as a dside access when it falls outside that range
101
+ bool in_iside_range = (addr >= pc && addr < pc + 8 );
102
+
103
+ if (!in_iside_range) {
104
+ dut_error = (check_mem_access (false , addr, len, bytes) != kCheckMemOk );
105
+ }
106
106
}
107
107
108
108
return !(bus_error || dut_error);
@@ -261,8 +261,8 @@ bool SpikeCosim::step(uint32_t write_reg, uint32_t write_reg_data, uint32_t pc,
261
261
if (pending_sync_exception) {
262
262
if (!sync_trap) {
263
263
std::stringstream err_str;
264
- err_str << " Synchronous trap was expected at ISS PC: "
265
- << std::hex << processor->get_state ()->pc
264
+ err_str << " Synchronous trap was expected at ISS PC: " << std::hex
265
+ << processor->get_state ()->pc
266
266
<< " but the DUT didn't report one at PC " << pc;
267
267
errors.emplace_back (err_str.str ());
268
268
return false ;
@@ -294,9 +294,8 @@ bool SpikeCosim::step(uint32_t write_reg, uint32_t write_reg_data, uint32_t pc,
294
294
295
295
if (pending_iside_error) {
296
296
std::stringstream err_str;
297
- err_str << " DUT generated an iside error for address: "
298
- << std::hex << pending_iside_err_addr
299
- << " but the ISS didn't produce one" ;
297
+ err_str << " DUT generated an iside error for address: " << std::hex
298
+ << pending_iside_err_addr << " but the ISS didn't produce one" ;
300
299
errors.emplace_back (err_str.str ());
301
300
return false ;
302
301
}
@@ -329,8 +328,8 @@ bool SpikeCosim::check_retired_instr(uint32_t write_reg,
329
328
if ((processor->get_state ()->last_inst_pc & 0xffffffff ) != dut_pc) {
330
329
std::stringstream err_str;
331
330
err_str << " PC mismatch, DUT retired : " << std::hex << dut_pc
332
- << " , but the ISS retired: "
333
- << std::hex << processor->get_state ()->last_inst_pc ;
331
+ << " , but the ISS retired: " << std::hex
332
+ << processor->get_state ()->last_inst_pc ;
334
333
errors.emplace_back (err_str.str ());
335
334
return false ;
336
335
}
@@ -385,18 +384,17 @@ bool SpikeCosim::check_retired_instr(uint32_t write_reg,
385
384
return true ;
386
385
}
387
386
388
- bool SpikeCosim::check_sync_trap (uint32_t write_reg,
389
- uint32_t dut_pc, uint32_t initial_spike_pc) {
387
+ bool SpikeCosim::check_sync_trap (uint32_t write_reg, uint32_t dut_pc,
388
+ uint32_t initial_spike_pc) {
390
389
// Check if an synchronously-trapping instruction matches
391
390
// between Spike and the DUT.
392
391
393
392
// Check that both spike and DUT trapped on the same pc
394
393
if (initial_spike_pc != dut_pc) {
395
394
std::stringstream err_str;
396
- err_str << " PC mismatch at synchronous trap, DUT at pc: "
397
- << std::hex << dut_pc
398
- << " while ISS pc is at : "
399
- << std::hex << initial_spike_pc;
395
+ err_str << " PC mismatch at synchronous trap, DUT at pc: " << std::hex
396
+ << dut_pc << " while ISS pc is at : " << std::hex
397
+ << initial_spike_pc;
400
398
errors.emplace_back (err_str.str ());
401
399
return false ;
402
400
}
@@ -411,6 +409,12 @@ bool SpikeCosim::check_sync_trap(uint32_t write_reg,
411
409
return false ;
412
410
}
413
411
412
+ if ((processor->get_state ()->mcause ->read () == 0x5 ) ||
413
+ (processor->get_state ()->mcause ->read () == 0x7 )) {
414
+ // We have a load or store access fault, apply fixup for misaligned accesses
415
+ misaligned_pmp_fixup ();
416
+ }
417
+
414
418
// If we see an internal NMI, that means we receive an extra memory intf item.
415
419
// Deleting that is necessary since next Load/Store would fail otherwise.
416
420
if (processor->get_state ()->mcause ->read () == 0xFFFFFFE0 ) {
@@ -577,11 +581,12 @@ void SpikeCosim::initial_proc_setup(uint32_t start_pc, uint32_t start_mtvec,
577
581
}
578
582
}
579
583
580
- void SpikeCosim::set_mip (uint32_t mip ) {
581
- uint32_t new_mip = mip ;
584
+ void SpikeCosim::set_mip (uint32_t pre_mip, uint32_t post_mip ) {
585
+ uint32_t new_mip = pre_mip ;
582
586
uint32_t old_mip = processor->get_state ()->mip ->read ();
583
587
584
- processor->get_state ()->mip ->write_with_mask (0xffffffff , mip);
588
+ processor->get_state ()->mip ->write_with_mask (0xffffffff , post_mip);
589
+ processor->get_state ()->mip ->write_pre_val (pre_mip);
585
590
586
591
if (processor->get_state ()->debug_mode ||
587
592
(processor->halt_request == processor_t ::HR_REGULAR) ||
@@ -619,6 +624,62 @@ void SpikeCosim::early_interrupt_handle() {
619
624
}
620
625
}
621
626
627
+ // Ibex splits misaligned accesses into two separate requests. They
628
+ // independently undergo PMP access checks. It is possible for one to fail (so
629
+ // no request produced for that half of the access) whilst the other successed
630
+ // (producing a request for that half of the access).
631
+ //
632
+ // Spike splits misaligned accesses up into bytes and will apply PMP access
633
+ // checks byte by byte in a linear order. As soon as a byte sees a PMP
634
+ // permission failure the rest of the misaligned access is aborted.
635
+ //
636
+ // This results in mismatches as in some misaligned access cases Ibex will
637
+ // produce a request and spike will not.
638
+ //
639
+ // This fixup detects this condition and removes the Ibex access from
640
+ // pending_dside_accesses to avoid a mismatch. This removed access is checked
641
+ // against PMP using the spike MMU to check spike agrees it passes PMP checks.
642
+ //
643
+ // There may be a better way to handle this (e.g. altering spike behaviour to
644
+ // match Ibex) so for now a warning is generated in fixup cases so they can be
645
+ // easily identified.
646
+ void SpikeCosim::misaligned_pmp_fixup () {
647
+ if (pending_dside_accesses.size () != 0 ) {
648
+ auto &top_pending_access = pending_dside_accesses.front ();
649
+ auto &top_pending_access_info = top_pending_access.dut_access_info ;
650
+
651
+ // If top access is the second half of a misaligned access where the first
652
+ // half saw an error we have the PMP fixup case
653
+ if (top_pending_access_info.misaligned_second &&
654
+ top_pending_access_info.misaligned_first_saw_error ) {
655
+ mmu_t *mmu = processor->get_mmu ();
656
+
657
+ // Check if the second half of the access (which Ibex produces a request
658
+ // for and spike does not) passes PMP
659
+ if (!mmu->pmp_ok (top_pending_access_info.addr , 4 ,
660
+ top_pending_access_info.store ? STORE : LOAD,
661
+ top_pending_access_info.m_mode_access ? PRV_M : PRV_U)) {
662
+ // Raise an error if the second half shouldn't have passed PMP
663
+ std::stringstream err_str;
664
+ err_str << " Saw second half of a misaligned access which not have "
665
+ << " generated a memory request as it does not pass a PMP check,"
666
+ << " address: " << std::hex << top_pending_access_info.addr ;
667
+ errors.emplace_back (err_str.str ());
668
+ } else {
669
+ // Output warning on stdout so we're aware which tests this is happening
670
+ // in
671
+ std::cout << " WARNING: Cosim dropping second half of misaligned access "
672
+ << " as first half saw an error and second half passed PMP "
673
+ << " check, address: " << std::hex
674
+ << top_pending_access_info.addr << std::endl;
675
+ std::cout << std::dec;
676
+
677
+ pending_dside_accesses.erase (pending_dside_accesses.begin ());
678
+ }
679
+ }
680
+ }
681
+ }
682
+
622
683
void SpikeCosim::set_nmi (bool nmi) {
623
684
if (nmi && !nmi_mode && !processor->get_state ()->debug_mode &&
624
685
processor->halt_request != processor_t ::HR_REGULAR) {
0 commit comments