そろそろ、シミュレータ に移行する。ISE は重くてやってられないし、Implement するための基本の書き方はクリアしたので当面不要。パーツも揃ってきたので、動かしながら 機能を付けていく。
シミュレータは、ここを見て、Windows 版 がある Icarus Verilog を使うことにした。
まずは紹介。簡単なテストベンチを用意して、動かしてみたらこうなった。

準備
これの評価は後ですることにして、これを出すためのものについて
- rtavr-wk05.tar.gz -- シミュレーション移行開始のコードのスナップショット
- 『Icarus Verilog for Windows』-- Windows 版 setup (GTKWave 同梱)
- MinGW+MSYS
用意するものは、これだけ。Icarus Verilog for Windows には、GTKWave が 同梱されているので、他にツールはいらない。MinGW+MSYS は make で 自動化するのに使っているが、コマンドプロンプトで 直接実行することも可能だから 必須ではない。
シミュレータで動かすために変更したところは以下のもの。
- rtavr_defs.v の定義で PORTA/PORTB/PORTC を外す。そうすると rtavr_ior_port を使わなくなってしまうので、rtavr_ior.v から分離して別ファイルにした。
- rtavr_defs.v の定義で ROM を外す。ROM へのアクセスを テストベンチの TOP にまで持って行って、そこから データを送り込む。
- エラーになったところは、ビット幅を指定しない 定数のみ。エラーになったところだけ修正。
動作の検証
最初のテストベンチは、nop を送り込むだけのもの。一体どういう風に動くのかまずは見てみることにした。
一見してひどいと分かるが、これが最初だから別にチェックするものがある。
- まず CLK 。
想定どおり。(後述) - ROM_ADDRA は PC を示しているが、RESET 後 次のクロックで 001 になってしまっていて、1 番地のデータが 最初の ROM 読み出しになってしまっている。そこはおかしいが、値が変わるタイミングはあっている。
- その期間、S1 は不定値を元に動くため、次の期間 S2 への指示が不定値になっている。
- CMD_OP3 が、1 になるのは正しい。NOP は OP3 の仲間で OP3_SEL = 0 。もうすこし詳しく書くと、CPSE の一種で 決して条件が成立しないという設計にしている。ただ、これだけ不定期間が長い。なぜ?
いよいよ、楽しくなってきた。まずは RESET 周りをちゃんとして、その後いろんな命令をシミュレートしてみよう。その後、いくつか命令の組み合わせをやって、それがクリアできたら、パイプライン制御。
05A

