本實驗講究實用性,故設計思想為:主機先向從機發送地址,若是向從機寫入數據,則向從機發送數據,若是讀取從機數據,則向從機發送時鍾,然后在時鍾下降沿讀取數據即可。cs信號上升沿作為SPI通信的結束信號。rom程序只是做測試使用。
每次發送16個時鍾信號,前八個是地址和命令,后八個是數據。其中:前8個時鍾接受的數據的最高位決定着這次通信是讀取數據還是寫入數據,最高位為1,則是讀取數據,為0則是寫入數據。
程序:

/********************************Copyright************************************** **----------------------------File information-------------------------- ** File name :spi_slave_2.v ** CreateDate :2015.004 ** Funtions :spi通信試驗。FPGA作為從機,與主機進行通信。先接收主機發來的地址,再根據地址最高位來判斷是讀數據還是些數據, 然后從機是接收數據還是送出數據。地址最高位為高則是讀取數據,否則為寫數據.上升沿接收數據,下降沿發送數據 ** Operate on :M5C06N3L114C7 ** Copyright :All rights reserved. ** Version :V1.0 **---------------------------Modify the file information---------------- ** Modified by : ** Modified data : ** Modify Content: *******************************************************************************/ module spi_slave_2 ( clk, rst_n, spi_cs, spi_sck, spi_miso, spi_mosi, spi_over ); input clk; input rst_n; input spi_cs; input spi_sck; input spi_mosi; output reg spi_miso; output spi_over; //-----------------------------// reg spi_cs_2,spi_cs_1; reg spi_sck_2,spi_sck_1; reg spi_mosi_2,spi_mosi_1; wire spi_cs_pos; wire spi_cs_flag; 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_pos = ~spi_cs_2 &spi_cs_1; assign spi_cs_flag = spi_cs_2; 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; assign spi_over = spi_cs_pos; //----------------------------------------// localparam idel = 4'd0; localparam rxd_addr = 4'd1; localparam jude_wr_rd = 4'd2; localparam rxd_data = 4'd3; localparam rxd_over = 4'd4; localparam txd_data = 4'd5; localparam txd_over = 4'd6; localparam end_sta = 4'd7; reg [3:0] state; reg [3:0] cnt; reg [7:0] raddr; reg [7:0] rdata; reg [7:0] tdata; reg rover_flag; reg wover_flag; reg rd_flag; wire [7:0] data_out; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin state <= 4'd0; cnt <= 0; raddr <= 8'd0; rdata <= 8'd0; tdata <= 8'd0; rover_flag <= 0; wover_flag <= 0; rd_flag <= 0; spi_miso <= 1; end else if(!spi_cs_flag) begin case(state) idel: begin state <= rxd_addr; cnt <= 0; raddr <= 8'd0; rdata <= 8'd0; tdata <= 8'd0; rover_flag <= 0; wover_flag <= 0; rd_flag <= 0; spi_miso <= 1; end rxd_addr: begin if(cnt == 8) begin cnt <= 0; state <= jude_wr_rd; end else if(spi_sck_pos) begin cnt <= cnt + 1; raddr[7 - cnt[2:0]] <= spi_mosi_flag; end end jude_wr_rd: begin if(raddr[7] == 1) state <= rxd_data; else begin state <= txd_data; rd_flag <= 1; end end rxd_data: begin if(cnt == 8) begin cnt <= 0; state <= rxd_over; end else if(spi_sck_pos) begin cnt <= cnt + 1; rdata[7 - cnt[2:0]] <= spi_mosi_flag; end end rxd_over: begin rover_flag <= 1; state <= end_sta; end txd_data: begin tdata <= data_out; if(cnt == 8) begin cnt <= 0; state <= txd_over; end else if(spi_sck_pos) begin cnt <= cnt + 1; spi_miso <= tdata[7 - cnt[2:0]]; end end txd_over: begin wover_flag <= 1; state <= end_sta; end end_sta: begin rover_flag <= 0; wover_flag <= 0; state <= end_sta; end default:state <= 4'd0; endcase end else begin state <= 4'd0; cnt <= 0; raddr <= 8'd0; rdata <= 8'd0; tdata <= 8'd0; rover_flag <= 0; wover_flag <= 0; rd_flag <= 0; spi_miso <= 1; end end data_rom data_rom_1 ( .clk(clk), .rst_n(rst_n), .wr(rover_flag), .rd(rd_flag), .addr(raddr[6:0]), .data_in(rdata), .data_out(data_out) ); endmodule
ROM:

