傅里叶变换处理音频c++_KWS-SoC——基于Wujian100的音频流关键词检测SoC拓展开发笔记之一...
Keyword Spotting(KWS, 關鍵詞檢測),目的是在一串連續的音頻流中檢測出預定義的詞或者詞組。在實際應用中,比如手機的智能助手,智能住宅里所支持的語音指令等,都需要用到關鍵詞檢測,當用戶講出預定義的關鍵詞后,會觸發相應的功能。
一個明顯的問題是,這種技術如果應用在移動終端,為了保持隨時響應,需要連續不間斷的運行來偵測用戶的語音請求。因此,在滿足準確性的前提下盡可能地做到低功耗設計,就是移動端KWS技術的目標。
KWS的技術原理:簡而言之,就是首先對音頻信號進行逐幀預處理,通過短時傅里葉變換或者梅爾頻譜等處理,將音頻數據轉換為二維的聲譜圖,其中聲譜圖橫坐標就是時間軸,縱坐標是其頻率特征信息。因此,KWS的語音識別問題實際上可以轉換為圖像處理問題。傳統的基于神經網絡的KWS,采用了DNN或者CNN,將分類輸出與預先定義的聲學模型label進行比較,看是否觸發關鍵詞。
為了提高時域上的感知強度,很多KWS系統采用了RNN或者dilated CNN。然而這些方式都需要考慮到低功耗、延遲等問題。本文采用的KWS IP核是IA&C Lab的鄭日雍師兄基于Vivado的HLS流程開發的,該IP核是基于DS-RNN(深度可分離卷積-循環神經網絡)算法實現,在保證準確率的同時,也大幅提高了能效比,減少了內存需求,非常適合在低功耗平臺中使用。最終,把IP核集成在Wujian100這個低功耗MCU平臺上,使得低功耗的優勢更加彰顯。限于篇幅,本文不展開論述算法與其硬件實現過程,本專欄會在后續的帖子里專門介紹。
Wujian100是平頭哥開源的一個基于RISC-V架構的芯片設計平臺。該平臺是一款平頭哥半導體開發的超低功耗的MCU平臺,也是一款基于安全可信系統框架與CSI標準軟件接口,支持從硬件到軟件到系統的全棧敏捷開發,助力客戶打造面向極低功耗的MCU產品。
在此平臺基礎上,可以快速地進行IP核拓展并進行系統前端仿真,并對拓展的硬件設計部分進行功能驗證。在制作好FPGA以后,可以通過平臺提供的CDK軟件集成開發環境對系統進行軟件功能開發,用戶可進行代碼的編輯、在線仿真調試、模擬器調試、在線編程、性能分析等工作。Wujian100平臺也提供了DebugServer工具,用于連接調試器和CPU在線仿真器,實現對CPU進行在線調試控制。
筆者在本篇文章中將展示將KWS加速核集成到Wujian100平臺上,并且進行功能仿真的例子。最終整個系統的實現已經在github上開源:KWS-SoC
KWS-SoC開源工程本文將簡要介紹開發的過程:
1. Wujian100仿真平臺的獲取
首先,需要準備Wujian100平臺:wujian100_open
相關的開發工具可以在平頭哥芯片開放社區(OCC)里下載,包括RISC-V工具鏈,CDK集成開發環境以及Debug工具。
關于仿真平臺的使用,平頭哥芯片開放社區的在線視頻中都已經詳細闡述?;旧暇褪窍仍趖ool目錄下的setup.sh中配置RISC-V工具鏈環境和VCS環境,然后在workdir目錄下,run在case中寫的測試程序即可進行仿真。
2. KWS模塊的集成
由于Vivado提供了豐富的IP核以及方便的用戶界面,在集成IP核時,筆者采用了Vivado 2019.2進行。具體集成的步驟如下所示
? 新建vivado工程
由于筆者采用的KWS IP核是采用HLS綜合得到的,且目前僅支持PYNQ-Z1 的開發板。如果需要在其他開發板移植,需要對HLS的源碼重新綜合,為了簡便,筆者在新建工程時直接選用了PYNQ-Z1 芯片。
? 導入wujian100平臺
接下來就是在項目工程中添加源碼了。將soc文件夾導入到design files中,將tb文件夾導入到simulation files中。需要注意的是,soc文件夾中有些文件是verilog header文件,tb文件都是system verilog文件,需要手動設置。
? 添加IP核傳輸接口
筆者用到的KWS IP核,其結構如下圖所示
KWS IP核可以看到,IP核需要四種數據的通道,分別是:
? data_in: 需要傳輸的預處理后的音頻幀數據
? weight: KWS IP核中神經網絡中所用到的已經訓練好的權重數據
? control: 用于控制權重和數據輸入的信號
? data_out: 輸出的數據,用于和label比較得到識別結果
除了數據通道,每個通道還有握手信號,以及表示stream傳輸結束的握手信號。
首先,下圖是Wujian100平臺的系統結構圖:
Wujian100系統結構圖為了保證傳輸速度,以及方便集成,筆者直接采用了BUS Matrix(HCLK)上的Dummy0/1/2/3作為四種數據的傳輸通道。wujian100平臺采用的是標準的AHB接口。為了能與KWS采用的axi-stream協議匹配,筆者對每個通道使用了vivado提供的ahb轉axi的接口,如下圖所示:
AHB-lite to AXI bridge由于KWS實際沒有用到完整的axi4協議,所以為了能使轉換bridge正常使用,需要對其中一些沒有用到的端口進行手動賦值,此處以data_in為例:
? ahb_hburst信號:該信號為ahb突發傳輸信號,由于wujian100暫時不支持突發傳輸,因此ahb的master端沒有給出burst信號,因此,此處需要將值賦值3'b0,指明單次傳輸
? data_in_axi_awvalid信號:由于data in數據僅為寫方向,所以將此信號賦值為1即可,表示寫地址通道永遠有效,而只將KWS的data_in_tvalid信號連接到wvalid,即寫數據valid信號即可
? B通道相關信號,該通道為axi4的從設備響應通道,KWS里并沒有對應的信號,因此需要對bvalid賦值恒為1,表示從設備始終響應
? 其他信號,比如和突發傳輸,傳輸錯誤信號等,KWS里都沒有利用到,所以需要對其進行相關賦值,具體賦值可以參考開源代碼。
最終集成好的IP核在SoC的層次位置,如下圖所示
KWS模塊在SoC中的位置? 例化memory
實際使用中,KWS所需要載入的權重數據已經大于200k,原生的SoC提供的4塊64kb的memory已經基本不能滿足仿真所需的數據存儲的要求。因此,筆者在ahb bus上的dmem接口例化了一個256kb的BRAM,用于存放權重數據 ,如下圖所示
BRAM在SoC中的位置需要注意的是,vivado提供的bram controller只支持axi接口,因此,需要像上一節講到的使用一個協議轉換接口,將ahb協議轉換為axi協議進行通訊。
3. 音頻數據預處理
以上就是整個系統的集成過程。接下來,為了能將數據正確的存放到mem中進行仿真,需要對數據進行預處理。
由于在KWS系統中,需要首先將音頻數據轉換為聲譜數據,這一過程可以用開源工程中的my_audio.py實現,通過調用my_mfcc將wav音頻轉換為梅爾頻率倒譜。
為了能測試KWS的計算結果,開源工程中test_mymodel_myaudio.py 腳本實現了對40組數據進行預處理和軟件模型計算的參考輸出,其中346行
fingerprints = my_audio.my_mfcc(my_spectrogram)將輸入數據轉化為單精度浮點數表示,350行中
prob = sess.run(net.probs[0], feed_dict = {net.fingerprint_input: fingerprints_3d})則是參考模型計算出的分類預測輸出,也是單精度浮點表示。對于計算出的結果,其輸出最大值所在的位置,對應預先模型訓練好的ds_rnn_labels.txt中label的位置,則是KWS識別出的詞匯。
4. KWS-SoC系統的仿真過程
? 編寫仿真程序
首先在wujian100的case目錄下新建仿真項目目錄,新建main.c文件。然后參考doc文件夾下的user_guide對四個ahb端口的基地址端口定義:
#define DATA_IN_BADDR 0x40010000; #define WEIGHT_IN_BADDR 0x40020000; #define CONTROL_IN_BADDR 0x40100000; #define DATA_OUT_BADDR 0x80000000;然后之后就可以對端口進行讀寫數據了,由于筆者是計劃weight放在第三步例化的bram中,data_in數據放在wujian100預留的sram1中,最終的data_out數據dump到sram0數據中,因此同樣需要定義這三個地址,都可以在User_guide中查閱。
volatile uint32_t RESULT_MEM_ADDR = 0x20000000; volatile uint32_t WEIGHT_MEM_ADDR = 0x30000000; volatile uint32_t DATA_IN_MEM_ADDR = 0x20010000;完成這個步驟后,就可以對對應地址進行讀寫,比如要將data_in的數據寫到kws的ip中,就可以用這句實現
*(volatile uint32_t *) DATA_IN_BADDR = *(volatile uint32_t *) (DATA_IN_MEM_ADDR+4*j);如果想讀數據,方法同理。
在測試中,權重數據有57244個,data_in輸入數據一組有490個,因此用兩個for循環,就可以完成所有數據的傳輸。
整個數據的傳輸流程:參考所使用的KWS ip使用流程,依次是control寫0,weight寫入57244個數據,control寫1,data_in寫入490個數據,data_out讀12個數據,control寫1,然后之后可以繼續寫490個data_in,讀12個data_out,control寫1次1,以此重復...筆者測試了多組輸入數據的情況,開源工程中默認的情況是只寫一組數據,有需求的讀者可以重寫程序進行編譯。
注:上述過程為CPU逐字讀寫數據的方式。wujian100提供了dmac可以實現將指定數量的數據從源地址搬運到目標地址,雖然也只是基于單次傳輸的形式,但是這種傳輸方式可以釋放CPU的負載。本實驗測試通過了DMA傳輸的方式,但是在開源工程中提供的程序是上述的CPU讀寫方式,如果有需求的讀者可以參考case中的dmac示例進行重新編譯程序。
程序寫好以后,按照wujian100官方給出的教程對程序進行編譯,筆者開發時,采用了官方提供的RISC-V工具鏈編譯程序。但是在仿真這個步驟時,由于采用了vivado的ip,在用最新版本的vivado導出VCS仿真腳本時出現了問題,所以最終沒有采用官方給出的VCS或者iverilog仿真腳本進行仿真。所以,只是采用了原版的SoC仿真平臺以及官方的仿真腳本生成指令。
因此,此步驟最好直接用原版的wujian100開源平臺進行編譯,否則會在仿真步驟報錯(對編譯沒有影響)。
編譯完成后,會在workdir目錄下生成仿真需要用到的16進制指令文件test.pat
? 在vivado中進行仿真
有了上述生成的16進制指令,就可以對整個KWS-SoC進行仿真了。在仿真之前,需要做一些準備工作:
首先需要將輸入單精度浮點數據轉換為16進制,此過程可以用到開源工程中的float2hex.py腳本,此腳本將40組數據轉換為16進制數據,同樣也對weight數據進行轉換。
然后需要在testbench中,在SoC復位后,首先將上一步的程序寫入wujian100的isram中,然后wujian100的core才能正常取值譯碼執行等。此過程wujian100平臺在testbench中已經給出,讀者只需要把path改為上一步workdir的路徑。
接下來,把輸入數據放到ram中
for(j=0;j<32'h490;j=j+1)begindata_one_word[31:0] = data_temp_mem_set1[j];wujian100_open_tb.x_wujian100_open_top.x_retu_top.x_smu_top.x_sms_top.x_sms1_top.x_sms_sram.x_fpga_spram.x_fpga_byte3_spram.mem[j][7:0] = data_one_word[31:24];wujian100_open_tb.x_wujian100_open_top.x_retu_top.x_smu_top.x_sms_top.x_sms1_top.x_sms_sram.x_fpga_spram.x_fpga_byte2_spram.mem[j][7:0] = data_one_word[23:16];wujian100_open_tb.x_wujian100_open_top.x_retu_top.x_smu_top.x_sms_top.x_sms1_top.x_sms_sram.x_fpga_spram.x_fpga_byte1_spram.mem[j][7:0] = data_one_word[15:8];wujian100_open_tb.x_wujian100_open_top.x_retu_top.x_smu_top.x_sms_top.x_sms1_top.x_sms_sram.x_fpga_spram.x_fpga_byte0_spram.mem[j][7:0] = data_one_word[7:0];end然后需要將權重數據放入BRAM中,由于權重數據太多,筆者直接通過在例化BRAM時,指定coe初始化文件的方式寫入了權重數據
最后是輸出結果寫入到ram中,因此在busmnt.v中添加dumpmemory的部分
for(k=0;k<32'd12;k=k+1)begin$fwrite(DATA_FILE_1, "%x" , wujian100_open_tb.x_wujian100_open_top.x_retu_top.x_smu_top.x_sms_top.x_sms0_top.x_sms_sram.x_fpga_spram.x_fpga_byte3_spram.mem[k]); $fwrite(DATA_FILE_1, "%x" , wujian100_open_tb.x_wujian100_open_top.x_retu_top.x_smu_top.x_sms_top.x_sms0_top.x_sms_sram.x_fpga_spram.x_fpga_byte2_spram.mem[k]);$fwrite(DATA_FILE_1, "%x" , wujian100_open_tb.x_wujian100_open_top.x_retu_top.x_smu_top.x_sms_top.x_sms0_top.x_sms_sram.x_fpga_spram.x_fpga_byte1_spram.mem[k]); $fwrite(DATA_FILE_1, "%xn" , wujian100_open_tb.x_wujian100_open_top.x_retu_top.x_smu_top.x_sms_top.x_sms0_top.x_sms_sram.x_fpga_spram.x_fpga_byte0_spram.mem[k]); end最終結果會把ram中的結果輸出到測試文件夾中,格式為16進制
接下來就可以Run simulation開始仿真,因為系統規模較大,因此仿真需要的時間比較久。
? 數據后處理
最終,在測試文件夾中可以得到輸出結果。為了得到最終的數據,可以用hex2float.py腳本進行16進制到浮點數的轉換,讀者可以將轉換后的結果與參考模型計算出的結果進行比對。同時,此腳本也會根據ds_rnn_label,最終print出預測的結果。
5. 總結
筆者在wujian100開源平臺上進行了拓展,將已有的IP核集成到soc平臺上并成功地進行了仿真驗證。當然,此過程仍有可以改進的地方,第一是可以把之前提到的所有過程集成到一個腳本中,減少人力操作的成本。第二是可以繼續生成bitstream,將SoC燒進板子中,繼續用官方提供的CDK和debug工具進行開發,最終可以實現上位機和板子之間的自動傳輸通訊。第三點是可以優化SoC的結構,比如control端口,其實并用不到ahb的高傳輸速率的特性,可以用另外的方式簡化接口,減少所用到的硬件資源。
總結
以上是生活随笔為你收集整理的傅里叶变换处理音频c++_KWS-SoC——基于Wujian100的音频流关键词检测SoC拓展开发笔记之一...的全部內容,希望文章能夠幫你解決所遇到的問題。