基于AM335X与FPGA的SPI通讯设计
在2013年的工作中,涉及到了AM3359与XC7K325T之间的相互通信,其目的是为了获取FPGA设计版本号,该FPGA版本号保存在FPGA的寄存器0xFFFF中,FPGA的版本值随着加载程序发生变化,当时的版本信息为0x1003.
需要说明的是,在本文中的代码风格是刚工作两年的时候的代码风格,现在回看,这些代码风格实在难以阅读。尤其是SPI的verilog程序等。并不代表现在的编程水平与代码风格。
设计框图如下:
本文主要从三个方面介绍AM3359与FPGA的通讯
第一部分:SPI通信的基础定义
第二部分:AM335x的SPI通信编程
第三部分:FPGA从机SPI设计
SPI通信基础定义
通信是怎么发生
SPI接口是一种典型的全双工接口。通过同步时钟SCLK的脉冲将数据一位一位地在主机和从机之间交换。所以在开始通信之前,Master首先需要配置接口时钟,在Master配置时钟的时候,不需要通知从机它所配置的时钟频率具体是多少,设计人员只需要确保通讯频率是从机所支持的即可。
当Master通过片选信号(SS,低电平有效)选定一个Slave的时候,每向Slave发送一个周期的SCLK信号,都会有1bit的数据从MOSI引脚发送给Slave,Slave只需要在对应的引脚接收数据即可;同时,slave每收到一个周期的SCLK信号,都会从MISO想Master发送1bit的数据。
从上段描述中可以分析出一下两点:
1.无论Master还是Slave,都是按照bit来传输的,那么对于需要发送或者接收的数据,必须在Master或Slave中有一个移位寄存器,这些是由硬件来保证的,普通的SPI接口设计者不需要考虑移位寄存器的因素。
2.在通信中,Master发送数据后,一般需要保证Slave收到数据,这样才能确定数据在收发的过程中不发生因为硬件而导致的bit丢失。在SPI中,数据传输以“位交换”的方式传输,这样能根据从机返回的数据来确定从机已经收到数据了。SPI同样与其他基础通信方式(USB,I2C,UART等)一样无法确保传输数据的正确性。
SPI是一个相对比较开放的接口,具体表现在时钟极性/相位、帧大小、传输速度、LSB/MSB等规则没有一个确定的定义,需要根据不同的通信环境由设计开发者进行定义。
SPI的接口时序
在实际开发使用SPI的时候,需要注意使Master和Slave处于相同的Mode下工作。不同mode的定义主要是针对时钟的相关特性。
SCLK极性(CPOL):clock Polarity
SCLK相位(CPHA):clock Phase
CPOL
在解释CPOL之前先要介绍什么是SCLK的空闲时刻。在SPI通讯传输的时候,SCLK并不是时刻都有。在SCLK发送数据之前和发送数据之后,都会回到空闲状态,这个状态下,SCLK要么保持在高电平,要么保持在低电平。这个是需要设计者来指定的,CPOL的作用就是来指定SPI在IDLE状态下的点评状态。
- CPOL = 0 :时钟空闲状态(IDLE)的电平为低电平(0)。
- CPOL = 1 :时钟空闲状态(IDLE)的点评是高电平(1)。
CPHA
CPHA表示数据采样,数据有效的时刻。对应的数据采样是在第几个边沿进行采样。
- CPHA = 0 :在时钟第一个边沿采样。
- 对于CPOL = 0 :因为IDLE为低电平,那么第一个边沿就是从低电平到高电平,即为上升沿。
- 对于CPOL = 1 :因为IDLE为高电平,那么第一个边沿就是从高电平到低电平,即为下降沿。
- CPHA = 1 :在时钟第二个边沿采样。
- 对于CPOL = 0 :因为IDLE为低电平,那么第二个边沿就是从高电平到低电平,即为下降沿。
- 对于CPOL = 1 :因为IDLE为高电平,那么第二个边沿就是从低电平到高电平,即为上升沿。
需要注意的是:采样一定是需要先准备好数据,才用时钟的有效沿将数据打到对应的引脚上。
Mode选择参考
SPI没有一个通用的推荐模式,但是基于工程设计的时候是否有一个推荐的Mode选择呢?在****博友的一篇《SPI接口扫盲 SPI定义/SPI时序(CPHA CPOL)》中,作者从功耗的角度分析,建议应该多选择SCLK在空闲状态下处于低电平,即CPOL保持在IDLE状态下为0。这是一个很好的分析方法。对于CPHA的选择分析,我更赞成根据实际的应用来做设计,而不是根据习惯来设计。
AM335x的SPI通信编程
在本工程中所使用的SPI为AM335x的SPI外设,即在Linux下,只需要对spidev进行文件操作。所用到的文件操作函数有以下四个:
open()
write()
read()
close()
相关的函数说明,请参考网络,在此不赘述。
在编写SPI驱动的时候,还需用到ioctl()函数,下面对ioctl做一些简要介绍。
什么是ioctl
ioctl是设备驱动程序中对设备的IO通道进行管理的函数。即是对设备的一些特性进行控制,例如对串口设备设置波特率,对SPI设备设置字长,通讯速率等。函数的调用方式如下:
- int ioctl(int fd,unsigned long cmd,...);
- /*
- fd:文件描述符
- cmd:控制命令
- ...:可选参数:插入*argp,具体内容依赖于cmd
- */
其中fd是用户程序打开设备是使用open()函数返回的文件标识符(句柄),cmd是用户程序对设备的控制命令,后面的省略号,表示该函数可能还有其他参数,该参数与cmd相关。
对于ioctl的详细描述,可以参考文末链接1来详细阅读。
AM335x驱动程序源码设计
- static uint8_t mode = 0;
- static uint8_t bits = 8;
- static uint32_t speed = 16000000;
- static uint8_t cs = 1;
- int fpga_fd = 0;
- uint8_t file_name_buf[FILE_NAME_MAX] = "/dev/spidev2.1";
- //==
- int fpga_config(uint8_t *file_name)
- {
- if (strlen(file_name) > FILE_NAME_MAX)
- {
- printf("File name length error\r\n");
- return 0;
- }
- strcpy(file_name_buf, file_name);
- return 1;
- }
- int fpga_spi_open(uint8_t *file_name)
- {
- int ret = 0;
- fpga_fd = open(file_name, O_RDWR);
- if (fpga_fd < 0)
- {
- printf("in fpga_spi_open, can't open device\r\n");
- return nQAM_ERROR_CAS_SPI_OPEN;
- }
- else
- {
- printf("in fpga_spi_open, open device success\r\n");
- }
- /*
- * spi mode
- */
- ret = ioctl(fpga_fd, SPI_IOC_WR_MODE, &mode);
- if (ret == -1)
- {
- printf("in fpga_spi_open, can't set spi mode\r\n");
- return nQAM_ERROR_CAS_SPI_CONFIG;
- }
- else
- {
- printf("in fpga_spi_open, set spi mode success\r\n");
- }
- /*
- * bits per word
- */
- ret = ioctl(fpga_fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
- if (ret == -1)
- {
- printf("in fpga_spi_open, can't set bits per word\r\n");
- return nQAM_ERROR_CAS_SPI_CONFIG;
- }
- else
- {
- printf("in fpga_spi_open, set bits per word success\r\n");
- }
- /*
- * max speed hz
- */
- ret = ioctl(fpga_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
- if (ret == -1)
- {
- printf("in fpga_spi_open, can't set max speed hz\r\n");
- return nQAM_ERROR_CAS_SPI_CONFIG;
- }
- else
- {
- printf("in fpga_spi_open, set max speed success\r\n");
- }
- /*
- * chip select
- */
- ret = ioctl(fpga_fd,SPI_IOC_WR_CHIP_SELECT,&cs);
- if(ret == -1)
- {
- printf("in fpga_spi_configure,can't set chip select\r\n");
- return nQAM_ERROR_CAS_SPI_CONFIG;
- }
- return nQAM_ERROR_NOERROR;
- }
- int fpga_spi_close()
- {
- return close(fpga_fd); // close device
- }
- //数据交换
- uint8_t transfer(uint8_t data)
- {
- uint8_t sbuf = data;
- if(write(fpga_fd,&sbuf,1) != -1)
- {
- if(read(fpga_fd, &sbuf, 1) != -1)
- {
- printf("In transfer, transfer data over\r\n");
- return sbuf;
- }
- else
- {
- printf("In transfer, read data from spi device failed!\r\n");
- }
- }
- else
- {
- printf("In transfer, write data to spi device failed!\r\n");
- }
- return 0;
- }
- //通过SPI获取版本号
- uint8_t get_fpga_version()
- {
- uint8_t buf;
- if(fpga_spi_open(file_name_buf) != nQAM_ERROR_NOERROR)
- {
- printf("in get_fpga_vesion, fpga spi open failed\r\n");
- return 0;
- }
- buf = transfer(READ_VESION);
- fpga_spi_close();
- printf("Read version done.\r\n");
- return buf;
- }
- //测试物理连接状态
- uint8_t test_fpga_connect()
- {
- uint8_t buf;
- if(fpga_spi_open(file_name_buf) != nQAM_ERROR_NOERROR)
- {
- printf("in test_spi_open, fpga spi open failed\r\n");
- return 0;
- }
- buf = transfer(TEST_CONNECT);
- fpga_spi_close();
- printf("FPGA spi test connect done.\r\n");
- return buf;
- }
附:spidev的ioctl命令。
SPI_IOC_RD_MODE: 读取spi_device的mode。
SPI_IOC_RD_LSB_FIRST: 如果是SPI_LSB_FIRST的方式则返回1。
SPI_IOC_RD_BITS_PER_WORD: 读取spi_device的bits_per_word.
SPI_IOC_RD_MAX_SPEED_HZ: 读取spi_device的max_speed_hz.
SPI_IOC_WR_MODE: 设置spi_device的mode,并调用spi_setup立即使设置生效。
SPI_IOC_WR_LSB_FIRST: 设置spi使用SPI_LSB_FIRST的传输模式。立即生效。
SPI_IOC_WR_BITS_PER_WORD: 读取字长。
SPI_IOC_WR_MAX_SPEED_HZ: 设置时钟速率。
AM335x应用程序设计
- int main(int argc, char *argv )
- {
- uint8_t connect;
- uint8_t version;
- connect = test_fpga_connect();
- printf("Test Value is %02X\r\n", connect); //测试连接状态
- version = get_fpga_version();
- printf("the fpga version is %02X\r\n", version);
- printf("****fpga app test run over!****\r\n");
- return 1;
- }
FPGA从机SPI设计
SPI从机的Verilog实现
- module spi_slave(clk,rst,data_i,wr_en_i,data_o,tx_valid,valid_o,start_o,end_o,we_ack_o,ss_i,sclk_i,mosi_i,miso_o
- );
- input clk; // master clock input
- input rst; // synchronous active high reset
- input [7:0] data_i; // data bus input
- input wr_en_i; // write enable input
- output [7:0] data_o; // data bus output
- output valid_o; // request signal output
- output tx_valid;
- output start_o;
- output end_o;
- output reg we_ack_o;
- //spi signals
- input ss_i; // slave select
- input sclk_i; // serial clock
- input mosi_i;
- output miso_o;
- reg [7:0] tx_data;
- reg [7:0] rx_data;
- reg tx_tip; //tx in progress
- reg rx_tip; //rx in progress
- wire rx_negedge; //miso is sampled on negative edge
- wire tx_negedge; //mosi is driven on nesedge edge
- wire [2:0] len; //char length
- wire lsb; //lsb first on line
- wire pos_edge; //recognize posedge of sclk
- wire neg_edge; //recognize negdege of sclk
- reg s_out;
- reg s_in;
- reg [2:0] s_sel;
- reg [2:0] s_clk;
- assign rx_negedge = 0; // decided by CPOL and CPHA
- assign tx_negedge = 1; // means mode == 00
- assign len = 7;
- assign lsb = 0;
- assign miso_o = s_out;
- assign valid_o = rst? 1'b0 : (!rx_tip);
- assign data_o = rst? 8'h00 : rx_data;
- //sync SCK to the FPGA clock using a 3-bits shift register
- always @ (posedge clk)
- begin
- s_clk <= {s_clk[1:0], sclk_i}; //sample the sclk_i using clk,when finding the first posedge the value is 001,when finding the first negedge the value is 110 or 010
- end
- assign pos_edge = (s_clk[1:0] == 2'b01); // posedge when s_clk[1:0] == 2'b01 or s_clk[2:0] = 3'b001
- assign neg_edge = (s_clk[1:0] == 2'b10); // negedge when s_clk[1:0] == 2'b10 or s_clk[2:0] = 3'b110
- //SSEL
- always @ (posedge clk)
- begin
- s_sel <= {s_sel[1:0], ss_i}; //sample the ss signal
- end
- wire sel_active; // from start_o is high to end_o is high sel_active is active
- assign sel_active = ~s_sel[1]; // sel[2:0] = 000 001 011 111 110 100 000 when 100 000 001 .0 is high
- assign start_o = (s_sel[1:0] == 2'b10); // start_o when s_sel[1:0] = 2'b10 or s_sel[2:0]= 3'b110
- assign end_o = (s_sel[2:1] == 2'b01); // end_o when s_sel[2:1] = 2'b01 or s_sel[2:0] = 3'b011
- //---------------------receiving bits from line---------------------
- wire rx_clk;
- wire rx_lst_bit;
- reg [2:0] rx_cnt; // rx data bit count
- assign rx_clk = rx_negedge? neg_edge : pos_edge; // question is the beginning value is "X"
- assign rx_lst_bit = !(|rx_cnt);
- always @(posedge clk)
- begin
- s_in <= mosi_i;
- end
- always @(posedge clk or posedge rst)
- begin
- if (rst)
- rx_cnt <= len;
- else begin
- if(!rx_tip || end_o || start_o)
- rx_cnt <= len;
- else
- rx_cnt <= rx_clk ? (rx_cnt - 1'b1) : rx_cnt; //question is the rx_cnt always is 7?
- end
- end
- //Transfer in process
- always @(posedge clk or posedge rst)
- begin
- if(rst)
- rx_tip <= 1'b0;
- else if(!rx_tip)
- rx_tip <= 1'b1;
- else if(rx_tip && rx_lst_bit && rx_clk)
- rx_tip <= 1'b0;
- end
- always @(posedge clk or posedge rst)
- begin
- if(rst)
- rx_data <= {8{1'b0}};
- else begin
- if(sel_active && rx_clk)
- rx_data <= lsb ? {s_in,rx_data[7:1]} : {rx_data[6:0],s_in}; // if lsb = 0 rx_data = {rx_data[6:0],s_in}
- // if lsb = 1 rx_data = {s_in,rx_data[7:1]}
- end // {s_in,rx_data[7:1]} shift right
- end // {rx_data[6:0],s_in} shift left
- //---------------------sending bits to line---------------------
- wire tx_clk;
- wire tx_lsb_bit;
- reg [2:0] tx_cnt;
- assign tx_clk = tx_negedge? neg_edge : pos_edge; // tx_negedge = 1 negedge transfer data
- assign tx_lsb_bit = !(|tx_cnt);
- assign tx_valid = ((s_sel[2:0] == 3'b111) && (tx_tip == 1'b0)) ? 1'b1 : 1'b0;
- // always @(posedge clk or posedge rst)
- // begin
- // if(rst)
- // tx_valid <= 1'b0;
- // else if((s_sel[2:0] == 3'b111) && (tx_tip == 1'b0))
- // tx_valid <= 1'b1;
- // else
- // tx_valid <= 1'b0;
- // end
- // character bit counter
- always @(posedge clk or posedge rst)
- begin
- if(rst)
- tx_cnt <= len;
- else begin
- if(!tx_tip || end_o || start_o)
- tx_cnt <= len;
- else
- tx_cnt <= tx_clk? (tx_cnt-1'b1):tx_cnt;
- end
- end
- //transfer in process
- always @(posedge clk or posedge rst)
- begin
- if(rst) begin
- tx_tip <= 1'b0;
- end
- else if(wr_en_i && (!tx_tip)) begin //wr_en_i is high when transfer the data
- tx_tip <= 1'b1;
- end
- else if(tx_tip && tx_lsb_bit && tx_clk) begin
- tx_tip <= 1'b0;
- end
- end
- always @(posedge clk or posedge rst)
- begin
- if(rst) begin
- tx_data <= 8'hff;
- we_ack_o <= 1'b0;
- end
- else begin
- we_ack_o <= 1'b0;
- if(wr_en_i && (!tx_tip)) begin
- tx_data[7:0] <= data_i[7:0];
- we_ack_o <= 1'b1;
- end
- else begin
- if(sel_active && rx_clk) begin
- s_out <= lsb? tx_data[0] : tx_data[7];
- tx_data <= lsb? {1'b1,tx_data[7:1]} : {tx_data[6:0],1'b1};
- end
- end
- end
- end
- endmodule
FPGA的SPI command verilog设计
- `timescale 1 ns / 1 ps
- module spi_cmd #(
- parameter BUS_DATA_WIDTH = 8,
- parameter BUS_ADDR_WIDTH = 16
- )(
- input clk,
- input rst,
- input [BUS_DATA_WIDTH-1:0] rx_data,
- input rx_valid,
- input rx_start,
- input rx_end,
- input tx_valid,
- input tx_ack,
- output reg [BUS_DATA_WIDTH-1:0] tx_data,
- output reg tx_req
- );
- localparam CON_WR_REG = 8'h51;
- localparam CON_RD_REG = 8'h52;
- localparam RD_DATA_REG = 8'h96;
- localparam RST_CMD = 8'h55;
- localparam ADDR_FPGA_VERSION = 16'hFFFF;
- localparam ADDR_SPI_TEST = 16'hF000;
- localparam FPGA_VERSION = 16'h1003;
- localparam REC_NO_ERR = 8'h90;
- localparam REC_INS_ERR = 8'hF1;
- localparam REC_LEN_ERR = 8'hF2;
- localparam REC_BUSY_ERR = 8'hF3;
- localparam REC_WR_ERR = 8'hF4;
- localparam REC_START = 1;
- localparam REC_TYPE_REG = 2;
- localparam REC_ADDR_REGH = 3;
- localparam REC_ADDR_REGL = 4;
- localparam REC_ADDR_LEN = 5;
- localparam REC_CON_WRH = 6;
- localparam REC_CON_WRL = 7;
- localparam RSP_STATUS = 8;
- localparam RSP_LEN = 9;
- localparam RSP_DIN = 10;
- localparam RSP_DOUT = 11;
- localparam READ_DATAH = 12;
- localparam READ_DATAL = 13;
- localparam READ_DONE = 14;
- reg [7:0] rec_type;
- reg [15:0] rec_addr;
- reg [7:0] addr_len;
- reg [7:0] len_count;
- reg [3:0] cmd_state;
- reg [7:0] err_code;
- reg [15:0] rsp_dout;
- reg rsp_rd;
- reg [15:0] spi_test;
- reg [3:0] test_flag;
- [email protected](posedge clk or posedge rst)
- begin
- if(rst) begin
- rec_type <= 0;
- rec_addr <= 0;
- addr_len <= 0;
- len_count <= 0;
- err_code <= REC_NO_ERR;
- tx_data <= 0;
- tx_req <= 0;
- rsp_rd <= 1'b0;
- cmd_state <= REC_START;
- test_flag <= 4'h0;
- end
- else begin
- if(tx_ack) begin
- tx_req <= 1'b0;
- end
- case(cmd_state)
- REC_START : begin
- len_count <= 0;
- if(rx_start) begin
- cmd_state <= REC_TYPE_REG;
- test_flag <= 4'h1;
- end
- end
- REC_TYPE_REG : begin
- if(rx_valid) begin
- case(rx_data)
- CON_WR_REG, CON_RD_REG : begin
- err_code <= REC_NO_ERR;
- rec_type <= rx_data;
- cmd_state <= REC_ADDR_REGH;
- test_flag <= 4'h2;
- end
- RD_DATA_REG : begin
- cmd_state <= RSP_STATUS;
- test_flag <= 4'h6;
- end
- default : cmd_state <= REC_START;
- endcase
- end
- end
- REC_ADDR_REGH : begin
- if(rx_valid) begin
- rec_addr[15:8] <= rx_data;
- cmd_state <= REC_ADDR_REGL;
- test_flag <= 4'h3;
- end
- end
- REC_ADDR_REGL : begin
- if(rx_valid) begin
- rec_addr[7:0] <= rx_data;
- cmd_state <= REC_ADDR_LEN;
- test_flag <= 4'h4;
- end
- end
- REC_ADDR_LEN : begin
- if(rx_valid) begin
- if(rx_data != 0) begin
- addr_len <= rx_data;
- test_flag <= 4'h5;
- case(rec_type)
- CON_WR_REG : cmd_state <= REC_CON_WRH;
- CON_RD_REG : cmd_state <= REC_START;
- default : cmd_state <= REC_START;
- endcase
- end
- else begin
- err_code <= REC_LEN_ERR;
- cmd_state <= REC_START;
- end
- end
- end
- REC_CON_WRH : begin
- if(len_count == addr_len) begin
- cmd_state <= REC_START;
- end
- else begin
- if(rx_valid) begin
- if(rec_addr == ADDR_FPGA_VERSION) begin
- err_code <= REC_WR_ERR;
- cmd_state <= REC_START;
- end
- else if(rec_addr == ADDR_SPI_TEST) begin
- spi_test[15:8] <= rx_data;
- end
- // else begin
- // rec_buf_data[15:8] <= rx_data;
- // end
- cmd_state <= REC_CON_WRL;
- end
- end
- end
- REC_CON_WRL : begin
- if(rx_valid) begin
- if(rec_addr == ADDR_SPI_TEST) begin
- spi_test[7:0] <= rx_data;
- end
- // else begin
- // rec_buf_data[7:0] <= rx_data;
- // rec_addr <= rec_addr + 1'b1;
- // end
- len_count <= len_count + 1'b1;
- end
- end
- RSP_STATUS : begin
- if(tx_valid) begin
- if(tx_ack) begin
- tx_req <= 1'b0;
- end
- else begin
- tx_req <= 1'b1;
- end
- rsp_rd <= 1'b1;
- tx_data <= err_code;
- cmd_state <= RSP_LEN;
- test_flag <= 4'h7;
- end
- end
- RSP_LEN : begin
- if(tx_valid) begin
- if(tx_ack) begin
- tx_req <= 1'b0;
- end
- else begin
- tx_req <= 1'b1;
- end
- case(rec_type)
- CON_WR_REG : begin
- tx_data <= 0;
- cmd_state <= REC_START;
- end
- CON_RD_REG : begin
- tx_data <= addr_len;
- cmd_state <= RSP_DIN;
- end
- default : begin
- tx_data <= 0;
- cmd_state <= REC_START;
- end
- endcase
- test_flag <= 4'h8;
- end
- end
- RSP_DIN : begin
- if(tx_valid) begin
- if(tx_ack) begin
- tx_req <= 1'b0;
- end
- else begin
- tx_req <= 1'b1;
- end
- if(len_count == addr_len) begin
- cmd_state <= READ_DONE;
- end
- else begin
- cmd_state <= RSP_DOUT;
- rsp_rd <= 1'b0;
- end
- test_flag <= 4'h9;
- end
- end
- RSP_DOUT : begin
- if(tx_valid) begin
- if(tx_ack) begin
- tx_req <= 1'b0;
- end
- else begin
- tx_req <= 1'b1;
- end
- cmd_state <= READ_DATAH;
- end
- end
- READ_DATAH : begin
- if(tx_valid) begin
- if(tx_ack) begin
- tx_req <= 1'b0;
- end
- else begin
- tx_req <= 1'b1;
- end
- tx_data <= rsp_dout[15:8];
- cmd_state <= READ_DATAL;
- test_flag <= 4'hA;
- end
- end
- READ_DATAL : begin
- if(tx_valid) begin
- if(tx_ack) begin
- tx_req <= 1'b0;
- end
- else begin
- tx_req <= 1'b1;
- end
- tx_data <= rsp_dout[7:0];
- len_count <= len_count + 1'b1;
- cmd_state <= RSP_DIN;
- test_flag <= 4'hB;
- end
- end
- READ_DONE : begin
- if(tx_valid) begin
- if(tx_ack) begin
- tx_req <= 1'b0;
- end
- else begin
- tx_req <= 1'b1;
- end
- tx_req <= 1'b0;
- len_count <= 0;
- tx_data <= 0;
- rec_type <= 0;
- cmd_state <= REC_START;
- end
- end
- endcase
- end
- end
- always @(posedge clk or posedge rst)
- begin
- if(rst == 1'b1)
- begin
- rsp_dout <= {16{1'b0}};
- end
- else if((rsp_rd == 1'b1) && (test_flag == 4'h7)) begin
- case(rec_addr)
- ADDR_FPGA_VERSION : rsp_dout <= FPGA_VERSION;
- ADDR_SPI_TEST : rsp_dout <= ~spi_test;
- default : ;
- endcase
- end
- end
- endmodule