FPGAボードで遊ぼう！- UART でテキスト通信 -

# はじめに

　UART (Universal Asynchronous Receiver Transmitter) はシリアル通信の方式の一つです[1][2][3]．FPGA から PC にログを送ったり[3]，デバイスにデータを送ったりすることができます[4]．UART はシリアル通信の方式としては単純なものであるため，様々なデバイスで使われています．
　この記事では FPGA ボードで UART を使う方法を簡潔に解説します．UART が初めての方でも理解できるように，まず UART の仕組みについて説明します．そして FPGA 上に UART で通信するための回路を実装し，PC と FPGA ボード間でテキスト通信する方法を紹介します．
　この記事を通して，UART を使って FPGA ボードで遊んでみましょう！

# 使用する FPGA ボード

　今回使用する FPGA ボードは Basys 3 [5] です．Basys3 は Digilent 社が販売している FPGA ボードです．FPGA そのものはリソース量が少ないものの，ペリフェラルが沢山備わっています．そしてなにより AMD 社のチップが乗ったボードの中でもかなり格安なので，ディジタル回路の入門に良さそうです．

![Basys 3 ボード](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/3b306611-4e77-7dfc-d892-4d906d082bba.jpeg)

# UART の仕組み

## ハードウェアの構成

　Basys 3 は USB-UART Bridge を搭載しています[6]．FPGA は，この USB-UART Bridge を介して，外部のデバイスと UART の通信を行います．Basys 3 と PC を USB ケーブルで接続することで，PC と FPGA 間で様々なデータをやりとりすることができます．

![Basys 3 と外部との接続](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/594a68b0-560a-a379-c352-f347355719e1.png)

## 通信プロトコル

　UART は調歩同期式のプロトコルで通信します[2]．このプロトコルは以下の4種類のビットでデータを通信します．各ビットはスタートビットから順番に送信されます．
　以降，この記事ではパリティビットなし（0ビット），ストップビット1ビットとします．
　例えばデータとして 0xA5 (=0b10100101) を送信する場合，送信するビット列は，スタートビットとストップビットを含めた，"0101001011" です（LSB から順に送信するため，データビットの順番を反転していることに注意）．

| ビットの種類   | 一般的なサイズ（ビット）|    電圧レベル    | 備考                   |
|:---------------|:-----------------------:|:----------------:|:-----------------------|
| スタートビット |            1            |       ロー       |                        |
| データビット   |            8            | 1/0 => ハイ/ロー | LSB から順に送信される |
| パリティビット |       0 または 1        |        -         |                        |
| ストップビット |       1 または 2        |       ハイ       |                        |

　これらのビットの一般的な通信速度[^1]は 9600 や 115200 bps などです．この場合における各ビットの送信周期はそれぞれ約 10416ns と約 868ns です．通信速度は PC 環境やデバイスによって扱えるものとそうでないものがありますので，事前に調べておきましょう．
　以降，この記事では通信速度を 115200 bps とします．
　先ほど挙げた 0xA5 の送信では，以下のタイミングで信号を駆動します．最初の0がスタートビットで，続く8ビットがデータビット，最後の1がストップビットです．

![調歩同期式での信号のタイミング](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/1d1fbf62-d7c0-d8d0-1b75-df660b840a87.png)

# UART モジュールの開発

　この節では UART で通信するためのモジュールを実装します．このモジュールは，USB-UART Bridge との信号の他に，AMD の FIFO バッファ IP とのインターフェースを持たせることにします．実装に使用する言語は SystemVerilog です．

## 送信モジュール

### 設計

