自定义AXI总线形式SPI接口IP核,点亮OLED

时间:2022-11-13 16:30:08

一、前言

  最近花费很多精力在算法仿真和实现上,外设接口的调试略有生疏。本文以FPGA控制OLED中的SPI接口为例,重新夯实下基础。重点内容为SPI时序的RTL设计以及AXI-Lite总线分析。当然做些项目时可以直接调用Xilinx提供的SPI IP核,这里仅出于练习的目的考虑。

二、接口时序分析

  本项目用的OLED型号为UG-2832HSWEG04,核心控制器是SSD1306。该芯片支持并口、I2C以及SPI接口,这里采用4线SPI作为数据总线。4线SPI接口包括:

SCLK:串行时钟,SSD1306上升沿采集数据

SDIN:串行数据输入,数据顺序为MSB

D/C:数据命令控制,高电平为数据,低电平为控制命令

CS:片选信号,低电平有效

时序图如下:

自定义AXI总线形式SPI接口IP核,点亮OLED

  片选信号有效期间,每第8个时钟周期上升沿时刻,控制芯片会采样D/C并同时将进入的一字节数据写入到显示缓存GDDRAM或控制寄存器中。

自定义AXI总线形式SPI接口IP核,点亮OLED

自定义AXI总线形式SPI接口IP核,点亮OLED

  根据datasheet中的AC Characteristics中参数,选择SPI串行时钟周期为200ns,占空比为50%以保证足够的时序裕量。此时传输速率为:5MHZ/8 = 625KHZ。

三、SPI接口模块设计

  根据上述分析,很容易可以设计出用于传输数据或命令的SPI时序接口模块。接口定义如下:

用户侧:clk rst_n com din busy,依次是系统时钟,复位,指令信号(1为发送控制信息,2则发送数据),待传输字节以及忙等待指示。

外设侧:SCLK SDIN CS D/C

  逻辑状态分为:IDLE SEND和DONE,具体时序如下:

