KirIn 落書き帳

素人がプログラミング, FPGA, LSIをお勉強しているメモ書きです。間違いがあればご指導していただけたら幸いです。

Python3でVerilog用画像入力テストベンチ自動生成

Verilogでグレイスケール画像群を読み込みモジュールに1画素ずつストリームに転送するためのテストベンチ、hexファイル、Makefileを自動生成するpythonスクリプトを作成しました。(ゴリ押しですが...)

画像群をROMに格納して自分のモジュールにinputします。

f:id:KirIn:20141125015721p:plain

実行環境はいかになります。

まずself.pathで画像ディレクトリを指定、self.extentionで拡張子を指定したverilogで読みたい画像達をいれます。
このサンプルでは./imageディレクトリにpng拡張子をもつ解像度200x200,8bitグレイスケールの画像を2枚入れています。

#! /usr/local/bin/ env python3
# -*- coding: utf-8 -*-
import numpy as np
from PIL import Image
import math
import os

class ImageRom:
    """make images to loading stream imput"""

    def __init__(self, bit, clk, topmodule):
        """create bit width"""
        self.bit = bit 
        self.hexbit = bit // 4
        self.clk = clk
        self.topmodule = topmodule
        self.path = './image'
        self.extention = 'png'

    def makereadh(self):
        """convert images to .hex file"""
        
        maxword = 0

        filelist = [os.path.join(self.path,f) for f in os.listdir(self.path) if f.endswith(self.extention)]

        wf = open("img.hex", 'w')

        for filename in filelist:
            pilin = Image.open(filename)
            maxcol, maxrow = pilin.size
            maxword += maxcol * maxrow
            frame = np.asarray(pilin)
            for i in range(maxrow):
                for j in range(maxcol):
                    hexcon = hex(frame[i, j])
                    hexsplit = "{0}".format(hexcon.replace('0x', '')).rjust(int(self.hexbit), '0')
                    print(hexsplit)
                    wf.write("{0}\n".format(hexsplit))

        wf.close()
        self.word = maxword
        self.wordadr = math.ceil(math.log2(maxword))
        print("success making hexfile")

    def makeTestbench(self):
        """ make testbench.v """
        wf = open("tbench.v", 'w')
        testbench = """`timescale 1ns/1ps
`define P_PXADDR {0}
`define P_IMGDEPTH {1}

module tbench;

    parameter P = {2};
    reg clk, rst;
    wire [`P_IMGDEPTH-1:0] q;

    initial begin
        $dumpfile("./test.vcd");
        $dumpvars(0, {3});
        clk <= 0;
        rst <= 0;

        # {4}
        rst <= 1;
# {5}
        $finish();
    end

    always #(P/2)
        clk <= ~clk;

    imgROM imgROM(.clk(clk), .rst(rst), .q(q));

    // add your instance module

endmodule

module imgROM(input clk,
              input rst,
              output [`P_IMGDEPTH-1:0] q);

    parameter P_maxword = `P_PXADDR'd{6};
    reg [`P_IMGDEPTH-1:0] mem [0:P_maxword];
    reg [`P_PXADDR-1:0] pxADDR;

    initial begin
        $readmemh("img.hex", mem, `P_PXADDR'd0, `P_PXADDR'd{6});
    end

    always @(posedge clk or negedge rst) begin
        if(!rst | pxADDR == P_maxword)
            pxADDR <= `P_PXADDR'd0;
        else 
            pxADDR <= pxADDR + `P_PXADDR'd1;
    end

    assign q = mem[pxADDR];

endmodule
""".format(self.wordadr, self.bit, self.clk, self.topmodule, self.clk * 2, self.word * self.clk, self.word - 1)
        wf.write(testbench)
        wf.close()
        print("success making tbench.v")

    def makeMakefile(self):
        """ make Makefile """
        wf = open("Makefile", 'w')

        text ="""TBENCH = ./tbench.v
#VERILOG = ./ # add your .v file

all:
\tiverilog -o ./test.vpp $(TBENCH) $(VERILOG)
\t./test.vpp
\tgtkwave ./test.vcd
"""

        wf.write(text)
        wf.close()
        print("success making Makefile")
    
if __name__ == '__main__':
    test = ImageRom(8, 100, "reverseq") # bit幅, クロック周波数, 自分のトップモジュールのインスタンス名
    test.makereadh()
    test.makeTestbench()
    test.makeMakefile()

上記のpython3を実行すると以下のファイルが作成されるはずです。

  • Makefile
  • tbench.v # testbenchファイル
  • img.hex # 画素値をhexにしたもの

自分の作成したモジュールを追加します。流れてきた画素値を反転するモジュールを追加します。(ファイル名はreverseq.vとします)

module reverseq(input clk,
            input [7:0] q,
            output reg [7:0] revq);

    always @(posedge clk) begin
        revq <= ~q;
    end

endmodule

作成したreverseq.vをMakefileとtestbench.vに追加します。

まずMakefile

TBENCH = ./tbench.v
#以下の行をコメントアウトを外して追加
VERILOG = ./reverseq.v # add your .v file

all:
  iverilog -o ./test.vpp $(TBENCH) $(VERILOG)
  ./test.vpp
  gtkwave ./test.vcd

そしてtbench.vに自作モジュールをインスタンス化します。(観測用のwireも追加)

`timescale 1ns/1ps
`define P_PXADDR 17
`define P_IMGDEPTH 8

module tbench;

    parameter P = 100;
    reg clk, rst;
    wire [`P_IMGDEPTH-1:0] q, revq;

    initial begin
        $dumpfile("./test.vcd");
        $dumpvars(0, reverseq);
        clk <= 0;
        rst <= 0;

        # 200
        rst <= 1;

        # 8000000
        $finish();
    end

    always #(P/2)
        clk <= ~clk;

    imgROM imgROM(.clk(clk), .rst(rst), .q(q));

    // add your instance module
    reverseq reverseq(.clk(clk), .q(q), .revq(revq));

endmodule

module imgROM(input clk,
              input rst,
              output [`P_IMGDEPTH-1:0] q);

    parameter P_maxword = `P_PXADDR'd79999;
    reg [`P_IMGDEPTH-1:0] mem [0:P_maxword];
    reg [`P_PXADDR-1:0] pxADDR;

    initial begin
        $readmemh("img.hex", mem, `P_PXADDR'd0, `P_PXADDR'd79999);
    end

    always @(posedge clk or negedge rst) begin
        if(!rst | pxADDR == P_maxword)
            pxADDR <= `P_PXADDR'd0;
        else 
            pxADDR <= pxADDR + `P_PXADDR'd1;
    end

    assign q = mem[pxADDR];

endmodule

あとはターミナル上でmakeを実行すると自動でGTKWAVEが起動するはずです。
実行結果は以下のようになります。

f:id:KirIn:20141125014620p:plain

クロックの周期は\`timescaleを書き換えるかpythonのクラスに渡す因数で調整してください。

もっと効率の良い方法をご存じの方はご教授いただけるとものすごく嬉しいです。