Posted at

Python で FPGA/UART を使う(Zybo 編)


UART を使う

Linux の UART ドライバと通信しようとおもいます。そのためにはどういうわけか u-boot から作り直さないといけないらしい。


先にデザイン

ざっくりとしたデザインを先に示します。async_trasnmitter というモジュールを使っています。これは verilog で書かれたもので python ではない。fpga4fun.com から拝借しています。

async.v

image.png

async_transmitter はダブルクリックで paramter を変更できます。基準のクロックが 125MHz なので初期設定の 50MHz から変更しておきます。

image.png


合成して実験

vio から 0x31 を送信する。あ、Linux が立ち上がったが気にしない。design_1_i/vio_0_probe_out0 に 1 を書いた瞬間に動き出します。

image.png

結果は 1 だらけになってとまりません。コントロールしてないから。

image.png


Zynq 側の UART をつかうと

参考までに書くと Zynq 側の UART0 を有効にする dtb を書いてみるとうまく Linux が動きません。当たり前ですが、u-boot で行われる初期化と Linux の初期化が不一致だと最悪、立ち上がらないようです。

なので上のデザインは PMOD から UART をだして USB-UART 経由で PC(Windows) と通信してます。


受信側追加

1の無限送信じゃ面白くないので echo バックさせる回路にします。

image.png

受信側も 125MHz にするパラメタ設定をします。そうしないと文字化けしますよ(経験者は語る)。

image.png

ピンの設定もつけておきましょう。


zybo.xdc

## PL System Clock

set_property -dict {PACKAGE_PIN L16 IOSTANDARD LVCMOS33} [get_ports clk]
create_clock -period 8.000 -name sys_clk_pin -waveform {0.000 4.000} -add [get_ports clk]

## LED
set_property -dict {PACKAGE_PIN M14 IOSTANDARD LVCMOS33} [get_ports led0]

## Buttons
set_property -dict {PACKAGE_PIN R18 IOSTANDARD LVCMOS33} [get_ports btn0]

set_property -dict {PACKAGE_PIN V12 IOSTANDARD LVCMOS33} [get_ports TxD_0]
set_property -dict {PACKAGE_PIN W16 IOSTANDARD LVCMOS33} [get_ports RxD_0]


うまく動きました。 Hello Hello と書いてあるのがエコーバックされた文字。そのまえの 1の羅列がひとつ前の処理。そして部分的に文字化けしているのが失敗した処理。

image.png


Polyphony を使う

ここまでは Polyphony つかってません。はい。使ってませんでした。フィルターとして大文字を小文字に小文字を大文字にするモジュールを作ります。

image.png


letter_x.py

import polyphony

from polyphony import is_worker_running
from polyphony.io import Port, Queue
from polyphony.typing import bit, bit8
from polyphony.timing import clksleep, clkfence, wait_value

@polyphony.module
class letter_x:
def __init__(self):
self.tx_start = Port(bit, 'out', 0)
self.tx_data = Port(bit8, 'out', 0)
self.tx_busy = Port(bit, 'in')

self.rx_ready = Port(bit, 'in')
self.rx_data = Port(bit8, 'in')

self.append_worker(self.main_worker)

def main_worker(self):
while is_worker_running():
wait_value(1, self.rx_ready)
data = self.rx_data.rd()

if ( data > 0x40 ) & ( data < 0x60 ) :
data += 0x20
elif ( data > 0x60 ) & ( data < 0x80 ) :
data -= 0x20

wait_value(0, self.tx_busy)
self.tx_data.wr(data)
self.tx_start.wr(1)

clkfence()
self.tx_start.wr(0)
wait_value(0, self.tx_busy)

print(data)

@polyphony.testbench
def test(obj):
data_list = (0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x21)
obj.tx_busy.wr(0)
obj.rx_ready.wr(0)

for iter in data_list:
obj.rx_data.wr(iter)
obj.rx_ready.wr(1)
clkfence()

obj.rx_ready.wr(0)
wait_value(1, obj.tx_start)
data = obj.tx_data.rd()
print(data)

if __name__ == '__main__':
obj = letter_x()
test(obj)


コンパイルして Vivado に埋め込み合成実行してみます(駆け足ですいません)。

image.png

分かりずらいですが Hello Good-Bye がエコーバックされて hELLO gOOD-bYE になりました。


ついでだから関数化する

worker だの clkfence だのが出てきてわかりづらいので、関数化/ライブラリ化してしまいます。


filter.py

 cat filter.py

def filter_func(data):
if ( data > 0x40 ) & ( data < 0x60 ) :
data += 0x20
elif ( data > 0x60 ) & ( data < 0x80 ) :
data -= 0x20

return data


使う側では import したうえで filter_func を呼べばいいだけです。これでややこしい部分は外に出せました。


使う側

from filter import filter_func

<>
def main_worker(self):
while is_worker_running():
wait_value(1, self.rx_ready)
data = self.rx_data.rd()

data = filter_func(data)

wait_value(0, self.tx_busy)
self.tx_data.wr(data)
self.tx_start.wr(1)

clkfence()
self.tx_start.wr(0)
wait_value(0, self.tx_busy)

print(data)