高云FPGA之IP核的使用
- 1、設計定義
- 2、設計輸入
- 2.1 數碼管譯碼顯示
- 2.2 74HC595驅動
- 2.3 主模塊設計
- 3、分析和綜合
- 4、功能仿真
- 6.1 hex8模塊仿真
- 6.2 HC595模塊
- 5、布局布線
- 6、時序仿真
- 7、IO分配以及配置文件(bit流文件)的生成
- 8、配置(燒錄)FPGA
- 9、在線調試
1、設計定義
通過74HC595芯片點亮8位數碼管,通過計時器實現數碼管計時顯示
軟件開發環境高云V1.99版本
硬件開發環境采用小梅哥ACG525(主芯片GW5A-LV25-UG324C2)
2、設計輸入
本章節內容分為分兩個子模塊和一個主模塊,一個子模塊負責驅動74hc595一個子模塊負責數碼管顯示,主模塊負責計時和調用子模塊進行數碼管顯示。
2.1 數碼管譯碼顯示
首先我們設計數碼管顯示子模塊,數碼管有兩種結構:共陰極與共陽極。 這兩者的區別在于,公共端是連接到地還是高電平,對于共陰數碼管需要給對應段以高電平才會使其點亮, 而對于共陽極數碼管則需要給低電平才會點亮。 本次實驗環境上是使用的共陽數碼管,同時為了顯示數字或字符,必須對數字或字符進行編碼譯碼。這里的表格dp點默認全部顯示,當我們需要顯示dp點的時候與上8’b01111111即可,相應的關閉顯示就或上8’b10000000;
數碼管的顯示方式也分為兩種靜態顯示和動態顯示,靜態顯示的特點是每個數碼管的段選必須接一個 8 位數據線來保持顯示的字形碼。當送入一次字形碼后,顯示字形可一直保持,直到送入新字形碼為止。這種方法由于每一個數碼管均需要獨立的數據線因此硬件電路比較復雜,成本較高,很少使用為了節約 IO 以及成本一般采用如下圖所示的電路結構,這樣 3 個數碼管接在一起就比靜態的少了 7*2 個 I/O。
這樣就實現了另一種顯示模式,動態顯示。動態顯示的特點是將所有位數碼管的段選線并聯在一起,由位選線控制是哪一位數碼管有效。選亮數碼管采用動態掃描顯示。所謂動態掃描顯示即輪流向各位數碼管送出字形碼和相應的位選,利用發光管的余輝和人眼視覺暫留作用,使人的感覺好像各位數碼管同時都在顯示,板載設計的電路為動態顯示電路,下面開始設計數碼管顯示代碼;
位選控制也就是我們上面說到的輪流選擇8位數碼管的位信號線(sel);
/*********************位選掃描時鐘**********************/
reg clk_1k; // 1k時鐘
reg [14:0]clk_count; // 系統時鐘計數
reg [7:0]sel_reg;//數碼管位選寄存器,通過信號傳送給seg_sel// 系統clk采用50mhz進行計算
always@(posedge clk or posedge reset)
beginif(reset)clk_count <= 15'd0;else if(!en)clk_count <= 15'd0;else if(clk_count == 24999)clk_count <= 15'd0;elseclk_count <= clk_count + 1'b1;
endalways@(posedge clk or posedge reset)
beginif(reset)clk_1k <= 1'b0;else if(clk_count == 24999)clk_1k <= ~clk_1k;elseclk_1k <= clk_1k;
endalways@(posedge clk_1k or posedge reset)
beginif(reset)sel_reg <= 8'b0000_0001;else if(sel_reg == 8'b1000_0000)sel_reg <= 8'b0000_0001;else sel_reg <= sel_reg << 1;
endassign sel = (en)?sel_reg:8'b0000_0000;
段選信號,通過上面的譯碼表實現查表傳送給seg端口,這里的dp點顯示我以位判斷的方式進行顯示,哪一位需要顯示dp點給dp寄存器哪一位或1即可
/***********************段選數據***********************/
reg [3:0]data_tmp;//數據緩存
always@(*)
begincase(sel_reg)8'b0000_0001:data_tmp = disp_data[3:0];8'b0000_0010:data_tmp = disp_data[7:4];8'b0000_0100:data_tmp = disp_data[11:8];8'b0000_1000:data_tmp = disp_data[15:12];8'b0001_0000:data_tmp = disp_data[19:16];8'b0010_0000:data_tmp = disp_data[23:20];8'b0100_0000:data_tmp = disp_data[27:24];8'b1000_0000:data_tmp = disp_data[31:28];default:data_tmp = 4'b0000;endcase
endalways@(*)
begincase(data_tmp)4'h0:seg = 8'b11000000;4'h1:seg = 8'b11111001;4'h2:seg = 8'b10100100;4'h3:seg = 8'b10110000;4'h4:seg = 8'b10011001;4'h5:seg = 8'b10010010;4'h6:seg = 8'b10000010;4'h7:seg = 8'b11111000;4'h8:seg = 8'b10000000;4'h9:seg = 8'b10010000;4'ha:seg = 8'b10001000;4'hb:seg = 8'b10000011;4'hc:seg = 8'b11000110;4'hd:seg = 8'b10100001;4'he:seg = 8'b10000110;4'hf:seg = 8'b10001110;endcaseif((sel_reg&dp) > 0)seg = seg & 8'b01111111;
end
完整代碼
module hex8(input clk,input reset_n,input en,input [31:0]disp_data, //顯示數據input [7:0]dp,output [7:0]sel,//數碼管段選(當前要顯示的內容)output reg [7:0]seg //數碼管位選(選擇當前要顯示的數碼管)
);assign reset=~reset_n;/*********************位選掃描時鐘**********************/
reg clk_1k; // 1k時鐘
reg [14:0]clk_count; // 系統時鐘計數
reg [7:0]sel_reg;//數碼管位選寄存器,通過信號傳送給seg_selalways@(posedge clk or posedge reset)
beginif(reset)clk_count <= 15'd0;else if(!en)clk_count <= 15'd0;else if(clk_count == 24999)clk_count <= 15'd0;elseclk_count <= clk_count + 1'b1;
endalways@(posedge clk or posedge reset)
beginif(reset)clk_1k <= 1'b0;else if(clk_count == 24999)clk_1k <= ~clk_1k;elseclk_1k <= clk_1k;
endalways@(posedge clk_1k or posedge reset)
beginif(reset)sel_reg <= 8'b0000_0001;else if(sel_reg == 8'b1000_0000)sel_reg <= 8'b0000_0001;else sel_reg <= sel_reg << 1;
endassign sel = (en)?sel_reg:8'b0000_0000;/***********************段選數據***********************/
reg [3:0]data_tmp;//數據緩存
always@(*)
begincase(sel_reg)8'b0000_0001:data_tmp = disp_data[3:0];8'b0000_0010:data_tmp = disp_data[7:4];8'b0000_0100:data_tmp = disp_data[11:8];8'b0000_1000:data_tmp = disp_data[15:12];8'b0001_0000:data_tmp = disp_data[19:16];8'b0010_0000:data_tmp = disp_data[23:20];8'b0100_0000:data_tmp = disp_data[27:24];8'b1000_0000:data_tmp = disp_data[31:28];default:data_tmp = 4'b0000;endcase
endalways@(*)
begincase(data_tmp)4'h0:seg = 8'b11000000;4'h1:seg = 8'b11111001;4'h2:seg = 8'b10100100;4'h3:seg = 8'b10110000;4'h4:seg = 8'b10011001;4'h5:seg = 8'b10010010;4'h6:seg = 8'b10000010;4'h7:seg = 8'b11111000;4'h8:seg = 8'b10000000;4'h9:seg = 8'b10010000;4'ha:seg = 8'b10001000;4'hb:seg = 8'b10000011;4'hc:seg = 8'b11000110;4'hd:seg = 8'b10100001;4'he:seg = 8'b10000110;4'hf:seg = 8'b10001110;endcaseif((sel_reg&dp) > 0)seg = seg & 8'b01111111;
endendmodule
模塊使用
wire [31:0]disp_data;
wire [7:0] sel;//數碼管位選(選擇當前要顯示的數碼管)
wire [7:0] seg;//數碼管段選(當前要顯示的內容)
wire [7:0]dp_data;//數碼管小數點(某位點亮某位置1)
reg dp_flag; // 小數點寄存器
reg [31:0]disp_data_reg = 32'h00000000; // 顯示數據寄存器hex8 hex8_mod(.clk(clk), // 50m時鐘.reset_n(reset_n), // 復位信號.en(1'b1), // 使能模塊寄存器.disp_data(disp_data), // 32位數據顯示,每一個數碼管可以顯示0-f占4位.sel(sel), // 位選信號.seg(seg), // 段選信號.dp(dp_data) // 8位dp信號
);
2.2 74HC595驅動
為了節省IO引腳開發板數碼管設計采用了74HC595來擴展IO,該芯片的作用是位移位寄存器,FPGA 只需要輸出 3 個管腳,即可達到發
送數碼管數據的目的,與傳統段選、位選方式相比,大大節省了 IO 設計資源,在該原理圖下,將第一片74HC595的Q7‘串行輸出端接到第二片的數據輸入端Ds,實現級聯功能。經過14個時鐘SHcp上升沿后,數據已經全部移位進入移位寄存器,一次共輸入14位數據,那么第一位輸入的串行數據會在第二片74HC595芯片的Q5輸出,此時給一個上升沿的STcp信號就可以將信號移入存儲寄存器,OE信號持續給低, 即可輸出。這里貼一個博主做的很好理解的gif圖。
設計代碼如下
module hc595(input clk,input reset_n,input [15:0]data,input s_en,output reg sh_cp,output reg st_cp,output reg ds
);assign reset=~reset_n;/*******************時鐘模塊*********************************/
parameter CNT_MAX = 2;
reg [15:0]r_data; //數據寄存器
reg [7:0]clk_count;//分頻計數器;
always@(posedge clk)
beginif(s_en)r_data <= data;
endalways@(posedge clk or posedge reset)
beginif(reset)clk_count <= 0;else if(clk_count == CNT_MAX - 1'b1)clk_count <= 0;elseclk_count <= clk_count + 1'b1;
end wire sck_plus;
assign sck_plus = (clk_count == CNT_MAX - 1'b1);//對 sck_pluse進行計數, 用于查找表實現數據的串行輸入以及移位時鐘 sh_cp與存儲時鐘 st_cp 的產生
reg [5:0]SHCP_EDGE_CNT;
always@(posedge clk or posedge reset)
beginif(reset)SHCP_EDGE_CNT <= 0;else if(sck_plus)beginif(SHCP_EDGE_CNT == 6'd32)SHCP_EDGE_CNT <= 0;elseSHCP_EDGE_CNT <= SHCP_EDGE_CNT + 1'b1;endelseSHCP_EDGE_CNT <= SHCP_EDGE_CNT;
end// 查找表實現狀態輸出
always@(posedge clk or posedge reset)
beginif(reset)beginst_cp <= 1'b0;ds <= 1'b0;sh_cp <= 1'd0;end else begincase(SHCP_EDGE_CNT)0: begin sh_cp <= 0; st_cp <= 1'd0;ds <= r_data[15];end1: begin sh_cp <= 1; st_cp <= 1'd0;end2: begin sh_cp <= 0; ds <= r_data[14];end3: begin sh_cp <= 1; end4: begin sh_cp <= 0; ds <= r_data[13];end 5: begin sh_cp <= 1; end6: begin sh_cp <= 0; ds <= r_data[12];end 7: begin sh_cp <= 1; end8: begin sh_cp <= 0; ds <= r_data[11];end 9: begin sh_cp <= 1; end10: begin sh_cp <= 0; ds <= r_data[10];end 11: begin sh_cp <= 1; end12: begin sh_cp <= 0; ds <= r_data[9];end 13: begin sh_cp <= 1; end14: begin sh_cp <= 0; ds <= r_data[8];end 15: begin sh_cp <= 1; end16: begin sh_cp <= 0; ds <= r_data[7];end 17: begin sh_cp <= 1; end18: begin sh_cp <= 0; ds <= r_data[6];end 19: begin sh_cp <= 1; end20: begin sh_cp <= 0; ds <= r_data[5];end 21: begin sh_cp <= 1; end22: begin sh_cp <= 0; ds <= r_data[4];end 23: begin sh_cp <= 1; end24: begin sh_cp <= 0; ds <= r_data[3];end 25: begin sh_cp <= 1; end26: begin sh_cp <= 0; ds <= r_data[2];end 27: begin sh_cp <= 1; end28: begin sh_cp <= 0; ds <= r_data[1];end 29: begin sh_cp <= 1; end30: begin sh_cp <= 0; ds <= r_data[0];end31: begin sh_cp <= 1; end32: st_cp <= 1'd1;default: beginst_cp <= 1'b0;ds <= 1'b0;sh_cp <= 1'd0;endendcaseend
endendmodule
2.3 主模塊設計
完成了數碼管驅動的顯示,要實現計時的功能我們需要在主模塊中設計一個1秒定時器來實現計數,然后在調用我們寫好的模塊將計數值傳入到數碼管中進行顯示,為了更簡單的理解這里沒有添加更多的功能,但接口都是比較完整的可以自行DIY優化設計屬于自己的數值鐘,這里開拓一些功能(按鍵調整時間,設置定時器鬧鐘,輪流顯示日期溫度和時間,網絡對時,掉點保存等等),好下面開始設計我們的簡單計時代碼;
module seg_top(input clk, // system 50minput reset_n,output sh_cp,output st_cp,output ds
);parameter MCNT = 49_999_999; // 一秒計數器 wire [31:0]disp_data;
wire [7:0] sel;//數碼管位選(選擇當前要顯示的數碼管)
wire [7:0] seg;//數碼管段選(當前要顯示的內容)
wire [7:0]dp_data;//數碼管小數點(某位點亮某位置1)reg dp_flag; // 小數點寄存器
reg [31:0]disp_data_reg = 32'h00000000; // 顯示數據寄存器
reg [25:0]cnt; //定義計數器寄存器reg[3:0] hour_reg_h;
reg[3:0] hour_reg_l;
reg[3:0] min_reg_h;
reg[3:0] min_reg_l;
reg[3:0] sec_reg_h;
reg[3:0] sec_reg_l;//assign disp_data = disp_data_reg;
assign disp_data = {4'h2,4'h4,hour_reg_h,hour_reg_l,min_reg_h,min_reg_l,sec_reg_h,sec_reg_l};
assign dp_data = (dp_flag) ? 8'b0101_0100 : 8'b0101_0000;hc595 hc595_mod(.clk(clk),.reset_n(reset_n),.data({seg,sel}),.s_en(1'b1),.sh_cp(sh_cp),.st_cp(st_cp),.ds(ds)
);hex8 hex8_mod(.clk(clk),.reset_n(reset_n),.en(1'b1), .disp_data(disp_data),.sel(sel),.seg(seg),.dp(dp_data)
);assign reset=~reset_n;
//計數器計數進程
always@(posedge clk or posedge reset)
beginif(reset)cnt <= 25'd0;else if(cnt == MCNT)cnt <= 25'd0;elsecnt <= cnt + 1'b1;
end//時鐘 輸出控制進程
always@(posedge clk or posedge reset)
beginif(reset)begindp_flag <= 1'b1;disp_data_reg = 0;endelse if(cnt == 24_999_999) // 0.5反轉一次dp點dp_flag <= ~dp_flag;else if(cnt == MCNT) // 一秒計時begindp_flag <= ~dp_flag;disp_data_reg = disp_data_reg+1;sec_reg_l = sec_reg_l+1;if(sec_reg_l == 4'd9)beginsec_reg_l <= 0;sec_reg_h <= sec_reg_h + 1;if(sec_reg_h == 4'd5)beginsec_reg_h <= 0;min_reg_l <= min_reg_l + 1;if(min_reg_l == 4'd9)beginmin_reg_l <= 0;min_reg_h <= min_reg_h + 1;if(min_reg_h == 4'd5)beginmin_reg_h <= 0;hour_reg_l <= hour_reg_l + 1;if((hour_reg_h==4'd2) && (hour_reg_l==4'd3))beginhour_reg_l <=0;hour_reg_h <=0;endif(hour_reg_l == 4'd9)beginhour_reg_l <= 0;hour_reg_h <= hour_reg_h + 1;endendendendendendelsedp_flag <= dp_flag;
endendmodule
3、分析和綜合
當邏輯輸入設計完成后需要對其進行驗證,該部分由軟件部分進行驗證,如果邏輯輸入有問題需要檢查語法錯誤或則重新設計設計輸入
4、功能仿真
當分析和綜合通過后應該進行功能性驗證,針對項目設計定義的功能使用設計的邏輯輸入驗證其功能能否實現,一般的做法都是通過功能仿真的方式進行驗證,比如軟件邏輯分析儀,modelsim、vivado等軟件自帶的仿真工具進行仿真驗證
功能仿真也稱為行為仿真,主旨在于驗證電路的功能是否符合設計要求,其特點是不考慮電路門延遲與線延遲,主要是驗證電路與理想情況是否一致。也可以叫做RTL仿真(test bench)
6.1 hex8模塊仿真
`timescale 1ns/1ns`define clk_period 20module hex8_tb;reg clk; //50M
reg reset_n;
reg en; //數碼管顯示使能,1使能_0關閉reg [31:0]disp_data;wire [7:0] sel;//數碼管位選(選擇當前要顯示的數碼管)
wire [6:0] seg;//數碼管段選(當前要顯示的內容)hex8 hex8(.clk(clk),.reset_n(reset_n),.en(en),.disp_data(disp_data),.sel(sel),.seg(seg)
);initial clk = 1;
always#(`clk_period/2) clk = ~clk;initial beginreset_n = 1'b0;en = 1;disp_data = 32'h12345678;#(`clk_period*20);reset_n = 1;#(`clk_period*20);#20000000;disp_data = 32'h87654321;#20000000;disp_data = 32'h89abcdef;#20000000;$stop;
endendmodule
6.2 HC595模塊
`timescale 1ns/1ns`define clk_period 20
module hc595_tb;reg clk;
reg reset_n;
reg [15 : 0] data; //data to send
reg s_en; //send en
wire sh_cp; //shift clock
wire st_cp; //latch data clock
wire ds; //shift serial datahc595 hc595_mod(.clk(clk),.reset_n(reset_n),.data(data),.s_en(s_en),.sh_cp(sh_cp),.st_cp(st_cp),.ds(ds)
);
initial clk = 1;
always#(`clk_period/2) clk = ~clk;initial beginreset_n = 1'b0;s_en = 1;data = 16'b1010_1111_0110_0101;#(`clk_period*20);reset_n = 1;#(`clk_period*20);#5000;data = 16'b0101_0101_1010_0101;#5000;$stop;
end
endmodule
5、布局布線
當我們的IO可以開始分配了我們首先需要分配IO,如果當前還沒到IO分配的時候我們可以將IO分配放在最后,當IO分配完成后我們就可以通過軟件進行布局布線,在芯片內部生成芯片電路
6、時序仿真
時序仿真也稱為布局布線后仿真,是指電路已經映射到特定的工藝環境以后,綜合考慮電路的路徑延遲與門延遲的影響,驗證電路能否在一定時序條件下滿足設計構想的過程,能較好地反映芯片的實際工作情況,當時序仿真不通過的時候可能還會設計到時序約束的一個過程(在比較復雜的設計中也需要用到);這個在上一個步驟(功能仿真中)我們已經完成了時序仿真的波形查看
7、IO分配以及配置文件(bit流文件)的生成
如果在布局布線時未進行IO分配在該步驟進行IO分配并生成BIT流文件,這里我們直接查看小梅哥給出的excel文檔,填寫我們需要輸出的時鐘引腳;
//Copyright (C)2014-2023 Gowin Semiconductor Corporation.
//All rights reserved.
//File Title: Physical Constraints file
//Tool Version: V1.9.9 (64-bit)
//Part Number: GW5A-LV25UG324C2/I1
//Device: GW5A-25
//Device Version: A
//Created Time: Sat 02 17 19:32:14 2024IO_LOC "ds" F4;
IO_PORT "ds" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_LOC "st_cp" F3;
IO_PORT "st_cp" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_LOC "sh_cp" H4;
IO_PORT "sh_cp" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_LOC "reset_n" B16;
IO_PORT "reset_n" PULL_MODE=NONE BANK_VCCIO=3.3;
IO_LOC "clk" T9;
IO_PORT "clk" PULL_MODE=NONE BANK_VCCIO=3.3;
到這里軟件的模擬仿真驗證就完成了最后是燒錄到板子上進行測試驗證
8、配置(燒錄)FPGA
時序通過了后需要通過硬件進行驗證也就是最后一步的實物驗證
9、在線調試
當系統出現問題運行不正常我們可以通過外部硬件示波器或者邏輯分析儀進行實際引腳信號抓取分析