　送信モジュールは前節で述べたタイミングでビット列を送信します．送信モジュールの状態遷移図を以下に記載します．
　初めに STATE\_WAIT 状態の送信モジュールは，入力 FIFO バッファ の empty 信号がローになったのをトリガとして，STATE\_ASSERT\_READ\_ENABLE 状態に遷移します．ここでは，バッファにデータが入力されたのを検出し，バッファに対して re (read enable) 信号をアサートします．
　続いて STATE\_READ\_WORD 状態に遷移します．先程 re 信号をアサートしたことで，入力データをバッファから読み出せるようになりました．送信する入力データを読み出します．
　最後に STATE\_TRANSMIT\_BITS 状態に遷移します．ここではスタートビット，データビット，ストップビットを順に送信します．
　以上のビット列が送信完了したら STATE\_WAIT 状態に戻ります．再び新たな入力データを送信することが可能です．

<img width="75%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/96029861-d425-5ac2-16d2-27804e235d4e.png">

### 実装

　送信モジュールの実装を以下に記載します．このモジュールは `data` レジスタにスタートビット，データビット及びストップビットを格納し，1ビットずつ送信します．
　実装を簡単にするために，`data` レジスタの扱いを工夫しました．このレジスタはビットの送信を完了する度に内容を1ビット右シフトし，MSB を 0 埋めします．これにより，ストップビット送信時にのみレジスタの内容が `0b0000000001` となります．よって何ビット目を送信中なのかをカウントする必要がありません．

```verilog:transmitter.sv
module transmitter #(
  parameter [31:0] CLOCK_FREQUENCY = 32'd100_000_000,
  parameter [31:0] BAUD_RATE       = 32'd115200,
  parameter [31:0] WORD_WIDTH      = 32'd8
) (
  input                  clk,
  input                  rst,
  input [WORD_WIDTH-1:0] din,
  input                  empty,
  output                 re,
  output                 dout
);
  // constant
  localparam [31:0] CLOCKS_PER_BIT = CLOCK_FREQUENCY / BAUD_RATE;

  // type definition
  typedef enum logic [1:0] {
    STATE_WAIT               = 2'h0,
    STATE_ASSERT_READ_ENABLE = 2'h1,
    STATE_READ_WORD          = 2'h2,
    STATE_TRANSMIT_BITS      = 2'h3
  } state_t;

  // registers and wires
  state_t                state;
  logic [WORD_WIDTH+1:0] data;
  logic [31:0]           clock_counts;
  logic                  full_clock_counts;

  // logics
  assign re = (state == STATE_ASSERT_READ_ENABLE);
  assign dout = (state == STATE_TRANSMIT_BITS) ? data[0] : 1'b1;

  always_ff@(posedge clk)
  if (rst)
    state <= STATE_WAIT;
  else
    case (state)
      STATE_WAIT:               state <= (~empty) ? STATE_ASSERT_READ_ENABLE : state;
      STATE_ASSERT_READ_ENABLE: state <= STATE_READ_WORD;
      STATE_READ_WORD:          state <= STATE_TRANSMIT_BITS;
      STATE_TRANSMIT_BITS:      state <= (full_clock_counts && (data == {{WORD_WIDTH{1'b0}}, 1'b1})) ? STATE_WAIT : state;
    endcase

  always_ff@(posedge clk)
  if (rst)
    data <= {WORD_WIDTH+1{1'b1}};
  else
    case (state)
      STATE_READ_WORD:     data <= {1'b1, din, 1'b0};
      STATE_TRANSMIT_BITS: data <= full_clock_counts ? {1'b0, data[WORD_WIDTH+1:1]} : data;
      default:             data <= {WORD_WIDTH+1{1'b1}};
    endcase

  always_ff@(posedge clk)
  if (rst)
    clock_counts <= 32'h0;
  else
    case (state)
      STATE_TRANSMIT_BITS: clock_counts <= full_clock_counts ? 32'h0 : clock_counts + 32'h1;
      default:             clock_counts <= 32'h0;
    endcase

  assign full_clock_counts = (clock_counts == (CLOCKS_PER_BIT - 1));
endmodule
```

## 受信モジュール

### 設計