/********************************Copyright************************************** **----------------------------File information-------------------------- ** File name :data_rom.v ** CreateDate :2015.04 ** Funtions : 簡單的數據讀寫存儲程序,配合測試 ** Operate on :M5C06N3L114C7 ** Copyright :All rights reserved. ** Version :V1.0 **---------------------------Modify the file information---------------- ** Modified by : ** Modified data : ** Modify Content: *******************************************************************************/ module data_rom ( clk, rst_n, wr, rd, addr, data_in, data_out ); input clk; input rst_n; input wr; input rd; input [6:0] addr; input [7:0] data_in; output reg [7:0] data_out; reg [7:0] table_1 [7:0]; wire [7:0] table_2 [7:0]; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin table_1[7] <= 0; table_1[6] <= 0; table_1[5] <= 0; table_1[4] <= 0; table_1[3] <= 0; table_1[2] <= 0; table_1[1] <= 0; table_1[0] <= 0; data_out <= 0; end else if(wr) begin table_1[addr] <= data_in; end else if(rd) data_out <= table_1[addr]; else begin table_1[7] <= table_1[7]; table_1[6] <= table_1[6]; table_1[5] <= table_1[5]; table_1[4] <= table_1[4]; table_1[3] <= table_1[3]; table_1[2] <= table_1[2]; table_1[1] <= table_1[1]; table_1[0] <= table_1[0]; data_out <= data_out; end end assign table_2[7] = table_1[7]; assign table_2[6] = table_1[6]; assign table_2[5] = table_1[5]; assign table_2[4] = table_1[4]; assign table_2[3] = table_1[3]; assign table_2[2] = table_1[2]; assign table_2[1] = table_1[1]; assign table_2[0] = table_1[0]; endmodule
測試程序:

/********************************Copyright************************************** **----------------------------File information-------------------------- ** File name :spi_slave_tb.v ** CreateDate :2015.04 ** Funtions :測試文件 ** Operate on :M5C06N3L114C7 ** Copyright :All rights reserved. ** Version :V1.0 **---------------------------Modify the file information---------------- ** Modified by : ** Modified data : ** Modify Content: *******************************************************************************/ `timescale 1 ns/1 ns module spi_slave_tb ; reg clk; reg rst_n; reg spi_cs; reg spi_sck; wire spi_miso; reg spi_mosi; wire spi_over; spi_slave_2 spi_slave_2_1( .clk, .rst_n, .spi_cs, .spi_sck, .spi_miso, .spi_mosi, .spi_over ); parameter tck = 24; parameter t = 1000/tck; always #(t/2) clk = ~clk; //------------------------------- /* 模仿spi主機的發送程序,這個task很好,仿順序操作,可以直觀的顯示過程 */ task spi_sd; input [7:0] data_in; begin #(5*t); spi_sck = 0; spi_mosi= data_in[7]; #(5*t); spi_sck = 1; #(5*t); //send bit[7] spi_sck = 0; spi_mosi= data_in[6]; #(5*t); spi_sck = 1; #(5*t); //send bit[6] spi_sck = 0; spi_mosi= data_in[5]; #(5*t); spi_sck = 1; #(5*t); //send bit[5] spi_sck = 0; spi_mosi= data_in[4]; #(5*t); spi_sck = 1; #(5*t); //send bit[4] spi_sck = 0; spi_mosi= data_in[3]; #(5*t); spi_sck = 1; #(5*t); //send bit[3] spi_sck = 0; spi_mosi= data_in[2]; #(5*t); spi_sck = 1; #(5*t); //send bit[2] spi_sck = 0; spi_mosi= data_in[1]; #(5*t); spi_sck = 1; #(5*t); //send bit[1] spi_sck = 0; spi_mosi= data_in[0]; #(5*t); spi_sck = 1; #(5*t); //send bit[0] spi_sck = 0; end endtask initial begin clk = 0; rst_n = 0; spi_cs = 1; spi_sck = 0; spi_mosi = 1; #(20*t) rst_n = 1; #(10*t); spi_cs = 0; spi_sd(8'h81); #(50*t); spi_sd(8'h04); #(50*t); #(50*t); spi_cs = 1; #(20*t); spi_cs = 0; spi_sd(8'h01); #(50*t); spi_sd(8'h00); #(50*t); spi_cs = 1; end endmodule
仿真圖:
圖中可以看出,第一次輸入8‘h81,意味着向01的地址寫入數據。第二個數8’h04,則是要寫入的數據。然后寫入數據8‘h01,則意味着要讀取01地址的數據,然后發送8個時鍾則是再讀取數據。