当前位置:网站首页>【FPGA】day22-SPI协议回环
【FPGA】day22-SPI协议回环
2022-08-11 03:28:00 【春风浅作序】
一、实验概述
1、实验任务

2、SPI协议
(1)简介
SPI协议多用来配置芯片,而使用其他高速协议传输数据
(2)一主机多从机

(3)通信原理
MSB:先发高位(与I2C相同)
但SPI不是标准协议,I2C为标准协议
(4)工作模式

二、项目设计
1、项目要求
1、SPI主从机通信;
2、主机模拟FPGA,从机模拟存储器;
3、主机通过命令对从机进行数据读出或写入;
4、帧格式–读/写命令(1B)、地址(1B)、数据(XB),写命令-8’ha9;读命令-8’h28。
2、模块框图

三、项目源码
1、顶层模块
module top(
input clk ,
input rst_n ,
input [1:0] key
);
//信号定义
wire key_out ;
wire [7:0] master_dout ;
wire master_dout_vld ;
wire spi_miso ;
wire spi_mosi ;
wire spi_sclk ;
wire spi_cs_n ;
//模块例化
key_debounce #(.KEY_W(3)) u_key(
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*input [KEY_W-1:0] */.key_in (key ),
/*output [KEY_W-1:0] */.key_out (key_out )
);
spi_master u_master(
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*input */.start (key_out ),
/*output [7:0] */.dout (master_dout ),
/*output */.dout_vld (master_dout_vld),
/*output */.cs_n (spi_cs_n ),
/*output */.sclk (spi_sclk ),
/*output */.mosi (spi_mosi ),
/*input */.miso (spi_miso )
);
spi_slave u_slave(
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*output [7:0] */.dout (slave_dout ),
/*output */.dout_vld (slave_dout_vld ),
/*input */.cs_n (spi_cs_n ),
/*input */.sclk (spi_sclk ),
/*input */.mosi (spi_mosi ),
/*output */.miso (spi_miso )
);
endmodule
2、参数模块
`define CMD_WRITE 8'ha9
`define CMD_READ 8'h28
3、主机顶层模块
module spi_master (
input clk ,
input rst_n ,
input write ,
input read ,
output cs_n ,
output sclk ,
output mosi ,
input miso
);
//定义信号
wire req ;
wire [7:0] wr_data ;
wire [7:0] rd_data ;
wire done ;
//模块例化
master_ctrl u_ctrl(
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*input */.write (write ),
/*input */.read (read ),
/*output */.req (req ),
/*output [7:0] */.wr_data (wr_data ),
/*input [7:0] */.rd_data (rd_data ),
/*input */.done (done )
);
master_intf u_intf(
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*input */.req (req ),
/*input [7:0] */.din (wr_data ),
/*output [7:0] */.dout (rd_data ),
/*output */.done (done ),
/*output */.cs_n (cs_n ),//输出到spi_slave
/*output */.sclk (sclk ),
/*output */.mosi (mosi ),
/*input */.miso (miso )
);
endmodule
4、主机控制模块
`include "cfg.v"
`timescale 1ns /1ns
module master_ctrl (
input clk ,
input rst_n ,
input write ,
input read ,
output req ,
output [7:0] wr_data ,
input [7:0] rd_data ,
input done
);
//参数定义
localparam IDLE = 4'b0001,
CMD = 4'b0010,
ADDR = 4'b0100,
DATA = 4'b1000;
parameter BYTE_NUM = 8;
//信号定义
reg [3:0] state_c ;
reg [3:0] state_n ;
reg [3:0] cnt_byte ;//字节计数器
wire add_cnt_byte;
wire end_cnt_byte;
reg [3:0] xx ;
reg [7:0] wr_addr ;//写数据地址 256
reg [7:0] rd_addr ;//读数据地址
reg flag ;//表示写或读
reg trans_req ;
reg [7:0] trans_data ;
wire idle2cmd ;
wire cmd2addr ;
wire addr2data ;
wire data2idle ;
//FSM
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
state_c <= IDLE ;
end
else begin
state_c <= #1 state_n;
end
end
always @(*) begin
case(state_c)
IDLE :begin
if(idle2cmd)
state_n = CMD ;
else
state_n = state_c ;
end
CMD :begin
if(cmd2addr)
state_n = ADDR ;
else
state_n = state_c ;
end
ADDR :begin
if(addr2data)
state_n = DATA ;
else
state_n = state_c ;
end
DATA :begin
if(data2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
default : state_n = IDLE ;
endcase
end
assign idle2cmd = state_c==IDLE && (write | read);
assign cmd2addr = state_c==CMD && (end_cnt_byte);
assign addr2data = state_c==ADDR && (end_cnt_byte);
assign data2idle = state_c==DATA && (end_cnt_byte);
always @(posedge clk or negedge rst_n) begin //计数主机发送了几个字节
if (rst_n==0) begin
cnt_byte <= 0;
end
else if(add_cnt_byte) begin
if(end_cnt_byte)
cnt_byte <= 0;
else
cnt_byte <= cnt_byte+1 ;
end
end
assign add_cnt_byte = (state_c != IDLE && done);
assign end_cnt_byte = add_cnt_byte && cnt_byte == (xx)-1 ;
always @(*)begin
if(state_c == CMD)begin //发命令
xx = 1;
end
else if(state_c == ADDR)begin //发地址
xx = 1;
end
else begin //发数据
xx = BYTE_NUM;
end
end
//写地址计数器
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
wr_addr <= 0;
end
else if(data2idle & ~flag)begin //写完1次 地址偏移
wr_addr <= wr_addr + BYTE_NUM;
end
end
//读地址计数器
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
rd_addr <= 0;
end
else if(data2idle & flag)begin //读完1次 地址偏移
rd_addr <= rd_addr + BYTE_NUM;
end
end
//flag 读写标志 0:写 1:读
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
flag <= 1'b0;
end
else if(read)begin
flag <= 1'b1;
end
else if(write)begin
flag <= 1'b0;
end
end
//输出
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
trans_req <= 1'b0;
end
else if(idle2cmd | cmd2addr | addr2data)begin
trans_req <= 1'b1;
end
else if(state_c == DATA && add_cnt_byte && ~end_cnt_byte)begin
trans_req <= 1'b1;
end
else begin
trans_req <= 1'b0;
end
end
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
trans_data <= 0;
end
else if(idle2cmd)begin //发命令
trans_data <= flag ? `CMD_READ : `CMD_WRITE; //发写命令或读命令
end
else if(cmd2addr)begin
trans_data <= flag ? rd_addr : wr_addr; //发写地址或读地址
end
else if(addr2data || state_c == DATA && add_cnt_byte && ~end_cnt_byte)begin //发数据
trans_data <= flag ? 0 : wr_addr + cnt_byte;
end
end
assign req = trans_req;
assign wr_data = trans_data;
endmodule
5、主机接口模块
module master_intf(
input clk ,
input rst_n ,
input req ,
input [7:0] din ,
output [7:0] dout ,
output done ,
output cs_n ,//输出到spi_slave
output sclk ,
output mosi ,
input miso
);
//参数定义
localparam IDLE = 3'b001,
TRANS = 3'b010,//发送/采样数据
DONE = 3'b100;
//信号定义
reg [2:0] state_c ;
reg [2:0] state_n ;
reg [4:0] cnt_sclk ;//spi时钟计数器
wire add_cnt_sclk;
wire end_cnt_sclk;
reg [3:0] cnt_bit ;//串并、并串转换
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [7:0] rx_data ;//串并转换寄存器 采样数据
wire idle2trans ;
wire trans2done ;
wire done2trans ;
wire done2idle ;
reg spi_csn ;//片选
reg spi_sclk ;
reg spi_mosi ;
//FSM
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
state_c <= IDLE ;
end
else begin
state_c <= state_n;
end
end
always @(*) begin
case(state_c)
IDLE :begin
if(idle2trans)
state_n = TRANS ;
else
state_n = state_c ;
end
TRANS :begin
if(trans2done)
state_n = DONE ;
else
state_n = state_c ;
end
DONE :begin
if(done2trans)
state_n = TRANS ;
else if(done2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
default : state_n = IDLE ;
endcase
end
assign idle2trans = state_c==IDLE && (req);
assign trans2done = state_c==TRANS && (end_cnt_bit);
assign done2trans = state_c==DONE && (req);
assign done2idle = state_c==DONE && (~req);
//计数器
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
cnt_sclk <= 0;
end
else if(add_cnt_sclk) begin
if(end_cnt_sclk)
cnt_sclk <= 0;
else
cnt_sclk <= cnt_sclk+1 ;
end
end
assign add_cnt_sclk = (state_c == TRANS);
assign end_cnt_sclk = add_cnt_sclk && cnt_sclk == (16)-1 ;
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
cnt_bit <= 0;
end
else if(add_cnt_bit) begin
if(end_cnt_bit)
cnt_bit <= 0;
else
cnt_bit <= cnt_bit+1 ;
end
end
assign add_cnt_bit = (end_cnt_sclk);
assign end_cnt_bit = add_cnt_bit && cnt_bit == (8)-1 ;
//输出
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
spi_csn <= 1'b1;
end
else if(idle2trans)begin //开始传输前拉低
spi_csn <= 1'b0;
end
else if(done2idle)begin
spi_csn <= 1'b1; //传输完成后拉高
end
end
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
spi_sclk <= 1'b1;
end
else if(add_cnt_sclk && cnt_sclk == 4-1)begin
spi_sclk <= 1'b0;
end
else if(add_cnt_sclk && cnt_sclk == 12-1)begin
spi_sclk <= 1'b1;
end
end
always @(posedge clk or negedge rst_n)begin //主机输出
if(~rst_n)begin
spi_mosi <= 1'b0;
end
else if(add_cnt_sclk && cnt_sclk == 4-1)begin
spi_mosi <= din[7-cnt_bit];
end
end
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
rx_data <= 0;
end
else if(add_cnt_sclk && cnt_sclk == 12-1)begin
rx_data[7-cnt_bit] <= miso;
end
end
assign dout = rx_data;
assign done = trans2done;
assign cs_n = spi_csn;
assign mosi = spi_mosi;
assign sclk = spi_sclk;
endmodule
6、按键消抖模块
module key_debounce #(parameter KEY_W = 3,TIME_20MS = 1000_000)(
input clk ,
input rst_n ,
input [KEY_W-1:0] key_in ,
output reg [KEY_W-1:0] key_out //检测到按下,输出一个周期的高脉冲,其他时刻为0
);
//信号定义
reg [19:0] cnt ;
wire add_cnt ;
wire end_cnt ;
reg add_flag;
reg [KEY_W-1:0] key_r0 ;//同步按键输入
reg [KEY_W-1:0] key_r1 ;//打拍
wire [KEY_W-1:0] nedge ;//检测下降沿
//计数器 检测到下降沿的时候,开启计数器延时20ms
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
end
assign add_cnt = add_flag;
assign end_cnt = add_cnt && cnt == TIME_20MS-1;
//检测到下降沿的时候,拉高计数器计数使能信号,延时结束时,再拉低使能信号
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
add_flag <= 1'b0;
end
else if(nedge)begin
add_flag <= 1'b1;
end
else if(end_cnt)begin
add_flag <= 1'b0;
end
end
//同步按键输入,并打一拍,以检测下降沿
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_r0 <= {
KEY_W{
1'b1}};
key_r1 <= {
KEY_W{
1'b1}};
end
else begin
key_r0 <= key_in;//同步
key_r1 <= key_r0;//打拍
end
end
assign nedge = ~key_r0 & key_r1;
//延时20ms结束的时钟周期,输出按键的状态,若按下输出一个周期的高脉冲,否则输出0
[email protected](posedge clk or negedge rst_n)begin
if(~rst_n)begin
key_out <= 0;
end
else begin
key_out <= end_cnt?~key_r1:0;
end
end
endmodule
从机顶层模块、控制模块、接口模块暂无!!!
四、项目 仿真
1、对spi主机进行仿真
`timescale 1 ns/1 ns
module master_tb();
//时钟 复位
reg clk ;
reg rst_n ;
//激励信号
reg write ;
reg read ;
reg miso ;
//输出
wire cs_n ;
wire mosi ;
wire sclk ;
//参数定义
parameter CYCLE = 20;
parameter RST_TIME = 3 ;
//模块例化
spi_master u_master(
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*input */.write (write ),
/*input */.read (read ),
/*output */.cs_n (cs_n ),
/*output */.sclk (sclk ),
/*output */.mosi (mosi ),
/*input */.miso (miso )
);
//产生时钟
initial begin
clk = 1;
forever #(CYCLE/2)
clk=~clk;
end
//产生复位
initial begin
rst_n = 1;
#2;
rst_n = 0;
#(CYCLE*RST_TIME);
rst_n = 1;
end
//产生写、读请求
initial begin
#3;
write = 0 ;
read = 0 ;
#(10*CYCLE);
repeat (2) begin //写测试
write = 1'b1;
#(1*CYCLE);
write = 1'b0;
@(posedge u_master.u_ctrl.data2idle) //写完
#(10*CYCLE);
end
repeat (2) begin //读测试
read = 1'b1;
#(1*CYCLE);
read = 1'b0;
@(posedge u_master.u_ctrl.data2idle) //读完
#(10*CYCLE);
end
#(100*CYCLE);
$stop;
end
//miso从机输入
initial begin
#1;
miso = 0 ;
#(10*CYCLE);
forever #(20*CYCLE)
miso = {
$random};
end
endmodule
边栏推荐
- (CVPR - 2017) in depth and potential body learning context awareness feature for pedestrian recognition
- Talk about the understanding of RPC
- 【Yugong Series】August 2022 Go Teaching Course 036-Type Assertion
- 21 Day Learning Challenge Week 1 Summary
- UNI-APP_iphone苹果手机底部安全区域
- 大马驮2石粮食,中马驮1石粮食,两头小马驮一石粮食,要用100匹马,驮100石粮食,如何分配?
- 电商项目——商城限时秒杀功能系统
- Qnet Weak Network Test Tool Operation Guide
- 轮转数组问题:如何实现数组“整体逆序,内部有序”?“三步转换法”妙转数组
- 获取链表长度
猜你喜欢

Idea (preferred) cherry-pick operation

DNS separation resolution and intelligent resolution

Qnet弱网测试工具操作指南

What should I do if the channel ServerID is incorrect when EasyCVR is connected to a Hikvision Dahua device and selects another cluster server?

rac备库双节点查询到的表最后更新时间不一致

互换性测量技术-几何误差

leetcode: 358. Reorder strings at K distance intervals

A Practical Arrangement of Map GIS Development Matters (Part 1)

STC8H开发(十五): GPIO驱动Ci24R1无线模块

互换性测量与技术——偏差与公差的计算,公差图的绘制,配合与公差等级的选择方法
随机推荐
STC8H development (15): GPIO drive Ci24R1 wireless module
输入起始位置,终止位置截取链表
程序化交易与主观交易对盈利曲线的影响!
EasyCVR接入GB28181设备时,设备接入正常但视频无法播放是什么原因?
E-commerce project - mall time-limited seckill function system
添加用户报错useradd: cannot open /etc/passwd
【LeetCode】Day112-repetitive DNA sequence
[ADI low-power 2k code] Based on ADuCM4050, ADXL363, TMP75 acceleration, temperature detection and serial port printing, buzzer playing music (lone warrior)
广州纸质发票再见!开住宿费电子发票即将全面取代酒店餐饮加油站发票
MongoDB 基础了解(二)
【LeetCode】Day112-重复的DNA序列
What is third-party payment?
【愚公系列】2022年08月 Go教学课程 035-接口和继承和转换与空接口
I didn't expect MySQL to ask these...
uni-app - 城市选择索引列表 / 通过 A-Z 排序的城市列表(uview 组件库 IndexList 索引列表)
Roewe imax8ev cube battery security, what blackening and swelling are hidden behind it?
font
When EasyCVR is connected to the GB28181 device, what is the reason that the device is connected normally but the video cannot be played?
学编程的第十三天
C language recv() function, recvfrom() function, recvmsg() function