本文针对verilog HDL语言编写不同通信协议时,需要对不同命令状态机进行代码实现的方法进行了浅薄的探讨。作者水平有限,还请大家指正。
文中利用SPI协议对flash发送全擦除指令为例子,分别探讨以下两种不同编写方法的优劣及具体实现方式:1、利用always语句块方法得到状态跳变条件或时间等待标志进行协议端口输出的写法(下文称 “逻辑法”);2、使用cnt计数器,在驱动时钟频率至少是spi_clk时钟频率两倍以上的前提下,对时序的不同时刻行为进行具体相应描述的编写方法(下文称 “时序法”)。
两者比较的结论:
逻辑法:优点-后续工程中定制化修改更加方便,协议通信描述逻辑更加清晰具体,更能够抽象化规律化描述协议通信时的行为。缺点-编写不同协议行为之间的跳变限制逻辑设计较为复杂,代码仿真调节次数较多,需要反复考虑不同寄存器之间的延迟问题以提前空出延时余量,满足时序高效紧凑需求。(特点:小块时序逻辑更多,布线更简单,面积更小,速度稍慢)
时序法:优点-编写新思路简单清晰,开发速度快,可以简单有效地定位到bug存在位置,代码执行效率高。缺点-后期定制化其他功能修改的地方多,不容易看懂时序之间跳变的规律,不能够很客观的描述协议行为序列变化之间的关系。(特点:程序块较少,但一个case里计数器递进判断条件较多,布线由于比较集中,因此比较复杂,面积较大,速度更快)
使用建议:逻辑法更适用于已知固定长度,固定格式,一小段命令一小段命令发送命令或者接收状态的协议。对于非定常连续触发的协议或者一条命令内通信线时序变化非常复杂的协议,则推荐使用时序法来进行编写。 但是在实际工程运用中,其实它们并没有很明确的分界线,一般都是配合使用,博主的菜鸡经验是,连续信号耦合强且该信号持续期间有状态跳变的用逻辑法,连续信号只存在一个状态内且多线信号变化较为复杂的用时序法。
本文利用spi协议与flash芯片进行通信,向它发送全部扇区擦除指令。该指令由两个部分组成,一个是写使能部分WE,一个是全擦除命令BE部分。时序图如下。
本文将使用硬件表述语言,分别使用两种方法来描述上述时序。其中WE写使能(8'h06)是用时序法实现的,BE全擦除(8'hc7)是用逻辑法实现的。
时序
由上图可以看到,当spi_clk为上升沿时从机就会通过mosi对数据进行采样,因此要在上升沿之间的低电平时期就要把需要发出的命令数据放在mosi线上了(这是由于组合逻辑的非阻塞赋值延时一个时钟周期决定的)。
注意: 此时spi_cmd_reg的命令8'n06是不变的,这是由于下面代码中命令直接寻址发送的编写方法决定的。
源码
时序法通过case语句在每个时刻对spi协议的各个数据线行为跳变进行直接操控,使得这种方法编写起来简单有效。代码执行效率高,不需要在不同数据线之间设计相互承接的变化触发条件。但是这个方法对于人的阅读来说不够客观。
w_en_100ns : begin
case(cnt_son_stage)
7'd0 : begin
spi_cs <= 1'b0;
spi_clk <= 1'b0;
spi_mosi <= spi_cmd_reg[7];
end
7'd1 : spi_clk <= 1'b1;
7'd2 : begin
spi_clk <= 1'b0;
spi_mosi <= spi_cmd_reg[6];
end
7'd3 : spi_clk <= 1'b1;
7'd4 : begin
spi_clk <= 1'b0;
spi_mosi <= spi_cmd_reg[5];
end
7'd5 : spi_clk <= 1'b1;
7'd6 : begin
spi_clk <= 1'b0;
spi_mosi <= spi_cmd_reg[4];
end
7'd7 : spi_clk <= 1'b1;
7'd8 : begin
spi_clk <= 1'b0;
spi_mosi <= spi_cmd_reg[3];
end
7'd9 : spi_clk <= 1'b1;
7'd10 : begin
spi_clk <= 1'b0;
spi_mosi <= spi_cmd_reg[2];
end
7'd11: spi_clk <= 1'b1;
7'd12: begin
spi_clk <= 1'b0;
spi_mosi <= spi_cmd_reg[1];
end
7'd13: spi_clk <= 1'b1;
7'd14: begin
spi_clk <= 1'b0;
spi_mosi <= spi_cmd_reg[0];
end
7'd15: spi_clk <= 1'b1;
7'd16: begin
spi_clk <= 1'b0;
spi_mosi <= spi_cmd_reg[0];
end
7'd17: begin
spi_cs <= 1'b1;
spi_mosi <= 1'b0;
end
7'd20: begin
cnt_son_stage <= 7'd127;
sondone <= 1'b1;
end
default: ;
endcase
end
时序
由上图可以看到,当spi_clk为上升沿时从机就会通过mosi对数据进行采样,因此要在上升沿之间的低电平时期就要把需要发出的命令数据放在mosi线上了(这是由于组合逻辑的非阻塞赋值延时一个时钟周期决定的)。
注意: 此时spi_cmd_reg的命令8'nca是改变的,这是由于下面代码中命令是通过高位移位循环地址发送的编写方法决定的。
源码
逻辑法通过少数的if条件判断语句对时序的行为进行间接触发操控,使得这种方法编写起来不仅需要分析和编写不同时序段协议应该干什么,还需要在每个时段下分析此时不同信号线之间的触发关系。代码描述信号变化规律比较客观易懂,有利于后期修改与人员阅读。
cmd_cyc : begin
if(cnt_son_stage == 7'd20) begin // 完成
sondone <= 1'b1;
cnt_son_stage <= 7'd127;
end
else if(cnt_son_stage == 7'd1) begin
spi_cs <= 1'b0;
spi_clk <= 1'b0;
spi_mosi <= spi_cmd_reg[7];
spi_cmd_reg <= {spi_cmd_reg[6:0],1'b0};
end
else if(cnt_son_stage > 7'd1 && cnt_son_stage < 7'd18) begin
spi_clk <= ~spi_clk;
if(spi_clk) begin
spi_mosi <= spi_cmd_reg[7];
spi_cmd_reg <= {spi_cmd_reg[6:0],1'b0};
end
else begin
spi_mosi <= spi_mosi ;
spi_cmd_reg <= spi_cmd_reg;
end
end
else begin
spi_cs <= 1'b1 ;
spi_cmd_reg <= spi_cmd_reg;
spi_mosi <= 1'b0;
spi_clk <= 1'b0;
end
end
`timescale 1ns/1ns
`define WEL_CMD 8'h06 // 写使能命令
`define C_ERA_CMD 8'hc7 // 全部擦除命令
module tb_test();
reg sys_clk ; //模拟时钟信号
reg sys_rst_n ; //模拟复位信号
reg spi_start ;//spi开启使能。
reg spi_cs ;//SPI从机的片选信号,低电平有效。
reg spi_clk ;//主从机之间的数据同步时钟。
reg spi_mosi ;//数据引脚,主机输出,从机输入。
reg [ 7:0] spi_cmd ;
reg [ 2:0] cnt_stage ;
reg [ 6:0] cnt_son_stage ;
reg [ 7:0] spi_cmd_reg ;//FLAH操作指令
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
spi_start <= 1'b0;
#100
sys_rst_n <= 1'b1;
#300
spi_start <= 1'b1;
//spi_addr_reg <= ;//FLASH地址
#(20*47)
spi_start <= 1'b0;
#300
spi_start <= 1'b1;
//spi_addr_reg <= ;//FLASH地址
#(20*47)
spi_start <= 1'b0;
#600
$stop();
end
always # 10 sys_clk <= ~sys_clk; // 50M hz
reg clk_25M; // 读时钟最大20MHZ,这里将在状态机里降频到12.5Mhz
wire clk_drive;
reg spi_start_reg;
// 时钟
always @(posedge sys_clk or negedge sys_rst_n ) begin
if(!sys_rst_n)
clk_25M <= 1'b0;
else
clk_25M <= ~clk_25M;
end
assign clk_drive = clk_25M;
always@(posedge clk_drive or negedge sys_rst_n) begin
if(!sys_rst_n) begin
spi_start_reg <= 1'b0;
end
else
spi_start_reg <= spi_start;
end
always@(posedge clk_drive or negedge sys_rst_n) begin
if(!sys_rst_n) begin
spi_cmd <= 8'b0;
end
else if(spi_start && !spi_start_reg)
if( spi_cmd == 8'b0)
spi_cmd <= 8'hc7;
//else if ( spi_cmd == 8'h06)
// spi_cmd <= 8'hc7;
else
spi_cmd <= spi_cmd;
end
// 状态机状态
localparam IDLE = 7'b000_0001;//空闲状态
localparam WEL = 7'b000_0010;//写使能状态
localparam C_ERA = 7'b000_1000;//全局擦除
// 命令输出状态
localparam idle_stage = 8'b0000_0001,
w_en_100ns = 8'b0000_0010,
cmd_cyc = 8'b0000_0100,
adds_cyc = 8'b0000_1000,
end_6ns = 8'b0001_0000,
w_n_bit = 8'b0010_0000,
r_n_bit = 8'b0100_0000,
busy_8b = 8'b1000_0000;
reg [7:0] cur_stage ; //当前阶段
reg stdone ; //命令完成标志
reg sondone ; //子命令阶段完成标志
reg [6:0] cur_state ; //状态机当前状态
reg [6:0] next_state; //状态机下一状态
//(三段式状态机)同步时序描述状态转移
always @(posedge clk_drive or negedge sys_rst_n) begin
if(!sys_rst_n)
cur_state <= IDLE;
else
cur_state <= next_state;
end
always@(*) begin
next_state = IDLE;
case(cur_state)
IDLE : begin
if(spi_start && spi_cmd == `WEL_CMD)
next_state = WEL;
else if(spi_start && spi_cmd == `S_ERA_CMD)
next_state = S_ERA;
else
next_state=IDLE;
end
WEL: begin
if(stdone)
next_state = IDLE;
else
next_state = WEL;
end
C_ERA: begin
if(stdone)
next_state = IDLE;
else
next_state = C_ERA;
end
default: next_state=IDLE;
endcase
end
always@(posedge clk_drive or negedge sys_rst_n) begin
if(!sys_rst_n) begin
spi_cs <= 1'b1 ;
spi_clk <= 1'b0 ;
spi_mosi <= 1'b0 ;
spi_cmd_reg <= 8'b0 ;
cur_stage <= idle_stage;
cnt_stage <= 3'b0;
cnt_son_stage <= 7'b0;
sondone <= 1'b0;
stdone <= 1'b0;
end
else begin
cur_stage <= idle_stage;
stdone <= 1'b0;
if(sondone) begin
cnt_stage <= cnt_stage + 1'b1;
sondone <= 1'b0;
end
else begin
cnt_stage <= cnt_stage;
sondone <= sondone;
end
case(cur_state)
IDLE : begin
cur_stage <= idle_stage;
cnt_stage <= 3'b0;
spi_cs <= 1'b1;
end
WEL: begin
case(cnt_stage) // mosi
3'd0 : begin
cur_stage <= w_en_100ns;
if(cnt_son_stage == 7'd1)
spi_cmd_reg <= `WEL_CMD ;
else
spi_cmd_reg <= spi_cmd_reg;
end
3'd1 : begin
stdone <= 1'b1;
cnt_stage <= 3'd2;
end
default: ;
endcase
end
C_ERA : begin
case(cnt_stage) // mosi
3'd0 : begin
cur_stage <= w_en_100ns;
if(spi_cs)
spi_cmd_reg <= `WEL_CMD ;
else
spi_cmd_reg <= spi_cmd_reg;
end
3'd1 : begin
cur_stage <= cmd_cyc;
if(spi_cs)
spi_cmd_reg <= `C_ERA_CMD ;
else
spi_cmd_reg <= spi_cmd_reg;
end
3'd2 : cur_stage <= end_6ns;
3'd3 : begin
stdone <= 1'b1;
cnt_stage <= 3'd4;
end
default: ;
endcase
end
default: ;
endcase
if(spi_start || cnt_son_stage != 7'd127)
cnt_son_stage <= cnt_son_stage + 1'b1;
else
cnt_son_stage <= cnt_son_stage;
case(cur_stage)
idle_stage : begin
spi_clk <= 1'b0;
spi_mosi <= 1'b0;
cnt_son_stage <= 7'b0;
end
w_en_100ns : begin
case(cnt_son_stage)
7'd0 : begin
spi_cs <= 1'b0;
spi_clk <= 1'b0;
spi_mosi <= spi_cmd_reg[7];
end
7'd1 : spi_clk <= 1'b1;
7'd2 : begin
spi_clk <= 1'b0;
spi_mosi <= spi_cmd_reg[6];
end
7'd3 : spi_clk <= 1'b1;
7'd4 : begin
spi_clk <= 1'b0;
spi_mosi <= spi_cmd_reg[5];
end
7'd5 : spi_clk <= 1'b1;
7'd6 : begin
spi_clk <= 1'b0;
spi_mosi <= spi_cmd_reg[4];
end
7'd7 : spi_clk <= 1'b1;
7'd8 : begin
spi_clk <= 1'b0;
spi_mosi <= spi_cmd_reg[3];
end
7'd9 : spi_clk <= 1'b1;
7'd10 : begin
spi_clk <= 1'b0;
spi_mosi <= spi_cmd_reg[2];
end
7'd11: spi_clk <= 1'b1;
7'd12: begin
spi_clk <= 1'b0;
spi_mosi <= spi_cmd_reg[1];
end
7'd13: spi_clk <= 1'b1;
7'd14: begin
spi_clk <= 1'b0;
spi_mosi <= spi_cmd_reg[0];
end
7'd15: spi_clk <= 1'b1;
7'd16: begin
spi_clk <= 1'b0;
spi_mosi <= spi_cmd_reg[0];
end
7'd17: begin
spi_cs <= 1'b1;
spi_mosi <= 1'b0;
end
7'd20: begin
cnt_son_stage <= 7'd127;
sondone <= 1'b1;
end
default: ;
endcase
end
cmd_cyc : begin
if(cnt_son_stage == 7'd18) begin // 完成
sondone <= 1'b1;
cnt_son_stage <= 7'd127;
end
else if(cnt_son_stage == 7'd1) begin
spi_cs <= 1'b0;
spi_clk <= 1'b0;
spi_mosi <= spi_cmd_reg[7];
spi_cmd_reg <= {spi_cmd_reg[6:0],1'b0};
end
else if(cnt_son_stage > 7'd1 && cnt_son_stage < 7'd18) begin
spi_clk <= ~spi_clk;
if(spi_clk) begin
spi_mosi <= spi_cmd_reg[7];
spi_cmd_reg <= {spi_cmd_reg[6:0],1'b0};
end
else begin
spi_mosi <= spi_mosi ;
spi_cmd_reg <= spi_cmd_reg;
end
end
else begin
spi_cs <= 1'b1 ;
spi_cmd_reg <= spi_cmd_reg;
spi_mosi <= 1'b0;
spi_clk <= 1'b0;
end
end
end_6ns : begin
if(cnt_son_stage == 7'd6) begin // 完成
sondone <= 1'b1;
cnt_son_stage <= 7'd127;
end
else begin
spi_cs <= 1'b1 ;
spi_cmd_reg <= 8'b0;
spi_mosi <= 1'b0;
spi_clk <= 1'b0;
end
end
default: ;
endcase
end
end
endmodule
在我平时编写通信协议与器件进行通讯的过程中,会遇到这么一个问题。当项目使用到的命令数量较少时,我们可以直接对每条命令利用时序法进行描述,并设计状态机跳转来实现。可是当项目使用的命令数很多时,如果还是像上面的方法那么处理,将会产生很多的代码冗余和寄存器资源浪费,那么这种情况下用什么方法编写更合适呢?
相信读者心里各自有自己答案,希望大家在评论区多多指点。作者学习ic不久,提出一下自己不成熟的想法。如上面的test bench源码所展示的结构,我尝试将不同命令差分成更小的共性小块,然后实现这些小块,不同的命令只要按照自己的需要使用这些小块进行顺序组合就可以实现了。这样是否能够做到多命令兼容呢?理论上应该可行。
如上图的全擦除命令与扇区擦除命令,其实他们可以拆成几个不同的通用小块:1、写使能和它后面的100ns延时。2、5ns开头和8个spi_clk周期的命令发送阶段。3、器件操作地址的24位宽地址输出阶段。4、最后5ns的数据结尾完成阶段。使用上述四个小块就可以组合成两条不同的操作指令了。如有不当之处,还请各位多多指正。
针对上述想法,本人进行了实际实验。验证了自己的一部分猜想,同时也获得了一些思考。
直接上结论再上例子!
分析:上述的分小快命令组成不同大命令的想法是可行的,它可以让人看或者写代码时鲁棒性显著增加,但是这也付出了一定的代价,即状态跳转次数增加导致的功耗增加(状态数量增加)、需要设计而外的组合电路来实现状态依据不同的条件实时跳变,虽然增加了面积可是要比每条命令都具体实现要小很多。
总结:该框架可以缩短开发时间,降低代码维护成本,减低占用面积,但是会增加功耗以及布线难度。又由于芯片设计中的硬件功耗及速度成本是远大于人力成本与面积成本的,因此该框架在实际工程使用中的实际意义不强。
新猜想:那么有没有既代码可读维护性高,又功耗小且高速的协议命令状态机设计方法呢?其实我们可以引入引入规范化的代码编写格式和编写更全面的统一函数库来管理来实现这一目标。甚至我们可以设置一些脚本文件,利用脚本文件兼顾代码的可读和可维护新,用脚本文件自动拼接含有小块时序逻辑结构的真正硬件描述代码来兼顾功耗与效率,最后进行小范围人为调整即可。
预备知识:在上述执行spi的flash全擦除指令C_ERA_CMD即图46-16时,其实还需要一个命令R_STA_REG_CMD来等待从机miso线返回flash已经擦除完毕的,以便于主机知道何时开始发送下一条命令。一次我们下面将基于一次完整的flash全擦除过程实现例子,即写使能WREN -》扇区全擦除RE -》等待查询寄存器选中05h -》等待miso线上的每8位的最低位为b[0]=0时,数据全擦除过程结束。
1.
2.
时序图:
实例代码
`timescale 1ns/1ns
`define WEL_CMD 8'h06 // 写使能命令
`define S_ERA_CMD 8'h20 // 扇区擦除命令
`define C_ERA_CMD 8'hc7 // 全部擦除命令
`define READ_CMD 8'h03 // 读扇区命令
`define WRITE_CMD 8'h02 // 写扇区命令
`define R_STA_REG_CMD 8'h05 // 等待写忙信号结束空闲命令
module tb_test();
reg sys_clk ; //模拟时钟信号
reg sys_rst_n ; //模拟复位信号
reg spi_start ; //spi开启使能。
reg spi_cs ; //SPI从机的片选信号,低电平有效。
reg spi_clk ; //主从机之间的数据同步时钟。
reg spi_mosi ; //数据引脚,主机输出,从机输入。
reg spi_miso ;
reg [ 7:0] spi_cmd ;
reg [ 2:0] cnt_stage ;
reg [ 6:0] cnt_son_stage ;
reg [ 7:0] spi_cmd_reg ;//FLAH操作指令
reg stdone ; //命令完成标志
reg son_done ; //子命令阶段完成标志
initial begin
sys_clk = 1'b1;
sys_rst_n = 1'b0;
#100
sys_rst_n = 1'b1;
#6500
$stop();
end
// 模拟命令订阅行为 spi_start的实现
reg [4:0] cnt_spi_start;
always @(posedge sys_clk or negedge sys_rst_n ) begin
if(!sys_rst_n) begin
spi_start <= 1'b0;
cnt_spi_start <= 4'b0;
end
else if(stdone) begin
spi_start <= 1'b0;
cnt_spi_start <= 4'd10;
end
else if(cnt_spi_start<=3'd7) begin
spi_start <= 1'b1;
cnt_spi_start <= cnt_spi_start + 1'b1;
end
else
cnt_spi_start <= cnt_spi_start;
end
//提供系统时钟
always # 10 sys_clk <= ~sys_clk; // 50M hz
// 驱动状态机的降频时钟生成
reg clk_25M; // 读时钟最大20MHZ,这里将在状态机里降频到12.5Mhz
wire clk_drive;
always @(posedge sys_clk or negedge sys_rst_n ) begin
if(!sys_rst_n)
clk_25M <= 1'b0;
else
clk_25M <= ~clk_25M;
end
assign clk_drive = clk_25M;
// 模拟主机上层模块命令发送
reg spi_start_reg;
always@(posedge clk_drive or negedge sys_rst_n) begin
if(!sys_rst_n) begin
spi_start_reg <= 1'b0;
end
else
spi_start_reg <= spi_start;
end
always@(posedge clk_drive or negedge sys_rst_n) begin
if(!sys_rst_n) begin
spi_cmd <= 8'b0;
end
else if(spi_start && !spi_start_reg)
if( spi_cmd == 8'b0)
spi_cmd <= 8'hc7;
else
spi_cmd <= 8'hff;
//else if ( spi_cmd == 8'h06)
// spi_cmd <= 8'hc7;
else
spi_cmd <= spi_cmd;
end
// 命令状态机状态设定
localparam IDLE = 7'b000_0001;//空闲状态
localparam WEL = 7'b000_0010;//写使能状态
localparam S_ERA = 7'b000_0100;//扇区擦除状态
localparam C_ERA = 7'b000_1000;//全局擦除
localparam READ = 7'b001_0000;//读状态
localparam WRITE = 7'b010_0000;//写状态
localparam R_STA_REG = 7'b100_0000;//轮询寄存器,等待忙结束状态
// 小命令块输出状态设定
localparam idle_stage = 8'b0000_0001,
w_en_100ns = 8'b0000_0010,
cmd_cyc = 8'b0000_0100,
adds_cyc = 8'b0000_1000,
end_6ns = 8'b0001_0000,
w_n_bit = 8'b0010_0000,
r_n_bit = 8'b0100_0000,
miso_8b = 8'b1000_0000;
reg [7:0] cur_stage ; //当前阶段
reg [6:0] cur_state ; //状态机当前状态
reg [6:0] next_state; //状态机下一状态
reg [7:0] miso_8bufe; //数据接收缓存
// 模拟从机应答
reg [5:0] cnt_miso_data;
always@(posedge spi_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_miso_data <= 6'b0;
else if(cnt_miso_data >= 6'd25 || cur_stage != miso_8b)
cnt_miso_data <= cnt_miso_data;
else if(cur_stage == miso_8b)
cnt_miso_data <= cnt_miso_data + 1'b1;
else
cnt_miso_data <= cnt_miso_data;
end
always@(negedge spi_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
spi_miso <= 1'b1;
end
else if(cur_stage == miso_8b ) begin
case(cnt_miso_data)
6'd7 : spi_miso <= 1'b1;
6'd15 : spi_miso <= 1'b1;
6'd23 : spi_miso <= 1'b0;
default: spi_miso <= 1'b1;
endcase
end
else
spi_miso <= 1'b1;
end
// 使用时序逻辑生成spi时钟
reg [4:0] cnt_clk;
reg spi_clk_sta;
always@(posedge clk_drive or negedge sys_rst_n) begin
if(!sys_rst_n) begin
cnt_clk <= 5'b0;
spi_clk <= 1'b0;
end
else if(spi_clk_sta) begin
spi_clk <= ~spi_clk;
cnt_clk <= cnt_clk + 1'b1;
end
else begin
cnt_clk <= 5'b0;
spi_clk <= 1'b0;
end
end
//(三段式状态机)
//第一段:同步时序描述状态转移
always @(posedge clk_drive or negedge sys_rst_n) begin
if(!sys_rst_n)
cur_state <= IDLE;
else
cur_state <= next_state;
end
//第二段:组合逻辑描述命令状态跳变
always@(*) begin
next_state = IDLE;
case(cur_state)
IDLE : begin
if(spi_start && spi_cmd == `WEL_CMD)
next_state = WEL;
else if(spi_start && spi_cmd == `C_ERA_CMD)
next_state = C_ERA;
else if(spi_start && spi_cmd == `S_ERA_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
WEL: begin
if(stdone)
next_state = IDLE;
else
next_state = WEL;
end
S_ERA: begin
if(stdone)
next_state = R_STA_REG;
else
next_state = S_ERA;
end
C_ERA: begin
if(stdone)
next_state = IDLE;
//next_state = R_STA_REG;
else
next_state = C_ERA;
end
READ: begin
if(stdone)
next_state=IDLE;
else
next_state=READ;
end
WRITE: begin
if(stdone)
next_state = R_STA_REG;
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_drive or negedge sys_rst_n) begin
if(!sys_rst_n) begin
cnt_stage <= 3'b0;
end
else if(son_done || stdone)
cnt_stage <= cnt_stage + 1'b1;
else if(cur_state==IDLE)
cnt_stage <= 3'b0;
else
cnt_stage <= cnt_stage;
end
//命令块组合逻辑由条件变化状态跳变
reg pro_continue;
always@(*) begin
// 命令子模块订阅
case(cur_state)
IDLE : begin
cur_stage = idle_stage;
spi_cmd_reg = 8'b0 ;
stdone = 1'b0;
pro_continue = 1'b1;
end
WEL: begin
case(cnt_stage) // mosi
3'd0 : begin
cur_stage = w_en_100ns;
if(cnt_son_stage == 7'd1)
spi_cmd_reg = `WEL_CMD ;
else
spi_cmd_reg = spi_cmd_reg;
end
3'd1 : begin
stdone = 1'b1;
end
default: ;
endcase
end
C_ERA : begin
case(cnt_stage) // mosi
3'd0 : begin
cur_stage = w_en_100ns;
if(spi_cs)
spi_cmd_reg = `WEL_CMD ;
else
spi_cmd_reg = spi_cmd_reg;
end
3'd1 : begin
cur_stage <= cmd_cyc;
if(spi_cs)
spi_cmd_reg = `C_ERA_CMD ;
else
spi_cmd_reg = spi_cmd_reg;
end
3'd2 : cur_stage = end_6ns;
3'd3 : begin
pro_continue = 1'b0;
cur_stage = cmd_cyc;
if(spi_cs)
spi_cmd_reg = `R_STA_REG_CMD ;
else
spi_cmd_reg = spi_cmd_reg;
if(son_done) cur_stage = miso_8b;
else cur_stage = cur_stage;
end
3'd5 : begin
stdone = 1'b1;
end
default: ;
endcase
end
default: begin
stdone = 1'b0;
spi_cmd_reg = 8'b0;
pro_continue = 1'b1;
end
endcase
end
//第三段:由状态机状态,有时序逻辑进行spi接口的跳变输出
always@(posedge clk_drive or negedge sys_rst_n) begin
if(!sys_rst_n) begin
spi_cs <= 1'b1 ;
spi_mosi <= 1'b0 ;
//spi_addr_reg <= 24'b0;
//spi_data_reg <= 8'b0 ;
cnt_son_stage <= 7'b0;
miso_8bufe <= 8'hff;
son_done <= 1'b0;
spi_clk_sta <= 1'b0;
end
else begin
son_done <= 1'b0;
//命令子模块执行
if(spi_start || (cnt_son_stage != 7'd127))
cnt_son_stage <= cnt_son_stage + 1'b1;
else
cnt_son_stage <= cnt_son_stage;
case(cur_stage)
idle_stage : begin
spi_mosi <= 1'b0;
//spi_addr_reg <= spi_addr;
//spi_data_reg <= spi_data;
cnt_son_stage <= 7'b0;
spi_clk_sta <= 1'b0;
end
w_en_100ns : begin
case(cnt_son_stage)
7'd0 : begin
spi_cs <= 1'b0;
spi_mosi <= spi_cmd_reg[7];
spi_clk_sta <= 1'b1;
end
7'd2 : spi_mosi <= spi_cmd_reg[6];
7'd4 : spi_mosi <= spi_cmd_reg[5];
7'd6 : spi_mosi <= spi_cmd_reg[4];
7'd8 : spi_mosi <= spi_cmd_reg[3];
7'd10: spi_mosi <= spi_cmd_reg[2];
7'd12: spi_mosi <= spi_cmd_reg[1];
7'd14: begin
spi_mosi <= spi_cmd_reg[0];
spi_clk_sta <= 1'b0;
end
7'd15: begin
spi_cs <= 1'b1;
spi_mosi <= 1'b0;
end
7'd16: begin
cnt_son_stage <= 7'd127;
son_done <= 1'b1;
end
default: ;
endcase
end
cmd_cyc : begin
case(cnt_son_stage)
7'd0 : begin
spi_cs <= 1'b0;
spi_mosi <= spi_cmd_reg[7];
spi_clk_sta <= 1'b1;
end
7'd2 : spi_mosi <= spi_cmd_reg[6];
7'd4 : spi_mosi <= spi_cmd_reg[5];
7'd6 : spi_mosi <= spi_cmd_reg[4];
7'd8 : spi_mosi <= spi_cmd_reg[3];
7'd10: spi_mosi <= spi_cmd_reg[2];
7'd12: spi_mosi <= spi_cmd_reg[1];
7'd14: spi_mosi <= spi_cmd_reg[0];
7'd16: begin
son_done <= 1'b1;
spi_mosi <= 1'b0;
if(pro_continue) begin
cnt_son_stage <= 7'd127;
spi_cs <= 1'b1;
spi_clk_sta <= 1'b0;
end
else begin
cnt_son_stage <= 7'b0;
spi_cs <= 1'b0;
spi_clk_sta <= spi_clk_sta;
end
end
default: ;
endcase
end
end_6ns : begin
if(cnt_son_stage == 7'd3) begin // 完成
cnt_son_stage <= 7'd127;
end
else if(cnt_son_stage == 7'd2)
son_done <= 1'b1;
else begin
spi_cs <= 1'b1 ;
spi_mosi <= 1'b0;
end
end
miso_8b : begin // miso 接收数据线传回的8bit信号
case(cnt_son_stage)
7'd0 : begin
spi_cs <= 1'b0;
spi_clk_sta <= 1'b1;
miso_8bufe <= {miso_8bufe[6:0],spi_miso};
end
7'd2 : miso_8bufe <= {miso_8bufe[6:0],spi_miso};
7'd4 : miso_8bufe <= {miso_8bufe[6:0],spi_miso};
7'd6 : miso_8bufe <= {miso_8bufe[6:0],spi_miso};
7'd8 : miso_8bufe <= {miso_8bufe[6:0],spi_miso};
7'd10: miso_8bufe <= {miso_8bufe[6:0],spi_miso};
7'd12: miso_8bufe <= {miso_8bufe[6:0],spi_miso};
7'd14: miso_8bufe <= {miso_8bufe[6:0],spi_miso};
7'd15: begin
if(!spi_miso && !pro_continue) begin
cnt_son_stage <= 7'd16;
son_done <= 1'b1;
spi_cs <= 1'b1;
spi_clk_sta <= 1'b0;
end
else begin
cnt_son_stage <= spi_clk_sta ? 7'd0 : 7'd127;
son_done <= 1'b0;
end
end
7'd16: cnt_son_stage <= 7'd127;
default: ;
endcase
end
default: ;
endcase
end
end
endmodule