Address misalign exception
想开始加exception了,之前cpu6 soc1里面只做过illegal instruction的,这次准备先搞address misalign的。
lsu里没处理这个,比如store,不管地址是不是misalign,都直接发出去给cache,发出data_req,只是要写的数据没有写进去。
st.h存两个字节,地址是0xc0000019,是不对其的。data_addr_ok是给回来了,后面data_data_ok是咋回事,这是个st指令。。
先记下,回头再调。
看了下龙芯手册,不是用的中断向量表,除了TLB,所有的exception和interrupt都是走CSR.EENTRY的入口。
然后通过CSR.ESTA里的信息由软件来判断是什么情况。
单这个EENTRY地址怎么定的,手册里没有。reset后的地址是1c000000是手册给出来的,这个EENTRY没给。
看了下chiplab,chiplab里没有exception这部分功能,之前也没注意。
查了下龙芯手册,结果发现LoongArch32和LoongArch64还不一样。
或者说LoongArch32是CSR.ECFG.VS为0时候的特殊情况。LoongArch64里可以用中断向量表的,为什么要这么设计,还不懂。
调的过程中遇到个问题。
lsu里给出的lsu_rdata_valid_m,这个名字我起的不好,应该叫lsu_finish_m。表示load或store操作结束,但并不能保证store是否成功,load是否成功读到数据。
问题是,如果lsu_rdata_valid_m不给出,lsu就一直stall ifu。
assign lsu_rdata_valid_m = data_data_ok;
1015 //
1016 // lsu_dispatch_d is the staring signal
1017 // lsu_ecl_rdata_valid_m ends it
1018 //
1019 assign lsu_stall_req_next = (lsu_dispatch_d) | (lsu_stall_req & ~lsu_ecl_rdata_valid_m);
1020
1021 dffr_s #(1) lsu_stall_req_reg (
1022 .din (lsu_stall_req_next),
1023 .clk (clk),
1024 .q (lsu_stall_req),
1025 .se(), .si(), .so(), .rst (~resetn));
导致的问题就是即使store的时候有address misalign exception,因为ifu被stall住了,pc_bf接换到eentry,但没法取值,就全卡在那了。
之前改成了如果有exception就不发data_req,这样后面应该自己给出lsu_finish_m,让这条store指令结束,直接进eentry。
assign data_req = valid & !lsu_except;
这样还要等一拍再给lsu_finish_m,也就是lsu_rdata_valid_m,因为ale是在_e就发现的。
现在就先让data_req发出,就算有exception也发出,让外面cache按原来的逻辑走,以后再改这块。
改了以后, lsu_rdata_valid_m就下个周期就回来了,这样store指令就能完成(红框),这样才能跳到eentry继续后面的代码。
不过黄框里的lsu_except却一直是1,这里应该和lsu的valid一起给出信号,表示这个except确实是lsu这时候给出的。
lsu_except一直是1也会让pc_bf一直卡在eentry上。
lsu_dispatch_d是在_d时的,在_e的dispatch,就是lsu里的valid。
330 // ale should go with valid, and valid is actually lsu_dispatch_e, only last one cycle
331 assign lsu_ecl_ale_e = lsu_ale & valid;
现在的问题是exu_ifu_stall_req是2cycle,而lsu_except是1个cycle,这两个信号会同时影响pc_bf。
最好的情况是exu_ifu_stall_req后马上接lsu_except,意思是ifu不被当前store指令stall了以后,马上跳入eentry。
首先,
206 assign ifu_pcbf_sel_excpc_bf_l = ~exu_ifu_except | exu_ifu_stall_req;
ifu_pcbf_sel_excpc_bf_l不能这样,因为exu_ifu_stall_req比exu_ifu_except持续的久,所以永远ifu_pcbf_sel_excpc_bf_l也不会是0。
而且exu_ifu_except的优先级要高过exu_ifu_stall_req,这个意思是exu_ifu_stall_req的过程中(可能是load或store指令引起的)可能会发生异常。
异常出了以后ifu取值的pc_bf要马上指向eentry,这个时候exu_ifu_stall_req应该取消了,因为load或store指令出错,已经被终止了。
取消exu_ifu_stall_req要到源头上去取消,有可能是lsu,也可能是csr,就麻烦了。
可以让exu_ifu_except的优先级高,这样取值pc_bf就没问题。但隐藏的问题可能是,当这个cycle过了,stall还在继续,特别是load指令,这个stall可能还要持续4-5个cycle。
所以最正确的做法是,在lsu源头上,_e的时候就能发现ale这种问题,就不data_req了,而是让
assign lsu_rdata_valid_m = data_data_ok;
变成
assign lsu_rdata_valid_m = data_data_ok | lsu_except;
而这个信号的名字也应该改成
assign lsu_finish_m = data_data_ok | lsu_except;
不过这个信号是在_m给出的,这就还是遇到了开头说到的那个问题。
并且stall还是会持续同样的2个cycle。
这样不管怎么改都很乱啊,跳转的逻辑很复杂。
要不要考虑和中断一样,exception也是标记上,然后跟着指令走,在_w的时候处理?
这时候lsu都已经在_m的时候完成了指令。
这样也有问题,不是在第一时间,而是在_w的时候处理exception,这样前面进到pipeline里的指令要刷掉。
还是先看看opensparc。
先处理了exu_ifu_except,这样pc_bf选择地址是没问题了。
200 assign ifu_pcbf_sel_old_bf_l = ((inst_valid || reset || br_taken) & (~exu_ifu_stall_req)) | exu_ifu_except; // exception need ifu to fetc\
h instruction from eentry
而且对于store指令来说,没啥问题。
但按之前说的,在load的时候,exu_ifu_except过去以后还会有很长的一段stall继续。
改成之前说的那样了,如果有ale,不发data_req,并且自己signal lsu_finish_m。
292 // if ale, data req is not sent out, so there will be no data_data_ok back
293 // lsu_ale is at _e, wait for the next cycle to signal lsu_finish_m
294 assign lsu_finish_m = data_data_ok | (lsu_ale_m & valid_m);
295
296
297
298 assign data_req = valid_e & !lsu_ale_e; // if ale, do not send out data req