手搓8080接口,封装成AXI4‐ip,用zynq驱动(一) - minichao9901/TangNano-20k-Zynq-7020 GitHub Wiki

手搓8080接口,封装成AXI4-ip,用zynq驱动(一)

1)说明

  • st7796是sitronix公司经典的小尺寸lcd_driver驱动芯片
  • 手头的这款屏尺寸是3.5寸,分辨率是320x480
  • 这个模组的引脚定义如下。这次我们选择的是8080接口16bits模式,也就是im[2:0]配置成3'b010

image image 62298ce4da42a02193b5f9a21efa1036

2)fmc16b代码

代码的构思过程

  • 1.1 序列机or状态机? 用序列机不太好,因此存在等待状态。等待状态是等待xfer_start以进行下一次读写。因此序列长度是不固定的。全局控制用状态机更好。序列机还是要的,用在局部,控制一次传输的节拍产生。一次传输节拍是确定的。

  • 1.2 几个状态?IDLE,SEND_CMD, SEND_DATA, SEND_WAIT。注意这里存在一个等待状态。当传输一定长度数据时,必须要等待数据传完,才能拉高CS。整个传输期间CS为低。

  • 1.3 几个控制信号? xfer_start由外部上位机给。xfer_end由内部产生,每次传输完成产生一个xfer_end通知上位机。xfer_start/xfer_end形成交互。

  • 1.4 几个计数器?lsm_cnt用于一次传输的内部节拍产生,相当于是局部序列机。xfer_cnt是一个减计数器,用于记录传输次数。当xfer_cnt为0时表示传输完成。

  • 1.5 状态机的跳转条件(xfer_start, xfer_end, xfer_cnt): IDLE状态,当收到xfer_start则进入SEND_CMD状态。 SEND_CMD状态,当收到xfer_end时,如果xfer_cnt=0则进入IDLE状态(xfer_cnt=0表示只发cmd),否则进入SEND_WAIT状态。 SEND_DATA状态,当收到xfer_end时,如果xfer_cnt=1则进入IDLE状态(表示最后一个数据已发完,为什么这里是=1与xfer_cnt的产生条件有关系),否则进入SEND_WAIT状态。 SEND_WAIT状态,当收到xfer_start时,进入SEND_DATA状态

  • 1.6 xfer_end的产生条件: 与wr=1的时刻一致,需要根据序列机来产生。通过仿真看波形图来调整。 由于xfer_end的宽度是一个脉宽,因此不能与wrx/rdx/csx等信号放在一起产生,而是需要单独一个always产生。

  • 1.7 输出csx/dcx/wrx/rdx/do等信号的产生 比较简单,根据时序图,用excel表列出每拍干什么就可以了。根据仿真微调一下。

  • 1.7 xfer_cnt的计数条件 IDLE状态,且收到xfer_start时,将length赋给xfer_cnt作为初值。 SEND_DATA状态,每次收到xfer_end时,xfer_cnt=xfer_cnt-1

  • 1.8 lsm_cnt的计数条件 lsm_cnt定位为局部计数器,因此位宽很小。 IDLE和SEND_WAIT状态清零,也就是不计数。 否则(SEND_CMD, SEND_DATA状态)计数。这个计数器让它自然计数,不需要设置清零,因为由于每次发送需要多少个cycle是确定的,lsm_cnt不会等到溢出就被清零了,因为跳转到SEND_WAIT状态或IDLE状态就会自动清零。

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2024/02/23 16:19:06
// Design Name: 
// Module Name: fmc
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////

module fmc(
    input sys_clk,
    input sys_rst_n,
    
    input xfer_start,
    output reg xfer_end,
    
    input [15:0] data_in, //cmd or data
    input [17:0] length,
    
    output reg csx,
    output reg dcx,
    output reg wrx,
    output reg rdx,
    output reg [15:0] do
    );
    
parameter LSM_MAX=4'd15;
reg [3:0] lsm_cnt;

parameter IDLE=2'b00;
parameter SEND_CMD=2'b01;
parameter SEND_DATA=2'b10;
parameter SEND_WAIT=2'b11;

