硬件描述語言的一個(gè)突出優(yōu)點(diǎn)就是指令執(zhí)行的并行性。多條語句能夠在相同時(shí)鐘周期內(nèi)并行處理多個(gè)信號數(shù)據(jù)。
但是當(dāng)數(shù)據(jù)串行輸入時(shí),指令執(zhí)行的并行性并不能體現(xiàn)出其優(yōu)勢。而且很多時(shí)候有些計(jì)算并不能在一個(gè)或兩個(gè)時(shí)鐘周期內(nèi)執(zhí)行完畢,如果每次輸入的串行數(shù)據(jù)都需要等待上一次計(jì)算執(zhí)行完畢后才能開啟下一次的計(jì)算,那效率是相當(dāng)?shù)偷摹A魉€就是解決多周期下串行數(shù)據(jù)計(jì)算效率低的問題。
流水線的基本思想是:把一個(gè)重復(fù)的過程分解為若干個(gè)子過程,每個(gè)子過程由專門的功能部件來實(shí)現(xiàn)。將多個(gè)處理過程在時(shí)間上錯(cuò)開,依次通過各功能段,這樣每個(gè)子過程就可以與其他子過程并行進(jìn)行。
假如一個(gè)洗衣店內(nèi)洗衣服的過程分為 4 個(gè)階段:取衣、洗衣、烘干、裝柜。每個(gè)階段都需要半小時(shí)來完成,則洗一次衣服需要 2 小時(shí)。
考慮最差情況,洗衣店內(nèi)只有一臺洗衣機(jī)、一臺烘干機(jī)、一個(gè)衣柜。如果每半小時(shí)送來一批要洗的衣服,每次等待上一批衣服洗完需要 2 小時(shí),那么洗完 4 批衣服需要的時(shí)間就是 8 小時(shí)。
圖示如下:
對這個(gè)洗衣店的裝備進(jìn)行升級,一共引進(jìn) 4 套洗衣服的裝備,工作人員也增加到 4 個(gè),每個(gè)人負(fù)責(zé)一個(gè)洗衣階段。所以每批次的衣服,都能夠及時(shí)的被相同的人放入到不同的洗衣機(jī)內(nèi)。由于時(shí)間上是錯(cuò)開的,每批次的衣服都能被相同的人在不同的設(shè)備與時(shí)間段(半小時(shí))內(nèi)洗衣、烘干和裝柜。圖示如下。
可以看出,洗完 4 批衣服只需要 3 個(gè)半小時(shí),效率明顯提高。
其實(shí),在 2 小時(shí)后第一套洗衣裝備已經(jīng)完成洗衣過程而處于空閑狀態(tài),如果此時(shí)還有第 5 批衣服的送入,那么第一套設(shè)備又可以開始工作。依次類推,只要衣服批次不停的輸入,4 臺洗衣設(shè)備即可不間斷的完成對所有衣服的清洗過程。且除了第一批次洗衣時(shí)間需要 2 小時(shí),后面每半小時(shí)都會有一批次衣服清洗完成。
衣服批次越多,節(jié)省的時(shí)間就越明顯。假如有 N 批次衣服,需要的時(shí)間為 (4+N) 個(gè)半小時(shí)。
當(dāng)然,升級后洗衣流程也有缺點(diǎn)。設(shè)備和工作人員的增加導(dǎo)致了投入的成本增加,洗衣店內(nèi)剩余空間也被縮小,工作狀態(tài)看起來比較繁忙。
和洗衣服過程類似,數(shù)據(jù)的處理路徑也可以看作是一條生產(chǎn)線,路徑上的每個(gè)數(shù)字處理單元都可以看作是一個(gè)階段,會產(chǎn)生延時(shí)。
流水線設(shè)計(jì)就是將路徑系統(tǒng)的分割成一個(gè)個(gè)數(shù)字處理單元(階段),并在各個(gè)處理單元之間插入寄存器來暫存中間階段的數(shù)據(jù)。被分割的單元能夠按階段并行的執(zhí)行,相互間沒有影響。所以最后流水線設(shè)計(jì)能夠提高數(shù)據(jù)的吞吐率,即提高數(shù)據(jù)的處理速度。
流水線設(shè)計(jì)的缺點(diǎn)就是,各個(gè)處理階段都需要增加寄存器保存中間計(jì)算狀態(tài),而且多條指令并行執(zhí)行,勢必會導(dǎo)致功耗增加。
下面,設(shè)計(jì)一個(gè)乘法器,并對是否采用流水線設(shè)計(jì)進(jìn)行對比。
也許有人會問,直接用乘號 ?*
? 來完成 2 個(gè)數(shù)的相乘不是更快更簡單嗎?
如果你有這個(gè)疑問,說明你對硬件描述語言的認(rèn)知還有所不足。就像之前所說,Verilog 描述的是硬件電路,直接用乘號完成相乘過程,編譯器在編譯的時(shí)候也會把這個(gè)乘法表達(dá)式映射成默認(rèn)的乘法器,但其構(gòu)造不得而知。
例如,在 FPGA 設(shè)計(jì)中,可以直接調(diào)用 IP 核來生成一個(gè)高性能的乘法器。在位寬較小的時(shí)候,一個(gè)周期內(nèi)就可以輸出結(jié)果,位寬較大時(shí)也可以流水輸出。在能滿足要求的前提下,可以謹(jǐn)慎的用 ?*
? 或直接調(diào)用 IP 來完成乘法運(yùn)算。
但乘法器 IP 也有很多的缺陷,例如位寬的限制,未知的時(shí)序等。尤其使用乘號,會為數(shù)字設(shè)計(jì)的不確定性埋下很大的隱瞞。
很多時(shí)候,常數(shù)的乘法都會用移位相加的形式實(shí)現(xiàn),例如:
A = A<<1 ; //完成A * 2
A = (A<<1) + A ; //對應(yīng)A * 3
A = (A<<3) + (A<<2) + (A<<1) + A ; //對應(yīng)A * 15
用一個(gè)移位寄存器和一個(gè)加法器就能完成乘以 3 的操作。但是乘以 15 時(shí)就需要 3 個(gè)移位寄存器和 3 個(gè)加法器(當(dāng)然乘以 15 可以用移位相減的方式)。
有時(shí)候數(shù)字電路在一個(gè)周期內(nèi)并不能夠完成多個(gè)變量同時(shí)相加的操作。所以數(shù)字設(shè)計(jì)中,最保險(xiǎn)的加法操作是同一時(shí)刻只對 2 個(gè)數(shù)據(jù)進(jìn)行加法運(yùn)算,最差設(shè)計(jì)是同一時(shí)刻對 4 個(gè)及以上的數(shù)據(jù)進(jìn)行加法運(yùn)算。
如果設(shè)計(jì)中有同時(shí)對 4 個(gè)數(shù)據(jù)進(jìn)行加法運(yùn)算的操作設(shè)計(jì),那么此部分設(shè)計(jì)就會有危險(xiǎn),可能導(dǎo)致時(shí)序不滿足。
此時(shí),設(shè)計(jì)參數(shù)可配、時(shí)序可控的流水線式乘法器就顯得有必要了。
和十進(jìn)制乘法類似,計(jì)算 13 與 5 的相乘過程如下所示:
由此可知,被乘數(shù)按照乘數(shù)對應(yīng) bit 位進(jìn)行移位累加,便可完成相乘的過程。
假設(shè)每個(gè)周期只能完成一次累加,那么一次乘法計(jì)算時(shí)間最少的時(shí)鐘數(shù)恰好是乘數(shù)的位寬。所以建議,將位寬窄的數(shù)當(dāng)做乘數(shù),此時(shí)計(jì)算周期短。
考慮每次乘法運(yùn)算只能輸出一個(gè)結(jié)果(非流水線設(shè)計(jì)),設(shè)計(jì)代碼如下。
module mult_low
#(parameter N=4,
parameter M=4)
(
input clk,
input rstn,
input data_rdy , //數(shù)據(jù)輸入使能
input [N-1:0] mult1, //被乘數(shù)
input [M-1:0] mult2, //乘數(shù)
output res_rdy , //數(shù)據(jù)輸出使能
output [N+M-1:0] res //乘法結(jié)果
);
//calculate counter
reg [31:0] cnt ;
//乘法周期計(jì)數(shù)器
wire [31:0] cnt_temp = (cnt == M)? 'b0 : cnt + 1'b1 ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
cnt <= 'b0 ;
end
else if (data_rdy) begin //數(shù)據(jù)使能時(shí)開始計(jì)數(shù)
cnt <= cnt_temp ;
end
else if (cnt != 0 ) begin //防止輸入使能端持續(xù)時(shí)間過短
cnt <= cnt_temp ;
end
else begin
cnt <= 'b0 ;
end
end
//multiply
reg [M-1:0] mult2_shift ;
reg [M+N-1:0] mult1_shift ;
reg [M+N-1:0] mult1_acc ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
mult2_shift <= 'b0 ;
mult1_shift <= 'b0 ;
mult1_acc <= 'b0 ;
end
else if (data_rdy && cnt=='b0) begin //初始化
mult1_shift <= {{(N){1'b0}}, mult1} << 1 ;
mult2_shift <= mult2 >> 1 ;
mult1_acc <= mult2[0] ? {{(N){1'b0}}, mult1} : 'b0 ;
end
else if (cnt != M) begin
mult1_shift <= mult1_shift << 1 ; //被乘數(shù)乘2
mult2_shift <= mult2_shift >> 1 ; //乘數(shù)右移,方便判斷
//判斷乘數(shù)對應(yīng)為是否為1,為1則累加
mult1_acc <= mult2_shift[0] ? mult1_acc + mult1_shift : mult1_acc ;
end
else begin
mult2_shift <= 'b0 ;
mult1_shift <= 'b0 ;
mult1_acc <= 'b0 ;
end
end
//results
reg [M+N-1:0] res_r ;
reg res_rdy_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
res_r <= 'b0 ;
res_rdy_r <= 'b0 ;
end
else if (cnt == M) begin
res_r <= mult1_acc ; //乘法周期結(jié)束時(shí)輸出結(jié)果
res_rdy_r <= 1'b1 ;
end
else begin
res_r <= 'b0 ;
res_rdy_r <= 'b0 ;
end
end
assign res_rdy = res_rdy_r;
assign res = res_r;
endmodule
testbench
`timescale 1ns/1ns
module test ;
parameter N = 8 ;
parameter M = 4 ;
reg clk, rstn;
//clock
always begin
clk = 0 ; #5 ;
clk = 1 ; #5 ;
end
//reset
initial begin
rstn = 1'b0 ;
#8 ; rstn = 1'b1 ;
end
//no pipeline
reg data_rdy_low ;
reg [N-1:0] mult1_low ;
reg [M-1:0] mult2_low ;
wire [M+N-1:0] res_low ;
wire res_rdy_low ;
//使用任務(wù)周期激勵
task mult_data_in ;
input [M+N-1:0] mult1_task, mult2_task ;
begin
wait(!test.u_mult_low.res_rdy) ; //not output state
@(negedge clk ) ;
data_rdy_low = 1'b1 ;
mult1_low = mult1_task ;
mult2_low = mult2_task ;
@(negedge clk ) ;
data_rdy_low = 1'b0 ;
wait(test.u_mult_low.res_rdy) ; //test the output state
end
endtask
//driver
initial begin
#55 ;
mult_data_in(25, 5 ) ;
mult_data_in(16, 10 ) ;
mult_data_in(10, 4 ) ;
mult_data_in(15, 7) ;
mult_data_in(215, 9) ;
end
mult_low #(.N(N), .M(M))
u_mult_low
(
.clk (clk),
.rstn (rstn),
.data_rdy (data_rdy_low),
.mult1 (mult1_low),
.mult2 (mult2_low),
.res_rdy (res_rdy_low),
.res (res_low));
//simulation finish
initial begin
forever begin
#100;
if ($time >= 10000) $finish ;
end
end
endmodule // test
仿真結(jié)果如下。
由圖可知,輸入的 2 個(gè)數(shù)據(jù)在延遲 4 個(gè)周期后,得到了正確的相乘結(jié)果。算上中間送入數(shù)據(jù)的延遲時(shí)間,計(jì)算 4 次乘法大約需要 20 個(gè)時(shí)鐘周期。
下面對乘法執(zhí)行過程的中間狀態(tài)進(jìn)行保存,以便流水工作,設(shè)計(jì)代碼如下。
單次累加計(jì)算過程的代碼文件如下(mult_cell.v ):
module mult_cell
#(parameter N=4,
parameter M=4)
(
input clk,
input rstn,
input en,
input [M+N-1:0] mult1, //被乘數(shù)
input [M-1:0] mult2, //乘數(shù)
input [M+N-1:0] mult1_acci, //上次累加結(jié)果
output reg [M+N-1:0] mult1_o, //被乘數(shù)移位后保存值
output reg [M-1:0] mult2_shift, //乘數(shù)移位后保存值
output reg [N+M-1:0] mult1_acco, //當(dāng)前累加結(jié)果
output reg rdy );
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
rdy <= 'b0 ;
mult1_o <= 'b0 ;
mult1_acco <= 'b0 ;
mult2_shift <= 'b0 ;
end
else if (en) begin
rdy <= 1'b1 ;
mult2_shift <= mult2 >> 1 ;
mult1_o <= mult1 << 1 ;
if (mult2[0]) begin
//乘數(shù)對應(yīng)位為1則累加
mult1_acco <= mult1_acci + mult1 ;
end
else begin
mult1_acco <= mult1_acci ; //乘數(shù)對應(yīng)位為1則保持
end
end
else begin
rdy <= 'b0 ;
mult1_o <= 'b0 ;
mult1_acco <= 'b0 ;
mult2_shift <= 'b0 ;
end
end
endmodule
多次模塊例化完成多次累加,代碼文件如下(mult_man.v ):
module mult_man
#(parameter N=4,
parameter M=4)
(
input clk,
input rstn,
input data_rdy ,
input [N-1:0] mult1,
input [M-1:0] mult2,
output res_rdy ,
output [N+M-1:0] res );
wire [N+M-1:0] mult1_t [M-1:0] ;
wire [M-1:0] mult2_t [M-1:0] ;
wire [N+M-1:0] mult1_acc_t [M-1:0] ;
wire [M-1:0] rdy_t ;
//第一次例化相當(dāng)于初始化,不能用 generate 語句
mult_cell #(.N(N), .M(M))
u_mult_step0
(
.clk (clk),
.rstn (rstn),
.en (data_rdy),
.mult1 ({{(M){1'b0}}, mult1}),
.mult2 (mult2),
.mult1_acci ({(N+M){1'b0}}),
//output
.mult1_acco (mult1_acc_t[0]),
.mult2_shift (mult2_t[0]),
.mult1_o (mult1_t[0]),
.rdy (rdy_t[0]) );
//多次模塊例化,用 generate 語句
genvar i ;
generate
for(i=1; i<=M-1; i=i+1) begin: mult_stepx
mult_cell #(.N(N), .M(M))
u_mult_step
(
.clk (clk),
.rstn (rstn),
.en (rdy_t[i-1]),
.mult1 (mult1_t[i-1]),
.mult2 (mult2_t[i-1]),
//上一次累加結(jié)果作為下一次累加輸入
.mult1_acci (mult1_acc_t[i-1]),
//output
.mult1_acco (mult1_acc_t[i]),
.mult1_o (mult1_t[i]), //被乘數(shù)移位狀態(tài)傳遞
.mult2_shift (mult2_t[i]), //乘數(shù)移位狀態(tài)傳遞
.rdy (rdy_t[i]) );
end
endgenerate
assign res_rdy = rdy_t[M-1];
assign res = mult1_acc_t[M-1];
endmodule
testbench
將下述仿真描述添加到非流水乘法器設(shè)計(jì)例子的 testbench 中,即可得到流水式乘法運(yùn)算的仿真結(jié)果。
2 路數(shù)據(jù)為不間斷串行輸入,且?guī)в凶孕r?yàn)?zāi)K,可自動判斷乘法運(yùn)算結(jié)果的正確性。
reg data_rdy ;
reg [N-1:0] mult1 ;
reg [M-1:0] mult2 ;
wire res_rdy ;
wire [N+M-1:0] res ;
//driver
initial begin
#55 ;
@(negedge clk ) ;
data_rdy = 1'b1 ;
mult1 = 25; mult2 = 5;
#10 ; mult1 = 16; mult2 = 10;
#10 ; mult1 = 10; mult2 = 4;
#10 ; mult1 = 15; mult2 = 7;
mult2 = 7; repeat(32) #10 mult1 = mult1 + 1 ;
mult2 = 1; repeat(32) #10 mult1 = mult1 + 1 ;
mult2 = 15; repeat(32) #10 mult1 = mult1 + 1 ;
mult2 = 3; repeat(32) #10 mult1 = mult1 + 1 ;
mult2 = 11; repeat(32) #10 mult1 = mult1 + 1 ;
mult2 = 4; repeat(32) #10 mult1 = mult1 + 1 ;
mult2 = 9; repeat(32) #10 mult1 = mult1 + 1 ;
end
//對輸入數(shù)據(jù)進(jìn)行移位,方便后續(xù)校驗(yàn)
reg [N-1:0] mult1_ref [M-1:0];
reg [M-1:0] mult2_ref [M-1:0];
always @(posedge clk) begin
mult1_ref[0] <= mult1 ;
mult2_ref[0] <= mult2 ;
end
genvar i ;
generate
for(i=1; i<=M-1; i=i+1) begin
always @(posedge clk) begin
mult1_ref[i] <= mult1_ref[i-1];
mult2_ref[i] <= mult2_ref[i-1];
end
end
endgenerate
//自校驗(yàn)
reg error_flag ;
always @(posedge clk) begin
# 1 ;
if (mult1_ref[M-1] * mult2_ref[M-1] != res && res_rdy) begin
error_flag <= 1'b1 ;
end
else begin
error_flag <= 1'b0 ;
end
end
//module instantiation
mult_man #(.N(N), .M(M))
u_mult
(
.clk (clk),
.rstn (rstn),
.data_rdy (data_rdy),
.mult1 (mult1),
.mult2 (mult2),
.res_rdy (res_rdy),
.res (res));
前幾十個(gè)時(shí)鐘周期的仿真結(jié)果如下。
由圖可知,仿真結(jié)果判斷信號 ?error_flag
?一直為 0,表示乘法設(shè)計(jì)正確。
數(shù)據(jù)在時(shí)鐘驅(qū)動下不斷串行輸入,乘法輸出結(jié)果延遲了 4 個(gè)時(shí)鐘周期后,也源源不斷的在每個(gè)時(shí)鐘下無延時(shí)輸出,完成了流水線式的工作。
相對于一般不采用流水線的乘法器,乘法計(jì)算效率有了很大的改善。
但是,流水線式乘法器使用的寄存器資源也大約是之前不采用流水線式的 4 倍。
所以,一個(gè)數(shù)字設(shè)計(jì),是否采用流水線設(shè)計(jì),需要從資源和效率兩方面進(jìn)行權(quán)衡。
點(diǎn)擊這里下載源碼
更多建議: