当前位置:网站首页>【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
边栏推荐
- Is there any way for kingbaseES to not read the system view under sys_catalog by default?
- Google search skills - programmer is recommended
- The 125th day of starting a business - a note
- console.log alternatives you didn't know about
- STC8H开发(十五): GPIO驱动Ci24R1无线模块
- Detailed explanation of VIT source code
- 添加用户报错useradd: cannot open /etc/passwd
- Unity2D animation (1) introduction to Unity scheme - animation system composition and the function of use
- [Pdf generated automatically bookmarks]
- Idea (优选)cherry-pick操作
猜你喜欢
Official release丨VS Code 1.70
互换性测量技术-几何误差
音视频开发,为什么要学习FFmpeg?应该怎么入手FFmpeg学习?
[idea error] Invalid target distribution: 17 solution reference
QueryDet:级联稀疏query加速高分辨率下的小目标检测
云平台下ESB产品开发步骤说明
Homework 8.10 TFTP protocol download function
A Practical Arrangement of Map GIS Development Matters (Part 1)
没想到MySQL还会问这些...
Ten Advanced Concepts of SQL Development
随机推荐
oracle的基数会影响到查询速度吗?
[yu gong series] Go program 035-08 2022 interfaces and inheritance and transformation and empty interface
音频编解码,利用FAAC来实现AAC编码
CSDN 博客更换皮肤
分布式和集群的区别和联系
A practice arrangement about map GIS (below) GIS practice of Redis
rac备库双节点查询到的表最后更新时间不一致
"How to kick a bad habit to read notes?
Paper Accuracy - 2017 CVPR "High-Resolution Image Inpainting using Multi-Scale Neural Patch Synthesis"
怎么删除语句审计日志?
QueryDet: Cascading Sparse Query Accelerates Small Object Detection at High Resolution
Goodbye Guangzhou paper invoices!The issuance of electronic invoices for accommodation fees will completely replace the invoices of hotels, restaurants and gas stations
音视频开发,为什么要学习FFmpeg?应该怎么入手FFmpeg学习?
7 sorting algorithms that are often tested in interviews
C语言之自定义类型------结构体
荣威imax8ev魔方电池安全感,背后隐藏着哪些黑化膨胀?
Summary of debugging skills
What problems should we pay attention to when building a programmatic trading system?
Typescript study notes | Byte Youth Training Notes
作业8.10 TFTP协议 下载功能