reg [1:0] state;
reg [17:0] xfer_cnt;

always @(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==0)
    state<=IDLE;
else case(state)
    IDLE: if(xfer_start) 
            state<=SEND_CMD;
    SEND_CMD: if(xfer_end && xfer_cnt==0)
                state<=IDLE;
              else if(xfer_end)
                state<=SEND_WAIT;
    SEND_DATA: if(xfer_end && xfer_cnt==1)
                state<=IDLE;
               else if(xfer_end)
                state<=SEND_WAIT;
    SEND_WAIT: if(xfer_start)
                state<=SEND_DATA;
    default: state<=IDLE;
endcase

always @(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==0)
    xfer_cnt<=18'd0;
else if(state==IDLE && xfer_start)
    xfer_cnt<=length;
else if(state==SEND_DATA && xfer_end)
    xfer_cnt<=xfer_cnt-1;

    
always @(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==0)
    lsm_cnt<=0;
else if(state==IDLE || state==SEND_WAIT)
    lsm_cnt<=0;  
else
    begin
        if(lsm_cnt==LSM_MAX-1)
            lsm_cnt<=0;
        else
            lsm_cnt<=lsm_cnt+1;   
    end
 
always @(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==0) begin
    csx<=1;
    dcx<=1;
    wrx<=1;
    do<=16'h00;
end
else case(state)
    IDLE: csx<=1;  //begin and final state
    SEND_CMD: begin            
         if(length==0)
             case(lsm_cnt)
                0: begin csx<=0; dcx<=0; wrx<=0; do<=data_in; end
                1: begin wrx<=1;end
                2: begin csx<=1;end               
            endcase 
        else
            case(lsm_cnt)
                0: begin csx<=0; dcx<=0; wrx<=0; do<=data_in; end
                1: begin wrx<=1;end
            endcase           
     end
     
     SEND_DATA: begin
            dcx<=1;
    
            if(lsm_cnt==0)
                begin wrx<=0; do<=data_in; end
            else if(lsm_cnt==1)
                wrx<=1;                              
     end   
     SEND_WAIT: csx<=0;
        
endcase

//xfer_end要单独产生,因为它只占一个aclk脉宽,与其它信号不一样
always @(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==0) begin
    xfer_end<=0;
end
else if(state==SEND_CMD && length==0 && lsm_cnt==2)
    xfer_end<=1;
else if(state==SEND_CMD && length>0 && lsm_cnt==1)
    xfer_end<=1;    
else if(state==SEND_DATA && lsm_cnt==1)
    xfer_end<=1;
else
    xfer_end<=0; 
    
endmodule

3)写testbench验证裸核读写(波形略)

`timescale 1ns / 1ps
module tb;

reg sys_clk, sys_rst_n;
initial sys_clk=0;
always #10 sys_clk=~sys_clk;

initial begin
    sys_rst_n=0;
    #1000;
    sys_rst_n=1;
end 

bit xfer_start;
bit [15:0] data_in;
bit [15:0] length;

initial begin
    wait(sys_rst_n==1);
    
    #100;
    data_in=16'h2A;
    length=0;
    xfer_start=0; #20; xfer_start=1; #30; xfer_start=0;    
    
    #5000;
    data_in=16'h36;
    length=4;
    xfer_start=0; #20; xfer_start=1; #30; xfer_start=0;     

    wait(fmc_inst.state==2'b11);
    data_in=16'h23;
    xfer_start=0; #20; xfer_start=1; #30; xfer_start=0;    
    
    wait(fmc_inst.state==2'b11);
    data_in=16'h34;
    xfer_start=0; #20; xfer_start=1; #30; xfer_start=0;    
    
    wait(fmc_inst.state==2'b11);
    data_in=16'h45;
    xfer_start=0; #20; xfer_start=1; #30; xfer_start=0;    
    
    wait(fmc_inst.state==2'b11);
    data_in=16'h56;
    xfer_start=0; #20; xfer_start=1; #30; xfer_start=0;                

end

fmc fmc_inst(
    .sys_clk(sys_clk),
    .sys_rst_n(sys_rst_n),
    
    .xfer_start(xfer_start),
    
    .data_in(data_in), //cmd or data
    .length(length)
    );

endmodule

4)写testbench验证裸核发图,并上板验证(波形略)

  • 综合生成bitstream后,可以产生固定的单色图。
`timescale 1ns / 1ps
module fmc_wrapper(
    input sys_clk,
    input sys_rst_n,    
    
    output csx,
    output dcx,
    output wrx,
    output rdx,
    output [15:0] do,
    
    output [2:0] im,
    output rst_o
    );

