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