- 实现目标:上位机通过串口发送图片到FPGA中,FPGA接收图片并保存在DDR2中,保存在 DDR2中的图片分为两路,一路经过VGA时序输出到RGB触摸屏上,另一路按照以太网帧格式进 行编码, 将以太网帧发送到PC端后,可用上位机软件实时显示视频图像。
- 主要工作:1.FPGA串口图像接收。2.DDR2读写。3.千兆以太网发送。4.VGA视频图像显示。
- 使用工具:Quartus ii、Modelsim、Wireshark、小梅哥UDP摄像头、串口传图工具。
三、DDR2控制器设计
1.DDR2 IP核调用
根据自己板子上的DDR2型号进行配置,我这里使用的是小梅哥AC6012开发板,所以配置参数按照小梅哥教程配置。
突发长度为8
其他部分保持不变,最终Finish完成。
DDR2输入输出接口还需要两个FIFO
WR_FIFO
RD_FIFO
DDR2控制器设计如下:
module DDR2_Control_4Port
(
// HOST Side
REF_CLK,
RESET_N,
GLOBAL_RES,
// FIFO Write Side 1
WR1_DATA,
WR1,
WR1_ADDR,
WR1_MAX_ADDR,
WR1_LENGTH,
WR1_LOAD,
WR1_CLK,
WR1_FULL,
WR1_USE,
// FIFO Read Side 1
RD1_DATA,
RD1,
RD1_ADDR,
RD1_MAX_ADDR,
RD1_LENGTH,
RD1_LOAD,
RD1_CLK,
// DDR Side
mem_odt,
mem_cs_n,
mem_cke,
mem_addr,
mem_ba,
mem_ras_n,
mem_cas_n,
mem_we_n,
mem_dm,
mem_clk,
mem_clk_n,
mem_dq,
mem_dqs,
local_rdata
);
`define ASIZE 24 // total address width of the DDR2
`define DSIEZE_IN 16 // data width of the CMOS
`define DSIEZE_DDR2 32 // data width of the DDR2 IP
`define RATIO 2 // the data width ratio of `DSIEZE_DDR2/`DSIEZE_IN
// HOST Side
input REF_CLK; //System Clock
input RESET_N; //System Reset
output GLOBAL_RES;
// FIFO Write Side 1
input [`DSIEZE_IN-1:0] WR1_DATA; //Data input
input WR1; //Write Request
input [`ASIZE-1:0] WR1_ADDR; //Write start address
input [`ASIZE-1:0] WR1_MAX_ADDR; //Write max address
input [`ASIZE-1:0] WR1_LENGTH; //Write length
input WR1_LOAD; //Write register load & fifo clear
input WR1_CLK; //Write fifo clock
output WR1_FULL; //Write fifo full
output[`ASIZE-1:0] WR1_USE; //Write fifo usedw
// FIFO Read Side 1
output[`DSIEZE_IN-1:0] RD1_DATA; //Data output
input RD1; //Read Request
input [`ASIZE-1:0] RD1_ADDR; //Read start address
input [`ASIZE-1:0] RD1_MAX_ADDR; //Read max address
input [`ASIZE-1:0] RD1_LENGTH; //Read length
input RD1_LOAD; //Read register load & fifo clear
input RD1_CLK; //Read fifo clock
output[0:0] mem_odt;
output[0:0] mem_cs_n;
output[0:0] mem_cke;
output[12:0] mem_addr;
output[2:0] mem_ba;
output mem_ras_n;
output mem_cas_n;
output mem_we_n;
output[1:0] mem_dm;
inout [0:0] mem_clk;
inout [0:0] mem_clk_n;
inout [15:0] mem_dq;
inout [1:0] mem_dqs;
// Internal Registers/Wires
// Controller
reg [`ASIZE-1:0] mADDR; //Internal address
reg [`ASIZE-1:0] mLENGTH; //Internal length
reg [`ASIZE-1:0] rWR1_ADDR; //Register write address
reg [`ASIZE-1:0] rWR1_MAX_ADDR; //Register max write address
reg [`ASIZE-1:0] rWR1_LENGTH; //Register write length
reg [`ASIZE-1:0] rRD1_ADDR; //Register read address
reg [`ASIZE-1:0] rRD1_MAX_ADDR; //Register max read address
reg [`ASIZE-1:0] rRD1_LENGTH; //Register read length
reg [1:0] WR_MASK; //Write port active mask
reg [1:0] RD_MASK; //Read port active mask
reg mWR_DONE; //Flag write done, 1 pulse SDR_CLK
reg mRD_DONE; //Flag read done, 1 pulse SDR_CLK
reg mWR; //Internal WR edge capture
reg mRD; //Internal RD edge capture
wire [`DSIEZE_DDR2-1:0] WR_DATA1; //Controller Data input 1
wire [`DSIEZE_DDR2-1:0] WR_DATA2; //Controller Data input 2
// FIFO Control
wire [`ASIZE-1:0] write_side_fifo_rusedw1;
wire [`ASIZE-1:0] read_side_fifo_wusedw1;
wire [`ASIZE-1:0] write_side_fifo_rusedw2;
wire [`ASIZE-1:0] read_side_fifo_wusedw2;
// DRAM Internal Control
wire CLK;
wire [3:0] local_size;
wire local_ready;
wire [3:0] local_be;
reg [`DSIEZE_DDR2-1:0] local_wdata;
output wire [`DSIEZE_DDR2-1:0] local_rdata;
reg [`ASIZE:0] local_address;
reg local_read_req;
reg local_write_req;
reg local_burstbegin;
wire local_init_done;
wire local_rdata_valid;
assign local_be = 4'hF;//写入字节使能,每一位对应一个字节
assign local_size = 4'h8;//用户接口的突发长度,指定每次用户发起一次写入请求会写入多个数据
assign GLOBAL_RES = local_init_done;
//DDR2 IP核
DDR2 I_DDR2
(
.pll_ref_clk (REF_CLK),
.phy_clk (CLK),
.soft_reset_n (1'b1),
.global_reset_n (RESET_N),
.local_address (local_address), //数据写入的DDR2存储器的地址{CS,ROW,BA,COL}
.local_write_req (local_write_req), //写数据请求信号为1则local_wdata端口上的数据允许被写入ddr2控制器
.local_wdata (local_wdata), //写入ddr2中的数据
.local_read_req (local_read_req), //读请求信号
.local_rdata (local_rdata), //ddr2中读出的数据
.local_burstbegin (local_burstbegin), //用户接口的突发启动信号,该信号每一个高脉冲会启动一次突发传输,每次传输长度为local_size指定的长度
.local_be (local_be), //写入字节使能,每一位对应一个字节
.local_size (local_size), //用户接口的突发长度,指定每次用户发起一次写入请求会写入多个数据,最大不超过IP核配置中的最大长度
.local_ready (local_ready), //ddr控制器就绪标志信号,该信号有效则表明当前数据被成功写入了ddr2控制器中,为0则表示ddr控制器无法接受当前操作,用户需要保持当前地址、数据和控制信号不变直到ready信号再次拉高
.local_rdata_valid (local_rdata_valid),//读取数据有效标志信号,当该信号有效时,表明local_rdata上的数据是读到的有效内容
.local_init_done (local_init_done), //ddr2控制器初始化完成标志信号
.mem_odt (mem_odt), //DDR2片上终结信号
.mem_cs_n (mem_cs_n), //DDR2片选信号
.mem_cke (mem_cke), //DDR2时钟使能信号
.mem_addr (mem_addr), //DDR2地址总线
.mem_ba (mem_ba), //DDR2BANK信号
.mem_ras_n (mem_ras_n), //DDR2行地址选择信号
.mem_cas_n (mem_cas_n), //DDR2列地址选择信号
.mem_we_n (mem_we_n), //DDR2写使能信号
.mem_dm (mem_dm), //DDR2数据掩膜信号
.mem_clk (mem_clk), //DDR2时钟信号
.mem_clk_n (mem_clk_n), //DDR2时钟反相信号
.mem_dq (mem_dq), //DDR2数据总线
.mem_dqs (mem_dqs) //DDR2数据源同步信号
);
reg WR_FIFO_EN;
//写FIFO 16转32位,show-ahead模式
WR_FIFO WR_FIFO_1
(
.data (WR1_DATA),
.wrreq (WR1),
.wrclk (WR1_CLK),
.aclr ((!local_init_done) || WR1_LOAD),
.rdreq (WR_FIFO_EN & WR_MASK[0]),
.rdclk (CLK),
.q (WR_DATA1),
.wrfull (WR1_FULL),
.wrusedw (WR1_USE),
.rdusedw (write_side_fifo_rusedw1)
);
//读FIFO 32转16位,show-ahead模式
RD_FIFO RD_FIFO_1
(
.data (local_rdata),
.wrreq (local_rdata_valid & RD_MASK[0]),
.wrclk (CLK),
.aclr ((!local_init_done) || RD1_LOAD),
.rdreq (RD1),
.rdclk (RD1_CLK),
.q (RD1_DATA),
.wrusedw (read_side_fifo_wusedw1)
);
//读写控制器
reg [9:0] Write_NUM;
reg [9:0] Read_NUM;
reg [3:0] WR_Step;
reg [3:0] RD_Step;
reg [7:0] WR_Time;
reg [7:0] RD_Time;
always@(posedge CLK or negedge local_init_done)
begin
if(!local_init_done)
begin
mWR_DONE <= 0;
mRD_DONE <= 0;
local_write_req <= 0;
local_read_req <= 0;
local_burstbegin <= 0;
local_address <= 0;
local_wdata <= 0;
Write_NUM <= 0;
Read_NUM <= 0;
WR_FIFO_EN <= 0;
WR_Step <= 0;
RD_Step <= 0;
end
else
begin
case(WR_MASK)
2'b01 :
local_wdata <= WR_DATA1;
2'b10 :
local_wdata <= WR_DATA2;
default:
local_wdata <= 0;
endcase
if(mWR)//写判断
begin
if(local_ready)//ddr控制器就绪标志信号,只有该标志为1才能写入
begin
case(WR_Step)
4'd0: //先初始化
begin
local_write_req <= 0; local_burstbegin <= 0; Write_NUM <= 0; WR_Time <= 0; WR_FIFO_EN <= 0; WR_Step <= 4'd1;
end
4'd1://写FIFO模块的读侧使能
begin
WR_FIFO_EN <= 1; WR_Step <= 4'd2;
end
4'd2://发送写请求,突发开始,写地址赋值
begin
local_write_req <= 1; local_burstbegin <= 1; local_address<= mADDR + WR_Time * local_size; WR_FIFO_EN <= 1;
WR_Step <= 4'd3;
end
4'd3://写数据:每次突发连续写8次,一共写WR_Time次
begin
local_burstbegin <= 1'b0;
if(Write_NUM >= local_size - 2)
WR_FIFO_EN <= 0;
else
WR_FIFO_EN <= 1;
if(Write_NUM < local_size - 1)
begin
Write_NUM <= Write_NUM + 1'b1; WR_Step <= 4'd3;
end
else
begin
Write_NUM <= 0; local_write_req <= 0;
if(WR_Time < mLENGTH/local_size -1) //判断写到第几个批次的突发写周期
begin
WR_Time <= WR_Time + 8'd1; WR_Step <= 4'd1;
end
else
begin
mWR_DONE <= 1; WR_Step <= 4'd0; //发送写完成标志,写状态回到初始态
end
end
end
default:
WR_Step <= 4'd0;
endcase
end
else
begin
local_write_req <= 0; WR_FIFO_EN <= 0;
end
end
else
begin
mWR_DONE <= 0; local_write_req <= 0; local_burstbegin <= 0; Write_NUM <= 0; WR_Time <= 0; WR_FIFO_EN <= 0; WR_Step <= 4'd0;
end
if(mRD)//读判断
begin
case(RD_Step)
4'd0: //初始化
begin
local_burstbegin <= 0; local_read_req <= 0; Read_NUM <= 0; RD_Time <= 0; RD_Step <= 4'd1;
end
4'd1: //发送读请求,突发开始,读地址赋值
begin
local_read_req <= 1; local_burstbegin <= 1'b1; Read_NUM <= 0; local_address <= mADDR + RD_Time * local_size ; RD_Step <= 4'd2;
end
4'd2:
begin
if(local_ready)//ddr控制器就绪标志信号,只有该标志为1才能读
begin
local_burstbegin <= 1'b0; local_read_req <= 0;
if(local_rdata_valid)//读取数据有效标志信号,当该信号有效时,表明local_rdata上的数据是读到的有效内容。读数据:每次突发连续读8次,一共写RD_Time次
begin
if(Read_NUM < local_size -1)
begin
Read_NUM <= Read_NUM + 1'b1; RD_Step <= 4'd2;
end
else
begin
Read_NUM <= 0;
if(RD_Time < mLENGTH/local_size -1)
begin
RD_Time <= RD_Time + 8'd1; RD_Step <= 4'd1;
end
else
begin
RD_Time <= 0; mRD_DONE <= 1; RD_Step <= 4'd0;
end
end
end
end
else
RD_Step <= 4'd2;
end
default:
RD_Step <= 4'd0;
endcase
end
else
begin
mRD_DONE <= 0; local_burstbegin <= 0; local_read_req <= 0; Read_NUM <= 0; RD_Time <= 0; RD_Step <= 4'd0;
end
end
end
// Internal Address & Length Control
always@(posedge CLK or negedge local_init_done)
begin
if(!local_init_done)//赋初值
begin
rWR1_ADDR <= WR1_ADDR/`RATIO;
rWR1_MAX_ADDR <= WR1_MAX_ADDR;
rRD1_ADDR <= RD1_ADDR/`RATIO;
rRD1_MAX_ADDR <= RD1_MAX_ADDR;
rWR1_LENGTH <= WR1_LENGTH;
rRD1_LENGTH <= RD1_LENGTH;
end
else
begin
// Write Side 1
if(WR1_LOAD)//加载写入ddr2的地址和写入的长度
begin
rWR1_ADDR <= WR1_ADDR/`RATIO;
rWR1_LENGTH <= WR1_LENGTH;
end
else if(mWR_DONE&WR_MASK[0])//每次写完rWR1_LENGTH长度后更新写地址+rWR1_LENGTH
begin
if(rWR1_ADDR<rWR1_MAX_ADDR/`RATIO-rWR1_LENGTH)
rWR1_ADDR <= rWR1_ADDR + rWR1_LENGTH;
else
rWR1_ADDR <= WR1_ADDR/`RATIO;//写完一张图片以后写地址归零
end
// Read Side 1
if(RD1_LOAD)//加载读ddr2的地址和读的长度
begin
rRD1_ADDR <= RD1_ADDR/`RATIO;
rRD1_LENGTH <= RD1_LENGTH;
end
else if(mRD_DONE&RD_MASK[0])//每次读完rRD1_LENGTH长度后更新写地址+rRD1_LENGTH
begin
if(rRD1_ADDR<rRD1_MAX_ADDR/`RATIO-rRD1_LENGTH)
rRD1_ADDR <= rRD1_ADDR+rRD1_LENGTH;
else
rRD1_ADDR <= RD1_ADDR/`RATIO;
end
end
end
//****************Global controller**************************//
/*
*/
always@(posedge CLK or negedge local_init_done)
begin
if(!local_init_done)
begin
mWR <= 0;
mRD <= 0;
mADDR <= 0;
mLENGTH <= 0;
WR_MASK <= 2'b00;
RD_MASK <= 2'b00;
end
else
begin
if( (mWR==0) && (mRD==0) &&
(WR_MASK==2'b0) && (RD_MASK==2'b0) &&
(WR1_LOAD==1'b0) && (RD1_LOAD==1'b0)
)
begin
// Write Side 1 当写FIFO中的数据超过rWR1_LENGTH时,写FIFO向DDR2中写入数据
if( (write_side_fifo_rusedw1 >= rWR1_LENGTH) && (rWR1_LENGTH!=0) )
begin
mADDR <= rWR1_ADDR;
mLENGTH <= rWR1_LENGTH;
WR_MASK <= 2'b01;
RD_MASK <= 2'b00;
mWR <= 1;
mRD <= 0;
end
// Read Side 1 当读FIFO中的数据少于rRD1_LENGTH时,读FIFO读取DDR2中的数据
else if( (read_side_fifo_wusedw1 < rRD1_LENGTH) )
begin
mADDR <= rRD1_ADDR;
mLENGTH <= rRD1_LENGTH;
WR_MASK <= 2'b00;
RD_MASK <= 2'b01;
mWR <= 0;
mRD <= 1;
end
end
if(mWR_DONE)
begin
WR_MASK <= 0;
mWR <= 0;
end
if(mRD_DONE)
begin
RD_MASK <= 0;
mRD <= 0;
end
end
end
endmodule
参考资料:(1)小梅哥DDR2简明教程V1.1
(2)Altera DDR2控制器学习笔记
(3)咸鱼FPGA博客