assign im=3'b010;
assign rst_o=sys_rst_n;

parameter NDATA=320*480;
parameter XSTART=0, XEND=319;
parameter YSTART=0, YEND=479;
parameter  RED     =   16'hF800,
            ORANGE  =   16'hFC00,
            YELLOW  =   16'hFFE0,
            GREEN   =   16'h07E0,
            CYAN    =   16'h07FF,
            BLUE    =   16'h001F,
            PURPPLE =   16'hF81F,
            BLACK   =   16'h0000,
            WHITE   =   16'hFFFF,
            GRAY    =   16'hD69A;

parameter DIV_MAX=5-1;
parameter LSM_MAX=NDATA+20-1;

reg xfer_start;
wire xfer_end;    
    
reg [15:0] data_in;
reg [17:0] length;   
 
reg [25:0] div_cnt; 
reg [17:0] lsm_cnt;
always @(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==0)
    div_cnt<=0; 
else if(div_cnt==DIV_MAX) 
    div_cnt<=0;
else
    div_cnt<=div_cnt+1;
    
always @(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==0)
    lsm_cnt<=0; 
else if(div_cnt==DIV_MAX && lsm_cnt==LSM_MAX) 
    lsm_cnt<=LSM_MAX;
else if(div_cnt==DIV_MAX)
    lsm_cnt<=lsm_cnt+1;  
    

reg [2:0] color_index;
always @(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==0)
    color_index<=0; 
else if(div_cnt==DIV_MAX && lsm_cnt==LSM_MAX) 
    color_index<=color_index+1;   

reg [15:0] color;    
always @(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==0)
    color<=16'h0000;
else case(color_index)
    0: color<= RED;
    1: color<= GREEN;
    2: color<= BLUE;
    3: color<= YELLOW;
    4: color<=  PURPPLE;
    default: color<= WHITE;
  endcase     
    
always @(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==0)  begin
    xfer_start<=0;
    data_in<=16'h00;
    length<=16'd0;
end
else if(div_cnt==DIV_MAX) begin
    case(lsm_cnt)
        0: begin length<=0; data_in<=16'h11; xfer_start<=1; end
        1: begin length<=0; data_in<=16'h29; xfer_start<=1; end
        2: begin length<=1; data_in<=16'h36; xfer_start<=1; end
        3: begin data_in<=16'h48; xfer_start<=1; end      
        4: begin length<=1; data_in<=16'h3a; xfer_start<=1; end
        5: begin data_in<=16'h05; xfer_start<=1; end   
        
        6: begin length<=4; data_in<=16'h2a; xfer_start<=1; end
        7: begin data_in<=XSTART/256; xfer_start<=1; end
        8: begin data_in<=XSTART%256; xfer_start<=1; end    
        9: begin data_in<=XEND/256; xfer_start<=1; end    
        10: begin data_in<=XEND%256; xfer_start<=1; end  
        
        11: begin length<=4; data_in<=16'h2b; xfer_start<=1; end
        12: begin data_in<=YSTART/256; xfer_start<=1; end
        13: begin data_in<=YSTART%256; xfer_start<=1; end    
        14: begin data_in<=YEND/256; xfer_start<=1; end    
        15: begin data_in<=YEND%256; xfer_start<=1; end 
        
        16: begin length<=NDATA; data_in<=16'h2c; xfer_start<=1; end
        default: begin data_in<=GREEN; xfer_start<=1; end                                                           
    endcase
end
else
    xfer_start<=0;
 