自定义AXI总线形式SPI接口IP核,点亮OLED

  直接对照上图编写HDL:

 `timescale 1ns / 1ps

 module spi_4wire#(parameter DIV_CYC = )
(
//本地接口
input clk,//100MHZ
input rst_n,
input [-:] com,//1发送控制信息,2发送数据 其他无效
input [-:] din,
output busy,
//芯片侧接口
output reg sclk = ,
output reg sdin = ,
output reg cs = 'b1,
output reg dc = //1是数据,0是控制命令
);
//**************************参数定义********************************************
function integer clogb2 (input integer bit_depth);
begin
for(clogb2=; bit_depth>; clogb2=clogb2+)
bit_depth = bit_depth >> ;
end
endfunction localparam DIV_CNT_W = clogb2(DIV_CYC-),
BIT_CNT_W = clogb2(-); localparam IDLE = ,
SEND = ,
DONE = ; //************************变量定义****************************************
reg [ (DIV_CNT_W-):] div_cnt = ;
wire add_div_cnt ;
wire end_div_cnt ;
reg [ (BIT_CNT_W-):] bit_cnt = ;
wire add_bit_cnt ;
wire end_bit_cnt ;
reg [-:] state_c = IDLE,state_n = IDLE;
wire idle2send,send2done,done2idle;
reg [+-:] data_tmp = ;
wire din_vld;
wire start_send;
reg busy_flag = ;
//************************逻辑****************************************
//sclk时钟分频 T:10ns --> 200ns 20倍
//分频计数器
always @(posedge clk or negedge rst_n) begin
if (rst_n==) begin
div_cnt <= ;
end
else if(add_div_cnt) begin
if(end_div_cnt)
div_cnt <= ;
else
div_cnt <= div_cnt+ ;
end
end
assign add_div_cnt = ();
assign end_div_cnt = add_div_cnt && div_cnt == (DIV_CYC)- ; //比特计数器
always @(posedge clk or negedge rst_n) begin
if (rst_n==) begin
bit_cnt <= ;
end
else if(add_bit_cnt) begin
if(end_bit_cnt)
bit_cnt <= ;
else
bit_cnt <= bit_cnt+ ;
end
end
assign add_bit_cnt = (state_c == SEND && end_div_cnt);
assign end_bit_cnt = add_bit_cnt && bit_cnt == ()- ; //控制状态机
always @(posedge clk or negedge rst_n) begin
if (rst_n==) begin
state_c <= IDLE ;
end
else begin
state_c <= state_n;
end
end always @(*) begin
case(state_c)
IDLE :begin
if(idle2send )
state_n = SEND ;
else
state_n = state_c ;
end
SEND :begin
if(send2done )
state_n = DONE ;
else
state_n = state_c ;
end
DONE :begin
if(done2idle )
state_n = IDLE ;
else
state_n = state_c ;
end
default : state_n = IDLE ;
endcase
end assign idle2send = state_c==IDLE && (end_div_cnt && data_tmp[- -:] != );
assign send2done = state_c==SEND && (end_bit_cnt);
assign done2idle = state_c==DONE && (end_div_cnt); //输入命令/数据寄存
always @(posedge clk or negedge rst_n)begin
if(rst_n=='b0)begin
data_tmp <= ;
end
else if(din_vld)begin
data_tmp <= {com,din};
end
else if(done2idle)begin
data_tmp <= ;
end
end assign din_vld = busy_flag == 'b0 && com != 2'd0; //SPI输出信号
always @(posedge clk or negedge rst_n)begin
if(rst_n=='b0)begin
sdin <= ;
end
else if(add_bit_cnt)begin
sdin <= data_tmp[--bit_cnt];
end
end always @(posedge clk or negedge rst_n)begin
if(rst_n=='b0)begin
cs <= 'b1;
end
else if(start_send)begin
cs <= ;
end
else if(done2idle)begin
cs <= 'b1;
end
end assign start_send = add_bit_cnt && bit_cnt == ; always @(posedge clk or negedge rst_n)begin
if(rst_n=='b0)begin
dc <= 'b0;
end
else if(start_send)begin
case(data_tmp[:])//1发送控制信息,2发送数据 其他无效
'd1:dc <= 1'b0;//1是数据,0是控制命令
'd2:dc <= 1'b1;
default:dc <= 'b0;
endcase
end
else if(done2idle)begin
dc <= ;
end
end //SCLK
always @(posedge clk or negedge rst_n)begin
if(rst_n=='b0)begin
sclk <= ;
end
else if(add_div_cnt && div_cnt == DIV_CYC/-)begin
sclk <= 'b1;
end
else if(end_div_cnt)begin
sclk <= ;
end
end //本地侧输出
always@(posedge clk or negedge rst_n)begin
if(rst_n == )begin
busy_flag <= ;
end
else if(din_vld)begin
busy_flag <= 'b1;
end
else if(done2idle)begin
busy_flag <= ;
end
end assign busy = busy_flag | din_vld; endmodule

spi_4wire

  逻辑非常清晰,分频计数器控制和比特计数器作为整个时序接口模块的跳变时刻。状态机决定SPI中的CS SCLK SDIN D/C信号变化。比较重要的是busy接口信号,该信号为后续衔接AXI总线作准备。

四、AXI(AXI-Lite)总线详解及接口封装

  核心逻辑设计完成,最后是总线接口封装工作。由于SPI本地侧发送一个字节数据后需要很长一段时间才能将其转换成的串行数据发送完毕,因此使用AXI-Lite总线即可满足数据传输需求。利用VIVADO IP封装器自带的AXI总线模板可以简化设计,看下总线接口:

1 写地址通道:

S_AXI_AWADDR:写地址

S_AXI_AWPORT:写地址保护类型

S_AXI_AWVALID:写地址有效

S_AXI_AWREADY:写地址准备

2 写数据通道:

S_AXI_WDATA:写数据

S_AXI_WSTRB:指示对应字节是有效数据还是位置信息(1为有效数据)

S_AXI_WVALID:写有效

S_AXI_WREADY:写数据准备

3 写响应通道:

S_AXI_BRESP:指示写传输状态

S_AXI_BVALID:写响应有效指示

S_AXI_BREADY:响应准备

4 读地址通道:

S_AXI_ARADDR:读地址

S_AXI_ARPROT:读地址保护类型

S_AXI_ARVALID:读地址有效指示

S_AXI_ARREADY:读地址准备

5 读数据通道:

S_AXI_RDATA:读数据

S_AXI_RVALID:读数据有效

S_AXI_RREADY:读数据准备

  可以看出,每个通道无论有多少信号,数据信息,有效指示以及准备就绪信号是必然存在的,这三个信号能够完成最基本的总线握手传输。

自定义AXI总线形式SPI接口IP核,点亮OLED

  这里将之前设计的SPI接口模块例化在AXI Wrapper(spi_4wire_w_v1_0)中,并添加与Slave接口模块(spi_4wire_w_v1_0_S00_AXI)的连接信号。其中Slave接口模块内ready信号默认是在ready为0且valid为1时拉高一个时钟周期,但应考虑SPI模块是否准备就绪或上一个数据传输完成,改动后AXI wrapper以及AXI-Lite Slave接口逻辑如下:

AXI Wrapper:

 `timescale  ns /  ps

     module spi_4wire_w_v1_0 #
