循环卷积和周期卷积的关系_基于单口RAM读写的卷积电路(下)
這是遲到很久的卷積電路verilog設(shè)計(jì)的下篇。。。你看我還有機(jī)會嗎。。。
上回我們給出系統(tǒng)的層次結(jié)構(gòu)、卷積計(jì)算模塊以及用于數(shù)據(jù)緩存的fifo模塊,今天我們首先回顧一下上一次的關(guān)鍵內(nèi)容。
系統(tǒng)結(jié)構(gòu)回顧
RTL代碼文件可以分為結(jié)構(gòu)如下所示?
~|--top_conv_tb.v|--top_conv.v| |--sram_input.v| |--sram_weight.v| |--sram_output.v |--conv.v |--weight_addr_gen.v |--pixel_addr_gen.v |--conv_calculation.v |--conv_fifo.v |--fifo_wr_control.v其中top_conv.v為設(shè)計(jì)的頂層模塊,其只有start,reset,clk三個(gè)輸入端口以及一個(gè)輸出端口finish,top_conv.v模塊負(fù)責(zé)例化各子模塊,完成conv.v, sram_input, sram_onput以及sram_weight之間的連接。?
sram_input, sram_onput以及sram_weight三個(gè)子模塊用于存儲輸入和輸出,直接例化所給代碼。
conv模塊是實(shí)驗(yàn)中需要主要設(shè)計(jì)的模塊,共包含五個(gè)子模塊,各個(gè)子模塊的功能概述如下:
?weight_addr_gen模塊:用于在每個(gè)時(shí)鐘周期產(chǎn)生當(dāng)前單元計(jì)算卷積所需要的權(quán)重在sram中的地址;?pixel_addr_gen模塊:用于在每個(gè)時(shí)鐘周期產(chǎn)生當(dāng)前單元計(jì)算卷積所需要的像素值在sram中的地址;?conv_calculation模塊:利用得到的像素值和權(quán)重完成相乘、累加,并在當(dāng)前窗口全部像素計(jì)算完成后,產(chǎn)生輸出請求信號,并輸出該窗口的卷積值;?conv_fifo模塊:輸入數(shù)據(jù)的sram和計(jì)算模塊之間的緩沖模塊,接受像素?cái)?shù)據(jù)存儲器sram_input和權(quán)重?cái)?shù)據(jù)存儲器及sram_weight的輸出數(shù)據(jù),并向conv_calculation模塊輸出緩存數(shù)據(jù);?fifo_wr_control模塊:這一模塊較為簡單,為一級寄存器,用于調(diào)整兩個(gè)地址產(chǎn)生模塊的讀請求、讀地址信號和寫fifo之間的時(shí)序。
整合后的系統(tǒng)框圖如下所示:
下面就進(jìn)入到今天的新內(nèi)容,我們將介紹系統(tǒng)中最后一個(gè)稍微麻煩的地址產(chǎn)生模塊以及給出最后的結(jié)果展示。
地址產(chǎn)生模塊
對于權(quán)重的地址產(chǎn)生模塊較為簡單,只需要依次從0到8進(jìn)行循環(huán)即可:
//generate output weight_addralways @(*) beginif (start) begin case(weight_cnt) 4'd0: weight_addr = 0; 4'd1: weight_addr = 1; 4'd2: weight_addr = 2; 4'd3: weight_addr = 3; 4'd4: weight_addr = 4; 4'd5: weight_addr = 5; 4'd6: weight_addr = 6; 4'd7: weight_addr = 7; 4'd8: weight_addr = 8; default:weight_addr = `weight_addr_width'bx; endcaseendelseweight_addr = 0;end而對于像素的地址產(chǎn)生模塊,要相應(yīng)的復(fù)雜一些,這里分兩步進(jìn)行,首先產(chǎn)生每次計(jì)算時(shí)像素窗口第一個(gè)像素(左上角)的地址first_pixel,然后依次遍歷窗口內(nèi)的各個(gè)元素:
//generate the first position(top-left) in the conv windowalways @(posedge clk or negedge reset) beginif (!reset) first_pixel <= `pixel_addr_width'b0;else if (start&&pixel_cnt==8) begin case(first_pixel) 29,61,93,125,157,189,221,253,285,317,349,381,413, 445,477,509,541,573,605,637,669,701,733,765,797, 829,861,893,925: first_pixel <= first_pixel+3; default:first_pixel <= first_pixel+1; endcaseendelse first_pixel <= first_pixel;end//generate output pixel_addralways @(*) beginif (start) begin case(pixel_cnt) 4'd0: pixel_addr = first_pixel + 7'd0; 4'd1: pixel_addr = first_pixel + 7'd1; 4'd2: pixel_addr = first_pixel + 7'd2; 4'd3: pixel_addr = first_pixel + 7'd32; 4'd4: pixel_addr = first_pixel + 7'd33; 4'd5: pixel_addr = first_pixel + 7'd34; 4'd6: pixel_addr = first_pixel + 7'd64; 4'd7: pixel_addr = first_pixel + 7'd65; 4'd8: pixel_addr = first_pixel + 7'd66; default:pixel_addr = `pixel_addr_width'bx; endcaseendelsepixel_addr = 0;end結(jié)果展示
1.波形仿真
我們首先進(jìn)行波形仿真驗(yàn)證。運(yùn)行vivado對我們的設(shè)計(jì)進(jìn)行仿真驗(yàn)證,波形輸出如下圖所示:
仿真的關(guān)鍵信號大致可以分為讀sram,寫fifo,讀fifo,卷積計(jì)算以及輸出這五組,從圖中可以看出各個(gè)信號的時(shí)序滿足我們的設(shè)計(jì)需求,在開始信號start拉高后,conv_calculation模塊發(fā)出數(shù)據(jù)請求信號s_read_req,fifo即開始從sram中讀取數(shù)據(jù)。在fifo為非empty的后一個(gè)周期,fifo_read信號有效,conv_calculation開始從fifo中讀取數(shù)據(jù),在開始計(jì)算后的第九個(gè)周期,計(jì)算模塊產(chǎn)生當(dāng)前單元的正確卷積結(jié)果并發(fā)出完成信號sum_done用于輸出請求。
關(guān)于計(jì)算模塊的仿真波形區(qū)域放大圖,展示如下:
通過sram_output.v中的以下語句,我們將卷積輸出結(jié)果輸出到文本文件"Write_Out_File .txt中。
Write_Out_File =$fopen("Write_Out_File .txt");$fdisplay(Write_Out_File,"%h",s_write_data_b);Write_Out_File .txt文件共900行,對應(yīng)于輸出圖片的900個(gè)像素點(diǎn),安裝實(shí)驗(yàn)要求,load_txt_to_pic.py將txt中的矩陣結(jié)果轉(zhuǎn)化為輸出圖片并顯示。作為參考,我們可以調(diào)用python中opencv庫中的cv.filter2D函數(shù)進(jìn)行卷積運(yùn)算,結(jié)果如下所示:
可以看出經(jīng)過conv.v卷積邊緣提取后的結(jié)果仍能大致看出原有圖片輪廓,但是與直接運(yùn)用python中cv.filter2D相比,有些細(xì)節(jié)仍然有所丟失。這與我們實(shí)驗(yàn)中進(jìn)行數(shù)據(jù)定點(diǎn)化處理以及cv.filter2D中卷積計(jì)算時(shí)的優(yōu)化處理有關(guān)。另外,我們可以看出使用cv.filter2D后,圖片的大小仍然保持不變,這是由于在cv.filter2D中進(jìn)行了一定的填充插值處理。
2.計(jì)算資源消耗
?計(jì)算延時(shí):整個(gè)計(jì)算過程開始于start信號拉高的23ns,結(jié)束于finish信號拉高的89935ns,在一個(gè)時(shí)鐘周期為10ns的前提下,完成計(jì)算共需要8995個(gè)時(shí)鐘周期。?乘法器資源:實(shí)驗(yàn)中一共進(jìn)行了900次乘法運(yùn)算,但每9個(gè)時(shí)鐘周期內(nèi)只單獨(dú)進(jìn)行一次乘法運(yùn)算,所以總共需要一個(gè)乘法器。?訪存次數(shù):訪存需要的次數(shù)與參考設(shè)計(jì)中相同,即每次計(jì)算一個(gè)3*3卷積窗,從input ram和weight ram取數(shù)3*3次,并存output到output ram一次。共訪問input ram 30*30*3*3次,訪問weight ram 30*30*3*3次,訪問output ram 900次。總共訪問次數(shù)為17100次。
vivado綜合后的資源使用情況如下:
優(yōu)化方向
在前面的設(shè)計(jì)中,我們是按照卷積窗口的移動順序每次從sram中依次取出數(shù)據(jù),對于這樣方法,計(jì)算一個(gè)卷積窗口需要9次訪存,總共900個(gè)窗口需要900*9次訪存,然而事實(shí)上,窗口中的9個(gè)weight的數(shù)據(jù)在計(jì)算始終保持不變,而計(jì)算相鄰窗口時(shí)pixel的部分?jǐn)?shù)據(jù)也可以重復(fù)利用(數(shù)據(jù)復(fù)用),因此我們可以通過減少訪問模塊外部的sram的次數(shù)來提高系統(tǒng)的運(yùn)行速度。?
對于weight來說,如果我們將sram中的數(shù)據(jù)讀取后存儲在計(jì)算模塊內(nèi)的存儲器中,事實(shí)上總共只需要一次讀weight_sram的操作,從而可以大大減少訪問weight時(shí)的訪存次數(shù)。對于pixel來說,相鄰的窗口的部分?jǐn)?shù)據(jù)讀到conv模塊后可以進(jìn)行復(fù)用,如下圖所示,使用數(shù)據(jù)復(fù)用也可以減少pixel時(shí)的訪存次數(shù)。
另一種更加普遍的思路是將卷積計(jì)算轉(zhuǎn)化為矩陣乘法計(jì)算(im2col),對于矩陣乘法運(yùn)算有大量的優(yōu)化算法,并可以進(jìn)一步利用脈動陣列來實(shí)現(xiàn)計(jì)算的并行以及數(shù)據(jù)的重用(谷歌TPU的基本架構(gòu)),或者使用加法器和多個(gè)并行乘法器組成的加法樹完成計(jì)算的并行(寒武紀(jì)Diannao的基本架構(gòu))。這些優(yōu)化方法由于時(shí)間原因,沒有進(jìn)一步在現(xiàn)有的卷積電路上加以實(shí)現(xiàn),但我們會在后續(xù)的推文中給出使用HSL搭建的卷積電路,從中可以明顯看出在使用了流水線以及循環(huán)展開unroll來獲得226倍的加速比。
總結(jié)
以上是生活随笔為你收集整理的循环卷积和周期卷积的关系_基于单口RAM读写的卷积电路(下)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 麟龙指标通达信指标公式源码_通达信指标公
- 下一篇: 原神手游噗噗雪人怎么获得