FPGA (3) - Realización del protocolo de comunicación SPI basado en FPGA

1. El principio básico de la comunicación SPI

1. Introducción a la comunicación SPI

SPI (Interfaz periférica en serie, Interfaz periférica en serie) es una tecnología de interfaz en serie sincrónica introducida por Motorola. El bus SPI se implementa físicamente mediante un módulo llamado puerto serie síncrono (Synchronous Serial Port) en la unidad de control del microprocesador (MCU) conectado al microcontrolador del dispositivo periférico (PICmicro), que permite que la MCU realice comunicaciones de datos de alta velocidad con varios periféricos. dispositivos en modo serial síncrono full-duplex.

SPI se utiliza principalmente entre EEPROM, Flash, reloj en tiempo real (RTC), convertidor de digital a analógico (ADC), procesador de señal digital (DSP) y decodificador de señal digital. Solo ocupa cuatro pines (Pin) en el chip para control y transmisión de datos, lo que ahorra la cantidad de pines del chip y ahorra espacio para el diseño de PCB. Es precisamente por esta función fácil de usar que cada vez más chips tienen tecnología SPI integrada.

SPI tiene principalmente las siguientes características:

  1. El método de control de modo maestro-esclavo (maestro-esclavo)
    SPI estipula que la comunicación entre dos dispositivos SPI debe ser controlada por el dispositivo maestro (maestro) para controlar el dispositivo esclavo (esclavo).Un dispositivo maestro puede proporcionar reloj y chip de dispositivo esclavo selección (Slave Select) para controlar varios dispositivos Esclavo. El protocolo SPI también estipula que el reloj del dispositivo Esclavo es proporcionado al dispositivo Esclavo por el dispositivo Maestro a través del pin SCK. El dispositivo Esclavo en sí no puede generar o controlar el Reloj. Sin el Reloj, el dispositivo Esclavo no puede funcionar normalmente. .

inserte la descripción de la imagen aquí

  1. Uso del modo síncrono (Synchronous) para transmitir datos
    El dispositivo maestro generará el pulso de reloj correspondiente (Pulso de reloj) de acuerdo con los datos que se van a intercambiar, el pulso de reloj constituye la señal de reloj (Señal de reloj), la señal de reloj pasa la polaridad del reloj (CPOL) y la fase del reloj (CPHA) controla cuándo se intercambian datos entre dos dispositivos SPI y cuándo se muestrean los datos recibidos para garantizar que los datos se transmitan sincrónicamente entre los dos dispositivos.

  2. Intercambios de datos
    La razón por la que la transmisión de datos entre dispositivos SPI también se denomina intercambio de datos se debe a que el protocolo SPI estipula que un dispositivo SPI no puede actuar únicamente como "Transmisor" o "Receptor" en el proceso de comunicación de datos (Receptor)”. cada período de reloj, el dispositivo SPI enviará y recibirá datos de tamaño de bits, lo que significa que el dispositivo ha intercambiado datos de tamaño de bits.Si un dispositivo esclavo puede recibir los datos enviados por el maestro, la señal de control debe poder
    ser accedido por el dispositivo Maestro antes de este (Acceso). Por lo tanto, el dispositivo Maestro primero debe seleccionar el dispositivo Esclavo a través del pin SS/CS, y seleccionar el dispositivo Esclavo al que desea acceder. En el proceso de transmisión de datos En , cada
    recibido los datos deben ser muestreados antes de la próxima transmisión de datos. Si los datos recibidos previamente no se leen, entonces los datos que se han recibido pueden descartarse, lo que resulta en la falla final del módulo físico SPI. Por lo tanto, en el programa, los datos en el dispositivo SPI generalmente se lee después de que el SPI transfiere los datos, incluso si los datos (datos ficticios) son inútiles en nuestro programa.

inserte la descripción de la imagen aquí

2. Reglas de comunicación del módulo maestro-esclavo SPI

La figura es una descripción simple de la comunicación entre dispositivos SPI, expliquemos los varios componentes (Módulo) que se muestran en la figura:

inserte la descripción de la imagen aquí

SSPBUF, búfer de puerto serie síncrono, generalmente se refiere al búfer interno en el dispositivo SPI, generalmente en forma de FIFO físicamente, que almacena datos temporales durante la transmisión;

