カウンタ回路(Chisel)
カウンタ回路の用途
イベントのカウント
時間の間隔を定義
時間の間隔を定義?
例えば,クロックのサイクル数をカウントして,指定の時間間隔が経過したら,動作トリガを起動
Chiselの実装例
3サイクル後,0に戻るカウンタ回路
val countReg = RegInit(0.U(32.W)) countReg := Mux(countReg === 2.U, 0.U, countReg + 1.U)
レジスタはクロックの立ち上がりエッジで入力を取り組む.
0 -> 1 -> 2 -> このタイミングで0に戻したい.ので
countReg === 2.U
と設定する.
countRegの出力が2のときにリセットすれば,3サイクルを数えたことになる.
# 豆知識
Chiselでは「レジスタ信号」と「組み合わせ論理信号」を区別するために,レジスタ名の後ろにRegをつける習慣がある.
キャメルケースとサフィックスで分かりやすくしている.
テスト・シミュレーションのメモ(System verilog)
- `timescale
- `default_nettype
- module
- clk, rst
- その他信号
- その他定数
- テストするモジュールの宣言
- initial
- $display
- logic宣言した信号初期化
- クロック生成
- クロックごとの処理
- 遅延追加
- デバッグ出力
- $finish
だんだん完成に近づいていく.
`timescale
`timescale 1ns / 100ps
`default_nettype
`timescale 1ns / 100ps `default_nettype none ... `default_nettype wire
module
`timescale 1ns / 100ps `default_nettype none module testcore(); ... endmodule `default_nettype wire
clk, rst
`timescale 1ns / 100ps `default_nettype none module testcore(); logic clk; wire rst = 1; endmodule `default_nettype wire
その他信号
`timescale 1ns / 100ps `default_nettype none module testcore(); logic clk; wire rst = 1; logic foo; logic bar; wire [31:0] tmp1; wire [31:0] tmp2 [31:0]; wire complete; endmodule `default_nettype wire
その他定数
`timescale 1ns / 100ps `default_nettype none module testcore(); logic clk; wire rst = 1; logic foo; logic bar; wire [31:0] tmp1; wire [31:0] tmp2 [31:0]; wire complete; int maxclocks = 10000000; int i, j, k, l; endmodule `default_nettype wire
テストするモジュールの宣言
`timescale 1ns / 100ps `default_nettype none module testcore(); logic clk; wire rst = 1; logic foo; logic bar; wire [31:0] tmp1; wire [31:0] tmp2 [31:0]; wire complete; int maxclocks = 10000000; int i, j, k, l; moduleA _moduleA(clk, rst, foo, bar, tmp1, tmp2, complete); endmodule `default_nettype wire
initial
`timescale 1ns / 100ps `default_nettype none module testcore(); logic clk; wire rst = 1; logic foo; logic bar; wire [31:0] tmp1; wire [31:0] tmp2 [31:0]; wire complete; int maxclocks = 10000000; int i, j, k, l; moduleA _moduleA(clk, rst, foo, bar, tmp1, tmp2, complete); initial begin ... end endmodule `default_nettype wire
$display
`timescale 1ns / 100ps `default_nettype none module testcore(); logic clk; wire rst = 1; logic foo; logic bar; wire [31:0] tmp1; wire [31:0] tmp2 [31:0]; wire complete; int maxclocks = 10000000; int i, j, k, l; moduleA _moduleA(clk, rst, foo, bar, tmp1, tmp2, complete); initial begin $display("------------ start ---------------"); end endmodule `default_nettype wire
logic宣言した信号初期化
`timescale 1ns / 100ps `default_nettype none module testcore(); logic clk; wire rst = 1; logic foo; logic bar; wire [31:0] tmp1; wire [31:0] tmp2 [31:0]; wire complete; int maxclocks = 10000000; int i, j, k, l; moduleA _moduleA(clk, rst, foo, bar, tmp1, tmp2, complete); initial begin $display("------------ start ---------------"); clk = 0; foo = 0; bar = 0; end endmodule `default_nettype wire
クロック生成
`timescale 1ns / 100ps `default_nettype none module testcore(); logic clk; wire rst = 1; logic foo; logic bar; wire [31:0] tmp1; wire [31:0] tmp2 [31:0]; wire complete; int maxclocks = 10000000; int i, j, k, l; moduleA _moduleA(clk, rst, foo, bar, tmp1, tmp2, complete); initial begin $display("------------ start ---------------"); clk = 0; foo = 0; bar = 0; for (i=1; i<=maxclocks; i++) begin for (j=0; j<2; j++) begin clk = ~clk; end end end endmodule `default_nettype wire
クロックごとの処理
`timescale 1ns / 100ps `default_nettype none module testcore(); logic clk; wire rst = 1; logic foo; logic bar; wire [31:0] tmp1; wire [31:0] tmp2 [31:0]; wire complete; int maxclocks = 10000000; int i, j, k, l; moduleA _moduleA(clk, rst, foo, bar, tmp1, tmp2, complete); initial begin $display("------------ start ---------------"); clk = 0; foo = 0; bar = 0; for (i=1; i<=maxclocks; i++) begin if (i == 1000) begin foo = 1; end if (i == 2000) begin foo = 0; end if (i == 3000) begin bar = 1; end if (i == 4000) begin bar = 0; end for (j=0; j<2; j++) begin clk = ~clk; end if (complete) begin break; end end end endmodule `default_nettype wire
遅延追加
`timescale 1ns / 100ps `default_nettype none module testcore(); logic clk; wire rst = 1; logic foo; logic bar; wire [31:0] tmp1; wire [31:0] tmp2 [31:0]; wire complete; int maxclocks = 10000000; int i, j, k, l; moduleA _moduleA(clk, rst, foo, bar, tmp1, tmp2, complete); initial begin $display("------------ start ---------------"); clk = 0; foo = 0; bar = 0; for (i=1; i<=maxclocks; i++) begin if (i == 1000) begin foo = 1; end if (i == 2000) begin foo = 0; end if (i == 3000) begin bar = 1; end if (i == 4000) begin bar = 0; end for (j=0; j<2; j++) begin #10 clk = ~clk; end if (complete) begin break; end end end endmodule `default_nettype wire
デバッグ出力
`timescale 1ns / 100ps `default_nettype none module testcore(); logic clk; wire rst = 1; logic foo; logic bar; wire [31:0] tmp1; wire [31:0] tmp2 [2:0]; wire complete; int maxclocks = 10000000; int i, j, k, l; moduleA _moduleA(clk, rst, foo, bar, tmp1, tmp2, complete); initial begin $display("------------ start ---------------"); clk = 0; foo = 0; bar = 0; for (i=1; i<=maxclocks; i++) begin if (i == 1000) begin foo = 1; end if (i == 2000) begin foo = 0; end if (i == 3000) begin bar = 1; end if (i == 4000) begin bar = 0; end for (j=0; j<2; j++) begin #10 clk = ~clk; end if (complete) begin break; end end $display("finish clk :%5d", i); $display("tmp1 :%5d", tmp1); $display("tmp2 :%5d, %5d, %5d", tmp2[0], tmp2[1], tmp2[2]); end endmodule `default_nettype wire
$finish
`timescale 1ns / 100ps `default_nettype none module testcore(); logic clk; wire rst = 1; logic foo; logic bar; wire [31:0] tmp1; wire [31:0] tmp2 [2:0]; wire complete; int maxclocks = 10000000; int i, j, k, l; moduleA _moduleA(clk, rst, foo, bar, tmp1, tmp2, complete); initial begin $display("------------ start ---------------"); clk = 0; foo = 0; bar = 0; for (i=1; i<=maxclocks; i++) begin if (i == 1000) begin foo = 1; end if (i == 2000) begin foo = 0; end if (i == 3000) begin bar = 1; end if (i == 4000) begin bar = 0; end for (j=0; j<2; j++) begin #10 clk = ~clk; end if (complete) begin break; end end $display("finish clk :%5d", i); $display("tmp1 :%5d", tmp1); $display("tmp2 :%5d, %5d, %5d", tmp2[0], tmp2[1], tmp2[2]); $display("--------------finish---------------"); $finish; end endmodule `default_nettype wire
情報科学における直交性とは?
システム内の要素の多寡を指し示す慣用的な用語
- どの要素も本質的には同じように振る舞うならば直交性が高いと表現する.
CISCは命令の直交性が高い
- CISCは,任意の演算で、どのアドレッシングモードでも使えるため,「命令の直交性が高い」と言える.
(アセンブラ言語が用いられていた頃には豊富なアドレッシングモードを備えて,命令の直交性を備えることが良いと考えられていた.1970年代後半,IBMなどの研究で,実際に使用されているプログラムを解析したところ,複数の処理を一気に行う高機能な命令や,いわゆる直交性のある,命令とアドレッシングモードの組み合わせの大部分は実際のプログラムでは使われていないことが判明した.)
RISCはレジスタの直交性が高い
アドレッシング・モード RV32Iの例
レジスタ間接(ベースオフセット/レジスタオフセット)
ロード命令
1. 符号拡張1バイトロード
レジスタrs1に符号付き符号拡張即値を加えた値を有効アドレスとして1バイトロードして,符号拡張してレジスタrdに書き込む.
ld rd, offset(rs1)
2. ゼロ拡張1バイトロード
レジスタrs1に符号付き拡張即値を加えた値を有効アドレスとして1バイトロードして,ゼロ拡張してレジスタrdに書き込む.
lbu rd, offset(rs1)
3. 符号拡張2バイトロード
レジスタrs1に符号付き拡張即値を加えた値を有効アドレスとして2バイトロードして,符号拡張してレジスタrdに書き込む.
lh rd, offset(rs1)
4. ゼロ拡張2バイトロード
レジスタrs1に符号付き拡張即値を加えた値を有効アドレスとして2バイトロードして,ゼロ拡張してレジスタrdに書き込む.
lhu rd, offset(rs1)
5. 4バイトロード(32bitだから符号拡張なし)
レジスタrs1に符号付き拡張即値を加えた値を有効アドレスとして4バイトロードしてレジスタrdに書き込む.
lhu rd, offset(rs1)
参考文献
アドレッシング・モード
アドレッシング・モードとは?
- メモリを参照する方式(メモリアドレスの指定の方法)
参照したアドレスはなんて呼ぶ?
- 有効アドレス(Effective Address)
アドレッシング・モードを構成する要素は?
ベースポインタ/ベースレジスタ
アクセスすべきメモリアドレスを格納するレジスタ
メモリブロックの先頭アドレスを保持して,オフセット(ディスプレースメント)を利用してメモリブロック内の任意のアドレスを指定するのにも使われる.
即値/イミディエイト
直接アドレス
オフセット(ディスプレースメント)
命令用アドレッシング・モードの種類?
絶対アドレス方式
- オペランドに即値で直接アドレスを指定する方式
PC相対方式
次の命令のアドレス(PC)にオペランドとして指定したオフセット値を加えたものを有効PCアドレスとする方式
分岐命令でよく見られる.なぜなら,分岐やループは比較的近いアドレスへの分岐するので,絶対アドレスを指定するよりもPCからの相対アドレスで十分.
レジスタ間接方式
データ用アドレッシング・モードの種類?
レジスタ
ベース+オフセット(ディスプレースメント)
- ベースレジスタの内容に,オフセット値を加えた値を有効アドレスとする.
即値
- 即値を直接オペランドに指定
暗黙
- 昔はアキュムレータという演算対象となるたった一つのレジスタに暗黙にアクセスしていた.
RV32E(16ビット圧縮命令)を例に
ロードストア命令
CISC系
RISC系
メモリとHexファイルの話
FPGAを触っていると,バイナリをHexファイルに変換して読み込みたいときがある.
(例えば,RISC-Vを自作しているときとか..FPGA内蔵メモリにHexファイルを埋め込みたい..
今回は32bit命令を扱うメモリを例に考える.
Hexファイルの形式
1. 1行4バイト
1行4バイトで作成したHexファイルをメモリに埋め込んだとき,このメモリにはどうアクセスする?
32bit命令の例: 命令1 0 00912a23 --------------------------------------- 命令2 1 02010413 -------------------------------------- 命令3 2 fea42623 -------------------------------------- 3 fec42703 4 00100793 5 00e7c663 . .
PCは基本的には4ずつカウントアップする.(ちなみにPCは現在実行している命令のメモリアドレスが格納されている.)
このPCに合わせてアクセスすると3行分の命令を毎回飛ばしてしまう.それは困る.
実装1
- PCに格納されているアドレスの下位2ビットを無視することで,本来 0 -> 4 -> 8 -> 12 ... となるPCを 0 -> 1 -> 2 -> 3 ... と解釈できる.これでメモリ内の命令を読み飛ばすことはなくなる.よし.
inst <= mem[pc[ADDR_SIZE:2]];
2. 1行1バイト
1行1バイトで作成したHexファイルをメモリに埋め込んだとき,このメモリにはどうアクセスする?
32bit命令の例: 命令1 0 00 1 91 2 2a 3 23 --------------------------- 命令2 4 02 5 01 6 04 7 13 . .
PCは現在実行している命令のメモリアドレスの先頭を指している.0番地を指しているときは「0番地, 1番地, 2番地, 3番地」で32bit命令を作りたい.
実装2
PCからのオフセットを用いて32bit命令を作成する.
リトルエンディアンの場合, メモリアドレスの先頭は「LSB」であることに注意.つまり3番地から順番にビット連接する.
リトルエンディアンの場合: inst <= { mem[pc+3], mem[pc+2], mem[pc+1], mem[pc] };