(
// Users to add parameters here // User parameters ends
// Do not modify the parameters beyond this line // Parameters of Axi Slave Bus Interface S00_AXI
parameter integer C_S00_AXI_DATA_WIDTH = ,
parameter integer C_S00_AXI_ADDR_WIDTH =
)
(
// Users to add ports here
//SPI signals
output sclk,
output sdin,
output cs,
output dc,
// User ports ends
// Do not modify the ports beyond this line // Ports of Axi Slave Bus Interface S00_AXI
input wire s00_axi_aclk,
input wire s00_axi_aresetn,
input wire [C_S00_AXI_ADDR_WIDTH- : ] s00_axi_awaddr,
input wire [ : ] s00_axi_awprot,
input wire s00_axi_awvalid,
output wire s00_axi_awready,
input wire [C_S00_AXI_DATA_WIDTH- : ] s00_axi_wdata,
input wire [(C_S00_AXI_DATA_WIDTH/)- : ] s00_axi_wstrb,
input wire s00_axi_wvalid,
output wire s00_axi_wready,
output wire [ : ] s00_axi_bresp,
output wire s00_axi_bvalid,
input wire s00_axi_bready,
input wire [C_S00_AXI_ADDR_WIDTH- : ] s00_axi_araddr,
input wire [ : ] s00_axi_arprot,
input wire s00_axi_arvalid,
output wire s00_axi_arready,
output wire [C_S00_AXI_DATA_WIDTH- : ] s00_axi_rdata,
output wire [ : ] s00_axi_rresp,
output wire s00_axi_rvalid,
input wire s00_axi_rready
); reg rst_n = 'b1;
wire [-:] din;
wire [-:] com;
wire busy;
wire [-:] data;
wire data_vld; // Instantiation of Axi Bus Interface S00_AXI
spi_4wire_w_v1_0_S00_AXI # (
.C_S_AXI_DATA_WIDTH(C_S00_AXI_DATA_WIDTH),
.C_S_AXI_ADDR_WIDTH(C_S00_AXI_ADDR_WIDTH)
) spi_4wire_w_v1_0_S00_AXI_inst (
.local_dout(data),
.local_dout_vld(data_vld),
.local_busy(busy), .S_AXI_ACLK(s00_axi_aclk),
.S_AXI_ARESETN(s00_axi_aresetn),
.S_AXI_AWADDR(s00_axi_awaddr),
.S_AXI_AWPROT(s00_axi_awprot),
.S_AXI_AWVALID(s00_axi_awvalid),
.S_AXI_AWREADY(s00_axi_awready),
.S_AXI_WDATA(s00_axi_wdata),
.S_AXI_WSTRB(s00_axi_wstrb),
.S_AXI_WVALID(s00_axi_wvalid),
.S_AXI_WREADY(s00_axi_wready),
.S_AXI_BRESP(s00_axi_bresp),
.S_AXI_BVALID(s00_axi_bvalid),
.S_AXI_BREADY(s00_axi_bready),
.S_AXI_ARADDR(s00_axi_araddr),
.S_AXI_ARPROT(s00_axi_arprot),
.S_AXI_ARVALID(s00_axi_arvalid),
.S_AXI_ARREADY(s00_axi_arready),
.S_AXI_RDATA(s00_axi_rdata),
.S_AXI_RRESP(s00_axi_rresp),
.S_AXI_RVALID(s00_axi_rvalid),
.S_AXI_RREADY(s00_axi_rready)
); // Add user logic here
spi_4wire#(.DIV_CYC())
uut
(
.clk (s00_axi_aclk) ,//100MHZ
.rst_n (rst_n) ,
.com (com) ,//1发送控制信息,2发送数据 其他无效
.din (din) ,
.busy (busy) ,//rdy .sclk (sclk) ,
.sdin (sdin) ,
.cs (cs) ,
.dc (dc) //1是数据,0是控制命令
); always@(posedge s00_axi_aclk)begin
rst_n <= s00_axi_aresetn;
end assign com = data_vld ? data[:] : 'd0;
assign din = data_vld ? data[-:] : 'd0;
// User logic ends endmodule