fmc fmc_inst (
   // Input Ports - Single Bit
   .sys_clk       (sys_clk),    
   .sys_rst_n     (sys_rst_n),  
   .xfer_start    (xfer_start), 
   // Input Ports - Busses
   .data_in (data_in),
   .length  (length),
   // Output Ports - Single Bit
   .csx           (csx),        
   .dcx           (dcx),        
   .rdx           (rdx),        
   .wrx           (wrx),        
   .xfer_end      (xfer_end),   
   // Output Ports - Busses
   .do     (do)   
   // InOut Ports - Single Bit
   // InOut Ports - Busses
); 
    
endmodule
`timescale 1ns / 1ps
module fmc_wrapper_test;
reg sys_clk, sys_rst_n;
initial sys_clk=0;
always #10 sys_clk=~sys_clk;

initial begin
    sys_rst_n=0;
    #1000;
    sys_rst_n=1;
end 

fmc_wrapper fmc_wrapper_inst(
    .sys_clk(sys_clk),
    .sys_rst_n(sys_rst_n)
    );

defparam fmc_wrapper_inst.NDATA=10;    

endmodule

5)封装成AXI4-ip

关键代码:接口,寄存器读写,例化,如何产生xfer_start和使用xfer_end

		// Users to add ports here
        output csx,
        output dcx,
        output wrx,
        output rdx,
        output [15:0] do,

        output xfer_start,
        output xfer_end,         
		// User ports ends

	always @( posedge S_AXI_ACLK )
	begin
	  if ( S_AXI_ARESETN == 1'b0 )
	    begin
	      slv_reg0 <= 0;
	      slv_reg1 <= 0;
	      slv_reg2 <= 0;
	      slv_reg3 <= 0;
	      slv_reg4 <= 0;
	      slv_reg5 <= 0;
	      slv_reg6 <= 0;
	      slv_reg7 <= 0;
	    end
	  if(xfer_end) begin
	       slv_reg3<=32'h1;
	  end 


	// Add user logic here
    /*
    slv_reg0 => xfer_start
    slv_reg1 => data_in
    slv_reg2 => length
    slv_reg3[0] => xfer_end
    */	
    reg xfer_start;
    always @( posedge S_AXI_ACLK )
    if ( S_AXI_ARESETN == 1'b0 )
        xfer_start<= 0;
    else
        xfer_start <= (slv_reg_wren && axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB]==2'h0)? 1:0;
        
        
    fmc fmc_inst (
       // Input Ports - Single Bit
       .sys_clk       (S_AXI_ACLK),    
       .sys_rst_n     (S_AXI_ARESETN),  
       .xfer_start    (xfer_start),       
       // Input Ports - Busses
       .data_in (slv_reg1[15:0]),
       .length  (slv_reg2[17:0]),
       // Output Ports - Single Bit
       .csx           (csx),        
       .dcx           (dcx),        
       .rdx           (rdx),        
       .wrx           (wrx),        
       .xfer_end      (xfer_end),   
       // Output Ports - Busses
       .do     (do)   
       // InOut Ports - Single Bit
       // InOut Ports - Busses
    ); 

	// User logic ends

6)用VIP验证

  • 代码的特别之处:我们使用了task,以简化重复的操作。
`timescale 1ns / 1ps

//此处import报错为vivado bug,不影响仿真
//Import two required packages: axi_vip_pkg and <component_name>_pkg.
//how to get component_name? use tcl command: get_ips *vip*
import axi_vip_pkg::*;
import system_axi_vip_0_0_pkg::*;

module my_sim_top;
    
  bit sys_clk;
  bit sys_rst_n;
  bit csx,dcx,rdx,wrx;
  bit xfer_start,xfer_end;
  bit [15:0] dout;

  system_wrapper system_wrapper_i
       (.csx(csx),
        .dcx(dcx),
        .dout(dout),
        .rdx(rdx),
        .sys_clk(sys_clk),
        .sys_rst_n(sys_rst_n),
        .wrx(wrx),
        .xfer_end(xfer_end),
        .xfer_start(xfer_start));    
        
