🏩

HDL書けないのでChatGPT使ってパターン表示回路を作る

2023/03/27に公開

XilinxのFPGAでパターン表示回路を作る

はじめに

FPGAを勉強するにあたり「FPGAプログラミング大全 第2版」がとてもオススメらしく、
購入したのでやってみる
https://amzn.to/40isQyG

HDL経験:Verilog入門書を一日読んだだけ 基本の文法のみ
本投稿では本書2-3,「PC用ディスプレイにパターンを表示する」を実施したメモを書きます

やりたいこと

パターン表示回路をFPGAで合成し、HDMIラインから画像を表示させる

環境

FPGA : Xilinx Zybo Z7-20
OS : WSL2 Ubuntu20.04
開発環境 : Vivado ML edition 2022.1 Linux版

仕様

  • FPGAボードのHDMI端子から出力

  • 白・黒を含めた8色を表示

  • VGA(640*480)

  • 動作タイミング

Pix CLK H CLK V VLK
25.175MHz 31.469kHz 59.94kHz
Item Dot
H Period 800
H Front Poach 16
H Width 96
H Back Poach 48
V Period 525
V Front Poach 10
V Width 2
V Back Poach 33

パターン表示回路

下記ファイルで構成される
コードについては著作権が怪しいので載せません 下記URLに置いてあります
https://www.shuwasystem.co.jp/support/7980html/6326.html

  • vga_param.vh
  • syncgen.v
  • pattern.v
  • pckgen.v
  • pattern_hdmi.v

上記のようなプロジェクト構成にしたらそのままGenerate Bitstream

実行結果

FPGAによって出力されたHDMIの画面をキャプチャするのは面倒なのでRun Simulation -> Behavioral Simulationを実施
Simulation時に表示画像をfwriteで書き込むようなテストベンチをVerilogで書いてSimulation Sourcesに追加しておく(本書3章)

一部抜粋

initial begin
                RST = 0;
    fd = $fopen("imagedata.raw", "wb");
    #(STEP*600) RST = 1;
    #(STEP*20)  RST = 0;
    #(STEP*CLKNUM);
    $fclose(fd);
    $stop;
end

always @(posedge PCK) begin
    if ( VGA_DE ) begin
        $fwrite(fd, "%c", VGA_R);
        $fwrite(fd, "%c", VGA_G);
        $fwrite(fd, "%c", VGA_B);
    end
end

SIMULATION -> Run ALLを実行して波形を表示

Data EnableがHになってからR/G/Bの値が変化する(Analog表示のためやや分かりづらい笑)
画像だと見切れているが、1V = 1/59.94 s = 16ms程度になっている

書き込まれたRGBデータはrawファイルとしてプロジェクト直下のsimに生成される


意図通りの表示がされる事を確認!
左上から
(R,G,B)= White(1,1,1) -> Yellow(1,1,0) -> Purple(1,0,1) ->...Black(0,0,0)となっている

パターン表示回路の拡張/階調を付けてグラデーション表示する

本記事で一番やりたかった部分。上記までのコードを参考に、下記要件を満たす画像が出力されるようにpattern.vを変更する。

要件

  • 階調を16段階で変化させたパターンを表示する回路
  • 各色4bitの階調表現とする
  • 左右方向は10分割して64dotのブロックを形成し、縦方向は4分割して上から白、赤、緑、青とする
  • 各ブロックの横方向は4dot事に変化させる

表示させたい画像

始めにゴールを提示する。最終的にこれを表示させるようなpattern.vを作成する

という訳で、ChatGPTと相談して作成(HDL書けない)

module pattern(
    input               CLK,
    input               RST,
    output  reg [7:0]   VGA_R, VGA_G, VGA_B,
    output              VGA_HS, VGA_VS,
    output  reg         VGA_DE,
    output              PCK
);

