Virtual I/OをUARTの代わりに
なんかAlveoU250でUARTが使えない
うーん.なぜかAlveo U250でUARTが使えない.
ただ文字を表示したいだけだし,UARTモジュールを実装してみたんだけど,動かなかった.
なんでや!と思ってXDCファイルを更新したり,ドライバ再インスコしたり,再起動したり,色々したんだけど...
困るのよねえ.何かtipsあればTwitter(@tnk_make)で教えてください...
代わりにVirtual I/Oを使う
マジで嫌々,仕方無〜〜〜くUARTの代わりにVirtual IOで文字列の確認できないかなあと考えた.
こんなクソ発想せずにUARTしたいんだが,,,.
Virtual I/O ってなあに
XilinxのVirtual Input/Output (VIO)についてのドキュメントは
こちら(https://docs.xilinx.com/v/u/en-US/pg159-vio).
フレームレートに関して,1msとか指定できるけど,なんかよくエラー吐くので使わないようにしよう.
まあ100ms
程度であれば問題ないかな.うーん,10Hzか.なんだこれ.
動作確認
ひとまずこんな予定
- Arty S7で動かしてみる
- Alveo U250で動かしてみる
まずは個人の持っているArty S7で正しく動くことを確認する.
その上でもしAlveoU250でうまく動かなければ,多分Clocking Wizardの使い方を間違えている.
AlveoU250で動いたら,まあOK.
設計
設計はこんな感じ.AlveoではこのCLK100MHz
にClocking Wizardを接続しようかな.

ディレクトリ構成
.
├── consts
│ ├── Arty-S7-50-Master.xdc
│ └── alveo-u250-xdc.xdc
├── rtls
│ ├── clock_gen_for_vio.sv
│ ├── dammy_rtl.sv
│ ├── top_usb_uart_tx.sv
│ ├── top_usb_uart_tx_with_clkwiz.sv
│ └── tx_fifo.sv
└── tcl
├── build.tcl
├── define.tcl
├── show_vio.tcl
└── sources
├── make_clocking_wizard_design.tcl
└── make_vio_design.tcl
RTL
./rtls
の中身をそれぞれ解説しますわね.
Clocking Wizardなしverのトップモジュール (top_usb_uart_tx.sv)
Artyならこれを使う.
信号名 |
bit幅[bit] |
役割 |
CLK100MHZ |
1 |
100MHzのクロック信号 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
module top_usb_uart_tx (
input logic CLK100MHZ
);
logic rst_n;
logic CLK10HZ;
logic [31:0] cnt;
logic tx_en_rtl;
logic [ 7:0] tx_rtl;
logic tx_en_vio;
logic [ 7:0] tx_vio;
design_1 #()vio(
.clk_i(CLK100MHZ),
.rst_n(rst_n),
.tx_en(tx_en_vio),
.tx(tx_vio)
);
tx_fifo tx_q(
.rst_n(rst_n),
.clk_10hz_i(CLK10HZ),
.clk_i(CLK100MHZ),
.tx_en_i(tx_en_rtl),
.tx_i(tx_rtl),
.tx_en_o(tx_en_vio),
.tx_o(tx_vio)
);
clock_gen_for_vio clk_gen(
.rst_n(rst_n),
.clk_i(CLK100MHZ),
.clk_10hz_o(CLK10HZ)
);
dammy_rtl dammy(
.rst_n(rst_n),
.clk_i(CLK100MHZ),
.tx_en_rtl(tx_en_rtl),
.tx_rtl(tx_rtl)
);
endmodule
|
Clocking Wizardありverのトップモジュール (top_usb_uart_tx_with_clkwiz.sv)
Clocking Wizardを使うver.Alveoで使う.
信号名 |
bit幅[bit] |
役割 |
SYSCLK0_300_P |
1 |
作動クロック信号のP |
SYSCLK0_300_N |
1 |
作動クロック信号のN |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
module top_usb_uart_tx_with_clkwiz (
input logic SYSCLK0_300_P,
input logic SYSCLK0_300_N
);
/* ------追加----- */
logic CLK100MHZ;
design_2 #()clkwiz(
.SYSCLK0_300_P(SYSCLK0_300_P),
.SYSCLK0_300_N(SYSCLK0_300_N),
.CLK100MHZ(CLK100MHZ),
.rst_n(rst_n)
);
/* ------------- */
logic rst_n;
logic CLK10HZ;
logic [31:0] cnt;
logic tx_en_rtl;
logic [ 7:0] tx_rtl;
logic tx_en_vio;
logic [ 7:0] tx_vio;
design_1 #()vio(
.clk_i(CLK100MHZ),
.rst_n(rst_n),
.tx_en(tx_en_vio),
.tx(tx_vio)
);
tx_fifo tx_q(
.rst_n(rst_n),
.clk_10hz_i(CLK10HZ),
.clk_i(CLK100MHZ),
.tx_en_i(tx_en_rtl),
.tx_i(tx_rtl),
.tx_en_o(tx_en_vio),
.tx_o(tx_vio)
);
clock_gen_for_vio clk_gen(
.rst_n(rst_n),
.clk_i(CLK100MHZ),
.clk_10hz_o(CLK10HZ)
);
dammy_rtl dammy(
.rst_n(rst_n),
.clk_i(CLK100MHZ),
.tx_en_rtl(tx_en_rtl),
.tx_rtl(tx_rtl)
);
endmodule
|
適当なモジュール (dammy_rtl.sv)
とりあえずRTLの出力はこんな感じ.
まあtx_rtl
もtx_en_rtl
も出力できりゃあなんでもいいんですよねえ.
信号名 |
bit幅[bit] |
役割 |
rst_n |
1 |
リセット信号.不論理 |
clk_i |
1 |
クロック信号 |
tx_en_rtl |
1 |
tx_dataが有効なら1 |
tx_data_rtl |
8 |
出力したい文字1個 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
module dammy_rtl (
input logic rst_n,
input logic clk_i,
output logic tx_en_rtl,
output logic [ 7:0] tx_rtl
);
logic [31:0] cnt;
always_ff @(posedge clk_i) begin
if(!rst_n)begin
cnt <= 32'b0;
tx_en_rtl <= 1'b0;
tx_rtl <= 8'h72;
end else begin
cnt <= (cnt!=32'hFFFFFFFF)?cnt+32'b1:32'b0;
if (cnt>=32'd100000 && cnt<=32'd100020 ) begin
tx_en_rtl <= cnt[0];
tx_rtl <= 8'h41 + {7'h0,cnt[0]};
end else begin
tx_en_rtl <= 1'b0;
tx_rtl <= 8'h43;
end
end
end
endmodule
|
Virtual I/Oの速度に合わせたクロック生成モジュール (clock_gen_for_vio.sv)
Virtual I/Oは10Hz
で値を更新させるので,その速度に合わせたクロック信号を生成するモジュール.
ちなみにArtyもAlveoもベースとなるクロック動作周波数は100MHz
としましたわ.
信号名 |
bit幅[bit] |
役割 |
rst_n |
1 |
リセット信号.不論理 |
clk_i |
1 |
ベースとなるクロック信号(100MHz) |
clk_10hz_o |
1 |
10Hzのクロック信号 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
module clock_gen_for_vio #(
parameter MASTER_CLK_HZ = 100000000
)(
input logic rst_n,
input logic clk_i,
output logic clk_10hz_o
);
localparam CLK_CNT_MAX = (MASTER_CLK_HZ/10)-1;
logic [ 29: 0] cnt;
always_ff @(posedge clk_i) begin
if (!rst_n)begin
cnt <= 30'b0;
clk_10hz_o <= 1'b0;
end else begin
if(cnt==CLK_CNT_MAX)begin
cnt <= 30'b0;
clk_10hz_o <= ~clk_10hz_o;
end else begin
cnt <= cnt + 30'b1;
end
end
end
endmodule
|
Virtual I/Oの出力データを貯めるFIFO (tx_fifo.sv)
入力は100MHzのクロック信号に合わせて入るものとする.
出力はVIOモジュールに合わせて10Hzです.
信号名 |
bit幅[bit] |
役割 |
rst_n |
1 |
リセット信号.不論理 |
clk_i |
1 |
ベースとなるクロック信号(100MHz) |
clk_10hz_i |
1 |
10Hzのクロック信号 |
tx_en_i |
1 |
ダミーのモジュールから入力されたデータが有効か |
tx_i |
8 |
ダミーのモジュールから入力されたデータ |
tx_en_o |
1 |
出力するデータが有効か |
tx_o |
8 |
出力するデータ |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
module tx_fifo (
input logic rst_n,
input logic clk_i,
input logic clk_10hz_i,
input logic tx_en_i,
input logic [ 7:0] tx_i,
output logic tx_en_o,
output logic [ 7:0] tx_o
);
logic [ 7: 0] tx_ring_queue [ 1023: 0];
logic [ 9: 0] queue_head;
logic [ 9: 0] queue_tail;
logic empty;
assign empty = (queue_head == queue_tail)?1'b1:1'b0;
logic full;
assign full = ((queue_tail==10'd0) && ( queue_head== 10'd1023) )?1'b1 :((queue_tail)==(queue_head+10'd1))?1'b1:1'b0;
logic clk_10hz_i_old;
logic posedge_clk_10hz;
assign posedge_clk_10hz = ((clk_10hz_i==1'b1) && (clk_10hz_i_old==1'b0))?1'b1:1'b0;
always_ff @(posedge clk_i) begin
if(!rst_n)begin
queue_head <= 10'b0;
end else if(!full)begin
if(tx_en_i)begin
tx_ring_queue[queue_head] <= tx_i;
queue_head <= (queue_head==10'd1023)?10'b0:(queue_head+10'b1);
end
end
end
always_ff @(posedge clk_i) begin
if(!rst_n)begin
queue_tail <= 10'b0;
tx_en_o <= 1'b0;
tx_o <= 8'b0;
end else if (posedge_clk_10hz) begin
if (empty) begin
tx_en_o <= 1'b0;
tx_o <= 8'b0;
end else begin
tx_en_o <= 1'b1;
tx_o <= tx_ring_queue[queue_tail];
queue_tail <= (queue_tail==10'd1023)?10'b0:(queue_tail+10'b1);
end
end
clk_10hz_i_old <= clk_10hz_i;
end
endmodule
|
Bitstream生成用のTCLファイル
Bitstream生成まで行うTCLファイル(build.tcl)
デザインの作成からbitstream生成まで行うTCLファイルbuild.tcl
はこんな感じ.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
set projectPath [file normalize [file dirname [file dirname [info script]]]]
cd $projectPath
# -----defineファイル追加-------------
source "$projectPath/tcl/define.tcl" ; # defineファイルを追加
# -----ソースファイル追加-------------
set sourceDir "$projectPath/tcl/sources" ; # ソースファイルの置き場所
set source_files [glob -directory $sourceDir -type f "*.tcl"]; # ソースのTCLファイル追加
foreach f $source_files {
source $f
}
# -----元のプロジェクトファイル削除-------------
if {[file exists $outputDir] == 1} { ; # プロジェクトファイルがあれば
file delete -force $outputDir ; # プロジェクトファイルを削除
}
# -----プロジェクトの作成-------------
create_project $projectName $outputDir -part $partName ; # プロジェクトファイル作成
update_ip_catalog ; # IPカタログ更新
# -----vio作成-------------
set vio_module_name "design_1"
set vio_ip_link "xilinx.com:ip:vio:3.0"
make_vio_module $vio_module_name $vio_ip_link 100000000 ; # VIOモジュール作成
# -----clkwiz作成-------------
set clkwiz_module_name "design_2"
set clkwiz_ip_link "xilinx.com:ip:clk_wiz:6.0"
if { $need_clkwiz == 1 } { ; # Clocking Wizardが必要なら
make_clkwiz_module $clkwiz_module_name $clkwiz_ip_link ; # Clocking Wizardを作成
}
# -----RTLファイルの追加-------------
add_files $rtl_directory ; # RTLファイル追加
add_files -fileset constrs_1 $constraint_file ; # xdcファイル追加
# -----トップモジュールの設定-------------
if { $need_clkwiz == 1 } { ; # Clocking Wizardを使うなら
set_property top "top_usb_uart_tx_with_clkwiz" [current_fileset] ; # Clocking Wizardありverのトップモジュールを指定
} else { ; # そうでないなら
set_property top "top_usb_uart_tx" [current_fileset] ; # Clocking Wizardなしverのトップモジュールを指定
}
# -----ファイルセットの更新-------------
update_compile_order -fileset sources_1 ; # 更新
# -----Run Synthesis-------------
launch_runs synth_1 -jobs $howManyThread ; # Synthesis実行開始
wait_on_run synth_1 ; # Synthesis実行終了待ち
# -----Run Implementation-------------
launch_runs impl_1 -jobs $howManyThread ; # Run Implementation開始
wait_on_run impl_1 ; # Run Implementation終了待ち
# -----Generate bitstream-------------
launch_runs impl_1 -to_step write_bitstream -jobs $howManyThread ; # bitstream生成開始
wait_on_run impl_1 ; # bitstream終了待ち
# -----プロジェクトを閉じる-------------
close_project
|
色々定義したTCLファイル(define.tcl)
ここを変えればある程度?ボード変えてもいい??本当か???(他のボードで試してないからわからん)
1
2
3
4
5
6
7
8
9
10
11
12
|
# -----変数-------------
set howManyThread 2 ;# 実行時のスレッド数
set outputDir "$projectPath/output" ;# プロジェクトの出力先
set projectName "vio_show_project" ;# プロジェクト名
set rtl_directory "$projectPath/rtls" ;# RTLファイルのディレクトリ
# set constraint_file "$projectPath/consts/Arty-S7-50-Master.xdc" ; # xdcファイルの設定/
set constraint_file "$projectPath/consts/alveo-u250-xdc.xdc" ; # xdcファイルの設定/
# set partName "xc7s50csga324-1" ;# パーツ名
set partName "xcu250-figd2104-2L-e" ;# パーツ名
set hw_device_name "xc7s50_0" ;# デバイス名
set logfile "./output/vio.log" ;# ログファイルの保存先
set need_clkwiz 1 ;# Clocking Wizardが必要なら1に
|
VIOモジュール作成用のTCLファイル(make_vio_design.tcl)
これでできるモジュールの入出力は次の通り.
信号名 |
bit幅[bit] |
役割 |
rst_n |
1 |
(出力)リセット信号.不論理 |
clk_i |
1 |
(入力)ベースとなるクロック信号(100MHz) |
tx_en_i |
1 |
(入力)ダミーのモジュールから入力されたデータが有効か |
tx_i |
8 |
(入力)ダミーのモジュールから入力されたデータ |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
proc make_vio_module { { vio_module_name } { vio_ip_link } { clock_speed_hz } } {
create_bd_design $vio_module_name
startgroup
create_bd_cell -type ip -vlnv $vio_ip_link vio_0
endgroup
set_property -dict [list \
CONFIG.C_NUM_PROBE_IN {2} \
CONFIG.C_PROBE_IN1_WIDTH {8} \
] [get_bd_cells vio_0]
create_bd_port -dir I -type clk -freq_hz $clock_speed_hz clk_i
create_bd_port -dir O rst_n
create_bd_port -dir I tx_en
create_bd_port -dir I -from 7 -to 0 tx
connect_bd_net [get_bd_ports clk_i] [get_bd_pins vio_0/clk]
connect_bd_net [get_bd_ports tx_en] [get_bd_pins vio_0/probe_in0]
connect_bd_net [get_bd_ports tx] [get_bd_pins vio_0/probe_in1]
connect_bd_net [get_bd_ports rst_n] [get_bd_pins vio_0/probe_out0]
save_bd_design
}
|
ClockingWizardモジュール作成用のTCLファイル(make_clocking_wizard_design.tcl)
これでできるモジュールの入出力は次の通り.
信号名 |
bit幅[bit] |
役割 |
SYSCLK0_300_P |
1 |
(入力)作動クロック信号のP |
SYSCLK0_300_N |
1 |
(入力)作動クロック信号のN |
rst_n |
1 |
(入力)リセット信号.不論理 |
CLK100MHZ |
1 |
(出力)ベースとなるクロック信号(100MHz) |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
proc make_clkwiz_module { { clkwiz_module_name } { clkwiz_ip_link }} {
create_bd_design $clkwiz_module_name
create_bd_cell -type ip -vlnv $clkwiz_ip_link clk_wiz_0
set_property -dict [list CONFIG.PRIMITIVE {MMCM} CONFIG.CLK_IN1_BOARD_INTERFACE {default_300mhz_clk0} CONFIG.USE_LOCKED {false} CONFIG.USE_RESET {false} CONFIG.PRIM_SOURCE {Differential_clock_capable_pin} CONFIG.CLKIN1_JITTER_PS {33.330000000000005} CONFIG.CLKOUT1_DRIVES {Buffer} CONFIG.CLKOUT2_DRIVES {Buffer} CONFIG.CLKOUT3_DRIVES {Buffer} CONFIG.CLKOUT4_DRIVES {Buffer} CONFIG.CLKOUT5_DRIVES {Buffer} CONFIG.CLKOUT6_DRIVES {Buffer} CONFIG.CLKOUT7_DRIVES {Buffer} CONFIG.FEEDBACK_SOURCE {FDBK_AUTO} CONFIG.MMCM_BANDWIDTH {OPTIMIZED} CONFIG.MMCM_CLKFBOUT_MULT_F {4.000} CONFIG.MMCM_CLKIN1_PERIOD {3.333} CONFIG.MMCM_CLKIN2_PERIOD {10.0} CONFIG.MMCM_COMPENSATION {AUTO} CONFIG.MMCM_CLKOUT0_DIVIDE_F {12.000} CONFIG.CLKOUT1_JITTER {101.475} CONFIG.CLKOUT1_PHASE_ERROR {77.836}] [get_bd_cells clk_wiz_0]
set_property -dict [list \
CONFIG.CLKOUT1_JITTER {101.475} \
CONFIG.CLKOUT1_PHASE_ERROR {77.836} \
CONFIG.CLKOUT1_REQUESTED_OUT_FREQ {100} \
CONFIG.MMCM_CLKFBOUT_MULT_F {4.000} \
CONFIG.MMCM_CLKOUT0_DIVIDE_F {12.000} \
CONFIG.MMCM_DIVCLK_DIVIDE {1} \
] [get_bd_cells clk_wiz_0]
# -----入力部分の接続--------------------------------
create_bd_port -dir I SYSCLK0_300_N
create_bd_port -dir I SYSCLK0_300_P
connect_bd_net [get_bd_ports SYSCLK0_300_N] [get_bd_pins clk_wiz_0/clk_in1_n]
connect_bd_net [get_bd_ports SYSCLK0_300_P] [get_bd_pins clk_wiz_0/clk_in1_p]
# -----出力部分の接続--------------------------------
create_bd_port -dir O CLK100MHZ
connect_bd_net [get_bd_ports CLK100MHZ] [get_bd_pins clk_wiz_0/clk_out1]
# -----保存--------------------------------
save_bd_design
}
|
FPGA上のVIO(出力)を表示させるTCLファイル
やりたいこと
作成したbitstreamファイルをFPGAに書き込んだのち,行いたいのは以下の2つ
- 最初にrst_nを1→0→1とリセットをかけたい
- ターミナルやファイルにtx_dataを表示したい
実際のコードshow_vio.tcl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
set projectPath [file normalize [file dirname [file dirname [info script]]]]
cd $projectPath
#############################################
# defineファイル追加
#############################################
source "$projectPath/tcl/define.tcl"
#############################################
# 保存先ファイルの初期化
#############################################
set log [open $logfile w] ;#① 保存先ファイルをopen
puts -nonewline $log "" ;#② 中身を削除
close $log ;#③ 保存先ファイルをclose
#############################################
# ボーレートの設定
# $delay_time[ms]ごとに計測
#############################################
set_property CORE_REFRESH_RATE_MS 100 [get_hw_vios [get_hw_vios -of_objects [get_hw_devices $hw_device_name] -filter {CELL_NAME=~"vio/vio_0"}]]
#############################################
# リセット信号(vio_0_probe_out0)の出力
# 1→0→1 となる
#############################################
# 1を出力
set_property OUTPUT_VALUE 1 [get_hw_probes vio/vio_0_probe_out0 -of_objects [get_hw_vios -of_objects [get_hw_devices $hw_device_name] -filter {CELL_NAME=~"vio/vio_0"}]]
commit_hw_vio [get_hw_probes {vio/vio_0_probe_out0} -of_objects [get_hw_vios -of_objects [get_hw_devices $hw_device_name] -filter {CELL_NAME=~"vio/vio_0"}]]
after 1000
# 0を出力
set_property OUTPUT_VALUE 0 [get_hw_probes vio/vio_0_probe_out0 -of_objects [get_hw_vios -of_objects [get_hw_devices $hw_device_name] -filter {CELL_NAME=~"vio/vio_0"}]]
commit_hw_vio [get_hw_probes {vio/vio_0_probe_out0} -of_objects [get_hw_vios -of_objects [get_hw_devices $hw_device_name] -filter {CELL_NAME=~"vio/vio_0"}]]
after 1000
# 1を出力
set_property OUTPUT_VALUE 1 [get_hw_probes vio/vio_0_probe_out0 -of_objects [get_hw_vios -of_objects [get_hw_devices $hw_device_name] -filter {CELL_NAME=~"vio/vio_0"}]]
commit_hw_vio [get_hw_probes {vio/vio_0_probe_out0} -of_objects [get_hw_vios -of_objects [get_hw_devices $hw_device_name] -filter {CELL_NAME=~"vio/vio_0"}]]
#############################################
# 中断するまで表示を繰り返す
# Canselを押して中断
#############################################
while {1} {
set tx_value_num [get_property INPUT_VALUE [get_hw_probes vio/tx_1] ] ;#RTLから受け取った表示したい文字データ(数値)
set tx_value [format %c $tx_value_num] ;#数値を文字に変換
set tx_en_value [get_property INPUT_VALUE [get_hw_probes vio/tx_en_1] ] ;#RTLから受け取った表示したい文字データのenable
if {$tx_en_value == 1} then { ;# 表示したい文字があれば
puts -nonewline $tx_value ;# TCLコンソールに表示
set log [open $logfile a] ;# 保存先ファイルをopen
puts -nonewline $log $tx_value ;# 保存先ファイルに出力
close $log ;# 保存先ファイルをclose
}
after 100 ;# 100[ms]停止
}
|
結果
〜bitstream生成
ターミナルで実行しましょ.
1
|
vivado -mode batch -source tcl/build.tcl
|
実行後lsするとoutput
ってディレクトリができますわよ.
vivadoを立ち上げて書き込み
Open Hardware Manager
からOpen Target
のAuto Connect
して,Program Device
で書き込み.
書き込んだFPGAと通信
Vivadoのtclコンソールで実行しましょ.
1
|
source tcl/show_vio.tcl
|
その結果.Vivadoはこんな画面になる.
下の方にBDFHJLNPRT
と表示されてればOK
Cansel
を押せば停止できますわ.

output/vio.log
の中身はこんな感じになる.

まとめ
AlveoU250でシリアル通信ができなかった.しかしまあVirtual I/Oを使って10Hzの速度で出力を文字列で確認できるようにした.
今後このモジュールを使わなくて済むことを祈っている.