SSPSR, Synchronous Serial Port Register, generalmente se refiere al registro de desplazamiento (Shift Regitser) en el dispositivo SPI, su función es mover datos dentro o fuera de SSPBUF de acuerdo con el ancho de bits de datos establecido (ancho de bits);

Controlador, generalmente se refiere a los registros de control en el dispositivo SPI, puede configurarlos para establecer el modo de transmisión del bus SPI.

Normalmente, solo necesitamos programar los cuatro pines (pines) descritos anteriormente para controlar la comunicación de datos entre todo el dispositivo SPI:

SCK, Serial Clock, la función principal es transmitir la señal del reloj desde el dispositivo Maestro al dispositivo Esclavo, y controlar el tiempo y la tasa de intercambio de datos;

SS/CS, Selección de esclavo/Selección de chip, utilizado para la selección del chip del dispositivo maestro Dispositivo esclavo, de modo que el dispositivo maestro pueda acceder al dispositivo esclavo seleccionado;

SDO/MOSI, Salida de datos en serie/Salida principal Esclavo de entrada, también conocido como Tx-Channel en el Maestro, como exportación de datos, utilizado principalmente para equipos SPI para enviar datos;

SDI/MISO, Entrada de datos en serie/Entrada maestra Salida esclava, también conocida como Rx-Channel en el maestro, como entrada de datos, utilizada principalmente para que los dispositivos SPI reciban datos;