`include "vga_param.vh"

localparam HSIZE = 10'd80;
localparam VSIZE = 10'd120;
localparam HBLK_SIZE = 10'd64;
localparam VBLK_SIZE = 10'd480 / 4;

wire    [9:0]   HCNT, VCNT;

syncgen syncgen(
    .CLK    (CLK),
    .RST    (RST),
    .PCK    (PCK),
    .VGA_HS (VGA_HS),
    .VGA_VS (VGA_VS),
    .HCNT   (HCNT),
    .VCNT   (VCNT)
);

wire [9:0] HBLANK = HFRONT + HWIDTH + HBACK;
wire [9:0] VBLANK = VFRONT + VWIDTH + VBACK;

wire disp_enable = (VBLANK <= VCNT)
                && (HBLANK-10'd1 <= HCNT) && (HCNT < HPERIOD-10'd1);

wire [3:0] gradient_index = ((HCNT - HBLANK) % HBLK_SIZE) / 10'd4;
wire [1:0] block_index = (HCNT - HBLANK) / HBLK_SIZE;

reg [2:0] color;
always @( posedge PCK ) begin
    case (VCNT / VBLK_SIZE)
        2'd0: color <= 3'b111; // 白
        2'd1: color <= 3'b100; // 赤
        2'd2: color <= 3'b010; // 緑
        2'd3: color <= 3'b001; // 青
        default: color <= 3'b000;
    endcase
end

reg [3:0] color_level;
always @( posedge PCK ) begin
    if (block_index[0] == 1'b0) begin
        color_level <= gradient_index;
    end else begin
        color_level <= 4'd15 - gradient_index;
    end
end

wire [7:0] VGA_R_temp = color[2] ? color_level * 8'h11 : 8'h00;
wire [7:0] VGA_G_temp = color[1] ? color_level * 8'h11 : 8'h00;
wire [7:0] VGA_B_temp = color[0] ? color_level * 8'h11 : 8'h00;

always @( posedge PCK ) begin
    if ( RST )
        {VGA_R, VGA_G, VGA_B} <= 24'h0;
    else if ( disp_enable )
        {VGA_R, VGA_G, VGA_B} <= {VGA_R_temp, VGA_G_temp, VGA_B_temp};
    else
        {VGA_R, VGA_G, VGA_B} <= 24'h0;
end

always @( posedge PCK ) begin
    if ( RST )
        VGA_DE <= 1'b0;
    else
        VGA_DE <= disp_enable;
end

endmodule

pattern.vを変更してGenerate Bitstream -> Behavioral Simulationでrawdataを書き込み、先ほど同様に内容を確認

波形確認


VGA_DE(DataEnable) H後のRGBデータの波形が変化している

表示画像


表示したい画像と微妙に違っている。(ほとんどChatGPTに任せきりだったので仕方なし)
問題点は下記

  • グラデーションの方向が64dotで反転してしまっている
  • 画面下10%程度が真っ暗。恐らくDisplayのV Blank期間を考慮できていない
    再びChatGPTと相談

    修正したコードを記述
module pattern(
    input               CLK,
    input               RST,
    output  reg [7:0]   VGA_R, VGA_G, VGA_B,
    output              VGA_HS, VGA_VS,
    output  reg         VGA_DE,
    output              PCK
);

`include "vga_param.vh"

localparam HSIZE = 10'd80;
localparam VSIZE = 10'd120;
localparam HBLK_SIZE = 10'd64;
localparam VBLK_SIZE = (10'd480 - 10'd45 )/ 4;

wire    [9:0]   HCNT, VCNT;

syncgen syncgen(
    .CLK    (CLK),
    .RST    (RST),
    .PCK    (PCK),
    .VGA_HS (VGA_HS),
    .VGA_VS (VGA_VS),
    .HCNT   (HCNT),
    .VCNT   (VCNT)
);

wire [9:0] HBLANK = HFRONT + HWIDTH + HBACK;
wire [9:0] VBLANK = VFRONT + VWIDTH + VBACK;

wire disp_enable = (VBLANK <= VCNT)
                && (HBLANK-10'd1 <= HCNT) && (HCNT < HPERIOD-10'd1);

wire [3:0] gradient_index = ((HCNT - HBLANK) % HBLK_SIZE) / 10'd4;
wire [1:0] block_index = (HCNT - HBLANK) / HBLK_SIZE;

reg [2:0] color;
always @( posedge PCK ) begin
    case ((VCNT - VBLANK + 10'd1) / VSIZE)
        2'd0: color <= 3'b111; // 白
        2'd1: color <= 3'b100; // 赤
        2'd2: color <= 3'b010; // 緑
        2'd3: color <= 3'b001; // 青
        default: color <= 3'b000;
    endcase
end

reg [3:0] color_level;
always @( posedge PCK ) begin
    color_level <= gradient_index;
end


wire [7:0] VGA_R_temp = color[2] ? color_level * 8'h11 : 8'h00;
wire [7:0] VGA_G_temp = color[1] ? color_level * 8'h11 : 8'h00;
wire [7:0] VGA_B_temp = color[0] ? color_level * 8'h11 : 8'h00;

always @( posedge PCK ) begin
    if ( RST )
        {VGA_R, VGA_G, VGA_B} <= 24'h0;
    else if ( disp_enable )
        {VGA_R, VGA_G, VGA_B} <= {VGA_R_temp, VGA_G_temp, VGA_B_temp};
    else
        {VGA_R, VGA_G, VGA_B} <= 24'h0;
end

always @( posedge PCK ) begin
    if ( RST )
        VGA_DE <= 1'b0;
    else
        VGA_DE <= disp_enable;
end

endmodule

表示画像


だいぶ完璧に近いが、一番左のグラデーションが(R,G,B)=(0,0,0)ではなく(255,255,255)になっていそう。恐らくグラデーションの演算がズレていると思われる

ここまで来ればグラデーションの開始位置のズレを修正するだけ!
+4でオフセットをかけて画面一番左の余剰演算結果が1増加するよう変更

wire [3:0] gradient_index = ((HCNT - HBLANK +10'd4) % HBLK_SIZE) / 10'd4;


完成!!!!!

おわりに

  • HDL書けなくても要件定義をキチンとすればChatGPTとの組み合わせで何とかなる
  • 最後に4ライン分のグラデーションがズレたのはパターン表示回路の最終段でフリップフロップをかませるためと予想。従って水平方向は1CLK前に値を用意する必要があった。

Discussion