ハードウェアとして,Tang Primer 138K Pro Dockを使います.合成系はGowin IDEです.

目標はコレ

●生成されたRTLコード(シェーダー部分のみ)

▲初期実装

// ============================================================================
//  shader.sv
//  座標 (x, y) と時間 (t) から 24bit ピクセル色 (R,G,B 各8bit) を生成する回路
//
//  【設計方針】
//   ・三角関数 (sin/cos) は使用しない
//   ・浮動小数点は使用しない
//   ・整数の加算・定数乗算・右シフト・ビット反転のみで実装
//
//  【描画内容】
//   空      : 上ほど濃い青、地平線に近づくほど明るい青のグラデーション
//   奥の山  : 霞んだ青灰色の山並み
//   手前の山: 濃い緑の山並み (奥の山を隠す)
//   雪      : 高い稜線の頂部に白い雪
//
//   山の稜線は「三角波の重ね合わせ」で生成する。
//   三角波は位相の MSB で下位ビットを反転するだけで作れるため、
//   sin/cos も除算も使わずにギザギザした自然な輪郭が得られる。
//
//   時間変化:
//     ・山並みがゆっくり横に流れる (手前と奥で速度を変えて視差を付ける)
//     ・フレームカウンタから作った三角波で明るさ(色)が周期的に変化する
// ============================================================================
module shader #(
    parameter int H_ACTIVE = 1280,   // 水平アクティブ画素数
    parameter int V_ACTIVE = 720     // 垂直アクティブ画素数
) (
    input  logic        clk,      // ピクセルクロック
    input  logic        rst,      // 同期リセット (アクティブHigh)
    input  logic        de,       // データイネーブル (アクティブ期間で1)
    input  logic [11:0] hcount,   // X座標 (0 .. H_ACTIVE-1)
    input  logic [11:0] vcount,   // Y座標 (0 .. V_ACTIVE-1)
    output logic [7:0]  r,        // 赤 (8bit)
    output logic [7:0]  g,        // 緑 (8bit)
    output logic [7:0]  b         // 青 (8bit)
);

    // ------------------------------------------------------------------------
    // 1. 時間カウンタ (フレーム単位)
    //    各フレームの先頭 (hcount=0, vcount=0, de=1) で +1 する
    // ------------------------------------------------------------------------
    logic [31:0] frame_cnt;

    always_ff @(posedge clk) begin
        if (rst)
            frame_cnt <= 32'd0;
        else if (de && (hcount == 12'd0) && (vcount == 12'd0))
            frame_cnt <= frame_cnt + 32'd1;
    end

    // 時間値 (8bit) : フレームカウンタの下位8bit
    logic [7:0] t_val;
    assign t_val = frame_cnt[7:0];

    // 時間三角波 (0 .. 254) : 明るさ(色)を周期的に変化させる
    //   のこぎり波の MSB が 1 のとき下位ビットを反転すると三角波になる
    logic [7:0] saw_t;
    logic [7:0] wave_t;

    assign saw_t  = {t_val[6:0], 1'b0};        // t_val << 1 (0..254)
    assign wave_t = t_val[7] ? ~saw_t : saw_t; // 三角波 0..254

    // ------------------------------------------------------------------------
    // 2. 画面下からの高さ y_up (0..255)
    //    vcount=0 が画面上端 → y_up=255、vcount=V_ACTIVE-1 が下端 → y_up=0
    //    除算を避けるため「定数乗算 + 右シフト」でスケーリングする
    //      y_up = 255 - (vcount * 91 >> 8)   (0..719 → 255..0)
    // ------------------------------------------------------------------------
    logic [15:0] y_prod;
    logic [7:0]  y_up;

    assign y_prod = {4'b0000, vcount} * 16'd91;   // 最大 719*91 = 65429
    assign y_up   = 8'd255 - y_prod[15:8];        // 0(下端) .. 255(上端)

    // ------------------------------------------------------------------------
    // 3. 山の稜線 (周期の異なる三角波の重ね合わせ)
    //    三角波 : 位相の MSB が 1 なら下位ビットを反転 (0..255)
    //    周期の違う波を足すことで、単調なギザギザではなく
    //    大小の峰が混ざった自然な山並みになる
    //    山の高さは「画面下からの高さ」で表す (大きいほど高い)
    // ------------------------------------------------------------------------
    logic [11:0] x_far, x_near;   // 横スクロールを加えた X 座標

    // 手前の山は奥の山より速く流れる (視差効果)
    assign x_far  = hcount + {4'b0000, t_val};         // ゆっくり
    assign x_near = hcount + {3'b000, t_val, 1'b0};    // 2倍速

    // 三角波を生成する関数 (位相の MSB で折り返す)
    // 周期 512 の大きな波 (峰と谷がゆったり)
    function automatic logic [7:0] tri_slow(input logic [11:0] phase);
        tri_slow = phase[8] ? ~phase[7:0] : phase[7:0];
    endfunction

    // 周期 128 の細かい波 (岩肌のギザギザ)
    function automatic logic [7:0] tri_fast(input logic [11:0] phase);
        tri_fast = phase[6] ? ~{1'b0, phase[6:0]} : {1'b0, phase[6:0]};
    endfunction

    // 各山の波をいったん信号に受ける
    logic [7:0] far_slow, far_fast, near_slow, near_fast;

    assign far_slow  = tri_slow(x_far);
    assign far_fast  = tri_fast(x_far);
    assign near_slow = tri_slow(x_near);
    assign near_fast = tri_fast(x_near);

    // 稜線の高さ (0..255) : 大きな波 + 細かい波 を合成
    //   奥の山  : 高くそびえる (70..164)
    //   手前の山: 低く手前に広がる (30..125)
    logic [7:0] h_far, h_near;

    assign h_far  = 8'd70 + {1'b0, far_slow[7:2]}  + {2'b00, far_fast[6:2]};
    assign h_near = 8'd30 + {1'b0, near_slow[7:2]} + {2'b00, near_fast[6:2]};

    // ------------------------------------------------------------------------
    // 4. 空のグラデーション
    //    上ほど濃い青、地平線に近づくほど明るい水色
    // ------------------------------------------------------------------------
    logic [7:0] sky_r, sky_g, sky_b;

    assign sky_r = 8'd40  + {2'b00, vcount[9:4]};        // 40..103
    assign sky_g = 8'd90  + {1'b0, vcount[9:3]};         // 90..217
    assign sky_b = 8'd200 + {3'b000, vcount[9:5]};       // 200..231

    // ------------------------------------------------------------------------
    // 5. 山の色 (奥は霞んだ青灰色、手前は濃い緑)
    //    時間三角波で明るさを変化させる
    // ------------------------------------------------------------------------
    logic [7:0] far_r, far_g, far_b;
    logic [7:0] near_r, near_g, near_b;

    // 奥の山 : 青灰色 + 時間による明暗
    assign far_r = 8'd70  + {1'b0, wave_t[7:2]};   // 70..133
    assign far_g = 8'd85  + {1'b0, wave_t[7:2]};   // 85..148
    assign far_b = 8'd110 + {1'b0, wave_t[7:2]};   // 110..173

    // 手前の山 : 濃い緑 + 時間による明暗
    assign near_r = 8'd20  + {2'b00, wave_t[7:3]}; // 20..51
    assign near_g = 8'd70  + {1'b0, wave_t[7:2]};  // 70..133
    assign near_b = 8'd30  + {2'b00, wave_t[7:3]}; // 30..61

    // ------------------------------------------------------------------------
    // 6. 雪 : 奥の山の高い稜線 (頂上付近) を白くする
    // ------------------------------------------------------------------------
    logic snow_far;

    assign snow_far = (h_far > 8'd140) && (y_up > (h_far - 8'd18));

    // ------------------------------------------------------------------------
    // 7. 太陽 : 円の内部判定 (dx^2 + dy^2 < r^2) を整数乗算だけで行う
    // ------------------------------------------------------------------------
    logic [11:0] sun_dx, sun_dy;
    logic [23:0] sun_dist;
    logic        sun_on;

    assign sun_dx = (hcount > 12'd950) ? (hcount - 12'd950) : (12'd950 - hcount);
    assign sun_dy = (y_up   > 8'd200)  ? ({4'b0000, y_up} - 12'd200)
                                       : (12'd200 - {4'b0000, y_up});
    // 乗算結果が切り捨てられないよう、オペランドを 24bit に拡張する
    assign sun_dist = ({12'b0, sun_dx} * {12'b0, sun_dx})
                    + ({12'b0, sun_dy} * {12'b0, sun_dy});
    assign sun_on   = (sun_dist < 24'd1600);   // 半径 40 の円

    // ------------------------------------------------------------------------
    // 8. ピクセル合成 (手前の山が奥の山を隠す)
    // ------------------------------------------------------------------------
    always_comb begin
        if (!de) begin
            // ブランキング期間は黒
            r = 8'h00;
            g = 8'h00;
            b = 8'h00;
        end else if (y_up < h_near) begin
            // 手前の山より下 → 手前の山 (緑)
            r = near_r; g = near_g; b = near_b;
        end else if (y_up < h_far) begin
            // 奥の山より下 → 奥の山 (青灰色) / 頂上は雪
            if (snow_far) begin
                r = 8'hE8; g = 8'hEE; b = 8'hFF;
            end else begin
                r = far_r; g = far_g; b = far_b;
            end
        end else begin
            // それより上 → 空 (太陽があれば太陽)
            if (sun_on) begin
                r = 8'hFF; g = 8'hF4; b = 8'hC0;
            end else begin
                r = sky_r; g = sky_g; b = sky_b;
            end
        end
    end

endmodule
広げる

コメントを残す