這篇要談的是 j1 CPU 的 Verilog 程式中和系統重設以及 program counter 有關的部份,請見以下檔案:
src/hardware/verilog/j1.v
在 j1.v 中,系統重設的訊號是 sys_rst_i,因此搜尋所有 sys_rst_i 出現的地方。
以下是其中一處:
always @(posedge sys_clk_i)
begin
if (sys_rst_i) begin
pc <= 0;
dsp <= 0;
st0 <= 0;
rsp <= 0;
end else begin
dsp <= _dsp;
pc <= _pc;
st0 <= _st0;
rsp <= _rsp;
end
end
說明如下:
- sys_clk_i:系統的 clock 訊號
- always @(poseedge sys_clk_i):在系統的 clock 訊號的上升邊緣總是要執行底下 begin 至 end 的事。
- sys_rst_i:系統的重設訊號
- pc:program counter
- dsp: 資料堆疊指標
- st0: 資料堆疊頂端的暫存器
- rsp:返回堆疊的指標
以下是另一處:
always @*
begin
if (sys_rst_i)
_pc = pc;
else
if ((insn[15:13] == 3'b000) |
((insn[15:13] == 3'b001) & (|st0 == 0)) |
(insn[15:13] == 3'b010))
_pc = insn[12:0];
else if (is_alu & insn[12])
_pc = rst0[15:1]; // 注意推上返回堆疊的副程式返回位址是 8 bit 的 byte 位址,而 program counter 使用的是 16 bit 的 word 位址,因此用 rts0[15:1],不使用 rts0[15:0]。
else
_pc = pc_plus_1;
end
為了瞭解以上程式必須瞭解 J1 機器碼的編碼,請見下圖或是 J1: a small Forth CPU Core for FPGAs。
程式說明如下:
- always @*:總是要做以下 begin 至 end 間的事。
- insn:放的是目前要執行的機器碼。
- 如果系統重設訊號 sys_rst_i 為真,則 _pc = pc, 不做任何改變。
- 如果重設訊號為假,則檢查目前要執行的機器碼,
- 如果第 13 到第 15 位元為二進制的 000, 或是 001 或是 010,分別是 j1 CPU 的 jump, conditaional jump 及 call 指令,則 program counter 被設為機器碼中 0 到 12 位元的值。
- 如果機器碼是 alu (13到15位元為二進制的 011),而且 12 位元為 1,則這是一個把返回堆疊頂的資料放到 program counter 的指令,就是副程式返回,就是 Forth 的 NEXT 指令啦。
- 如果以上皆非,那麼 program counter 等於 program counter 加一,處理下一個機器碼。
程式中 |st0 == 0 的 | 會把 st0 中的 bits 都 or 起來,因此,這句判斷堆疊最上面的資料是否為零。這被用在 conditional jump:如果堆疊最上面的資料為零就要 jump。
由於 jump, conditional jump 及 call 後的位址只有 0 到 12 共 13 個位元,我們知道 j1 的程式最大只有 8K。不是 8K bytes,因為 J1 是 16 位元的 CPU,每個機器碼長度為 16 bits。
這樣子,我們就瞭解了 j1 在系統重設時的行為,以及它是如何實現 jump, conditional jump, call 及 ret (NEXT) 的。 由於 j1.v 只有 200 行程式,我們已經看懂了它的 30/200 之一的程式。
不過,在看上面的程式時要注意 Verilog 的 <= 和 = 的差異,這部份比較隱晦,請閱讀相關書籍。
沒有留言:
張貼留言