spi_4wire_w_v1_0

Slave接口模块:

 `timescale  ns /  ps

     module spi_4wire_w_v1_0_S00_AXI #
(
// Users to add parameters here // User parameters ends
// Do not modify the parameters beyond this line // Width of S_AXI data bus
parameter integer C_S_AXI_DATA_WIDTH = ,
// Width of S_AXI address bus
parameter integer C_S_AXI_ADDR_WIDTH =
)
(
// Users to add ports here
output [C_S_AXI_DATA_WIDTH-:] local_dout,
output reg local_dout_vld = ,
input local_busy,
// User ports ends
// Do not modify the ports beyond this line // Global Clock Signal
input wire S_AXI_ACLK,
// Global Reset Signal. This Signal is Active LOW
input wire S_AXI_ARESETN,
// Write address (issued by master, acceped by Slave)
input wire [C_S_AXI_ADDR_WIDTH- : ] S_AXI_AWADDR,
// Write channel Protection type. This signal indicates the
// privilege and security level of the transaction, and whether
// the transaction is a data access or an instruction access.
input wire [ : ] S_AXI_AWPROT,
// Write address valid. This signal indicates that the master signaling
// valid write address and control information.
input wire S_AXI_AWVALID,
// Write address ready. This signal indicates that the slave is ready
// to accept an address and associated control signals.
output wire S_AXI_AWREADY,
// Write data (issued by master, acceped by Slave)
input wire [C_S_AXI_DATA_WIDTH- : ] S_AXI_WDATA,
// Write strobes. This signal indicates which byte lanes hold
// valid data. There is one write strobe bit for each eight
// bits of the write data bus.
input wire [(C_S_AXI_DATA_WIDTH/)- : ] S_AXI_WSTRB,
// Write valid. This signal indicates that valid write
// data and strobes are available.
input wire S_AXI_WVALID,
// Write ready. This signal indicates that the slave
// can accept the write data.
output wire S_AXI_WREADY,
// Write response. This signal indicates the status
// of the write transaction.
output wire [ : ] S_AXI_BRESP,
// Write response valid. This signal indicates that the channel
// is signaling a valid write response.
output wire S_AXI_BVALID,
// Response ready. This signal indicates that the master
// can accept a write response.
input wire S_AXI_BREADY,
// Read address (issued by master, acceped by Slave)
input wire [C_S_AXI_ADDR_WIDTH- : ] S_AXI_ARADDR,
// Protection type. This signal indicates the privilege
// and security level of the transaction, and whether the
// transaction is a data access or an instruction access.
input wire [ : ] S_AXI_ARPROT,
// Read address valid. This signal indicates that the channel
// is signaling valid read address and control information.
input wire S_AXI_ARVALID,
// Read address ready. This signal indicates that the slave is
// ready to accept an address and associated control signals.
output wire S_AXI_ARREADY,
// Read data (issued by slave)
output wire [C_S_AXI_DATA_WIDTH- : ] S_AXI_RDATA,
// Read response. This signal indicates the status of the
// read transfer.
output wire [ : ] S_AXI_RRESP,
// Read valid. This signal indicates that the channel is
// signaling the required read data.
output wire S_AXI_RVALID,
// Read ready. This signal indicates that the master can
// accept the read data and response information.
input wire S_AXI_RREADY
); // AXI4LITE signals
reg [C_S_AXI_ADDR_WIDTH- : ] axi_awaddr;
reg axi_awready;
reg axi_wready;
reg [ : ] axi_bresp;
reg axi_bvalid;
reg [C_S_AXI_ADDR_WIDTH- : ] axi_araddr;
reg axi_arready;
reg [C_S_AXI_DATA_WIDTH- : ] axi_rdata;
reg [ : ] axi_rresp;
reg axi_rvalid; // Example-specific design signals
// local parameter for addressing 32 bit / 64 bit C_S_AXI_DATA_WIDTH
// ADDR_LSB is used for addressing 32/64 bit registers/memories
// ADDR_LSB = 2 for 32 bits (n downto 2)
// ADDR_LSB = 3 for 64 bits (n downto 3)
localparam integer ADDR_LSB = (C_S_AXI_DATA_WIDTH/) + ;
localparam integer OPT_MEM_ADDR_BITS = ;
//----------------------------------------------
//-- Signals for user logic register space example
//------------------------------------------------
//-- Number of Slave Registers 4
reg [C_S_AXI_DATA_WIDTH-:] slv_reg0;
reg [C_S_AXI_DATA_WIDTH-:] slv_reg1;
reg [C_S_AXI_DATA_WIDTH-:] slv_reg2;
reg [C_S_AXI_DATA_WIDTH-:] slv_reg3;
wire slv_reg_rden;
wire slv_reg_wren;
reg [C_S_AXI_DATA_WIDTH-:] reg_data_out;
integer byte_index; // I/O Connections assignments assign S_AXI_AWREADY = axi_awready;
assign S_AXI_WREADY = axi_wready;
assign S_AXI_BRESP = axi_bresp;
assign S_AXI_BVALID = axi_bvalid;
assign S_AXI_ARREADY = axi_arready;
assign S_AXI_RDATA = axi_rdata;
assign S_AXI_RRESP = axi_rresp;
assign S_AXI_RVALID = axi_rvalid; assign local_dout = slv_reg0; always@(posedge S_AXI_ACLK)begin
if( S_AXI_ARESETN == 'b0)
local_dout_vld <= ;
else
local_dout_vld <= slv_reg_wren;
end // Implement axi_awready generation
// axi_awready is asserted for one S_AXI_ACLK clock cycle when both
// S_AXI_AWVALID and S_AXI_WVALID are asserted. axi_awready is
// de-asserted when reset is low. always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 )
begin
axi_awready <= 'b0;
end
else
begin
if (~axi_awready && S_AXI_AWVALID && S_AXI_WVALID && ~local_busy)
begin
// slave is ready to accept write address when
// there is a valid write address and write data
// on the write address and data bus. This design
// expects no outstanding transactions.
axi_awready <= 'b1;
end
else
begin
axi_awready <= 'b0;
end
end
end // Implement axi_awaddr latching
// This process is used to latch the address when both
// S_AXI_AWVALID and S_AXI_WVALID are valid. always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 )
begin
axi_awaddr <= ;
end
else
begin
if (~axi_awready && S_AXI_AWVALID && S_AXI_WVALID && ~local_busy)
begin
// Write Address latching
axi_awaddr <= S_AXI_AWADDR;
end
end
end // Implement axi_wready generation
// axi_wready is asserted for one S_AXI_ACLK clock cycle when both
// S_AXI_AWVALID and S_AXI_WVALID are asserted. axi_wready is
// de-asserted when reset is low. always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 )
begin
axi_wready <= 'b0;
end
else
begin
if (~axi_wready && S_AXI_WVALID && S_AXI_AWVALID && ~local_busy)
begin
// slave is ready to accept write data when
// there is a valid write address and write data
// on the write address and data bus. This design
// expects no outstanding transactions.
axi_wready <= 'b1;
end
else
begin
axi_wready <= 'b0;
end
end
end // Implement memory mapped register select and write logic generation
// The write data is accepted and written to memory mapped registers when
// axi_awready, S_AXI_WVALID, axi_wready and S_AXI_WVALID are asserted. Write strobes are used to
// select byte enables of slave registers while writing.
// These registers are cleared when reset (active low) is applied.
// Slave register write enable is asserted when valid address and data are available
// and the slave is ready to accept the write address and write data.
assign slv_reg_wren = axi_wready && S_AXI_WVALID && axi_awready && S_AXI_AWVALID; always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 )
begin
slv_reg0 <= ;
slv_reg1 <= ;
slv_reg2 <= ;
slv_reg3 <= ;
end
else begin
if (slv_reg_wren)
begin
case ( axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
'h0:
for ( byte_index = ; byte_index <= (C_S_AXI_DATA_WIDTH/)-; byte_index = byte_index+ )
if ( S_AXI_WSTRB[byte_index] == ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 0
slv_reg0[(byte_index*) +: ] <= S_AXI_WDATA[(byte_index*) +: ];
end
'h1:
for ( byte_index = ; byte_index <= (C_S_AXI_DATA_WIDTH/)-; byte_index = byte_index+ )
if ( S_AXI_WSTRB[byte_index] == ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 1
slv_reg1[(byte_index*) +: ] <= S_AXI_WDATA[(byte_index*) +: ];
end
'h2:
for ( byte_index = ; byte_index <= (C_S_AXI_DATA_WIDTH/)-; byte_index = byte_index+ )
if ( S_AXI_WSTRB[byte_index] == ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 2
slv_reg2[(byte_index*) +: ] <= S_AXI_WDATA[(byte_index*) +: ];
end
'h3:
for ( byte_index = ; byte_index <= (C_S_AXI_DATA_WIDTH/)-; byte_index = byte_index+ )
if ( S_AXI_WSTRB[byte_index] == ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 3
slv_reg3[(byte_index*) +: ] <= S_AXI_WDATA[(byte_index*) +: ];
end
default : begin
slv_reg0 <= slv_reg0;
slv_reg1 <= slv_reg1;
slv_reg2 <= slv_reg2;
slv_reg3 <= slv_reg3;
end
endcase
end
end
end // Implement write response logic generation
// The write response and response valid signals are asserted by the slave
// when axi_wready, S_AXI_WVALID, axi_wready and S_AXI_WVALID are asserted.
// This marks the acceptance of address and indicates the status of
// write transaction. always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 )
begin
axi_bvalid <= ;
axi_bresp <= 'b0;
end
else
begin
if (axi_awready && S_AXI_AWVALID && ~axi_bvalid && axi_wready && S_AXI_WVALID)
begin
// indicates a valid write response is available
axi_bvalid <= 'b1;
axi_bresp <= 'b0; // 'OKAY' response
end // work error responses in future
else
begin
if (S_AXI_BREADY && axi_bvalid)
//check if bready is asserted while bvalid is high)
//(there is a possibility that bready is always asserted high)
begin
axi_bvalid <= 'b0;
end
end
end
end // Implement axi_arready generation
// axi_arready is asserted for one S_AXI_ACLK clock cycle when
// S_AXI_ARVALID is asserted. axi_awready is
// de-asserted when reset (active low) is asserted.
// The read address is also latched when S_AXI_ARVALID is
// asserted. axi_araddr is reset to zero on reset assertion. always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 )
begin
axi_arready <= 'b0;
axi_araddr <= 'b0;
end
else
begin
if (~axi_arready && S_AXI_ARVALID)
begin
// indicates that the slave has acceped the valid read address
axi_arready <= 'b1;
// Read address latching
axi_araddr <= S_AXI_ARADDR;
end
else
begin
axi_arready <= 'b0;
end
end
end // Implement axi_arvalid generation
// axi_rvalid is asserted for one S_AXI_ACLK clock cycle when both
// S_AXI_ARVALID and axi_arready are asserted. The slave registers
// data are available on the axi_rdata bus at this instance. The
// assertion of axi_rvalid marks the validity of read data on the
// bus and axi_rresp indicates the status of read transaction.axi_rvalid
// is deasserted on reset (active low). axi_rresp and axi_rdata are
// cleared to zero on reset (active low).
always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 )
begin
axi_rvalid <= ;
axi_rresp <= ;
end
else
begin
if (axi_arready && S_AXI_ARVALID && ~axi_rvalid)
begin
// Valid read data is available at the read data bus
axi_rvalid <= 'b1;
axi_rresp <= 'b0; // 'OKAY' response
end
else if (axi_rvalid && S_AXI_RREADY)
begin
// Read data is accepted by the master
axi_rvalid <= 'b0;
end
end
end // Implement memory mapped register select and read logic generation
// Slave register read enable is asserted when valid address is available
// and the slave is ready to accept the read address.
assign slv_reg_rden = axi_arready & S_AXI_ARVALID & ~axi_rvalid;
always @(*)
begin
// Address decoding for reading registers
case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
'h0 : reg_data_out <= slv_reg0;
'h1 : reg_data_out <= slv_reg1;
'h2 : reg_data_out <= slv_reg2;
'h3 : reg_data_out <= slv_reg3;
default : reg_data_out <= ;
endcase
end // Output register or memory read data
always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 )
begin
axi_rdata <= ;
end
else
begin
// When there is a valid read address (S_AXI_ARVALID) with
// acceptance of read address by the slave (axi_arready),
// output the read dada
if (slv_reg_rden)
begin
axi_rdata <= reg_data_out; // register read data
end
end
end // Add user logic here // User logic ends endmodule

spi_4wire_w_v1_0_S00_AXI

五、仿真测试

  我们写个简单的testbench测试接口和SPI时序逻辑正确性。只要准备就绪,便向顶层模块写入h02_4a。

testbench代码:

 `timescale 1ns / 1ps

 module axi_spi_tb( );

 parameter CYC = ,
