SystemVerilog|メディアンフィルタの実装

Systemverilog
田中太郎
田中太郎

メディアンフィルタ回路を実装して画像データに実行しました

全体構成

BMPの画像に3×1のメディアンフィルタをかけてresult.bmpの名前で保存します

画像データをテストベンチで読み込み回路に与えます

enableがHighのときのdinが有効入力画像データで、
validがHighのときのdoutが有効出力画像データです

画像の水平方向両端は注目画素を出力します

top.sv

main.svとctrl.svをインスタンスしています

module top #(
    parameter IMG_W = 64,
    parameter IMG_H = 64
)(
    input clk,
    input rst_n,
    input enable,
    input [7:0] din,
    output logic valid,
    output logic [7:0] dout,

    output logic interrupt
);
    localparam LATENCY = 1;

    logic edge_en; // 画像の水平方向両端を知らせる
    ctrl #(
        .IMG_W(IMG_W),
        .IMG_H(IMG_H),
        .LATENCY(LATENCY)
    ) i_ctrl(
        .clk(clk),
        .rst_n(rst_n),
        .enable(enable),
        .edge_en(edge_en),
        .valid(valid),
        .interrupt(interrupt)
    );

    main #(
    ) i_main(
        .clk(clk),
        .rst_n(rst_n),
        .edge_en(edge_en),
        .din(din),
        .dout(dout)
    );
endmodule
main.sv

メディアン処理を行う回路です

dinを2回FFで保持して中央値をdoutとして出力します

また、ctrl.svから出力されるedge_enが入力されると、注目画素が出力されます

edge_enは画像の水平方向両端であることを示します

module main #(
)(
    input clk,
    input rst_n,
    input [7:0] din,
    input edge_en,
    output logic [7:0] dout
);
    logic [7:0] data_d1;
    logic [7:0] data_d2;

    // 画像データを2画素ぶん保持する
    always_ff @(posedge clk, negedge rst_n) begin
        if (!rst_n) begin
            data_d1 <= '0;
            data_d2 <= '0;
        end
        else begin
            data_d1 <= din;
            data_d2 <= data_d1;
        end
    end

    // 3画素の中央値を選択する
    logic [7:0] median;
    always_comb begin
        if (data_d1 > data_d2) begin
            if (data_d2 > din) begin
                median = data_d2;
            end
            else if (din > data_d1) begin
                median = data_d1;
            end
            else begin
                median = din;
            end
        end
        else if (data_d2 > din) begin
            if (din > data_d1) begin
                median = din;
            end
            else begin
                median = data_d1;
            end
        end
        else begin
            median = data_d2;
        end
    end

    // 水平方向両端は注目画素、それ以外のときはメディアン値を出力する
    always_ff @(posedge clk, negedge rst_n) begin
        if (!rst_n) begin
            dout <= '0;
        end
        else begin
            if (edge_en) begin
                dout <= data_d1;
            end
            else begin
                dout <= median;
            end
        end
    end
endmodule
ctrl.sv

制御回路です

enableがHighになったらcntが初期化されて1クロック毎にカウントアップします

main.svのレイテンシを鑑みてvalidをHighにします

validはdoutが有効画像データであることを知らせます

また、注目画素が水平方向両端であることをmain.svに知らせるためにedge_enを生成します

module ctrl #(
    parameter IMG_W = 512,
    parameter IMG_H = 512,
    parameter LATENCY = 1
)(
    input clk,
    input rst_n,
    input enable,
    output logic edge_en,
    output logic valid,

    output logic interrupt
);
    logic [18:0] cnt;
    logic enable_d;

    // cntを初期化するためにenableのエッジを検出
    always_ff @(posedge clk, negedge rst_n) begin
        if (!rst_n) begin
            enable_d <= '0;
        end
        else begin
            enable_d <= enable;
        end
    end

    // enableのエッジで初期化するカウンタ
    always_ff @(posedge clk, negedge rst_n) begin
        if (!rst_n) begin
            cnt <= '1;
        end
        else begin
            if (enable & ~enable_d) begin
                cnt <= '0;
            end
            else if (cnt < IMG_W*IMG_H + LATENCY) begin
                cnt <= cnt + 1'b1;
            end
        end
    end

    // 水平方向両端でedge_enがHighになりmain.svに知らせる
    always_comb begin
        if (cnt == 0 || cnt%IMG_W == 0) begin
            edge_en = 1'b1;
        end
        else begin
            edge_en = 1'b0;
        end
    end

    // 入力画像をすべて処理したらinterrupがHighになる
    always_comb begin
        if (cnt == IMG_W*IMG_H + LATENCY) begin
            interrupt = '1;
        end
        else begin
            interrupt = '0;
        end
    end

    // 出力するデータが有効かどうかをテストベンチにしらせる
    always_comb begin
        if (LATENCY <= cnt && cnt < IMG_W*IMG_H + LATENCY) begin
            valid = '1;
        end
        else begin
            valid = '0;
        end
    end
endmodule
tb.sv(テストベンチ)

画像データを読み込んで回路に1画素ずつ送信します

回路から出力されるvalidがHighのときのdoutを有効画像としてファイル(result.bmp)に保存します

module tb;
    initial begin
        $dumpfile("wave.vcd");
        $dumpvars(0, tb);
    end

    localparam IMG_W = 64;
    localparam IMG_H = 64;

    int f;
    bit [7:0] bmp[54+1024];
    bit [7:0] data[IMG_H*IMG_W];
    logic [7:0] din;
    logic [7:0] dout;
    bit enable;
    bit valid;
    bit interrupt;

    bit clk = 0;
    bit rst_n = 0;
    always #5 clk = ~clk;

    initial begin
        f = $fopen("sample.bmp", "rb");  // sample.bmpを読み込む
        $fread(bmp, f, 0, 54+1024); // ヘッダを保持する
        $fread(data, f, 0, IMG_H*IMG_W); // 画素データ
        $fclose(f);

        f = $fopen("result.bmp", "wb"); // result.bmpに書き込む
        foreach (bmp[i])  // ヘッダを書き込む
            $fwrite(f, "%c", bmp[i]);

        #50 rst_n = 1;
        #10;

        enable = 1;
        fork
            foreach (data[i]) begin // 1クロック毎に1画素ずつ回路に送る
                din = data[i];
                @(posedge clk);
            end
        join_none

        @(posedge clk);
        enable = 0;

        forever begin
            if (valid) begin // validがHighのときのデータをファイルに書き込み
                $fwrite(f, "%c", dout);
            end
            @(posedge clk);
            if (interrupt) begin // interruptがHighになったらテスト終了
                break;
            end
        end
        $fclose(f);

        $finish;
    end

    top #(
        .IMG_W(IMG_W),
        .IMG_H(IMG_H)
    ) i_top(
        .clk(clk),
        .rst_n(rst_n),
        .enable(enable),
        .din(din),
        .valid(valid),
        .dout(dout),
        .interrupt(interrupt)
    );
endmodule
入力画像

出力画像

まとめ

メディアンフィルタ回路を実装しました

テストベンチを作成して画像にメディアンフィルタをかけました

コメント

タイトルとURLをコピーしました