使用FPGA實現奇數50%佔空比分頻
阿新 • • 發佈:2020-12-12
技術標籤:FPGA
奇數分頻: 如果要實現佔空比為50%的奇數倍分頻,不能同偶數分頻一樣計數器記到一半的時候輸出時鐘翻轉,那樣得不到佔空比50%的時鐘。以待分頻時鐘CLK為例,如果以偶數分頻的方法來做奇數分頻,在CLK上升沿觸發,將得到不是50%佔空比的一個時鐘訊號(正週期比負週期多一個時鐘或者少一個時鐘);但是如果在CLK下降沿也觸發,又得到另外一個不是50%佔空比的時鐘訊號,這兩個時鐘相位正好相差半個CLK時鐘週期。通過這兩個時鐘訊號進行邏輯運算我們可以巧妙的得到50%佔空比的時鐘。
總結如下:對於實現佔空比為50%的N倍奇數分頻,首先進行上升沿觸發進行模N計數,計數選定到某一個值進行輸出時鐘翻轉,然後經過(N-1)/2再次進行翻轉得到一個佔空比非50%奇數n分頻時鐘。再者同時進行下降沿觸發的模N計數,到和上升沿觸發輸出時鐘翻轉選定值相同值時,進行輸出時鐘時鐘翻轉,同樣經過(N-1)/2時,輸出時鐘再次翻轉生成佔空比非50%的奇數n分頻時鐘。兩個佔空比非50%的n分頻時鐘進行邏輯運算(正週期多的相與,負週期多的相或),得到佔空比為50%的奇數n分頻時鐘。
Verilog程式碼
// ********************************************************************
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// ********************************************************************
// File name : divide.v
// Module name : divide
// Author : STEP
// Description : clock divider
// Web : www.stepfpga.com
//
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date: |Changes Made:
// V1.0 |2017/03/02 |Initial ver
// --------------------------------------------------------------------
// Module Function:任意整數時鐘分頻
module divide ( clk,rst_n,clkout);
input clk,rst_n; //輸入訊號,其中clk連線到FPGA的C1腳,頻率為12MHz
output clkout; //輸出訊號,可以連線到LED觀察分頻的時鐘
//parameter是verilog裡常數語句
parameter WIDTH = 3; //計數器的位數,計數的最大值為 2**WIDTH-1
parameter N = 5; //分頻係數,請確保 N < 2**WIDTH-1,否則計數會溢位
reg [WIDTH-1:0] cnt_p,cnt_n; //cnt_p為上升沿觸發時的計數器,cnt_n為下降沿觸發時的計數器
reg clk_p,clk_n; //clk_p為上升沿觸發時分頻時鐘,clk_n為下降沿觸發時分頻時鐘
//上升沿觸發時計數器的控制
always @ (posedge clk or negedge rst_n ) //posedge和negedge是verilog表示訊號上升沿和下降沿
//當clk上升沿來臨或者rst_n變低的時候執行一次always裡的語句
begin
if(!rst_n)
cnt_p<=0;
else if (cnt_p==(N-1))
cnt_p<=0;
else cnt_p<=cnt_p+1; //計數器一直計數,當計數到N-1的時候清零,這是一個模N的計數器
end
//上升沿觸發的分頻時鐘輸出,如果N為奇數得到的時鐘佔空比不是50%;如果N為偶數得到的時鐘佔空比為50%
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
clk_p<=0;
else if (cnt_p<(N>>1)) //N>>1表示右移一位,相當於除以2去掉餘數
clk_p<=0;
else
clk_p<=1; //得到的分頻時鐘正週期比負週期多一個clk時鐘
end
//下降沿觸發時計數器的控制
always @ (negedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_n<=0;
else if (cnt_n==(N-1))
cnt_n<=0;
else cnt_n<=cnt_n+1;
end
//下降沿觸發的分頻時鐘輸出,和clk_p相差半個時鐘
always @ (negedge clk)
begin
if(!rst_n)
clk_n<=0;
else if (cnt_n<(N>>1))
clk_n<=0;
else
clk_n<=1; //得到的分頻時鐘正週期比負週期多一個clk時鐘
end
assign clkout = (N==1)?clk:(N[0])?(clk_p&clk_n):clk_p; //條件判斷表示式
//當N=1時,直接輸出clk
//當N為偶數也就是N的最低位為0,N(0)=0,輸出clk_p
//當N為奇數也就是N最低位為1,N(0)=1,輸出clk_p&clk_n。正週期多所以是相與
endmodule
測試檔案
進行功能模擬時需要編寫testbench測試檔案。verilog裡的testbench檔案和原始檔一樣也是.v檔案,模擬能讓我們更直觀的觀察訊號波形,可以先閱讀Diamond的使用瞭解如何使用Diamond中整合的模擬工具。
// ********************************************************************
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// ********************************************************************
// File name : divide_tb.v
// Module name : divide_tb
// Author : STEP
// Description : clock divider
// Web : www.stepfpga.com
//
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date: |Changes Made:
// V1.0 |2017/03/02 |Initial ver
// --------------------------------------------------------------------
// Module Function:divide.v時鐘分頻器的測試檔案
`timescale 1ns/100ps //模擬時間單位/時間精度,時間單位要大於或者等於時間精度
module divide_tb(); //測試檔案也是一個module,因為用於模擬所以無需輸入輸出訊號
reg clk,rst_n; //需要產生的激勵訊號定義,激勵訊號需要過程塊產生所以定義為reg型變數
wire clkout; //需要觀察的輸出訊號定義,定義為wire型變數
//初始化過程塊
initial
begin
clk = 0;
rst_n = 0;
#25 //#表示延時25個時間單位
rst_n = 1; //產生了一個初始25ns低電平,然後變高電平的復位訊號
end
always #10 clk = ~clk; //每隔10ns翻轉一次clk訊號,也就是產生一個時鐘週期20ns的clk,頻率為50MHz
//module呼叫例化格式
divide #(.WIDTH(4),.N(11)) u1 ( //#後面的()中為引數傳遞,如果不傳遞引數就是所呼叫模組中的引數預設值
//divide表示所要例化的module名稱,u1是我們定義的例化名稱,必須以字母開頭
.clk (clk), //輸入輸出訊號連線。 .clk表示module本身定義的訊號名稱;(clk)表示我們在這裡定義的激勵訊號
.rst_n (rst_n), //在testbench裡定義的訊號名稱可以與所要呼叫module的埠訊號名稱不同
.clkout (clkout)
);
endmodule
//分頻,實現從500MHZ到100MHZ的時鐘,這樣可以保持輸出和輸入時鐘同步,觀測輸出資料是否是正常的
//reg clk_p;
//reg [3:0] cnt_p;
//parameter N = 4'd5;
//[email protected](posedge clk_500M or negedge rst_n)
//begin
// if(!rst_n)
// cnt_p <= 4'd0;
// else if(cnt_p == N-1)
// cnt_p <= 4'd0;
// else
// cnt_p <= cnt_p + 1'b1;
//end
//always @(posedge clk_500M or negedge rst_n)
//begin
// if(!rst_n)
// clk_p <= 1'b0;
// else if(cnt_p == (N-1)/2)
// clk_p <= ~clk_p;
// else if(cnt_p == N-1)
// clk_p <= ~clk_p;
// else
// clk_p <= clk_p;
//end
//reg clk_n;
//reg [3:0] cnt_n;
//[email protected](posedge clk_500M or negedge rst_n)
//begin
// if(!rst_n)
// cnt_n <= 4'd0;
// else if(cnt_n == N-1)
// cnt_n <= 4'd0;
// else
// cnt_n <= cnt_n + 1'b1;
//end
//always @(posedge clk_500M or negedge rst_n)
//begin
// if(!rst_n)
// clk_n <= 1'b0;
// else if(cnt_n == (N-1)/2)
// clk_n <= ~clk_n;
// else if(cnt_n == N-1)
// clk_n <= ~clk_n;
// else
// clk_n <= clk_n;
//end
//assign clk_100L = clk_p|clk_n;
/////////////////////////////////////////
參考文獻
[1] FPGA時鐘分頻