　受信モジュールも前節で述べたタイミングでビット列を受信します．受信モジュールの状態遷移図を以下に記載します．
　初めに STATE\_WAIT 状態の送信モジュールは，入力信号がローになったのをトリガとして，STATE\_RECEIVE\_BITS 状態に遷移します．ただし，遷移後にスタートビットを検出できなかった場合，STATE\_WAIT 状態に戻ります．スタートビットを検出できた場合，続くデータビットとストップビットを受信します．
　ストップビットを検出できた場合，STATE\_WRITE\_WORD 状態に遷移します．この状態では，出力 FIFO バッファに受信データを書き込みます．その後 STATE\_WAIT 状態に戻ります．
　ストップビットを検出できなかった場合，STATE\_WAIT 状態に遷移します．つまりバッファに受信データを書き込みません．
　STATE\_WAIT 状態に戻ると，再び新たな入力データを送信することが可能です．

<img width="75%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/90ae1822-2f63-aacd-66fe-c23f41d41590.png">

　「STATE\_WAIT 状態で入力信号がローになったらスタートビットを検出できているのに，どうして STATE\_RECEIVE\_BITS 状態でスタートビットを検出するのだろう」と思った方がいるかもしれません．これは実装の都合です．今回実装するモジュールは，スタートビットの立ち下がりよりも半周期遅れた時点から，毎周期信号をサンプリングします．STATE\_RECEIVE\_BITS 状態に遷移した時点でスタートビットが受信済みとする実装は，かえって複雑になりそうです．そのためこのような設計にしています．
　もちろんこの設計が最良とは思いません．ぜひ他の方の実装も調べてみて下さい．

![信号のサンプリングタイミング](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/671a25b7-1894-0fa0-4751-e7791fa89212.png)

### 実装

　受信モジュールの実装を以下に記載します．このモジュールは入力データを `data` レジスタに格納し，FIFO バッファに書き込みます．
　送信モジュールと同じく，実装を簡単にするために `data` レジスタの扱いを工夫しました．このレジスタはデータビットだけでなく，スタートビットとストップビットも格納します．また初期値は 10'b1111111111 です．よって MSB が 1 かつ LSB が 0 の場合，通信が期待通りに完了したことが分かります．MSB と LSB がそれぞれ 0 の場合，ストップビットの受信に失敗したことが分かります．その場合は受信データをバッファに書き込みません．

```verilog:receiver.sv
module receiver #(
  parameter [31:0] CLOCK_FREQUENCY = 32'd100_000_000,
  parameter [31:0] BAUD_RATE       = 32'd115200,
  parameter [31:0] WORD_WIDTH      = 32'd8
) (
  input                   clk,
  input                   rst,
  input                   din,
  output [WORD_WIDTH-1:0] dout,
  input                   full,
  output                  we
);
  // constants
  localparam [31:0] CLOCKS_PER_BIT = CLOCK_FREQUENCY / BAUD_RATE;

  // type definition
  typedef enum logic [1:0] {
    STATE_WAIT         = 2'h0,
    STATE_RECEIVE_BITS = 2'h1,
    STATE_WRITE_WORD   = 2'h2
  } state_t;

  // registers and wires
  state_t                state;
  logic [WORD_WIDTH+1:0] data;
  logic [31:0]           clock_counts;
  logic                  half_clock_counts;
  logic                  full_clock_counts;

  // logics
  assign dout = data[WORD_WIDTH:1];
  assign we = (state == STATE_WRITE_WORD);

  always_ff@(posedge clk)
  if (rst)
    state <= STATE_WAIT;
  else
    case (state)
      STATE_WAIT:         state <= (~din) ? STATE_RECEIVE_BITS : state;
      STATE_RECEIVE_BITS:
        if (full_clock_counts) begin
          if (~data[0])
            state <= (data[WORD_WIDTH+1] && (~full)) ? STATE_WRITE_WORD : STATE_WAIT;
          else
            state <= (data == {{WORD_WIDTH+2}{1'b1}}) ? STATE_WAIT : state;
        end
        else
          state <= state;
      STATE_WRITE_WORD:   state <= STATE_WAIT;
      default:            state <= state;
    endcase

  always_ff@(posedge clk)
  if (rst)
    data <= {{WORD_WIDTH+2}{1'b1}};
  else
    case (state)
      STATE_RECEIVE_BITS: data <= half_clock_counts ? {din, data[WORD_WIDTH+1:1]} : data;
      STATE_WRITE_WORD:   data <= data;
      default:            data <= {{WORD_WIDTH+2}{1'b1}};
    endcase

  always_ff@(posedge clk)
  if (rst)
    clock_counts <= 32'h0;
  else
    case (state)
      STATE_RECEIVE_BITS: clock_counts <= full_clock_counts ? 32'h0 : clock_counts + 32'h1;
      default:            clock_counts <= 32'h0;
    endcase

  assign half_clock_counts = (clock_counts == (CLOCKS_PER_BIT / 2));
  assign full_clock_counts = (clock_counts == (CLOCKS_PER_BIT - 1));
endmodule
```