RST_TIM = ; reg s00_axi_aclk;
reg s00_axi_aresetn;
reg [-:] s00_axi_awaddr;
wire [-:] s00_axi_awprot;
reg s00_axi_awvalid;
wire s00_axi_awready;
reg [-:] s00_axi_wdata;
reg [-:] s00_axi_wstrb;
reg s00_axi_wvalid;
wire s00_axi_wready;
wire [-:] s00_axi_bresp;
wire s00_axi_bvalid;
wire s00_axi_bready;
reg [-:] s00_axi_araddr;
wire [-:] s00_axi_arprot;
reg s00_axi_arvalid;
wire s00_axi_arready;
wire [-:] s00_axi_rdata;
wire [-:] s00_axi_rresp;
wire s00_axi_rvalid;
wire s00_axi_rready; spi_4wire_w_v1_0 #
( .C_S00_AXI_DATA_WIDTH(),
.C_S00_AXI_ADDR_WIDTH()
)
uut
(
// Users to add ports here
//SPI signals
. sclk (sclk) ,
. sdin (sdin) ,
. cs (cs) ,
. dc (dc) , . s00_axi_aclk (s00_axi_aclk) ,
. s00_axi_aresetn (s00_axi_aresetn) ,
. s00_axi_awaddr (s00_axi_awaddr) ,
. s00_axi_awprot (s00_axi_awprot) ,
. s00_axi_awvalid (s00_axi_awvalid) ,
. s00_axi_awready (s00_axi_awready) ,
. s00_axi_wdata (s00_axi_wdata) ,
. s00_axi_wstrb (s00_axi_wstrb) ,
. s00_axi_wvalid (s00_axi_wvalid) ,
. s00_axi_wready (s00_axi_wready) ,
. s00_axi_bresp (s00_axi_bresp) ,
. s00_axi_bvalid (s00_axi_bvalid) ,
. s00_axi_bready (s00_axi_bready) ,
. s00_axi_araddr (s00_axi_araddr) ,
. s00_axi_arprot (s00_axi_arprot) ,
. s00_axi_arvalid (s00_axi_arvalid) ,
. s00_axi_arready (s00_axi_arready) ,
. s00_axi_rdata (s00_axi_rdata) ,
. s00_axi_rresp (s00_axi_rresp) ,
. s00_axi_rvalid (s00_axi_rvalid) ,
. s00_axi_rready (s00_axi_rready)
); initial begin
s00_axi_aclk = ;
forever #(CYC/2.0) s00_axi_aclk = ~s00_axi_aclk;
end initial begin
s00_axi_aresetn = ;
#;
s00_axi_aresetn = ;
#(RST_TIM*CYC);
s00_axi_aresetn = ;
end assign s00_axi_awprot = 'd0;
assign s00_axi_bready = 'b1;
assign s00_axi_arprot = 'd0;
assign s00_axi_rready = 'b1; initial begin
#;
s00_axi_awaddr = ;
s00_axi_awvalid = ;
s00_axi_wdata = ;
s00_axi_wstrb = ;
s00_axi_wvalid = ;
s00_axi_araddr = ;
s00_axi_arvalid = ;
#(RST_TIM*CYC);
#(CYC*);
s00_axi_awaddr = ;
s00_axi_awvalid = 'b1;
s00_axi_wdata = 'h02_4a;
s00_axi_wstrb = 'b1111;
s00_axi_wvalid = 'b1;
s00_axi_araddr = ;
s00_axi_arvalid = ;
#5_000;
$stop;
end endmodule

