12-分頻器 -偶分頻

2023-05-26 06:00:57

1.分頻器

計數器是對於時鐘訊號進行計數,板載晶振的時脈頻率是固定的,有時候需要進行分頻和倍頻才能滿足需要
開發板上只有一種晶振,只有一種頻率的時鐘,想要通過對與固定時鐘進行分頻或者是倍頻的方式得到各個模組所需的時脈頻率,得到比固定時鐘快的時鐘通過倍頻,得到比固定時鐘慢的時鐘通過分頻

  • 分頻和倍頻都有兩種方式:第一種是通過鎖相環(PLL),另外一種是編寫verilog程式碼
  • 分頻器是數位系統設計中最常見的基本電路之一,所謂分頻就是把輸入訊號的頻率變成成倍數地低於輸入頻率的輸出訊號
  • 分頻器原理是將輸入的訊號做為計數脈衝,計數器的輸出埠的脈衝是按一定頻率輸出的,就可以看作是輸出埠的分頻
  • 分頻器分為偶數分頻器和奇數分頻器,分頻器和計數器非常類似,有時可以認為是同一種東西

2.FPGA實現

  • 實現對於固定時鐘6分頻的電路

2.1 模組框圖和波形圖


2.2 RTL

module divider_six(
  input wire sys_clk,
  input wire sys_rst_n,
  
  output reg clk_out
);


  reg [1:0] cnt;
  
  // cnt變數
  always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
      cnt <= 2'd0;
    else if(cnt == 2'd2)
      cnt <= 2'd0;
    else
      cnt <= cnt + 2'd1;


  always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
      clk_out <= 1'b0;
    else if(cnt == 2'd2)
      clk_out <= ~clk_out;
    else 
      clk_out <= clk_out;

endmodule
  • 建立專案,編譯程式碼

2.3 Testbench

`timescale 1ns/1ns
module tb_divider_six();
  reg sys_clk;
  reg sys_rst_n;  

  wire [1:0] clk_out;

  // 初始化時鐘和復位訊號
  initial begin
    sys_clk = 1'b0;
    sys_rst_n = 1'b0;
    #20;
    sys_rst_n = 1'b1;
  end
    
  // 模擬時鐘訊號
  always #10 sys_clk = ~sys_clk;
  
  // 模組的範例化
  divider_six divider_six_inst(
    .sys_clk (sys_clk),
    .sys_rst_n (sys_rst_n),
    .clk_out (clk_out)
  );
endmodule
  • 載入模擬程式碼,進行模擬設定之後進行模擬

2.4 上板驗證

  • 將訊號輸出到擴充套件IO口,通過示波器進行觀察波形
  • 重新進行編譯,連線板卡(下載器和電源)
  • 新增sof檔案,進行程式下載
  • 連線示波器

2.5 優化

  • 這種做法是不嚴謹的,在低速系統中不易察覺,在高速系統中就容易出現問題,通過這種分頻的方式表面上是對系統時鐘進行了分頻產生了新的低頻時鐘,上述得到的分頻時鐘,實際上與真正的分頻時鐘是有不同的;
  • 在FPGA當中凡是時鐘訊號都要連線到全域性時鐘網路,全域性時鐘網路也叫做全域性時鐘樹,是FPGA廠商專門針對時鐘路徑進行設計的,能夠使時鐘訊號到達各個暫存器的時間儘可能相同,減少時序問題的產生,上面產生的分頻訊號沒有連線到全域性時鐘網路上,但是外部晶振產生的時鐘訊號,通過管腳連線到了專用時鐘引腳上,自然連線到了FPGA全域性時鐘網路中
  • 在系統時鐘工作下的訊號比在上述分頻訊號工作下的訊號更能在高速工作下保持穩定,如何對上述程式碼進行改進?使用時鐘標誌訊號cnt_flag
module divider_six(
  input wire sys_clk,
  input wire sys_rst_n,
  
  // output reg clk_out
  output reg clk_flag;
);


  reg [2:0] cnt;
  
  // cnt變數
  always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
      cnt <= 2'd0;
    else if(cnt == 3'd5)
      cnt <= 3'd0;
    else
      cnt <= cnt + 3'd1;


  always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
      clk_flag <= 1'b0;
    else if(cnt == 3'd4)    // flag訊號是在計數最大值減一的時候產生一個週期脈衝
      clk_flag <= 1'b1;
    else 
      clk_flag <= 1'b0;

  // 按照之前產生的分頻時鐘給變數a賦值
   reg a ;
  always @(posedge clk_out or negedge sys_rst_n) // 使用產生的分頻時鐘clk_out
    if(sys_rst_n == 1'b0)
      a <= 1'b0;  
    else 
      a <= a + 1'b1;

  // 時鐘標誌位產生的分頻時鐘對於變數進行賦值
  always@(posedge sys_clk or sys_rst_n)     // 仍然使用系統時鐘,更加穩定
    if(sys_rst_n == 1'b0) 
      a <= 1'b0;
    else if(cnt_flag == 1'b1)
      a <= 1'b1;
  

endmodule

`timescale 1ns/1ns
module tb_divider_six();
  reg sys_clk;
  reg sys_rst_n;  

  wire [2:0] clk_flag;

  // 初始化時鐘和復位訊號
  initial begin
    sys_clk = 1'b0;
    sys_rst_n = 1'b0;
    #20;
    sys_rst_n = 1'b1;
  end
    
  // 模擬時鐘訊號
  always #10 sys_clk = ~sys_clk;
  
  // 模組的範例化
  divider_six divider_six_inst(
    .sys_clk (sys_clk),
    .sys_rst_n (sys_rst_n),
    .clk_flag (clk_flag)
  );
endmodule

產生一個用於標記 6 分頻的 clk_flag 標誌訊號,這樣每兩 clk_flag 脈
衝之間的頻率就是對 sys_clk 時鐘訊號的 6 分頻,但是計數器計數的個數我們需增加一些,
如圖 18-4 所示需要從 0~5 共 6 個數,否則不能實現 6 分頻的功能。和方法 1 對比可以發
現,相當於把 clk_out 的上升沿訊號變成了 clk_flag 的脈衝電平訊號cnt_flag 是一樣的道理),為後級模組實現相同的降頻效果。**雖然這樣會多使用一些暫存器資源,不過不用擔心我們的系統是完全可以承擔的起的,而得到的好處卻遠遠大於這點資源的使用,能讓系統更加穩定。
在後級模組中需要使用低頻時鐘的情況,我們就可以不用 clk_out 這種訊號作為時
鍾了,而是繼續使用 sys_clk 系統時鐘來作為時鐘,但讓其執行語句的條件以 clk_flag 訊號
為高電平的時候有效。