学习使用RAM IP核的时候遇到了问题,编写的代码与想象的不一致,具体表现为时序滞后一拍,下面是问题的解决思路。
设计波形图:
如图rw_cnt信号应该随着ram_en信号的拉高开始工作,然后跟随计数器变动
RTL部分代码如下:
module ram_rw(
input clk, //时钟输入
input rst_n, //系统复位信号
input [8:0] ram_rd_data, //RAM读来的数据
output reg ram_en, //RAM总使能信号,高电平有效
output reg ram_we, //RAM写使能信号,高电平有效
output reg [4:0] ram_addr, //RAM地址,读写公用
output reg [8:0] ram_wr_data //向RAM写的数据
);
reg [5:0] rw_cnt;
//对RAM_EN使能信号进行赋值,结束复位之后拉高
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
ram_en <= 1'b0;
else
ram_en <= 1'b1;
end
//实现模64计数器,以便实现以下三个输出信号:
//ram_we,
//ram_addr,
//ram_wr_data
always @(posedge clk or negedge rst_n) begin
if(!rst_n || !ram_en)
rw_cnt <= 6'b0;
else
rw_cnt <= rw_cnt+6'b1; //计数满后自动清零
end
//设置ram_we信号,在计数0-31激活写入,32-63激活读取
always @(posedge clk or negedge rst_n) begin
if(!rst_n || !ram_en)
ram_we <= 1'b0;
else
ram_we <= (rw_cnt <= 6'd31); //31以下是计数器的值,31以上是计数器的值减去32
end
//设置地址信号,与计数器有关
always @(posedge clk or negedge rst_n) begin
if(!rst_n || !ram_en)
ram_addr <= 8'b0;
else
ram_addr <= (rw_cnt <= 6'd31) ? {2'b0,rw_cnt} : {2'b0,rw_cnt-6'd32};
//31以下是计数器的值,31以上是计数器的值减去32
end
//设置写数据输出,与计数器有关
always @(posedge clk or negedge rst_n) begin
if(!rst_n || !ram_en || !ram_we)
ram_wr_data <= 8'b0;
else
ram_wr_data <= rw_cnt; //在可以写入的时候写入值与计数器值相等
end
endmodule
实际仿真结果:
可以看到ram_we信号滞后了一个周期才开始工作,观察RTL代码可以发现,我们使用always赋值语句块来实现的ram_we,所以在这里信号延后了
//设置ram_we信号,在计数0-31激活写入,32-63激活读取
always @(posedge clk or negedge rst_n) begin
if(!rst_n || !ram_en)
ram_we <= 1'b0;
else
ram_we <= (rw_cnt <= 6'd31); //31以下是1,31以上是0
end
也就是说,在时钟上升沿触发该always语句块时(第一条蓝色线),因为ram_en处于跳变状态,所以判断结果为跳变之前的值(重要),即ram_en为低电平,所以触发if语句,ram_we为低电平。
直到下个时钟上升沿(第二条蓝色线),ram_en已经稳定为低电平,所以这时候ram_we才开始变为高电平有效状态。如果需要解决这个延后问题,就需要把ram_en改为组合逻辑。
另外观察到VIVADO出现了高阻状态的线,这一般与没接好线有关,仔细检查后发现RTL代码有误
这里全部写错了应该是[7:0]表示8位
修改RTL代码如下:
//设置ram_we信号,在计数0-31激活写入,32-63激活读取
assign ram_we = (!rst_n || !ram_en) ? 1'b0 : (rw_cnt <= 6'd31);
重新运行仿真
可以观察到根据计数器产生的信号也都延后了计数器一拍,这说明我们不能这样基于一个计数器设计,需要每个信号重新计数,每个信号只延后rst_n和ram_en一个周期。
重新设计RTL代码,使其与计数器无关,并且修改数位问题:
module ram_rw(
input clk, //时钟输入
input rst_n, //系统复位信号
input [7:0] ram_rd_data, //RAM读来的数据
output reg ram_en, //RAM总使能信号,高电平有效
output ram_we, //RAM写使能信号,高电平有效
output reg [4:0] ram_addr, //RAM地址,读写公用
output reg [7:0] ram_wr_data //向RAM写的数据
);
reg [5:0] rw_cnt;
//对RAM_EN使能信号进行赋值,结束复位之后拉高
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
ram_en <= 1'b0;
else
ram_en <= 1'b1;
end
//实现模64计数器,以便实现以下三个输出信号:
//ram_we,
//ram_addr,
//ram_wr_data
always @(posedge clk or negedge rst_n) begin
if(!rst_n || !ram_en)
rw_cnt <= 6'b0;
else
rw_cnt <= rw_cnt+6'b1; //计数满后自动清零
end
//设置ram_we信号,在计数0-31激活写入,32-63激活读取,即时随ram_en信号变化
assign ram_we = (!rst_n || !ram_en) ? 1'b0 : (rw_cnt <= 6'd31);
//设置地址信号,相当于计数器,延后ram_en信号一拍
always @(posedge clk or negedge rst_n) begin
if(!rst_n || !ram_en)
ram_addr <= 8'd0;
else if(ram_addr == 8'd31)
ram_addr <= 8'd0;
else
ram_addr <= ram_addr + 8'd1;
end
//设置写数据输出,延后ram_we信号一拍
always @(posedge clk or negedge rst_n) begin
if(!rst_n || !ram_en || !ram_we)
ram_wr_data <= 8'd0;
else if(ram_wr_data == 8'd31)
ram_wr_data <= 8'd0;
else
ram_wr_data <= ram_wr_data + 8'd1;
end
endmodule
此时没有任何问题。
总结
1.使用always判断时钟上升沿来临时,如果改语句块内进行了判断,则判断信号的电平是该时刻之前的电平,这会导致赋值延后一个周期
2.这是时序逻辑的特性,如果想同步变化需要使用组合逻辑