# 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