在进行通信测试前,需要对所用到的管脚进行叙述
图中高亮标注的为用到的所有功能引脚,SPI通信测试所用到的管脚为:SSN,SCK,SI,SO,RSTN,VCC,GND。
状态机的转移图如图所示:
SPI通信测试所用到的寄存器在第一节中已经叙述过,在进行第四步写寄存器1和第五步读寄存器5之前需要对gp22芯片进行上电复位和初始化操作,需要注意的是上电复位操作可通过SPI发送上电复位指令或给RSTN一个复位脉冲,本文选择通过SPI发送上电复位操作,在整个过程中RSTN需要一直置高电平,芯片才能正常工作。
SPI操作指令如下图所示
如图表所示,上电复位指令为8’h50,初始化为:8'h70,写入寄存器h8x,读寄存器:hBx,x代表的是寄存器的地址,写寄存器和读寄存器的地址可在芯片手册中查询,也可查询文章第一节,举例说明,若写入寄存器0数据,即8'h80+32'hxxxxxxxx,读寄存器0数据,即8'hb0+receiver16/32bit。
SPI时序部分:结合GP22芯片手册SPI时序图,VCC电压3.3V情况下,SPI串口最大速率为20MHz,即SCK时钟周期最小为25ns,时钟相位为1,时钟极性为0,gp22芯片发送和接收数据是在SCK下降沿时,也就是说在用fpga发送mosi管脚的数据时,可在SCK上升沿改变数据,保证在SCK下降沿时GP22接收信号的稳定性;接收GP22数据时在SCK下降沿缓存miso管脚的数据。本程序设置SCK时钟周期为30ns,可保证寄存器的正常写入和读取。SSN管脚空闲状态置高,在写入或读取数据时拉低,时序要求:SSN下降沿据第一个SCK上升沿时间最小间隔为10ns,拉高时上升沿距最后一个SCK下降沿时间最小间隔40ns,每次写入新的指令或寄存器时,中间SSN需置高50ns后再拉低写入。
module test_2(
clk,
reset_n,
key_one,
RST,
// //tb test
// current_state_tb,
// sck_count_tb,
// state_count_tb,
//
spi_ssn,
spi_mosi,
spi_miso,
spi_sck,
uart_tx
);
input clk;
input reset_n;
input key_one;
input spi_miso;
output wire RST;
output reg spi_mosi;
output reg spi_ssn;
output reg spi_sck;
output wire uart_tx;
// //tb_test
// output wire [3:0]current_state_tb;
// output wire [3:0]sck_count_tb;
// output wire [7:0]state_count_tb;
assign RST = 1;
reg [2:0]current_state;
reg [7:0]state_count;
parameter IDEL = 3'd0,
power_reset = 3'd1,
Init = 3'd2,
write_reg_1 = 3'd3,
read_reg_5 = 3'd4;
wire clk_100m;
clk_100m instance_name
(
// Clock out ports
.clk_out1(clk_100m), // output clk_out1
// Clock in ports
.clk_in1(clk)); // input clk_in1
/*****************************按键信号*******************************************************/
reg [2:0]key_one_reg;
always@(posedge clk_100m or negedge reset_n)
if(!reset_n)
key_one_reg <= 3'd0;
else begin
key_one_reg <= {key_one_reg[1:0],key_one};
end
wire key_en;
assign key_en = ((key_one_reg[2])&&(!key_one_reg[1]))?1:0;
/*****************************************状态切换******************************************/
always@(posedge clk_100m or negedge reset_n)
if(!reset_n)begin
current_state <= IDEL;
state_count <= 8'd0;
end
else begin
case(current_state)
IDEL:begin
if(key_en)
current_state <= power_reset;
else
current_state <= current_state;
end
power_reset:begin
if(state_count == 53)begin
current_state <= Init;
state_count <= 0;
end
else begin
current_state <= current_state;
state_count <= state_count + 1'd1;
end
end
Init:begin
if(state_count == 53)begin
current_state <= write_reg_1;
state_count <= 0;
end
else begin
current_state <= current_state;
state_count <= state_count + 1'd1;
end
end
write_reg_1:begin
if(state_count == 244)begin
current_state <= read_reg_5;
state_count <= 0;
end
else begin
current_state <= current_state;
state_count <= state_count + 1'd1;
end
end
read_reg_5:begin
if(state_count == 100)begin
current_state <= IDEL;
state_count <= 0;
end
else begin
current_state <= current_state;
state_count <= state_count + 1'd1;
end
end
endcase
end
/****************************************************SPI信号***********************************************************/
reg spi_sck_en;
always@(posedge clk_100m or negedge reset_n)
if(!reset_n)begin
spi_ssn <= 1;
spi_sck_en <= 0;
end
else begin
case(current_state)
IDEL:begin
spi_ssn <= 1;
spi_sck_en <= 0;
end
power_reset:begin
if(state_count == 2)
spi_sck_en <= 1;
else if(state_count == 3)
spi_ssn <= 0;
else if(state_count == 49)
spi_sck_en <= 0;
else if(state_count == 52)
spi_ssn <= 1;
else begin
spi_ssn <= spi_ssn;
spi_sck_en <= spi_sck_en;
end
end
Init:begin
if(state_count == 2)
spi_sck_en <= 1;
else if(state_count == 3)
spi_ssn <= 0;
else if(state_count == 49)
spi_sck_en <= 0;
else if(state_count == 52)
spi_ssn <= 1;
else begin
spi_ssn <= spi_ssn;
spi_sck_en <= spi_sck_en;
end
end
write_reg_1:begin
if(state_count == 2)
spi_sck_en <= 1;
else if(state_count == 3)
spi_ssn <= 0;
else if(state_count == 241)
spi_sck_en <= 0;
else if(state_count == 244)
spi_ssn <= 1;
else begin
spi_ssn <= spi_ssn;
spi_sck_en <= spi_sck_en;
end
end
read_reg_5:begin
if(state_count == 2)
spi_sck_en <= 1;
else if(state_count == 3)
spi_ssn <= 0;
else if(state_count == 97)
spi_sck_en <= 0;
else if(state_count == 100)
spi_ssn <= 1;
else begin
spi_ssn <= spi_ssn;
spi_sck_en <= spi_sck_en;
end
end
endcase
end
//sck信号
reg [3:0]sck_count;
always@(posedge clk_100m)
if(!spi_sck_en)
sck_count <= 0;
else if(sck_count == 5)
sck_count <= 0;
else
sck_count <= sck_count + 4'd1;
always@(posedge clk_100m)
if(!spi_sck_en)
spi_sck <= 0;
else if(sck_count == 1)
spi_sck <= 1;
else if(sck_count == 4)
spi_sck <= 0;
//spi_mosi信号
reg [7:0]spi_opcode;
reg [31:0]spi_write_data;
reg [39:0]write;
reg [7:0]receive_8bit;
always@(posedge clk_100m or negedge reset_n)
if(!reset_n)begin
spi_opcode <= 8'd0;
spi_write_data <= 32'd0;
spi_mosi <= 0;
receive_8bit <= 0;
end
else begin
case(current_state)
IDEL:begin
spi_mosi <= 0;
end
power_reset:begin
if(state_count == 1)
spi_opcode <= 8'h50;
else if(sck_count == 1)begin
spi_opcode <= {spi_opcode[6:0],1'b0};
spi_mosi <= spi_opcode[7];
end
end
Init:begin
if(state_count == 1)
spi_opcode <= 8'h70;
else if(sck_count == 1)begin
spi_opcode <= {spi_opcode[6:0],1'b0};
spi_mosi <= spi_opcode[7];
end
end
write_reg_1:begin
if(state_count == 1)begin
spi_opcode <= 8'h81;
spi_write_data <= 32'b1111_1010_10_001_001_10_000_000_10101010;
end
else if(state_count == 2)
write <= {spi_opcode,spi_write_data};
else if(sck_count == 1)begin
write <= {write[38:0],1'b0};
spi_mosi <= write[39];
end
end
read_reg_5:begin
if(state_count == 1)
spi_opcode <= 8'hB5;
else if(state_count < 50)begin
if(sck_count == 1)begin
spi_opcode <= {spi_opcode[6:0],1'b0};
spi_mosi <= spi_opcode[7];
end
end
else begin
if(sck_count == 4)begin
spi_mosi <= 0;
receive_8bit <= {receive_8bit[6:0],spi_miso};
end
else
receive_8bit <= receive_8bit;
end
end
endcase
end
ila_0 your_instance_name (
.clk(clk_100m), // input wire clk
.probe0(spi_miso), // input wire [0:0] probe0
.probe1(spi_mosi), // input wire [0:0] probe1
.probe2(spi_sck), // input wire [0:0] probe2
.probe3(spi_ssn), // input wire [0:0] probe3
.probe4(key_en), // input wire [0:0] probe4
.probe5(spi_sck_en), // input wire [0:0] probe5
.probe6(receive_8bit) // input wire [7:0] probe6
);
// assign current_state_tb = current_state;
// assign sck_count_tb = sck_count;
// assign state_count_tb = state_count;
endmodule
进行程序仿真时候需要加上代码中注释的几行代码,要自己加上PLL时钟倍频,输出时钟频率为100MHz,加入ILA探针观察是否正常读取寄存器数据。
tb程序
`timescale 1ns / 1ps
module test_2_tb();
reg reset_n;
reg clk;
reg key_one;
wire uart_tx;
wire spi_ssn ;
wire spi_sck ;
wire spi_mosi;
reg spi_miso;
wire RST;
wire [3:0]current_state_tb;
wire [3:0]sck_count_tb;
wire [7:0]state_count_tb;
test_2 test_2(
.clk(clk),
.reset_n(reset_n),
.key_one(key_one),
.RST(RST),
.current_state_tb(current_state_tb),
.sck_count_tb(sck_count_tb),
.state_count_tb(state_count_tb),
.spi_ssn(spi_ssn),
.spi_mosi(spi_mosi),
.spi_miso(spi_miso),
.spi_sck(spi_sck),
.uart_tx(uart_tx)
);
initial clk = 1;
always #10 clk = ~clk;
initial begin
reset_n = 0;
key_one = 0;
spi_miso = 0;
#21;
reset_n = 1;
#200;
key_one = 1;
#2000;
key_one = 0;
result;
#6000;
$stop;
end
task result;
begin
wait((current_state_tb == 4)&&(state_count_tb >= 50))
repeat(4)
begin
@(sck_count_tb == 2);
spi_miso = 1;
#20;
@(sck_count_tb == 2);
spi_miso = 0;
#20;
end
end
endtask
endmodule
仿真波形如下图所示
本程序是为了便于ILA抓取查看信号,通过按键信号使从IDEL状态进入写状态,若板子无预留按键接口,也可稍微修改程序循环写入读取。