# 応用

　前節の受信モジュールと送信モジュールを応用し，実際にシリアル通信をしてみます．まず簡単な応用例としてループバックを実装します．次にもう少しだけステップアップして，シーザー暗号を実装します．

## ループバック

　ループバックは受信したデータを送信します．つまり，PC からは送信したデータがそのまま帰ってきたようにみえます．

### 設計

　ループバックのブロック図を以下に記載します．入力と出力はそれぞれ UART の入出力信号です．受信モジュールは入力信号から入力データを受信し，FIFO バッファへ書き込みます．送信モジュールは FIFO バッファのデータを送信信号として出力します．

![ループバックのブロック図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/db7b4cd3-dc3b-5b1b-691f-c948b7a44da6.png)

　FIFO バッファのおかげで，受信モジュールは送信モジュールの状態を気にすることなくデータを出力できます．つまり，送信モジュールが待機中か送信中かに関わらず，受信モジュールはデータを出力できるわけです．
　もし両方のモジュールが直接接続される設計である場合，受信モジュールは送信モジュールがデータを受け取るまで待たされることになります．その間に次々と入力データが届いたとしたら，受信モジュールは入力データを取り零してしまいます．

　ただし，今回の設計では FIFO バッファが無くても前述した入力データの取り零しは発生し得ません．入出力信号の通信速度が同じであり，かつ両方のモジュールの間には FIFO バッファしかないためです．もし両方のモジュールの間に何かしらのモジュールがあり，そのモジュールの処理が一定程度の時間を要する場合は，入力データの取り零しが発生しそうです．
　本稿では今後の拡張性を考慮し，FIFO バッファを介する設計を採用しています．受信モジュールと送信モジュールの間に別のモジュールがある設計は，次のシーザー暗号化でチャレンジします．

### 実装

　ループバックの実装を以下に記載します．送信モジュールと受信モジュールは FIFO バッファを介しています．受信したデータは変えられることなく送信されます．
　なおこの FIFO バッファは AMD の IP ですので，この IP が使えるように Vivado で設定が必要です．次の節で設定を含む開発手順を説明します．

```verilog:loopback.sv
module loopback (
  input clk,
  input rst,
  input rxd,
  output txd
);
  logic [7:0] received_word;
  logic       full;
  logic       we;
  logic [7:0] word_to_transmit;
  logic       empty;
  logic       re;

  receiver rx (
    .clk(clk),
    .rst(rst),
    .din(rxd),
    .dout(received_word),
    .full(full),
    .we(we)
  );

  fifo_buffer fb (
    .clk(clk),
    .srst(rst),
    .din(received_word),
    .full(full),
    .wr_en(we),
    .dout(word_to_transmit),
    .empty(empty),
    .rd_en(re)
  );

  transmitter tx (
    .clk(clk),
    .rst(rst),
    .din(word_to_transmit),
    .empty(empty),
    .re(re),
    .dout(txd)
  );
endmodule
```

