6 min read

數位除法器設計與實現 - 基於FPGA的移位

數位除法器設計與實現 - 基於FPGA的移位
Photo by Florian Olivo / Unsplash

數位除法器設計與實現 - 基於FPGA的移位除法算法

前言

在數位電路設計中,除法運算是一個重要但相對複雜的算術操作。本文將介紹一個基於FPGA的32位元除法器設計,採用移位除法算法實現。這個設計不僅具有良好的可擴展性,還提供了完整的測試驗證平台。

一、設計目標

  1. 實現32位元無號數除法運算
  2. 支援可參數化的資料位寬
  3. 提供完整的控制機制
  4. 確保運算結果的準確性

二、設計架構

2.1 模組說明

  1. div_go_tick: 啟動信號處理模組

    • 功能:產生單一時鐘週期的觸發脈衝
    • 避免多週期觸發問題
  2. division: 除法器核心模組

    • 功能:執行移位除法運算
    • 提供商數和移位計數輸出

三、完整程式碼

3.1 訊號邊緣檢測模組

// *********************************************************************************
// 訊號邊緣檢測模組
// 功能:檢測輸入訊號的上升邊緣,產生一個時鐘週期的脈衝
// 應用:用於除法器的啟動控制
// *********************************************************************************

module div_go_tick (
    input clk,      // 時鐘輸入
    input nRst,     // 低電位有效的重置信號
    input iSignal,  // 輸入訊號
    output oSignal   // 輸出脈衝信號
);

reg [1:0] rvSignal_d, rvSignal_q;

always @(posedge clk or negedge nRst) begin
    if(!nRst)
        rvSignal_q <= 2'b00;
    else
        rvSignal_q <= rvSignal_d;
end

always @(*) begin
    rvSignal_d = {rvSignal_q[0], iSignal};
end

assign oSignal = (rvSignal_q == 2'b01) ? 1'b1 : 1'b0;

endmodule

3.2 可參數化的除法器模組

module division#(
    parameter   DATA_WIDTH = 32    // 資料位寬參數,預設32位元
)
(
    input                    clk,          // 時脈輸入
    input                    nRst,         // 低電位有效的重置信號
    input  [DATA_WIDTH-1:0]  ivDivisor,    // 除數輸入
    input  [DATA_WIDTH-1:0]  ivDividend,   // 被除數輸入
    output [DATA_WIDTH-1:0]  ovQuotient,   // 商數輸出
    output [7:0]            ovShift,      // 移位計數輸出
    input                   iGoTick,      // 開始運算觸發信號
    output                  oDone         // 運算完成信號
);

// 常數定義
localparam DATA_WIDTH_ = DATA_WIDTH - 1;

// 內部暫存器宣告
reg [DATA_WIDTH-1:0] rvDivisor_d, rvDivisor_q;           
reg [DATA_WIDTH-1:0] rvQuotientShift_d, rvQuotientShift_q; 
reg [DATA_WIDTH-1:0] rvRemainder_d, rvRemainder_q;       
wire [DATA_WIDTH-1:0] wvRemainder;                       
reg [DATA_WIDTH-1:0] rvQuotient_d, rvQuotient_q;        
reg [7:0] rvShift_d, rvShift_q;                         

// 控制信號暫存器
reg rFlag_d, rFlag_q;                    
reg [7:0] rvProcessCnt_d, rvProcessCnt_q; 
wire addProcessCnt, endProcessCnt;        
reg [7:0] rvShiftCnt_d, rvShiftCnt_q;    
reg [DATA_WIDTH-1:0] rvDivisorShift_d, rvDivisorShift_q; 
reg rProcessEn_d, rProcessEn_q;           
wire [DATA_WIDTH-1:0] wvCompare;          
reg rDone_d, rDone_q;                     

//=============================================================================
// 第一階段:初始對齊
// 通過移位操作找到正確的起始位置
//=============================================================================
// 比較邏輯:被除數減去移位後的除數
assign wvCompare = ivDividend - rvDivisorShift_q; 

// 除數移位暫存器
always @(posedge clk or negedge nRst) begin
    if(!nRst)
        rvDivisorShift_q <= 8'h0;
    else
        rvDivisorShift_q <= rvDivisorShift_d;
end

