MyHDLで画像処理


# MyHDLとは
http://www.myhdl.org/ 
で開発されている、Pythonでハードウェアのモデリングを行うためのライブラリです。今回、MyHDLを使って画像処理をモデリングしてみました。レイヤー的には、SystemCと同じ位置にいると感じました。

全てのソースはこちらにupしています。
https://github.com/natsutan/computervision/tree/master/LOCV/chap5

# 画像処理の説明
入力画像のある範囲にたいして、単純なぼかしを入れてみました。アルゴリズムは注目画素に対して、周辺5x5の平均値をその画素としています。

## OpenCVでの記述
ここはOpenCVを使ってさくっと書きます。

```py
def run_opencv():
    src = cv2.imread('../../image/twittan/twittan.jpg')
    dst = src.copy()

    roi_x = 100
    roi_y = 100
    roi_w = 150
    roi_h = 200

    dst[roi_y:roi_y + roi_h, roi_x:roi_x + roi_w] = cv2.blur(src[roi_y:roi_y + roi_h, roi_x:roi_x + roi_w], (5, 5),
                                                             (-1, -1))
    print("image size:width = %d, height = %d" % (dst.shape[1], dst.shape[0]))

    cv2.imwrite('twi_blur_cv.jpg', dst)
```
## 入力画像