### Vivado による開発手順

　Vivado でループバック回路を開発します．まず以下の構成のディレクトリを作成してください．

```shell
$ tree
.
└── src
    ├── loopback.sv
    ├── receiver.sv
    └── transmitter.sv

1 directory, 3 files
```

　続いて Vivado を起動し，このディレクトリにループバックのプロジェクトを作成します．Create Project をクリックして下さい．

![Vivadoの開始画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/0e613e70-b3fe-fe4f-b468-f28912f48d93.png)

　Next をクリックします．

![プロジェクトの作成画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/0f73f799-bc9e-e261-78fe-7185c183ce84.png)

　Project name は loopback，Project location は先程の src ディレクトリの位置とします．

![プロジェクト名とロケーションの設定画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/419ffafb-b2b7-4a3f-aebb-d147fe56423a.png)

　Project type は RTL Project とします．"Do not specify source at this time" にチェックを入れます．"Project is an extensible Vitis platform" のチェックは外したままにしておきます．

![プロジェクトタイプの設定画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/bc831f1c-bb46-2d5b-13f8-5cbaee711b65.png)

　Default Part を選択します．"Parts | Boards" は Boards を選択し，Vendor は digilentinc.com を選択します．Basys3 の行を選択し，Next を選択します．

![デフォルトパートの選択画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/d2aa3d2e-ba38-60ef-5488-7a987d7e2c7b.png)

 以下の内容のポップアップが表示されたら，Finish をクリックして下さい．

![Project Summary](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/2268eeb0-a5c6-b032-1139-cdc438a61c25.png)

　src ディレクトリにある SystemVerilog ファイルをプロジェクトに追加します．赤枠で囲われた Add Sources をクリックします．

![Vivado のメイン画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/8d41e209-6122-8ed6-1e88-cbcd4af4b9a1.png)

　次のポップアップが表示されます．"Add or create design sources" を選択し，Next をクリックして下さい．

![ファイルの追加画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/d99cbb38-b188-b4a8-73e1-25eb44114199.png)

　Add Files をクリックし，src ディレクトリのファイルを全て選択して下さい．ポップアップ上に追加したファイル名が表示されます．

![ソースコードの追加画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/75f517f5-1914-9cf7-853c-3ee4a1eafc55.png)

　FIFO バッファ IP を使えるように設定します．赤枠で囲われた IP Catalog から，FIFO Generator を検索して下さい．

![IP Catalog の検索画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/c1009631-fd3a-55b0-b1ec-438537619b25.png)

　FIFO Generator をダブルクリックすると，ポップアップが表示されます．Component Name を fifo_buffer として下さい. Basic タブでは，Interface Type を Native，他はデフォルトの状態にして下さい．

![FIFO Generator Basic 画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/59eaffbf-266f-d2ce-dca8-9e6b7c4d6d7d.png)

　続いて Native Ports タブに切り替え，Data Port Parameters の Write Width を 8，Write Depth を 16，他はデフォルトの状態にして下さい．

![FIFO Genrator Native Ports 画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/b15e9242-6891-0c44-915b-d8210dd9f234.png)

　OK を選択した後，次のポップアップが表示されます．Generate を選択して下さい．"Out-of-context module run was launched for generating output products." というポップアップが表示されたら成功です．OK を選択してポップアップを閉じて下さい．

![Generate 確認画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/3a002f44-6547-a056-0cd4-a45eba8570f8.png)

　制約ファイルをプロジェクトに追加します．ソースコードを追加したときと同じように，Add Sources から制約ファイルを生成します．

![制約ファイルの追加](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/de608d23-de9a-195c-4303-76ff6632770a.png)

 Create File をクリックし，constrains.xdc を生成します．

![制約ファイルの生成](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/8a2c0a50-d90e-d7a9-d34b-33c3cd0b5707.png)

