6510模拟器Klaus Dormann中断测试异常:NMI频繁触发求助
6510模拟器NMI误触发及IRQ电平敏感处理修复方案
问题核心
用Klaus Dormann的6502测试集验证自研6510模拟器时,除中断模块外功能正常,但NMI持续误触发。结合提示的两个关键问题:
- IRQ是电平敏感,而非边沿敏感
- 当前代码错误地在低电平转高电平时触发IRQ
从提供的代码来看,核心逻辑错误集中在中断触发判断、NMI规则实现两部分。
代码修复方案
1. 重构边沿/电平检测函数
原changed函数仅能检测低→高边沿,无法满足NMI和IRQ的不同触发需求,需扩展为更灵活的检测函数:
// 检测指定bit的电平是否发生变化 static inline bool bit_changed(int old_val, int new_val, int bit) { return ((old_val & bit) != (new_val & bit)); } // 检测指定bit是否从高电平转为低电平(负边沿,适配NMI触发规则) static inline bool high_to_low(int old_val, int new_val, int bit) { return ((old_val & bit) != 0) && ((new_val & bit) == 0); }
2. 重写中断处理逻辑
严格区分NMI和IRQ的触发规则:
- NMI:负边沿触发(高→低),一旦触发必须响应(不受IRQ屏蔽位影响)
- IRQ:低电平有效且电平敏感,仅当IRQ屏蔽位未置位时,在指令执行结束后响应
void interrupt_handler(MOS_6510* const c) { // 处理NMI:高→低负边沿触发,仅触发一次 if (high_to_low(c->old_status, c->irq_status, 0x2)) { NMI(c); } // 处理IRQ:低电平有效+电平敏感,未屏蔽时响应 // 需避免持续低电平下重复触发,所以仅在电平从高转低时响应一次 if (bit_changed(c->old_status, c->irq_status, 0x1) && !(c->irq_status & 0x1)) { if (!((c->p >> IRQ_DISABLE_FLAG) & 1)) { IRQ(c); } } // 更新旧状态,用于下一轮检测 c->old_status = c->irq_status; }
3. 修正NMI函数的错误标志位设置
原NMI函数错误设置了BREAK_COMMAND_FLAG,该标志仅在BRK指令触发中断时需要置位,NMI无需处理:
void NMI(MOS_6510* const c) { push_word(c, c->pc); push_byte(c, c->p); change_bit(c->p, IRQ_DISABLE_FLAG, 1); c->pc = rw(c, 0xFFFA); c->cyc += 7; }
4. 调整执行循环的状态读取逻辑
将irq_status的读取移至循环内部,确保每次迭代都获取最新的中断引脚状态:
while(true) { c->irq_status = rb(c, 0xBFFC); interrupt_handler(c); mnemonics(c); ... }
关键规则说明
- NMI触发逻辑:6502/6510的NMI为负边沿触发,仅在引脚从高电平转为低电平时触发一次,即使电平持续低,也不会重复触发,直到恢复高电平后再次变低。
- IRQ电平敏感逻辑:IRQ引脚保持低电平时,只要IRQ屏蔽位未置位,CPU会在每条指令执行结束后响应一次;若电平持续低,需等待电平恢复高后再次变低,才会触发下一次响应,避免无限循环触发。
内容的提问来源于stack exchange,提问作者cdunku
相关产品推荐
相关产品推荐

