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

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

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

[async.v](https://github.com/marsohod4you/btc-fpga-miner/blob/master/async.v)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/99921/e6b94233-4f6d-4874-cd1f-5beb54a00dc9.png)

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/99921/8ef134aa-ca6b-d737-3483-88ac4766a534.png)


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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/99921/d68143a9-daad-5bad-6f7d-642ed18c917a.png)

結果は 1 だらけになってとまりません。コントロールしてないから。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/99921/67267e9c-7ce1-d523-d188-8563753f7096.png)

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

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

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/99921/c08509b0-ffbf-86aa-e588-5ba678669fe3.png)

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/99921/670294bc-f836-1219-b664-10b387eaf576.png)

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

```tcl: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](https://qiita-image-store.s3.amazonaws.com/0/99921/d66fc90b-a0d7-631d-75eb-26e2816a2265.png)

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/99921/f1da1fc4-f31f-5417-ba1e-37c87555c383.png)

```python: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](https://qiita-image-store.s3.amazonaws.com/0/99921/bb08863e-23ae-bfe2-1687-1d767c692112.png)

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

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

```python: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 を呼べばいいだけです。これでややこしい部分は外に出せました。

```python:使う側
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)

略