実用的な回路の設計・実装と動作確認を通じて,ハードウェア記述言語 (HDL) を使った FPGA 上のディジタル回路の設計について学ぶコースの最終回です。
前回までに、シリアル通信 (UART) による文字送信回路を設計し、論理シミュレーションと実機の両方で、1文字を正しく送信できることが確認できました。今回はこの回路をもう少しだけ拡張し、シリアル通信で Hello, FPGA を FPGA から PC に送信し、その結果を確認する……つまり、本コースの最終目標に踏み込みます。
トップモジュールの拡張
拡張トップモジュールの設計
前回作成したトップモジュールは、1文字の ‘A’ だけを送信させるものでした。これを少し拡張すると、複数の文字を続けて送信できるようになります。これによって、「Hello, FPGA」に改行文字を加えた計12文字を FPGA から続けて送信するように、トップモジュールを拡張しましょう。
回路の外見 (入出力) は前回と変わらないので、中身がどう変わっていくか見ていきます。回路の構成をブロック図で表すと上図の通りとなります。
ここでは、カウンタ (順序回路) とデコーダ (組合せ回路) が追加されています。カウンタは、これまでに出力が済んだ文字数 (以下、送信バイト数と表記します) を数えるために用意します。また設計するデコーダは、送信バイト数を入力すると、次に出力したい文字の ASCII コードを出力するものとなります。送信したい文字列を格納した ROM、とも言い換えられます。例えば、最初の文字を出力するとき (すなわち、送信バイト数が0の場合) は、’H’ に対応する 0x48 を出力します。
送信バイト数 | 文字 | ASCII コード (16進) |
---|---|---|
0 | H | 48 |
1 | e | 65 |
2 | l | 6C |
3 | l | 6C |
4 | o | 6F |
5 | , | 2C |
6 | (スペース) | 20 |
7 | F | 46 |
8 | P | 50 |
9 | G | 47 |
10 | A | 41 |
11 | (改行) | 0A |
デコーダの送信バイト数と出力したい文字の ASCII コードとの関係は上の表になります。送信バイト数 (入力) と ASCII コード (出力) とを2進数で表記すれば、これはそのままデコーダの真理値表となります。送信バイト数が12以上の場合はドントケア (出力は何でもよい) です。
トップモジュールのステートマシンを、上図のように変更します。必要な状態は前回と変わりませんが、WAIT 状態から SEND 状態に戻る遷移が追加されていること、WAIT 状態から FIN 状態への遷移の条件が変更されていることが違います。
WAIT 状態からの遷移は、いずれも文字送信回路が送信を終了した (BUSY を ‘0’ にした) ときに行われます。もしこの時に出力済みの文字数が11、つまり12文字目の送信が終了したら、FIN 状態に遷移します。そうでなければ、送信バイト数のカウンタをインクリメントし、SEND 状態に戻ります。
拡張トップモジュールの SystemVerilog 記述
以上を踏まえて拡張したトップモジュールの SystemVerilog 記述は次のとおりです。前回との変更点をハイライトしています。
module serial_fpga2 (
input logic CLK, RST,
output logic TXD);
typedef enum {
STATE_SEND,
STATE_WAIT,
STATE_FIN
} state_type;
state_type state, n_state;
logic we;
logic busy;
logic [7:0] data_in;
logic [3:0] byte_cnt, n_byte_cnt;
serial_send # (
.WAIT_DIV(868))
ser (
.CLK(CLK),
.RST(RST),
.DATA_IN(data_in),
.WE(we),
.DATA_OUT(TXD),
.BUSY(busy));
always_comb begin
case (byte_cnt)
4'd0 : data_in = 8'h48; // H
4'd1 : data_in = 8'h65; // e
4'd2 : data_in = 8'h6c; // l
4'd3 : data_in = 8'h6c; // l
4'd4 : data_in = 8'h6f; // o
4'd5 : data_in = 8'h2c; // ,
4'd6 : data_in = 8'h20; //
4'd7 : data_in = 8'h46; // F
4'd8 : data_in = 8'h50; // P
4'd9 : data_in = 8'h47; // G
4'd10: data_in = 8'h41; // A
4'd11: data_in = 8'h0a; // \n
default: data_in = 8'h00;
endcase
end
always_comb begin
n_state = state;
n_byte_cnt = byte_cnt;
we = 1'b0;
if (state == STATE_SEND) begin
n_state = STATE_WAIT;
we = 1'b1;
end else if (state == STATE_WAIT) begin
if (~ busy) begin
if (byte_cnt == 4'd11) begin
n_state = STATE_FIN;
end else begin
n_state = STATE_SEND;
n_byte_cnt = byte_cnt + 1'b1;
end
end
end
end
always_ff @ (posedge CLK) begin
if (RST) begin
state <= STATE_SEND;
byte_cnt <= 4'd0;
end else begin
state <= n_state;
byte_cnt <= n_byte_cnt;
end
end
endmodule
2021-06-08 更新: state_type の定義抜けを修正しました.
変更点は大きく分けると3つです。
- 送信バイト数のためのカウンタの宣言 (14行) と、その値の更新 (66, 69行) が追加されています。
- 前回は固定値であった文字送信回路のデータ入力ですが、今回はデコーダの出力を与えます。入力と出力とが表引きで表せる場合、case 文を使うのが便利です (26~42行)。
- ステートマシンの組合せ回路の部分は、WAIT 状態からの遷移が変更されています (53~58行)。
Hello, FPGA の確認
トップモジュールの差し替え
それでは、前回作成したプロジェクトをベースに、トップモジュールを今回作成した拡張トップモジュールに差し替えて、論理合成から FPGA への書き込みを行っていきます。
いずれの操作も、Vivado の Project Manager でモジュールの階層関係を表示している画面から行えます。ソースファイル自体を差し替える場合は、差し替えたいモジュール (ここでは前回作成したトップモジュール) を右クリックして、「Replace File」を選択し、適切なファイル (拡張トップモジュールを記述したファイル) を選択します。
モジュールの名前が同じでなければ、前回作成したトップモジュールをプロジェクトに残したまま、拡張トップモジュールを「Add Sources」で追加しても構いません。ただしこの場合、どちらを対象に論理合成するかを指示する必要があります。論理合成対象のトップモジュールは太字で表示されているので、もし別のモジュールをトップにしたい場合は、そのモジュール (ここでは拡張トップモジュール) を右クリックして、「Set as Top」を選択します。
モジュールの名前が全く同じ場合、両方のモジュールをプロジェクトに共存させることはできません。この場合は、ソースファイル自体を差し替える方法を使ってください。
出力の確認
論理合成からの作業は前回と変わりません。ただし、Tera Term や GTKTerm で出力を確認する際、デフォルトの設定だと改行文字の認識がうまくできないかもしれません。そのため、受信時の改行文字を自動認識するように設定を変更 (またはそうなっているか確認) しておきます。
Tera Term の場合は、メニューの「設定 → 端末」を開き、「改行コード → 受信」の欄を「AUTO」に設定します。
GTKTerm の場合は、メニューの「Configuration → CR LF auto」にチェックを入れておきます。
それでは、Tera Term あるいは GTKTerm を開いたまま、FPGA に作成したビットストリームファイルを書き込みましょう。無事に「Hello, FPGA」が表示されれば、本連載の目標は達成です!
What’s Next?
ここからは連載の本題から外れますが、最後に、こうして作った回路をどうすれば更に活用できるかという観点で、少しだけお話したいと思います。
今回作った文字送信回路を使いやすくする1つの方策として、何文字かを連続して受け付けるようにすることが考えられます。今回の文字送信回路は、1つの文字の送信中はビジー出力 BUSY が ‘1’ になり、次の文字を受け付けることはできません。しかし、受け付けた文字をある一定数まで記憶し、受け付けた順番で出力する回路があれば、文字送信回路と組み合わせることで、送信中にも文字を先行して受け付けられるようになります。このような回路を先入れ先出し (first-in, first-out) 型の記憶回路、縮めて FIFO とよびます。
典型的な FIFO では、データの入力と出力のほかに、以下のような入出力をもちます。
- 書き込み有効入力 (WE): データ入力を FIFO に書き込むかどうか
- 読み出し有効入力 (RE): データ出力を FIFO から取り出すかどうか
- 空かどうかの出力 (EMPTY): FIFO で何も記憶していなければ ‘1’ を出力
- 満杯かどうかの出力 (FULL): FIFO で記憶しているデータの数が上限に達していれば ‘1’ を出力
EMPTY 出力が ‘1’ の場合は、読み出せるデータがないので、RE は無視されます。同様に、FULL 出力が ‘1’ の場合は、これ以上データが書き込めないので、WE は無視されます。
今回は FIFO の中身の設計や HDL 記述は省きますが、仮に適切に FIFO モジュールが設計・記述できたとすれば、上図のように文字送信回路と組み合わせることで、文字の先行入力が可能になります。
データの書き込みは、文字送信回路ではなく FIFO に対して行われるようになります。FIFO が満杯 (FULL が ‘1’) であれば、これ以上データは受け付けられないので、BUSY 出力を ‘1’ とします。文字送信回路が文字を受け付けられる (文字送信回路の BUSY が ‘0’ の) 場合、FIFO からデータを読み出そう (RE を ‘1’) とします。もし FIFO にまだ送信待ちのデータが残っている (EMPTY が ‘0’) ならば、文字送信回路の WE を ‘1’ とし、次のデータの送信を開始します。
まとめ
本コースでは、シリアル通信を使って FPGA に「Hello, FPGA」を送信させる回路を題材に、HDL を使った FPGA 上のディジタル回路設計について学んできました。特に今回は、これまでに作成してきた文字送信回路とトップモジュールをもとに、トップモジュールを拡張し、文字列を送信できるようにした上で、その動作を確認しました。
最後に少し触れたとおり、しっかりと回路のパーツを作り込むことで、他の回路といっしょに複雑な回路を構成することが容易になりますし、別の機会にこれらの回路を使い回すこともできます。さらには、先人たちが残してきた回路を借りてくることもしばしば起こるでしょう。こうした回路のパーツは、貴重な財産 (IP: Intellectual Property) です。是非これらの財産を使いこなし、また自分でもこうした財産を築けるようになってください。
今、読者の皆さんは FPGA の世界への最初のあいさつを済ませたところです。本コースが、FPGA の世界への旅立ちの良きガイドの1つとなることができたのであれば幸いです。
愛知工業大学 藤枝直輝