Durante el proceso de comunicación del dispositivo SPI, se generará un bucle de enlace de datos (Data Loop) entre el dispositivo Maestro y el dispositivo Esclavo, como se muestra en la figura anterior, a través de los pines SDO y SDI, el SSPSR controla los datos que se van a enviar. se mueve dentro y fuera del SSPBUF, el controlador determina el modo de comunicación del bus SPI y el SCK transmite la señal del reloj.

  1. Temporización
    Primero, explique dos conceptos aquí:
    CPOL: polaridad del reloj, que indica si la señal del reloj es alta o baja cuando el SPI está inactivo. Si CPOL está configurado en 1, entonces el dispositivo está debajo del pin SCK cuando está inactivo. El pin SCK es alto. Cuando CPOL se establece en 0, es todo lo contrario.
    CPHA: Fase de reloj, lo que indica que el dispositivo SPI activa el muestreo de datos cuando la señal del reloj en el pin SCK se convierte en un flanco ascendente, o cuando la señal del reloj cambia Activa el muestreo de datos cuando el flanco descendente. Si CPHA se establece en 1, el dispositivo SPI activa el muestreo de datos cuando la señal del reloj se convierte en un flanco descendente y envía datos cuando el flanco ascendente. Cuando CPHA se establece en 0, es todo lo contrario. Este
    ejemplo El modo de transmisión de datos SPI utilizado se establece en CPOL = 1, CPHA = 1. De esta manera, dentro de un período de Reloj, cada dispositivo SPI individual puede enviar y recibir al mismo tiempo en un modo dúplex completo (Full-Duplex ) forma de datos de 1 bit, lo que equivale a intercambiar datos de tamaño de 1 bit. Si el ancho de canal del bus SPI se establece en Byte, significa que la unidad mínima de cada transmisión de datos en el bus SPI es Byte, entonces el dispositivo montado en el bus SPI Cada proceso de transmisión de datos requiere al menos 8 ciclos de reloj (ignorando el retraso físico del dispositivo). Por lo tanto, cuanto más rápida sea la frecuencia del bus SPI y más corto el ciclo de reloj, más rápida será la tasa de intercambio de datos entre dispositivos SPI.

  2. SSPSR
    SSPSR es el registro de desplazamiento (Shift Register) dentro del dispositivo SPI. Su función principal es mover datos dentro o fuera del SSPBUF de acuerdo con el estado de la señal de reloj SPI. El tamaño de los datos que se mueven cada vez está determinado por el Bus-Width y Channel-Width La función de Bus-Width es especificar la unidad
    de transmisión de datos entre el bus de direcciones y el dispositivo maestro.
    Por ejemplo, queremos escribir datos de 16 bytes en el SSPBUF en el dispositivo maestro: Primero, configure el ancho de bus para el registro de configuración del dispositivo maestro. El ancho es byte; luego escriba datos en el registro de desplazamiento Tx-Data del dispositivo maestro en la entrada del bus de direcciones, y escriba datos de 1 byte cada vez (usando la función writeb); después de escribir datos de 1 byte, los datos en el dispositivo maestro El registro de desplazamiento Tx-Data cambiará automáticamente los datos de 1 byte del bus de direcciones al SSPBUF; las acciones anteriores deben repetirse 16 veces. de
    ancho de canal es especificar la unidad de transmisión de datos entre el dispositivo maestro y el dispositivo esclavo. Similar al ancho de bus, el registro de desplazamiento dentro del dispositivo maestro transferirá automáticamente los datos del maestro-SSPBUF al esclavo-SDI pin en el dispositivo Esclavo a través del pin Maestro-SDO de acuerdo con el Ancho del Canal, y luego el Esclavo-SSPSR Cada dato recibido se mueve al Esclavo-SSPBUF. Normalmente, el Ancho del
    Bus siempre será mayor o igual que el Ancho del Canal , para garantizar que la frecuencia de intercambio de datos entre el maestro y el esclavo sea mayor que la del bus de direcciones y el maestro. La frecuencia del intercambio de datos entre ellos debe ser rápida, lo que da como resultado que los datos almacenados en SSPBUF no sean válidos. .

  3. SSPBUF
    Sabemos que en cada ciclo de reloj, los datos intercambiados entre Maestro y Esclavo son en realidad copiados de SSPBUF por el registro de desplazamiento interno SPI.Podemos pasar al registro correspondiente a SSPBUF (registro Tx-Data / Rx-Data) para leer y escribir datos y manipular indirectamente el SSPBUF dentro del dispositivo SPI.
    Por ejemplo, antes de enviar datos, primero debemos escribir los datos que se enviarán al registro Tx-Data del Maestro, y estos datos serán transferidos por el Maestro-SSPSR registro de desplazamiento De acuerdo con el ancho del bus, los datos se mueven automáticamente al maestro-SSPBUF, y luego los datos se moverán fuera del maestro-SSPBUF de acuerdo con el ancho del canal por el maestro-SSPSR y se pasarán al esclavo -SDI pin a través del pin Master-SDO, y Slave-SSPSR transferirá Los datos recibidos de Slave-SDI se mueven a Slave-SSPBUF Al mismo tiempo, los datos en Slave-SSPBUF se envían a Master-SDI, Master -SSPSR a través de Slave-SDO de acuerdo con el tamaño de cada dato recibido (Channel-Width). Luego mueva los datos recibidos de Master-SDI a Master-SSPBUF. Después de completar una sola transmisión de datos, el programa de usuario puede leer los datos intercambiados por el dispositivo Maestro desde el registro Rx-Data del dispositivo Maestro.

  4. Controlador El
    Controlador en el dispositivo Maestro controla principalmente el dispositivo Esclavo a través de la señal de reloj (Clock Signal) y la señal de selección de chip (Señal de Selección Esclavo).El dispositivo Esclavo esperará hasta que reciba la señal de selección de chip enviada por el dispositivo Maestro, y luego, de acuerdo con la señal del reloj para trabajar.La
    operación de selección de chip del dispositivo maestro debe ser implementada por el programa.Por ejemplo: el programa baja la señal del reloj del pin SS/CS para completar el trabajo preliminar de los datos del dispositivo SPI comunicación; cuando el programa quiere que el dispositivo SPI finalice los datos Durante la comunicación, lleve la señal del reloj en el pin SS/CS a un nivel alto.

2. Caso del módulo maestro-esclavo SPI e implementación de FPGA

1. Descripción del caso

Para diseñar un módulo maestro SPI de 4 hilos y un módulo esclavo, los requisitos son los siguientes:

  1. Definición de la interfaz del módulo host (maestro):
