@@ -39,7 +39,8 @@ 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 ), pending_iside_error(false ), insn_cnt(0 ),
43+ pending_expanded_insn(0 ) {
4344 FILE *log_file = nullptr ;
4445 if (trace_log_path.length () != 0 ) {
4546 log = std::make_unique<log_file_t >(trace_log_path.c_str ());
@@ -215,7 +216,12 @@ bool SpikeCosim::step(uint32_t write_reg, uint32_t write_reg_data, uint32_t pc,
215216 // (If the current step causes a synchronous trap, it will be
216217 // recorded against the current pc)
217218 initial_spike_pc = (processor->get_state ()->pc & 0xffffffff );
218- processor->step (1 );
219+ // Only step Spike if the current instruction is not an expanded one. Spike
220+ // does not expand instructions, so we have to consume a single Spike step
221+ // over multiple steps of Ibex.
222+ if (pending_expanded_insn == 0 ) {
223+ processor->step (1 );
224+ }
219225
220226 // ISS
221227 // - If encountered an async trap,
@@ -310,15 +316,98 @@ bool SpikeCosim::step(uint32_t write_reg, uint32_t write_reg_data, uint32_t pc,
310316 suppressed_write_reg_data);
311317 }
312318
313- if (!check_retired_instr (write_reg, write_reg_data, pc, suppress_reg_write)) {
314- return false ;
319+ if (expanded_insn) {
320+ if (!check_expanded_instr (write_reg, write_reg_data, pc, suppress_reg_write,
321+ expanded_insn)) {
322+ return false ;
323+ }
324+ } else {
325+ if (!check_retired_instr (write_reg, write_reg_data, pc, suppress_reg_write,
326+ expanded_insn)) {
327+ return false ;
328+ }
315329 }
316330
317331 // Only increment insn_cnt and return true if there are no errors
318332 insn_cnt++;
319333 return true ;
320334}
321335
336+ bool SpikeCosim::check_expanded_instr (uint32_t write_reg,
337+ uint32_t write_reg_data, uint32_t dut_pc,
338+ bool suppress_reg_write,
339+ uint32_t expanded_insn) {
340+ // If this is the first step of an expanded instruction, set up our expectations.
341+ if (!pending_expanded_insn) {
342+ pending_expanded_insn = expanded_insn;
343+ expanded_insn_pc = dut_pc;
344+ expanded_reg_changes.clear ();
345+
346+ // The ISS has just stepped once for the entire expanded instruction.
347+ // We now collect all the GPR changes it produced and store them in a map
348+ auto ®_changes = processor->get_state ()->log_reg_write ;
349+ for (auto reg_change : reg_changes) {
350+ // Check if it's a GPR write (type 0)
351+ if ((reg_change.first & 0xf ) == 0 ) {
352+ uint32_t iss_write_reg = (reg_change.first >> 4 ) & 0x1f ;
353+ // Ignore writes to x0
354+ if (iss_write_reg == 0 ) {
355+ continue ;
356+ }
357+ uint32_t iss_write_data = reg_change.second .v [0 ];
358+ expanded_reg_changes[iss_write_reg] = iss_write_data;
359+ }
360+ }
361+ }
362+
363+ // TODO: We have to deal with suppressed writes here too
364+ if (suppress_reg_write) {
365+ return true ;
366+ }
367+
368+ // If the DUT did not write a register, this is a valid micro-op (e.g., a
369+ // memory access or intermediate calculation). There is nothing to check.
370+ if (write_reg == 0 ) {
371+ return true ;
372+ }
373+
374+ // The DUT wrote a register. Check if it matches one of our expectations.
375+ auto reg_change = expanded_reg_changes.find (write_reg);
376+
377+ // DUT wrote, but ISS didn't
378+ if (reg_change == expanded_reg_changes.end ()) {
379+ std::stringstream err_str;
380+ err_str << " Expanded instruction at PC 0x" << std::hex << expanded_insn_pc
381+ << " : DUT wrote to x" << std::dec << write_reg
382+ << " , which was not an expected register write for this sequence." ;
383+ errors.emplace_back (err_str.str ());
384+ return false ;
385+ }
386+
387+ // The register index matches. Now check if the data matches.
388+ uint32_t expected_data = reg_change->second ;
389+ if (write_reg_data != expected_data) {
390+ std::stringstream err_str;
391+ err_str << " Expanded instruction at PC 0x" << std::hex << expanded_insn_pc
392+ << " : Data mismatch for register x" << std::dec << write_reg
393+ << " . DUT wrote: 0x" << std::hex << write_reg_data
394+ << " , but ISS expected: 0x" << expected_data;
395+ errors.emplace_back (err_str.str ());
396+ return false ;
397+ }
398+
399+ // Match is perfect. Remove this from our set of expectations.
400+ expanded_reg_changes.erase (reg_change);
401+
402+ if (expanded_reg_changes.empty ()) {
403+ // All expected register writes have been matched. Clear the pending state.
404+ pending_expanded_insn = 0 ;
405+ expanded_insn_pc = 0 ;
406+ }
407+
408+ return true ;
409+ }
410+
322411bool SpikeCosim::check_retired_instr (uint32_t write_reg,
323412 uint32_t write_reg_data, uint32_t dut_pc,
324413 bool suppress_reg_write,
@@ -335,6 +424,11 @@ bool SpikeCosim::check_retired_instr(uint32_t write_reg,
335424 << " , but the ISS retired: " << std::hex
336425 << (processor->get_state ()->last_inst_pc & 0xffffffff );
337426 errors.emplace_back (err_str.str ());
427+ if (pending_expanded_insn) {
428+ err_str << " (while processing expanded instruction at PC 0x"
429+ << std::hex << expanded_insn_pc << " )" ;
430+ errors.emplace_back (err_str.str ());
431+ }
338432 return false ;
339433 }
340434
0 commit comments