　Sources から Constraints を確認して下さい．生成したファイルを開けるようになりました．

![空の制約ファイル](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/de347175-2734-21e7-66d7-6d4dcb4adb00.png)

　制約ファイルを記述します．ファイルの内容は以下のとおりです．トップモジュールの入出力信号と FPGA のピンを対応させています. 

```plaintext:constraints.xdc
## Clock signal
set_property -dict { PACKAGE_PIN W5   IOSTANDARD LVCMOS33 } [get_ports clk]
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports clk]

##Buttons
set_property -dict { PACKAGE_PIN U18   IOSTANDARD LVCMOS33 } [get_ports rst]

##USB-RS232 Interface
set_property -dict { PACKAGE_PIN B18   IOSTANDARD LVCMOS33 } [get_ports rxd]
set_property -dict { PACKAGE_PIN A18   IOSTANDARD LVCMOS33 } [get_ports txd]

## Configuration options, can be used for all designs
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property CFGBVS VCCO [current_design]

## SPI configuration mode options for QSPI boot, can be used for all designs
set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design]
set_property BITSTREAM.CONFIG.CONFIGRATE 33 [current_design]
set_property CONFIG_MODE SPIx4 [current_design]
```

　最後にウィンドウ左側の `Generate Bitstream` をクリックして bitstream を生成します．以下のポップアップが表示されたら成功です．Open Hardware Manager を選択し，OK をクリックします．

![Bitstream 生成成功のポップアップ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/45f01096-bbd5-4272-935c-3ba6d41b2509.png)

　PC と Basys 3 を USB ケーブルで接続して，bitstream を書き込みましょう．Program device をクリックします．

![ハードウェアマネージャの画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/6de92008-d756-7587-cf04-d342621a6ab6.png)

　Program をクリックすると，bitstream が FPGA に書き込まれます．

![Program Device](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/3325a657-d37d-12fd-17d5-ffc21ec8290b.png)

### 動作確認

　実際に PC と Basys 3 の間でシリアル通信してみます．
　今回は Ubuntu マシンにインストールした `screen` コマンドでテキストを送受信してみます．以下はターミナルへの入力です．私の環境では実行に管理者権限が必要でした．またシリアル通信のためのデバイスファイルは `/dev/ttyUSB1` でした．デバイスファイルは環境によって名前が異なりますので注意して下さい．最後の 115200 は通信速度です．

```shell
$ sudo screen /dev/ttyUSB1 115200
```

　以下はテキスト通信の様子です．キーボードで入力したテキストがターミナルに表示されています．screen コマンドで表示されるテキストは，受信したテキストです．つまり，PC から送信したテキストが FPGA に届き，FPGA からそのテキストが PC に届いたことが分かります．

![テキスト通信の様子](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/e89e7397-e903-7bd8-ff46-105ae22a7caa.png)

## シーザー暗号化

　シーザー暗号は古典的な暗号です．アルファベットのテキストを3文字シフトして暗号文とする暗号です．例えば 'apple' は暗号化すると 'dssoh' です．実社会で使われている暗号と比較すると実用には耐えませんが，遊びで使う分には十分です．

### 設計

　シーザー暗号化のブロック図を以下に記載します．基本的な構成はループバックと同じです．異なる点は，シーザー暗号化カーネルが追加されたことと，FIFO バッファを1つ増やしたことです．
　シーザー暗号化カーネルは，FIFO バッファ0から受信データを読み出します．このデータを3文字シフトした後，FIFO バッファ1に書き込みます．

![シーザー暗号化のブロック図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/ba8c9909-57d4-2d0c-a57c-84c69e892413.png)

### 実装

　シーザー暗号化回路の実装を以下に記載します．ループバック回路の実装に，暗号化モジュールともう一つの FIFO バッファを追加しています．