module spi_master(
    input         clk_40k,      //时钟信号,40kHz
    input         rst_n,        //复位信号,低有效
	input  [7:0]  data_in,      //主机准备要输出给从机的数据,8位宽
	input		  send_start,	 //通信使能信号,高有效,宽度为1个时钟周期(40kHz),收到该信号后开始一次主从设备通信。
	output [7:0]  data_out,     //主机从从机接收到的数据,8位宽
	output		  data_out_vld,	 //输出数据有效标志,高电平有效,宽度为1个时钟周期(40kHz)
	output		  cs_n,		 //从设备片选使能信号,低有效,低电平时选中从设备与主设备进行通信,处于通信状态时维持低电平。
	output        sclk,         //同步时钟,1kHz,空闲时置低电平
    input         miso,        //主机当前从从机收到的串行数据
    output        mosi        //主机当前发送给从机的串行数据
 	);
  1. Definición de la interfaz del módulo esclavo (esclavo):
module spi_slave(
    input        rst_n,        //复位信号,低有效
	input        cs_n,         //从设备片选使能信号
	input        sclk,         //SPI时钟,1kHz空闲时置低电平,
 	input        mosi,        //从机从主机接收到的串行数据
    output       miso,        //从机要发送给主机的串行数据
	output [7:0]   reg0_out,	 //内部寄存器0的值
	output [7:0]   reg1_out,	 //内部寄存器1的值
	output [7:0]   reg2_out,	 //内部寄存器2的值
	output [7:0]   reg3_out 	 //内部寄存器3的值
);
  1. Descripción de la función del circuito:

Hay cuatro registros internos de ocho bits (reg0, reg1, reg2, reg3) en el módulo esclavo, y las direcciones son respectivamente 0 ~ 3. El módulo maestro configura los valores de los cuatro registros en el módulo esclavo a través del SPI bus, y el valor del registro esclavo se pasa directamente a través de su puerto.
Después de que el módulo maestro recibe la señal send_start, envía los datos data_in al reg0 del módulo esclavo a través del bus spi, luego cambia data_in a la derecha dos bits y lo envía al reg1 del módulo esclavo, y luego cambia data_in a la derecha por dos bits y lo envía a reg2 Finalmente, data_in se recircula a la derecha por dos bits y se envía a reg3. Hasta el momento, el maestro ha completado la configuración de todos los registros en el esclavo. Luego, el maestro lee los datos de reg3 en el esclavo a través del bus spi, los envía a través de data_out y proporciona un data_out_vld de ancho de ciclo al mismo tiempo.

  1. Formato de transmisión SPI:

Cada cuadro de datos SPI contiene 16 bits. El bit 0 que se envía primero es el bit de control de lectura y escritura. Si el bit es 0, significa que el maestro escribe datos en el esclavo, y si es 1, significa que el maestro lee datos del esclavo; los bits 1-7 se envían más tarde Es el bit de dirección, envía primero la dirección alta y luego la dirección baja, 9-16 bits son los bits de datos, y los datos de bit alto se envían primero. Todos los datos se generan en el borde ascendente de sclk y se muestrean en el borde descendente.

El formato de datos de escritura SPI se muestra en la Figura 1:

inserte la descripción de la imagen aquí

El formato de datos de lectura SPI se muestra en la Figura 2:

inserte la descripción de la imagen aquí

2. Ideas de diseño

  1. El protocolo SPI está determinado tanto por el maestro como por el esclavo. Aquí, se requiere que el módulo maestro envíe 5 grupos de números cada vez. Los primeros cuatro grupos se usan para enviar datos a los módulos esclavos con diferentes direcciones, y el quinto grupo es se usa para enviar direcciones para leer los datos recibidos por el último módulo esclavo, así que solo envíe mosi según sea necesario;
  2. El esclavo del módulo esclavo juzgará los datos leídos mosi, si lee 1, juzgue si los siguientes 7 bits son la dirección, si es así, envíe los datos del módulo esclavo esclavo a la dirección a través de miso; si lee 0, juzgue el 7 bits siguientes Si el bit es la dirección, si lo es, los datos enviados por mosi se recibirán en el registro del módulo esclavo esclavo en la dirección.

3. código fuente FPGA

1.SPI_master: módulo principal

module spi_master(
    input         clk_40k,     
    input         rst_n,        
    input  [7:0]  data_in,      
    input	        send_start,	 
    
    output  reg [7:0]  data_out,     
    output  reg   data_out_vld,	
    output  reg   cs_n,		
    output  reg   sclk,         

    input         miso,       
    output  reg   mosi      
 	);


