@@ -39,7 +39,10 @@ SpikeCosim::SpikeCosim(const std::string &isa_string, uint32_t start_pc,
3939 uint32_t pmp_num_regions, uint32_t pmp_granularity,
4040 uint32_t mhpm_counter_num, uint32_t dm_start_addr,
4141 uint32_t dm_end_addr)
42- : nmi_mode(false ), pending_iside_error(false ), insn_cnt(0 ) {
42+ : nmi_mode(false ),
43+ pending_iside_error(false ),
44+ insn_cnt(0 ),
45+ pending_expanded_insn(0 ) {
4346 FILE *log_file = nullptr ;
4447 if (trace_log_path.length () != 0 ) {
4548 log = std::make_unique<log_file_t >(trace_log_path.c_str ());
@@ -215,7 +218,13 @@ bool SpikeCosim::step(uint32_t write_reg, uint32_t write_reg_data, uint32_t pc,
215218 // (If the current step causes a synchronous trap, it will be
216219 // recorded against the current pc)
217220 initial_spike_pc = (processor->get_state ()->pc & 0xffffffff );
218- processor->step (1 );
221+ // Only step Spike if the current instruction is not an expanded one. Spike
222+ // does not expand instructions, so we have to consume a single Spike step
223+ // over multiple steps of Ibex. In the last Ibex step of the expanded
224+ // instruction, we step Spike.
225+ if (pending_expanded_insn == 0 ) {
226+ processor->step (1 );
227+ }
219228
220229 // ISS
221230 // - If encountered an async trap,
@@ -310,15 +319,99 @@ bool SpikeCosim::step(uint32_t write_reg, uint32_t write_reg_data, uint32_t pc,
310319 suppressed_write_reg_data);
311320 }
312321
313- if (!check_retired_instr (write_reg, write_reg_data, pc, suppress_reg_write)) {
314- return false ;
322+ if (expanded_insn) {
323+ if (!check_expanded_instr (write_reg, write_reg_data, pc, suppress_reg_write,
324+ expanded_insn)) {
325+ return false ;
326+ }
327+ } else {
328+ if (!check_retired_instr (write_reg, write_reg_data, pc, suppress_reg_write,
329+ expanded_insn)) {
330+ return false ;
331+ }
315332 }
316333
317334 // Only increment insn_cnt and return true if there are no errors
318335 insn_cnt++;
319336 return true ;
320337}
321338
339+ bool SpikeCosim::check_expanded_instr (uint32_t write_reg,
340+ uint32_t write_reg_data, uint32_t dut_pc,
341+ bool suppress_reg_write,
342+ uint32_t expanded_insn) {
343+ // If this is the first step of an expanded instruction, set up our
344+ // expectations.
345+ if (!pending_expanded_insn) {
346+ pending_expanded_insn = expanded_insn;
347+ expanded_insn_pc = dut_pc;
348+ expanded_reg_changes.clear ();
349+
350+ // The ISS has just stepped once for the entire expanded instruction.
351+ // We now collect all the GPR changes it produced and store them in a map
352+ auto ®_changes = processor->get_state ()->log_reg_write ;
353+ for (auto reg_change : reg_changes) {
354+ // Check if it's a GPR write (type 0)
355+ if ((reg_change.first & 0xf ) == 0 ) {
356+ uint32_t iss_write_reg = (reg_change.first >> 4 ) & 0x1f ;
357+ // Ignore writes to x0
358+ if (iss_write_reg == 0 ) {
359+ continue ;
360+ }
361+ uint32_t iss_write_data = reg_change.second .v [0 ];
362+ expanded_reg_changes[iss_write_reg] = iss_write_data;
363+ }
364+ }
365+ }
366+
367+ // TODO: We have to deal with suppressed writes here too
368+ if (suppress_reg_write) {
369+ return true ;
370+ }
371+
372+ // If the DUT did not write a register, this is a valid micro-op (e.g., a
373+ // memory access or intermediate calculation). There is nothing to check.
374+ if (write_reg == 0 ) {
375+ return true ;
376+ }
377+
378+ // The DUT wrote a register. Check if it matches one of our expectations.
379+ auto reg_change = expanded_reg_changes.find (write_reg);
380+
381+ // DUT wrote, but ISS didn't
382+ if (reg_change == expanded_reg_changes.end ()) {
383+ std::stringstream err_str;
384+ err_str << " Expanded instruction at PC 0x" << std::hex << expanded_insn_pc
385+ << " : DUT wrote to x" << std::dec << write_reg
386+ << " , which was not an expected register write for this sequence." ;
387+ errors.emplace_back (err_str.str ());
388+ return false ;
389+ }
390+
391+ // The register index matches. Now check if the data matches.
392+ uint32_t expected_data = reg_change->second ;
393+ if (write_reg_data != expected_data) {
394+ std::stringstream err_str;
395+ err_str << " Expanded instruction at PC 0x" << std::hex << expanded_insn_pc
396+ << " : Data mismatch for register x" << std::dec << write_reg
397+ << " . DUT wrote: 0x" << std::hex << write_reg_data
398+ << " , but ISS expected: 0x" << expected_data;
399+ errors.emplace_back (err_str.str ());
400+ return false ;
401+ }
402+
403+ // Match is perfect. Remove this from our set of expectations.
404+ expanded_reg_changes.erase (reg_change);
405+
406+ if (expanded_reg_changes.empty ()) {
407+ // All expected register writes have been matched. Clear the pending state.
408+ pending_expanded_insn = 0 ;
409+ expanded_insn_pc = 0 ;
410+ }
411+
412+ return true ;
413+ }
414+
322415bool SpikeCosim::check_retired_instr (uint32_t write_reg,
323416 uint32_t write_reg_data, uint32_t dut_pc,
324417 bool suppress_reg_write,
@@ -335,6 +428,11 @@ bool SpikeCosim::check_retired_instr(uint32_t write_reg,
335428 << " , but the ISS retired: " << std::hex
336429 << (processor->get_state ()->last_inst_pc & 0xffffffff );
337430 errors.emplace_back (err_str.str ());
431+ if (pending_expanded_insn) {
432+ err_str << " (while processing expanded instruction at PC 0x" << std::hex
433+ << expanded_insn_pc << " )" ;
434+ errors.emplace_back (err_str.str ());
435+ }
338436 return false ;
339437 }
340438
0 commit comments