// 除數移位控制邏輯
always @(*) begin
    if(endProcessCnt)
        rvDivisorShift_d = 8'h0;
    else if(iGoTick)
        rvDivisorShift_d = ivDivisor;
    else if(wvCompare[DATA_WIDTH-1] == 1'b0)  // 如果比較結果為正
        rvDivisorShift_d = rvDivisorShift_q << 1;  // 繼續左移
    else
        rvDivisorShift_d = rvDivisorShift_q;
end

// 移位計數器邏輯
always @(posedge clk or negedge nRst) begin
    if(!nRst)
        rvShiftCnt_q <= 8'h0;
    else
        rvShiftCnt_q <= rvShiftCnt_d;
end

// 移位計數控制
always @(*) begin
    if(iGoTick || !rFlag_q)
        rvShiftCnt_d = 8'h0;
    else if(wvCompare[DATA_WIDTH-1] == 1'b0)
        rvShiftCnt_d = rvShiftCnt_q + 1'b1;
    else
        rvShiftCnt_d = rvShiftCnt_q;
end

//=============================================================================
// 處理控制邏輯
//=============================================================================
// 處理啟用控制
always @(posedge clk or negedge nRst) begin
    if(!nRst)
        rProcessEn_q <= 1'b0;
    else
        rProcessEn_q <= rProcessEn_d;
end

always @(*) begin
    if(iGoTick || endProcessCnt)
        rProcessEn_d = 1'b0;
    else if(wvCompare[DATA_WIDTH-1] == 1'b1)  // 當比較結果為負時開始處理
        rProcessEn_d = 1'b1;
    else
        rProcessEn_d = rProcessEn_q;
end

// 運算狀態標誌控制
always @(posedge clk or negedge nRst) begin
    if(!nRst)
        rFlag_q <= 1'b0;
    else
        rFlag_q <= rFlag_d;
end

always @(*) begin
    if(endProcessCnt)
        rFlag_d = 1'b0;
    else if(iGoTick)
        rFlag_d = 1'b1;
    else
        rFlag_d = rFlag_q;
end

//=============================================================================
// 第二階段:執行除法運算
//=============================================================================
// 餘數計算
assign wvRemainder = rvRemainder_q - rvDivisor_q;

// 處理計數器控制
assign addProcessCnt = rProcessEn_q;
assign endProcessCnt = addProcessCnt && (rvProcessCnt_q == DATA_WIDTH);

// 處理計數器邏輯
always @(posedge clk or negedge nRst) begin
    if(!nRst)
        rvProcessCnt_q <= 8'h0;
    else
        rvProcessCnt_q <= rvProcessCnt_d;
end

always @(*) begin
    if(endProcessCnt)
        rvProcessCnt_d = 8'h0;
    else if(addProcessCnt)
        rvProcessCnt_d = rvProcessCnt_q + 1'b1;
    else
        rvProcessCnt_d = rvProcessCnt_q;
end

// 最終輸出賦值
assign ovQuotient = rvQuotient_q;
assign ovShift = rvShift_q;
assign oDone = rDone_q;

endmodule

3.3 測試平台

// *********************************************************************************
// 測試平台
// *********************************************************************************

`timescale 1ns/1ps
module division_tb;
localparam DATA_WIDTH = 32;    // 設定資料位寬為32位元

// 時鐘和控制信號
reg                     clk;        // 系統時鐘
reg                     nRst;       // 低電位有效的重置信號
reg  [DATA_WIDTH-1:0]   divisor1;   // 除數輸入
reg  [DATA_WIDTH-1:0]   dividend1;  // 被除數輸入
wire [DATA_WIDTH-1:0]   quotient1;  // 商數輸出
wire [7:0]             shift1;     // 移位計數輸出
reg                     go;         // 啟動信號
wire                    goTick;     // 邊緣檢測後的啟動脈衝

// 邊緣檢測模組實例化
div_go_tick div_go_tick_inst (
    .clk     (clk),
    .nRst    (nRst),
    .iSignal (go),      // 輸入啟動信號
    .oSignal (goTick)   // 輸出脈衝信號
);

// 除法器模組實例化
division #(
    .DATA_WIDTH(DATA_WIDTH)
) DUT (
    .clk        (clk),
    .nRst       (nRst),
    .ivDivisor  (divisor1),
    .ivDividend (dividend1),
    .ovQuotient (quotient1),
    .ovShift    (shift1),
    .iGoTick    (goTick)
);

// 時鐘產生器:週期為20ns (50MHz)
initial
    clk = 1'b0;
always #10 clk = ~clk;

// 系統重置任務
task SYSTEM_RESET;
begin
    nRst = 1'b0;                    // 啟動重置
    repeat(100) @(posedge clk);     // 等待100個時鐘週期
    nRst = 1'b1;                    // 釋放重置
end
endtask

// 測試程序
initial begin
    // 初始化和系統重置
    SYSTEM_RESET;
    go = 0;
    #1000;

    // 測試案例1: 300 ÷ 300 = 1
    divisor1 = 300;
    dividend1 = 300;
    #100;
    go = 1;
    #100;
    go = 0;

    // 測試案例2: 13 ÷ 3 = 4.333...
    #1000;
    divisor1 = 3;
    dividend1 = 13;
    #100;
    go = 1;
    #100;
    go = 0;

    // 測試案例3: 280 ÷ 256 = 1.09375
    #1000;
    divisor1 = 256;
    dividend1 = 280;
    #100;
    go = 1;
    #100;
    go = 0;

    // 測試案例4: 83 ÷ 19 = 4.368421...
    #1000;
    divisor1 = 19;
    dividend1 = 83;
    #100;
    go = 1;
    #100;
    go = 0;

    // 等待運算完成
    #300000;

    // 結束模擬
    $stop;
end

endmodule

四、波形分析

4.1 實際波形結果

fpga_divider.png

波形說明:

  1. 啟動控制信號

    • iSignal 觸發後產生 goTick 脈衝
    • 單一時鐘週期的控制信號
  2. 除法運算過程

    • 初始對齊階段
    • 移位除法運算階段
    • 結果輸出階段
  3. 關鍵時序點

    • 運算開始時間點
    • 商數產生時間點
    • 完成信號產生時間點

五、結論

本設計實現了一個穩定可靠的除法器,具有:

  1. 良好的可擴展性
  2. 準確的運算結果
  3. 完整的控制機制
  4. 清晰的時序關係

六、未來優化方向

  1. 增加管線級數提高運算效率
  2. 添加除數為零檢查
  3. 支援有號數運算
  4. 優化關鍵路徑降低延遲