目錄
前言:
一、整體系統設計
二、各部分子產品設計
1、時鐘子產品
2、OV7670初始化子產品
3、DVP協定資料流子產品
4、寫FIFO子產品
5、讀FIFO子產品
6、寫FIFO控制子產品
7、讀FIFO控制子產品
8、SDRAM控制子產品
9、VGA控制子產品
10、頂層子產品
三、仿真測試
四、上闆驗證
五、總結
工程檔案下載下傳連結:https://download.csdn.net/download/qq_33231534/13010542
前言:
這個專題的部落格中寫的都是關于OV7670攝像頭顯示所需要的子產品,并對每個子產品進行仿真驗證,最後再對每個子產品進行整合,本篇就是對整個攝像頭系統進行整合和彙總。在過程中遇到很多問題,應該也會是很多人會遇到的問題,涉及到調試和代碼的問題,在下邊也會加以講解。
一、整體系統設計
如下系統框圖:
圖:整體系統框圖
如圖所示,FPGA中主要子產品包含:時鐘子產品、OV7670初始化子產品、DVP協定資料流子產品、寫FIFO子產品、寫FIFO控制子產品、SDRAM控制子產品、讀FIFO子產品、讀FIFO控制子產品、VGA控制子產品。
其中OV7670初始化子產品、DVP協定資料流子產品和VGA控制子產品都在本專題部落格中寫過,這裡不再贅述。寫FIFO和讀FIFO子產品使用的IP核,都是寬度16位,長度256,其中讀FIFO使用的是showahead模式。SDRAM控制器漆面的部落格也寫過,這邊做了一些改動,添加了一些需要的信号。
其整體流程為:啟動時先對攝像頭進行初始化設定,初始化完成後,FPGA從攝像頭擷取一幀一幀的圖像資料,根據資料手冊将ov7670資料流轉換成我們需要的RGB565資料流,随後存入寫FIFO子產品;(寫控制子產品)當寫FIFO子產品中存儲的資料大于等于8時,發出SDRAM寫請求,SDRAM寫請求通過後,讀取FIFO資料存儲起來;(讀FIFO子產品)當讀FIFO資料小于等于8時,讀取SDRAM中的資料經過讀FIFO緩存後送入VGA顯示子產品進行顯示。同時寫控制子產品和讀控制子產品控制SDRAM讀寫位址的增加。
二、各部分子產品設計
1、時鐘子產品
這裡使用PLL的IP核,以50MHz時鐘生成25MHz和100MHz時鐘,其中攝像頭初始化子產品和VGA控制子產品使用的是25MHz,SDRAM控制子產品、寫FIFO控制子產品和讀FIFO控制子產品使用的是100MHz,寫FIFO和讀FIFO子產品都是異步FIFO,使用25MHz和100MHz時鐘。
2、OV7670初始化子產品
前面部落格講的很詳細,也沒有改動,這裡不再贅述。
3、DVP協定資料流子產品
前面部落格講的很詳細,也沒有改動,這裡不再贅述。
4、寫FIFO子產品
這裡使用的IP核,資料寬度為16,長度為256,普通模式。
5、讀FIFO子產品
這裡使用的IP核,資料寬度為16,長度為256,showahead模式。showahead模式是為了在讀出FIFO資料時先出一個資料,和VGA顯示的資料有效信号好對齊。
6、寫FIFO控制子產品
主要實作兩個功能:
(1)當寫FIFO中資料大于等于8時,向SDRAM控制器發出寫請求信号
(2)發送SDRASM控制器寫位址(行位址和列位址),當SDRAM控制器寫完資料後,寫位址進行相應變化。
代碼如下:
// Company :
// Engineer :
// -----------------------------------------------------------------------------
// https://blog.csdn.net/qq_33231534 PHF's CSDN blog
// -----------------------------------------------------------------------------
// Create Date : 2020-09-27 12:44:59
// Revise Data : 2020-09-27 12:45:39
// File Name : wr_control.v
// Target Devices : XC7Z015-CLG485-2
// Tool Versions : Vivado 2019.2
// Revision : V1.1
// Editor : sublime text3, tab size (4)
// Description : 寫FIFO控制子產品
module wr_control(
input clk ,//100MHz
input rst_n ,//系統複位
input [7:0] rdusedw ,//寫FIFO中資料個數
input [9:0] row_addr_max ,//行位址最大
input [9:0] col_addr_max ,//列位址最大
input sdram_wdata_done,//SDRAM寫資料結束标志
input aclr ,//一幀結束清零信号
output reg sdram_wr_en ,//SDRAM寫使能信号
output reg [11:0] wr_row_addr ,//SDRAM 行位址
output reg [8:0] wr_col_addr //SDRAM 列位址
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sdram_wr_en <= 0;
end
else if (rdusedw>=8) begin
sdram_wr_en <= 1;
end
else begin
sdram_wr_en <= 0;
end
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr_col_addr <= 9'd0;
end
else if (sdram_wdata_done) begin
if (wr_col_addr==col_addr_max-4'd8) begin
wr_col_addr <= 9'd0;
end
else begin
wr_col_addr <= wr_col_addr + 4'd8;
end
end
else if (aclr) begin
wr_col_addr <= 0;
end
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr_row_addr <= 12'd0;
end
else if (sdram_wdata_done && wr_col_addr==col_addr_max-4'd8) begin
if (wr_row_addr==row_addr_max-1) begin
wr_row_addr <= 12'd0;
end
else begin
wr_row_addr <= wr_row_addr + 1'b1;
end
end
else if (aclr) begin
wr_row_addr <= 0;
end
end
endmodule
7、讀FIFO控制子產品
主要實作兩個功能:
(1)當讀FIFO中資料小于等于8時,向SDRAM控制器發出讀請求信号
(2)發送SDRASM控制器讀位址(行位址和列位址),當SDRAM控制器讀完資料後,讀位址進行相應變化。
代碼如下:
// Company :
// Engineer :
// -----------------------------------------------------------------------------
// https://blog.csdn.net/qq_33231534 PHF's CSDN blog
// -----------------------------------------------------------------------------
// Create Date : 2020-09-27 16:40:08
// Revise Data : 2020-09-27 16:40:08
// File Name : rd_control.v
// Target Devices : XC7Z015-CLG485-2
// Tool Versions : Vivado 2019.2
// Revision : V1.1
// Editor : sublime text3, tab size (4)
// Description : 讀FIFO控制子產品
module rd_control(
input clk ,//時鐘100MHz
input rst_n ,//複位信号
input [7:0] rdusedw ,//讀FIFO中資料個數
input [9:0] row_addr_max ,//行位址最大
input [9:0] col_addr_max ,//列位址最大
input sdram_rdata_done,//SDRAM讀資料結束标志
input aclr ,//一幀結束清零标志
output reg sdram_rd_en ,//SDRAM讀使能
output reg [11:0] rd_row_addr ,//SDRAM 行位址
output reg [8:0] rd_col_addr //SDRAM 列位址
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sdram_rd_en <= 0;
end
else if (rdusedw<=8) begin
sdram_rd_en <= 1;
end
else begin
sdram_rd_en <= 0;
end
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rd_col_addr <= 9'd0;
end
else if (sdram_rdata_done) begin
if (rd_col_addr==col_addr_max-4'd8) begin
rd_col_addr <= 9'd0;
end
else begin
rd_col_addr <= rd_col_addr + 4'd8;
end
end
else if (aclr) begin
rd_col_addr <= 0;
end
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rd_row_addr <= 12'd0;
end
else if (sdram_rdata_done && rd_col_addr==col_addr_max-4'd8) begin
if (rd_row_addr==row_addr_max-1) begin
rd_row_addr <= 9'd0;
end
else begin
rd_row_addr <= rd_row_addr + 1'b1;
end
end
else if (aclr) begin
rd_row_addr <= 0;
end
end
endmodule
8、SDRAM控制子產品
前面有個專題專門講的SDRAM控制器原理和實作方法,這裡在原來的代碼上,根據現在系統的需求,進行了略微的修改,主要功能沒有修改,隻是增加幾個外部需要的信号,讀優先級還是大于寫優先級(寫大于讀也可以)。
這裡給出代碼:
// Company :
// Engineer :
// -----------------------------------------------------------------------------
// https://blog.csdn.net/qq_33231534 PHF's CSDN blog
// -----------------------------------------------------------------------------
// Create Date : 2020-09-18 14:20:22
// Revise Data : 2020-10-20 15:30:16
// File Name : SDRAM_control.v
// Target Devices : XC7Z015-CLG485-2
// Tool Versions : Vivado 2019.2
// Revision : V1.1
// Editor : sublime text3, tab size (4)
// Description : SDRAM控制器,支援讀寫沖突長度為8,cas為3
module SDRAM_control(
input clk ,//100MHZ
input rst_n ,//複位
input wr_en ,//寫使能信号
input [15:0] wr_data ,//寫資料
input rd_en ,//讀使能信号
input [1:0] bank_addr ,//bank位址
input [11:0] row_addr ,//行位址
input [8:0] col_addr ,//列位址
output reg fifo_rdreq ,//寫FIFO讀請求信号
output reg [15:0] rd_data ,//讀出的資料
output reg rd_data_vld ,//讀出資料有效位
output wire wr_data_vld ,//寫入資料有效位
output wire wdata_done ,//寫資料結束标志
output wire rdata_done ,//讀資料結束标志
output wire sdram_clk ,//SDRAM時鐘信号
output reg [3:0] sdram_commond ,//{cs,ras,cas,we}
output wire sdram_cke ,//時鐘使能信号
output reg [1:0] sdram_dqm ,//資料線屏蔽信号
output reg [11:0] sdram_addr ,//SDRAM位址線
output reg [1:0] sdram_bank ,//SDRAM bank選取
inout wire[15:0] sdram_dq , //SDRAM資料輸出輸入總線
output wire state_wr_req , //新增,用于讀寫位址判斷
output wire state_rd_req //新增,用于讀寫位址判斷
);
//延時
localparam TWAIT_200us = 15'd20000 ;//上電等待時間
localparam TRP = 2'd3 ;//預充電周期
localparam TRC = 4'd10 ;//自重新整理周期
localparam TRSC = 2'd3 ;//加載模式寄存器周期
localparam TRCD = 2'd2 ;//激活指令周期
localparam TREAD_11 = 4'd11 ;//burst=8,cas=3
localparam TWRITE_8 = 4'd8 ;//burst=8
localparam AUTO_REF_TIME= 11'd1562 ;
//狀态
localparam NOP = 3'd0 ;
localparam PRECHARGE = 3'd1 ;
localparam REF = 3'd2 ;
localparam MODE = 3'd3 ;
localparam IDLE = 3'd4 ;
localparam ACTIVE = 3'd5 ;
localparam WRITE = 3'd6 ;
localparam READ = 3'd7 ;
//操作指令
localparam NOP_CMD = 4'b0111 ;
localparam PRECHARGE_CMD= 4'b0010 ;
localparam REF_CMD = 4'b0001 ;
localparam MODE_CMD = 4'b0000 ;
localparam ACTIVE_CMD = 4'b0011 ;
localparam WRITE_CMD = 4'b0100 ;
localparam READ_CMD = 4'b0101 ;
//初始化階段位址線
localparam ALL_BANK = 12'b01_0_00_000_0_000;//預充電位址線
localparam MODE_CONFIG = 12'b00_0_00_011_0_011;//配置模式寄存器時位址線
wire nop_to_pre_start ;
wire pre_to_ref_start ;
wire pre_to_idle_start ;
wire ref_to_mode_start ;
wire ref_to_idle_start ;
wire ref_to_ref_start ;
wire mode_to_idle_start ;
wire idle_to_active_start ;
wire idle_to_ref_start ;
wire active_to_write_start ;
wire active_to_read_start ;
wire write_to_pre_start ;
wire read_to_pre_start ;
reg [2:0] state_c ;
reg [2:0] state_n ;
wire sdram_dq_en ;
wire[15:0] sdram_dq_r ;
reg rd_data_vld_ff0 ;
reg rd_data_vld_ff1 ;
reg rd_data_vld_ff2 ;
reg rd_data_vld_ff3 ;
reg [10:0] auto_ref_cnt ;
wire add_auto_ref_cnt;
wire end_auto_ref_cnt;
reg ref_req ;
wire init_done ;
reg init_flag ;
reg [14:0] cnt0 ;
wire add_cnt0 ;
wire end_cnt0 ;
reg [14:0] x ;
reg [3:0] ref_cnt1 ;
wire add_cnt1 ;
wire end_cnt1 ;
reg flag_rd ;
reg flag_wr ;
reg fifo_rdreq_f ;
reg [2:0] fifo_rdreq_cnt;
assign state_wr_req = state_c==IDLE;
assign state_rd_req = state_c==IDLE;
assign sdram_clk = ~clk;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state_c <= NOP;
end
else begin
state_c <= state_n;
end
end
always @(*)begin
case(state_c)
NOP :begin
if (nop_to_pre_start) begin
state_n = PRECHARGE;
end
else begin
state_n = state_c;
end
end
PRECHARGE:begin
if (pre_to_ref_start) begin
state_n = REF;
end
else if (pre_to_idle_start) begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
REF:begin
if (ref_to_mode_start) begin
state_n = MODE;
end
else if (ref_to_idle_start) begin
state_n = IDLE;
end
else if (ref_to_ref_start) begin
state_n = REF;
end
else begin
state_n = state_c;
end
end
MODE:begin
if (mode_to_idle_start) begin
state_n = IDLE;
end
else begin
state_n =state_c;
end
end
IDLE:begin
if (idle_to_active_start) begin
state_n = ACTIVE;
end
else if (idle_to_ref_start) begin
state_n = REF;
end
else begin
state_n = state_c;
end
end
ACTIVE:begin
if (active_to_write_start) begin
state_n = WRITE;
end
else if (active_to_read_start) begin
state_n = READ;
end
else begin
state_n = state_c;
end
end
WRITE:begin
if(write_to_pre_start)begin
state_n = PRECHARGE;
end
else begin
state_n = state_c;
end
end
READ:begin
if (read_to_pre_start) begin
state_n = PRECHARGE;
end
else begin
state_n = state_c;
end
end
default:state_n = IDLE;
endcase
end
assign nop_to_pre_start = (state_c==NOP && end_cnt0);
assign pre_to_ref_start = (state_c==PRECHARGE && end_cnt0 && init_flag==1);
assign pre_to_idle_start = (state_c==PRECHARGE && end_cnt0 && init_flag==0);
assign ref_to_mode_start = (state_c==REF && init_flag==1 && end_cnt1);
assign ref_to_idle_start = (state_c==REF && init_flag==0 && end_cnt0);
assign ref_to_ref_start = (state_c==REF && init_flag==1 && end_cnt0 && ref_cnt1<7);
assign mode_to_idle_start = (state_c==MODE && init_flag==1 && end_cnt0);
assign idle_to_active_start = (state_c==IDLE && (wr_en || rd_en) && ref_req==0);
assign idle_to_ref_start = (state_c==IDLE && ref_req==1);
assign active_to_write_start = (state_c==ACTIVE && end_cnt0 && flag_wr);
assign active_to_read_start = (state_c==ACTIVE && end_cnt0 && flag_rd);
assign write_to_pre_start = (state_c==WRITE && end_cnt0);
assign read_to_pre_start = (state_c==READ && end_cnt0);
//指令控制字 sdram_commond = {CS,RAS,CAS,WE};
always @(posedge clk or negedge rst_n)begin
if (!rst_n) begin
sdram_commond <= NOP_CMD;
end
else if (nop_to_pre_start || write_to_pre_start || read_to_pre_start) begin
sdram_commond <= PRECHARGE_CMD;
end
else if (pre_to_ref_start || ref_to_ref_start || idle_to_ref_start) begin
sdram_commond <= REF_CMD;
end
else if (ref_to_mode_start) begin
sdram_commond <= MODE_CMD;
end
else if (idle_to_active_start) begin
sdram_commond <= ACTIVE_CMD;
end
else if (active_to_write_start) begin
sdram_commond <= WRITE_CMD;
end
else if (active_to_read_start) begin
sdram_commond <= READ_CMD;
end
else begin
sdram_commond <= NOP_CMD;
end
end
//cke信号保持拉高
assign sdram_cke = 1;
//dqm信号
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sdram_dqm <= 2'b11;
end
else if(init_done)begin
sdram_dqm <= 2'b00;
end
end
//位址線
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sdram_addr <= 12'b0;
end
else if(nop_to_pre_start || write_to_pre_start || read_to_pre_start)begin
sdram_addr <= ALL_BANK;
end
else if(ref_to_mode_start)begin
sdram_addr <= MODE_CONFIG;
end
else if(idle_to_active_start)begin
sdram_addr <= row_addr;
end
else if (active_to_read_start || active_to_write_start) begin
sdram_addr <= {3'b000,col_addr};
end
else begin
sdram_addr <= 12'b0;
end
end
//sdram_bank
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sdram_bank <= 2'b00;
end
else if (idle_to_active_start || active_to_write_start || active_to_read_start) begin
sdram_bank <= bank_addr;
end
else begin
sdram_bank <= 2'b00;
end
end
//sdram_dq
assign sdram_dq_en = (state_c==WRITE) ? 1'b1 : 1'b0;
assign sdram_dq = sdram_dq_en ? sdram_dq_r : 16'hzzzz;
assign sdram_dq_r = wr_data;
assign wr_data_vld = state_c==WRITE;
assign wdata_done = write_to_pre_start;
assign rdata_done = read_to_pre_start;
always @(posedge clk or negedge rst_n)begin
if (!rst_n) begin
rd_data <= 16'd0;
end
else begin
rd_data <= sdram_dq;
end
end
// assign rd_data = state_c==READ ? sdram_dq:16'hzzzz;
//讀有效标志
always @(posedge clk or negedge rst_n)begin
if (!rst_n) begin
rd_data_vld_ff0 <= 0;
end
else if (active_to_read_start) begin
rd_data_vld_ff0 <= 1;
end
else if (state_c==READ && cnt0==TREAD_11-4) begin
rd_data_vld_ff0 <= 0;
end
end
always @(posedge clk or negedge rst_n)begin
if (!rst_n) begin
rd_data_vld_ff1 <= 0;
rd_data_vld_ff2 <= 0;
rd_data_vld_ff3 <= 0;
rd_data_vld <= 0;
end
else begin
rd_data_vld_ff1 <= rd_data_vld_ff0;
rd_data_vld_ff2 <= rd_data_vld_ff1;
rd_data_vld_ff3 <= rd_data_vld_ff2;
rd_data_vld <= rd_data_vld_ff3;
end
end
//重新整理請求計數
always @(posedge clk or negedge rst_n)begin
if (!rst_n) begin
auto_ref_cnt <= 0;
end
else if (add_auto_ref_cnt) begin
if (end_auto_ref_cnt) begin
auto_ref_cnt <= 0;
end
else begin
auto_ref_cnt <= auto_ref_cnt + 1'b1;
end
end
end
assign add_auto_ref_cnt = init_flag==0;
assign end_auto_ref_cnt = (add_auto_ref_cnt && auto_ref_cnt==AUTO_REF_TIME-1);
//ref_req 重新整理請求
always @(posedge clk or negedge rst_n)begin
if (!rst_n) begin
ref_req <= 0;
end
else if (end_auto_ref_cnt) begin
ref_req <= 1;
end
else if (state_c==IDLE && ref_req==1) begin
ref_req <= 0;
end
end
//初始化标志
assign init_done = (state_c==MODE && end_cnt0);
always @(posedge clk or negedge rst_n)begin
if (!rst_n) begin
init_flag <= 1;
end
else if (init_done) begin
init_flag <= 0;
end
end
always @(posedge clk or negedge rst_n)begin
if (!rst_n) begin
cnt0 <= 0;
end
else if (add_cnt0) begin
if (end_cnt0) begin
cnt0 <= 0;
end
else begin
cnt0 <= cnt0 + 1'b1;
end
end
end
assign add_cnt0 = state_c!=IDLE;
assign end_cnt0 = add_cnt0 && cnt0==x-1'b1;
always @(*)begin
case(state_c)
NOP : x = TWAIT_200us;
PRECHARGE : x = TRP;
REF : x = TRC;
MODE : x = TRSC;
ACTIVE : x = TRCD;
WRITE : x = TWRITE_8;
READ : x = TREAD_11;
default : x = 0;
endcase
end
//初始化自重新整理8個周期計數
always @(posedge clk or negedge rst_n)begin
if (!rst_n) begin
ref_cnt1 <= 0;
end
else if (add_cnt1) begin
if (end_cnt1) begin
ref_cnt1 <= 0;
end
else begin
ref_cnt1 <= ref_cnt1 + 1'b1;
end
end
end
assign add_cnt1 = (state_c==REF && init_flag==1 && end_cnt0);
assign end_cnt1 = (add_cnt1 && ref_cnt1== 8-1);
//讀寫信号标志
always @(posedge clk or negedge rst_n)begin
if (!rst_n) begin
flag_rd <= 0;
end
else if (state_c==IDLE && rd_en && ref_req==0) begin
flag_rd <= 1;
end
else if (pre_to_idle_start && flag_rd==1) begin
flag_rd <= 0;
end
end
always @(posedge clk or negedge rst_n)begin
if (!rst_n) begin
flag_wr <= 0;
end
else if (state_c==IDLE && wr_en && rd_en==0 && ref_req==0) begin //讀優先級高于寫優先級
flag_wr <= 1;
end
else if (pre_to_idle_start && flag_wr==1) begin
flag_wr <= 0;
end
end
//寫FIFO讀請求信号
always @(posedge clk or negedge rst_n)begin
if (!rst_n) begin
fifo_rdreq_f <= 0;
end
else if (state_c==IDLE && wr_en && rd_en==0 && ref_req==0) begin
fifo_rdreq_f <= 1;
end
else if (fifo_rdreq_cnt>=7) begin
fifo_rdreq_f <= 0;
end
end
always @(posedge clk or negedge rst_n)begin
if (!rst_n) begin
fifo_rdreq_cnt <= 0;
end
else if (fifo_rdreq_f) begin
if (fifo_rdreq_cnt >= 7) begin
fifo_rdreq_cnt <= 0;
end
else begin
fifo_rdreq_cnt <= fifo_rdreq_cnt + 1'b1;
end
end
end
//延遲一拍,時序對齊
always @(posedge clk or negedge rst_n)begin
if (!rst_n) begin
fifo_rdreq <= 0;
end
else begin
fifo_rdreq <= fifo_rdreq_f;
end
end
endmodule
9、VGA控制子產品
本專題部落格講的很詳細,也沒有改動,這裡不再贅述。
10、頂層子產品
// Company :
// Engineer :
// -----------------------------------------------------------------------------
// https://blog.csdn.net/qq_33231534 PHF's CSDN blog
// -----------------------------------------------------------------------------
// Create Date : 2020-09-27 11:04:42
// Revise Data : 2020-09-30 11:11:00
// File Name : camera_ov7670_top.v
// Target Devices : XC7Z015-CLG485-2
// Tool Versions : Vivado 2019.2
// Revision : V1.1
// Editor : sublime text3, tab size (4)
// Description :
module camera_ov7670_top(
input clk ,//系統時鐘50MHz
input rst_n ,//系統複位
input vsync ,//OV7670子產品輸入場同步信号
input href ,//OV7670子產品輸入行同步信号
input [7:0] din ,//OV7670子產品攝像頭資料輸入
input pclk ,//OV7670子產品像素時鐘輸入
output scl ,//OV7670子產品配置SCCB協定時鐘線
inout sda ,//OV7670子產品配置SCCB協定資料線
output xclk ,//OV7670子產品輸入時鐘
output pwdn ,//OV7670子產品模式選擇 0:工作 1:POWER DOWN
output reset ,//OV7670子產品初始化所有寄存器到預設值 0:RESET 模式 1:一般模式
output wire VGA_clk ,//25MHz
output wire[23:0] VGA_RGB ,//VGA子產品圖像資料{R[7:0],G[7:0],B[7:0]}
output wire VGA_HS ,//VGA子產品行同步信号
output wire VGA_VS ,//VGA子產品場同步信号
output wire VGA_BLK ,//VGA子產品消影信号
output wire sdram_clk ,//SDRAM時鐘信号
output wire[3:0] sdram_commond ,//{cs,ras,cas,we}
output wire sdram_cke ,//時鐘使能信号
output wire[1:0] sdram_dqm ,//資料線屏蔽信号
output wire[11:0] sdram_addr ,//SDRAM位址線
output wire[1:0] sdram_bank ,//SDRAM bank選取
inout wire[15:0] sdram_dq //SDRAM資料輸出輸入總線
);
wire clk_25M ;
wire clk_100M ;
wire init_done ;
wire [15:0] data_rgb565 ;
wire data_rgb565_vld ;
wire ov7670_vsync ;
wire fifo_rdreq ;
wire [15:0] wr_data ;
wire [7:0] rdusedw ;
wire [7:0] rdusedw1 ;
wire wdata_done ;
wire wr_en ;
wire [11:0] wr_row_addr ;
wire [8:0] wr_col_addr ;
wire rd_en ;
reg [11:0] row_addr ;
reg [8:0] col_addr ;
wire [15:0] rd_data ;
wire rd_data_vld ;
wire rdata_done ;
wire [11:0] rd_row_addr ;
wire [8:0] rd_col_addr ;
wire dat_act ;
wire [15:0] q ;
wire wr_addr_req ;
wire rd_addr_req ;
wire state_wr_req ;
wire state_rd_req ;
assign xclk = clk_25M;
assign pwdn = 0;
assign reset = 1;
always @(*) begin
if (!rst_n) begin
row_addr <= 12'd0;
col_addr <= 9'd0;
end
else if (wr_addr_req) begin
row_addr <= wr_row_addr;
col_addr <= wr_col_addr;
end
else if (rd_addr_req) begin
row_addr <= rd_row_addr;
col_addr <= rd_col_addr;
end
else begin
row_addr <= row_addr;
col_addr <= col_addr;
end
end
assign wr_addr_req = (wr_en&&!rd_addr_req&&state_wr_req)?1'b1:1'b0;
assign rd_addr_req = (rd_en&&state_rd_req)?1'b1:1'b0;
pll inst_pll
(
.inclk0(clk),
.c0(clk_100M),
.c1(clk_25M)
);
ov7670_init inst_ov7670_init
(
.clk(clk_25M),
.rst_n(rst_n),
.scl(scl),
.sda(sda),
.init_done(init_done)
);
ov7670_data_16rgb565 inst_ov7670_data_16rgb565
(
.clk (pclk),
.rst_n (rst_n),
.vsync (vsync),
.href (href),
.din (din),
.init_done (init_done),
.data_rgb565 (data_rgb565),
.data_rgb565_vld (data_rgb565_vld),
.ov7670_vsync (ov7670_vsync) //用來給寫FIFO清零
);
async_fifo wr_async_fifo
(
.aclr (ov7670_vsync),
.data (data_rgb565),
.rdclk (clk_100M),
.rdreq (fifo_rdreq),
.wrclk (clk_25M),
.wrreq (data_rgb565_vld),
.q (wr_data),
.rdempty (),
.rdusedw (rdusedw),
.wrfull ()
);
wr_control inst_wr_control
(
.clk (clk_100M),
.rst_n (rst_n),
.rdusedw (rdusedw),
.row_addr_max (10'd600),
.col_addr_max (10'd512),
.sdram_wdata_done (wdata_done),
.aclr (ov7670_vsync),
.sdram_wr_en (wr_en),
.wr_row_addr (wr_row_addr), //
.wr_col_addr (wr_col_addr) //
);
SDRAM_control inst_SDRAM_control
(
.clk (clk_100M),
.rst_n (rst_n),
.wr_en (wr_en),
.wr_data (wr_data),
.rd_en (rd_en),
.bank_addr (2'b00),
.row_addr (row_addr),
.col_addr (col_addr),
.fifo_rdreq (fifo_rdreq),
.rd_data (rd_data),
.rd_data_vld (rd_data_vld),
.wr_data_vld (),
.wdata_done (wdata_done),
.rdata_done (rdata_done),
.sdram_clk (sdram_clk),
.sdram_commond (sdram_commond),
.sdram_cke (sdram_cke),
.sdram_dqm (sdram_dqm),
.sdram_addr (sdram_addr),
.sdram_bank (sdram_bank),
.sdram_dq (sdram_dq),
.state_wr_req (state_wr_req),
.state_rd_req (state_rd_req)
);
rd_control inst_rd_control
(
.clk (clk_100M),
.rst_n (rst_n),
.rdusedw (rdusedw1),
.row_addr_max (10'd600),
.col_addr_max (10'd512),
.sdram_rdata_done (rdata_done),
.aclr (),
.sdram_rd_en (rd_en),
.rd_row_addr (rd_row_addr), //
.rd_col_addr (rd_col_addr) //
);
async_fifo_ahead inst_async_fifo_ahead
(
.aclr (),
.data (rd_data),
.rdclk (clk_25M),
.rdreq (dat_act),
.wrclk (clk_100M),
.wrreq (rd_data_vld),
.q (q),
.rdempty (),
.rdusedw (rdusedw1),
.wrfull ()
);
VGA_ctrl inst_VGA_ctrl
(
.clk_25M (clk_25M),
.rst_n (rst_n),
.data_in ({q[15:11],3'b000,q[10:5],2'b00,q[4:0],3'b000}),
.VGA_clk (VGA_clk),
.VGA_RGB (VGA_RGB),
.VGA_HS (VGA_HS),
.VGA_VS (VGA_VS),
.VGA_BLK (VGA_BLK),
.hcount (),
.vcount (),
.dat_act (dat_act)
);
endmodule
三、仿真測試
仿真的時候沒有加入攝像頭資料流部分,從寫FIFO開始,資料自己設定寫入寫FIFO,經過SDRAM存儲,這裡用SDRAM的仿真模型模拟SDRAM,然後讀出資料到讀FIFO中,再從讀FIFO中讀出資料。
測試模型的頂層代碼為:
// Company :
// Engineer :
// -----------------------------------------------------------------------------
// https://blog.csdn.net/qq_33231534 PHF's CSDN blog
// -----------------------------------------------------------------------------
// Create Date : 2020-10-20 17:19:56
// Revise Data : 2020-10-20 17:19:56
// File Name : SDRAM_control_tb.v
// Target Devices : XC7Z015-CLG485-2
// Tool Versions : Vivado 2019.2
// Revision : V1.1
// Editor : sublime text3, tab size (4)
// Description :
】
module SDRAM_control_test(
input clk_25M,
input clk_100M,
input rst_n,
input [15:0] data_rgb565,
input data_rgb565_vld,
input ov7670_vsync,
output [15:0] q
);
reg [11:0] row_addr;
reg [8:0] col_addr;
wire fifo_rdreq;
wire [15:0] wr_data;
wire [7:0] rdusedw;
wire wdata_done;
wire wr_en;
wire [11:0] wr_row_addr;
wire [8:0] wr_col_addr;
wire rd_en;
wire [15:0] rd_data;
wire rd_data_vld;
wire rdata_done;
wire sdram_clk;
wire [3:0] sdram_commond;
wire sdram_cke;
wire [1:0] sdram_dqm;
wire [11:0] sdram_addr;
wire [1:0] sdram_bank;
wire [15:0] sdram_dq;
wire [7:0] rdusedw1;
wire [11:0] rd_row_addr;
wire [8:0] rd_col_addr;
// wire [15:0] q;
wire wr_addr_req ;
wire rd_addr_req ;
wire state_wr_req ;
wire state_rd_req ;
//重點出錯
assign row_addr = state_wr_req ? wr_row_addr : rd_row_addr;
assign col_addr = state_wr_req ? wr_col_addr : rd_col_addr;
async_fifo wr_async_fifo
(
.aclr (ov7670_vsync),
.data (data_rgb565),
.rdclk (clk_100M),
.rdreq (fifo_rdreq),
.wrclk (clk_25M),
.wrreq (data_rgb565_vld),
.q (wr_data),
.rdempty (),
.rdusedw (rdusedw),
.wrfull ()
);
wr_control inst_wr_control
(
.clk (clk_100M),
.rst_n (rst_n),
.rdusedw (rdusedw),
.row_addr_max (10'd600),
.col_addr_max (10'd512),
.sdram_wdata_done (wdata_done),
.aclr (ov7670_vsync),
.sdram_wr_en (wr_en),
.wr_row_addr (wr_row_addr), //
.wr_col_addr (wr_col_addr) //
);
SDRAM_control inst_SDRAM_control
(
.clk (clk_100M),
.rst_n (rst_n),
.wr_en (wr_en),
.wr_data (wr_data),
.rd_en (rd_en),
.bank_addr (2'b00),
.row_addr (row_addr),
.col_addr (col_addr),
.fifo_rdreq (fifo_rdreq),
.rd_data (rd_data),
.rd_data_vld (rd_data_vld),
.wr_data_vld (),
.wdata_done (wdata_done),
.rdata_done (rdata_done),
.sdram_clk (sdram_clk),
.sdram_commond (sdram_commond),
.sdram_cke (sdram_cke),
.sdram_dqm (sdram_dqm),
.sdram_addr (sdram_addr),
.sdram_bank (sdram_bank),
.sdram_dq (sdram_dq),
.state_wr_req (state_wr_req),
.state_rd_req (state_rd_req)
);
sdram_model_plus #(
.addr_bits(12),
.data_bits(16),
.col_bits(9),
.mem_sizes(640*500)
) inst_sdram_model_plus (
.Dq (sdram_dq),
.Addr (sdram_addr),
.Ba (sdram_bank),
.Clk (sdram_clk),
.Cke (sdram_cke),
.Cs_n (sdram_commond[3]),
.Ras_n (sdram_commond[2]),
.Cas_n (sdram_commond[1]),
.We_n (sdram_commond[0]),
.Dqm (sdram_dqm),
.Debug (1'b1)
);
rd_control inst_rd_control
(
.clk (clk_100M),
.rst_n (rst_n),
.rdusedw (rdusedw1),
.row_addr_max (10'd600),
.col_addr_max (10'd512),
.sdram_rdata_done (rdata_done),
.aclr (),
.sdram_rd_en (rd_en),
.rd_row_addr (rd_row_addr), //
.rd_col_addr (rd_col_addr) //
);
async_fifo_ahead inst_async_fifo_ahead
(
.aclr (),
.data (rd_data),
.rdclk (clk_25M),
.rdreq (1'b1),
.wrclk (clk_100M),
.wrreq (rd_data_vld),
.q (q),
.rdempty (),
.rdusedw (rdusedw1),
.wrfull ()
);
endmodule
測試代碼為:發送兩幀資料
`timescale 1ns/1ns
module SDRAM_control_test_tb (); /* this is automatically generated */
reg rst_n;
reg clk_100M;
reg clk_25M;
// (*NOTE*) replace reset, clock, others
reg [15:0] data_rgb565;
reg data_rgb565_vld;
reg ov7670_vsync;
wire [15:0] q;
SDRAM_control_test inst_SDRAM_control_test
(
.clk_25M (clk_25M),
.clk_100M (clk_100M),
.rst_n (rst_n),
.data_rgb565 (data_rgb565),
.data_rgb565_vld (data_rgb565_vld),
.ov7670_vsync (ov7670_vsync),
.q (q)
);
initial clk_25M = 1;
always #20 clk_25M = ~clk_25M;
initial clk_100M = 1;
always #5 clk_100M = ~clk_100M;
initial begin
#1;
rst_n = 0;
data_rgb565 = 0;
data_rgb565_vld = 0;
ov7670_vsync = 0;
#200;
rst_n = 1;
#200;
@(posedge inst_SDRAM_control_test.inst_SDRAM_control.mode_to_idle_start)
#2000;
data_rgb565_vld = 1;
repeat(600)begin
repeat(512)begin
#40;
data_rgb565 = data_rgb565 + 1;
end
end
data_rgb565_vld = 0;
#20000;
data_rgb565_vld = 1;
repeat(600)begin
repeat(512)begin
#40;
data_rgb565 = data_rgb565 + 1;
end
end
#200;
$stop;
end
endmodule
仿真後可觀察modelsim控制台資訊,觀察SDRAM資料讀寫的記錄,從中觀察是否有錯誤。如圖:
這裡容易出錯的地方是SDRAM讀寫位址,仿真中容易在讀資料時用的是寫資料的行位址,如圖:
SDRAM讀寫位址代碼如下,這裡高了挺久,很容易出錯,對時序要求挺高,隻能用組合電路實作。
四、上闆驗證
剛開始寫好代碼上闆圖:
調好焦距以後(很大部分資料錯亂):
一次修改後上闆圖(少部分資料錯亂):
最終圖(無資料丢失錯亂):
五、總結
這個代碼寫好挺久了,但有問題。兩三個星期,然後因為各種事情耽擱了,就一直沒有修改,找問題。一開始出的圖資料丢失錯亂嚴重,通過quartus軟體的sjgnal tap logic analyzer工具抓取資料分析,由于抓取資料有限,根本看不出來哪裡的問題,是以後邊做了挺久也沒找到問題在哪。自己分析應該是SDRAM控制器寫的有問題,于是就對SDRAM控制器進行了仿真測試,當然測試情況要多一些,然後還是發現沒什麼問題。然後昨天寫了個測試代碼,就是上邊給出來的,仿真測試的時候,在modelsim控制台輸出資訊有sdram讀取資料的資訊,就發現讀資料時行位址有時候會用上一次讀資料的行位址,這個問題從這裡就找出來了,通過定位發現,在頂層子產品中,對SDRAM讀寫位址部分代碼寫的有問題,對時序要求較高,才出現問題,這裡對這個問題不做細講,第一次修改後明顯圖像好了很多,還是有資料錯亂丢失,其實還是這裡沒寫好,經過修改後就沒有這些問題了。
在代碼出現問題的時候千萬不要嫌麻煩,多動手多測試,問題總會解決的。
工程檔案下載下傳連結:https://download.csdn.net/download/qq_33231534/13010542