![twi.jpg](https://qiita-image-store.s3.amazonaws.com/0/327/2bb6f06c-74e6-3aaa-5373-a088fd36c1fb.jpeg)

## 出力画像
すこし顔面にぼかしが入ってます。
![twi_blur_cv.jpg](https://qiita-image-store.s3.amazonaws.com/0/327/f5c2cc12-7017-43ec-988a-8220e5589dea.jpeg)

# MyHDLでの記述
## ファイル構成
今回は5つのファイルを使っています。
- smooth.py　OpenCVの処理を含む全体のTOPです。python smooty.py でSim実行します。
- myhdl_top.py MyHDL環境のTOPです。Clk, Resetが含まれます。
- smooth_hdl.py RTLが記載されています。
- mem.py　MyHDLのシミュレーション用メモリモデルです。
- reg_driver.py レジスタ設定を行います。
 
## テストベンチ側
テストベンチ側の記述です。
### CLK, Reset等
サンプルとほぼ同じなので省略
###  レジスタ設定
正論理のリセットが1clkだけ来るので、それを待ってレジスタ設定を行っています。その後、startレジスタを1clkだけ1にし処理を開始、endレジスタが1になるのを待って処理終了です。 yield clk.posedge がwait posedge(clk); に相当します。


```py:reg_driver.py
# -*- coding: utf-8 -*-
__author__ = 'natu'
from myhdl import *

def reg_driver_top(
        clk, reset,
        reg_start, reg_end,
        reg_width, reg_height,
        reg_roi_x, reg_roi_y, reg_roi_h, reg_roi_w
        ):

    @instance
    def regDriver():
        while reset == 0:
            yield clk.posedge
        while reset == 1:
            yield clk.posedge

        reg_width.next = 358
        reg_height.next = 557
        reg_roi_x.next = 100
        reg_roi_y.next = 100
        reg_roi_h.next = 200
        reg_roi_w.next = 150
        yield clk.posedge

        reg_start.next = 1
        yield clk.posedge
        reg_start.next = 0
        yield clk.posedge

        while reg_end == 0:
            yield clk.posedge

        print("end == 1")
        yield clk.posedge

    return regDriver
```

### メモリ
ここはPythonのパワーが発揮できるところでした。OpenCVのimreadを使ってjpegファイルを直接オープンしてSimに使用できます。Hexダンプして、readmemh を使わなくても大丈夫です。出力も同様にOpenCVで直接画像に落とせます。

read側は組み合わせ回路でradrが変化したらread_r, read_g, read_bを更新、write側はclk同期でwenが1の時メモリに書き込みます。

```py:mem.py
# -*- coding: utf-8 -*-
__author__ = 'natu'
from myhdl import *
import numpy
import cv2

dst = None

def mem_top(
        clk, reset,
        read_r, read_g, read_b, radr,
        write_r, write_g, write_b, wadr, wen):
    global dst

    src = cv2.imread('../../image/twittan/twittan.jpg')
    dst = numpy.zeros(src.shape)

    @always_comb
    def mem_read():
        x, y = adr_dec(radr)
        read_r.next = clop_8bit(src[y][x][0])
        read_g.next = clop_8bit(src[y][x][1])
        read_b.next = clop_8bit(src[y][x][2])


    @instance
    def mem_write():
        while True:
            if wen == 1:
                x, y = adr_dec(wadr)
                dst[y][x][0] = write_r
                dst[y][x][1] = write_g
                dst[y][x][2] = write_b
            yield clk.posedge

    return mem_read, mem_write

def write_image():
    cv2.imwrite('twi_blur_rtl.jpg', dst)

def adr_dec(adr):
    width = dst.shape[1]
    x = int(adr) % width
    y = int(adr) / width
    return x, y

def clop_8bit(x):
    if x >= 255:
        return 255

    return int(x)
```

## RTL側
ステートマシーンはサンプルにあったのでとりあえず入れてますが、あまり重要な動きはしていません。x, yの処理をするのに、forで回せるのが便利ですね。

この2重ループで、注目画素の周辺5x5を取得しています。

```py
      for ry in range(-2,3):
          for rx in range(-2,3):
```
sum_r, sum_g, sum_bに値を足しこんでいって、//25で平均値を計算しています。これだけで合成してくれたらすごく便利ですね。

以下、全ソース

```py:smooth_hdl.py
# -*- coding: utf-8 -*-
__author__ = 'natu'
from myhdl import *

t_State = enum('IDLE', 'RUNNING')

def smoother_top(
        clk, reset,
        rin, gin, bin, radr,
        rout, gout, bout, wadr, wen,
        reg_start, reg_end,
        reg_width, reg_height,
        reg_roi_x, reg_roi_y, reg_roi_h, reg_roi_w
    ):

    state = Signal(t_State.IDLE)

    @instance
    def main_proc():
        while 1:
            if state == t_State.RUNNING:
                for y in range(reg_height):
                    print("y = %d" % y)
                    for x in range(reg_width):
                        if reg_roi_x <= x and x < reg_roi_x + reg_roi_w and reg_roi_y <= y and y < reg_roi_y + reg_roi_h:
                            # ROI
                            sum_r = 0
                            sum_g = 0
                            sum_b = 0
                            for ry in range(-2,3):
                                for rx in range(-2,3):
                                    radr.next = adr(x + rx, y + ry)
                                    yield  clk.posedge
                                    sum_r = sum_r + rin
                                    sum_g = sum_g + gin
                                    sum_b = sum_b + bin
                                    yield  clk.posedge
                            wadr.next = adr(x, y)
                            rout.next = sum_r // 25
                            gout.next = sum_g // 25
                            bout.next = sum_b // 25
                            wen.next = 1
                            yield  clk.posedge
                            wen.next = 0
                        else:
                            radr.next = adr(x, y)
                            yield  clk.posedge
                            wadr.next = adr(x, y)
                            rout.next = rin
                            gout.next = gin
                            bout.next = bin
                            wen.next = 1
                            yield  clk.posedge
                            wen.next = 0
                reg_end.next = 1
                yield  clk.posedge

            yield  clk.posedge


    def adr(x, y):
        return y * reg_width + x

    @always_seq(clk.posedge, reset=reset)
    def fsm():
        if state == t_State.IDLE:
            if reg_start == 1:
                state.next = t_State.RUNNING
        elif state == t_State.RUNNING:
            if reg_end == 1:
                state.next = t_State.IDLE
        else:
            raise ValueError("Undefined state")
            
    return fsm, main_proc
```

# 処理結果
## MyHDLによる出力画像
OpenCVの結果とほぼ同じです。

![twi_blur_rtl.jpg](https://qiita-image-store.s3.amazonaws.com/0/327/d7d99932-976b-f122-f1b0-d06dd655bd9c.jpeg)

## vcd出力
vcdも出力できるので、gtkwaveで波形が確認できます。思ったとおりに動いてます。
![gtkwave.png](https://qiita-image-store.s3.amazonaws.com/0/327/30cf4f5d-96e9-1abf-c472-f2d7db81a241.png)

# Verilogへの変換
## 変換方法
Verilogへの変換も、toVerilog関数を使うと一発です。
while 1:はOKなのにwhile Trueは駄目とか、微妙な壁はありましたが、この記述でVerilogできました。

```py
    toVerilog(smoother_top,
        clk, reset,
        rin, gin, bin, radr,
        rout, gout, bout, wadr, wen,
        reg_start, reg_end,
        reg_width, reg_height,
        reg_roi_x, reg_roi_y, reg_roi_h, reg_roi_w
    )
```
##変換結果
わくわくしますね。

```verilog
// File: smoother_top.v
// Generated by MyHDL 0.9.dev0
// Date: Tue May 19 14:30:05 2015


`timescale 1ns/10ps

module smoother_top (
    clk,
    reset,
    rin,
    gin,
    bin,
    radr,
    rout,
    gout,
    bout,
    wadr,
    wen,
    reg_start,
    reg_end,
    reg_width,
    reg_height,
    reg_roi_x,
    reg_roi_y,
    reg_roi_h,
    reg_roi_w
);


input clk;
input reset;
input [7:0] rin;
input [7:0] gin;
input [7:0] bin;
output [19:0] radr;
reg [19:0] radr;
output [7:0] rout;
reg [7:0] rout;
output [7:0] gout;
reg [7:0] gout;
output [7:0] bout;
reg [7:0] bout;
output [19:0] wadr;
reg [19:0] wadr;
output wen;
reg wen;
input reg_start;
output reg_end;
reg reg_end;
input [9:0] reg_width;
input [9:0] reg_height;
input [9:0] reg_roi_x;
input [9:0] reg_roi_y;
input [9:0] reg_roi_h;
input [9:0] reg_roi_w;

reg [0:0] state;




function integer MYHDL13_adr;
    input x;
    integer x;
    input y;
    integer y;
begin: MYHDL17_RETURN
    MYHDL13_adr = ((y * $signed({1'b0, reg_width})) + x);
    disable MYHDL17_RETURN;
end
endfunction

function integer MYHDL14_adr;
    input x;
    integer x;
    input y;
    integer y;
begin: MYHDL18_RETURN
    MYHDL14_adr = ((y * $signed({1'b0, reg_width})) + x);
    disable MYHDL18_RETURN;
end
endfunction

function integer MYHDL15_adr;
    input x;
    integer x;
    input y;
    integer y;
begin: MYHDL19_RETURN
    MYHDL15_adr = ((y * $signed({1'b0, reg_width})) + x);
    disable MYHDL19_RETURN;
end
endfunction

function integer MYHDL16_adr;
    input x;
    integer x;
    input y;
    integer y;
begin: MYHDL20_RETURN
    MYHDL16_adr = ((y * $signed({1'b0, reg_width})) + x);
    disable MYHDL20_RETURN;
end
endfunction


always @(posedge clk, posedge reset) begin: SMOOTHER_TOP_FSM
    if (reset == 1) begin
        state <= 1'b0;
    end
    else begin
        case (state)
            1'b0: begin
                if ((reg_start == 1)) begin
                    state <= 1'b1;
                end
            end
            1'b1: begin
                if ((reg_end == 1)) begin
                    state <= 1'b0;
                end
            end
            default: begin
                $finish;
            end
        endcase
    end
end


initial begin: SMOOTHER_TOP_MAIN_PROC
    integer sum_b;
    integer rx;
    integer ry;
    integer sum_g;
    integer y;
    integer x;
    integer sum_r;
    while (1) begin
        if ((state == 1'b1)) begin
            for (y=0; y<reg_height; y=y+1) begin
                $write("y = ");
                $write("%0d", y);
                $write("\n");
                for (x=0; x<reg_width; x=x+1) begin
                    if ((($signed({1'b0, reg_roi_x}) <= x) && (x < (reg_roi_x + reg_roi_w)) && ($signed({1'b0, reg_roi_y}) <= y) && (y < (reg_roi_y + reg_roi_h)))) begin
                        sum_r = 0;
                        sum_g = 0;
                        sum_b = 0;
                        for (ry=(-2); ry<3; ry=ry+1) begin
                            for (rx=(-2); rx<3; rx=rx+1) begin
                                radr <= MYHDL13_adr((x + rx), (y + ry));
                                @(posedge clk);
                                sum_r = (sum_r + rin);
                                sum_g = (sum_g + gin);
                                sum_b = (sum_b + bin);
                                @(posedge clk);
                            end
                        end
                        wadr <= MYHDL14_adr(x, y);
                        rout <= (sum_r / 25);
                        gout <= (sum_g / 25);
                        bout <= (sum_b / 25);
                        wen <= 1;
                        @(posedge clk);
                        wen <= 0;
                    end
                    else begin
                        radr <= MYHDL15_adr(x, y);
                        @(posedge clk);
                        wadr <= MYHDL16_adr(x, y);
                        rout <= rin;
                        gout <= gin;
                        bout <= bin;
                        wen <= 1;
                        @(posedge clk);
                        wen <= 0;
                    end
                end
            end
            reg_end <= 1;
            @(posedge clk);
        end
        @(posedge clk);
    end
end

endmodule
```

initial begin: SMOOTHER_TOP_MAIN_PROC、、、

(つд⊂)ｺﾞｼｺﾞｼ→（；ﾟ Дﾟ） …！？

＿人人人人人人人人＿
＞　initial begin:　＜
￣Y^Y^Y^Y^Y^Y^￣

合成できない！
だめじゃん＞＜

# 感想
## 良かったところ
- PyCharmが便利。未使用の信号を教えてくれたり、スコープを理解した変数の一括置換など。ただ、デフォルトの状態だと警告が多すぎて使いにくい。やれ、改行が多いだの少ないだの、ここにスペースがいるだのいらんだの、ちょっと辛くなってしまった。
- Pythonのライブラリが便利。

アサーションとかカバレッジも挑戦したかったけど、自分の作った回路が合成できないと知ってがっくしきてしまい、そこまで踏み込めず。

## 駄目だったところ
- 動く回路を書ききる難易度が高い。回路の動きを記載した関数を返すというのが直感的ではない。デバッグしにくい。
- エラーが分かりにくい。Pythonの構文ツリーを直接見ているのか、エラーメッセージが長くC++のような絶望感がある。 sum_r = 0 を間違えて sum_r == 0 とするだけで67行のエラーメッセージが出てくる。
- 合成できる記述が非常に限られている。今回の回路でも全体は無限ループ、waitが入るのは全てclkの立ち上がりなので、これは合成可能なVerilogに置き換えてくれるだろうと思って書いていたが、この記述だと駄目でした。

```py
    @always_seq(clk.posedge, reset=reset)
    def fsm():
        if state == t_State.IDLE:
            if reg_start == 1:
                state.next = t_State.RUNNING
        elif state == t_State.RUNNING:
            if reg_end == 1:
                state.next = t_State.IDLE
        else:
            raise ValueError("Undefined state")
```

合成可能な記述はこのパターンで、途中にyieldが入ってこないジェネレータを書ききらないといけない。つまりVerilog-HDLと同じ。あくまでモデリングの言語ですね。