- rtavr-wk05a.tar.gz
変更すれば、前のテストベンチの出力も変わってしまう。面倒だがいちいち スナップショットを取ることにした。
まず、RESET周りだが、CLK で止めていたのが最初の間違い。そんなことをすれば、他の回路は RESET がかからない。initial 使って 初期化はするが、RESET で CLK は止めないようにした。
あと、S1 をあれこれいじってちゃんとスタートアップできるようにした。
initial begin : test
// LDI 1110:KKKK:dddd:KKKK
sys_reset(16'he51a); // LDI r17, 0x5a
// DEC 1001:010d:dddd:1010 #
rom_out(16'h951a); // DEC, r17
// MOV 0010:11rd:dddd:rrrr #
rom_out(16'h2f21); // MOV r18,r17
rom_out(16'h0000); // NOP
rom_out(16'h0000); // NOP
rom_out(16'h0000); // NOP
rom_out(16'h0000); // NOP
$display("*** Simulation Complete! ***");
$finish;
end
今回は、LDI / DEC / MOV 。
最初のクロックは捨てて、次から開始。
E51A 951A 2F21 0000 0000
S0 LDI DEC MOV NOP NOP
S1 LDI DEC MOV NOP
S2 LDI DEC MOV
CMD_OP2 CMD_OP1 CMD_OP3(WR)
XX_SEL 2 4 7
こんな風に見る。シーケンスはあっていそうだ。
LDI の OP2_SEL は 2 だがあっている。DEC も、4 で OK。(コードをみたら - 0xff していた)。MOV の OP3_SEL も 7 で OK。
GPR はあっているのだろうか?
そもそも、DEC なのに 0x5A から 0x5B に増えている。(→ DEC のバグ) さらに、次の命令で 0x5A を見ている。r18 のロードも不定値でないといけない。
あと、GPR_ADDRxx が変化するタイミングはこれで良かったのか?
-- いくつか問題がありそうだ。
最初に動かすのだから、まぁこんなものか。思ったよりはマシな感じがする。
後で調べたら、Rd_in と Rr_in が逆だったり、タイミングが変になっていたり、いろいろグダグダになっている。GPR 単独でデバッグするつもり。
05B

- rtavr-wk05b.tar.gz
なんとなく正しそうな、結果を得た。
initial begin : test
// LDI 1110:KKKK:dddd:KKKK
sys_reset(16'he51a); // LDI r17, 0x5a
// DEC 1001:010d:dddd:1010 #
rom_out(16'h951a); // DEC, r17
// MOV 0010:11rd:dddd:rrrr #
rom_out(16'h2f21); // MOV r18,r17
// SUBI 0101:KKKK:dddd:KKKK #
rom_out(16'h5510); // SUBI r17, 0x50
// ADD/ADC 000C:11rd:dddd:rrrr #
rom_out(16'h0f21); // ADD r18,r17
rom_out(16'h0000); // NOP
rom_out(16'h0000); // NOP
rom_out(16'h0000); // NOP
$display("*** Simulation Complete! ***");
$finish;
end
今回命令も追加。LDI / DEC / MOV / SUBI / ADD の 計 5 命令。
E51A 951A 2F21 5510 0F21 0000 0000
S0 LDI DEC MOV SUBI ADD NOP NOP
S1 LDI DEC MOV SUBI ADD NOP
S2 LDI DEC MOV SUBI ADD
Rd_in xxxx 0x5A xxxx 0x59 0x59
Rr_in xxxx 0x59 0x09
DI(Rd_out) 0x5A 0x59 0x59 0x09 0x62
検証したが、一応正しいようだ。
ちなみに、直前の結果を使っていないのは、最後のADD の Rd_in と最初の LDI だけ。
バグで大きかったのは、GPR 。なにを勘違いしたのか ... negedge CLK2X でのみアクセスすべき ところを posedge CLK / negedge CLK に変えてしまっていた。
それ以外だと
- Rd_in と Rr_in の定義が逆。(上述)
- negedge CLK2X での CLK 値の取得。(上記と同じ理由で まずい ので posedge CLK2X で一旦ラッチ
- ADDRBL/ADDRBH のラッチとか フォワーディング関係。
... なんだか結構直してようやく、こんな感じになった。対処療法的な修正なので、ちょっと気持ち悪い。INDEX レジスタ操作でもう一回見直すので、そのときに再チェックしよう。
05C

- rtavr-wk05c.tar.gz
STORE 命令 。大分苦労したが、なんとなく正しそうな、結果を得た。
まず、インデックスレジスタの インクリメントとデクリメント。これは、read-modefy-write で できるようになった。
だが、設計では、インデックスレジスタ の読み込みと Rd の書き込みを (無謀にも)PORTA だけでやろうとしていたのだった。どうにも上手くいかないようなので、仕様変更をすることにした。
PORT A は、
(1) Rd を WRITE する。
(2) インデックスレジスタ(16bit) の READ (+ INC/DEC)
の2つの使い方だけをする。これは変わらないが、(1) と (2) が競合したら、(2)( = 後の命令 ) を 1clock 遅らせる。
// LDI 1110:KKKK:dddd:KKKK
rom_out(16'he0a0); // LDI r26, 0x00
rom_out(16'he0b1); // LDI r27, 0x01
// ST.X++.Rr 1001:001r:rrrr:1101 #
rom_out(16'h931d); // ST X++, r17 # X 0x0100 -> 0x0101
rom_out(16'h931d); // reentrant
// ST.X.Rr 1001:001r:rrrr:1100 #
rom_out(16'h932c); // ST X, r18 # X 0x0101
// ST.--X.Rr 1001:001r:rrrr:1110 #
rom_out(16'h931e); // ST --X, r17 # X 0x0100
// ST.--X.Rr 1001:001r:rrrr:1110 #
rom_out(16'h932e); // ST --X, r18 # X 0x00ff
// LD.Rd.X 1001:000d:dddd:1100 #
rom_out(16'h913c); // LD X, r19 # X 0x00ff
GtkWave の画面は、このようなシーケンスを出したもの。
ST が並んでいて、最初の 931D だけが 2 クロックかかっている。これは、ひとつ前の LDI で Rd に書く -- (1) のと ST での インデックスレジスタの読み込み -- (2)が競合したために遅らせたわけだ。
ST 自体はいくら並べても競合しない。最後に ST → LD があるがこれも競合しない。
ちんみに、下に見える EA が インデックスレジスタ値。 PREDEC/POSTINC もしている。

これは、続くシーケンス。
// LD.Rd.X 1001:000d:dddd:1100 #
rom_out(16'h913c); // LD X, r19 # X 0x00ff
rom_out(16'h913c); // reentrant
// LD.Rd.--X 1001:000d:dddd:1110
rom_out(16'h914e); // LD --X, r20 # X 0x00fe
rom_out(16'h914e); // reentrant
// LD.Rd.X++ 1001:000d:dddd:1101 #
rom_out(16'h915d); // LD X++, r21 # X 0x00fe -> 0x00ff
rom_out(16'h915d); // reentrant
rom_out(16'h916d); // LD X++, r22 # X 0x00ff -> 0x0100
rom_out(16'h916d); // reentrant
rom_out(16'h0000); // NOP
rom_out(16'h0000); // NOP
rom_out(16'h0000); // NOP
$display("*** Simulation Complete! ***");
$finish;
end
LD をやっているのだが、全然ダメみたいだ。ちなみに、LD はレジスタに書くから競合しまくる。2 クロックかかるわけだ。
せめて、PREDEC/POSTINC しないときは、1 命令で済ませたいような気がする。PORTB は、2 つロードできるが、1 つしか必要ない。PORTA の代わりにPORTB から読ませることは可能。
PORTB は、書き込むレジスタ値の先取り(フォワーディング)もしているし、うまく行くかも知れない。
これは、基本が動いてから、回路規模と相談して決めようと思う。
図らずも簡単なパイプライン制御のテストになってしまった。JUMP や SKIP は、割と簡単に入るような気がしてきた。実をいうと PUSH や POP のような 複数クロック命令をどう入れようか悩んでいたのだが、今の実装だと、前の命令を止めたときに S1 の状態が 止めた命令で固定される。サブステートを作ってやれば簡単に実装できるような気がしてきた。あと実装方法が決まらないのは割り込みぐらい。
05D

- rtavr-wk05d.tar.gz
次は、LOAD 命令 。
STORE 命令は OKなのに、LOAD はダメなのは、仕様上の問題だった。
LOAD 命令 は、インデックスレジスタ番号と Rd を指定する。インデックスレジスタは 上位バイトと下位バイトがあるから 3 つ指定する必要があったが、全部 PORTA だったから 競合していた。
最初の解決策は、下位バイト + 1 だと決まっているから、GPR 内で計算する方法。
もうひとつ解決策があって、PORTB の Rr に 上位バイトのレジスタ番号を指定する方法。この方法にすると、アドレスだけでなく中身もロードできてしまう。それなら、PREDEC/POSTINC しないときだけでも、中身も使いたい。そうすれば、PORTA の 上位バイトが空いて競合が減る。
これを NO_STALL_IDX という define で切り分けるようにした。
ちょっと苦労したが、最終的には、どちらも動かすことができた。
掲載しているのは、NO_STALL_IDX の方。LD の最後の命令(917C)は、LD X を 2 つ並べたが、(競合しないので)どちらも実行されている。
さて、ISE で規模がどれぐらい変わるか確認してみた。
// (1) (2) (3) (4)
// Number of Slice Flip Flops: 226 228
// Number of 4 input LUTs: 765 780
// Number of occupied Slices: 471 475
// Total Number of 4 input LUTs: 805 821
// Number used as logic 749 764
// Number used as a route-thru 40 41
// Number used for Dual Port RAMs 16 16
// Number of bonded IOBs : 67 67
// IOB Flip Flops : - -
// Number of BUFGMUXs : 2 2
// Number of RAMB16BWEs : 1 1
// Clock to Setup
//on destination clock CLK2X:
// Rise-Rise 10.052 9.871
// Fall-Rise 10.911 10.828
// Rise-Fall 9.137 10.367
// Fall-Rise 10.484 11.251
//
// (1) NORMAL for simulating -- NO ROM/PORTA/PORTB/PORTC
// (2) + NO_STALL_IDX
スライスで +4 しか違わない。性能には微妙な違いがあるが、1 クロック遅らせるケースが減るのだ。これからは IO_STALL_IDX を標準にしよう。
うまく行ったので、仕込んでいた RJMP / IJMP / SBRC/SBRS 命令に移った。
// RJMP 1100:kkkk:kkkk:kkkk #
rom_out(16'hc100); // RJMP +100
rom_out(16'h0000); // NOP
// LDI 1110:KKKK:dddd:KKKK
rom_out(16'he2e0); // LDI r30, 0x20
rom_out(16'he0f2); // LDI r31, 0x02
// IJMP 1001:0100:0000:1001 #
rom_out(16'h9409); // IJMP
rom_out(16'h0000); // NOP
// SBRC 1111:110r:rrrr:0bbb #
// SBRS 1111:111r:rrrr:0bbb #
rom_out(16'hfdf0); // SBRC r31,0
rom_out(16'hfdf1); // SBRC r31,1
rom_out(16'h0000); // NOP
rom_out(16'hfff1); // SBRS r31,1
rom_out(16'hfff0); // SBRS r31,0
rom_out(16'h0000); // NOP
命令列はこう。分岐してもアドレスが変わるだけで、命令列は決まっている。
最初の RJMP(C100) は、アドレスが + 0x100 になって、S1 を無効にしている。RJMP は アドレス 0x012 ではなく、0x011 。INST が 0x011 になったときには、すでに次の命令を読み込んでいるわけだ。それを無効にしているだけだから、2 クロックで RJMP 命令が動いている。
次は、Z レジスタに 0x220 を入れて IJMP 。S2 で書き込んだ 値を 同じクロックで 読み込めているから ちゃんと 0x220 に飛ぶ。
SBRC/SBRR はスキップするだけ。Tiny10 系は、1命令 1 ワードと決まっているから 条件が成立したら、S1 を無効に するだけの処理で良い。-- ちょっと、仕様が変わったので変更するところがあったが、問題なし。
条件分岐は、完成していないが、JMP 系と スキップ命令の 合わせ技のようなものだから、問題なく組み込めるだろう。
あと、IN/OUT LDS/STS , SBI/CBI , BLD/BST といったものが残っている。これらを片付けてから、CALL / PUSH / POP / 割り込みに移ろうかと思う。
最後に、実際のプログラムを動かしながら個々の命令を検証していく。
ついに、完成が見えてきた。あとひといき。
追記: IN/OUT LDS/STS は、OK だった。BLD/BST , SBI/CBI も組み込んだ。だが、SBI/CBI が少々論理を消費していて、ちょっと悩ましい。
BLD / CBI(SBI) / BSET(BCLR) 実装するだけで、39 スライスも消費している。命令デコードもふくんでいるから 多くなっているのかも知れないが、書き方が悪いのではないかという気もしている。ちなみに BSET(BCLR) は、CBI(SBI) のロジックを間借り。0x3f (SREG) に対する SBI(CBI) として実装している。あと残っている命令は、PUSH/POP/ICALL/RCALL/RET/RETI (+ 割り込み)のみ。
いまの性能のネックは、GPR 。POSTINC と PREDEC の両方を 1 クロックでやるのは無理があった。試しに PREDEC の機能をなくすと、割と良いバランスになった。オリジナル同様 PREDEC に 1 クロックかけたほうが良いらしい。
だが、複数クロックの命令という概念を導入した、後でしか入れられない。とりあえずは、今の枠組みの中で工夫してみることにする。
これなのだが、以下の GPR_WB_TUNE の部分に変えてみた。
`ifdef GPR_WB_TUNE
wire [7:0] DOA2 = (PREDEC & (~CLK | c) ) ? DOA - 1 : DOA;
wire [8:0] WB_DATA = (PREDEC & (~CLK | c) ) ? {1'b0 , DOA} - 1
: (POSTINC & (~CLK | c)) ? {1'b0 , DOA} + 1
: {1'b0 , DOA};
`else
wire [8:0] DOA2 = {1'b0 , DOA} - (PREDEC & (~CLK | c));
wire [8:0] WB_DATA = DOA2 + (POSTINC & (~CLK | c));
`endif
DOA2 が実効アドレス , WB_DATA が変更後の値。WB_DATA の依存関係が 2 段になっているをやめた。あと、-1 / +1 の結果を出しておいて 条件で選択するようにしてみた。
// Number of Slice Flip Flops: 51 51
// Number of 4 input LUTs: 92 112
// Number of occupied Slices: 66 71
// Total Number of 4 input LUTs: 100 113
// Number used as logic 76 96
// Number used as a route-thru 8 1
// Number used for Dual Port RAMs 16 16
// Number of bonded IOBs : 90 90
// IOB Flip Flops : - -
// Number of BUFGMUXs : 1 1
// Number of RAMB16BWEs : - -
こんな風に ロジックが増える結果になった。だが、
---------------+---------+---------+---------+---------+
| Src:Rise| Src:Fall| Src:Rise| Src:Fall|
Source Clock |Dest:Rise|Dest:Rise|Dest:Fall|Dest:Fall|
---------------+---------+---------+---------+---------+
変更前
---------------+---------+---------+---------+---------+
CLK2X_IN | 9.151| 10.150| 9.350| 10.241|
---------------+---------+---------+---------+---------+
GPR_WB_TUNE :
---------------+---------+---------+---------+---------+
CLK2X_IN | 6.282| 6.344| 6.299| 7.468|
---------------+---------+---------+---------+---------+
こんなに、違ったのだった。これでもたぶんボトルネックだが、これぐらいなら、無理して 2 命令化する必要はないかも知れない。
05E

- rtavr-wk05e.tar.gz
一旦スナップショット。とりあえずの命令列は、
// IN 1011:0AA1:dddd:AAAA # 00
// OUT 1011:1AA1:rrrr:AAAA # 00
// LDS 1010:0kkK:dddd:kkkk #
// STS 1010:1kkK:dddd:kkkk #
// LDI 1110:KKKK:dddd:KKKK
rom_out(16'hebcc); // LDI r28, 0xbc
rom_out(16'he0d0); // LDI r29, 0x00
rom_out(16'hbfcd); // OUT SPL , r28
-------- 画面ここから
rom_out(16'hbfde); // OUT SPH , r29
rom_out(16'hafcc); // STS 0xbc, r28
rom_out(16'hafdd); // STS 0xbd, r29
// LD.Rd.Y 1000:000d:dddd:1000 #
// LD.Rd.--Y 1001:000d:dddd:1010
rom_out(16'h8138); // LD r19,Y
rom_out(16'h0000); // NOP
// MOV 0010:11rd:dddd:rrrr #
rom_out(16'h2f43); // MOV r20,r19
rom_out(16'h8138); // LD r19,Y
// BST 1111:101d:dddd:0bbb #
rom_out(16'hfb42); // BST 2, r20
// BLD 1111:100d:dddd:0bbb #
rom_out(16'hf930); // BLD r19, 0
// BSET 1001:0100:0sss:1000 #
rom_out(16'h9478); // BSET I (STI)
// BCLR 1001:0100:1sss:1000 #
rom_out(16'h94f8); // BCLR I (CLI)
// BRBC 1111:01kk:kkkk:ksss #
rom_out(16'hf487); // BRBC I
rom_out(16'h0000); // NOP
// BRBS 1111:00kk:kkkk:ksss #
rom_out(16'hf087); // BRBS I
rom_out(16'h0000); // NOP
BST と BLD はまったく違う。BST は、SBRS/SBRC でも使う v_sbrx_bit を使って T に格納するだけ。BLD は、OP1/2/3 と並列で、Rd_in を演算して Rd_out (DI) に書きだす。
BSET(BCLR) は、CBI(SBI) と 同じ処理で、CMD_LOAD で IOR から読み出し、CMD_STORE で 書き戻す。そして、CMD_LOAD で IOR から読み込むデータに 演算が入っている。
最後に条件分岐を試している。+0x10 だけの 分岐で I が 0 に戻っているから BRBC で 分岐している。次の BRBS では分岐しないので、PC が増えない。
まぁこんなところ。いよいよ PUSH/POP から初めて 複数クロックの CALL/RET を実装する段階になった。
PUSH/POP の対象には、PC を含めないといけないし、 CALL/RETでも利用できるようにしないといけない。
ICALL(RCALL) は IJMP(RJMP) と同じように動作した上で、PC を PUSH する。いままで s1_invalid にするだけだった次の命令で、 PUSH を指示しないといけない。
PUSH は、CMD_STORE の一部で、何を STORE するかを EA_PUSH + PUSH_SEL で指示している。
整理すると ICALL(RCALL) の 1 命令目では、CMD_STORE , EA_PUSH , PUSH_SEL を設定するが、
2命令目でも同様。ただし、2 命令目では、PC の値が変わってしまっているので、覚えておかないといけない。
05F

- rtavr-wk05f.tar.gz
CALL と RET が完成。追加した 命令列は、
// tb_007
// PUSH 1001:001d:dddd:1111 #
rom_out(16'h933f); // PUSH r19
rom_out(16'h934f); // PUSH r20
// POP 1001:000d:dddd:1111 #
rom_out(16'h913f); // POP r19
rom_out(16'h914f); // POP r20
// ICALL 1001:0101:0000:1001 #
rom_out(16'h9509); // ICALL
rom_out(16'h0000); // NOP
rom_out(16'h0000); // NOP
// RET 1001:0101:0000:1000 #
// RETI 1001:0101:0001:1000 #
rom_out(16'h9508); // RET
rom_out(16'h0000); // NOP
rom_out(16'h0000); // NOP
933F から始まる PUSH - PUSH - POP - POP 。これでレジスタの内容 r19 = 0xbd , r20 = 0xbc を入れ替えてみた。.. うまくいった。
次は、ICALL/RET 。 2/3 クロック目の状態を、新たに作り 例外として扱うことにした。-- s1_invalid なのに S2 に指令を出すとかいうことになった 。inst の invalid という意味になったが、面倒なのでこのまま。
あと PC をどこに覚えておくか ちょっと工夫。8bit 分の r_tmp は作らざるを得なかったが、S2 に渡すPUSH するデータは、op2 での即値用 r_imm と共用した。POP したときのデータも覚えておかないとならないが、r_tmp と共用できた。
// wk005f Implement Results
// (e) (f)
// Number of Slice Flip Flops: 244 252
// Number of 4 input LUTs: 880 907
// Number of occupied Slices: 526 529
// Total Number of 4 input LUTs: 909 943
// Number used as logic 864 891
// Number used as a route-thru 36 36
// Number used for Dual Port RAMs 16 16
// Number of bonded IOBs : 67 67
// IOB Flip Flops : - -
// Number of BUFGMUXs : 2 2
// Number of RAMB16BWEs : 1 1
// Clock to Setup
//on destination clock CLK2X:
// Rise-Rise 8.635 7.953
// Fall-Rise 9.173 8.910
// Rise-Fall 9.130 7.933
// Fall-Rise 9.962 9.009
// (e) rtavr-wk05e
// (f) rtavr-wk05f
さて、次は割り込み。
, input [4:0] INT_VEC
, input INT_REQ
, output INT_ACK
というインターフェイスの外側は作った。INT_REQ は、割り込み要求で、I フラグとの AND 済み。INT_VEC は、割り込み番号で、保留されている割り込みの 優先度が最も高いものの番号。INT_ACK が 1 になると、次の割り込みに切り替わる。
s1 では、命令の間 -- s1_invalid でないなら 割り込み を受付け 割り込み処理中状態にする。そして、割り込み処理中状態が終われば、INT_ACK を 1 クロック 1 にする。それ以上 1 にすると処理していない割り込みの保留がとける。
割り込み処理では、INT_VEC のアドレスに call する。その後は i2_call に合流させて call の処理にかぶせる。i2_int というのも作ってこれを INT_ACK に割り当て。基本 call と同じだから 受け付けて 2 クロックで割り込み処理関数実行。そこが RETI なら +3 クロックで復帰。
実装してテストしているのだが、05f で、なにかおかしくなっていた。調べたら RET/RETI のデコードが間違っていた。
正しくは、
wire f_ret = f_ext & INST[8]
& (INST[3:0] == 4'b1000); // RET/RETI
あと、BSET(SEI) の実行後 1 クロックは、割り込みは受け付けない。これはどう解釈したら良いのだろう? I フラグが変化したら? (I フラグ を変化させる 命令は、BSET(SEI) 以外に SBI と OUT がある。) それとも SEI だけの特殊効果?
ちなみに、 SLEEP 命令には、必須の機能。SLEEP 実行前に待ちたい割り込みを受け付けてしまうと、SLEEP で止まってしまうことになる。さらに説明しておくと、他のプロセッサだと SLEEP (相当)命令で、SEI (相当) を自動的に行う方が一般的。
ここは、SEI だけの特殊効果 ということにしておこう。あと、割り込みを受け付けたら I フラグが自動的に 0 になる。RETI で CMD_SETI という s2 への指示を作ったので、 CMD_CLRI というのも作ることにした。
大分うまくいった .. と思ったのだが、RETI で 戻るアドレスが +1 されてしまっている。存在しない命令で置き換えたようなもので、今の仕組みではそうなってしまう.. ということは分かった。それで、命令を止めるべく検討を始めたら ... 分岐 や スキップする場合でも S0 での PREDEC/POSTINC を実行してたことが分かった。... というより後回しにしたのを忘れていた。
ちょっと複雑なのと、分岐 や スキップ , 割り込みが関係するので、ここでまとめようと思う。
S0 で PREDEC/POSTINC の指示を抑止 する理由は、次の 3 つがある。
(1) S1 実行中の命令が、次の S2 で Rd に書き戻す。 (s0_inv_conflict)
(2) S1 実行中の命令が、スキップする。 (s0_inv_skip)
(3) S1 実行中の命令が、分岐 する。 (s0_inv_jump)
(割り込み処理を含む)
(4) S1 で割り込み処理が開始される。 (v_int)
抑止したら当然 次のクロックでの S1 は実行しない。(s1_invalid)
(割り込み処理は、例外)
(1),(4) は、PC の更新をしない。(2) は +1 する。(3) はロード。
ただし、割り込み処理が開始できる条件は、(2) (3) の状態でないこと。
これで、書きなおしたら うまくいった... ようなかんじ。
05G

- rtavr-wk05g.tar.gz
ついに割り込みも完成。本当に大丈夫なのか? ちょっと検証してみよう。
今回の 命令列は、
// tb_008
IOR_INT0 = 1;
rom_out(16'h0000); // NOP
// BSET 1001:0100:0sss:1000 #
rom_out(16'h9478); // BSET(SEI)
// LDI 1110:KKKK:dddd:KKKK
rom_out(16'he51a); // LDI r17, 0x5a
rom_out(16'h0000); // NOP
rom_out(16'h0000); // NOP
// RETI 1001:0101:0001:1000 #
rom_out(16'h9518); // RETI
rom_out(16'h0000); // NOP
rom_out(16'h0000); // NOP
rom_out(16'h0000); // NOP
rom_out(16'h0000); // NOP
rom_out(16'h0000); // NOP
INT0 を取り出して、テストベンチで操作できるようにした。まず IOR_INT0 = 1 で割り込み要求を出す。が、I=0 なので INT_REQ が 1 にならない。SEI(9478) してやると、v_int が 1 になる。これは準備段階で、まず S0 を抑止。S1 に入っている命令は止めない。
止めるのは、24F の NOP(0000)から。3 クロック消費して、001 番地の RETI(9518) が実行される。3 クロック消費で、24F から実行開始。分岐系は 2 クロックだが、割り込みは 3 クロック。
... なにか間違えたかも。2 クロックでできるような気がしてきた。
一応動いているのだから、変更は後回しでも良いか。
ついでに ToDO メモ: GPR が ボトルネックになってしまったので、ケチらないで 16bit 幅にしたものを作ってみたい。
規模はこんな風になった。
// wk005g Implement Results
// (f) (g) (g2)
// Number of Slice Flip Flops: 252 252 290
// Number of 4 input LUTs: 907 925 927
// Number of occupied Slices: 529 537 562
// Total Number of 4 input LUTs: 943 960 964
// Number used as logic 891 909 911
// Number used as a route-thru 36 35 37
// Number used for Dual Port RAMs 16 16 16
// Number of bonded IOBs : 67 67 10
// IOB Flip Flops : - - -
// Number of BUFGMUXs : 2 2 2
// Number of RAMB16BWEs : 1 1 3
// Clock to Setup
//on destination clock CLK2X:
// Rise-Rise 7.953 8.684 7.802
// Fall-Rise 8.910 10.331 9.392
// Rise-Fall 7.933 8.784 8.286
// Fall-Rise 9.009 10.395 9.828
537 スライス。少し増えただけですんだ。となりの (g2) は、PORTC と INT0 それに ROM を入れたもの。ポート 8 bit と RESET , CLK2X の 10 ピン構成。タイマーはもとより入れてあるから、動作できる最小構成。
これに、USART を入れて、なんとか 50A で使えるようにするのが目標。あと、DCM も 50A で使うつもり。一応全機能入れているから、ここから大幅に大きくなることはない。なんとかなりそうな気がする。
もともとの目標は、コアだけで 300 以内。この場合のコアには、タイマーも割り込みコントローラも入っていない。RAM すら入れなくて良いとも思う。果たして この目標はクリアできるのだろうか?
さて、そろそろ次の段階に入る。実際のプログラムを動かしながら、個々の命令をテストしていく。
(続く)
関連記事:
著作権について
ここで提示しているコードは正しく動作しないとはいえ、既に著作権は発生しています。
著作権は、すzが保持しており放棄はしていません。
教育目的および私用目的では、もともと著作権の範囲外なので自由につかえます。また、ライセンスとして、GPL を適用しています。GPL に従う範囲において 個別の許可なく使用することができます。許諾のための連絡も不要です。
なお、GPL なので、生成したバイナリを作り直せる範囲のソース開示が必要になります。FPGA だと 通常 チップ全体になってしまいます。また、開示したソースを GPL の範囲で再利用されることを妨げることはできなくなります。このコードを利用する場合この点に留意してください。
個別の許可を得れば、GPL 以外の条件での使用は可能です。が、作業中のものには許可は出さない予定です。作業が完了(もしくは中断)したとき、ライセンスは見直します。
なお、すでに公開してしまったものの、ライセンスを取り消すことはできないと考えていますが、ライセンスの追加は可能です。また、新しく公開するものについては、ライセンスしないことすら可能で、どのような制限もありません。変更する可能性がありますので、この点にも留意してください。