FPGAを使ってShader芸で遊びます.

 使用したFPGAボードは,Tang Mega 138K ProDock(Sipeed) です.画面は,ボードのHDMIコネクタからDVIで出力します.

 まずは,フラグメント・シェーダで球(っぽいもの)を描画します.

ルート演算を嫌って,近似値で妥協したので8角形のスフィアに…(モニタ画面をカメラで撮影)

●topモジュール

・PLL(Gowin IP)

 DVI出力するために,ピクセルクロックとシリアルクロック(ピクセルクロック×5)を生成します.

・タイミング ジェネレータ -video_timing-

 1,650×720でDVI出力するための同期信号を生成します.よくある回路なので,Claude Codeが生成したものをそのまま使えます.ただし,FullHDで試した時は,解像度1920に対して60fps だとブランキングを入れて2200を使うことが多いですが,そこのカウンタが11ビットになっていて,永遠にDataEnableが立たない状態でした.

・シェーダー -shader-

 中央に,Sphere(っぽいもの)を書くシェーダーです.簡単のため今回は,R=G=B

・DVI_TX_Top(Gowin IP)

 DVI出力用のモジュールです.

Verilog
module top (
    input  wire clk,
    input  wire rst_n,

    // DVI TMDS output (HDMI connector)
    output wire tmds_clk_p,
    output wire tmds_clk_n,
    output wire [2:0] tmds_data_p,
    output wire [2:0] tmds_data_n,
    output logic[5:0] state_led
);

assign state_led[0] = rst;   // board LED
assign state_led[1] = rst_n; // board LED
assign state_led[2] = de;    // board LED

// Clock Generation
wire clk_pixel;
wire clk_tmds;
wire pll_lock;

    Gowin_PLL pll_inst(
        .clkin(clk),         //input  clkin
        .clkout0(clk_tmds),  //output clkout0
        .clkout1(clk_pixel), //output clkout1
        .lock(pll_lock),     //output lock
        .reset(~rst_n)       //input  reset
);

wire rst = ~rst_n | ~pll_lock;

wire [10:0] hcount;
wire [10:0] vcount;
wire hsync, vsync, de, field_start;

//Timing Generator
video_timing u_timing (
    .clk      (clk_pixel),
    .rst      (rst),
    .hcount   (hcount),
    .vcount   (vcount),
    .hsync    (hsync),
    .vsync    (vsync),
    .de       (de),
    .field_start(field_start)
);

// Shader Pixel Generator
wire [7:0] r_out, g_out, b_out;
wire shader_de;

shader u_shader (
    .clk (clk_pixel),
    .x   (hcount),
    .y   (vcount),
    .r    (r_out),
    .g    (g_out),
    .b    (b_out)
);

// DVI output
DVI_TX_Top your_instance_name(
	.I_rst_n(rst_n),             //input I_rst_n
	.I_serial_clk(clk_tmds),     //input I_serial_clk
	.I_rgb_clk(clk_pixel),       //input I_rgb_clk
	.I_rgb_vs(vsync),            //input I_rgb_vs
	.I_rgb_hs(hsync),            //input I_rgb_hs
	.I_rgb_de(de),               //input I_rgb_de
	.I_rgb_r(r_out),             //input [7:0] I_rgb_r
	.I_rgb_g(g_out),             //input [7:0] I_rgb_g
	.I_rgb_b(b_out),             //input [7:0] I_rgb_b
	.O_tmds_clk_p(tmds_clk_p),   //output O_tmds_clk_p
	.O_tmds_clk_n(tmds_clk_n),   //output O_tmds_clk_n
	.O_tmds_data_p(tmds_data_p), //output [2:0] O_tmds_data_p
	.O_tmds_data_n(tmds_data_n)  //output [2:0] O_tmds_data_n
);

endmodule
Expand

●シェーダー -shader-

 入力するのは,X,Y座標のみです.

Verilog
module shader (
    input wire clk,
    input wire [11:0] x,
    input wire [11:0] y,

    output reg [7:0] r,
    output reg [7:0] g,
    output reg [7:0] b
);