axi_spi_tb

行为仿真波形:

自定义AXI总线形式SPI接口IP核,点亮OLED

自定义AXI总线形式SPI接口IP核,点亮OLED

    在CS为低时,串行输出为:0_1_0_0_1_0_1_0,正确完成SPI数据写功能。暂仅进行行为仿真,还没有上板验证,遇到问题后续改动更新。

自定义AXI总线形式SPI接口IP核,点亮OLED的更多相关文章

  1. 基于AMBA总线的SPI协议IP核的设计与验证

    https://wenku.baidu.com/view/9542213131126edb6f1a1048.html?mark_pay_doc=2&mark_rec_page=1&ma ...

  2. ZYNQ自定义AXI总线IP应用——PWM实现呼吸灯效果

    一.前言 在实时性要求较高的场合中,CPU软件执行的方式显然不能满足需求,这时需要硬件逻辑实现部分功能.要想使自定义IP核被CPU访问,就必须带有总线接口.ZYNQ采用AXI BUS实现PS和PL之间 ...

  3. 基于AXI4总线卷积FPGA加速IP核的尝试

    本文先总结不同AXI IP核的实现的方法,性能的对比,性能差异的分析,可能改进的方面.使用的硬件平台是Zedboard. 不同的AXI总线卷积加速模块的概况 这次实现并逐渐优化了三个版本的卷积加速模块 ...

  4. Zynq-PL中创建AXI Master接口IP及AXI4-Lite总线主从读写时序测试&lpar;转)

    转载:原文  http://www.eefocus.com/antaur/blog/17-08/423751_6cc0d.html 0. 引言 通过之前的学习,可以在PL端创建从机模式的AXI接口IP ...

  5. 利用ZYNQ SOC快速打开算法验证通路(6)——利用AXI总线实时配置sysGen子系统

    利用ZYNQ验证算法的一大优势在于,可以在上位机发送指令借助CPU的控制能力和C语言易开发特点,实时配置算法模块的工作模式.参数等对来对其算法模块性能进行全面的评估.最重要的是无需重新综合硬件模块. ...

  6. AXI-Lite总线及其自定义IP核使用分析总结

    ZYNQ的优势在于通过高效的接口总线组成了ARM+FPGA的架构.我认为两者是互为底层的,当进行算法验证时,ARM端现有的硬件控制器和库函数可以很方便地连接外设,而不像FPGA设计那样完全写出接口时序 ...

  7. 一步一步学ZedBoard &amp&semi; Zynq&lpar;四&rpar;:基于AXI Lite 总线的从设备IP设计

    本帖最后由 xinxincaijq 于 2013-1-9 10:27 编辑 一步一步学ZedBoard & Zynq(四):基于AXI Lite 总线的从设备IP设计 转自博客:http:// ...

  8. 将自己写的HDL代码封装成带AXI总线的IP

    将自己写的HDL代码封装成带AXI总线的IP 1.Tools->create and package IP 2.create AXI4总线的IP 3.新建block design 4.点击右键, ...

  9. SPI、I2C、UART三种串行总线协议的区别和SPI接口介绍(转)

    SPI.I2C.UART三种串行总线协议的区别 第一个区别当然是名字: SPI(Serial Peripheral Interface:串行外设接口); I2C(INTER IC BUS) UART( ...

随机推荐

  1. Javascript杂记&lpar;一&rpar;

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. VLC说明

    一.简介 vlc的全名是Video Lan Client,是一个开源的.跨平台的视频播放器.VLC支持大量的音视频传输.封装和编码格式,完整的功能特性列表可以在这里获得http://www.video ...

  3. matlab实现高斯消去法、LU分解

    朴素高斯消去法: function x = GauElim(n, A, b) if nargin < 2 for i = 1 : 1 : n for j = 1 : 1 : n A(i, j) ...

  4. Input File 表单上传按钮美化

    HTML <div class="input-file-button"> 上传图片<input type="file" class=&quot ...

  5. vue2&period;0 样式表引入的方法 css sass less

    在引入样式之前,首先要了解static.assets两个文件夹的区别. 从字面上可以看出,static用来存放静态文件,assets用来存放资源文件: static存放的文件不会被编译,打包后直接赋值 ...

  6. x宝23大洋包邮的老式大朝华MP3播放器简单评测

    (纯兴趣测评,非广告) 最近逛X宝,看到了这个古董级MP3播放器居然还在售,于是脑抽+情怀泛滥买了一个. 然后呢,从遥远的深圳跨越好几千公里邮过来了这个玩意: 那节南孚5号电池是我自己的,是为了对比一 ...

  7. MySQL 误删数据、误更新数据(update&comma;delete忘加where条件)

    MySQL 误操作后数据恢复(update,delete忘加where条件) 关键词:mysql误删数据,mysql误更新数据 转自:https://www.cnblogs.com/gomysql/p ...

  8. MiniUI破解方法

    解决JQuery MiniUI前端库到期alert弹窗 MINIUI的到期提示是通过JS的Alert 方法弹出的. 那么我们可以不可以截获所有Alert方法,过滤文本.然后….你们懂得 我们只需要在页 ...

  9. 西山居首页jQuery焦点图代码

    西山居首页jQuery焦点图代码是一款带文字描述,左右箭头,索引按钮,自动轮播切换的jQuery特效代码.效果图如下: 在线预览   源码下载 实现的代码. html代码: <div style ...

  10. 32位Windows7 利用多余的不能识别的电脑内存 RAMDISK5&period;5教程

    32位Windows7 利用多余的不能识别的电脑内存 RAMDISK5.5教程 环境:Windows7 32位 Ultimate 内存8GB 只能识别2.95GB内存 ramdisk5.5只适用于Wi ...