TPのSystemVerilogでシリアル通信入力

# はじめに

[前回の記事(SystemVerilogをtwo-process methodで書く)](https://qiita.com/ryo_i6/items/cf064e816bfdb7babb81)では
two process method(TP)の概要を説明しました。

前回の基本概念の説明がメインで単純なカウンタを例にしていたので
今回はもう少し複雑な処理をTPで書いてみます。

# DFのコード

今回は元となるコードとして次の記事のものを使わせていただきました。
[Qiita - Verilogでシリアル通信入力](https://qiita.com/cyebu1103/items/b72eed2e0f50b843ae21)

SystemVerilogで書き直したものが次のコードです。
定数宣言、三項演算子への置き換えなど一部変更していますが基本は同じです。
コードの読みやすさを比べるためにコメントはあえて削除しています。

```verilog:interface
interface uart_interface;
    logic [7:0] data;
    logic       enable;
    modport receiver(
        output data, enable
    );
endinterface
```

```verilog:UART_RX.sv(DF)
module UART_RX #(
    parameter DIV_WID = 8,
    parameter DIV_CNT = 8'hAE
) (
    input logic             i_rst_n,
    input logic             i_clk,
    input logic             i_UART_RX,
    uart_interface.receiver uart_if
);

    localparam START_BIT = 1'b0;
    localparam STOP_BIT = 1'b1;

    logic[2:0]         rx;
    logic              start;
    logic              fin;
    logic              busy;
    logic[DIV_WID-1:0] div;
    logic[4:0]         cnt;
    logic              dt_get;
    logic[9:0]         sp_ff;
    logic              chk_trg;
    logic[7:0]         dat;
    logic              ena;

    always_ff @(posedge i_clk, negedge i_rst_n) begin
        if (~i_rst_n) begin
            rx <= '1;
        end else begin
            rx <= {rx[1:0], i_UART_RX};
        end
    end

    assign start = (rx[2:1] == 2'b10) & (busy == 1'b0);

    assign fin = (cnt == 5'd9) & (dt_get == 1'b1);

    always_ff @(posedge i_clk, negedge i_rst_n) begin
        if (~i_rst_n)
            busy <= 1'b0;
        else if (start)
            busy <= 1'b1;
        else if (fin)
            busy <= 1'b0;
    end

    always_ff @(posedge i_clk, negedge i_rst_n) begin
        if (~i_rst_n) begin
            div <= '0;
        end else if (start) begin
            div <= {1'b0, DIV_CNT[DIV_WID-1:1]};
        end else if (busy) begin
            div <= (div == 8'd0) ? DIV_CNT : div <= div-1;
        end else begin
            div <= '0;
        end
    end

    always_ff @(posedge i_clk, negedge i_rst_n) begin
        if (~i_rst_n) begin
            cnt <= '0;
        end else if (start) begin
            cnt <= '0;
        end else if (dt_get) begin
            cnt <= cnt+1;
        end
    end

    assign dt_get = (busy) & (div == 8'd0);

    always_ff @(posedge i_clk, negedge i_rst_n) begin
        if (~i_rst_n) begin
            sp_ff <= '1;
        end else if (dt_get) begin
            sp_ff <= {rx[2], sp_ff[9:1]};
        end
    end

    always_ff @(posedge i_clk, negedge i_rst_n) begin
        if (~i_rst_n) begin
            chk_trg <= 1'b0;
        end else begin
            chk_trg <= fin;
        end
    end

    always_ff @(posedge i_clk, negedge i_rst_n) begin
        if (~i_rst_n) begin
            dat <= 8'h00;
            ena <= 1'b0;
        end else if ((chk_trg) & (sp_ff[0] == START_BIT) & (sp_ff[9] == STOP_BIT)) begin
            dat <= sp_ff[8:1];
            ena <= 1'b1;
        end else begin
            dat <= dat;
            ena <= 1'b0;
        end
    end

    assign uart_if.dat = dat;
    assign uart_if.ena = ena;

endmodule
```

以下は全体のブロック図とタイミングチャートです。
読み出し制御のブロックが少し複雑になっており
コード上でも少し読みにくくなっていることがわかります。

![block-diagram.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/313464/b18c6483-f53d-e5b6-9f09-7acff5e1702a.jpeg)

![timing-chart.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/313464/00e092d2-7e2e-b8e5-37ed-cc7ca6fba558.jpeg)

# TPへの書き換え

## 処理を変えずにTPにする

ひとまず、処理を変えずにDFをTPの形式にしたものが次のコードです。

```verilog:UART_RX.sv(DF->TP)

module UART_RX #(
    parameter DIV_WID = 8,
    parameter DIV_CNT = 8'hAE
) (
    input logic i_rst_n,
    input logic i_clk,
    input logic i_UART_RX,
    uart_interface.receiver uart_if
);

    localparam START_BIT = 1'b0;
    localparam STOP_BIT = 1'b1;

    logic start;
    logic fin;
    logic dt_get;

    typedef struct packed{
        logic                        busy;
        logic [2:0]                  rx;
        logic unsigned [DIV_WID-1:0] div;
        logic unsigned [3:0]         cnt;
        logic [9:0]                  sp_ff;
        logic                        chk_trg;
        logic [7:0]                  dat;
        logic                        ena;
    } reg_type;

    localparam reg_type INIT = '{
        busy : 1'b0,
        rx : '1,
        div : '0,
        cnt : '0,
        sp_ff : '1,
        chk_trg : 1'b0,
        dat : '0,
        ena : 1'b0
    };

    reg_type rin, r = INIT;

    always_comb begin

        rin = r;

        rin.rx = {r.rx[1:0], i_UART_RX};

        start = (r.rx[2:1] == 2'b10) & (~r.busy);
        dt_get = (r.busy) & (r.div == 8'd0);
        fin = (r.cnt == 4'd9) & (dt_get);

        if (start) begin
            rin.busy = 1'b1;
        end else if (fin) begin
            rin.busy = 1'b0;
        end

        if (start) begin
            rin.div = {1'b0, DIV_CNT[DIV_WID-1:1]};
        end else if (r.busy) begin
            rin.div = (r.div == 8'd0) ? DIV_CNT : r.div - 1;
        end else begin
            rin.div = 8'd0;
        end

        if (start) begin
            rin.cnt = 4'd0;
        end else if (dt_get) begin
            rin.cnt = r.cnt + 1;
        end

        if (dt_get) begin
            rin.sp_ff = {r.rx[2], r.sp_ff[9:1]};
        end

        rin.chk_trg = fin;

        rin.ena = 1'b0;
        if ((r.chk_trg) & (r.sp_ff[0] == START_BIT) & (r.sp_ff[9] == STOP_BIT)) begin
            rin.dat = r.sp_ff[8:1];
            rin.ena = 1'b1;
        end

        uart_if.data = r.dat;
        uart_if.enable = r.ena;

    end

    always_ff @(posedge i_clk, negedge i_rst_n) begin
        if (~i_rst_n) begin
            r <= INIT;
        end else begin
            r <= rin;
        end
    end

endmodule

```

この時点で読みやすくなった点は次の通りです。

* regが明示される
    * レジスタは構造体になる
* リセット処理が分離される
    * リセット処理をいちいち読み飛ばさなくてよくなる
    * レジスタのリセット漏れがないかどうかは`INIT`を見るだけでよくなる
      * 今回はリセット時に全てのレジスタが初期化されるため一括代入できる
      * リセット時も保存しておきたいレジスタがある場合はレジスタ毎にリセットを書く必要がある
* 処理を上から読むことができる
    * 元のコードでは`fin`の条件に`dt_get`が含まれるが、`dt_get`の処理は終盤のほうに書かれているため、探す必要がある
    * TPでは処理が上から順番に行われるため、`dt_get`の処理を`fin`の処理より先に書くように強制される

## 関連する処理をまとめる

上記のコードにおいて`busy`、`div`、`cnt`はそれぞれ読み出し制御を行うためのレジスタですが
それぞれの関係が見た目ではわかりません。

それぞれのif文を1つにまとめてみます。
このとき、if文中に`start`、`dt_get`、`fin`があると読みにくいので合わせて展開します。

整理した結果が次のコードです。

```verilog
        if (~r.busy) begin
            rin.div = 8'd0;
            if (r.rx[2:1] == 2'b10) begin
                rin.div = {1'b0, DIV_CNT[DIV_WID-1:1]};
                rin.cnt = 4'd0;
                rin.busy = 1'b1;
            end
        end else begin
            rin.div = (r.div == 8'd0) ? DIV_CNT : r.div - 1;
            if (r.div == 8'd0) begin
                rin.cnt = r.cnt + 1;
                if (r.cnt == 4'd9) begin
                    rin.busy = 1'b0;
                end
            end
        end
```

ネストは深くなってしまいますが、
if文を読むだけでよくなるので信号を探し回る必要がなくなります。

この時点でコードからは以下のことがすぐ読み取れます。

* busy状態でないとき
    * `div`だけ固定値が入れられている
        * `div`に固定値を入れる必要はないかも？
        * `cnt`に固定値を入れないのはバグかも？
    * `rx`の立下りで`div`、`cnt`の初期値を設定してbusy状態になる
* busy状態のとき
    * `div`は常にカウントダウンしている
    * `cnt`は`div`が0のときだけカウントアップする
    * `div`と`cnt`が条件を満たしたらbusy状態を終了する

ここで改めて整理したコードと次の`start`、`dt_get`、`fin`の式を見ると
共通の条件が多数あることがわかります。

```verilog
    start = (r.rx[2:1] == 2'b10) & (~r.busy);
    dt_get = (r.busy) & (r.div == 8'd0);
    fin = (r.cnt == 4'd9) & (dt_get);
```

文脈的にも`start`、`dt_get`、`fin`は`busy`、`div`、`cnt`から生成される状態を
読みやすくするための中間変数として使われているのでまとめてしまってもよさそうです。

まとめて整理した結果が次のコードです。

```verilog
        start = 1'b0;
        dt_get = 1'b0;
        fin = 1'b0;
        if (~r.busy) begin
            rin.div = 8'd0;
            if (r.rx[2:1] == 2'b10) begin
                start = 1'b1;
                rin.div = {1'b0, DIV_CNT[DIV_WID-1:1]};
                rin.cnt = 4'd0;
                rin.busy = 1'b1;
            end
        end else begin
            rin.div = (r.div == 8'd0) ? DIV_CNT : r.div - 1;
            if (r.div == 8'd0) begin
                dt_get = 1'b1;
                rin.cnt = r.cnt + 1;
                if (r.cnt == 4'd9) begin
                    fin = 1'b1;
                    rin.busy = 1'b0;
                end
            end
        end
```

このコードからは以下のことがすぐ読み取れます。

* `start`、`dt_get`、`fin`はラッチしない
* busyでない状態のとき
    * `rx`が立ち下がると開始(`start`)
* busyでない状態のとき
    * `div`が0になるたびにデータをラッチする(`dt_get`)
    * 必要なビット数をラッチしたら終了(`fin`)

以上の結果をまとめると全体のコードは次のようになります。
`start`はどの処理でも使われていないので削除しました。
処理の流れと意味のまとまりがDFよりも見やすくなっていると思います。

```verilog:UART_RX.sv(TP)
module UART_RX #(
    parameter DIV_WID = 8,
    parameter DIV_CNT = 8'hAE
) (
    input logic i_rst_n,
    input logic i_clk,
    input logic i_UART_RX,
    uart_interface.receiver uart_if
);

    localparam START_BIT = 1'b0;
    localparam STOP_BIT = 1'b1;

    logic fin;
    logic dt_get;

    typedef struct packed{
        logic                        busy;
        logic [2:0]                  rx;
        logic unsigned [DIV_WID-1:0] div;
        logic unsigned [3:0]         cnt;
        logic [9:0]                  sp_ff;
        logic                        chk_trg;
        logic [7:0]                  dat;
        logic                        ena;
    } reg_type;

    localparam reg_type INIT = '{
        busy : 1'b0,
        rx : '1,
        div : '0,
        cnt : '0,
        sp_ff : '1,
        chk_trg : 1'b0,
        dat : '0,
        ena : 1'b0
    };

    reg_type rin, r = INIT;

    always_comb begin

        rin = r;

        rin.rx = {r.rx[1:0], i_UART_RX};

        dt_get = 1'b0;
        fin = 1'b0;
        if (~r.busy) begin
            rin.div = 8'd0;
            if (r.rx[2:1] == 2'b10) begin
                rin.div = {1'b0, DIV_CNT[DIV_WID-1:1]};
                rin.cnt = 4'd0;
                rin.busy = 1'b1;
            end
        end else begin
            rin.div = (r.div == 8'd0) ? DIV_CNT : r.div - 1;
            if (r.div == 8'd0) begin
                dt_get = 1'b1;
                rin.cnt = r.cnt + 1;
                if (r.cnt == 4'd9) begin
                    fin = 1'b1;
                    rin.busy = 1'b0;
                end
            end
        end

        if (dt_get) begin
            rin.sp_ff = {r.rx[2], r.sp_ff[9:1]};
        end

        rin.chk_trg = fin;

        rin.ena = 1'b0;
        if ((r.chk_trg) & (r.sp_ff[0] == START_BIT) & (r.sp_ff[9] == STOP_BIT)) begin
            rin.dat = r.sp_ff[8:1];
            rin.ena = 1'b1;
        end

        uart_if.data = r.dat;
        uart_if.enable = r.ena;

    end

    always_ff @(posedge i_clk, negedge i_rst_n) begin
        if (~i_rst_n) begin
            r <= INIT;
        end else begin
            r <= rin;
        end
    end

endmodule
```

実際にはコメントが入るのでもう少し読みやすくなります。

```verilog:UART_RX.sv(TP)
//-------------------------------------------------------------------
// uart receiver
//   * clock             : 20[MHz]                   => 50[ns]
//   * baudrate          : 115200[bps]               => 8.68[us]
//   * count             : clock[Hz] / baudrate[bps] => 174(0xAE)[/bit]
//   * parity            : none
//   * stop bit          : 1 bit
//   * metastable filter : 2-FF
//   * noise filter      : none (connect to buffer IC only)
//-------------------------------------------------------------------

interface uart_interface;

    logic [7:0] data;
    logic       enable;

    modport receiver(
        output data, enable
    )

endinterface

module UART_RX #(
    parameter DIV_WID = 8,    // frequency division counter bits
    parameter DIV_CNT = 8'hAE // sampling period (clocks per sampling)
) (
    input logic i_rst_n,
    input logic i_clk,
    input logic i_UART_RX,
    uart_interface.receiver uart_if
);

    localparam START_BIT = 1'b0;
    localparam STOP_BIT = 1'b1;

    // wire
    logic fin;    // finish pulse
    logic dt_get; // data latch trigger

    // reg
    typedef struct packed{
        logic                        busy;    // busy
        logic [2:0]                  rx;      // edge detection([2:1]) + metastable filter([1:0])
        logic unsigned [DIV_WID-1:0] div;     // frequency division counter
        logic unsigned [3:0]         cnt;     // received bit count
        logic [9:0]                  sp_ff;   // serial to parallel ff
        logic                        chk_trg; // check trigger
        logic [7:0]                  dat;     // received data
        logic                        ena;     // received data enable
    } reg_type;

    localparam reg_type INIT = '{
        busy : 1'b0,
        rx : '1,
        div : '0,
        cnt : '0,
        sp_ff : '1,
        chk_trg : 1'b0,
        dat : '0,
        ena : 1'b0
    };

    reg_type rin, r = INIT;

    always_comb begin

        // default assignment
        rin = r;

        // metastable filter + edge detection
        rin.rx = {r.rx[1:0], i_UART_RX};

        // timing control
        dt_get = 1'b0;
        fin = 1'b0;
        if (~r.busy) begin
            rin.div = 8'd0;
            if (r.rx[2:1] == 2'b10) begin
                rin.div = {1'b0, DIV_CNT[DIV_WID-1:1]}; // shift 1/2 period
                rin.cnt = 4'd0; // counter reset
                rin.busy = 1'b1;
            end
        end else begin
            rin.div = (r.div == 8'd0) ? DIV_CNT : r.div - 1; // frequency division counter
            if (r.div == 8'd0) begin
                dt_get = 1'b1; // data latch trigger
                rin.cnt = r.cnt + 1; // bit counter
                if (r.cnt == 4'd9) begin
                    fin = 1'b1; // finish pulse
                    rin.busy = 1'b0;
                end
            end
        end

        // serial to parallel (LSB first)
        if (dt_get) begin
            rin.sp_ff = {r.rx[2], r.sp_ff[9:1]};
        end

        // gerate check trigger
        rin.chk_trg = fin;

        // check data
        rin.ena = 1'b0;
        if ((r.chk_trg) & (r.sp_ff[0] == START_BIT) & (r.sp_ff[9] == STOP_BIT)) begin
            rin.dat = r.sp_ff[8:1];
            rin.ena = 1'b1;
        end

        // output
        uart_if.data = r.dat;
        uart_if.enable = r.ena;

    end

    always_ff @(posedge i_clk, negedge i_rst_n) begin
        if (~i_rst_n) begin
            r <= INIT;
        end else begin
            r <= rin;
        end
    end

endmodule

```

# まとめ

TPで書くことでコードを上から順に読むことができ
DFよりも処理の流れが追いやすくなります。

次の方法を使えば、ここからさらに読みやすくすることもできるでしょう。

* `busy`のかわりに`typedef enum {IDLE, RECEIVE} state_type`のようなユーザ定義型を使う。
* 特定の処理をSubprogramにまとめる。

一方で次のような問題点もあります。

1. 合成結果の問題

    TPでは1つのprocessで処理を記述するため、論理合成の結果が合成ツールに依存しやすくなります。
    タイミングや回路規模がシビアな設計には向いていないかもしれません。

    普段は~100MHzクロック程度までしか設計していませんが、
    その範囲では特に困ったことはありません。

2. 制約の問題

    今回作成したコードではメタステーブル対策の2段FFがありますが、
    1段目と2段目のFF間の配線は短いほうが望ましいです。
    このような制約を入れる方法についてはまだ把握していません。

    現状、制約が必要なものはDFで書いています。


# 参考

* [Qiita - SystemVerilogをtwo-process methodで書く](https://qiita.com/ryo_i6/items/cf064e816bfdb7babb81) 前回記事
* [Qiita - Verilogでシリアル通信入力](https://qiita.com/cyebu1103/items/b72eed2e0f50b843ae21)