reg flag; //recieve&transfer part
reg addr_flag;  //mosi address output
reg data_flag;  //mosi data output

reg [4:0] bit_cnt;  //bit count
reg [6:0] clk_cnt;  //40 count
reg [2:0] cs_n_cnt;  //data transform count

reg [7:0] data_in_m0;  //shifting input data 
reg [7:0] data_in_m1;  //shifting input data 
reg [7:0] data_in_m2;  //shifting input data 
reg [7:0] data_in_m3;  //shifting input data 

reg [7:0] rx_data_out; //output data

reg [7:0] addr0;
reg [7:0] addr1;
reg [7:0] addr2;
reg [7:0] addr3;
reg [7:0] read;

parameter reg0_address = 7'b0000000; //address of reg0
parameter reg1_address = 7'b0000001; //address of reg1
parameter reg2_address = 7'b0000010; //address of reg2
parameter reg3_address = 7'b0000011; //address of reg3

localparam	    		 idle = 1'b0;
localparam	    		 work = 1'b1;
localparam        transfer = 1'b0;
localparam        recieve = 1'b1;

//set address of reg
always @ *
begin
  if(~rst_n)
  begin
    addr0 <= {
    
    1'b0, reg0_address};
    addr1 <= {
    
    1'b0, reg1_address};
    addr2 <= {
    
    1'b0, reg2_address};
    addr3 <= {
    
    1'b0, reg3_address};
    read <= {
    
    1'b1, reg3_address};
  end
end

//cs_n
always @ (posedge clk_40k or negedge rst_n)
begin
  if(~rst_n)
    cs_n <= 1'b1;
  else if(send_start == 1'b1)
    cs_n <= 1'b0;
  else if(clk_cnt == 7'd39 && bit_cnt == 5'd15 && cs_n_cnt == 3'b100)
    cs_n <= 1'b1;
end

//clk_cnt 20 count
always @ (posedge clk_40k or negedge rst_n)
begin
	if(~rst_n)
		clk_cnt <= 7'b0;
	else if(cs_n == 1'b0 && clk_cnt != 7'd39)
		clk_cnt <= clk_cnt + 1'b1;
	else if(cs_n == 1'b0 && clk_cnt == 7'd39)
		clk_cnt <= 7'b0;
end

//sclk
always @ (posedge clk_40k or negedge rst_n)
begin
	if(~rst_n)
		sclk <= 1'b0;
	else if(send_start == 1'b1)
		sclk <= 1'b1;
	else if(clk_cnt == 7'd19 || clk_cnt == 7'd39)
		sclk <= ~sclk;
end

//bit_cnt 16 count
always @ (posedge clk_40k or negedge rst_n)
begin
	if(~rst_n || cs_n)
		bit_cnt <= 5'b0;
	else if(bit_cnt != 5'd15 && cs_n == 1'b0 && clk_cnt == 7'd39)
		bit_cnt <= bit_cnt + 1'b1;
	else if(bit_cnt == 5'd15 && cs_n == 1'b0 && clk_cnt == 7'd39)
		bit_cnt <= 5'b0;
	else if(cs_n == 1'b1)
	  bit_cnt <= 5'b0;
end

//cs_n_cnt 4 salve count
always @ (posedge clk_40k or negedge rst_n)
begin
	if(~rst_n || cs_n)
		cs_n_cnt <= 3'b0;
	else if(bit_cnt == 5'd15 && cs_n_cnt != 3'b100 && clk_cnt == 7'd39)
		cs_n_cnt <= cs_n_cnt + 1'b1;
	else if(bit_cnt == 5'd15 && cs_n_cnt == 3'b100 && clk_cnt == 7'd39)
		cs_n_cnt <= 3'b0;
	else if(cs_n == 1'b1)
	  cs_n_cnt <= 3'b0;
end

//input data shifting
always @ (posedge sclk)
begin
	if(send_start == 1'b1)
	begin
		data_in_m0 <= data_in;
		data_in_m1 <= {
    
    data_in[1:0],data_in[7:2]};
		data_in_m2 <= {
    
    data_in[3:0],data_in[7:4]};
		data_in_m3 <= {
    
    data_in[5:0],data_in[7:6]};
  end
end

//send part mosi
//flag of transfering address and data
always @ (negedge sclk or negedge rst_n)
begin
  if(~rst_n)
  begin
    addr_flag <= work;
    data_flag <= idle;
  end
  else if(bit_cnt == 5'd7)
  begin
    addr_flag <= idle;
    data_flag <= work;
  end
  else if(bit_cnt == 5'd15 || send_start == 1'b1)
  begin
    addr_flag <= work;
    data_flag <= idle;
  end
end

//output mosi address and data
always @ (posedge sclk)
begin
  if(addr_flag == work && cs_n == 1'b0)
	begin
	  case(cs_n_cnt)
	    3'b000:
	    begin
		    mosi <= addr0[7];
		    addr0 <= {
    
    addr0[6:0],addr0[7]};
	    end
	    3'b001:
	    begin
		    mosi <= addr1[7];
		    addr1 <= {
    
    addr1[6:0],addr1[7]};
	    end
	    3'b010:
	    begin
		    mosi <= addr2[7];
		    addr2 <= {
    
    addr2[6:0],addr2[7]};
	    end
	    3'b011:
	    begin
		    mosi <= addr3[7];
		    addr3 <= {
    
    addr3[6:0],addr3[7]};
	    end
	    3'b100:
	    begin
		    mosi <= read[7];
		    read <= {
    
    read[6:0],read[7]};
	    end
	  endcase
	end
	else if(data_flag == work && cs_n == 1'b0)
	begin
	  case(cs_n_cnt)
	    3'b000:
	    begin
		    mosi <= data_in_m0[7];
		    data_in_m0 <= {
    
    data_in_m0[6:0],data_in_m0[7]};
	    end
	    3'b001:
	    begin
		    mosi <= data_in_m1[7];
		    data_in_m1 <= {
    
    data_in_m1[6:0],data_in_m1[7]};
	    end
	    3'b010:
	    begin
		    mosi <= data_in_m2[7];
		    data_in_m2 <= {
    
    data_in_m2[6:0],data_in_m2[7]};
	    end
	    3'b011:
	    begin
		    mosi <= data_in_m3[7];
		    data_in_m3 <= {
    
    data_in_m3[6:0],data_in_m3[7]};
	    end
	    3'b100:
	    begin
	      mosi <= 1'bz;
	     end
	  endcase
	end
	else if(cs_n == 1'b1)
		mosi <= 1'bz;
end


//recieve part miso
//recieve data
always @ (negedge sclk or negedge rst_n)
begin
	if(~rst_n)
		rx_data_out <= 8'b0;
	else if(flag == 1'b1)
		rx_data_out <= {
    
    rx_data_out[6:0],miso};
end

//output & valid
always @ (posedge clk_40k or negedge rst_n)
begin
	if(~rst_n)
		data_out_vld <= 1'b0;
	else if(bit_cnt == 5'd15 && clk_cnt== 7'd39 && cs_n == 1'b0 && cs_n_cnt == 3'b100)
	begin
		data_out_vld <= 1'b1;
		data_out <= rx_data_out;
	end
	else
		data_out_vld <= 1'b0;
end

//recieve/transfer switch
always @ (posedge clk_40k or negedge rst_n)
begin
  if(~rst_n)
    flag <= 1'b0;
  else if(clk_cnt == 7'd39 && bit_cnt == 5'd7 && cs_n_cnt == 3'b100 && flag == 1'b0)
    flag <= 1'b1;
  else if(clk_cnt == 7'd0 && bit_cnt == 5'd0 && flag == 1'b1)
    flag <= 1'b0;
end

endmodule


2.SPI_slave: módulo esclavo


module	spi_slave
(
  input					 rst_n,
	input      cs_n,
	input	     sclk,
	input		    mosi,
  output reg               miso,
	output reg  [7:0]        reg0_out,
  output reg  [7:0]        reg1_out,
  output reg  [7:0]        reg2_out,
  output reg  [7:0]        reg3_out
	);

reg         [6:0]       bit_cnt;
reg                     state;
reg                     n_state;
reg         [6:0]       address;
reg                     token;
localparam	    		 idle = 1'b0;
localparam	    		 transmit = 1'b1;
	
	
//bit_cnt
always @ (posedge sclk or negedge rst_n)
    if(~rst_n || cs_n)
        bit_cnt <= 7'd0;
    else if(bit_cnt == 7'd15)
        bit_cnt <= 7'd0;
    else if(state || n_state)
        bit_cnt <= bit_cnt + 1'b1;

//n_state
always @ *
    if(cs_n)
        n_state <= idle;
    else
        n_state <= transmit;
        
//state
always @ (posedge sclk or negedge rst_n)
    if(~rst_n)
        state <= idle;
    else
        state <= n_state;
       
//reg_out
always @ (negedge sclk or negedge rst_n)
    if(~rst_n) 
    begin
        reg0_out <= 8'd0;
        reg1_out <= 8'd0;
        reg2_out <= 8'd0;
        reg3_out <= 8'd0;
    end
    else if(bit_cnt >= 'd8 && token == 'b0) 
    begin
        case(address)
        7'd0:
            reg0_out <= {
    
    reg0_out[6:0], mosi};
        7'd1:
            reg1_out <= {
    
    reg1_out[6:0], mosi};
        7'd2:
            reg2_out <= {
    
    reg2_out[6:0], mosi};
        7'd3: 
            reg3_out <= {
    
    reg3_out[6:0], mosi}; 
        endcase
    end

//address
always @ (negedge sclk or negedge rst_n)
    if(~rst_n)
        address <= 7'd0;
    else if(bit_cnt >= 7'd1 && bit_cnt <= 7'd7)
        address <= {
    
    address[5:0], mosi};
        
//token
always @ (negedge sclk or negedge rst_n)
    if(~rst_n)
        token <= 1'b0;
    else if(state == transmit && bit_cnt == 7'b0)
        token <= mosi;

//miso
always @ (posedge sclk or negedge rst_n)
    if(~rst_n || state == idle || cs_n)
        miso <= 1'b0;
    else if(state == transmit && token == 1'b1 && bit_cnt >= 7'd7) 
    begin
        case(address)
            7'd0: miso <= reg0_out[14-bit_cnt];
            7'd1: miso <= reg1_out[14-bit_cnt];         
            7'd2: miso <= reg2_out[14-bit_cnt];
            7'd3: miso <= reg3_out[14-bit_cnt];
        endcase
    end
    else
        miso <= 1'b0;
endmodule


3.testbench: archivo de prueba

`timescale 1us/1us

module tb();

	reg	clk_40k;   
    	reg	rst_n;        
	reg  [7:0]  data_in;      
	reg	send_start;	 

	wire	sclk;
	wire	cs_n;
	wire	mosi;

	wire	miso;
	wire [7:0] data_out;
	wire    data_out_vld;

	wire [7:0] reg0_out;
	wire [7:0] reg1_out;
	wire [7:0] reg2_out;
	wire [7:0] reg3_out;

	spi_master i_spi_master(
		.clk_40k	(clk_40k   ),     
    		.rst_n		(rst_n	   ),
		.data_in	(data_in   ),
		.send_start	(send_start),
		.sclk		(sclk	   ),
		.cs_n		(cs_n	   ),
		.mosi		(mosi	   ),
		.miso		(miso      ),
		.data_out	(data_out  ),  
		.data_out_vld	(data_out_vld  )  
 	);

	spi_slave i_spi_slave(
    		.rst_n		(rst_n	   ),       
    		.cs_n           (cs_n	   ),
    		.sclk		(sclk	   ),        
    		.mosi		(mosi	   ),      
    		.miso		(miso	   ),       
    
    		.reg0_out	(reg0_out  ),	 
    		.reg1_out	(reg1_out  ),	 
    		.reg2_out	(reg2_out  ),	 
    		.reg3_out	(reg3_out  ) 	 
);


	initial 
	begin
		rst_n = 1'b0;
	#10	rst_n = 1'b1;
	end


	initial
	begin
		clk_40k = 1'b0;
		forever
	#1	clk_40k = ~clk_40k;
	end


	initial 
	begin
		send_start = 1'b0;
		data_in = 8'd0;
		forever
		begin
			#200;
			data_in = $random()%256;
			send_start = 1'b1;
			#2
			send_start = 1'b0;
			#8000;
		end
	end

endmodule


Resultados de salida de forma de onda:

inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/weixin_43361652/article/details/108399366
Recomendado
Clasificación