---
# System prepended metadata

title: Zedboard + AD9361 Tutorial

---

# Zedboard + AD9361 Tutorial

# Introduction

## FPGA
現場可程式化邏輯閘陣列(Field **Programmable** Gate Array, FPGA)

一種半客製化電路，可以用Verilog等硬體描述語言，在邏輯合成(logic synthesis)、布局(placement)、布線(routing)的工具軟體(Ex.Vivado)上，快速地燒錄至FPGA上進行測試。

可程式化邏輯元件可以被用來實現基本的邏輯閘數字電路，或者更複雜一些的組合邏輯功能。這些可編輯的元件裡通常也包含記憶元件，從而構成序向邏輯電路──任何時刻的穩態輸出取決於當前的輸入，並與前一時刻輸入形成的狀態有關。

使用者可以通過可編輯的連接，把FPGA內部的邏輯塊連接起來，所以FPGA可以完成使用者所需要的邏輯功能。

基於內部邏輯可以被使用者反覆修改的特性，FPGA的電晶體製程無法太小，因此效能與大小會比不上特定應用積體電路(Application Specific Integrated Circuit, **ASIC**)。但是，FPGA可以快速成品，可以反覆修改並除錯，所以方便設計與開發，然後將設計轉移到類似於專用積體電路的晶片上。

