FPGAでLCDを制御したい

# はじめに
[木更津高専Advent Calender](https://qiita.com/advent-calendar/2020/nit_kisarazu) 12日目担当のわくとです。お久しぶりです。

4月辺りにFPGAを購入後、「CPUの創り方」のTD4を作ったあと放置していたのでマンデルブロ集合の表示を目標に、LCDの制御とRAMの制御を勉強します。
初心者ですので間違い等あったら指摘をお願いしますm(_ _)m

終わらなかったので数回に分けて記事にします。

# やること
1. LCDの制御**←今回はこれ**
2. メモリの読み書き
3. マンデルブロ集合の計算

# 使用機材について
初めてのFPGAだからと安い中華のものを買ってしまったら[公式のドキュメント](https://tang.sipeed.com/en/)が書き途中だったり独自のツールを使っていたりでとても苦労しました…

知識のある人には、安くてある程度の性能もありポートも充実している（microSD, FPC40pinソケット, FPC24pinソケット,など）のでいいのではないでしょうか？

|  使用機材 | モデル |
| -------- | -------- |
|FPGA|[Tang Primer](https://www.switch-science.com/catalog/5703/)|
|LCD|[アキバで買ったやつ](https://www.shigezone.com/?product=43_40pin_lcd)|
|IDE|[Tang Dynasty IDE](https://tang.sipeed.com/en/getting-started/installing-td-ide/)|


# LCDへ渡す信号
ディスプレイの制御は初めてだったので用語を調べるところからやりました。とりあえず以下の信号がわかれば制御できると思います。
このLCDでは色の情報をRGBそれぞれ8bitで入力します。
詳細な値はデータシートを参照します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265415/97e66083-7487-a787-0de0-f715c74a9097.png)

- **水平・垂直同期信号**
  - 画面端から描画を始めるタイミングを取るための信号
  - 水平同期信号：画面左端を描画し始める前にパルスを入力
  - 垂直同期信号：画面上側を描画し始める前にパルスを入力

- **水平・垂直同期信号幅**
  - 同期信号のパルスの幅

- **水平・垂直表示期間幅**
  - 実際に表示される信号の期間

- **フロントポーチ**
  - 表示期間の終わりから同期パルスの始まりまでの間の長さ

- **バックポーチ**
  - 同期パルスの始まりから表示期間の始まりまでの間の長さ

- **水平・垂直同期間隔**
  - 同期信号幅+バックポーチ+表示期間幅+フロントポーチ

- **DCLK**（データクロック？）
  - データ入力のタイミングを取るクロック
  - 上の信号たちはDCLK何クロック分かで表される

# 動作チェック
LCDのデータシートを見ながら実装していきます。途中、表示期間幅を間違えて下のような表示になったりもしましたが。以下のようなVerilogコードを書いて無事動作チェックができました。試しにカラーバーを表示した写真を添付しています。
### 途中遭遇したバグ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265415/b096be5f-27d0-f02c-a728-1a669259791e.png)

```verilog:LCD.v
module LCD(
input wire CLK,
output wire V_SYNC,
output wire H_SYNC,
output wire DCLK,
output wire [7:0] DR,
output wire [7:0] DG,
output wire [7:0] DB,
output wire DE,
output wire LED
);

// 水平
localparam Thdisp = 480;  // 表示期間幅
localparam Thbp = 3;      // バックポーチ
localparam Thfp = 2;      // フロントポーチ
localparam Thw = 2;       // 同期信号幅
localparam Th = Thdisp + Thbp + Thfp + Thw;   // 同期間隔
// 垂直
localparam Tvdisp = 272;
localparam Tvbp = 2;
localparam Tvfp = 2;
localparam Tvw = 2;
localparam Tv = Tvdisp + Tvbp + Tvfp + Tvw;

// 8MHzに分周
reg CLK_8MHz;
assign DCLK = CLK_8MHz;   // DCLKに8MHzを出力
reg [2:0] clk_24_8;       // 分周用レジスタ
reg [25:0]clkcnt;
always @(posedge CLK) begin
    if(clk_24_8 == 3'd3) begin
        CLK_8MHz <= ~CLK_8MHz;
        clk_24_8 <= 3'd0;
    end
    clkcnt <= clkcnt+26'd1;
    clk_24_8 <= clk_24_8 + 3'd1;
end

// H_Sync
reg [23:0] dclk_cnt;
reg hsync;    // 水平同期信号
reg vsync;    // 垂直同期信号

// V_Sync
reg [23:0] vsync_cnt;

reg [23:0] col;           // 表示色用レジスタ
reg data_Enable;
assign DB = col[7:0];     // BLUE
assign DG = col[15:8];    // GREEN
assign DR = col[23:16];   // RED
assign LED = 1'b1;
assign V_SYNC = vsync;
assign H_SYNC = hsync;
assign DE = data_Enable;

reg [6:0] color_count;
reg [2:0] bar_count;
reg pwm;

always @(posedge DCLK) begin
    // horizontal count
    if(dclk_cnt == Th-1) dclk_cnt <= 24'd0;
    else dclk_cnt <= dclk_cnt + 24'd1;
    
    // H_SYNC CONTROL
    if(dclk_cnt < Thw) begin
        hsync <= 1'b1;
    end else begin
        hsync <= 1'b0;
    end

    // 水平表示期間
    if(dclk_cnt >= (Thw+Thbp) && dclk_cnt < (Thdisp+Thw+Thbp)
       && vsync_cnt >= (Tvw+Tvbp) && vsync_cnt < (Tvdisp+Tvw+Tvbp)) begin
        // このタイミングで描画データを送る
        // カラーバーを表示
        data_Enable <= 1'b1;
        if(color_count == (Thdisp/5)-1) begin
            color_count <= 7'd0;
            bar_count <= bar_count + 3'b1;
        end else color_count <= color_count + 7'b1;

        case(bar_count)
            3'd0: col <= 24'h0000FF;
            3'd1: col <= 24'h00FF00;
            3'd2: col <= 24'hFF0000;
            3'd3: col <= 24'hFFFFFF;
            3'd4: col <= 24'h000000;
            default: bar_count <= 3'd0;
        endcase        
    end else begin
        bar_count <= 3'd0;
        color_count <= 7'd0;
    	data_Enable <= 1'b0;
    end
end

// 垂直同期信号
always @(posedge hsync) begin
    if(vsync_cnt == Tv) vsync_cnt <= 0;
    else vsync_cnt <= vsync_cnt + 24'd1;
    if(vsync_cnt < Tvw) begin
        vsync <= 1'b1;
    end else begin
        vsync <= 1'b0;
    end
end

endmodule
```
### 動作した様子
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265415/721b5226-f54d-294f-4ed4-e2ea3d3fbf86.png)
# 気になった点
- reg変数の扱いがわかってない
    - たまに2箇所で同じレジスタに代入してたりしてバグる
- 変数の宣言がごちゃごちゃする

# 次回
投稿日時は未定です。RAM/ROMを使います。クリスマスらしくサンタでも表示してみたいですね🎅