```verilog:top.sv
module top (
  input clk,
  input rst,
  input rxd,
  output txd
);
  logic [7:0] received_word;
  logic       full0;
  logic       we0;
  logic [7:0] word_to_encrypt;
  logic       empty0;
  logic       re0;
  logic [7:0] encrypted_word;
  logic       full1;
  logic       we1;
  logic [7:0] word_to_transmit;
  logic       empty1;
  logic       re1;

  receiver rx (
    .clk(clk),
    .rst(rst),
    .din(rxd),
    .dout(received_word),
    .full(full0),
    .we(we0)
  );

  fifo_buffer fb0 (
    .clk(clk),
    .srst(rst),
    .din(received_word),
    .full(full0),
    .wr_en(we0),
    .dout(word_to_encrypt),
    .empty(empty0),
    .rd_en(re0)
  );

  caesar_encryption kernel (
    .clk(clk),
    .rst(rst),
    .din(word_to_encrypt),
    .empty(empty0),
    .re(re0),
    .dout(encrypted_word),
    .full(full1),
    .we(we1)
  );

  fifo_buffer fb1 (
    .clk(clk),
    .srst(rst),
    .din(encrypted_word),
    .full(full1),
    .wr_en(we1),
    .dout(word_to_transmit),
    .empty(empty1),
    .rd_en(re1)
  );

  transmitter tx (
    .clk(clk),
    .rst(rst),
    .din(word_to_transmit),
    .empty(empty1),
    .re(re1),
    .dout(txd)
  );
endmodule
```

　暗号化モジュールを以下に記載します．入力 FIFO バッファにデータがあれば読み出し，3文字シフトして出力 FIFO バッファに書き出します．なおアルファベットでない文字が入力された場合は，入力文字をそのまま出力することにしました．

```verilog:caesar_encryption.sv
module caesar_encryption (
  input              clk,
  input              rst,
  input        [7:0] din,
  input              empty,
  output             re,
  output logic [7:0] dout,
  input              full,
  output             we
);
  // type definition
  typedef enum logic [1:0] {
    STATE_WAIT               = 2'h0,
    STATE_ASSERT_READ_ENABLE = 2'h1,
    STATE_READ_WORD          = 2'h2,
    STATE_WRITE_WORD         = 2'h3
  } state_t;

  // registers and wires
  state_t     state;
  logic [7:0] data;

  // logics
  assign re = (state == STATE_ASSERT_READ_ENABLE);
  assign we = (state == STATE_WRITE_WORD);

  always_comb
  if ((8'h41 <= data) && (data <= 8'h57))      /* 'A' - 'W' */
    dout = data + 8'h3;
  else if ((8'h58 <= data) && (data <= 8'h5A)) /* 'X' - 'Z' */
    dout = data - 8'h17;
  else if ((8'h61 <= data) && (data <= 8'h77)) /* 'a' - 'w' */
    dout = data + 8'h3;
  else if ((8'h78 <= data) && (data <= 8'h7A)) /* 'x' - 'z' */
    dout = data - 8'h17;
  else                                         /* otherwise */
    dout = data;

  always_ff@(posedge clk)
  if (rst)
    state <= STATE_WAIT;
  else
    case (state)
      STATE_WAIT:               state <= (~empty) ? STATE_ASSERT_READ_ENABLE : state;
      STATE_ASSERT_READ_ENABLE: state <= STATE_READ_WORD;
      STATE_READ_WORD:          state <= STATE_WRITE_WORD;
      STATE_WRITE_WORD:         state <= STATE_WAIT;
    endcase

  always_ff@(posedge clk)
  if (rst)
    data <= " ";
  else
    case (state)
      STATE_READ_WORD: data <= din;
      default:         data <= data;
    endcase
endmodule
```

### Vivado による開発手順

　ループバック回路の開発と共通する手順については省略します．
　Vivado でシーザー暗号化回路を開発します．まず以下の構成のディレクトリを作成してください．

