本文设计思想采用明德扬至简设计法。在高速信号处理场合下,很短时间内就要缓存大量的数据,这时片内存储资源已经远远不够了。DDR SDRAM因其极高的性价比几乎是每一款中高档FPGA开发板的首选外部存储芯片。DDR操作时序非常复杂,之所以在FPGA开发中用途如此广泛,都要得意于MIG IP核。网上关于MIG控制DDR的资料很多,因此本文只讲述个人认为较重要的内容。由于MIG IP核用户接口时序较复杂,这里给出扩展接口模块用于进一步简化接口时序。
我们从IP核配置开始说起。Controller Options这页最为重要,其中包括时钟策略和外部DDR芯片参数配置。首先时钟周期选择为400MHz,此时PHY to Controller Clock Ratio只能是4:1,也就是说MIG用户侧时钟为100MHz。下半部分是选择合适的DDR芯片型号和参数,要再三确认无误。
Memory Options这页输入时钟周期选择为200MHz,根据Controller Options页的选项,该时钟经过PLL分频和倍频后的时钟分别作为用户侧时钟100MHz和DDR接口时钟100MHz。
这里有个参考时钟选项,如果Memory Options页PLL输入时钟频率选为200MHz,此处可以直接选择Use System Clock,从而简化接口。
以上是MIG IP核配置过程中较为重要的部分,实际上上述配置也可通过修改工程代码中参数来重定义。IP核配置完成,打开example design工程顶层文件,我们来重点关注下用户侧接口功能和时序。
这是本人写的注释,更具体清晰的说明还是要查看官方文档UG586.接下来看看写数据和读数据的接口时序图(时钟比例4:1,burst length = 8为例):
指令通道:
写数据:
从时序图可以看出,指令地址和数据使用两套时序,彼此相互独立。为了便于设计,直接将两套时序严格对齐(情况1)也可以正常工作。
读数据:
为什么说“时钟比例4:1,burst length = 8为例”?这一点特别关键。此时用户时钟周期是DDR接口时钟周期的4倍,也就是一个用户时钟信号上升沿对应8个DDR时钟边沿。burst length可以理解为MIG连续操作DDR地址的个数,故在4:1时钟比例下,一个用户时钟周期正好对8个地址进行了读/写操作,256bit数据分8次(32bit)写入DDR中。由此分析,在写数据时让app_wdf_end = app_wdf_wren即可,并且读/写操作时地址递增步长为8.
虽然MIG IP核提供了用户接口,但读写指令通道复用且需要实时关注两个rdy信号造成了时序操作上的不方便。为此我们需要对接口进一步封装,保证写操作时只关注:写使能user_wdata_en 写地址user_waddr 写数据user_wdata和写准备就绪信号user_wdata_rdy,读操作时只关注:读使能user_rdata_en 读地址user_raddr 读数据user_rdata 读数据有效user_rdata_vld和读操作准备就绪user_rdata_rdy。
利用扩展接口模块,将读通道和写通道接口分离,并分别例化一个FIFO缓存地址和数据。当读/写指令同时有效时,通过MIG侧的优先级轮换逻辑轮流读取其中一个FIFO,每次选一个FIFO读取直至FIFO为空再重新选择。其工程结构和核心代码如下:
读侧逻辑核心代码:
复制代码
1 //读侧--------------------------------------------------------------
2
3 always @(posedge clk or negedge rst_n )begin
4 if(rst_n==0) begin
5 rd_flag <= (0) ;
6 end
7 else if(rd_flag == 0 && mig_rdy && mig_wdf_rdy && !rdempty1 && (rdempty0 || (!rdempty0 && priority == 0)))begin
8 rd_flag <= (2'b01) ;//读取 写指令FIFO
9 end
10 else if(rd_flag == 0 && mig_rdy && !rdempty0 && (rdempty1 || (!rdempty1 && priority == 1)))begin
11 rd_flag <= (2'b10) ;//读取 读指令FIFO
12 end
13 else if((rd_flag == 2'b01 && rdempty1)www.wmyl11.com ||(rd_flag == 2'b10 && rdempty0))
14 rd_flag <= 0;
15 end
16
17 //同时非空时轮换优先级
18 always @(posedge clk or negedge rst_n )begin
19 if(rst_n==0) begin
20 priority <= (0) ;
21 end
22 else if(rd_flag == 0 && !rdempty0 && www.wmyl15.com !rdempty1)begin
23 priority <= (!priority) ;
24 end
25 end
复制代码
为了方便测试,设计样式生成模块与扩展接口模块用户侧连接,不断向一段地址写入固定数据序列并在一段时间后读回。
复制代码
1 `timescale 1ns / 1ps
2 /*
3 该模块功能:
4 周期性向一段地址执行读写操作 产生固定样式待写入数据用户测试目的
5 测试完毕后删除该模块,开发用户接口
6
7 具体为:
8 1 写从0开始之后的10个用户地址(80个DDR地址):0~9递增序列
9 2 等待20个时钟周期
10 3 读取写入的10个用户地址
11 4 等待20个时钟周期
12 5 重复上述步骤
13
14 说明:
15 1 每个步骤之间有一个时钟周期空闲
16 2 由于burst_len = 8 4:1时钟模式下一个用户时钟周期写入数据对应同样时间内8个DDR时钟边沿写入数据,
17 因此地址递增步长为8
18 */
19 module traffic_gen
20 #(parameter DATA_WIDTH = 32,
21 ADDR_WIDTH = 29)
22 (
23 input clk ,
24 input rst_n ,
25
26 output reg gen_wdata_en ,
27 output reg [ ADDR_WIDTH-1:0] gen_waddr ,
28 output reg [ DATA_WIDTH-1:0] gen_wdata ,
29 input gen_www.niucaiyule1.cn wdata_rdy ,//写指令和数据通道准备就绪
30
31 output reg gen_rdata_en ,
32 output reg [ ADDR_WIDTH-1:0] gen_raddr ,
33 input [ DATA_WIDTH-1:0] gen_rdata ,
34 input gen_rdata_vld ,
35 input gen_rdata_rdy www.dashidai1.com///读指令通道准备就绪
36 );
37
38
39 reg [ (8-1):0] cnt0 ;
40 wire add_cnt0 ;
41 wire end_cnt0 ;
42 reg [ (2-1):0] cnt1 ;
43 wire add_cnt1 ;
44 wire end_cnt1 ;
45
46 reg [ DATA_WIDTH-1:0] gen_rdata_r ;
47 reg gen_rdata_vld_r ;
48 reg com_flag ;
49
50 wire wri_state;
51 wire rd_state;
52 wire com_change_t;
53
54 //操作周期计数器,计数值为欲操作用户地址段长度+1(需要一个时钟周期空闲)
55 always @(posedge clk or negedge rst_n) begin
56 if (rst_n==0) begin
57 cnt0 <= 0;
58 end
59 else if(add_cnt0) begin
60 if(end_cnt0)
61 cnt0 <= 0;
62 else
63 cnt0 <= cnt0+1 ;
64 end
65 end
66 assign add_cnt0 = (com_flag == www.chushiyl.cn 0 && gen_wdata_rdy) || (com_flag == 1 && gen_rdata_rdy);
67 assign end_cnt0 = add_cnt0 && cnt0 == (30)-1 ;
68
69 //指令标志位 先是0--写 再是1--读
70 always @(posedge clk or negedge rst_n )begin
71 if(rst_n==0) begin
72 com_flag <= (0) ;
73 end
74 else if(com_change_t)begin
75 com_flag <= (!com_flag) ;
76 end
77 end
78
79 assign com_change_t = add_cnt0 && cnt0 == 10 - 1;
80
81 //写操作---------------------------------------------
82 always @(posedge clk or negedge rst_n )begin
83 if(rst_n==0) begin
84 gen_wdata_en <= (0) ;
85 end
86 else if(wri_state)begin
87 gen_wdata_en <= (1'b1) ;
88 end
89 else begin
90 gen_wdata_en <= (0) ;
91 end
92 end
93
94 assign wri_state = add_cnt0 && cnt0 <=www.caitianxia178.com 10-1 && com_flag == 0;
95 assign rd_state = add_cnt0 && cnt0 <= 10-1 && com_flag == 1;
96
97 always @(posedge clk or negedge rst_n )begin
98 if(rst_n==0) begin
99 gen_wdata <= (0) ;
100 end
101 else begin
102 gen_wdata <= (cnt0) ;
103 end
104 end
105
106 always@(posedge clk or negedge www.boshenyl.cn rst_n)begin
107 if(rst_n == 0)
108 gen_waddr <= 0;
109 else if(wri_state)
110 gen_waddr <= gen_waddr + 29'd8;
111 else
112 gen_waddr <= 0;
113 end
114 //读操作----------------------------------------------
115
116 always @(posedge clk or negedge rst_n )begin
117 if(rst_n==0) begin
118 gen_rdata_en <= (0) ;
119 end
120 else if(rd_state)begin
121 gen_rdata_en <= (1'b1) ;
122 end
123 else begin
124 gen_rdata_en <= (0) ;
125 end
126 end
127
128 always@(posedge clk or negedge rst_n)begin
129 if(rst_n == 0)
130 gen_raddr <= 0;
131 else if(rd_state)
132 gen_raddr <= gen_raddr + 29'd8;
133 else
134 gen_raddr <= 0;
135 end
136
137 always @(posedge clk or negedge rst_n )begin
138 if(rst_n==0) begin
139 gen_rdata_r <= (0) ;
140 end
141 else begin
142 gen_rdata_r <= (gen_rdata) ;
143 end
144 end
145
146 always @(posedge clk or negedge rst_n )begin
147 if(rst_n==0) begin
148 gen_rdata_vld_r <= (0) ;
149 end
150 else if(gen_rdata_vld)begin
151 gen_rdata_vld_r <= (1'b1) ;
152 end
153 else begin
154 gen_rdata_vld_r <= (0) ;
155 end
156 end
157
158 endmodule
复制代码
将traffic_gen和extend_interface模块例化在MIG的example design中,利用ILA抓取MIG IP核用户接口信号。
向地址8~80写入数据0~9,再从此段地址中读回数据,0~9被正确读出,MIG IP核控制DDR3读写测试完毕。
我们从IP核配置开始说起。Controller Options这页最为重要,其中包括时钟策略和外部DDR芯片参数配置。首先时钟周期选择为400MHz,此时PHY to Controller Clock Ratio只能是4:1,也就是说MIG用户侧时钟为100MHz。下半部分是选择合适的DDR芯片型号和参数,要再三确认无误。
Memory Options这页输入时钟周期选择为200MHz,根据Controller Options页的选项,该时钟经过PLL分频和倍频后的时钟分别作为用户侧时钟100MHz和DDR接口时钟100MHz。
这里有个参考时钟选项,如果Memory Options页PLL输入时钟频率选为200MHz,此处可以直接选择Use System Clock,从而简化接口。
以上是MIG IP核配置过程中较为重要的部分,实际上上述配置也可通过修改工程代码中参数来重定义。IP核配置完成,打开example design工程顶层文件,我们来重点关注下用户侧接口功能和时序。
这是本人写的注释,更具体清晰的说明还是要查看官方文档UG586.接下来看看写数据和读数据的接口时序图(时钟比例4:1,burst length = 8为例):
指令通道:
写数据:
从时序图可以看出,指令地址和数据使用两套时序,彼此相互独立。为了便于设计,直接将两套时序严格对齐(情况1)也可以正常工作。
读数据:
为什么说“时钟比例4:1,burst length = 8为例”?这一点特别关键。此时用户时钟周期是DDR接口时钟周期的4倍,也就是一个用户时钟信号上升沿对应8个DDR时钟边沿。burst length可以理解为MIG连续操作DDR地址的个数,故在4:1时钟比例下,一个用户时钟周期正好对8个地址进行了读/写操作,256bit数据分8次(32bit)写入DDR中。由此分析,在写数据时让app_wdf_end = app_wdf_wren即可,并且读/写操作时地址递增步长为8.
虽然MIG IP核提供了用户接口,但读写指令通道复用且需要实时关注两个rdy信号造成了时序操作上的不方便。为此我们需要对接口进一步封装,保证写操作时只关注:写使能user_wdata_en 写地址user_waddr 写数据user_wdata和写准备就绪信号user_wdata_rdy,读操作时只关注:读使能user_rdata_en 读地址user_raddr 读数据user_rdata 读数据有效user_rdata_vld和读操作准备就绪user_rdata_rdy。
利用扩展接口模块,将读通道和写通道接口分离,并分别例化一个FIFO缓存地址和数据。当读/写指令同时有效时,通过MIG侧的优先级轮换逻辑轮流读取其中一个FIFO,每次选一个FIFO读取直至FIFO为空再重新选择。其工程结构和核心代码如下:
读侧逻辑核心代码:
复制代码
1 //读侧--------------------------------------------------------------
2
3 always @(posedge clk or negedge rst_n )begin
4 if(rst_n==0) begin
5 rd_flag <= (0) ;
6 end
7 else if(rd_flag == 0 && mig_rdy && mig_wdf_rdy && !rdempty1 && (rdempty0 || (!rdempty0 && priority == 0)))begin
8 rd_flag <= (2'b01) ;//读取 写指令FIFO
9 end
10 else if(rd_flag == 0 && mig_rdy && !rdempty0 && (rdempty1 || (!rdempty1 && priority == 1)))begin
11 rd_flag <= (2'b10) ;//读取 读指令FIFO
12 end
13 else if((rd_flag == 2'b01 && rdempty1)www.wmyl11.com ||(rd_flag == 2'b10 && rdempty0))
14 rd_flag <= 0;
15 end
16
17 //同时非空时轮换优先级
18 always @(posedge clk or negedge rst_n )begin
19 if(rst_n==0) begin
20 priority <= (0) ;
21 end
22 else if(rd_flag == 0 && !rdempty0 && www.wmyl15.com !rdempty1)begin
23 priority <= (!priority) ;
24 end
25 end
复制代码
为了方便测试,设计样式生成模块与扩展接口模块用户侧连接,不断向一段地址写入固定数据序列并在一段时间后读回。
复制代码
1 `timescale 1ns / 1ps
2 /*
3 该模块功能:
4 周期性向一段地址执行读写操作 产生固定样式待写入数据用户测试目的
5 测试完毕后删除该模块,开发用户接口
6
7 具体为:
8 1 写从0开始之后的10个用户地址(80个DDR地址):0~9递增序列
9 2 等待20个时钟周期
10 3 读取写入的10个用户地址
11 4 等待20个时钟周期
12 5 重复上述步骤
13
14 说明:
15 1 每个步骤之间有一个时钟周期空闲
16 2 由于burst_len = 8 4:1时钟模式下一个用户时钟周期写入数据对应同样时间内8个DDR时钟边沿写入数据,
17 因此地址递增步长为8
18 */
19 module traffic_gen
20 #(parameter DATA_WIDTH = 32,
21 ADDR_WIDTH = 29)
22 (
23 input clk ,
24 input rst_n ,
25
26 output reg gen_wdata_en ,
27 output reg [ ADDR_WIDTH-1:0] gen_waddr ,
28 output reg [ DATA_WIDTH-1:0] gen_wdata ,
29 input gen_www.niucaiyule1.cn wdata_rdy ,//写指令和数据通道准备就绪
30
31 output reg gen_rdata_en ,
32 output reg [ ADDR_WIDTH-1:0] gen_raddr ,
33 input [ DATA_WIDTH-1:0] gen_rdata ,
34 input gen_rdata_vld ,
35 input gen_rdata_rdy www.dashidai1.com///读指令通道准备就绪
36 );
37
38
39 reg [ (8-1):0] cnt0 ;
40 wire add_cnt0 ;
41 wire end_cnt0 ;
42 reg [ (2-1):0] cnt1 ;
43 wire add_cnt1 ;
44 wire end_cnt1 ;
45
46 reg [ DATA_WIDTH-1:0] gen_rdata_r ;
47 reg gen_rdata_vld_r ;
48 reg com_flag ;
49
50 wire wri_state;
51 wire rd_state;
52 wire com_change_t;
53
54 //操作周期计数器,计数值为欲操作用户地址段长度+1(需要一个时钟周期空闲)
55 always @(posedge clk or negedge rst_n) begin
56 if (rst_n==0) begin
57 cnt0 <= 0;
58 end
59 else if(add_cnt0) begin
60 if(end_cnt0)
61 cnt0 <= 0;
62 else
63 cnt0 <= cnt0+1 ;
64 end
65 end
66 assign add_cnt0 = (com_flag == www.chushiyl.cn 0 && gen_wdata_rdy) || (com_flag == 1 && gen_rdata_rdy);
67 assign end_cnt0 = add_cnt0 && cnt0 == (30)-1 ;
68
69 //指令标志位 先是0--写 再是1--读
70 always @(posedge clk or negedge rst_n )begin
71 if(rst_n==0) begin
72 com_flag <= (0) ;
73 end
74 else if(com_change_t)begin
75 com_flag <= (!com_flag) ;
76 end
77 end
78
79 assign com_change_t = add_cnt0 && cnt0 == 10 - 1;
80
81 //写操作---------------------------------------------
82 always @(posedge clk or negedge rst_n )begin
83 if(rst_n==0) begin
84 gen_wdata_en <= (0) ;
85 end
86 else if(wri_state)begin
87 gen_wdata_en <= (1'b1) ;
88 end
89 else begin
90 gen_wdata_en <= (0) ;
91 end
92 end
93
94 assign wri_state = add_cnt0 && cnt0 <=www.caitianxia178.com 10-1 && com_flag == 0;
95 assign rd_state = add_cnt0 && cnt0 <= 10-1 && com_flag == 1;
96
97 always @(posedge clk or negedge rst_n )begin
98 if(rst_n==0) begin
99 gen_wdata <= (0) ;
100 end
101 else begin
102 gen_wdata <= (cnt0) ;
103 end
104 end
105
106 always@(posedge clk or negedge www.boshenyl.cn rst_n)begin
107 if(rst_n == 0)
108 gen_waddr <= 0;
109 else if(wri_state)
110 gen_waddr <= gen_waddr + 29'd8;
111 else
112 gen_waddr <= 0;
113 end
114 //读操作----------------------------------------------
115
116 always @(posedge clk or negedge rst_n )begin
117 if(rst_n==0) begin
118 gen_rdata_en <= (0) ;
119 end
120 else if(rd_state)begin
121 gen_rdata_en <= (1'b1) ;
122 end
123 else begin
124 gen_rdata_en <= (0) ;
125 end
126 end
127
128 always@(posedge clk or negedge rst_n)begin
129 if(rst_n == 0)
130 gen_raddr <= 0;
131 else if(rd_state)
132 gen_raddr <= gen_raddr + 29'd8;
133 else
134 gen_raddr <= 0;
135 end
136
137 always @(posedge clk or negedge rst_n )begin
138 if(rst_n==0) begin
139 gen_rdata_r <= (0) ;
140 end
141 else begin
142 gen_rdata_r <= (gen_rdata) ;
143 end
144 end
145
146 always @(posedge clk or negedge rst_n )begin
147 if(rst_n==0) begin
148 gen_rdata_vld_r <= (0) ;
149 end
150 else if(gen_rdata_vld)begin
151 gen_rdata_vld_r <= (1'b1) ;
152 end
153 else begin
154 gen_rdata_vld_r <= (0) ;
155 end
156 end
157
158 endmodule
复制代码
将traffic_gen和extend_interface模块例化在MIG的example design中,利用ILA抓取MIG IP核用户接口信号。
向地址8~80写入数据0~9,再从此段地址中读回数据,0~9被正确读出,MIG IP核控制DDR3读写测试完毕。