1.3.2.5. 输入输出数据选择模块¶
该模块是乒乓操作的核心模块,我们需要通过该模块对输入输出数据进行选择,从而达到乒乓操作的处理效果。模块框图如图 42‑5所示。
图 42‑5 输入输出数据选择模块框图
该模块我们需要产生控制两个双口RAM的读写相关信号,同时将两个RAM中读出的数据作为输入,通过简单的处理后对读出的数据进行输出。该模块的各个信号简介,如表格 42‑3所示。
表格 42‑3 数据选择模块输入输出信号描述
信号
位宽
类型
功能描述
clk_50m
1bit
input
ram写数据时钟,频率50MHz
clk_25m
1bit
input
ram读数据时钟,频率25MHz
rst_n
1bit
input
复位信号,低有效
ram1_rd_data
16bit
input
ram1读出数据
ram2_rd_data
16bit
input
ram2读出数据
data_en
1bit
input
输入数据使能信号
data_in
8bti
input
输入数据
ram1_wr_en
1bit
output
ram1写使能
ram1_wr_addr
7bit
output
ram1写地址
ram1_wr_data
8bit
output
ram1写数据
ram1_rd_en
1bit
output
ram1读使能
ram1_rd_addr
6bit
output
ram1读地址
data_otu
16bit
output
输出乒乓操作数据
ram2_wr_en
1bit
output
ram2写使能
ram2_wr_addr
7bit
output
ram2写地址
ram2_wr_data
8bit
output
ram2写数据
ram2_rd_en
1bit
output
ram2读使能
ram2_rd_addr
6bit
output
ram2读地址
该模块要实现的功能,我们通过一个简单的状态机去进行了解,如图 42‑6所示。
图 42‑6 数据选择状态跳转图
IDLE:初始状态,在不工作或复位时就让状态机置为初始状态。
WRAM1:写RAM1状态。该状态我们开始往RAM1中写入数据,此时由于RAM2中并没有写入数据,所以我们不用对RAM2进行读取。那什么时候跳转到这个状态呢?从前面的数据生成模块中我们可知,当输入数据使能为高时,数据有效开始传输,所以当数据使能为高时我们让状态跳转到写RAM1状态,在该状态下将第一个
数据包(8’d0~8’d99)写入RAM1之中。
WRAM2_RRAM1:写RAM2读RAM1状态,当第一包数据写入完毕之后,马上跳到该状态,将第二包数据写入到RAM2中的同时读出RAM1中的写入的第一包数据。当第二包数据写完之后,我们的第一包数据应该也是刚好读完的,此时我们跳转到下一状态。
WRAM1_RRAM2:写RAM1读RAM2状态。在该状态下我们开始向RAM1中写入第三包数据,此时第三包数据会把第一包数据覆盖,而我们的第一包数据已经读取出来了,并不会使数据丢失。在往RAM1中写入第三包数据的同时,我们读出RAM2中的第二包数据,当读写完成之后,跳回WRAM2_RRAM1状态开始
下一包的数据写入以及读取,如此循环我们就能无缝地将连续的输入数据读取出来了。
通过状态机的讲解,相信大家对大概的控制流程都已了解,下面我们通过绘制波形图去具体讲解各个信号的时序逻辑。
波形图绘制
首先我们先看看RAM的写入相关信号的时序波形图,如图 42‑7所示。
图 42‑7 输入输出数据选择模块波形图(一)
前面说到们输入数据是用的50MHz时钟,所以我们我们使用50MHz时钟进行对各输入相关信号进行控制。
如上图所示,当检测到数据使能信号(data_en)为高时,状态跳转到WRAM1状态,这里我们使用时钟的下降沿进行检测触发跳转,这是为什么呢?在前面章节对RAM的学习我们知道,无论是写入还是读取都是时钟的上升沿进行的,而如果我们使用时钟的上升沿产生使能、地址、数据的话、写入或读取时上升沿采到的就是数据
变化的那一刻,这样采到的信号可能就是不稳定的状态,从而导致数据出错,所以这里我们使用时钟的下降沿去产生这些信号的话,上升沿就能采到数据的稳定状态了。
要往ram里写入数据,我们需要产生写使能,写地址,写数据。
ram1_wr_en:ram1写使能,初始值为0。当状态机为写RAM1状态时,我们让ram1写使能为高,这里我们可以使用组合逻辑赋值。
ram1_wr_addr:ram1写地址,初使值为0。当ram1写使能为高时让写地址开始相加,一个时钟写一个数据,同样采用时钟的下降沿触发。当地址加到8’d99时说明100个数据已经写完,。写完之后地址归0,状态机跳到下一状态。
ram1_wr_data:ram1写数据。ram1和ram2中的写数据都是由数据生成模块传过来的,而上一模块数据是由时钟上升沿产生的,所以这里我们需先对传来的数据,使用下降沿先进行寄存,当写使能为1时,让写入的数据为寄存的数据即可。这里我们使用组合逻辑赋值,这样使能、地址、数据在同一时钟沿下才能相互
对应。
当状态机跳转到WRAM2_RRAM1状态时,我们需要往ram2中写入数据的同时读取ram1中的数据(读取时序下一段为大家讲解)。往ram2中写入数据时:使能、地址和数据的时序产生方法与ram1的使能、地址和数据的时序产生方法是一致的,这里我们不在过多讲解了。
ram2写完之后状态机跳到WRAM1_RRAM2状态,在该状态我们需对ram1写,ram2读,相关信号的时序与前面状态的产生方法一致。写完之后又跳回WRAM2_RRAM1状态,如此循环。
ram写使能相关信号的时序讲解完之后,下面我们看看他们的读相关信号的时序该如何产生,如图 42‑8所示。
图 42‑8 输入输出数据选择模块波形图(二)
同写相关信号一样,读相关信号也使用时钟下降沿去进行产生,这样读数据时能采到稳定的读地址。我们使用的读时钟是25MHz时钟,所以读相关信号我们也使用该时钟去产生。
如上图所示:状态机是在clk_50m时钟的下降沿处变化的,在WRAM2_RRAM1状态时我们需要读取ram1里的数据,我们就需要产生读使能和读地址。在该状态下我们让读使能为1,此时我们不能用组合逻辑去进行产生读使能,而需要使用clk_25m时钟下降沿触发去产生,这样我们读使能才能与读时钟对应。
ram1_rd_addr:在读ram1使能信号为高时让其一直加即可,因为我们设置的读取数据位宽是16bit,是输入数据位宽的两倍,即读出的一个数据为写入的两个数据。所以当其地址加到49时,表明读出了50个16bit数据,这说明写入的100个8bit数据已读完。这个时候我们让地址归0,等待下一次的读取
。
当状态为WRAM1_RRAM2时,需要读取ram2中的数据,使能和地址的产生方法与ram1一致。
读使能信号和地址产生了之后,读时钟的上升沿采到使能和地址信号后就会读出数据,每个数据为16bit,为两个写入数据。即读取的第一个数据为写入的前两个数据16’h0100,写入ram1的最后两个数据为十进制的98、99,转换为16进制就是62、63,所以读取的最后一个数据为16’h6362。同理ram2
读取的第一个数据为16’h6564,最后一个数据为16’hc7c6。
data_out:乒乓操作输出的数据。当读ram1使能为高时,输出读ram1的值;读ram2使能为高时,输出读ram2的值。因为读数据是在读时钟上升出产生的,我们使用读时钟下降沿将读出的数据给data_out输出,这样就能无缝地把写入的数据全部输出。
代码编写
参照绘制波形图,编写模块代码。模块参考代码,具体见代码清单 42‑2。
代码清单 42‑2 输入输出数据选择模块参考代码(ram_ctrl.v)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167module ram_ctrl
(
input wire clk_50m , //写ram时钟,50MHz
input wire clk_25m , //读ram时钟,25MHz
input wire rst_n , //复位信号,低有效
input wire [15:0] ram1_rd_data, //ram1读数据
input wire [15:0] ram2_rd_data, //ram2读数据
input wire data_en , //输入数据使能信号
input wire [7:0] data_in , //输入数据
output reg ram1_wr_en , //ram1写使能
output reg ram1_rd_en , //ram1读使能
output reg [6:0] ram1_wr_addr, //ram1写地址
output reg [5:0] ram1_rd_addr, //ram1读地址
output wire [7:0] ram1_wr_data, //ram1写数据
output reg ram2_wr_en , //ram2写使能
output reg ram2_rd_en , //ram2读使能
output reg [6:0] ram2_wr_addr, //ram2写地址
output reg [5:0] ram2_rd_addr, //ram2读地址
output wire [7:0] ram2_wr_data, //ram2写数据
output reg [15:0] data_out //输出乒乓操作数据
);
////
//\* Parameter and Internal Signal \//
////
//parameter define
parameter IDLE = 4'b0001, //初始状态
WRAM1 = 4'b0010, //写RAM1状态
WRAM2_RRAM1 = 4'b0100, //写RAM2读RAM1状态
WRAM1_RRAM2 = 4'b1000; //写RAM1读RAM2状态
//reg define
reg [3:0] state ; //状态机状态
reg [7:0] data_in_reg ; //数据寄存器
////
//\* Main Code \//
////
//使用组合逻辑赋值,这样使能和数据地址才能对应
assign ram1_wr_data = (ram1_wr_en == 1'b1) ? data_in_reg: 8'd0;
assign ram2_wr_data = (ram2_wr_en == 1'b1) ? data_in_reg: 8'd0;
//使用写数据时钟下降沿寄存数据,使数据写入存储器时上升沿能踩到稳定的数据
always@(negedge clk_50m or negedge rst_n)
if(rst_n == 1'b0)
data_in_reg <= 8'd0;
else
data_in_reg <= data_in;
//状态机状态跳转
always@(negedge clk_50m or negedge rst_n)
if(rst_n == 1'b0)
state <= IDLE;
else case(state)
IDLE://检测到数据使能信号为高时,跳转到下一状态将数据写到RAM1
if(data_en == 1'b1)
state <= WRAM1;
WRAM1://RAM1数据写完之后,跳转到写RAM2读RAM1状态
if(ram1_wr_addr == 7'd99)
state <= WRAM2_RRAM1;
WRAM2_RRAM1://RAM2数据写完之后,跳转到写RAM1读RAM2状态
if(ram2_wr_addr == 7'd99)
state <= WRAM1_RRAM2;
WRAM1_RRAM2://RAM1数据写完之后,跳转到写RAM2读RAM1状态
if(ram1_wr_addr == 7'd99)
state <= WRAM2_RRAM1;
default:
state <= IDLE;
endcase
//RAM1,RAM2写使能赋值
always@(*)
case(state)
IDLE:
begin
ram1_wr_en = 1'b0;
ram2_wr_en = 1'b0;
end
WRAM1:
begin
ram1_wr_en = 1'b1;
ram2_wr_en = 1'b0;
end
WRAM2_RRAM1:
begin
ram1_wr_en = 1'b0;
ram2_wr_en = 1'b1;
end
WRAM1_RRAM2:
begin
ram1_wr_en = 1'b1;
ram2_wr_en = 1'b0;
end
default:;
endcase
//RAM1读使能,使用读时钟赋值
always@(negedge clk_25m or negedge rst_n)
if(rst_n == 1'b0)
ram1_rd_en <= 1'b0;
else if(state == WRAM2_RRAM1)
ram1_rd_en <= 1'b1;
else
ram1_rd_en <= 1'b0;
//RAM2读使能,使用读时钟赋值
always@(negedge clk_25m or negedge rst_n)
if(rst_n == 1'b0)
ram2_rd_en <= 1'b0;
else if(state == WRAM1_RRAM2)
ram2_rd_en <= 1'b1;
else
ram2_rd_en <= 1'b0;
//RAM1写地址
always@(negedge clk_50m or negedge rst_n)
if(rst_n == 1'b0)
ram1_wr_addr <= 7'd0;
else if(ram1_wr_addr == 7'd99)
ram1_wr_addr <= 7'd0;
else if(ram1_wr_en == 1'b1)
ram1_wr_addr <= ram1_wr_addr + 1'b1;
//RAM2写地址
always@(negedge clk_50m or negedge rst_n)
if(rst_n == 1'b0)
ram2_wr_addr <= 7'b0;
else if(ram2_wr_addr == 7'd99)
ram2_wr_addr <= 7'b0;
else if(ram2_wr_en == 1'b1)
ram2_wr_addr <= ram2_wr_addr + 1'b1;
//RAM1读地址
always@(negedge clk_25m or negedge rst_n)
if(rst_n == 1'b0)
ram1_rd_addr <= 6'd0;
else if(ram1_rd_addr == 6'd49)
ram1_rd_addr <= 6'b0;
else if(ram1_rd_en == 1'b1)
ram1_rd_addr <= ram1_rd_addr + 1'b1;
//RAM2读地址
always@(negedge clk_25m or negedge rst_n)
if(rst_n == 1'b0)
ram2_rd_addr <= 6'd0;
else if(ram2_rd_addr == 6'd49)
ram2_rd_addr <= 6'b0;
else if(ram2_rd_en == 1'b1)
ram2_rd_addr <= ram2_rd_addr + 1'b1;
//将乒乓操作读出的数据选择输出
always@(negedge clk_25m or negedge rst_n)
if(rst_n == 1'b0)
data_out <= 16'd0;
else if(ram1_rd_en == 1'b1)
data_out <= ram1_rd_data;
else if(ram2_rd_en == 1'b1)
data_out <= ram2_rd_data;
else
data_out <= 16'd0;
endmodule
代码是根据绘制的波形图进行讲解的,在波形图讲解对各个信号都有了详细的讲解,这里就不再过多叙述了。本设计思路只做参考,并非唯一方法,读者也可利用所学知识,按照自己思路进行设计。