[[Reference](https://zh.wikipedia.org/wiki/%E7%8E%B0%E5%9C%BA%E5%8F%AF%E7%BC%96%E7%A8%8B%E9%80%BB%E8%BE%91%E9%97%A8%E9%98%B5%E5%88%97)]

---
## Vivado
可以用來配置FPGA的程式軟體，內部有許多已經完成的module可以直接使用。
使用Verilog。
![](https://i.imgur.com/pgN5Re5.png)

### Verilog
硬體描述語言(**HDL**)，用文字描述電路的語言。
原則上以C語言為基礎，可使用下面的連結做練習。
[Verilog練習題](https://hdlbits.01xz.net/wiki/Problem_sets#Verilog_Language)

### Creat Block design

以圖像化的方式表現出對硬體的設計，可以使用內建的module並直接從PIN角接線做設計。
可以用add source從外部加入已經寫好的module。
雙擊module可以查看及更改該module的屬性及參數設定。
1. Run Block Automation
    對已存在的module接出預設的輸出。
2. Run Connection Automation
    對已存在的多個module做自動接線。
    自己做的IP沒有辦法用。

### Creat HDL wrapper
把Block design中的所有設計整合為Verilog語言。

### Run Synthesis
邏輯合成，將HDL轉換到的邏輯閘級別的電路連線網表的過程。
簡單來說是將HDL Wrapper轉換成電路懂的樣子。
可以在 **Windows $\to$ I/O ports** 中直接設定(選擇)Constraints。

### Run Implementation
整合所有設定好的Constraints。

### Creat Bigstream
製作燒錄用的檔案 .bit檔。

### program device
將 .bit檔燒錄至FPGA上。

## ZYNQ
ZYNQ-7000 SoC
![](https://i.imgur.com/Uqdyouu.png)
[[Reference](https://www.xilinx.com/products/silicon-devices/soc/zynq-7000.html)]
![](https://i.imgur.com/CeYkcYV.png)
[[Reference](https://www.mouser.tw/new/xilinx/xilinx-zynq-7000-socs/)]
先進的FPGA系統單晶片(SoC)裝置，內建可編程邏輯(Programmable Logic, PL)結構結合Arm處理器，相當於FPGA+CPU的配置，以及如圖444,000個的邏輯元件、2,020個數位訊號處理(DSP)區塊或稱(slice)。

藉由處理器與PL結構的整合，整合式處理器可執行傳統的微控制器工作，包括管理PL結構和其他晶片上周邊裝置。因此，FPGA載入流程更貼近於傳統微控制器的開機流程。憑藉PYNQ環境，ZYNQ SoC有效簡化了複雜的FPGA開發流程。

[[Reference](https://www.digikey.tw/zh/articles/build-and-program-fpga-based-designs-quickly-python-jupyter-notebooks)]

## Jupyter
旨在「為數十種程式語言的互動式計算開發開源軟體，開放標準和服務」
Jupyter由Notebook Fronted, Jupyter Server及Kernel Protocol組成，如圖所示：
![](https://i.imgur.com/9jGtyh0.png)
主要包含兩項功能：
1. 網頁應用(Web Application)
    基於瀏覽器(web-based)的互動創作及應用工具，包括可以計算、數學、文檔創作及豐富的多媒體輸出。
2. 文檔顯示(Notebook Documents)
    顯示所有在上述Web Application當中的內容，包括計算的輸入/輸出、文件說明/解釋、數學運算及式子、圖片及所有豐富多媒體內容。 

[[Reference 1](https://zh.wikipedia.org/wiki/Jupyter)]
[[Reference 2](ttps://medium.com/python4u/jupyter-notebook%E5%AE%8C%E6%95%B4%E4%BB%8B%E7%B4%B9%E5%8F%8A%E5%AE%89%E8%A3%9D%E8%AA%AA%E6%98%8E-b8fcadba15f)]

## PYNQ
Python+ZYNQ
PYNQ是一套可以部屬Jupyter的linux平台，使用Python語言。
![](https://i.imgur.com/AaE3O5P.png)
[[Reference](http://www.pynq.io/)]

在PYNQ中，PL位元流會囊括到預先建構的函式庫中，稱為「重疊」。使用者透過與每個重疊相關聯的Python應用程式開發介面(API)來利用「重疊」功能。在開發過程中，使用者可以依照需求合併軟體函式庫與「重疊」，並透過自己的API來實作應用。在執行期間，處理器系統會照常執行軟體函式庫的程式碼，同時 PL結構會實作「重疊」所提供的功能。

此外，Python有龐大且持續發展的生態系統。使用者有機會在開放原始碼Python模組的儲存庫中找到支援服務或專用演算法所需的軟體函式庫。同時，由於PYNQ使用通用的C語言實作Python解譯器，所以使用者也能以C語言實作關鍵功能。
 [[Reference](https://www.digikey.tw/zh/articles/build-and-program-fpga-based-designs-quickly-python-jupyter-notebooks)]
 
---
# Programming Example: LED

## Block Design
![](https://i.imgur.com/b3zDayc.png)

### ZYNQ7 Processing System
PS端
Run Block Automation接上輸出至DDR與fixed IO (MIO與DDR, CLK等)。

### Processor System Reset
提供reset訊號
Run Block Automation會從外部接入一reset訊號，不過因為**ZYNQ7 Process System**已經有reset訊號了，所以改成由**ZYNQ7 Process System**輸出。

>老師：之前有直接用**ZYNQ7 Process System**輸出的reset去接硬體，但是好像有問題，所以才額外用**Processor System Reset**。

同時，把CLK連起來。

### Counter AXI
用Add source從**NCU_SDR_Verilog_LIB_2022**資料夾加入已經寫好的**counter**。
CLK接線，然後連上reset和enable。
要除頻，所以把output的寬度改成26bits。
把tvalid接上tready，valid馬上就ready，然後只要enable high就會valid，達到real time的效果(每個CLK都跑的意思)。

>這裡ZYNQ7 Process System的CLK被給定為100MHz，如果counter沒辦法跑到100MHz的話，會有錯誤產生。

### Slice
取出一串資料的其中某些值。
Counter的輸出為Bus，要接上**slice**才能取出用來控制LED燈的值。
設定上Data寬度是26，取最後一個bit。

    Din Width: 26, Din from: 25, Din down to: 25, Dout width: 1

## Constraints
1. Add constraints
於**Add source**的選項中選擇**Add constraints**，通常匯入檔案，也可以自己打。
![](https://i.imgur.com/KofB3Ij.png)
    
    ```set_property -dict {PACKAGE_PIN T23  IOSTANDARD LVCMOS} [get_ports led_0]:```
set_property -dict {PIN腳位資訊} [get_ports 變數名稱]

2. I/O ports
執行 **Creat HDL Wrapper $\to$ Run Synthesis** 後，在上方 **Window $\to$ I/O Ports** 打開，會位於視窗下方。
這裡也可以設定PIN腳位 (紅色框框中可以選擇)。
![](https://i.imgur.com/H7gY4NG.png)

## Synthesis

可以看到現在電路板被劃分出來使用的區域。
![](https://i.imgur.com/tGrA5Bw.png)
在這個例子當中，我們使用LED的T22腳位，所以可以看到T22已經被劃分出來了。

## Implement
![](https://i.imgur.com/qFujuf4.png)

可以看到為了使設計運作，畫出了FPGA中的設計。
可以把FPGA想像為一堆開關的集合，可以去配置這些開關使其達到使用者的設計。
現在亮起來的部分就是開關，也可以理解成這次的設計中所用到的資源。

## Program
Run Bigstream製作燒錄用的檔案.bit檔，檔案會在 **project $\to$ runs $\to$ implement** 裡面。
1. 用Vivado的Program Device(用USB直接連接J17-PROG腳位)
用 **Hardware Manager $\to$ Open target $\to$ Auto connect** 讓電腦連線到實體的板子(Zedboard)，連線到的話如下圖：
![](https://i.imgur.com/Rsdp1u5.png)
接著**Program device**，系統會自動找到.bit檔，Program後會看到T22的LED開始閃爍，如下圖：
![](https://i.imgur.com/53FCj1N.jpg)

2. 用Jupyter
找到**project $\to$ runs $\to$ implement** 的.bit檔。
找到**srcs $\to$ source $\to$ block board $\to$ design $\to$ hardware** 的.hwh檔。
將兩個檔案改成相同的檔名丟入同一個資料夾。
![](https://i.imgur.com/W8G7sDe.png)
在**New**中執行**Python3**。
用**Overlay**同樣可以執行燒錄。
![](https://i.imgur.com/nqwTYPD.png)

    ```
    from pynq import Overlay
    import time
    import numpy as np
    from pynq.lib import AxiGPIO
    ol = Overlay("./design_2022_04_12.bit")
    ol?
    ```

## Switch
可以設計一個switch控制LED燈，重複上述過程，就可以用開關來控制LED燈了。
![](https://i.imgur.com/BCrHTj7.png)
![](https://i.imgur.com/26k3Ggi.png)
![](https://i.imgur.com/wHAli3g.png)

---
# DMA
![](https://i.imgur.com/8WX2N1l.png)
[Reference](https://welkinchen.pixnet.net/blog/post/44175020-%E9%A9%85%E5%8B%95%E7%A8%8B%E5%BC%8F%E4%B8%AD%E7%9A%84dma%E5%95%8F%E9%A1%8C-)
DMA(Direct Memory Access)是一項技術。
DMA允許I/O device在不經過CPU下，從主要記憶體(RAM)傳送或接收資料。
傳統上，外部存取記憶體需要透過CPU作中繼才能進入記憶體。
>傳統上：**CPU收到存儲Interrupt$\to$將資料複製進cache$\to$寫入新位置**。

但是這些存取要求會對CPU造成負擔，因為：
1. 外部設備的操作時脈可能異於CPU
2. CPU本身不用介入傳送，無須取指令、取值、儲存等動作
3. Interrupt令CPU需要停下當前動作

[Reference](https://hackmd.io/@Pieapplej/S1jWKKgN5#DMA%E7%B0%A1%E4%BB%8B)

簡而言之，DMA可以繞過CPU的指令直接搬運資料。
為了使搬運資料的過程不出錯，必需要知道：
1. 原始資料的位置
2. 目的地的位置
3. 搬運的資料長度

所以對於DMA來說，重點只有三個：兩個位置資料與一個資料長度。
[Reference](http://sharing-icdesign-experience.blogspot.com/2014/05/dma.html)

## Design
![](https://i.imgur.com/L8Swtum.png)
![](https://i.imgur.com/lgygNxc.png)
本次的目的要看DMA自傳自收以檢測結果。
>DMA自傳自收：DMA將內存的數據傳到IP中，再從IP傳輸回內存。

這裡使用**FIFO**作為暫存的IP。
設計上，用AXI-lite總線來配置AXI DMA。
**MM2S**: Memory Mapped to Stream，從DDR記憶體抓資料出來。
**S2MM**: Stream to Memory Mapped，把資料傳進DDR記憶體中。
**AXI**負責內存(DDR記憶體)和AXI DMA間的通信。
**AXIS**負責FIFO和DMA間的通信，一般來說有方向沒有地址，上游對下游的通信。
[Reference](https://www.twblogs.net/a/5e65406fbd9eee2117c6fd0f)

## IPs
1. ZYNQ7 Processing System
2. AXI GPIO (GPIO: leds 8bits) (GPIO2: sws 8bits)
3. AXI Direct Memory Access
4. AXI Stream Data FIFO (Or FIR)

## ZYNQ7 Processing System
PS端
Run Block Automation接上輸出至DDR與fixed IO (MIO與DDR, CLK等)。
因為DMA的M_AXI_MM2S和M_AXI_S2MM要連到PS端上的Slave接口，所以在這裡要開啟Slave的Port。
![](https://i.imgur.com/FiwZftC.png)

## AXI GPIO
![](https://i.imgur.com/gBeNZVc.png)
GPIO(General-purpose input/output)可以被視為一個開關
輸入端有AXI, CLK和reset，皆為控制GPIO用，輸出端的結果與CLK無關
這裡的GPIO輸出可以直接選擇輸出為預設的選項或是自己設定，這裡選擇為預設的leds 8bits。
若打開Synthesized Desigen，可以看到作為LED燈的8的部分已經被劃分出來了。
![](https://i.imgur.com/kLggHrd.png)

## AXI Interconnect
![](https://i.imgur.com/fGtSDPG.png)
![](https://i.imgur.com/bgraOoT.png)
### AXI
AXI（Advanced eXtensible Interface）匯流排。
AXI總線分成**Master主機**和**Slave從機**兩端，常見的主機有CPU和DMA，從機則為DDR等等。
>Master就是Valid。
>Write address: Master。
>Read address: Master。
>Read Data Channel: Slave。

主機可以通過從機讀取或寫入儲存介質，但是從機無法主動向主機寫入數據。
AXI4有五個獨立通道：讀地址AR、讀數據R、寫地址AW、寫數據W、寫回復R。
Write transcation: 
![](https://i.imgur.com/5NWLDvu.png)
Read transcation: 
![](https://i.imgur.com/hzUIDin.png)
[Reference](https://zhuanlan.zhihu.com/p/45122977)

### Interconnect
因為AXI總線的寬度和**GPIO**的輸入寬度不同，這裡加入一個**AXI Interconnect**提供**GPIO**的輸入用。
再加入DMA之後，**Interconnect**同時提供**GPIO**和**DMA**用。
這裡的**Interconnect**為Crossbar，1個Slave接2個Master。
**Interconnect**的意涵為處理總線互聯，意即多Master與多Slave的狀況
基本的互連結構有三種：
1. 共享地址和數據總線
2. 共享地址總線，多個數據總線
3. 多個地址總線，多個數據總線

換句話說，一個(或多個)主機有Master控制器和一個(或多個)從機有Slave控制器之間，以**Interconnect**為橋梁制定一套互聯的規矩(數據、地址總線等)。
**AXI Interconnect**允許任意AXI主機和AXI從機的連接，根據數據寬度、CLK和AXI Sub-protocol進行轉換，當外部主機或從機的接口特性不同於互聯模塊內部的crossbar switch的接口特性時，相應的基本模塊(Infrastructure cores)就會被自動的引入來執行正確的轉換。
基本功能有：
1. AXI Crossbar:
Master和Slave互聯。
2. AXI Width Converter:
Master連接寬度不同的Slave。
3. AXI Clock Converter:
Master連接時鐘域不同的Slave。
4. AXI Protocol Converter:
Master連接不同協議(AXI3, AXI4, AXI-Lite, ...)的Slave。
5. AXI FIFO:
Master和Slave間增加一組FIFO。
6. AXI Register Slice:
Master和Slave間增加一組Instruction Pipeline。
7. AXI MMU:
提供AXI地址的decoding和remapping。
[Reference](https://www.twblogs.net/a/5e52cc23bd9eee2117c30ef6)

## AXI DMA
![](https://i.imgur.com/7JVbgfK.png)
**S_AXI_LITE**: 由PS端傳過來，用來配置、設計DMA用。
**Read channel**: 從DDR記憶體讀資料出來，MM2S。
**Write channel**: 寫到DDR記憶體裡面，S2MM。
因為要自傳自收，所以兩者都enable。
資料由DDR記憶體從**M_AXI_MM2S**進到DMA，然後從**M_AXIS_MM2S**出來送往FIFO。
>**MM2S**就有設定Source的位置。
>**M_AXI_MM2S**: Read address，**M_AXIS_MM2S**: Write

資料由FIFO從**S_AXIS_S2MM**進到DMA，然後從**M_AXI_S2MM**出來送往DDR記憶體。
>**S2MM**就有設定destination的位置。
>**S_AXIS_S2MM**: Read Data，**M_AXI_S2MM**: Write address

## AXI Smart Connect
![](https://i.imgur.com/wcpVRSk.png)
因為從DDR記憶體讀進來丟到Stream，然後Stream又要寫回來到DDR記憶體，都是走同一個Port。
**AXI Smart Connect**會把要接到同一個Port的東西安排好(輪流OR之類的)
這裡把2個Slave接1個Master。

## AXI4 Data Stream FIFO
![](https://i.imgur.com/33pePNM.png)
FIFO(First In, First Out)先進先出法。
演算法，先進佇列的工作會先被完成，後到的要稍後。
當面對**不同時鐘域**或**不同寬度**間的資料傳輸時，可使用FIFO作為資料緩衝。
可以做為資料暫存的地方。
從**S_AXIS**接收DMA的**M_AXIS_MM2S**資料，來源由**DMA**的**M_AXI_MM2S**給定。
自傳自收，所以**M_AXIS**的輸出要接回DMA的**S_AXIS_S2MM**，做為資料輸入。
>在這裡FIFO會被寫做一個Processing System。
>從DDR來的資料，經過一段處理之後，再寫回另外的Memory address。

## Jupyter
### Program
使用Jupyter進行燒錄與執行。
![](https://i.imgur.com/vrsvORB.png)
```.
from pynq import Overlay
import time
import numpy as np
from pynq.lib import AxiGPIO

ol = Overlay("./design_2022_04_12.bit")
led_ip = ol.ip_dict['axi_gpio_LED']
leds = AxiGPIO(led_ip).channel1
mask = 0xffffffff
leds.write(0xee, mask)
ol? 
```
其中```ol```為Overlay的變數名稱，可以隨意更改。
```led_ip = ol.ip_dict['axi_gpio_LED']```中的dict要依照Block Diagram中定義的名稱操作。
可以用```ol?```來確認燒錄是否完成。
同樣也可以看Zedboard板子上的LED燈是否亮起，判斷燒錄是否完成。
>```leds.write(0xee, mask)```所寫的為```8'hee=8'd238=8'b11101110```，所以亮燈應為11101110

![](https://i.imgur.com/xCZs5zO.jpg)
### Configure

然後就可以寫DMA的code來啟動DMA。
這裡展現的PYNQ的優點，可以程式的與硬體互動，測試硬體。
Python的優點在跑完後狀態會留著，有修改的話不用全部重新跑。
```.
import pynq.lib.dma
from pynq import Xlnk
import numpy as np

xlnk = Xlnk()
dma = ol.axi_dma_0

input_buffer = xlnk.cma_array(shape=(5,), dtype=np.uint32)
output_buffer = xlnk.cma_array(shape=(5,), dtype=np.uint32)
for i in range(5):
    input_buffer[i] = i + 77
print(input_buffer)    
dma.sendchannel.transfer(input_buffer)
dma.recvchannel.transfer(output_buffer)
dma.sendchannel.wait()
dma.recvchannel.wait() 
print(output_buffer)
```
>```xlnk = Xlnk()```表示取得物件。
>```xlnk.cma_array```是在allocate DMA，下面```input_buffer[i] = i + 77```輸入值。
>```dma.sendchannel.transfer(input_buffer)```的```send```是指Memory 到 Memory。
>```dma.recvchannel.transfer(output_buffer)```的```recv```是指Stream 到 Memory。

輸入從input_buffer丟到Hardware，由MM2S進到DMA。
變成M_AXIS接到FIFO的S_AXIS，再由FIFO的M_AXIS回到DMA的S_AXIS。
然後轉到S2MM，最後回到output_buffer變成輸出。

>```dma.sendchannel.wait()```和```dma.recvchannel.wait()```的```wait```表示等待。
>Hardware的assign都是non-blocking ── 同步執行。
>加上```wait```後變為blocking，須執行完上一行才能進入下一行。
>要等傳完資料才能接收資料的意思。

執行後的結果為下圖所示：
![](https://i.imgur.com/7nym4oZ.png)
>如果把```dma.sendchannel.wait()```和```dma.recvchannel.wait()```注解掉的話，在這個case裡面，DMA可以照常運作。
![](https://i.imgur.com/bIHunMs.png)

放```print(output_buffer)```到```dma.recvchannel.transfer(output_buffer)```上面，出來的結果應為```[0 0 0 0 0]```，表示還未輸出，證明確實為自傳自收。
![](https://i.imgur.com/J2rVAer.png)

## FIR
可以把前一次設計中的FIFO換成FIR。
![](https://i.imgur.com/5FbpuHL.png)
FIR(Finite Impulse Response)有限脈衝響應是因果系統的移動平均濾波器。
所謂的濾波器，就是藉由排除掉輸入訊號中的某些頻率項，使輸出訊號變「好」 。
>例如：輸入有雜訊的弦波，在低通濾波器作用下，可以得到比較單純的弦波。

因果系統(Casual system)：輸出只與過去或目前的訊號有關。
移動平均濾波器：輸出的值為過去與現在的值取加權平均的結果。
FIR的設計為第$n$個輸出為$M$個訊號與加權值$b_k$的和：
$y[n]=\sum^M_0b_kx[n-k]$ 
不同於IIR(infinite impulse response)無限脈衝響應。
IIR包含了前面的值與前面的濾波器的值(回授)，所以IIR會考慮到前面所有的東西。
    
>   基於運作原理，FIR要等到$M+1$個訊號產生後才能產生訊號，所以會有unit delay，或稱作latency

GPIO除了原本接上了LED之外，這次也接上了Switch。
所以從Synthesised Design中可以看到除了LED的區域外，Switch的區域也被畫出來了。
![](https://i.imgur.com/XWgY9su.png)

本次的FIR由下列的程式碼給定：
```0
module FIR(CLK, RSTN, FIR_in_tvalid, FIR_in_tready, FIR_in_tlast, FIR_in_tdata,
           FIR_out_tvalid, FIR_out_tready, FIR_out_tlast, FIR_out_tdata);
input  CLK, RSTN;
input  FIR_in_tvalid, FIR_out_tready, FIR_in_tlast;
output FIR_out_tvalid, FIR_in_tready, FIR_out_tlast;
input  [31:0] FIR_in_tdata;
output [31:0] FIR_out_tdata;

wire [7:0] h_0, h_1, h_2; 
reg  [39:0] TDL [0:2];
wire [31:0] X, Y;   
wire [39:0] a [0:2]; 
    
always @ (posedge CLK)
    if (~RSTN)
        begin
            TDL[0] <= 0;
            TDL[1] <= 0;
            TDL[2] <= 0;
        end
    else
        begin
            TDL[0] <= a[0];
            TDL[1] <= TDL[0] + a[1];
            TDL[2] <= TDL[1] + a[2];
        end
    assign X = FIR_in_tdata;
    assign h_0 = 32;
    assign h_1 = 64;
    assign h_2 = 16;
    assign a[0] = X*h_2;
    assign a[1] = X*h_1;
    assign a[2] = X*h_0;
    assign Y = TDL[2][39:8];
    assign FIR_out_tdata = Y;
    assign FIR_out_tvalid = FIR_in_tvalid;
    assign FIR_in_tready = FIR_out_tready;
    assign FIR_out_tlast = FIR_in_tlast;
endmodule
```

這串程式碼當中可以看到一個變數：**tlast**。
前面有說過，對於DMA來說重要的東西有三個：2個地址和1個資料長度。
**tlast**對於DMA來說就是資料長度，意義為最後一筆資料。
只要DMA收到**tlast**，就會停止運作，所以**tlast**對於DMA來說是必要的。
   
    在FIFO的例子當中，tlast已經被包在AXIS裡面了

完成後同樣我們可以用Python code去控制Zedboard。

![](https://i.imgur.com/ErRLATs.png)
In [1]：燒錄。
In [2]：控制LED燈與Switch。
```.
led_ip = ol.ip_dict['axi_gpio_led1_sw2']
leds = AxiGPIO(led_ip).channel1
mask = 0xffffffff
leds.write(0x88, mask)

SW = AxiGPIO(led_ip).channel2
while True:
    A = SW.read()
    print(A)
```
在這裡```led.write```控制LED燈的量法，0x88=0d136=0b10001000
```SW```為自訂的變數，表示Switch的讀數。
```While True```為無線迴圈，```SW.read```會不斷重新讀取```SW```。
從```print(A)```數值可以看到```SW```由0x63=0b00111111變為0x127=0b01111111。

![](https://i.imgur.com/rcah6CD.png)
```1
import pynq.lib.dma
from pynq import Xlnk
import numpy as np

xlnk = Xlnk()
dma = ol.axi_dma_0
N = 20
input_buffer = xlnk.cma_array(shape=(N,), dtype=np.uint32)
output_buffer = xlnk.cma_array(shape=(N,), dtype=np.uint32)
for i in range(N):
    input_buffer[i] = (i == 0) * 256
print(input_buffer)    
dma.sendchannel.transfer(input_buffer)
print(output_buffer)
dma.recvchannel.transfer(output_buffer)
dma.sendchannel.wait()
dma.recvchannel.wait()
print(output_buffer)
```
最後測試FIR與DMA。
Input定為長度20的矩陣，只有第1格有值256，如輸出的第一列所示。
分兩個不同的地方輸出Output，分別為DMA取資料前與取資料後。
取資料前的Output是沒有數值的，所以輸出應為0，如輸出的第二列所示。
取資料後的Output是通過FIR的數值。
FIR的權重前面設定取3項，分別為32, 64, 16，再加上FIR的特性會晚一個訊號才會動作。
因此結果如輸出的第三列所示，為0 32 64 16 0 ... 0。

---
# Matlab $\leftrightarrow$ ZYNQ
可以通過Jupyter使PC端的Matlab和ZYNQ做溝通。
算是Matlab和PS的互動，然後PS可以和PL做互動，達到控制SDR的目的。
原則上不需要做Block design，有ZYNQ然後開機就可以使用了。
## Matlab to PYNQ
### Python code running on ZYNQ
原則上先在ZYNQ上設定完之後再去Matlab上操作。
![](https://i.imgur.com/0Qdf2JG.png)
```0
import socket
# HOST = '192.168.2.99'  # default
HOST = '192.168.50.220'  # CIA router
ctrl_port = 8000
data_port = 8001
server_data = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_data.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
#作業系統會在伺服器socket被關閉或伺服器程序終止後馬上釋放該伺服器的埠，
# 否則作業系統會保留幾分鐘該埠。
server_data.bind((HOST, data_port))
server_data.listen(5)

server_ctrl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_ctrl.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
#作業系統會在伺服器socket被關閉或伺服器程序終止後馬上釋放該伺服器的埠，
# 否則作業系統會保留幾分鐘該埠。
server_ctrl.bind((HOST, ctrl_port))
server_ctrl.listen(5)   # 5 links

print("wait")
conn_data, addr_data = server_data.accept()
conn_ctrl, addr_ctrl = server_ctrl.accept()

M2P_data = np.zeros(16,dtype=np.uint16)

conn_data.recv_into(M2P_data,4)
print(M2P_data)
```
```HOST```的IP位置要根據怎麼掛Zedboard來改。
Run之後會輸出wait，如上圖，表示系統在跑在等待，接著就可以去Matlab上操。
### MATLAB code running on PC
```0
% Test of PYNQ TCP/IP
global ctrl_client data_client

% Control Port
ctrl_client = tcpip('192.168.50.220', 8000, 'NetworkRole', 'client', 'Timeout', 10); %QAM
%ctrl_client = tcpip('192.168.2.99', 8002, 'NetworkRole', 'client', 'Timeout', 5); %decoder
fopen(ctrl_client);

% Data Port
data_client = tcpip('192.168.50.220', 8001, 'NetworkRole', 'client', 'Timeout', 10); %QAM
%data_client = tcpip('192.168.2.99', 8003, 'NetworkRole', 'client', 'Timeout', 5); %decoder
data_client.InputBufferSize = 2^20;
data_client.OutputBufferSize = 2^20;
data_client.ByteOrder='littleEndian'; %'bigEndian'; %; %

Data_M2P= [10 12],
fopen(data_client);
fwrite(data_client,Data_M2P ,'int16')
```
把上面的code輸入到Matlab的script中，記得要改```tcpip```的IP位置。
按下Run之後就可以把資料送出去，結果如下：
![](https://i.imgur.com/InkG1Np.png)
其中Jupyter中```conn_data.recv_into(M2P_data,4)```的最後面的```4```為最大可以接收的bits數量。
現在數字為4，所以如果去增加Matlab中```Data_M2P```後面的數值，ZYNQ也讀不到：
![](https://i.imgur.com/O5dL5ib.png)
數值改大後就讀的到了：
![](https://i.imgur.com/KUPiw1d.png)
原則上只要有連線到，之後讀數值只需要兩行而已：
```0
conn_data.recv_into(M2P_data,20)
print(M2P_data)
```
同樣的在Matlab上，只要有```fopen```成功後，寫新數值只需要兩行而已：
```0
Data_M2P= [10 12 1 2 3 4 7 8];
fwrite(data_client,Data_M2P ,'int16')
```
![](https://i.imgur.com/xkbS474.png)
如此一來可以代表說，DATA可以寫給PS，然後PS再去做動作。
PS可以用DMA等方式把DATA丟到PL，PL就可以real time的播放。

## PYNQ to Matlab
### Python Code
```0
P2M_data = np.array([1, 2, 3, 4],dtype=np.int16)
conn_data.sendall(P2M_data)
```
把[1 2 3 4]的資料丟出去。

### MATLAB Code
```0
Data_P2M = fread(data_client,4,'int16')
```
同樣，Matlab中```fread(data_client,4,'int16')```的```4```代表了讀的bits數。
執行完後就可以收到PYNQ丟出來的資料了。
![](https://i.imgur.com/zE3XOku.png)

---
# SPI
SPI(Serial Peripheral Interface)串列周邊介面，是晶片通信的同步串行通信介面，是一種全雙工、高速、同步的通信總線。
可以一對一，也可以一主對多從。
![](https://i.imgur.com/6RIhC6i.png)
[Reference](https://zh.wikipedia.org/zh-tw/%E5%BA%8F%E5%88%97%E5%91%A8%E9%82%8A%E4%BB%8B%E9%9D%A2#/media/File:SPI_three_slaves.svg)
通常SPI有4條信號線以實現全雙工(或是3線的SPI為半雙工)：
1. SCLK(Serial Clock)
串行時鐘，Master提供CLK給Slave。
2. MOSI(Master Output, Slave Input)
主發從收信號，Master時傳送資料到Slave；Slave時接收從Master來的資料。
3. MISO(Master Input, Slave Output)
主發從收信號，Master時接收從Slave傳來的資料；Slave時傳送資料至Master。
4. SS(Slave Select)
片選信號，在一主對多從的系統中，SS的高低電位可以用於選擇接收資料的裝置。

所有的傳輸都根據同一個頻率信號。
頻率信號由Master產生，Slave則用此頻率信號來對收到的東西進行同步。
所謂的同步指Slave沒有自己的CLK，CLK由Master傳到Slave，所以兩者會共用同一個CLK。
[Reference](http://wiki.csie.ncku.edu.tw/embedded/SPI)

## IPs
1. ZYNQ7 Processing System
2. AXI GPIO (axi_gpio_AD9361_SPI)
3. GPIO32 to AXIS24.v
4. AXIS DATA FIFO (independent CLKs) (Width=24, Depth=16)
5. AD9361 SPI Interface

## Design
![](https://i.imgur.com/U4wEku1.png)
![](https://i.imgur.com/m6G5ABF.png)
![](https://i.imgur.com/ejGAOHD.png)
把內容都包進**AD9361_SPI**的hierarchy中單純只是讓畫面看起來更簡潔。

### ZYNQ7 Processing System
Run block automation接上DDR和FIXED_IO。

### AXI GPIO
Run connection automation
![](https://i.imgur.com/CpMUiNv.png)
系統會自動把**processing system**的M_AXI接到**GPIO**的S_AXI
這裡因為GPIO的輸出是我們自己設定的模組，所以不勾。
中間會自動產生**Reset**和**Interconnector**。
![](https://i.imgur.com/CgLpweb.png)
**GPIO**開兩個channel，GPIO為All output，GPIO2為All input。
因為GPIO2的輸入資料是8 bits，所以要把寬度改成8。
養成好習慣，把名字改掉。
這邊可以改成**axi_gpio_AD9361_SPI**。

### GPIO32 to AXIS24
**Add source $\to$ Add or creat design source $\to$ Add files**
加入NCU_SDR_Verilog_LIB_2022中已經寫好的**GPIO32_AXIS24**。
在Source中，有箭頭代表示hierarchy，打開發現裡面缺少**Ping_Pong_FIFO**。
用同樣的程序加入**Ping Pong FIFO**。
![](https://i.imgur.com/rcXtPMg.png)

確認一下**GPIO32_AXIS24**的內容，**Ping Pong FIFO**不是AXI機制的FIFO。
同時也可以打開**Ping_Pong_FIFO**的內容，確認變數名稱一致。
![](https://i.imgur.com/1Nx5gyx.png)

完成之後把**GPIO32_to_AXIS24**拉進Block diagram中。
![](https://i.imgur.com/81g27fB.png)

接CLK之前先確認**processing system**的CLK頻率多少。
看Clock configuration的PL fabric clocks，為100MHz。
![](https://i.imgur.com/1fOD1Np.png)

接上CLK和RSTN後，把All output的GPIO接到GPIO_in當中。
如果顯示no matching的話，把GPIO的加號打開，從裡面接線。

### AXIS DATA FIFO
加入IP **AXIS Stream Data FIFO**。
避免占掉太多資源，FIFO depth改成16。
Independent clocls改為Yes。
![](https://i.imgur.com/aXqMM9A.png)

把**GPIO32 to AXIS24**的master接到**FIFO**的S_AXIS。
Slave的CLK要接到100MHz的CLK上，RSTN也要接上。
Read的master的CLK要接到**GPIO32 to AXIS24**的四分之一CLK。
![](https://i.imgur.com/SRZ7lfu.png)

### AD9361 SPI interface
![](https://i.imgur.com/DjUKsn2.png)
```0
module AD9361_SPI_Interface(CLK, RSTN, s_tdata, s_tvalid, s_tready, SPI_ENB, SPI_CLK, SPI_DI, SPI_DO, SPI_led);

 input  CLK, RSTN;
 input  [23:0] s_tdata;   //  
 input  s_tvalid;
 output s_tready;
 input  SPI_DO; 
 output SPI_DI, SPI_ENB, SPI_CLK;
 output [7:0] SPI_led;
 
 wire [23:0] SPI_P2S_in;
 wire SPI_RST;
 reg [23:0] P2S_reg;
 reg [4:0]  P2S_counter;
 reg P2S_state; 
 reg [7:0]  SPO_reg;
 
 wire P2S_empty, P2S_full;
 wire P2S_reset, P2S_WR; 
 wire SPO_ENB;
 
 always@(posedge CLK)
  if (P2S_reset)
     P2S_state <= 0;
  else if (P2S_WR)
     P2S_state <= 1;

 always @(posedge CLK)
   if (P2S_reset)
      P2S_counter <= 0;
   else if (P2S_full)
      P2S_counter <= P2S_counter + 1;

 always @(posedge CLK)
    if (P2S_WR)
       P2S_reg <= SPI_P2S_in;

 always@(posedge CLK)
    if (SPI_RST)
       SPO_reg <= 8'b0;
    else if (SPO_ENB) 
       SPO_reg <= {SPO_reg[6:0],SPI_DO};
       
       assign SPI_P2S_in = s_tdata[23:0];
 assign SPI_RST = ~RSTN;
 assign P2S_reset = SPI_RST | (P2S_full & (P2S_counter == 31));
 assign P2S_empty = (P2S_state == 0);
 assign P2S_full = (P2S_state == 1);
 assign s_tready = ~P2S_full;
 assign P2S_WR = s_tvalid & s_tready;
// Output
  wire [4:0] P2S_index;
  assign P2S_index = 23-P2S_counter;
  assign SPI_DI = P2S_reg[P2S_index];
  assign SPI_CLK = CLK;
/////SPI_ENB//////////////////////////////////////
assign SPI_ENB = ~(P2S_full & (P2S_counter < 24));
assign SPO_ENB = P2S_full & ((P2S_counter >= 16) & (P2S_counter <= 23));
assign SPI_led = SPO_reg;
endmodule
```
![](https://i.imgur.com/s7Lpnrh.png)
加入NCU_SDR_Verilog_LIB_2022中已經寫好的**AD9361_SPI_interface**。
```SPI_DI = SPI_MOSI```、```SPI_DO = SPI_MIOS```
接上系統的RSTN。
CLK是接除以4的CLK，因為讀進來和寫出去都是這個速度。
Input要接FIFO出來的Async的地方。
Read的時候SPI_DO會去capture AD9361的8bits data，會存起來然後用SPI_led做呈現。
所以要把SPI_led的資料拉到GPIO2。
DI和DO是對AD9361來說是input和output，所以這邊是output和input。
接4條信號線全部都用make external，然後把預設的名字中的_0去掉，以符合constraint。

## Constraint
完成接線後，可以先validate，然後creat HDL wrapper，set as top。
**Zedboard $\leftrightarrow$ AD9361 SPI**
```0
# constraints
# Zedboard AD9361 SPI

set_property  -dict {PACKAGE_PIN  F18  IOSTANDARD LVCMOS25  PULLTYPE PULLUP} [get_ports SPI_ENB]; 
## D26  FMC_LPC_LA26_P
set_property  -dict {PACKAGE_PIN  E18  IOSTANDARD LVCMOS25} [get_ports SPI_CLK]; 
## D27  FMC_LPC_LA26_N
set_property  -dict {PACKAGE_PIN  E21  IOSTANDARD LVCMOS25} [get_ports SPI_DI]; 
## C26  FMC_LPC_LA27_P
set_property  -dict {PACKAGE_PIN  D21  IOSTANDARD LVCMOS25} [get_ports SPI_DO]; 
## C27  FMC_LPC_LA27_N
```
這邊可以直接從NCU_SDR_Verilog_LIB_2022加入constraint file
**Add source $\to$ Add or creat constraints $\to$ Add files**。
![](https://i.imgur.com/wD6Y5Gr.png)
裡面主要是SPI的constraint，其他的可以多但是不能少。

這邊可以順便在加入一個GPIO做測試，開兩個channel給led和sws用。
Run connection automation
![](https://i.imgur.com/391ZRPC.png)
Wrapper應該會自己更新。
完成後就可以Generate big stream。

## Jupyter
```0
from pynq import Overlay
import time
import numpy as np
from pynq.lib import AxiGPIO

ol = Overlay("SPI.bit")
ol?

def PYNQ_SPIWrite(x, y):
# PYNQ_SPIWrite(‘007’, ‘08’)
    mask = 0xffffffff
    ADI9361_Conf_comm = int(x + y, 16)+2**24+2**23
    SPI_WR_channel.write(0x0, mask)
    SPI_WR_channel.write(ADI9361_Conf_comm, mask)  #SPIWrite
    print(hex(ADI9361_Conf_comm))
    print(x)

def PYNQ_SPIRead(x):
# PYNQ_SPIRead(‘007’)
    mask = 0xffffffff
    ADI9361_Conf_comm = int(x + '00', 16) + 2**24 
    SPI_WR_channel.write(0x0, mask)
    SPI_WR_channel.write(ADI9361_Conf_comm, mask)  #SPIWrite
    SPI_DO = SPI_RD_channel.read()
    SPI_DO = hex(SPI_DO)
    print(SPI_DO)

SPI_ip = ol.ip_dict['AD9361_SPI/axi_gpio_AD9361_SPI']
SPI_WR_channel = AxiGPIO(SPI_ip).channel1
SPI_RD_channel = AxiGPIO(SPI_ip).channel2
```
先燒錄，定義```PYNQ_SPIWrite```和```PYNQ_SPIRead```的函數。
其中```PYNQ_SPIWrite(x, y)```的```x```是Address```y```是Data。
把SPI的write channel ```SPI_WR_channel```定義到先前寫的**GPIO**的output channel。
把SPI的read channel ```SPI_RD_channel```定義到先前寫的**GPIO**的input channel。
需要注意的是，如果前面有用Create hierarchy把**GPIO**包起來的話，**GPIO**的路徑需要寫成```'AD9361_SPI/axi_gpio_AD9361_SPI'```，不能只有```'axi_gpio_AD9361_SPI'```。
```0
PYNQ_SPIWrite('271', '09')
PYNQ_SPIRead('271')
```
燒錄與定義完後，可以試著寫東西進去然後讀。
這裡把```'09'```這個Data寫到```'271'```的位置上，然後再讀```'271'```的Data。
結果如下圖所示：

![](https://i.imgur.com/XdWNPX0.png)
第一段執行完之後應該除了```ol?```之外都不會有東西跑出來。
第二段可以看到讀出來的結果是寫進去的```0x9```。

![](https://i.imgur.com/GVWgwFH.png)
若把寫進去的Data從```'09'```換成```'7D'```，則讀出來的東西就變成```'0x7d'```。
也可以利用```While```一直寫東西進去
```0
i=0
while i<5:
    PYNQ_SPIWrite('00'+str(i), 'AA')
    i=i+1
#PYNQ_SPIRead('007')
```

---
# Tx Template (AD9361)
發射機，可以直接從上次做完的SPI加東西上去，這裡從頭開始做。
![](https://i.imgur.com/TVhJU5k.png)

## Needs
### AD9361 Configuration (Programming)
1. GPIO32_to_AXIS24.v
2. Ping_Pong_FIFO.v
3. AD9361_SPI_Interface.v
4. Zedboard_AD9361_SPI.xdc
5. Zedboard_AD9361_Configuration.py
6. PYNQ_AD9361_Configuration.txt

### AD9361 DAC (TX) Interface
1. ADI_DAC_Interface_AXIS
2. DDR_IQ_DAC.v
3. DELAY_CLOCK.v
4. SG_LUT.v
5. Zedboard_AD9361_ADC_DAC.xdc

## Design
![](https://i.imgur.com/IPx8ocG.png)
![](https://i.imgur.com/rCDNrLB.png)

### ZYNQ7 Processing System
Run block automation接上DDR和FIXED_IO。

### AXI GPIO
Run connection automation
開兩個GPIO：
1. GPIO：All outputs，Width: 32，寫資料用。
2. GPIO2：All inputs，Width: 8，讀資料用。

### GPIO32 to AXIS24
**Add source $\to$ Add or create source $\to$ Create file**
**File name: GPIO32_to_AXIS24**
Module definition會自己寫，先Cancel。
```0
module GPIO32_to_AXIS24(CLK, RSTN, GPIO_in, m_tdata, m_tvalid, m_tready, CLK_4); 
parameter DW_GPIO = 32, DW_AXIS = 24; 
input  CLK, RSTN, m_tready; 
input  [DW_GPIO-1:0] GPIO_in; 
output [DW_AXIS-1:0] m_tdata; 
output m_tvalid, CLK_4; 
reg DD; 
wire GPIO_wr, DD_in,  PP_WR, PP_RD, PP_full, PP_empty; 
wire [DW_AXIS-1:0] PP_in, PP_out;       
always @(posedge CLK)    
    if (~RSTN)       
        DD <= 0;    
    else if (~PP_full)   
        DD <= DD_in; 

Ping_Pong_FIFO #(.DW(DW_AXIS))  
        PP    (.CLK(CLK), .RSTN(RSTN),        
               .PP_in(PP_in),        
               .PP_WR(PP_WR),        
               .PP_out(PP_out),        
               .PP_RD(PP_RD),        
               .PP_full(PP_full),        
               .PP_empty(PP_empty)); 

assign GPIO_wr = ~DD & DD_in & ~PP_full;  
assign PP_WR = GPIO_wr;   
assign DD_in = GPIO_in[DW_AXIS];  
assign PP_in = GPIO_in[DW_AXIS-1:0];  
assign m_tdata = PP_out;  
assign m_tvalid = ~PP_empty;  
assign PP_RD = m_tvalid & m_tready;
//////////////////////////////////////////////////////////////////////////// 
reg [1:0] CC;
always @(posedge CLK)
    if (~RSTN)
        CC <= 0;
    else
        CC <= CC + 1;
     
assign CLK_4 = CC[1];              
endmodule
```
打完後存檔，在Source列表中發現少了Ping Pong FIFO。

### Ping Pong FIFO
**Add source $\to$ Add or create source $\to$ Create file**
**File name: Ping_Pong_FIFO**
```0
module Ping_Pong_FIFO(CLK, RSTN, PP_in, PP_WR, PP_out, PP_RD, PP_full, PP_empty);
parameter DW = 16;
    input  CLK, RSTN;
    input  [DW-1:0] PP_in;
    input  PP_WR, PP_RD;
    output [DW-1:0] PP_out;
    output PP_full, PP_empty;

reg  [DW-1:0] Ping_BUF, Pong_BUF;
reg  Ping_Load, Pong_Load, Ping_Pong_WR, Ping_Pong_RD;
wire Ping_WR, Pong_WR;
wire Ping_RD, Pong_RD;
 
always @(posedge CLK)
    if (~RSTN)
	    Ping_Pong_WR <= 0;
	else if (PP_WR) 
	    Ping_Pong_WR <= ~Ping_Pong_WR; 
        
always @(posedge CLK)
    if (~RSTN)
	    Ping_Pong_RD <= 0;
    else if (PP_RD) 
	    Ping_Pong_RD <= ~Ping_Pong_RD;
        
always @(posedge CLK)
    if (~RSTN | Ping_RD)
	    Ping_Load <= 0;
	else if (Ping_WR) 
	    Ping_Load <= 1;
        
always @(posedge CLK)
    if (~RSTN | Pong_RD)
	    Pong_Load <= 0;
    else if (Pong_WR) 
	    Pong_Load <= 1;
        
always @(posedge CLK)
    if (~RSTN)
	    Ping_BUF <= 0;
	else if (Ping_WR) 
	    Ping_BUF <= PP_in;
        
always @(posedge CLK)
    if (~RSTN)
	    Pong_BUF <= 0;
	else if (Pong_WR) 
	    Pong_BUF <= PP_in;

assign PP_full = Ping_Load & Pong_Load;
assign PP_empty = ~Ping_Load & ~Pong_Load;

assign Ping_WR = PP_WR & ~Ping_Pong_WR;
assign Pong_WR = PP_WR & Ping_Pong_WR;

assign Ping_RD = PP_RD & ~Ping_Pong_RD;
assign Pong_RD = PP_RD & Ping_Pong_RD;
assign PP_out = Ping_Pong_RD ? Pong_BUF : Ping_BUF;

endmodule
```
完成後可以把**GPIO32_to_AXIS24**拉進Block design。
接上CLK和RSTN。
GPIO_in為寫入信號，接上**GPIO**的GPIO，32 bits。

### Async FIFO (Data FIFO)
寫進進去的CLK和讀出來的CLK不一樣，寫進去的CLK是系統的CLK；讀出來的CLK是除4的CLK。
原因出在SPI的速度比較慢，這也是換平台都需要用新的IP的原因。
加入一個AXIS Data FIFO，先設定。
輸出是24 bits 3 bytes，基本上符合初始設定，所以FIFO depth可以小一點，這裡設16。
因為Input的Slave的CLK跟Output是Master的CLK不一樣，所以Independent CLK要Yes。
**GPIO32_to_AXIS24**的Output master要接S_AXIS，CLK_4要接Master的CLK。
剩下的Slave的CLK和RSTN都接系統的。
這樣Data FIFO就完成了。

### AD9361 SPI Interface
**Add source $\to$ Add or create source $\to$ Create file**
**File name: AD9361_SPI_Interface**
```0
module AD9361_SPI_Interface(CLK, RSTN, s_tdata, s_tvalid, s_tready, SPI_ENB, SPI_CLK, SPI_DI, SPI_DO, SPI_led);
input  CLK, RSTN;
input  [23:0] s_tdata;
input  s_tvalid;
output s_tready;
input  SPI_DO; 
output SPI_DI, SPI_ENB, SPI_CLK;
output [7:0] SPI_led;
 
wire [23:0] SPI_P2S_in;
wire SPI_RST;
reg [23:0] P2S_reg;
reg [4:0]  P2S_counter;
reg P2S_state; 
reg [7:0]  SPO_reg;
 
wire P2S_empty, P2S_full;
wire P2S_reset, P2S_WR; 
wire SPO_ENB;

always@(posedge CLK)
    if (P2S_reset)
        P2S_state <= 0;
    else if (P2S_WR)
        P2S_state <= 1;

always @(posedge CLK)
    if (P2S_reset)
        P2S_counter <= 0;
    else if (P2S_full)
        P2S_counter <= P2S_counter + 1;

always @(posedge CLK)
    if (P2S_WR)
        P2S_reg <= SPI_P2S_in;

always@(posedge CLK)
    if (SPI_RST)
        SPO_reg <= 8'b0;
    else if (SPO_ENB) 
        SPO_reg <= {SPO_reg[6:0],SPI_DO};

assign SPI_P2S_in = s_tdata[23:0];
assign SPI_RST = ~RSTN;
assign P2S_reset = SPI_RST | (P2S_full & (P2S_counter == 31));
assign P2S_empty = (P2S_state == 0);
assign P2S_full = (P2S_state == 1);
assign s_tready = ~P2S_full;
assign P2S_WR = s_tvalid & s_tready;
// Output
wire [4:0] P2S_index;
assign P2S_index = 23-P2S_counter;
assign SPI_DI = P2S_reg[P2S_index];
assign SPI_CLK = CLK;
// SPI_ENB
assign SPI_ENB = ~(P2S_full & (P2S_counter < 24));
assign SPO_ENB = P2S_full & ((P2S_counter >= 16) & (P2S_counter <= 23));
assign SPI_led = SPO_reg;
endmodule
```
完成後拉進Block design，如跳出SPI_CLK的Waring的話沒關係，那是Output之後會用到的變數。
**Async FIFO**的Master要接到這裡的Slave，因為Master前面的CLK是CLK_4，這裡也要接同一個比較慢的CLK。
RSTN接系統的，SPI_led是讀出來的資料，要接回**GPIO**的GPIO2，這樣才能用PS端去Access。
剩下4個腳位都Make external，要把名字預設的_0拿掉以符合Constraints。
SPI_DO是從AD9361讀資料回來的Port。

### Zedboard_AD9361_SPI.xdc
**Add source $\to$ Add or create Constraints $\to$ Create file**
**File name: Zedboard_AD9361_SPI**
```0
# constraints
# Zedboard AD9361 SPI

set_property  -dict {PACKAGE_PIN  F18  IOSTANDARD LVCMOS25  PULLTYPE PULLUP} [get_ports SPI_ENB]; 
 ## D26  FMC_LPC_LA26_P
set_property  -dict {PACKAGE_PIN  E18  IOSTANDARD LVCMOS25} [get_ports SPI_CLK]; 
## D27  FMC_LPC_LA26_N
set_property  -dict {PACKAGE_PIN  E21  IOSTANDARD LVCMOS25} [get_ports SPI_DI]; 
## C26  FMC_LPC_LA27_P
set_property  -dict {PACKAGE_PIN  D21  IOSTANDARD LVCMOS25} [get_ports SPI_DO]; 
## C27  FMC_LPC_LA27_N
```
基本上就是接我們包裝的腳位和邏輯系統的腳位，同時定義輸出的電壓的型態。
這裡可以驗證SPI，跟上一個章節一樣，不過這邊先跳過。
接下來要接DAC的路徑。

## DAC
DAC(Digital-to-Analog Converter)數位類比轉換器
DAC會以均勻的時間間格輸出類比電壓值。
每完成一次轉換，轉換器的輸出值能夠迅速從上一個輸出值更新為當前的輸出值。
每次讀到一個輸入值會轉換並產生一個相對的輸出電壓，輸出電壓會保持恆定直到下一個輸入值轉換完成。
這種因取樣率不同而產生不同的訊號輸出類似階躍函數，會造成Nyquist frequency以上的諧波，以取樣率的倍數在頻域上產生頻譜鏡像，這些頻譜上的干擾可以藉由低通濾波器消除。
![](https://i.imgur.com/W6auqBK.png)
[Reference](https://zh.wikipedia.org/zh-tw/%E6%95%B8%E4%BD%8D%E9%A1%9E%E6%AF%94%E8%BD%89%E6%8F%9B%E5%99%A8)

![](https://i.imgur.com/CxFLjdu.png)

在NCU_SDR_Verilog_LIB_2022中可以直接加入已經寫好的Source，在AD9361_IP中。
![](https://i.imgur.com/0IDNlv0.png)
不過這邊也可以自己寫。

### ADI DAC Interface AXIS
**Add source $\to$ Add or create source $\to$ Create file**
**File name: ADI_DAC_Interface_AXIS**
```0
module ADI_DAC_Interface_AXIS(
    input	RSTN, CLK,	
    input DAC_TX_tvalid,
    output DAC_TX_tready,
    input [11:0] DAC_TX1_I, DAC_TX1_Q, DAC_TX2_I, DAC_TX2_Q, 
    //AD9361 DAC port
    input	 ADI_DATA_CLK,
    output ADI_FB_CLK, ADI_TX_FRAME,
    output [11:0] ADI_P1_D);

wire DAC_TX_en, DAC_TX_WR;

assign DAC_TX_tready = DAC_TX_en;
assign DAC_TX_WR = DAC_TX_tvalid & DAC_TX_tready;

wire TX_CLK, TX_CLK_A;
DELAY_CLOCK #(.DEPTH(4))
DC   (.I(ADI_DATA_CLK), .O(ADI_FB_CLK));
 
assign TX_CLK = ADI_DATA_CLK;

reg  ADI_TX_Frame_reg;

always@(posedge TX_CLK)
	if(~RSTN)  
	    ADI_TX_Frame_reg <= 0;
	else 		 
	    ADI_TX_Frame_reg <= ~ADI_TX_Frame_reg;
 
assign ADI_TX_FRAME = ADI_TX_Frame_reg;
//// Asynchronous FIFO Buffer for DAC 				
wire DAC_BF_full, DAC_BF_WR, DAC_BF_empty, DAC_BF_RD;
wire [47:0] DAC_BF_in, DAC_BF_out;
Asyn_FIFO_48x512
DAC_BF  (.rst(~RSTN), .wr_clk(CLK), .wr_en(DAC_BF_WR), .din(DAC_BF_in),
	 .full(DAC_BF_full), .rd_clk(TX_CLK), .rd_en(DAC_BF_RD), 
         .dout(DAC_BF_out), .empty(DAC_BF_empty) );

assign DAC_BF_in = {DAC_TX1_I, DAC_TX1_Q, DAC_TX2_I, DAC_TX2_Q};
assign DAC_BF_WR = DAC_TX_WR;	
assign DAC_TX_en = ~DAC_BF_full;							 
assign DAC_BF_RD = ~DAC_BF_empty & ~ADI_TX_FRAME;
/// Dual-IQ Interface
wire [11:0] DAC_I_1, DAC_Q_1, DAC_I_2, DAC_Q_2;

DDR_IQ_DAC  
IQ_TX  (.I_in1(DAC_I_1), .Q_in1(DAC_Q_1), .I_in2(DAC_I_2), .Q_in2(DAC_Q_2),
        .DDR_out(ADI_P1_D), .ADI_FB_CLK(TX_CLK), .ADI_TX_FRAME(ADI_TX_FRAME));	
assign DAC_I_1 = DAC_BF_out[47:36];
assign DAC_Q_1 = DAC_BF_out[35:24];
assign DAC_I_2 = DAC_BF_out[23:12];
assign DAC_Q_2 = DAC_BF_out[11:0];
endmodule
```
其中```DELAY_CLOCK #(.DEPTH(4))```的DEPTH(4)滿critical的。
>老師說之前是用DEPTH(2)，可能會有不一樣的結果。

在Source欄看應該會缺DELAY_CLOCK, DDR_IQ_DAC, Asyn_FIFO_48x512。

### DELAY CLOCK
**Add source $\to$ Add or create source $\to$ Create file**
**File name: DELAY_CLOCK**
```0
`timescale 1ns/1ps
module DELAY_CLOCK(I, O);
parameter DEPTH = 0;
//-----------------------------------
input                I;
output               O;
//-----------------------------------
(* KEEP="TRUE" *) wire       [DEPTH:0] D;
//-----------------------------------
assign D[0] = I;

BUFG U_BUFG(.I(D[DEPTH]), .O(O));

generate
genvar i;
for(i = 0; i < DEPTH; i = i + 1)
    begin : LEVEL
        LUT1 #(.INIT(2'b10)) U_LUT1(.I0(D[i]), .O(D[i+1]));
    end
endgenerate
endmodule
```

### DDR IQ DAC
**Add source $\to$ Add or create source $\to$ Create file**
**File name: DDR_IQ_DAC**
DDR是Double Data Rate。
```0
module DDR_IQ_DAC(I_in1, Q_in1, I_in2, Q_in2, DDR_out, ADI_FB_CLK, ADI_TX_FRAME);
input  [11:0] I_in1, Q_in1, I_in2, Q_in2; 
input  ADI_FB_CLK;
input  ADI_TX_FRAME;
output [11:0] DDR_out;
reg [11:0] DD_I, DD_Q, D_Q;
reg [11:0] D_I2, D_Q2;
always@(negedge ADI_FB_CLK)
    begin
	    D_I2 <= I_in2;
	    D_Q2 <= Q_in2;
    end
 
always@(negedge ADI_FB_CLK)
    if (~ADI_TX_FRAME)
	    begin
	        DD_I <= I_in1;
	        D_Q <= Q_in1;
	    end
	else
	   begin
	       DD_I <= D_I2;
	       D_Q <= D_Q2;
	   end
always@(posedge ADI_FB_CLK)
	   begin
	      DD_Q <= D_Q;
	   end
	
generate
genvar i;
for(i=0;i<=11;i=i+1)
    begin
        ODDR #(.DDR_CLK_EDGE("OPPOSITE_EDGE"), .INIT(0), .SRTYPE("SYNC"))
        U_ADI_FB_CLK (.R(1'b0), .S(1'b0), .CE(1'b1), .C(ADI_FB_CLK), 
		        .D1(DD_I[i]), .D2(DD_Q[i]), .Q(DDR_out[i]));
    end
endgenerate
endmodule
```
其中```ODDR```是內建的IP，似乎有不同的IC這裡會換掉。

### Asyn FIFO 48x512
48由AD9361的規格給定，4路的I_1, Q_1, I_2, Q_2都為12 bits。
普通在Block design的add IP會直接在Block design產生的IP，但是因為現在要給Source作為component使用，所以要使用左列的**IP catalog**。
**IP catalog $\to$ FIFO generator $\to$ Customize IP**
![](https://i.imgur.com/1HXoBUf.png)
在Interface type的地方，因為這邊是AXIS，所以選擇不是AXI的**Native**
FIFO implementation要選用Independent clock的三者之一。
FPGA裡面有一些資源是hardware的Block RAM，另外Slice裡面有一些register是Distributed RAM。
不過Distributed RAM最好留給邏輯用，所以如果block RAM夠或是不要求速度太高，用Block RAM就好。
這裡選用**distributed RAM**，會只有一個rst，Block RAM會有wr_rst和rd_rst。
![](https://i.imgur.com/MIfZp4Y.png)

接著看Native port。
標準的FIFO在read的時候會慢一個CLK才把資料送出來，而且CLK是real time CLK，是系統給的不是能自己控制的，有的時候加上buffer甚至可以慢到兩個CLK。
**First word fall through**是在memory外面加一些邏輯弄成像FIFO，不會慢一個CLK。
做FIFO最簡單的方式是circular buffer，基本上就是memory加上counter
例如設一個深度16的circular buffer，寫的時候從[0]開始寫，可以一邊寫一邊讀，這樣[0]就會是最舊的資料，就會產生一個延遲16的buffer。
不過有的時候深度會包含一些buffer在裡面，所如果要確保延遲的CLK數的話最好自己寫一個Counter。
寬度如上面所說的為48，深度則為512。
Xilinx的FPGA裡面以前block RAM一組是18k bits $\times$ 1，現在大部分是36k bits $\times$ 1。
這個數值是可以configurable，可以改成例如16k bits $\times$ 1也是用一組block RAM，但是深度的最小單位就是512，所以如果設定比512小其所占的memory變小，反而會浪廢掉後面所有的memory，所以這邊才會設深度為512。
這個問題可能可以藉由DMA等其他技術去解決。
![](https://i.imgur.com/1t5zy2w.png)

接著把Component name改掉，改成跟上面缺的東西一樣的名字**Asyn_FIFO_48x512**，OK後在Generate之前，因為有多個Source都會用到它，把Synthesis options改成Global。
![](https://i.imgur.com/puz4J4R.png)
完成後再確認一次是否有缺其他東西，把**ADI_DAC_Interface**拉到Block design中。
跟**SPI**的部分相比，這裡的**Async FIFO**就是建立在IP內的。
![](https://i.imgur.com/xyOieWf.png)
輸出是ADI_FB_CLK、ADI_TX_FRAME和ADI_P1_D，輸入是ADI_DATA_CLK，這4個Port都跟AD9361直接連接，所以直接Make external。
CLK和RSTN都是接系統的，至此Template就完成了，現在缺的是信號源。
下一步可以拉**DDS**做信號源來驗證，也可以拉**SG_LUT**來作，前者會比後者複雜一些。

---
# Tx with DDS
以**DDS**做信號源。
信號$s[n]=A\cdot \exp(j\cdot\omega\cdot n)=A\cdot\exp(j\cdot\theta[n])$
$A$: 振幅，$\omega=\frac{2\pi}{16}\equiv\frac{2^{Q=16}}{16}=4096$，$\theta[n]=\theta[n-1]+\omega$，$\theta[0]=\theta_0$ initial phase。

## DDS
DDS(Direct Digital Synthesizer)直接頻率合成器
DDS使用數位方法產生類比波形，提供數位編成能力、更高的整合度與更低的成本。
DDS幾乎能瞬間改變頻率與相位，成為相當重要的穩定RF來源。
原理上以相位累加器為基礎，相位累加器會產生波形的瞬時相位。查找表可進行相位對振幅的轉換，再套用到DAC上，經過濾波後產生出所需的類比輸出。
![](https://i.imgur.com/TR4iwZc.png)
輸出會接到DAC，但是DAC的輸入會成為一連串取樣值，因而產生階躍。而階躍會以取樣率的倍數在頻域上產生頻譜鏡像，所以DAC後的輸出要加上低通濾波器濾掉這些鏡像的頻譜響應。
[Reference](https://www.digikey.tw/zh/articles/the-basics-of-direct-digital-synthesizers-ddss)

### Phase accumulator
相位累加器由簡單的加法器組成，一般為24~48位元，用意在把$2\pi$用幾個位元表示。
$N$位元的相位累加器會把$\begin{Bmatrix} 0,2\pi \end{Bmatrix}$這個區間分成$2^N$等分，每等分代表一個相位差$\Delta\theta$。
每隔一個CLK，相位累加器會增加一次相位幅度，幅度由$\Delta\theta$與調諧字元值$M$決定。
調諧字元值$M$表示每一次相位增幅要取$M$個$\Delta\theta$，可以用下圖來理解。
![](https://i.imgur.com/Cr4u1KZ.png)
所以DDS的輸出頻率由給定的$M$決定，可以用數學式來表達：
$f_{out}=\frac{M}{2^N}\cdot f_{CLK}$
$M$的最大值通常定為$2^{N-1}$，以滿足Nyquist frequency。
在這個例子當中，是拿16 bits當$2\pi$，即為$N=16$。
[Reference](https://www.digikey.tw/zh/articles/the-basics-of-direct-digital-synthesizers-ddss)

### DDS
從NCU_SDR_Verilog_LIB_2022加入已經寫好的Source，在CORDIC_IP中。
![](https://i.imgur.com/p6SJzXF.png)
CORDIC rotation是一層一層的，AXIS是上層，First stage比較特別的所以獨立寫，剩下的都在STAGE中。
這個CORDIC rotation就是用CORDIC演算法去做到某種精準度的相位旋轉。
**DDS**也是獨立另外寫的。
**DDS**會需要Counter，這裡寫在裡面了，可以打開來確認如下：
```0
always @(posedge CLK)
    if (~RSTN)
        Phase_reg <= Phase_V;
    else if (DDS_en)
        Phase_reg <= Phase_reg + Freq_V;
```
>DDS_en是控制鍵，就算假定都是1也寫一下比較好。

確認一下**DDS**的內容設定：
```0
module Direct_Digital_Synthesizer(CLK, RSTN, 
    m_axis_tvalid, m_axis_tready,  m_axis_tdata, Frequency);
input  CLK, RSTN;
input  m_axis_tready;
output m_axis_tvalid;
output [31:0] m_axis_tdata;  // {I_out,Q_out}
input  [15:0] Frequency;
// parameter Frequency = 1024, 
parameter Phase_init = 0, I_init = 16384, Q_init = 0;
```
可以發現這個**DDS**沒有```input```，只有初始設定```parameter```，所以需要改設定的話要到這裡面直接改。
這裡因為我們的Frequency要從外部的**GPIO**給，所以把```parameter```裡面的Frequency註解掉，補上module的參數和```input  [15:0] Frequency;```。
完成後就可以把**DDS**丟進Block design中
![](https://i.imgur.com/RTm1LW5.png)

## Connection
開始接線。
**DDS**的CLK和**DAC**的CLK接系統的CLK。
**DDS**的RSTN和**DAC**的RSTN接**Reset**的RSTN。
**DDS**作為Source，把Input從**DDS**的Master: m_axis接到**DAC**的Slave: DAC_TX：
1. tvalid接tvalid

2. tready接tready

3. tdata接2個**Slice**
tdata 32 bits要接到 I 16 bits 和 Q 16 bits
一般的習慣是把 I 和 Q 合併成32 bits
MSB 16 bits I + LSB 16 bits Q

4. tdata[31:0]接**Slice_0**接DAC_TX1_I[11:0]
信號的振幅是16384，可以參見**DDS**的設定，所以盡量抓前面的。
信號```Din From 31```取12 bits```Din Down To 20```，丟掉4 bits以防爆掉。
做法上是input拉大一點，減少運算上的誤差，丟資料出去的時候再只丟前面的就好，所以這邊丟掉的是[19:16]。
原先資料是16 bits的話，最高振幅不要超過2的15次方，-32768~32767。

5. DAC_TX2_Q[11:0]接**Slice_0**的out
另一路故意接相反。

6. tdata[31:0]接**Slice_1**接DAC_TX1_Q
複製前面**Slice**，這次要取 Q 的部分
信號```Din From 15```取12 bits```Din Down To 4```，丟掉[3:0]。

7. DAC_TX2_I[11:0]接**Slice_1**的out
另一路故意接相反。

8. 加入**AXI GPIO**
前面把**DDS**裡面的Frequency改成```input```，現在要用**GPIO**接上去。
在Configuration中，GPIO為All outputs，寬度為16 bits。
可以給Default output value，這樣在沒有config的時候也會有值，這裡先給定為```0x00001000```，如此一來剛開機還沒有動作的時候也不會是直流電。
改名字為**axi_gpio_DDS**。

9. Run Connection Automation
GPIO是我們自己要接的不要勾，勾S_AXI就好。

10. **GPIO**接**DDS**的Frequency
GPIO接**DDS**的Frequency時如果顯示no matching，展開GPIO應該就能接上了。

## Generate Big Stream
至此發射機就完成了。
按下存檔的話，在Source欄應該可以看到**DAC**和**DDS**都被併到Design裡面去了。
Validate確認看看有沒有問題。
Create HDL Wrapper。
![](https://i.imgur.com/5ZFb1Hb.png)
確認**ADC_DAC**和**SPI**的Constraint file有沒有加上去，內容也可以對一下。
確認沒有問題的話就可以Generate Big Stream。

## Jupyter
記得確認變數名稱有沒有改過。
定義會用到的函數。
```0
from pynq import Overlay
import time
import numpy as np
from pynq.lib import AxiGPIO

ol = Overlay("Tx_2022.04.22.bit")
ol?

def PYNQ_SPIWrite(x, y):
    mask = 0xffffffff
    ADI9361_Conf_comm = int(x + y, 16)+2**24+2**23
    SPI_WR_channel.write(0x0, mask)
    SPI_WR_channel.write(ADI9361_Conf_comm, mask)  #SPIWrite
    print(x)

def PYNQ_SPIRead(x):
    mask = 0xffffffff
    ADI9361_Conf_comm = int(x + '00', 16) + 2**24 
    SPI_WR_channel.write(0x0, mask)
    SPI_WR_channel.write(ADI9361_Conf_comm, mask)  #SPIWrite
    SPI_DO = SPI_RD_channel.read()
    SPI_DO = hex(SPI_DO)
    print(SPI_DO)
    
def DDS_Write(x):
    DDS_Conf_comm = (2**16 + x)
    DDS_channel.write(0x0, mask)
    DDS_channel.write(DDS_Conf_comm, mask)  #SPIWrite

mask = 0xffffffff
SPI_ip = ol.ip_dict['AD9361_SPI/axi_gpio_AD9361_SPI']
SPI_WR_channel = AxiGPIO(SPI_ip).channel1
SPI_RD_channel = AxiGPIO(SPI_ip).channel2

LED_sw_ip = ol.ip_dict['axi_gpio_LED_SW']
LED_channel = AxiGPIO(LED_sw_ip).channel1
SW_channel = AxiGPIO(LED_sw_ip).channel2

DDS_ip = ol.ip_dict['axi_gpio_DDS']
DDS_channel = AxiGPIO(DDS_ip).channel1
```
可以確認一下```ol?```裡面的變數名稱。
接著測試SPI可不可以Read和Write。
```0
PYNQ_SPIWrite('271','09')
PYNQ_SPIRead('271')

a = SW_channel.read()
print(a)
LED_channel.write(a,mask)
```
結果應該要跟**SPI**中的一樣，是的話代表有連上。
後要跑Matlab的Config。
```0
import socket

# HOST = '192.168.2.99' # default
HOST = '192.168.50.39'
ctrl_port = 8000
data_port = 8001

server_data = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_data.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
#作業系統會在伺服器socket被關閉或伺服器程序終止後馬上釋放該伺服器的埠，否則作業系統會保留幾分鐘該埠。
server_data.bind((HOST, data_port))
server_data.listen(5)

server_ctrl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_ctrl.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
#作業系統會在伺服器socket被關閉或伺服器程序終止後馬上釋放該伺服器的埠，否則作業系統會保留幾分鐘該埠。
server_ctrl.bind((HOST, ctrl_port))
server_ctrl.listen(5)

####################
print("wait for MATLAB Input")
### 
conn_data, addr_data = server_data.accept()
conn_ctrl, addr_ctrl = server_ctrl.accept()
mask = 0xffffffff
data = np.zeros(1,dtype=np.int)
ADI = 0
# for i in range(16) :
#conn_data.recv_into(data,16)  # 16-bytes
i = 0
while True:
    print("wait for MATLAB Input ")
    i = i + 1
    print(i)
    conn_data.recv_into(data,4, socket.MSG_WAITALL)
    print("Receive from MATLAB Input")
    ADI = data[0]
    a = ADI.item()
    print(a)
    if a >= 25427968:
        print('Configuration Finished')
        break
    SPI_channel.write(0x0, mask)    
    SPI_channel.write(a, mask) #   SPIWrite	009,17	// Enable Clocks

# data = conn_data.recv(16)
```
```HOST```的IP位置要記得改。
這個Matlab的Config要配上一個Matlab的程式：
>PYNQ_AD9361_PP_AGC.m
>裡面tcpip的IP位置也要改。

Run這段的話結果應該會輸出：wait for MATLAB Input。
![](https://i.imgur.com/WEerYZ6.png)
接著就到Matlab去跑上面的那個 .m檔。
```0
global ctrl_client data_client
ctrl_client = tcpip('192.168.2.99', 8000, 'NetworkRole', 'client', 'Timeout', 10); %QAM
%ctrl_client = tcpip('192.168.2.99', 8002, 'NetworkRole', 'client', 'Timeout', 5); %decoder
fopen(ctrl_client);
data_client = tcpip('192.168.2.99', 8001, 'NetworkRole', 'client', 'Timeout', 10); %QAM
%data_client = tcpip('192.168.2.99', 8003, 'NetworkRole', 'client', 'Timeout', 5); %decoder
data_client.InputBufferSize = 2^20;
data_client.OutputBufferSize = 2^20;
data_client.ByteOrder='littleEndian'; %'bigEndian'; %; %
fopen(data_client);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
PYNQ_SPIWrite('3DF', '01');
PYNQ_SPIWrite('2A6', '0E’);
…

```
.m檔當中的```PYNQ_SPIWrite```函數可以由下面給定：
```0
% Addr: 10 bits address in hex
% Data: 8 bits data in hex
% e.g. PYNQ_SPIWrite('3DF', '01');
% data_client: TCP/IP client port
function A=PYNQ_SPIWrite(Addr,Data)
global data_client
A=hex2dec(['18' Addr Data]);
fwrite(data_client,A,'uint32')
end
```
```tcpip```的IP位置也要記得改，如果有成功的話應該會如下圖。
![](https://i.imgur.com/9hd9cNc.png)
這邊總共寫入了2585行，這樣就算Condig完了。

## Spectrum
完成前面的步驟且都沒有問題的話，接下來可以接上頻譜分析儀，應該看的到東西。
在Juypter中可以給定產生訊號的頻率大小：
```0
DDS_Write(2048)    # check
```
根據公式，$f_{out}=\frac{2048}{2^{16}}\cdot Fc=3.125$MHz

Matlab的.m檔裡面也可以更改頻率：
> PYNQ_SPIWrite('271','78')是2.4GHz？

也可以在Jupyter上面驗證：
```0
print('Tx Carrier Frequency')
PYNQ_SPIRead('???')
...
```

---
# Tx with SG_LUT
SG_LUT: Signal Generator Look Up Table。
![](https://i.imgur.com/7eYOHLC.png)
![](https://i.imgur.com/DYBFtrd.png)

## SG LUT
**Add source $\to$ Add or create source $\to$ Create file**
**File name: SG_LUT**
```0
module SG_LUT(CLK, RSTN, SG_out_tdata_I, SG_out_tdata_Q, SG_out_tvalid, SG_out_tready);
input CLK, RSTN;
output  [11:0] SG_out_tdata_I, SG_out_tdata_Q;
output SG_out_tvalid;
input   SG_out_tready;
wire [11:0] LUT_I [0:15];
wire [11:0] LUT_Q [0:15]; 
reg  [3:0]  LUT_addr;
wire LUT_RD;    

always @(posedge CLK)
    if (~RSTN)
        LUT_addr <= 0;
    else if (LUT_RD)
        LUT_addr <= LUT_addr  + 1;

assign LUT_I[0] = 16384;    // = ???;
assign LUT_I[1] = 15137;    // …
assign LUT_I[2] = 11585;
assign LUT_I[3] = 6270;
assign LUT_I[4] = 0;
assign LUT_I[5] = -6270;
assign LUT_I[6] = -11585;
assign LUT_I[7] = -15137;
assign LUT_I[8] = -16384;
assign LUT_I[9] = -15137;
assign LUT_I[10] = -11585;
assign LUT_I[11] = -6270;
assign LUT_I[12] = 0;
assign LUT_I[13] = 6270;
assign LUT_I[14] = 11585;
assign LUT_I[15] = 15137;

assign LUT_Q[0] = 0;
assign LUT_Q[1] = 0;
assign LUT_Q[2] = 0;
assign LUT_Q[3] = 0;
assign LUT_Q[4] = 0;
assign LUT_Q[5] = 0;
assign LUT_Q[6] = 0;
assign LUT_Q[7] = 0;
assign LUT_Q[8] = 0;
assign LUT_Q[9] = 0;
assign LUT_Q[10] = 0;
assign LUT_Q[11] = 0;
assign LUT_Q[12] = 0;
assign LUT_Q[13] = 0;
assign LUT_Q[14] = 0;       // …
assign LUT_Q[15] = 0;       // = ???;

assign SG_out_tdata_I = LUT_I[LUT_addr];
assign SG_out_tdata_Q = LUT_Q[LUT_addr];

assign SG_out_tvalid = 1;
assign LUT_RD = SG_out_tvalid & SG_out_tready;
endmodule
```
註解中的```???```要自己給定，這邊在Matlab給定一個信號，振幅$2^{14}$，頻率剛好1個週期：
```0
s_B = round(2^14*exp(j*2*pi/16*[0:15]));
s_I = real(s_B).'
```
把這裡面的值填入 I 就會變成上面的樣子，為求方便 Q 先都給0。
做好之後把**SG_LUT**拉出來到Block design。
CLK和RSTN接系統的，tvalid和tready接到**DAC**的tvalid和tready上。
I 和 Q 接到**DAC**的 I 和 Q 上，**DAC**的第二個 I 和 Q 接可以反著接回 Q 和 I。

## Constraints
**Add source $\to$ Add or create Constraints $\to$ Create file**
**File name: Zedboard_AD9361_ADC_DAC**
```0
# constraints
# Zedboard AD9361 LVCMOS DDR, FDD, Dual-Port
set_property  -dict {PACKAGE_PIN  M19  IOSTANDARD LVCMOS25} [get_ports ADI_DATA_CLK]; ## G6 FMC_LPC_LA00_CC_P
set_property  -dict {PACKAGE_PIN  N19  IOSTANDARD LVCMOS25} [get_ports ADI_RX_FRAME]; ## D8   FMC_LPC_LA01_CC_P
set_property  -dict {PACKAGE_PIN  J21  IOSTANDARD LVCMOS25} [get_ports ADI_FB_CLK]; ## G12  FMC_LPC_LA08_P
set_property  -dict {PACKAGE_PIN  R20  IOSTANDARD LVCMOS25} [get_ports ADI_TX_FRAME]; ## D14  FMC_LPC_LA09_P

set_property  -dict {PACKAGE_PIN  N18  IOSTANDARD LVCMOS25} [get_ports ADI_P0_D[0] ]; ## H17  FMC_LPC_LA11_N
set_property  -dict {PACKAGE_PIN  N17  IOSTANDARD LVCMOS25} [get_ports ADI_P0_D[1] ]; ## H16  FMC_LPC_LA11_P 
set_property  -dict {PACKAGE_PIN  P21  IOSTANDARD LVCMOS25} [get_ports ADI_P0_D[2] ]; ## G16  FMC_LPC_LA12_N
set_property  -dict {PACKAGE_PIN  P20  IOSTANDARD LVCMOS25} [get_ports ADI_P0_D[3] ]; ## G15  FMC_LPC_LA12_P
set_property  -dict {PACKAGE_PIN  M17  IOSTANDARD LVCMOS25} [get_ports ADI_P0_D[4] ]; ## D18  FMC_LPC_LA13_N
set_property  -dict {PACKAGE_PIN  L17  IOSTANDARD LVCMOS25} [get_ports ADI_P0_D[5] ]; ## D17  FMC_LPC_LA13_P
set_property  -dict {PACKAGE_PIN  T19  IOSTANDARD LVCMOS25} [get_ports ADI_P0_D[6] ]; ## C15  FMC_LPC_LA10_N
set_property  -dict {PACKAGE_PIN  R19  IOSTANDARD LVCMOS25} [get_ports ADI_P0_D[7] ]; ## C14  FMC_LPC_LA10_P
set_property  -dict {PACKAGE_PIN  K20  IOSTANDARD LVCMOS25} [get_ports ADI_P0_D[8] ]; ## C19  FMC_LPC_LA14_N
set_property  -dict {PACKAGE_PIN  K19  IOSTANDARD LVCMOS25} [get_ports ADI_P0_D[9] ]; ## C18  FMC_LPC_LA14_P
set_property  -dict {PACKAGE_PIN  J17  IOSTANDARD LVCMOS25} [get_ports ADI_P0_D[10] ]; ## H20  FMC_LPC_LA15_N
set_property  -dict {PACKAGE_PIN  J16  IOSTANDARD LVCMOS25} [get_ports ADI_P0_D[11] ]; ## H19  FMC_LPC_LA15_P

set_property  -dict {PACKAGE_PIN  P18  IOSTANDARD LVCMOS25} [get_ports ADI_P1_D[0] ]; ## H8   FMC_LPC_LA02_N
set_property  -dict {PACKAGE_PIN  P17  IOSTANDARD LVCMOS25} [get_ports ADI_P1_D[1] ]; ## H7   FMC_LPC_LA02_P 
set_property  -dict {PACKAGE_PIN  P22  IOSTANDARD LVCMOS25} [get_ports ADI_P1_D[2] ]; ## G10  FMC_LPC_LA03_N
set_property  -dict {PACKAGE_PIN  N22  IOSTANDARD LVCMOS25} [get_ports ADI_P1_D[3] ]; ## G9   FMC_LPC_LA03_P 
set_property  -dict {PACKAGE_PIN  M22  IOSTANDARD LVCMOS25} [get_ports ADI_P1_D[4] ]; ## H11  FMC_LPC_LA04_N 
set_property  -dict {PACKAGE_PIN  M21  IOSTANDARD LVCMOS25} [get_ports ADI_P1_D[5] ]; ## H10  FMC_LPC_LA04_P
set_property  -dict {PACKAGE_PIN  K18  IOSTANDARD LVCMOS25} [get_ports ADI_P1_D[6] ]; ## D12  FMC_LPC_LA05_N 
set_property  -dict {PACKAGE_PIN  J18  IOSTANDARD LVCMOS25} [get_ports ADI_P1_D[7] ]; ## D11  FMC_LPC_LA05_P
set_property  -dict {PACKAGE_PIN  L22  IOSTANDARD LVCMOS25} [get_ports ADI_P1_D[8] ]; ## C11  FMC_LPC_LA06_N
set_property  -dict {PACKAGE_PIN  L21  IOSTANDARD LVCMOS25} [get_ports ADI_P1_D[9] ]; ## C10  FMC_LPC_LA06_P
set_property  -dict {PACKAGE_PIN  T17  IOSTANDARD LVCMOS25} [get_ports ADI_P1_D[10] ]; ## H14  FMC_LPC_LA07_N 
set_property  -dict {PACKAGE_PIN  T16  IOSTANDARD LVCMOS25} [get_ports ADI_P1_D[11] ]; ## H13  FMC_LPC_LA07_P

set_property  -dict {PACKAGE_PIN  J20  IOSTANDARD LVCMOS25} [get_ports ADI_ENABLE]; ## G18  FMC_LPC_LA16_P
set_property  -dict {PACKAGE_PIN  K21  IOSTANDARD LVCMOS25} [get_ports ADI_TXNRX]; ## G19  FMC_LPC_LA16_N
```
接下來就可以用Jupyter來驗證結果。

---
# Verification of DAC/ADC interface
AD9361 Data interface
![](https://i.imgur.com/ehmFsKU.png)
用模擬的方式去做DAC和ADC的介面。
因為我們會用SPI的programing去控制AD9361的狀態，所以下面的enable和TXNRX不用去接。

## DAC interface
![](https://i.imgur.com/6TZnMOy.png)
外接的CLK，上面我們現在的設定式30.72的兩倍所以大概60MHz；下面的CLK是系統設定的100MHz。
事實上系統的CLK設定不用到100MHz，至少要能夠讓Tx_Source使用(60/2MHz)就好。
![](https://i.imgur.com/FxxJyKC.png)
2T2R就是會有兩對 I 和 Q channel。
設計上一個CLK會傳一個 I1 和一個 Q1，下一個CLK會傳一個 I2 和一個 Q2。
現在的CLK是由~60MHz的CLK給定。
所以Tx_Source的速度會是一半，就是大概30MHz。
需要的東西有：
1. ADI_DAC_Interface_AXIS.v
2. DELAY_CLOCK.v
3. DDR_IQ_DAC.v
4. Asyn_FIFO_48x512 (FIFO Generator IP)
5. Tx_Source
Tx_Source可以用DDS，但是這邊只是模擬結果而已所以不用那麼複雜。
這邊的Source是用Counter：
![](https://i.imgur.com/Hxa1LM9.png)
```0
module Counter_AXIS(CLK, RSTN, C_en, m_axis_tvalid, m_axis_tready, m_axis_tdata, m_axis_tlast);
parameter DW = 16;
parameter C_init = 0, C_inc = 1;	
parameter CP_log2 = 4, CP_size = 2**CP_log2;				 
input  CLK, RSTN;
output m_axis_tvalid, m_axis_tlast;
input  m_axis_tready, C_en;
output [DW-1:0] m_axis_tdata;
reg [DW-1:0] CC;
wire CC_en, CC_last;
  
always @(posedge CLK)
    if (~RSTN)
        CC <= C_init;
    else if (CC_en)
        CC <= CC + C_inc;
  	  
assign m_axis_tvalid = C_en;
assign CC_en = m_axis_tvalid & m_axis_tready;
assign m_axis_tdata = CC;
assign CC_last = (CC[CP_log2-1:0] == (CP_size - 1));
assign m_axis_tlast = CC_last;
endmodule

```
### Design
![](https://i.imgur.com/uZSVQ15.png)

#### CLK source
從Source加入/NCU_SDR_Verilog_LIB_2022/AD9361裡的**CLK_RSTN_G**。
從Source拉出兩個CLK至Block diagram中，一個是CLK Data是AD9361提供的Data，所以可以改名叫**CLK_DATA**；另一個**CLK_RSTN_G_0**保留原來的設定，作為系統的CLK。
![](https://i.imgur.com/imc3Udg.png)
CLK裡面有Half period (Half P)，5表示100MHz，所以**CLK_DATA**要提供60MHz的話要改成8。
Start (St)是讓Reset晚一點上來，模擬比較真實的狀態。

#### DAC Interface AXIS
從Source加入/NCU_SDR_Verilog_LIB_2022/AD9361裡的**ADI_DAC_Interface_AXIS**。
如果這裡是從新的Project開始做的話，要記得加上D**ELAY_CLK**、**DDR_IQ_DAC**和**Asyn_FIFO_48x512**。
拉進來之後，ADI_DATA_CLK接**CLK_DATA**的CLK、CLK接**CLK_RSTN_G_0**的CLK。
**CLK_RSTN_G_0**的RSTN接系統的RSTM，**CLK_DATA**的RSTN先不接。
CLK信號就接好了，接著就要產生Tx_Source去產生DAC的3個output：
![](https://i.imgur.com/OyA70yg.png)

#### Counter
從Source加入/NCU_SDR_Verilog_LIB_2022裡的**Counter_AXIS**。
這邊拿**Counter_AXIS**作為Tx_Source。
CLK接上系統的CLK，RSTN接RSTN，因為RSTN會為高電位所以C_en也接上RSTN。
設定上先把Dw改成12，因為 I1 I2 Q1 Q2 都是12bits。
先做 I1 的Data，所以把名稱改成**SG_i1**，設定中C increment (C Inc)為+1。
填入想輸入的數值，這邊預設為自己的學號，所以在C lnit填入學號：

    因為相對於12bits的大小，學號太大，所以這邊改成用mod(學號,2^12)
![](https://i.imgur.com/ToEMMIQ.png)
完成後把輸出的Data接到Tx1的 I ，然後複製起來做 I2 。
I2 的基本接線一樣，C Inc為+2，把輸出接到Tx2的 Q 就好。
Q1 的C Inc 為-1，Q2 的C Inc 為-2。
因為所有Counter的Valid都一直是High的，所以Valid接 I1 的就好。
Ready則是都接同一個就好了，就會變成用Ready來控制。
DAC的3個Output這邊Make external。
主要是因為寫出去的速度和讀進來的速度不一樣，所以要用Ready來控制。
這樣到時候就可以觀察 I1 I2 Q1 Q2 是怎麼跑的，時間點是不是對的。
完成後Validate然後Creat HDL wrapper。

### Simulation
這次要看Simulation，所以要把**Source$\to$Simulation Source**裡面把這個Design Set as top。
完成後Run Simulation，Zoom fit應該會看到：
![](https://i.imgur.com/DivEbB8.png)
我們要關注的是ADI_P1_D_0的值，可以看到在前面有一些狀態造成比較晚才Ready出來，原因在Asyn FIFO上，不過這部分比較沒有掌握。
現在顯示的數字是hex的數字，可以**右鍵$\to$Radix$\to$Unsigned Decimal**看Dec的數值，這裡用Unsigned的原因在方便，不然在2048~4096之間的值會變負的。
放大之後可以跟前面**DAC interface**中的第二張圖做比較，看看順序是否為 I1 Q1 I2 Q2。
![](https://i.imgur.com/4UpBhOP.png)
0之後的第一個數值應該要是打在C lnit上的數值，在這裡前面輸入的是2350。
按照先前的設定，1個Frame有2個CLK，每個Frame中依序為：[I1 +1][Q1 -1][I2 +2][Q2 -2]。
至此就可以算是驗證完DAC了。

## ADC interface
![](https://i.imgur.com/iHUAm7m.png)
這邊一樣是AXIS的機制，此外為了要全速讀取，把Valid和Ready接起來。
讀出的出度跟在DAC寫進的速度一樣，都是60/2MHz。

### Design
![](https://i.imgur.com/qf5OawR.png)

### ADC interface AXIS
從Source加入/NCU_SDR_Verilog_LIB_2022/AD9361裡的**ADI_ADC_Interface_AXIS**。
若之前沒有做過的話要在Source加入**DDR_IQ_ADC**。
接上系統的CLK和RSTN，自己的Valid和自己的Ready相接。
AD_DATA_CLK和**DAC interface**的ADI_FB_CLK相接，Rx_Frame接Tx_Frame，P0_D接P1_D。
實際上這些都是應該要接去AD9361的驗證板的FMC上的Connector，透過印刷電路板接到FPGA的特定腳位，所以把FPGA的特定角為寫出來就可以寫成Constraint file。
ADC的4路Input這邊Make external。
這樣就可以把DAC和ADC整合在一起。
ADC的驗證比較麻煩，在PYNQ的環境就要把資料錄到FIFO去再用DMA把資料讀回來，這個部分就是Rx Buffer Engine。

### Simulation
P1_D是在DAC已經看過的部分，這裡著重在Rx1_I Rx1_Q Rx2_I Rx2_Q，先把這5個port都變成Dec。
![](https://i.imgur.com/klEkmVn.png)
雖然Rx比P1_D還有晚幾的CLK，不過可以仍然可以對數值。
RX1_I對到第1格，RX1_Q對到第2格，RX2_I對到第3格，RX2_Q對到第4格，符合所預想的。
至此ADC的驗證也算完成了。
這些都是模擬，至後就可以直接接AD9361的東西，到時候Constraints要弄好。

---
# Tx buffer engine
Tx_Source原先是DDS或是Look Up Table提供信號。
如果我們希望特過Python或是Matlab寫到Hardware的PL上的記憶體空間，要重複播送的這一個模式就叫做**Tx buffer engine**。
![](https://i.imgur.com/zm3w7Lc.png)
這個東西可以直接接Python或是**DMA**來做，但是需要先了解**Tx buffer engine**在幹嘛、要怎麼用。
模組最重要的部分是<font color="#f00">紅色</font>圈起來的mode的部分，會加上一個GPIO在這裡來控制功能。這裡也可以改成用Write的。
比較大的IP是Controller，記憶空間是上面的FIFO。
設計上**Counter**是主動Real time的，會一直Valid，所以控制交由TXBE_TB的輸入值來控制。

## Design
![](https://i.imgur.com/C0b91a8.png)
實際使用上如上圖，Python寫到**DMA**由MM2S會接到TXBE_in上面，tdata會接到**DAC**上面。這邊先模擬。
步驟照著左上角做，0的reset可以清空FIFO換下一組資料使用。
![](https://i.imgur.com/Z2ZBwdt.png)

### TXBE Controller
![](https://i.imgur.com/VUuOJES.png)
```0
module TXBE_Controller(CLK, RSTN, TXBE_mode_in, TXBE_in_tdata, TXBE_in_tready,
                TXBE_in_tvalid, TXBE_out_tdata, TXBE_out_tready, TXBE_out_tvalid,
                FF_in_tdata, FF_in_tready, FF_in_tvalid, FF_out_tdata, FF_out_tready,
                FF_out_tvalid, FF_rstn, TXBE_RST_mode, TXBE_WR_mode, TXBE_CPB_mode,
                TXBE_SPB_mode);
    parameter DW = 32; 
    input  CLK, RSTN;
    input  [1:0] TXBE_mode_in;
    output TXBE_in_tready, TXBE_out_tvalid, FF_out_tready, FF_in_tvalid, FF_rstn;
    input  TXBE_in_tvalid, TXBE_out_tready, FF_in_tready, FF_out_tvalid;
    input  [DW-1:0] TXBE_in_tdata, FF_out_tdata;
    output [DW-1:0] TXBE_out_tdata, FF_in_tdata;
    output TXBE_RST_mode;   // Buffer Reset mode
    output TXBE_WR_mode;    // Buffer Write mode
    output TXBE_CPB_mode;   // Buffer Cyclic Playback mode
    output TXBE_SPB_mode;   // Buffer Single Playback mode
// TX Mode/Parameter Configuration
wire [1:0] TX_mode;
assign TX_mode = TXBE_mode_in;
assign TXBE_RST_mode = (TX_mode == 0);
assign TXBE_WR_mode = (TX_mode == 1); 
assign TXBE_CPB_mode = (TX_mode == 2);
assign TXBE_SPB_mode = (TX_mode == 3);
// Data Path:
assign FF_in_tdata = TXBE_CPB_mode ? FF_out_tdata : TXBE_in_tdata;
assign TXBE_out_tdata = TXBE_out_tvalid ? FF_out_tdata : 0;
// Control Signal:
wire FF_RD, FF_WR;
assign FF_WR = FF_in_tvalid & FF_in_tready;
assign FF_RD = FF_out_tvalid & FF_out_tready;
assign TXBE_in_tready = TXBE_WR_mode ? FF_in_tready : 0;
assign TXBE_out_tvalid = (TXBE_CPB_mode | TXBE_SPB_mode) ? FF_out_tvalid : 0; 
assign FF_out_tready = (TXBE_CPB_mode | TXBE_SPB_mode) ? TXBE_out_tready : 0;
assign FF_in_tvalid = TXBE_WR_mode ? TXBE_in_tvalid : (TXBE_CPB_mode & FF_RD);
assign FF_rstn = RSTN & ~TXBE_RST_mode;
endmodule
```
如此一來就可以用程式軟體產生任意的波型，存在TX_BF_Engine裡面，重複播放出去。
Size由FIFO的大小決定。

### TXBE TB
原則上就是設定延遲幾個CLK時要做什麼事，TXBE_mode要給多少值
```0
module TXBE_TB(TXBE_mode_in);
output [1:0] TXBE_mode_in;
reg [1:0] A;
initial 
    begin 
        A = 0;         // Reset
        #800.1 A = 1;  // Load
        #160 A = 0;     
        #400 A = 1;
        #220 A = 2;    // Continuous Playback
        #400 A = 0;
        #100 A = 1;
        #320 A = 2;
        #300 A = 3;
    end
assign TXBE_mode_in = A; 
endmodule
```
控制的設計(模式或是狀態)：
1. TXBE_mode = 0 → Reset
Reset buffer (reset FIFO)
2. TXBE_mode = 1 → Down Load
從TXBE_in寫信號進來，記憶體在FIFO中所以寫去FIFO。
寫到DMA沒有傳資料的話就會停止，就可以切換到下一個mode。
3. TXBE_mode = 2 → Continuous_Playback
播放，把剛才寫進FIFO的資料丟出去。
由FF_out進來的資料會經過某些處理與判斷由tdata傳出去。
要循環，所以FIFO丟出來的資料會順便再寫回FIFO。
4. TXBE_mode = 3 → Load & Single_Playback
現在這裡沒有Load。
主要就是播的時候不再寫回FIFO，所以FIFO播完就空了，不會有後續的輸出。

## Simulation
![](https://i.imgur.com/Incyuno.png)
除了tdata為其他都是狀態。
Valid和Ready會相同，所以只接Valid出來。
上面的4個mode分別對應到TXBE_mode的4種狀態，螢幕上面顯示從左到右的狀態變化分別為：
**Reset $\to$ Write $\to$ Reset $\to$ Write $\to$ Continuous Playback**
所以對照下來，看tdata：
**Reset**時tdata沒有值$\to$**Write**時沒有值$\to$**Reset**時把剛才寫的值清掉$\to$**Write**時再寫值進去$\to$**Continuous Playback**時重複播放剛才寫進去的值。
接者把tdata有寫值的部分放大來看：
![](https://i.imgur.com/bA5RMEo.png)
根據前面**TXBE_TB**的設計：```#220 A = 2;    // Continuous Playback```
**Continuous Playback**前面的**Write**有220個CLK，這邊可以看到**Write**的時間從1360ns至1580ns，確實為220個CLK。
接著是：```#400 A = 0;```
**Continuous Playback**從1580ns開始至1980ns，這邊呈現出來的是8~21的連續撥放。
雖然寫資料的個數有點不符預期(原本預期220個CLK應該要有22個數值)，但是可以看到確實是循環撥放，推測原因出在FIFO的Valid設定上。
>有試過如果給**Write**設定的時間從#320變成#420，則數值數量確實會增加10個。

![](https://i.imgur.com/POeJNOV.png)
最後可以看到**Single Playback**的結果，前面的**Continuous Playback**讀寫到27停下來切成**Single Playback**，所以**Single Playback**播放完所有的東西到27就沒東西了，後面就變成0。

# Rx buffer engine
Rx buffer engine就是隨時去錄信號，構造上同樣是Controller $+$ FIFO。

## Design
![](https://i.imgur.com/6qoVdi9.png)
![](https://i.imgur.com/5hpzEul.png)
![](https://i.imgur.com/Yw92tBy.png)

### Rx Controller
![](https://i.imgur.com/nG3ahHU.png)
```0
module RXBE_Controller(CLK, RSTN,
    RXBE_s_tvalid, RXBE_s_tready, RXBE_s_tdata,  // RX Buffer Engine input (from ADC)
    RXBE_m_tvalid, RXBE_m_tready, RXBE_m_tlast, RXBE_m_tdata, // RX Buffer Engine output (to  DMA)
    RX_FIFO_rstn,
    RX_FIFO_s_tvalid, RX_FIFO_s_tready, RX_FIFO_s_tdata, // FIFO input interface (output)
    RX_FIFO_m_tvalid, RX_FIFO_m_tready, RX_FIFO_m_tdata, // FIFO output interface (input)
    RXBE_mode, RXBE_capture_size,   //  RXBE Status
    RXBE_C_out, RXBE_state, RXBE_reset_mode, RXBE_capture_mode, RXBE_upload_mode);

input  CLK, RSTN;
input  RXBE_s_tvalid, RXBE_m_tready;
output RXBE_s_tready, RXBE_m_tvalid, RXBE_m_tlast;
input  [23:0] RXBE_s_tdata;    // from ADC
output [31:0] RXBE_m_tdata;    // to DMA
input  RX_FIFO_s_tready, RX_FIFO_m_tvalid;
output RX_FIFO_m_tready, RX_FIFO_s_tvalid, RX_FIFO_rstn; 
input  [23:0] RX_FIFO_m_tdata;
output [23:0] RX_FIFO_s_tdata;
input  [1:0] RXBE_mode;  // 0: reset, 1: carpture, 2: upload
input  [31:0] RXBE_capture_size;
output [15:0] RXBE_C_out;
output [7:0]  RXBE_state;
output RXBE_reset_mode, RXBE_capture_mode, RXBE_upload_mode;
/// RXBE Status 
assign RXBE_reset_mode = (RXBE_mode == 0);
assign RXBE_capture_mode = (RXBE_mode == 1); 
assign RXBE_upload_mode = (RXBE_mode == 2); 
assign RXBE_state = {RXBE_m_tvalid, RXBE_m_tready, RX_FIFO_m_tready, RX_FIFO_s_tready,
        RX_FIFO_m_tvalid, RXBE_upload_mode, RXBE_capture_mode, RXBE_reset_mode};
////  RXBE Input Interface 
assign RX_FIFO_s_tdata = RXBE_s_tdata;  // ADC ==> FIFO input
assign RXBE_s_tready = 1;  
/// RX FIFO Control
reg [31:0] RXB_C;
wire RXB_C_rst, RXB_C_inc, RXB_full, RXB_C_dec;
wire RX_FIFO_WR, RX_FIFO_RD; 
always @(posedge CLK)
    if (RXB_C_rst)
        RXB_C <= 0;
    else if (RXB_C_inc)
        RXB_C <= RXB_C + 1;
    else if (RXB_C_dec)
        RXB_C <= RXB_C - 1;

assign RXB_full = (RXB_C == RXBE_capture_size); 
assign RXB_C_rst = ~RSTN | RXBE_reset_mode;   
assign RX_FIFO_WR = RX_FIFO_s_tready & RX_FIFO_s_tvalid;
assign RX_FIFO_RD = RX_FIFO_m_tvalid & RX_FIFO_m_tready;
assign RXB_C_inc = RX_FIFO_WR;
assign RXB_C_dec = RX_FIFO_RD;
assign RX_FIFO_s_tvalid = RXBE_capture_mode & ~RXB_full & RXBE_s_tvalid;
assign RX_FIFO_m_tready = RXBE_upload_mode & RXBE_m_tready;
assign RX_FIFO_rstn = ~RXBE_reset_mode & RSTN;
assign RXBE_C_out = RXB_C;
////  RXBE Output Interface 
wire [11:0] I_in, Q_in;
wire [15:0] I_out, Q_out;
assign I_in = RX_FIFO_m_tdata[23:12];
assign Q_in = RX_FIFO_m_tdata[11:0];
assign I_out = {{4{I_in[11]}},I_in};  // Signed Extension
assign Q_out = {{4{Q_in[11]}},Q_in};  // Signed Extension
assign RXBE_m_tdata = {I_out, Q_out};
assign RXBE_m_tvalid = RXBE_upload_mode & RX_FIFO_m_tvalid;
assign RXBE_m_tlast = RX_FIFO_RD & (RXB_C == 1);
endmodule
```

### RXBE TB
原則上就是設定延遲幾個CLK時要做什麼事，RXBE_mode要給多少值
```0
module RXBE_TB(RXBE_mode_in, RXB_capture_size);
output [1:0] RXBE_mode_in;
output [31:0] RXB_capture_size;
reg [1:0] A;
reg [31:0] RXB_CS_reg;
initial 
    begin 
        A = 0;                        // Reset mode
        RXB_CS_reg = 16;              // RXB_capture_size = 16
        #400.1 A = 1;                 // Capture mode
        #320 A = 2;                   // Up-Load mode
        #400 RXB_CS_reg = 32;         // (Up-load done) RXB_capture_size = 32
        #100 A = 1;                   // Capture mode
        #400 A = 2;                   // Up-Load mode
        #400 A = 1;
        #320 A = 2;
        #400 A = 1;
        #320 A = 2;
    end
assign RXBE_mode_in = A; 
assign RXB_capture_size = RXB_CS_reg;
endmodule
```
控制的設計(模式或是狀態)：
1. RX_BF_mode = 0 → Reset
Reset buffer (reset FIFO)，原則上用不太到，因為Upload後FIFO就空了。
2. RX_BF_mode = 1 → Capture-to-RX_Frame_size
錄信號至FIFO，Size滿了就會停止。
Size的大小由**FIFO** depth決定。
3. RX_BF_mode = 2 → Upload Capture
4. RX_BF_mode = 3 → Capture & Upload (尚未編輯)
如果處理速度夠高的話可以一直把資料傳出去，送到**DMA**的S2MM存到PS的DDR裡面，達到Real time的效果。
AD9361的 I Q channel一路是12 bits $=$ 3 bytes，所以如果DDR有1GB的話，可以錄1/3G samples。
## Simulation
![](https://i.imgur.com/JbhoNXB.png)

---
# Verification AD9361 DAC/ADC interface

## Design
![](https://i.imgur.com/YrSptS4.png)
範例檔案在**AD9361_TX_DDS_RX_Buffer_Engine_2022_5_3**中

## Juypter
見影片VIII(4)。

---
# Tx buffer engine + Rx buffer engine + AD9361

## Design
![](https://i.imgur.com/X20VUNg.png)

## Design Flow
1. ZYNQ7 Processing System
Run Block Automation。
後面要接DMA，所以先打開High Performance (HP) Slave 的 S AXI HP0。

### SPI
2. AXI GPIO SPI
開始SPI的部分。
取名：**axi_gpio_SPI**。
開兩個port，32 bits all outputs 和 8 bits all inputs。
Run Connection Automation，只要接S_AXI。

3. Add SPI Source
![](https://i.imgur.com/m8jJHRO.png)
少**Ping_Pong_FIFO**，再加。

4. GPIO32 to AXIS24
拉進來接線：**axi_gpio_SPI**輸出接GPIO_in、CLK和RSTN接系統的。

5. AXIS Data FIFO
改名**axis_data_fifo_SPI**。
資料寬度可以小，但是穩定性問題設為512，independent CLK: YES。
**GPIO32 to AXIS24**的Master輸出接S_AXIS，Slave的CLK和RSTN接系統的，Master的CLK接**GPIO32 to AXIS24**的CLK_4。

6. AD9361 SPI interface
**AXIS DATA FIFO**的M_AXI接Slave輸入。
CLK接降速的CLK_4，RSTN接系統的RSTN。
SPI輸出接**axi_gpio_SPI**的8 bits輸入。
剩下Make external，名字把_0拿掉。

### DAC
7. DAC interface
少**Asyn_FIFO_48x512**，**IP catalog $\to$ FIFO Generator**。
**Native $\to$ Independent Clocks Distributed RAM $\to$ First Word Fall Through $\to$ W48, D512 $\to$ Finish $\to$ Global**。
Input的ADI_DATA_CLK make external、三個Output make external，把_0去掉。
CLK和RSTN都接系統的。

### DDS
8. AXI GPIO DDS TXBE
控制DDS和TXBE的mode。
取名：**axi_gpio_DDS_TX**。
第一個Port，給DDS的16 bits的All outputs、預設$2^8=0x00000100$。
第二個Port，給TXBE的3 bits的All outputs。
第3個bit: 0 $\to$ default DDS、第3個bit: 1 $\to$ TXBE。
Run Connection Automation，只要接S_AXI。

9. DDS
從LIB/CORDIC_IP中把所有DDS的相關Source都加進來。
CLK和RSTN都接系統的，frequency接**axi_gpio_DDS_TX**的16 bits的All outputs。

10. IQ Slice (16 to 12)
從LIB中加入Source: **IQ_Slice**。
```0
module IQ_Slice(IQ_in, I_out, Q_out, IQ_out);
parameter DW_in = 16, DW_out = 12;  // DW_out <= DW_in
input  [2*DW_in-1:0] IQ_in;
output [DW_out-1:0] I_out, Q_out;
output [2*DW_out-1:0] IQ_out;
assign I_out = IQ_in[(2*DW_in-1)-:DW_out];  // [31:20]
assign Q_out = IQ_in[(DW_in-1)-:DW_out];    // [15:4]
assign IQ_out = {I_out, Q_out}; 
endmodule
```
**DDS**的tdata接32 bits 的 IQ_in。

11. AXIS MUX
從LIB中加入Source: **M_AXIS_MUX**。
**Multiplexer**的作用是從多個輸入當中選擇一個輸出。
```0
module M_AXIS_MUX(m1_axis_tdata, m1_axis_tvalid, m1_axis_tready,
                  m2_axis_tdata, m2_axis_tvalid, m2_axis_tready,
                  m_axis_tdata, m_axis_tvalid, m_axis_tready,
                  MUX_sel);
parameter DW = 16;
input  m1_axis_tvalid,  m2_axis_tvalid,  m_axis_tready;
output m1_axis_tready,  m2_axis_tready,  m_axis_tvalid;
input  [DW-1:0] m1_axis_tdata, m2_axis_tdata;
output [DW-1:0] m_axis_tdata;
input  MUX_sel;
  
assign m_axis_tvalid = MUX_sel ? m2_axis_tvalid :  m1_axis_tvalid;
assign m_axis_tdata = MUX_sel ? m2_axis_tdata :  m1_axis_tdata;
assign m1_axis_tready = MUX_sel ? 0 : m_axis_tready;
assign m2_axis_tready = MUX_sel ? m_axis_tready : 0;
endmodule
```
這裡用```MUX_sel```來決定要傳哪個資料與下游哪個東西要Ready。
```DW```在上面被定義為16，但是在這裡配合TXBE的設定，要雙擊IP把DW改成24。
m1的tdata接**IQ_Slice**的24 bits IQ_out。
**DDS**的Valid和Ready接到m1的Valid和Ready，Output的Valid和Ready接到DAC的Tx Valid和Ready。

12. IQ Slice (IQ to I,Q)
複製一個**IQ_Slice**，把DW In改成12 bits。
把**M_AXIS_MUX**的Output tdata接到24 bits IQ_in，I_out接 I1, Q2、Q_out接 Q1, I2。

>目前的design應該長這樣↓
>![](https://i.imgur.com/n2mMb3U.png)

### TXBE
13. TXBE Controller
從LIB中加入Source: **TXBE_Controller_AD9361**。
這邊要確定**TXBE_Controller_AD9361**中Assign給S_I和S_Q的tdata目標範圍是否和前面**IQ_Slice**相同。
CLK和RSTN接系統的。
TXBE_out要接到**M_AXIS_MUX**的m2_axis。

14. AXIS Data FIFO TXBE
加入**AXIS Data FIFO**，改名**axis_data_fifo_TXBE**。
depth: 4096，Memory Type: Block RAM，DW 3 bytes。
把**TXBE_Controller**的FF_out接到M_AXIS、FF_in接到S_AXIS。
把**TXBE_Controller**的FF_rstn接到FIFO的RSTN，CLK接系統的。

15. Slice (TX_sel)
新增IP，輸入3 bits，從2到2取1 bit，取名：**xlslice_TX_sel**。
輸入端為**axi_gpio_DDS_TX**的第二個Port，輸出端接到**M_AXIS_MUX**的MUX_sel。

16. Slice (TX_mode)
複製**xlslice_TX_sel**，改取1到0取2 bits，取名：**xlslice_TX_mode**。
輸入端不變，輸出端為**TXBE_Controller**的TXBE_mode_in。

17. DMA
AXI DMA，取消Enable Scatter Gather Engine，Buffer length設20，Burst size兩邊都開256。
取名**axi_dma_TXRX**，Run Connection Automation，會自動接上GPIO使可以從PS設定。
HP0的部分有MM2S和S2MM兩個部分，所以Run Connection Automation要兩次。
MM2S要接到**TXBE_Controller**的TXBE_in。
剩下部分與RXBE公用。

18. AD9361_SPI
Create Hierarchy:
![](https://i.imgur.com/MVVPRVp.png)

19. AD9361_TX
Create Hierarchy:
![](https://i.imgur.com/cDh3MMH.png)

>發射機的部分完成後應該長這樣子↓

![](https://i.imgur.com/ScgZzje.png)

### ADC
20. ADC interface
ADI_DATA_CLK接上make external的**DAC interface**的ADI_DATA_CLK。
CLK和RSTN都接系統的。
ADI_RX_FRAME、ADI_P0_D和ADI_ENABLE make external然後把_0拿掉。

### RXBE
21. AXIS GPIO RX
新增IP，取名：**axi_gpio_RX**。
開兩個Port，32 bits的All outputs預設0x00000100和2 bits的All outputs。
Run Connection Automation，只選S_AXI。

22. AXIS Data FIFO RX
新增IP，取名：**axis_data_fifo_RX**。
depth: 4096，Memory Type: Block RAM。
RXBE controller的FIFO_m接口應該是24 bits，所以DATA Width改成3 bytes。
CLK和RSTN都接系統的。

23. RXBE Controller
從Source拉進來。
RX_FIFO_s接到**axis_data_fifo_RX**的S_AXIS。
RX_FIFO_m接到**axis_data_fifo_RX**的M_AXIS。
**ADC interface**裡面有Asyn_FIFO，所以這邊CLK接系統的就好。RSTN也接系統的。
RXBE_capture_size接到**axi_gpio_RX**的第一個Port。
RXBE_mode接到**axi_gpio_RX**的第二個Port。
RXBE_s的Valid和Ready接**ADC interface**裡面ADC_RX的Valid和Ready。
RXBE_m要接到**axi_dma_TXRX**的S_AXIS_S2MM。

24. Concat
新增IP，將兩個輸入首尾相接，輸入和輸出的Width都是Auto。
這邊輸出的LSB是先輸入的Port，MSB是最後輸入的Port，
所以In0輸入為**ADC interface**的Q1 ，In1輸入為**ADC interface**的I1。
輸出接到**RXBE Controller**裡面RXBE_s的Data。

25. AXI GPIO State
單純靜態觀測RX的狀態，取名：**axi_gpio_RX_ST**，開兩個Port。
16 bits的All inputs接**RXBE Controller**的RXBE_C_out。
8 bits的All inputs接**RXBE Controller**的RXBE_state。
Run Connection Automation。

26. AD9361_RX
Create Hierarchy:
![](https://i.imgur.com/7XVNDED.png)

>完成後應該長這樣子↓
![](https://i.imgur.com/Wo1Fb9k.png)
Validate確認沒有少接，CLK的Warning沒關係。

27. Add Constraints file
![](https://i.imgur.com/oB9MkEM.png)

28. Create HDL Wrper
29. Set as top
30. Generate Big Stream

## Jupyter
### Import Library
```0
from pynq import Overlay
import time
import numpy as np
from pynq.lib import AxiGPIO
import matplotlib.pyplot as plt
import pynq.lib.dma
from pynq import Xlnk

mask = 0xffffffff
xlnk = Xlnk()
```
### Overlay Hardware Design
```0
ol = Overlay("TXBE_RXBE_AD9361.bit")
ol?
```
### Hardware Design IPs (GPIO & DMA)
```0
# IP Blocks: 'axi_gpio_SPI', 'axi_gpio_DDS_TX', 'axi_gpio_RX', 'axi_gpio_RX_ST', 'axi_dma_TXRX'
# TXBE FIFO (4096 I/Q samples), RXBE FIFO (4096 I/Q samples)

SPI_ip = ol.ip_dict['axi_gpio_SPI']
AD9361_SPI_Output = AxiGPIO(SPI_ip).channel1
AD9361_SPI_Input = AxiGPIO(SPI_ip).channel2

DDS_TX_ip = ol.ip_dict['axi_gpio_DDS_TX']
DDS_Frequency = AxiGPIO(DDS_TX_ip).channel1
TXBE_mode = AxiGPIO(DDS_TX_ip).channel2   # bit[2]=0/1 ==> DDS/TXBE_out, bit[1:0]=0/1/2 ==> TX RST/TXBE_Load/TXBE_Play

RXBE_IP = ol.ip_dict['axi_gpio_RX']
RXBE_Capture_size = AxiGPIO(RXBE_IP).channel1  # Default = 256 
RXBE_mode = AxiGPIO(RXBE_IP).channel2   # 0/1/2 ==> reset/capture/up-load

RXBE_ST_IP= ol.ip_dict['axi_gpio_RX_ST']
RXBE_Counter = AxiGPIO(RXBE_ST_IP).channel1
RXBE_Status = AxiGPIO(RXBE_ST_IP).channel2

dma = ol.axi_dma_TXRX
```
```SPI_Output```是對我們而言的Output，是把資料送進SPI，反之```SPI_Input```是收資料回來。
```RXBE_mode```開機是reset，capture收資料，up-load傳資料然後清空capture。
```XBE_Counter```是RXBE寫了多少資料在裡面。
### AD9361 SPI function & Testing
```0
def PYNQ_SPIWrite(x, y):
# PYNQ_SPIWrite(‘007’, ‘08’)
    ADI9361_Conf_comm = int(x,16)*2**8 + int(y, 16)+2**24+2**23
    AD9361_SPI_Output.write(0x0, mask)
    AD9361_SPI_Output.write(ADI9361_Conf_comm, mask)  #SPIWrite
    print(x)

def PYNQ_SPIRead(x):
#    PYNQ_SPIRead(‘007’)
    ADI9361_Conf_comm = int(x + '00', 16) + 2**24 
    AD9361_SPI_Output.write(0x0, mask)
    AD9361_SPI_Output.write(ADI9361_Conf_comm, mask)  #SPIWrite
    SPI_DO = AD9361_SPI_Input.read()
    SPI_DO = hex(SPI_DO)
    print(SPI_DO)
    
PYNQ_SPIWrite('007', 'cc')
PYNQ_SPIRead('007')
```
### AD9361 Configuration
```0
f = open("PYNQ_AD9361_Configuration.txt")
line = f.readline()
while line:
    print(line)
    a = int(line,16)
    AD9361_SPI_Output.write(0x0, mask)    
    AD9361_SPI_Output.write(a, mask)
    time.sleep(0.001)
    line = f.readline()
f.close()
#print(line)
```
```time.sleep```讓讀取有等待0.001秒，讓速度慢一點。
### TX Source Functions
```0
def DDS_Write(x):
    DDS_Conf_comm = x
    DDS_Frequency.write(DDS_Conf_comm, mask)  #SPIWrite
def DDS_Frequency_Setting(f0):
    fs = 30.72
    w0_fix=int(np.mod(np.round(f0/fs*2**16),2**16))
    print(w0_fix)  
    DDS_Write(w0_fix)
    
def TX_Source_DDS():  # 0/1 ==> DDS/TXBE
    A = TXBE_mode.read()
    AA = np.mod(A,4)
    A = int(AA)
    print(A)
    TXBE_mode.write(A, mask)
def TX_Source_TXBE():  # 0/1 ==> DDS/TXBE
    A = TXBE_mode.read()
    AA = np.mod(A,4)
    A = int(4 + AA)
    print(A)
    TXBE_mode.write(A, mask)
def TXBE_RST():
    A = TXBE_mode.read()
    AA = np.floor(A/4)
    A = int(AA*4 + 0)
    print(A)
    TXBE_mode.write(A, mask)
def TXBE_Download():
    A = TXBE_mode.read()
    AA = np.floor(A/4)
    A = AA*4 + 1
    TXBE_mode.write(int(A), mask)
def TXBE_Play():
    A = TXBE_mode.read()
    AA = np.floor(A/4)
    A = int(AA*4 + 2)
    print(A)
    TXBE_mode.write(A, mask)
    
def TXBE_Load_Signal(s_I, s_Q, N, NN):  # N <= NN (NN <= 4096)
    TXBE_RST()   # RST
    input_buffer = xlnk.cma_array(shape=(NN,), dtype=np.uint32)
    s_Ia = np.mod((np.round(s_I)), 2**16)
    s_Qa = np.mod((np.round(s_Q)), 2**16)
    for i in range(N):
        input_buffer[i] = s_Ia[i] * 2**16 + s_Qa[i]
    TXBE_Download()
    dma.sendchannel.transfer(input_buffer)
    dma.sendchannel.wait()
```
```fs=30.72```事由前面的configuration給定的。
```TXBE_Load_Signal```的N是取信號的第N個點，NN是Input buffer的size。
### RX Buffer Engine Functions
```0
def RXBE_Capture_size_Conf(RXB_size): # RXB_size <= 4096
    RXBE_Capture_size.write(RXB_size, mask)
def RXBE_RST():
    RXBE_mode.write(0x0, mask) 
def RXBE_Capture():
    RXBE_mode.write(0x1, mask) 
def RXBE_Upload():
    RXBE_mode.write(0x2, mask) 
def AD9361_RX_Capture(RXB_size):
#    global output_buffer
    output_buffer = xlnk.cma_array(shape=(RXB_size,), dtype=np.uint32)
    RXBE_Capture_size_Conf(RXB_size)
    RXBE_Capture()    # Capture mode
    time.sleep(0.1)
    RXBE_Upload()     # Up-Load mode
#    time.sleep(0.1)
    dma.recvchannel.transfer(output_buffer)
    dma.recvchannel.wait()
    s_Q = np.floor(np.mod(output_buffer[0:N], 2**16))#LSB
    s_I = np.floor(output_buffer[0:N]/2**16) #MSB
    s_I = s_I - (s_I>32767)*2**16 #12bits sign interger 
    s_Q = s_Q - (s_Q>32767)*2**16
    return s_I, s_Q
```
最下面s_I和s_Q的部分在做把收到32 bits的轉換成16 bits的 I 和 Q channel。
### Check the function of RX Buffer Engine
測試RX最容易出錯的部分。
```0
# RXB_state = {RX_FIFO_s_tready, RX_FIFO_m_tvalid, RXB_upload_mode, RXB_capture_mode, RXB_reset_mode};
# RXB_state = {RXB_m_tvalid, RXB_m_tready, RX_FIFO_m_tready, RX_FIFO_s_tready, RX_FIFO_m_tvalid, RXB_upload_mode, RXB_capture_mode, RXB_reset_mode};
# '01010001'
# '01011010'
# '10011100'
print(RXBE_Capture_size.read())  # Default of RXBE Capture Size = 256
print(hex(RXBE_Status.read()))              # 0x51
print(RXBE_mode.read())          # 0
print(RXBE_Counter.read())       # 0 (RX Buffer is empty)
```
測試Reset mode。
第二行Print的State參照上面註解的第二行，0x51=0b01010001。
```0
RXBE_Capture() # Capture mode (default size = 256)
RXBE_ST = RXBE_Status.read()
print(RXBE_Capture_size.read())  # Should be 256
print(hex(RXBE_ST))          # Should be 0x5a
print(RXBE_mode.read())      # Should be 1
print(RXBE_Counter.read())   # Should be 256 (RX Buffer is loaded)
# If the result is incorrect
#     ==> relaod the bit and proceed all the cells in sequence agian
```
測試Capture mode。
Size應該不變但是RXBE的State應該要變0x5a=0b01011010，RXBE_mode=1。
重點是Counter錄了256 bits所以要是256，若是0的話代表沒有錄東西，reload。
```0
RXBE_Upload() # RXBE Uplaod mode
RXB_size = RXBE_Capture_size.read()
output_buffer = xlnk.cma_array(shape=(RXB_size,), dtype=np.uint32)
dma.recvchannel.transfer(output_buffer)
dma.recvchannel.wait() 
print(RXBE_Capture_size.read())  # 256
RXBE_ST = RXBE_Status.read()     
print(hex(RXBE_ST))              # 0x74
print(RXBE_mode.read())          # 2
print(RXBE_Counter.read())       # 0 (RX Buffer is empty)
```
測試Upload mode。
RXBE的State應該要變0x74=0b，Counter因為讀完了所以會變0。
### Experimenting the SDR Platform
#### Setting DDS Frequency
```0
fs = 30.72*0.5
f0 = fs/64/4
DDS_Frequency_Setting(f0) #120khz
```
f0後面除上的數值是取樣率，或是說每幾個點一個週期。
#### Setting TX Source as **DDS**
以DDS作為信號源，和Setting TX Source as **TXBE**擇一。
```0
TX_Source_DDS()
```
#### Setting TX Source as **TXBE**
以TXBE作為信號源，和Setting TX Source as **DDS**擇一。
```0
#Down Load the Captured Signal to TX Buffer Engine
N =1024
NN = 1024
TXBE_Load_Signal(s_I*2, s_Q*2, N, NN)
TXBE_mode.read()
```
```0
TX_Source_TXBE()
TXBE_Play()
```
切換源頭到TXBE，然後讓TXBE撥放，沒有TXBE_Play的話後面的結果會沒有信號或是直流狀態。
#### Setting TX/RX Carrier Frequency and Gain
```0
PYNQ_SPIWrite('271', '78'); #TX 2.4 ghz
PYNQ_SPIWrite('231', '7D'); #RX 2.5 ghz
```
> 將"數值"輸入"位置"決定設定，意義要參照Register MAP，例如
> ![](https://i.imgur.com/mUKvaSo.png)
> Tx的頻率位置是'271'數值給定為20MHz乘上'78'=0d120，為2.4GHz。
> ![](https://i.imgur.com/AQqTPa7.png)
> Rx的頻率位置是'231'數值給定為20MHz乘上'7D'=0d125，為2.5GHz。

### Caputure Signal and Display
```0
N = int(256*4)
nn = np.array(range(N))
fig, ax = plt.subplots()
s_I, s_Q = AD9361_RX_Capture(N)
ax.plot(nn, s_I)
ax.plot(nn, s_Q, linewidth=3.0)
ax.set(xlim=(0, N), ylim=(-2048, 2048)) 
plt.show()

print(s_I)
print(s_Q)
```
Default size為256有 I1 I2 Q1 Q2 所以是1024。
ylim的範圍由12 bits所決定，所以是$\pm2^{11}$。
圖的部分，TX_Source為DDS時應為滿屏的弦波；TX_Source為TXBE時應為半屏弦波剩下為0。
```0
np.savetxt('s_I.txt',s_I,fmt='%d')
np.savetxt('s_Q.txt',s_Q,fmt='%d')
```
把資料存成.txt檔。

### Down load the captured signal to TXBE
```0
N = 512
NN = 512
TXBE_Load_Signal(s_I*2, s_Q*2, N, NN)
```
乘2單純放大信號。

### Control AD9361 Tx frequency
```0
# 頻率的設定範圍限制在 47MHz 到 6000 MHz
# 輸入單位為 MHz
def AD9361_TXRXC_Conf(Tx_C, Rx_C):
    f_ref = 80
    
    # 計算 VCO Divider
    Tx_VCO = int(np.floor(np.log2(12000/Tx_C)))-1
    Rx_VCO = int(np.floor(np.log2(12000/Rx_C)))-1
    f = (Tx_VCO << 4) + Rx_VCO
    
    # 計算 Interger Word
    Tx_F_RFPLL = Tx_C * (2**((Tx_VCO)+1))
    Rx_F_RFPLL = Rx_C * (2**((Rx_VCO)+1))
    Tx_N_Integer = int(np.floor(Tx_F_RFPLL/f_ref))
    Rx_N_Integer = int(np.floor(Rx_F_RFPLL/f_ref))
    Tx_d = (Tx_N_Integer >> 8)
    Rx_d = (Rx_N_Integer >> 8)
    Tx_e = (Tx_N_Integer) & 0xff
    Rx_e = (Rx_N_Integer) & 0xff
    
    # 計算Fractional Word
    Tx_N_Frac = int(np.round(8388593*(Tx_F_RFPLL/f_ref - Tx_N_Integer)))
    Rx_N_Frac = int(np.round(8388593*(Rx_F_RFPLL/f_ref - Rx_N_Integer)))
    Tx_a = Tx_N_Frac & 0xff
    Rx_a = Rx_N_Frac & 0xff
    Tx_b = (Tx_N_Frac >> 8) & 0xff
    Rx_b = (Rx_N_Frac >> 8) & 0xff
    Tx_c = (Tx_N_Frac >> 16) & 0x7f
    Rx_c = (Rx_N_Frac >> 16) & 0x7f
    
    # 寫入 Tx Register
    PYNQ_SPIWrite('273', hex(Tx_a))
    PYNQ_SPIWrite('274', hex(Tx_b))
    PYNQ_SPIWrite('275', hex(Tx_c))    
    PYNQ_SPIWrite('272', hex(Tx_d))
    PYNQ_SPIWrite('271', hex(Tx_e))
    
    # 寫入Rx Register
    PYNQ_SPIWrite('233', hex(Rx_a))
    PYNQ_SPIWrite('234', hex(Rx_b))
    PYNQ_SPIWrite('235', hex(Rx_c))
    PYNQ_SPIWrite('232', hex(Rx_d))
    PYNQ_SPIWrite('231', hex(Rx_e))
    
    PYNQ_SPIWrite('005', hex(f))
    PYNQ_SPIWrite('014','23')
```

# Reference
訊號處理實驗室, 陳逸民 副教授, 軟體定義無線電平台技術 Technology of Software-Defined Radio Platform, CO6065





