描述SPI协议verilog相关的电路

上一篇文章非常简单的介绍了我们配置芯片寄存器的spi协议的基础内容,接下来我们就需要开始写verilog描述出相关的电路。
写verilog第一步肯定需要将输入输出端口,常量等信息补齐全;
设置spi_addr_width标记spi传输数据命令的寄存器地址值宽度,spi_cmd_width变量标记spi传输数据的整体宽度。
输入输出变量中,clk时钟信号和rst复位信号都是必备的系统信号;除去spi接口的4根数据线外,还有输入的24位cmd_data,将需要发送的数据从这个端口传递给spi处理;输出的8位read_data,将读取到的寄存器数据输出便于做后续处理;以及控制信号,en控制spi的工作速率,ready指示spi发送工作的开始,sink_vld指示spi发送读取工作的结束。
上一篇文章谈到,我们将整个spi的发送读取分为5个状态,
spi状态机的5种状态
现在我们需要捋顺每个状态跳转的条件;idle空闲状态跳转到write_addr写地址状态,说明此时需要发送spi数据,所以ready信号是跳转条件;从上图可以看到,write_addr写地址状态可以跳转到write_data状态或read状态,而决定条件是spi的命令是读取命令还是写入命令,这取决于spi写入数据的msb(最高位);write_data状态和read状态跳转回idle空闲状态的条件是需要发送的数据已经发送完毕或需要读取的数据已经读取完毕。
另外,在write_addr写地址状态,write_data状态和read状态里面需要用到计数器,记录当前已经发送或读取的数据量,作为跳出该状态的判断依据之一。
由于这部分的状态机比较简单,所以第一版我采用了一段式状态机。为了便于理解spi_clk的产生,我选择使用分频操作生成spi_clk,但其实更推荐的方式是使用mmcm,pll等方式产生spi_clk。
localparam spi_data_width = spi_cmd_width - spi_addr_width;
assign flag_write_addr_update = (cnt < spi_addr_width && spi_clk == 1'b0) ? 1'b1 : 1'b0;
assign flag_write_addr_hold = (cnt < spi_addr_width) ? 1'b1 : 1'b0 ;
assign flag_data_update = (cnt < spi_data_width && spi_clk == 1'b0) ? 1'b1 : 1'b0 ;
assign flag_data_hold = (cnt < spi_data_width) ? 1'b1 : 1'b0 ;
always @ (posedge clk or posedge rst)
begin
if (rst)
begin
spi_clk <= spi_idle ;
spi_enb <= 1'b1 ;
spi_di <= 1'b0 ;
read_data <= 'd0 ;
sink_vld <= 1'b0 ;
state <= idle ;
cmd_data_r <= 'd0 ;
cnt <= 'd0 ;
flag_read <= 1'b0 ;
end
else if (en)
begin
case (state)
idle:
begin
if (ready)
begin
state <= write_addr ;
spi_enb <= 1'b0 ;
cmd_data_r <=cmd_data ;
cnt <= 'd0 ;
flag_read <= !cmd_data[23] ;
end
sink_vld <= 1'b0;
spi_di <= 1'b0;
spi_enb <= 1'b1;
end
write_addr:
begin 
spi_enb <= 1'b0;
if (flag_write_addr_update)
begin
spi_di <= cmd_data_r[23] ;
cmd_data_r <= {cmd_data_r[22:0], 1'b0} ;
spi_clk <= 1'b1 ;
cnt <= cnt + 8'd1 ;
end 
else if (flag_write_addr_hold)
begin
spi_clk <= 1'b0;
end
else 
begin
if (flag_read)
state <= read ;
else
state <= write_data ;
cnt <= 'd0 ;
spi_clk <= 1'b0 ;
end
end
write_data:
begin
if (flag_data_update)
begin
spi_di <= cmd_data_r[23] ;
cmd_data_r <= {cmd_data_r[22:0], 1'b0} ;
spi_clk <= 1'b1 ;
cnt <= cnt + 8'd1 ;
end 
else if (flag_data_hold)
begin
spi_clk <= 1'b0 ;
end
else 
begin
state <= idle ;
spi_clk <= 1'b0 ;
sink_vld<= 1'b1 ;
end
end
read:
begin
if (flag_data_update)
begin
spi_clk <= 1'b1 ;
end 
else if (flag_data_hold)
begin
spi_clk <= 1'b0 ;
read_data[0] <= spi_do ;
read_data[7:1] <= read_data[6:0] ;
cnt <= cnt + 8'd1 ;
end
else 
begin
state <= idle;
sink_vld <= 1'b1;
end
end
default : state <= idle;
endcase
end
end
从上一个文章里面,我们可以看到,在write_addr写地址状态,write_data状态里,我们需要在spi_clk时钟的上升沿更新spi_do值,在spi_clk下降沿保持spi_do值;
所以我们需要判断,
当spi_clk为低电平并且传输还没有结束时,我们需要将spi_clk拉高,将需要的发送数据串行数据更新到spi_di,更新计数器值;
而当spi_clk为高电平并且传输还没有结束时,我们仅需要将spi_clk拉低,保持spi_di不变。
read状态里,我们仅需要在spi_clk时钟的下降沿采样spi_do值;即
当spi_clk为高电平并且传输还没有结束时,我们需要将spi_clk拉低,将spi_do采样并保存至read_data,更新计数器值;
而当spi_clk为低电平并且传输还没有结束时,我们仅需要将spi_clk拉高。整个电路的流程就已经被我们用verilog描述出来了。
使用vcs仿真之后的结果,可以见下图:
希望通过这一版简单的讲解,能让大家对spi的verilog描述有更加清晰的认识。
这一版本的spi最终上板测试没有问题,但确实还存在一些问题,不推荐使用生成时钟做spi_clk, 如果还有更好的建议可以提出来一起讨论。


本田电动汽车将于2025年采用特斯拉充电
双目测距的机器视觉简单实例分享
研究人员利用3D打印制备石墨泡沫
阿里确认以20亿美元收购网易考拉,将考拉保持独立品牌运营
关于5G频谱的划分消息
描述SPI协议verilog相关的电路
高压断路器_高压断开关测试仪常见问答和处理方法
2019年是公认的5G元年,三星5G先锋让多款手机品牌的用户都可参与
电脑又死机,或许这些地方的散热被你忽略了
无线充方案都有哪些类型可以选择
ad9854调试使用心得
公交车站客流摄像头要什么软件
2018年6月20日 | 周三 | 区块链早报
基于ARM的嵌入式多路信号数据采集系统
宽带自组网电台选型指导
LC谐振回路在高频小信号放大电路和高频功放电路中的作用
深圳大学:在柔性自修复传感器领域取得研究成果,将有望帮助视障人士有便利的沟通和学习方式
PMOS开关管的选择与电路图
2019年第三季度华为销量最高,华为Mate30系列关注度占比近七成
基于增量K均值分段HMM的识别算法在微机器人控制系统中的应用