```shell
$ tree
.
└── src
    ├── caesar_encryption.sv
    ├── receiver.sv
    ├── top.sv
    └── transmitter.sv

1 directory, 4 files
```

　src ディレクトリにある SystemVerilog ファイルをプロジェクトに追加します．

　bitstream が作成されたら，FPGA に書き込みましょう．

### 動作確認

　PC と Basys 3 の間でシリアル通信してみます．ループバック回路と共通する手順については省略します．
　以下は暗号化の様子です．キーボードで入力したテキストが暗号化されてターミナルに表示されています．[^2]

![暗号化の様子](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271956/f6675037-8da5-bfd7-1aea-826c0207be8f.png)

# おわりに

　この記事では FPGA ボードで UART を使う方法について解説しました．
　はじめに UART の仕組みについて説明しました．Basys 3 ボードの USB-UART Bridge を介した UART 通信について述べ，UART でやりとりされる調歩同期式の信号がどのようなものであるか述べました．
　次に FPGA で UART を使うための方法を説明しました．状態遷移図と SystemVerilog コードを挙げて，送受信するための回路について述べました．
　最後に送受信モジュールを使用しテキスト通信する回路を開発しました．ループバックとシーザー暗号化の二種類の回路を作成し，送受信モジュールの応用方法を例示しました．
　この記事を読んだみなさんが，実際に UART を使って FPGA ボードで遊んでくださるのを期待しております．

　FPGA ボードでまだまだ遊びたい方は，以前の投稿もご覧下さい！

- [FPGAボードで遊ぼう！- Basys3でVGA出力 -](https://qiita.com/Kenta11/items/34555852efdf8d8f4b0c)

# 使用した開発環境

- OS: Ubuntu 22.04 LTS
- CPU: AMD Ryzen 7 3700X
- Vivado 2022.2

# 参考文献

[1]: AnalogDialogue, "UART――多様な非同期通信に対応可能なハードウェア通信プロトコル", [https://www.analog.com/jp/analog-dialogue/articles/uart-a-hardware-communication-protocol.html](https://www.analog.com/jp/analog-dialogue/articles/uart-a-hardware-communication-protocol.html).
[2]: ローム株式会社, "UART", [https://www.rohm.co.jp/electronics-basics/micon/mi_what9](https://www.rohm.co.jp/electronics-basics/micon/mi_what9).
[3]: ACRi, "シリアル通信で Hello, FPGA (1)", [https://www.acri.c.titech.ac.jp/wordpress/archives/123](https://www.acri.c.titech.ac.jp/wordpress/archives/123).
[4]: Digilent, "Basys 3 General I/O Demo", [https://digilent.com/reference/programmable-logic/basys-3/demos/gpio](https://digilent.com/reference/programmable-logic/basys-3/demos/gpio).
[5]: elchika, "Raspberry Pi の UART で MIDI 送信", [https://elchika.com/article/97a7905a-4144-4c4a-a68d-527a0827bd96/](https://elchika.com/article/97a7905a-4144-4c4a-a68d-527a0827bd96/).
[6]: AMD Xilinx, "Digilent Basys 3 Artix-7 FPGA Board," [https://japan.xilinx.com/products/boards-and-kits/1-54wqge.html](https://japan.xilinx.com/products/boards-and-kits/1-54wqge.html).
[7]: Digilent, "Basys 3 Reference Manual", [https://digilent.com/reference/programmable-logic/basys-3/reference-manual#usb-uart_bridge_serial_port](https://digilent.com/reference/programmable-logic/basys-3/reference-manual#usb-uart_bridge_serial_port).

# 注釈

[^1]: この通信速度はボーレートと呼ばれることもあります．ただし，実際は通信速度とボーレートは違う概念です．

[^2]: 先日 Twitter でシーザー暗号がトレント入りしました（https://www.inside-games.jp/article/2023/08/03/147608.html）．そのとき暗号化されたと思しき元のテキストを入力しています．まあシフト数が違うので，結果は全く違うのですが．