verilog bug记录——正点原子spi_drive存在的问题
- 问题概述
- 代码修改—spi_drive.v
- 遗留问题
问题概述
因为项目需求,需要利用spi对flash进行擦除和写入操作,所使用的开发板是正电原子的达芬奇开发板,我事先往Flash里面存了两个bit,分别对应LED0和LED1的点亮,但是我使用了正点原子的spi_dirve进行全擦除操作之后发现了很奇怪的现象:
1、没擦完,因为明显的看到LED1的灯亮了,说明擦除操作或许有效,但是可能只是破坏了第一个bit,第二个bit没有做修改;
2、时序不对,通过ila抓波形可以发现,全擦除之后的轮询寄存器,竟然只查了一次就自动跳出轮询的状态了,但是全擦除怎么说也不至于这么快吧。
我的flash_contol部分的指令操作顺序如下:
always @(*)begin
case(cmd_cnt)
0 : spi_cmd = WEL_CMD ; //写使能
1 : spi_cmd = R_STA_REG_CMD ; //轮询
2 : spi_cmd = WEL_CMD ; //写使能
3 : spi_cmd = R_STA_REG_CMD ; //轮询
4 : spi_cmd = BE_CMD ; //全擦除
5 : spi_cmd = R_STA_REG_CMD ; //轮询
6 : spi_cmd = WEL_CMD ; //轮询
7 : spi_cmd = R_STA_REG_CMD ; //读数据
default:;
endcase
end
我只执行了0~5步,另外原代码中指令运行是一上电就会自动运行,但是我改成了只有我按键按下的时候才会执行。
出问题的时序如下:
从图中黄线部分可以看到,CS信号在spi_clk信号立即拉高,但是我们看数据手册可以发现
在spi_clk停止输出的时候,CS信号至少要间隔4ns才拉高,虽然说实际上的时钟信号并不是理想的马上拉高,而是有一段过渡时间,但是这个过渡时间并不好把控,所以稳妥起见还是应该至少打一拍,因为时钟频率是100MHz,那么延迟未0.01us,即10ns,是满足时序要求的;
另外还有问题,看下图:
红圈标注的位置,从图中可以看出,我第一次发送写使能命令后进行轮询,会发现状态寄存器的第6位WEL并没有拉高,而是第二次发送写使能命令的时候才拉高。
根据数据手册上的要求,发送完写使能命令后,WEL位是应该拉高为高电平的
最后一个问题如下图所示:
该时序图是我我发送完全擦除指令随即发送轮询寄存器指令(读状态寄存器指令),会发现蓝线部分表示WEL位,红线部分表示WIP位,此时WIP位应该是高电平,轮询寄存器应该继续轮询才对,直到WIP为0表示擦除操作已完成,但是原代码中却是直接拉高,这就不合理。
代码修改—spi_drive.v
//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: spi_drive
// Last modified Date: 2020/12/01 10:39:20
// Last Version: V1.0
// Descriptions: FLASH读写实验
//
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2020/12/01 10:39:20
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module spi_drive(
input clk_100m ,
input sys_rst_n ,
//user interface
input spi_start ,//spi开启使能。
input [7:0 ] spi_cmd ,//FLAH操作指令
input [23:0] spi_addr ,//FLASH地址
input [7:0 ] spi_data ,//FLASH写入的数据
input [3:0 ] cmd_cnt ,//指令计数器
output idel_flag_p ,//空闲状态标志的上升沿
output reg w_data_req ,//FLASH写数据请求
output reg error_flag ,//读出的数据错误标志
//spi interface
output reg spi_cs ,//SPI从机的片选信号,低电平有效。
output reg spi_clk ,//主从机之间的数据同步时钟。
output reg spi_mosi ,//数据引脚,主机输出,从机输入。
input spi_miso //数据引脚,主机输入,从机输出。
);
//define parameter
//状态机
parameter IDLE = 4'd0; //空闲状态
parameter WR_EN = 4'd1; //写使能状态
parameter S_ERA = 4'd2; //扇区擦除状态
parameter B_ERA = 4'd3; //全局擦除
parameter READ = 4'd4; //读状态
parameter WRITE = 4'd5; //写状态
parameter R_STA_REG = 4'd6; //读状态寄存器状态
//指令集
parameter WEL_CMD = 8'h06; //写使能指令
parameter SE_CMD = 8'hd8; //扇区擦除指令
parameter BE_CMD = 8'hc7; //全擦除指令
parameter READ_CMD = 8'h03; //读指令
parameter WRITE_CMD = 8'h02; //写指令
parameter R_STA_REG_CMD = 8'h05; //读状态寄存器指令
//wire define
wire idel_flag;
//reg define
reg idel_flag_d0 ;
reg idel_flag_d1 ;
reg spi_clk_d0 ;
reg [3:0] current_state ;
reg [3:0] next_state ;
reg [7:0 ] data_reg ; //数据寄存
reg [7:0 ] cmd_reg ; //指令寄存
reg [23:0] addr_reg ; //地址寄存器
reg [31:0] bit_cnt ; //bit计数器
reg clk_cnt ; //时钟计数器
reg delay_cnt ; //延迟计数器
reg [15:0] delay_state_cnt ; //状态延迟计数器
reg [7:0 ] rd_data_reg ; //读数据寄存器
reg stdone ; //状态完成标志
reg [7:0 ] data_check ; //数据校验
reg [2047:0] r_w_data ;
reg r_w_data_req ;
reg [2047:0] r_r_data ;//FLASH读出的数据
reg r_wip_flag ;
reg [7 :0 ] r_rd_status_reg;
reg r_wel ;
//*****************************************************
//** main code
//*****************************************************
assign idel_flag = (current_state == IDLE) ? 1:0; //空闲状态标志
assign idel_flag_p = idel_flag_d0 && (~idel_flag_d1); //空闲状态标志的上升沿
//idel_flga打拍取沿
always @(posedge clk_100m or negedge sys_rst_n )begin
if(!sys_rst_n)begin
idel_flag_d0 <= 1'b1;
idel_flag_d1 <= 1'b1;
end
else begin
idel_flag_d0 <= idel_flag;
idel_flag_d1 <= idel_flag_d0;
end
end
//写数据请求信号
always @(posedge clk_100m or negedge sys_rst_n )begin
if(!sys_rst_n)
w_data_req <= 1'b0;
else if(current_state == WRITE && (bit_cnt+2)%8 == 0 && bit_cnt >= 30 && clk_cnt == 0)
w_data_req <= 1'b1;
else
w_data_req <= 1'b0;
end
always @(posedge clk_100m or negedge sys_rst_n )begin
if(!sys_rst_n)
r_w_data_req <= 'd0;
else
r_w_data_req <= w_data_req;
end
always @(posedge clk_100m or negedge sys_rst_n )begin
if(!sys_rst_n)
r_w_data <= 'd0;
else if(r_w_data_req)
r_w_data <= {r_w_data[2039:0],spi_data};
end
//读出的数据移位寄存
always @(posedge clk_100m or negedge sys_rst_n )begin
if(!sys_rst_n)
rd_data_reg <= 8'd0;
else if(current_state == READ && bit_cnt >= 32 && bit_cnt <= 2080 && clk_cnt == 0)
rd_data_reg <= {rd_data_reg[6:0],spi_miso};
else
rd_data_reg <= rd_data_reg;
end
// //检查读出的数据是否正确
// always @(posedge clk_100m or negedge sys_rst_n )begin
// if(!sys_rst_n)
// data_check <= 8'd0;
// else if(current_state == READ && bit_cnt%8 == 0 && bit_cnt >= 40 && clk_cnt == 1)
// data_check <= data_check + 1'd1;
// else
// data_check <= data_check;
// end
//读出的数据
always @(posedge clk_100m or negedge sys_rst_n )begin
if(!sys_rst_n)
r_r_data <= 2048'd0;
else if(current_state == READ && bit_cnt%8 == 0 && bit_cnt >38 && clk_cnt==1)
r_r_data <= {r_r_data[2039:0],rd_data_reg};
else
r_r_data <= r_r_data;
end
//读出的数据错误标志
always @(posedge clk_100m or negedge sys_rst_n )begin
if(!sys_rst_n)
error_flag<=1'd0;
else if(current_state == READ && cmd_cnt == 6 && idel_flag_p)begin
if(r_r_data!=r_w_data)
error_flag <= 1'd1;
else
error_flag <= error_flag;
end
else
error_flag <= error_flag;
end
//数据寄存器
always @(posedge clk_100m or negedge sys_rst_n )begin
if(!sys_rst_n)
data_reg <= 8'd0;
else if((bit_cnt + 1'd1)%8 == 0 && bit_cnt > 30 && clk_cnt == 1)
data_reg <= spi_data;
else if(current_state == WRITE && clk_cnt == 1 && bit_cnt >= 32)
data_reg <= {data_reg[6:0],data_reg[7]};
else
data_reg <= data_reg;
end
//指令寄存器
always @(posedge clk_100m or negedge sys_rst_n )begin
if(!sys_rst_n)
cmd_reg <= 8'd0;
else if(spi_cs == 0 && delay_cnt == 0)
cmd_reg <= spi_cmd;
else if((clk_cnt == 1) && (current_state == WR_EN || current_state == S_ERA
|| current_state == B_ERA || current_state == READ || current_state == WRITE
|| current_state == R_STA_REG) && (bit_cnt < 8))
cmd_reg <= {cmd_reg[6:0],1'b1};
else
cmd_reg <= cmd_reg;
end
//地址寄存器
always @(posedge clk_100m or negedge sys_rst_n )begin
if(!sys_rst_n)
addr_reg <= 8'd0;
else if(spi_cs == 0 && delay_cnt == 0)
addr_reg <= spi_addr;
else if(clk_cnt==1 && (current_state == READ || current_state == WRITE) && bit_cnt >= 8 && bit_cnt < 32)
addr_reg <= {addr_reg[22:0],addr_reg[23]};
else
addr_reg <= addr_reg;
end
//时钟计数器
always @(posedge clk_100m or negedge sys_rst_n )begin
if(!sys_rst_n)
clk_cnt <= 1'd0;
else if(delay_cnt==1)
clk_cnt <= clk_cnt+1'd1;
else
clk_cnt <= 1'd0;
end
//延迟标志
always @(posedge clk_100m or negedge sys_rst_n )begin
if(!sys_rst_n)
delay_cnt <= 1'd0;
else if(spi_cs == 0)begin
if(delay_cnt < 1)
delay_cnt <= delay_cnt + 1'd1;
else
delay_cnt <= delay_cnt;
end
else
delay_cnt <= 1'd0;
end
//状态延迟计数器
always @(posedge clk_100m or negedge sys_rst_n )begin
if(!sys_rst_n)
delay_state_cnt <= 1'd0;
else if(spi_start)
delay_state_cnt <= 1'd0;
else if(spi_cs)begin
if(delay_state_cnt < 20)
delay_state_cnt <= delay_state_cnt + 1'd1;
else
delay_state_cnt <= delay_state_cnt;
end
else
delay_state_cnt <= 1'd0;
end
//bit计数器
always @(posedge clk_100m or negedge sys_rst_n )begin
if(!sys_rst_n)
bit_cnt <= 16'd0;
else if(delay_cnt == 1)begin
if(clk_cnt == 1'b1)
bit_cnt <= bit_cnt+1'd1;
else
bit_cnt <= bit_cnt;
end
else
bit_cnt <= 16'd0;
end
// RDSR状态寄存器寄存
always @(posedge clk_100m or negedge sys_rst_n )begin
if(!sys_rst_n)
r_rd_status_reg <= 8'd0;
else if(current_state == R_STA_REG && bit_cnt >= 8 && clk_cnt == 1)
r_rd_status_reg <= {r_rd_status_reg[6:0],spi_miso};
else
r_rd_status_reg <= r_rd_status_reg;
end
//三段式状态机
always @(posedge clk_100m or negedge sys_rst_n )begin
if(!sys_rst_n)
current_state <= IDLE;
else
current_state <= next_state;
end
always @(*)begin
case(current_state)
IDLE: begin
if(spi_start && spi_cmd == WEL_CMD)
next_state = WR_EN;
else if(spi_start && spi_cmd == BE_CMD)
next_state = B_ERA;
else if(spi_start && spi_cmd == SE_CMD)
next_state = S_ERA;
else if(spi_start && spi_cmd == READ_CMD)
next_state = READ;
else if(spi_start && spi_cmd == WRITE_CMD)
next_state = WRITE;
else if(spi_start && spi_cmd == R_STA_REG_CMD)
next_state = R_STA_REG;
else
next_state = IDLE;
end
WR_EN: begin
if(stdone && bit_cnt >= 8)
next_state = IDLE;
else
next_state = WR_EN;
end
S_ERA: begin
if(stdone)
next_state = IDLE;
else
next_state = S_ERA;
end
B_ERA: begin
if(stdone)
next_state = IDLE;
else
next_state = B_ERA;
end
READ: begin
if(stdone && bit_cnt >= 8)
next_state = IDLE;
else
next_state = READ;
end
WRITE: begin
if(stdone && bit_cnt >= 8)
next_state = IDLE;
else
next_state = WRITE;
end
R_STA_REG: begin
if(stdone)
next_state = IDLE;
else
next_state = R_STA_REG;
end
default: next_state = IDLE;
endcase
end
always @(posedge clk_100m or negedge sys_rst_n )begin
if(!sys_rst_n) begin
spi_cs <= 1'b1;
spi_clk <= 1'b0;
spi_clk_d0 <= 1'b0;
spi_mosi <= 1'b0;
stdone <= 1'b0;
end
else begin
case(current_state)
IDLE: begin
stdone <= 1'b0;
spi_cs <= 1'b1;
spi_clk <= 1'b0;
spi_mosi <= 1'b0;
r_wip_flag <= 1'b0;
spi_clk_d0 <= 'd0;
end
WR_EN: begin
stdone <= 1'b0;
if(delay_state_cnt == 10)
spi_cs <= 1'b0;
else if(delay_cnt == 1 && bit_cnt < 8) begin
spi_clk_d0 <= ~spi_clk_d0;
spi_clk <= spi_clk_d0;
spi_mosi <= cmd_reg[7];
end
else if(bit_cnt == 8 && clk_cnt == 0)begin
stdone <= 1'b1;
spi_clk <= 1'b0;
spi_mosi <= 1'b0;
end
else if(bit_cnt == 8 && clk_cnt == 1)begin
spi_cs <= 1'b1;
end
end
B_ERA: begin
stdone <= 1'b0;
if(delay_state_cnt == 10)
spi_cs <= 1'b0;
else if(delay_cnt == 1 && bit_cnt < 8) begin
spi_clk_d0 <= ~spi_clk_d0;
spi_clk <= spi_clk_d0;
spi_mosi <= cmd_reg[7];
end
else if(bit_cnt == 8 && clk_cnt == 0)begin
stdone <= 1'b1;
spi_clk <= 1'b0;
spi_mosi <= 1'b0;
end
else if(bit_cnt == 8 && clk_cnt == 1)begin
spi_cs <= 1'b1;
end
end
S_ERA: begin
stdone <= 1'b0;
if(delay_state_cnt == 10)
spi_cs <= 1'b0;
else if(delay_cnt == 1 && bit_cnt < 8) begin
spi_clk_d0 <= ~spi_clk_d0;
spi_clk <= spi_clk_d0;
spi_mosi <= cmd_reg[7];
end
else if(bit_cnt >= 8&& bit_cnt < 32 && spi_cs == 0)begin
spi_cs <= 1'b0;
spi_clk_d0 <= ~spi_clk_d0;
spi_clk <= spi_clk_d0;
spi_mosi <= addr_reg[23];
end
else if(bit_cnt == 32 && clk_cnt == 0) begin
spi_cs <= 1'b1;
spi_clk <= 1'b0;
spi_mosi <= 1'b0;
stdone <= 1'b1;
end
end
READ: begin
stdone <= 1'b0;
if(delay_state_cnt == 10)
spi_cs <= 1'b0;
else if(delay_cnt == 1 && bit_cnt < 8) begin
spi_clk_d0 <= ~spi_clk_d0;
spi_clk <= spi_clk_d0;
spi_mosi <= cmd_reg[7];
end
else if(bit_cnt >= 8 && bit_cnt < 32 && spi_cs == 0)begin
spi_clk_d0 <= ~spi_clk_d0;
spi_clk <= spi_clk_d0;
spi_mosi <= addr_reg[23];
end
else if(bit_cnt >= 32 && bit_cnt < 2080)begin
spi_clk_d0 <= ~spi_clk_d0;
spi_clk <= spi_clk_d0;
spi_mosi <= 1'b0;
end
else if(bit_cnt == 2080 && clk_cnt == 0) begin
spi_clk <= 1'b0;
spi_mosi <= 1'b0;
stdone <= 1'b1;
end
else if(bit_cnt == 2080 && clk_cnt == 1) begin
spi_cs<=1'b1;
end
end
WRITE: begin
stdone<=1'b0;
if(delay_state_cnt == 10)
spi_cs <= 1'b0;
else if(delay_cnt == 1 && bit_cnt < 8) begin
spi_clk_d0 <= ~spi_clk_d0;
spi_clk <= spi_clk_d0;
spi_mosi <= cmd_reg[7];
end
else if(bit_cnt >= 8 && bit_cnt < 32 && spi_cs == 0)begin
spi_clk_d0 <= ~spi_clk_d0;
spi_clk <= spi_clk_d0;
spi_mosi <= addr_reg[23];
end
else if(bit_cnt >= 32 && bit_cnt < 2080)begin
spi_clk_d0 <= ~spi_clk_d0;
spi_clk <= spi_clk_d0;
spi_mosi <= data_reg[7];
end
else if(bit_cnt == 2080 && clk_cnt == 0) begin
spi_clk <= 1'b0;
spi_mosi <= 1'b0;
stdone <= 1'b1;
end
else if(bit_cnt == 2080 && clk_cnt == 1) begin
spi_cs <= 1'b1;
end
end
R_STA_REG:begin
stdone <= 1'b0;
if(delay_state_cnt == 10)
spi_cs <= 1'b0;
else if(delay_cnt == 1 && bit_cnt < 8)begin
spi_clk_d0 <= ~spi_clk_d0;
spi_clk <= spi_clk_d0;
spi_mosi <= cmd_reg[7];
end
else if(bit_cnt == 8)begin
spi_clk_d0 <= ~spi_clk_d0;
spi_clk <= spi_clk_d0;
spi_mosi <= 1'b0;
end
else if(~spi_miso && bit_cnt % 8==0 && bit_cnt > 8 && clk_cnt == 0)begin
r_wip_flag <= 1'b1;
spi_clk_d0 <= ~spi_clk_d0;
spi_clk <= spi_clk_d0;
end
else if(r_wip_flag && ~spi_miso && bit_cnt % 8==0 && bit_cnt > 8 && clk_cnt == 1)begin
spi_clk <= 1'b0;
spi_cs <= 1'b1;
stdone <= 1'b1;
end
else if(~spi_cs && delay_cnt == 1)begin
spi_clk_d0 <= ~spi_clk_d0;
spi_clk <= spi_clk_d0;
end
end
default: begin
stdone <= 1'b0;
spi_cs <= 1'b1;
spi_clk <= 1'b0;
spi_clk_d0 <= 1'b0;
spi_mosi <= 1'b0;
end
endcase
end
end
ila_spi u_ila_spi (
.clk(clk_100m), // input wire clk
.probe0(spi_start), // input wire [0:0] probe0
.probe1(spi_cmd ), // input wire [7:0] probe1
.probe2(spi_addr ), // input wire [23:0] probe2
.probe3(spi_data ), // input wire [7:0] probe3
.probe4(cmd_cnt ), // input wire [3:0] probe4
.probe5(idel_flag_p), // input wire [0:0] probe5
.probe6(w_data_req ), // input wire [0:0] probe6
.probe7(error_flag ), // input wire [0:0] probe7
.probe8(spi_cs ), // input wire [0:0] probe8
.probe9(spi_clk ), // input wire [0:0] probe9
.probe10(spi_mosi), // input wire [0:0] probe10
.probe11(spi_miso), // input wire [0:0] probe11
.probe12(idel_flag), // input wire [0:0] probe12
.probe13(idel_flag_d0), // input wire [0:0] probe13
.probe14(idel_flag_d1), // input wire [0:0] probe14
.probe15(spi_clk_d0 ), // input wire [0:0] probe15
.probe16(current_state ), // input wire [3:0] probe16
.probe17(next_state ), // input wire [3:0] probe17
.probe18(data_reg ), // input wire [7:0] probe18
.probe19(cmd_reg ), // input wire [7:0] probe19
.probe20(addr_reg ), // input wire [23:0] probe20
.probe21(bit_cnt ), // input wire [31:0] probe21
.probe22(clk_cnt ), // input wire [0:0] probe22
.probe23(delay_cnt ), // input wire [0:0] probe23
.probe24(delay_state_cnt), // input wire [15:0] probe24
.probe25(rd_data_reg ), // input wire [7:0] probe25
.probe26(stdone ), // input wire [0:0] probe26
.probe27(data_check ), // input wire [7:0] probe27
.probe28(r_w_data_req), // input wire [0:0] probe28
.probe29(r_wip_flag)
);
endmodule
修改后的代码核心在于判断轮询寄存器那里,以及spi_clk_d0每次在idle状态的时候都要清零,清零这个步骤是在为了确保每次新的指令来时,时钟状态都能从0开始(由SPI的驱动模式决定);
R_STA_REG:begin
stdone <= 1'b0;
if(delay_state_cnt == 10)
spi_cs <= 1'b0;
else if(delay_cnt == 1 && bit_cnt < 8)begin
spi_clk_d0 <= ~spi_clk_d0;
spi_clk <= spi_clk_d0;
spi_mosi <= cmd_reg[7];
end
else if(bit_cnt == 8)begin
spi_clk_d0 <= ~spi_clk_d0;
spi_clk <= spi_clk_d0;
spi_mosi <= 1'b0;
end
else if(~spi_miso && bit_cnt % 8==0 && bit_cnt > 8 && clk_cnt == 0)begin
r_wip_flag <= 1'b1;
spi_clk_d0 <= ~spi_clk_d0;
spi_clk <= spi_clk_d0;
end
else if(r_wip_flag && ~spi_miso && bit_cnt % 8==0 && bit_cnt > 8 && clk_cnt == 1)begin
spi_clk <= 1'b0;
spi_cs <= 1'b1;
stdone <= 1'b1;
end
else if(~spi_cs && delay_cnt == 1)begin
spi_clk_d0 <= ~spi_clk_d0;
spi_clk <= spi_clk_d0;
end
end
主要修改的就是这里,思路是:
我会首先判断WIP位是否为0,如果为0,则把r_wip_flag标志位拉高,等到下一次轮询状态寄存器的时候就可以跳出当前轮询寄存器的状态。
修改后的时序如下:
可以看到擦除命令执行完之后,还需要等待一段时间后才会完成擦除,擦除后的时序为:
即回到初始状态。
遗留问题
其实从上面波形中可以看到,仍然是第二次发送写指令的时候,WEL位才会拉高,目前猜测是flash本身的问题,之后的思路可以改成直到WEL位拉高后才执行之后的擦除或者写命令,否则就会一直发送写使能指令,直到WEL拉高。
目前可以暂时改成发送两次写使能