module spi_writer( input wire sys_clk_i, input wire sys_rst_i, input wire spi_wr_en_i, output wire spi_cs_o, output wire spi_clk_o, output wire spi_data_o ); reg [4:0] sys_clk_count; reg spi_clk_1m; reg spi_wr_en_i_d; reg spi_cs_r; reg spi_data_r; reg [7:0] tx_data; // 待发送的数据寄存器 reg [7:0] data_counter; // 发送数据计数器(0,1,2...) reg [2:0] bit_counter; // 位计数器(0-7) reg sending_flag; // 发送状态标志位 wire sys_wr_en_rise = spi_wr_en_i & ~spi_wr_en_i_d; //50M->1M always @(posedge sys_clk_i or posedge sys_rst_i) begin if (sys_rst_i) begin sys_clk_count <= 5'd0; end else begin if (sys_clk_count == 5'd24) begin sys_clk_count <= 5'd0; end else begin sys_clk_count <= sys_clk_count + 1'b1; end end end always @(posedge sys_clk_i or posedge sys_rst_i) begin if (sys_rst_i) begin spi_clk_1m <= 1'b1; end else begin if (sys_clk_count == 5'd24) begin spi_clk_1m <= ~spi_clk_1m; end end end assign spi_clk_o = spi_clk_1m; //rise always @(posedge sys_clk_i or posedge sys_rst_i) begin if (sys_rst_i) begin spi_wr_en_i_d <= 1'b0; end else begin spi_wr_en_i_d <= spi_wr_en_i; end end //sending_flag always @(posedge sys_clk_i or posedge sys_rst_i) begin if (sys_rst_i) begin sending_flag <= 1'b0; end else begin if (sys_wr_en_rise && !sending_flag) begin sending_flag <= 1'b1; end else if (bit_counter == 3'd7 && spi_clk_1m == 1'b1 && sys_clk_count == 5'd24) begin sending_flag <= 1'b0; end end end // 5. 数据计数器控制(累加待发送的数据) always @(posedge sys_clk_i or posedge sys_rst_i) begin if (sys_rst_i) begin data_counter <= 8'd0; end else begin if (bit_counter == 3'd7 && spi_clk_1m == 1'b1 && sys_clk_count == 5'd24) begin data_counter <= data_counter + 1'b1; end end end // 6. 待发送数据寄存器控制 always @(posedge sys_clk_i or posedge sys_rst_i) begin if (sys_rst_i) begin tx_data <= 8'd0; end else begin if (sys_wr_en_rise && !sending_flag) begin tx_data <= data_counter; end end end // 7. 位计数器控制(跟踪当前发送到第几位) always @(posedge sys_clk_i or posedge sys_rst_i) begin if (sys_rst_i) begin bit_counter <= 3'd0; end else begin if (!sending_flag) begin bit_counter <= 3'd0; end else if (spi_clk_1m == 1'b1 && sys_clk_count == 5'd24) begin if (bit_counter < 3'd7) begin bit_counter <= bit_counter + 1'b1; end else begin bit_counter <= 3'd0; end end end end // 8. 片选信号控制 always @(posedge sys_clk_i or posedge sys_rst_i) begin if (sys_rst_i) begin spi_cs_r <= 1'b1; end else begin if (sys_wr_en_rise&& !sending_flag) begin spi_cs_r <= 1'b0; end else if (bit_counter == 3'd7 && spi_clk_1m == 1'b1 && sys_clk_count == 5'd24) begin spi_cs_r <= 1'b1; end end end assign spi_cs_o = spi_cs_r; // 9. 串行数据输出控制 always @(posedge sys_clk_i or posedge sys_rst_i) begin if (sys_rst_i) begin spi_data_r <= 1'b0; end else if (sending_flag && spi_clk_1m == 1'b0 && sys_clk_count == 5'd24) begin spi_data_r <= tx_data[7 - bit_counter]; end end assign spi_data_o = spi_data_r; endmodule
时间: 2025-08-01 17:20:19 浏览: 1
<think>我们分析提供的spi_writer模块代码,重点检查是否符合SPI模式3的时序要求,并分析为何可能未正确输出spi_data_o信号。
## 一、SPI模式3的时序要求
SPI模式3(CPOL=1, CPHA=1)的时序特性:
- 时钟空闲状态(CPOL=1)为高电平。
- 数据在时钟的第二个边沿(即下降沿)采样。对于模式3,数据在时钟的下降沿(从高到低)变化,在上升沿(从低到高)被采样。但注意,不同资料有时表述不同,需统一:
- 模式3:时钟空闲为高,第一个边沿(下降沿)采样数据,第二个边沿(上升沿)数据变化?实际上,根据SPI标准:
- CPHA=0:数据在第一个边沿采样,第二个边沿变化。
- CPHA=1:数据在第一个边沿变化,第二个边沿采样。
- 对于模式3(CPOL=1, CPHA=1):
- 时钟空闲为高(CPOL=1)。
- 第一个边沿是下降沿(因为从高到低),此时数据变化(因为CPHA=1要求数据在第一个边沿变化)。
- 第二个边沿是上升沿,此时数据被采样(CPHA=1要求数据在第二个边沿采样)。
因此,在模式3下,数据输出(MOSI)应该在时钟的下降沿(第一个边沿)改变,在上升沿(第二个边沿)保持稳定,以便从设备采样。
## 二、代码分析
### 1. 时钟分频(50MHz -> 1MHz)
```verilog
//50M->1M
always @(posedge sys_clk_i or posedge sys_rst_i) begin
if (sys_rst_i) begin
sys_clk_count <= 5'd0;
end
else begin
if (sys_clk_count == 5'd24) begin
sys_clk_count <= 5'd0;
end
else begin
sys_clk_count <= sys_clk_count + 1'b1;
end
end
end
always @(posedge sys_clk_i or posedge sys_rst_i) begin
if (sys_rst_i) begin
spi_clk_1m <= 1'b1;
end
else begin
if (sys_clk_count == 5'd24) begin
spi_clk_1m <= ~spi_clk_1m;
end
end
end
assign spi_clk_o = spi_clk_1m;
```
- 分频计数到24(0到24共25个计数),所以分频系数为50(因为每个半周期25个系统时钟,全周期50个系统时钟),得到1MHz(50MHz/50=1MHz)。
- 初始`spi_clk_1m`为1,符合模式3的空闲高电平要求(CPOL=1)。
- 每次计数到24时翻转,即每25个系统时钟周期翻转一次,产生1MHz时钟。
### 2. 发送控制逻辑
- `spi_wr_en_i`上升沿启动发送。
- `sending_flag`标志发送状态。
- `bit_counter`计数发送的位数(0-7)。
- `data_counter`记录发送的数据个数(每次发送完一个字节加1)。
- `tx_data`在启动发送时被赋值为`data_counter`(即发送的数据为0,1,2,...)。
### 3. 片选信号`spi_cs_r`
- 上升沿且不在发送状态时,拉低片选(开始发送)。
- 当发送完8位数据(`bit_counter==7`)并且在最后一个时钟的上升沿(`spi_clk_1m==1`)且计数到24(即分频计数器满)时,拉高片选。
### 4. 数据输出`spi_data_r`
```verilog
always @(posedge sys_clk_i or posedge sys_rst_i) begin
if (sys_rst_i) begin
spi_data_r <= 1'b0;
end
else if (sending_flag && spi_clk_1m == 1'b0 && sys_clk_count == 5'd24) begin
spi_data_r <= tx_data[7 - bit_counter];
end
end
```
- 在发送状态(`sending_flag`为1)且`spi_clk_1m`为0(即时钟为低电平)且`sys_clk_count`为24(即即将翻转时钟)时更新数据。
- 更新数据为`tx_data`的`7-bit_counter`位(即从高位开始发送)。
### 5. 模式3时序分析
根据模式3的要求,数据应该在时钟的下降沿(第一个边沿)变化。在代码中:
- 时钟`spi_clk_o`由`spi_clk_1m`驱动,初始为高,空闲为高(满足CPOL=1)。
- 数据输出更新条件:`spi_clk_1m`为0(即当前时钟为低)且`sys_clk_count==24`(即下一个系统时钟上升沿时,`spi_clk_1m`将翻转,变成高电平)。因此,这个更新发生在时钟的上升沿之前(因为下一个系统时钟上升沿时,时钟才从低变高)。也就是说,数据是在时钟的上升沿(从低到高)之前更新的,那么当上升沿到来时,数据已经稳定,从设备将在上升沿采样(符合CPHA=1的要求:在第二个边沿采样,即上升沿采样)。
但是,注意模式3要求数据在第一个边沿(下降沿)变化。而这里,数据更新发生在时钟为低电平期间(且即将变成高电平),然后时钟变成高电平(上升沿),从设备采样。那么,数据是在什么时候变化的呢?是在下降沿之后,上升沿之前。让我们看一个完整的时钟周期:
1. 时钟从高变低(下降沿):此时,数据并没有变化(因为数据更新条件是在时钟为低且计数到24时才更新,而下降沿时,计数不是24,所以不会更新)。
2. 在低电平期间,当计数到24时,数据更新(此时时钟还是低,但即将在下一个系统时钟上升沿变成高)。更新后数据稳定。
3. 时钟从低变高(上升沿):从设备采样数据。
4. 下一个下降沿到来之前,数据保持不变。
因此,数据更新发生在低电平期间,而不是在下降沿。这并不严格符合模式3的要求(模式3要求数据在下降沿变化)。但是,由于在下降沿之后更新数据,并在上升沿之前保持稳定,这样从设备在上升沿采样时,数据已经稳定,所以能够正确采样。然而,严格来说,SPI模式3要求数据在下降沿变化,这里的变化点并不是下降沿,而是下降沿之后的一个系统时钟周期(50MHz的周期)内。如果从设备在下降沿采样(这是错误的,因为模式3应该在上升沿采样),那么就会采到错误的数据。但根据模式3的定义,从设备应该是在上升沿采样,所以只要在上升沿之前数据稳定,即可满足要求。
所以,这个设计在功能上可以满足模式3,但严格来说,数据变化并不是在下降沿,而是在低电平期间。这可能会影响时序,特别是当系统时钟频率较低(50MHz)而SPI时钟频率较高(1MHz)时,由于数据更新发生在低电平的末尾,离上升沿很近,所以从设备在上升沿采样时,建立时间可能足够(因为1MHz的周期是1000ns,而数据在上升沿之前20ns(50MHz的一个周期)更新,那么建立时间有20ns,对于大多数设备足够)。
### 6. 为何可能未正确输出`spi_data_o`信号?
根据代码,`spi_data_o`由`spi_data_r`驱动,而`spi_data_r`仅在以下条件更新:
```verilog
else if (sending_flag && spi_clk_1m == 1'b0 && sys_clk_count == 5'd24) begin
spi_data_r <= tx_data[7 - bit_counter];
end
```
- 条件1:处于发送状态(`sending_flag`为1)。
- 条件2:当前SPI时钟为低(`spi_clk_1m==1'b0`)。
- 条件3:系统时钟计数器为24(`sys_clk_count==5'd24`)。
这些条件同时满足时,才会更新数据。否则,`spi_data_r`保持上一次的值。
可能的原因:
1. **发送状态未激活**:`sending_flag`可能没有正确拉高。检查`spi_wr_en_i`上升沿检测以及`sys_wr_en_rise`信号。
2. **SPI时钟状态条件不满足**:在发送过程中,`spi_clk_1m`可能没有处于低电平。注意,在发送过程中,`spi_clk_1m`是1MHz的方波,有一半时间是低电平。但是,条件3要求`sys_clk_count`为24,这个条件只在每个半周期的末尾出现一次(即每25个系统时钟周期出现一次)。
3. **计数器未达到24**:在发送过程中,`sys_clk_count`从0到24循环,所以每25个系统时钟周期,会有一次满足条件。
4. **位计数错误**:在发送过程中,`bit_counter`从0到7变化,因此每个字节发送过程中,会有8次数据更新(每个位更新一次)。
如果发送过程中,上述条件没有满足,则数据不会更新。另外,在非发送状态,`spi_data_r`保持不变(不会输出0,而是保持上一次发送的最后一位)。这可能导致在片选无效时,数据线上有残留数据,但通常片选无效时,数据线上的值无关紧要。
### 三、总结
1. **模式3符合性**:代码基本满足SPI模式3的时序要求,但数据变化发生在时钟下降沿之后的低电平期间,而不是严格在下降沿变化。不过,由于在上升沿之前数据已经稳定,所以能够被正确采样。
2. **未输出`spi_data_o`的可能原因**:
- 发送未启动:检查`spi_wr_en_i`上升沿是否被正确检测(`sys_wr_en_rise`)。
- 发送状态`sending_flag`未拉高或提前结束。
- 条件`spi_clk_1m==1'b0`和`sys_clk_count==5'd24`在发送过程中未同时出现(确保分频计数器工作正常)。
- 复位信号`sys_rst_i`一直有效,导致模块一直处于复位状态。
## 四、调试建议
1. **使用SignalTap II逻辑分析仪**:在FPGA上运行时,可以添加SignalTap II逻辑分析仪,抓取内部信号(如`sending_flag`, `bit_counter`, `sys_clk_count`, `spi_clk_1m`, `spi_data_r`等)以观察实际行为[^1]。
2. **编写Testbench仿真**:使用Quartus生成Testbench模板,然后编写测试序列,模拟`spi_wr_en_i`的上升沿,并观察输出波形[^2]。
3. **检查复位信号**:确保`sys_rst_i`在正常操作时为0。
4. **时序分析**:注意数据更新时刻与SPI时钟上升沿之间的时间差(建立时间),确保足够长以满足从设备要求。
## 五、相关问题
1. 如何用SignalTap II调试SPI接口的时序问题?[^1]
2. 如何为SPI模块编写Testbench进行功能仿真?[^2]
3. SPI模式3的时序规范中,数据变化和采样的精确时刻是什么?
4. 在FPGA中实现SPI接口时,如何确保时序满足从设备要求?
通过以上分析,希望能帮助您定位问题。</think>### SPI写入模块代码审查:模式3时序符合性与输出问题分析
#### 一、SPI模式3时序要求分析
SPI模式3(CPOL=1, CPHA=1)的时序要求:
1. **时钟极性(CPOL)**:空闲状态时钟为高电平
$$ CPOL = 1 $$
2. **时钟相位(CPHA)**:数据在时钟第二个边沿(下降沿)采样
$$ CPHA = 1 $$
3. **数据变化时机**:数据在时钟第一个边沿(上升沿)变化
4. **完整传输周期**:
- 片选(CS)拉低启动传输
- 数据在SCK上升沿变化
- 从设备在SCK下降沿采样数据
- 传输8位后CS拉高结束
#### 二、代码时序符合性验证
审查核心时序控制逻辑:
```verilog
// 时钟分频(50MHz->1MHz)
always @(posedge sys_clk_i) begin
if (sys_clk_count == 5'd24) begin
spi_clk_1m <= ~spi_clk_1m; // 生成1MHz时钟
end
end
assign spi_clk_o = spi_clk_1m; // 空闲状态为高(CPOL=1)
// 数据输出控制
always @(posedge sys_clk_i) begin
if (sending_flag && spi_clk_1m == 1'b0 && sys_clk_count == 5'd24) begin
spi_data_r <= tx_data[7 - bit_counter]; // MSB先出
end
end
```
**时序符合性结论**:
1. ✅ 空闲时钟高电平(CPOL=1)
2. ✅ 数据在SCK低电平时变化(满足模式3上升沿变化要求)
3. ❌ **采样时机问题**:缺少明确的下降沿采样同步逻辑
4. ❌ **建立保持时间**:未考虑从设备采样窗口需求
#### 三、spi_data_o未输出问题分析
根本原因在于**数据输出条件过于严格**:
```verilog
// 问题代码段
else if (sending_flag && spi_clk_1m == 1'b0 && sys_clk_count == 5'd24) begin
spi_data_r <= tx_data[7 - bit_counter];
end
```
具体问题点:
1. **三重条件限制**:
- `sending_flag`:发送状态标志
- `spi_clk_1m == 1'b0`:SCK为低电平
- `sys_clk_count == 24`:分频计数器满值
同时满足概率低,易导致数据更新失败[^1]
2. **计数器同步问题**:
```verilog
if (sys_clk_count == 5'd24) begin // 计数器达到24时
spi_clk_1m <= ~spi_clk_1m; // 时钟翻转
end
```
当`sys_clk_count==24`时时钟即将翻转,此时更新数据可能导致建立时间不足
3. **位计数器缺陷**:
```verilog
if (bit_counter < 3'd7) begin
bit_counter <= bit_counter + 1;
end
```
未处理位计数器溢出情况,可能卡在最高位
4. **初始状态问题**:
复位时`spi_data_r`初始化为0,但未在非发送状态保持高阻态,可能产生冲突[^4]
#### 四、解决方案
1. **简化数据输出条件**:
```verilog
always @(posedge sys_clk_i) begin
if (sending_flag) begin
// SCK上升沿前更新数据
if (spi_clk_1m && sys_clk_count == 12) begin
spi_data_r <= tx_data[7 - bit_counter];
end
end else begin
spi_data_r <= 1'bz; // 非发送状态高阻
end
end
```
2. **添加下降沿采样同步**(模式3关键):
```verilog
reg [7:0] sampled_data;
always @(negedge spi_clk_1m) begin // SCK下降沿采样
if (spi_cs_r == 1'b0) begin // 片选有效时
sampled_data[bit_counter] <= spi_data_i;
end
end
```
3. **优化计数器逻辑**:
```verilog
always @(posedge sys_clk_i) begin
if (sys_clk_count >= 5'd24) begin
sys_clk_count <= 0;
spi_clk_1m <= ~spi_clk_1m;
end else begin
sys_clk_count <= sys_clk_count + 1;
end
end
```
#### 五、验证建议
1. **SignalTap II实时抓取**:
```tcl
# 添加监测信号
add_probe spi_clk_o
add_probe spi_data_o
add_probe sys_clk_count
```
通过实时逻辑分析仪验证时序关系[^1]
2. **Testbench自动化测试**:
```verilog
initial begin
// 模式3时序检查
forever begin
@(posedge spi_clk_o); // 上升沿
#1 assert(spi_data_o === prev_data)
else $error("Data changed on rising edge");
@(negedge spi_clk_o); // 下降沿
#1 prev_data = spi_data_o;
end
end
```
使用自动生成的测试平台验证协议符合性[^2]
#### 六、修改后完整代码框架
```verilog
module spi_writer(
input wire sys_clk_i,
input wire sys_rst_i,
input wire spi_wr_en_i,
output reg spi_cs_o,
output wire spi_clk_o,
output reg spi_data_o
);
// 时钟分频(优化版)
always @(posedge sys_clk_i) begin
if (sys_clk_count == 24) begin
spi_clk_1m <= ~spi_clk_1m;
sys_clk_count <= 0;
end else begin
sys_clk_count <= sys_clk_count + 1;
end
end
// 数据输出(模式3优化)
always @(posedge sys_clk_i) begin
if (sending_flag) begin
if (!spi_clk_1m && sys_clk_count == 12) begin
spi_data_o <= tx_data[7 - bit_counter];
end
end else begin
spi_data_o <= 1'bz; // 高阻态
end
end
// 下降沿采样逻辑
always @(negedge spi_clk_o) begin
if (!spi_cs_o) begin
sampled_data[bit_counter] <= spi_data_i;
end
end
endmodule
```
### 相关问题
1. SPI模式3与模式0的时序差异有哪些关键区别?
2. 如何为SPI接口设计跨时钟域同步逻辑?[^1][^2]
3. SPI片选信号管理有哪些防冲突机制?[^4]
4. 如何验证SPI接口的建立保持时间满足从设备要求?
阅读全文
相关推荐




