localparam [11:0] CAMERA_x = 1650 / 2;
localparam [11:0] CAMERA_y = 720 / 2;

// =====================
// Sphire
// =====================
logic[7:0] pixel_value;

function [11:0] length;
    input [15:0] val1;
    input [15:0] val2;

    length = ( (((val1>val2)?val1:val2) << 16'd4) + (((val1<val2)?val1:val2) << 16'd3)) >> 16'd4;
endfunction

logic[11:0] c_x, c_y; // current pixel position(x,y) from center
logic[11:0] sphere_length;

assign c_x = (x < CAMERA_x)?CAMERA_x - x: x - CAMERA_x;
assign c_y = (y < CAMERA_y)?CAMERA_y - y: y - CAMERA_y;
assign sphere_length = length(c_x,c_y);

always_ff @(posedge clk)begin

    if (sphere_length[11:0] <100)begin
        pixel_value <= 8'hff;
    end else begin
        pixel_value <= (8'hff < (sphere_length[11:0]*2-90))?8'b0:(8'hff - (sphere_length[7:0]*2-90));
    end
    r <= pixel_value;
    g <= pixel_value;
    b <= pixel_value;
end

endmodule

●タイミングジェネレータ -video_timing-

 Claude Codeに生成させたものをベースにしているので,コメントは参考にしないでください.

Verilog
// ============================================================
// Video Timing Generator
// 1280×720 @ 30 Hz  Pixel Clock: 37.125 MHz
//
// CEA-861 720p ブランキング (30 Hz = 60 Hz の半フレームレート)
// H: Active=1280  FP=110  Sync=40(+)  BP=220  Total=1650
// V: Active=720   FP=5    Sync=5(+)   BP=20   Total=750
// Pixel clock: 37.125 MHz
// Frame rate:  37.125 MHz / (1650 × 750) = 30.000 Hz
// Hsync/Vsync polarity: positive
// ============================================================
module video_timing (
    input  wire        clk,
    input  wire        rst,
    output reg  [11:0] hcount,      // 0 .. H_TOTAL-1  (max 1649, 11bit)
    output reg  [10:0] vcount,      // 0 .. V_TOTAL-1  (max 749,  10bit)
    output wire        hsync,
    output wire        vsync,
    output wire        de,           // data enable (active area)
    output reg         field_start   // one-cycle pulse at start of frame
);

// ── Timing parameters ──────────────────────────────────────
localparam H_ACTIVE = 1280;
localparam H_FP     = 110;
localparam H_SYNC   = 40;
localparam H_BP     = 220;
localparam H_TOTAL  = H_ACTIVE + H_FP + H_SYNC + H_BP; // 1650

localparam V_ACTIVE = 720;
localparam V_FP     = 5;
localparam V_SYNC   = 5;
localparam V_BP     = 20;
localparam V_TOTAL  = V_ACTIVE + V_FP + V_SYNC + V_BP;  // 750


// ── Counters ───────────────────────────────────────────────
always @(posedge clk or posedge rst) begin
    if (rst) begin
        hcount      <= 0;
        vcount      <= 0;
        field_start <= 0;
    end else begin
        field_start <= 0;
        if (hcount == H_TOTAL - 1) begin
            hcount <= 0;
            if (vcount == V_TOTAL - 1) begin
                vcount      <= 0;
                field_start <= 1;
            end else begin
                vcount <= vcount + 1;
            end
        end else begin
            hcount <= hcount + 1;
        end
    end
end

// ── Output signals ─────────────────────────────────────────
assign hsync = (hcount >= H_BP) && (hcount < (H_BP + H_SYNC));
assign vsync = (vcount >= V_BP) && (vcount < (V_BP + V_SYNC));
assign de    = (hcount > (H_BP + H_SYNC)) && (hcount < (H_BP+H_SYNC + H_ACTIVE)) && (vcount > (V_BP + V_SYNC)) && (vcount < (V_BP + V_SYNC + V_ACTIVE));

endmodule
Expand

コメントを残す