//AXI4-Lite signals
xil_axi_resp_t 	resp;
bit[31:0]  addr, data, base_addr = 32'h44A00000, switch_state;  

parameter NDATA=320*480;
parameter XSTART=0, XEND=319;
parameter YSTART=0, YEND=479;
parameter  RED     =   16'hF800,
            ORANGE  =   16'hFC00,
            YELLOW  =   16'hFFE0,
            GREEN   =   16'h07E0,
            CYAN    =   16'h07FF,
            BLUE    =   16'h001F,
            PURPPLE =   16'hF81F,
            BLACK   =   16'h0000,
            WHITE   =   16'hFFFF,
            GRAY    =   16'hD69A;      

//创建时钟和复位信号
// Generate the clock : 50 MHz
initial sys_clk=0;    
always #10ns sys_clk = ~sys_clk;
initial begin
    //Assert the reset
    sys_rst_n = 0;
    #340ns
    // Release the reset
    sys_rst_n = 1;
end   

//创建axi_vip的agent
//<component_name>_mst_t
system_axi_vip_0_0_mst_t      master_agent;
    
initial begin    

    // Step 4 - Create a new agent
    master_agent = new("master vip agent",system_wrapper_i.system_i.axi_vip_0.inst.IF);
    
    // Step 5 - Start the agent
    master_agent.start_master();
    
    //Wait for the reset to be released
    wait (sys_rst_n == 1'b1);
    
    #1us
    set_length(32'h00);    
    send_data(32'h11);
    send_data(32'h29);    

    #1us
    set_length(32'h01);    
    send_data(32'h36);
    send_data(32'h48);     
    
    #1us
    set_length(32'h01);    
    send_data(32'h3a);
    send_data(32'h05);         
    
    #1us
    set_length(32'h04);    
    send_data(32'h2a);
    send_data(XSTART/256);  
    send_data(XSTART%256);             
    send_data(XEND/256);  
    send_data(XEND%256); 
    
    #1us
    set_length(32'h04);    
    send_data(32'h2b);
    send_data(YSTART/256);  
    send_data(YSTART%256);             
    send_data(YEND/256);  
    send_data(YEND%256);   
    
    #1us
    set_length(NDATA+4);    
    send_data(32'h2c);
    for(int i=0; i<NDATA; i++) begin
        send_data(RED);              
    end              
end

task set_length(input logic [31:0] data);
    // set length
    addr = 32'h08;
    master_agent.AXI4LITE_WRITE_BURST(base_addr + addr, 0, data, resp);
endtask

task send_data(input logic [31:0] data);
    // set cmd
    addr = 32'h04;
    master_agent.AXI4LITE_WRITE_BURST(base_addr + addr, 0, data, resp);

    // xfer_start
    addr = 32'h00;
    data = 32'h01;
    master_agent.AXI4LITE_WRITE_BURST(base_addr + addr, 0, data, resp);

    // wait xfer_end
    wait(xfer_end == 1);
endtask
      

//  initial begin
//    $monitor("Signal tx changed to %b at time %t", tx, $time);
//  end 
    
endmodule

我们看到,如下0x2a指令的执行,实际需要85个cycles。

    set_length(32'h04);    
    send_data(32'h2a);
    send_data(XSTART/256);  
    send_data(XSTART%256);             
    send_data(XEND/256);  
    send_data(XEND%256); 

7)底层波形

fmc16底层波形,发一条纯指令0x11的执行过程

fmc16底层波形,发一条配寄存器{0x36, 0x48}的执行过程

8)状态转移图

   +----------+             +-------------+
   |          |             |             |
   |   IDLE   | -- xfer_start --> |  SEND_CMD  |
   |          |             |             |
   +----------+             +-------------+
         |
    xfer_end=1
         v
   +-----------+
   |           |
   | SEND_WAIT |
   |           |
   +-----------+
         |
    xfer_start
         v
   +------------+
   |            |
   | SEND_DATA  |
   |            |
   +------------+
         |
    xfer_end=1
         v
   +-----------+
   |           |
   | SEND_WAIT |
   |           |
   +-----------+