MyHDLとは
http://www.myhdl.org/
で開発されている、Pythonでハードウェアのモデリングを行うためのライブラリです。今回、MyHDLを使って画像処理をモデリングしてみました。レイヤー的には、SystemCと同じ位置にいると感じました。
全てのソースはこちらにupしています。
https://github.com/natsutan/computervision/tree/master/LOCV/chap5
画像処理の説明
入力画像のある範囲にたいして、単純なぼかしを入れてみました。アルゴリズムは注目画素に対して、周辺5x5の平均値をその画素としています。
OpenCVでの記述
ここはOpenCVを使ってさくっと書きます。
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)
入力画像
出力画像
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); に相当します。
# -*- 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の時メモリに書き込みます。
# -*- 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を取得しています。
for ry in range(-2,3):
for rx in range(-2,3):
sum_r, sum_g, sum_bに値を足しこんでいって、//25で平均値を計算しています。これだけで合成してくれたらすごく便利ですね。
以下、全ソース
# -*- 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の結果とほぼ同じです。
vcd出力
vcdも出力できるので、gtkwaveで波形が確認できます。思ったとおりに動いてます。
Verilogへの変換
変換方法
Verilogへの変換も、toVerilog関数を使うと一発です。
while 1:はOKなのにwhile Trueは駄目とか、微妙な壁はありましたが、この記述でVerilogできました。
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
)
変換結果
わくわくしますね。
// 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に置き換えてくれるだろうと思って書いていたが、この記述だと駄目でした。
@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と同じ。あくまでモデリングの言語ですね。