一、SPI協議介紹
二、程序設計
1、spi_slave模塊
- 該模塊接收8路16bit的數據信號
ave1---ave8
,以及標志數據有效的信號ave_valid
; - 該模塊作為SPI的slave端,可以通過
spi_miso
將ave數據發送出去;也可以通過spi_mosi
接收master端發送來的數據,並將數據再通過godata
發送出去; - 該模塊采用的是
模式0:CPOL = 0,CPHA = 0
; - 該模塊可以接收兩種命令:讀命令
COMMAND_READ = 8'hA5
、寫命令COMMAND_WRITE = 8'H5A
;
`timescale 1ns/1ps
module spi_slave(
input clk,//芯片外部輸入的clk_50m
input rst_n,//sys_rst模塊輸出的復位信號rst_n
input ave_valid,//average輸出的平均值有效信號
//spi_input_chose模塊的輸出信號,
//sw_cnt控制spi_input_chose模塊選擇特定的數據輸出給spi_slave
input [15:0] ave1,
input [15:0] ave2,
input [15:0] ave3,
input [15:0] ave4,
input [15:0] ave5,
input [15:0] ave6,
input [15:0] ave7,
input [15:0] ave8,
//spi協議的相關信號
input spi_cs,
input spi_sck,
input spi_mosi,
output reg spi_miso,//spi slave的數據輸出
//下面3個信號是連接到para_rom1模塊的,
//和para_rom1模塊輸出兩點校正的兩個參數G\O有關
output reg data_valid,
output [4:0] addr,
output reg [15:0] godata,
//spi初始化完成標志
output reg init_finish
);
// cs、sck、mosi的delay信號
reg spi_cs_2,spi_cs_1;
reg spi_sck_2,spi_sck_1;
reg spi_mosi_2,spi_mosi_1;
// cs、sck的下降沿/mosi的上升沿
wire spi_cs_neg;
wire spi_sck_neg;
wire spi_sck_pos;
wire spi_mosi_flag;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
{spi_cs_2,spi_cs_1} <= 2'b11;
{spi_sck_2,spi_sck_1} <= 2'b00;
{spi_mosi_2,spi_mosi_1} <= 2'b00;
end
else
begin
{spi_cs_2,spi_cs_1} <= {spi_cs_1,spi_cs};
{spi_sck_2,spi_sck_1} <= {spi_sck_1,spi_sck};
{spi_mosi_2,spi_mosi_1} <= {spi_mosi_1,spi_mosi};
end
end
assign spi_cs_neg = (spi_cs_2&(~spi_cs_1));
assign spi_sck_neg = ~spi_sck_1&spi_sck_2;
assign spi_sck_pos = ~spi_sck_2&spi_sck_1;
assign spi_mosi_flag = spi_mosi_2;
localparam IDLE = 6'b000001;
localparam RXD_COM = 6'b000010;
localparam JUDGE = 6'b000100;
localparam TXD_NUM = 6'b001000;
localparam TXD_DATA = 6'b010000;
localparam RXD_PARA = 6'b100000;
parameter COMMAND_READ = 8'hA5;
parameter COMMAND_WRITE = 8'H5A;
reg [5:0] state;
reg [3:0] cnt; //計數接收命令參數的位數
reg [3:0] cnt0; //發送數據的地址
reg [4:0] cnt1; //計數發送數據的位數
reg [4:0] cnt2; //計數接收兩點校正參數的位數
reg [4:0] cnt3; //cnt2和godata輸出到的存儲參數的存儲器的地址有關
reg [7:0] para;
reg [15:0] tdata;
reg rd_flag;
wire [15:0] data_out;
reg rxd_finish;
reg rxd_finish_en;
reg [7:0] counter;
assign addr = cnt2 - 1;
always @(posedge clk or negedge rst_n)
if(!rst_n)
state <= IDLE;
else
begin
case(state)
IDLE:
begin
if(spi_cs_neg)
state <= RXD_COM;
else
state <= IDLE;
end
RXD_COM:
begin
if(cnt == 4'b1000)
state <= JUDGE;
else
state <= RXD_COM;
end
JUDGE:
begin
if(para == COMMAND_READ)
state <= TXD_NUM;
else if(para == COMMAND_WRITE)
state <= RXD_PARA;
else
state <= IDLE;
end
TXD_NUM:
begin
state <= TXD_DATA;
end
TXD_DATA:
begin
//每發送完8個ave數據回到idle狀態
if(cnt0 == 4'b1000 && cnt1 == 5'b00001)
state <= IDLE;
//每發送完一個ave數據,就進入TXD_NUM狀態
//此狀態更新一次新的要發送的ave
else if(cnt1 == 5'b10000)
state <= TXD_NUM;
else
state <= TXD_DATA;
end
RXD_PARA:
begin
if(cnt2 < 5'b10110)
state <= RXD_PARA;
else
state <= IDLE;
end
default: state <= IDLE;
endcase
end
always @(posedge clk or negedge rst_n)
if(!rst_n)
begin
cnt <= 4'b0;
cnt0 <= 4'b0;
cnt1 <= 5'b0;
para <= 8'b0;
tdata <= 16'b0;
rd_flag <= 1'b0;
spi_miso <= 1'b1;
data_valid <= 1'b0;
cnt3 <= 5'b0000;
cnt2 <= 5'b0000;
godata <= 16'b0;
rxd_finish <= 1'b0;
end
else
begin
case(state)
IDLE:
begin
cnt <= 4'b0;
cnt0 <= 4'b0;
cnt1 <= 5'b0;
para <= 8'b0;
tdata <= 16'b0;
godata <= 16'b0;
rd_flag <= 1'b0;
spi_miso <= 1'b1;
cnt3 <= 5'b0000;
cnt2 <= 5'b0000;
data_valid <= 1'b0;
end
RXD_COM://接收命令參數
begin
if(cnt == 4'b1000)
cnt <= 4'b0000;
else if(spi_sck_pos)//上升沿接收數據
begin
//接收命令參數存入para 也即是寫COMMAND_WRITE還是讀COMMAND_READ
cnt <= cnt + 4'b0001;
//從高位到低位接收
para[7 - cnt[2:0]] <= spi_mosi_flag;
end
else
begin
cnt <= cnt;
para <= para;
end
end
JUDGE:
begin
if(para == COMMAND_READ)
//識別到讀ave數據的命令COMMAND_READ,
//rd_flag拉高,直到讀完8個數據再拉低
rd_flag <= 1'b1;
else
rd_flag <= 1'b0;
end
TXD_NUM:
begin
tdata <= data_out;
end
TXD_DATA://發送數據
begin
if(cnt1 == 5'b10000)
begin
//每發送完一個數據,cnt0+1,cnt0作為地址讀取下一個要發送的數據
cnt1 <= 5'b00000;
cnt0 <= cnt0 + 4'b0001;
end
else if(spi_sck_neg)//下降沿發送數據
begin
cnt1 <= cnt1 + 5'b00001;
//從高位到低位發送
spi_miso <= tdata[15 - cnt1[4:0]];
end
else
begin
spi_miso <= spi_miso;
cnt1 <= cnt1;
end
end
RXD_PARA://接收兩點校正的參數
begin
//這里表示每接收22個兩點校正的參數拉高rxd_finish
if(cnt2 == 5'b10101 && cnt3 == 5'b01111)
rxd_finish <= 1'b1;
else
rxd_finish <= 1'b0;
//接收完一個16位的參數,data_valid拉高,cnt2 + 1,
//cnt2和godata輸出到的存儲參數的存儲器的地址有關
if(cnt3 == 5'b10000)
begin
cnt3 <= 5'b0000;
cnt2 <= cnt2 + 5'b00001;
data_valid <= 1'b1;
end
else if(spi_sck_pos)//上升沿接收數據
begin
data_valid <= 1'b0;
cnt3 <= cnt3 + 5'b00001;
godata[15 - cnt3[4:0]] <= spi_mosi_flag;
end
else
begin
data_valid <= data_valid;
cnt3 <= cnt3;
godata <= godata;
end
end
default:
begin
cnt <= 4'b0000;
cnt0 <= 4'b0000;
cnt1 <= 5'b00000;
para <= 8'b0;
tdata <= 12'b0;
rd_flag <= 1'b0;
spi_miso <= 1'b1;
end
endcase
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
rxd_finish_en <= 1'b0;
else if (rxd_finish)
rxd_finish_en <= 1'b1;
else
rxd_finish_en <= rxd_finish_en;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
counter <= 8'b0;
//兩點校正參數接收完成之后counter再計數一段時間,最后初始化完成
else if (rxd_finish_en && counter < 8'b11111111)
counter <= counter + 1'b1;
else
counter <= counter;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
init_finish <= 1'b0;
else if (counter == 8'b11111111)
init_finish <= 1'b1;
else
init_finish <= init_finish;
end
ave8_rom ave8_rom (
.clk(clk),
.rst_n(rst_n),
.rd(rd_flag),
.addr(cnt0),
.ave1_in(ave1),
.ave2_in(ave2),
.ave3_in(ave3),
.ave4_in(ave4),
.ave5_in(ave5),
.ave6_in(ave6),
.ave7_in(ave7),
.ave8_in(ave8),
.ave_valid(ave_valid),
.ave_out(data_out)
);
endmodule
該模塊的狀態機有六個狀態:
localparam IDLE = 6'b000001;
localparam RXD_COM = 6'b000010;
localparam JUDGE = 6'b000100;
localparam TXD_NUM = 6'b001000;
localparam TXD_DATA = 6'b010000;
localparam RXD_PARA = 6'b100000;
分別是:
- 空閑狀態
IDLE
- 接收命令狀態
RXD_COM
- 判斷命令是讀還是寫的狀態
JUDGE
- 讀取要發送的ave數據的狀態
TXD_NUM
- 發送ave數據的狀態
TXD_DATA
- 接收數據的狀態
RXD_PARA
以下五個計數變量的意思:
reg [3:0] cnt; //計數接收命令參數的位數
reg [3:0] cnt0; //發送數據的地址
reg [4:0] cnt1; //計數發送數據的位數
reg [4:0] cnt2; //計數接收兩點校正參數的位數
reg [4:0] cnt3; //cnt2和godata輸出到的存儲參數的存儲器的地址有關
下面代碼可以看出是下降沿發送數據:
else if(spi_sck_neg)//下降沿發送數據
begin
cnt1 <= cnt1 + 5'b00001;
//從高位到低位發送
spi_miso <= tdata[15 - cnt1[4:0]];
end
下面代碼可以看出是上升沿接收數據:
else if(spi_sck_pos)//上升沿接收數據
begin
//接收命令參數存入para 也即是寫COMMAND_WRITE還是讀COMMAND_READ
cnt <= cnt + 4'b0001;
//從高位到低位接收
para[7 - cnt[2:0]] <= spi_mosi_flag;
end
2、ave8_rom模塊
`timescale 1ns/1ps
/* 該模塊在spi_slave1模塊里面被例化,輸出的ave_out會被spi slave發給master */
module ave8_rom (
clk,//時鍾
rst_n,//復位信號
rd,//讀使能
addr,//讀地址
ave_valid,//平均值有效信號
ave1_in,//輸入的8個通道的圖像數據平均值
ave2_in,//這是直接由平均值計算模塊輸入的,和spi沒關系
ave3_in,
ave4_in,
ave5_in,
ave6_in,
ave7_in,
ave8_in,
ave_out//平均值輸出,其實是被外部的spi master讀取的
);
input clk;
input rst_n;
input rd;
input [3:0] addr;
input ave_valid;
input [15:0] ave1_in;
input [15:0] ave2_in;
input [15:0] ave3_in;
input [15:0] ave4_in;
input [15:0] ave5_in;
input [15:0] ave6_in;
input [15:0] ave7_in;
input [15:0] ave8_in;
output [15:0] ave_out;
reg [15:0] ave_table [7:0];
assign ave_out = rd ? ave_table[addr] : 16'b1;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
ave_table[7] <= 16'b0000_0000_0000_1000;
ave_table[6] <= 16'b0000_0000_0000_0111;
ave_table[5] <= 16'b0000_0000_0000_0110;
ave_table[4] <= 16'b0000_0000_0000_0101;
ave_table[3] <= 16'b0000_0000_0000_0100;
ave_table[2] <= 16'b0000_0000_0000_0011;
ave_table[1] <= 16'b0000_0000_0000_0010;
ave_table[0] <= 16'b0000_0000_0000_0001 ;
end
else if (ave_valid && rd ==1'b0 )
begin
ave_table[7] <= ave8_in;
ave_table[6] <= ave7_in;
ave_table[5] <= ave6_in;
ave_table[4] <= ave5_in;
ave_table[3] <= ave4_in;
ave_table[2] <= ave3_in;
ave_table[1] <= ave2_in;
ave_table[0] <= ave1_in;
end
else
begin
ave_table[7] <= ave_table[7];
ave_table[6] <= ave_table[6];
ave_table[5] <= ave_table[5];
ave_table[4] <= ave_table[4];
ave_table[3] <= ave_table[3];
ave_table[2] <= ave_table[2];
ave_table[1] <= ave_table[1];
ave_table[0] <= ave_table[0];
end
end
endmodule