打印
[FPGA]

Lattice CrossLinkNX软件开发入门分享之(4)-RISC-V处理器访问I2CFIFO

[复制链接]
547|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
作者:Hello,Panda
熊猫君这次分享的是如何通过CrosslinkNX的RISC-V软核来访问I2CFIFO软核。IIC是最常用的外设总线,通常一块板子上要挂好些个IIC器件,如果完全使用RTL状态机来实现总线控制的话,无疑非常的繁琐且非常的不友好,调试和使用起来也很不方便。熊猫君的这次分享就是为了解决这个问题,将Lattice的I2CFIFO模块挂到RISC-V软核的APB总线上,通过软件来实现IIC总线,I2CFIFO模块就相当于是RISC-V处理器的一个外设,按地址访问就行了。
在阅读这个例子之前,读者需要了解以下基本知识:
(1)lattice 基于RISC-V处理器的SOC基本开发流程;
(2)Arm的APB总线协议《AMBA 3APB Protocol Specification》;
(3)Lattice的lmmi总线协议《Lattice Memory Mapped Interface and Lattice Interrupt Interface》,lattice内部文档编码UG-02039;
(4)I2CFIFO模块使用手册《I2CFIFO Module- Lattice Radiant Software》,lattice内部文档编码IPUG-02064;
一、电路板硬件
熊猫君用的公司自制的评估板,板上集成了若干个IIC从设备,硬件原理图见下图1。
图1 评估板硬件原理图
由图中可以看出,IIC设备有扩展IO芯片、MIPI接口的图像传感器、时钟芯片、触摸屏等,通过一个IIC Switch芯片来进行通道选择,这样的硬件结构如果直接用RTL状态机实现,那无疑是非常复杂,因此通过挂到APB总线上作为一个RISC-V外设使用就相对简单多了。
二、软件设计
本案软件至少需要包括RISC-V处理器、程序运行RAM、总线仲裁器、I2CFIFO模块等,系统结构见图2所示。
图2 软件系统结构框图
(1)SoC方框内所有模块是在Propel中搭建的,其中APB IF是一个自定义的接口IP,目的是将APB总线引出并给其在RISC-V处理器中分配访问地址,自定义的IP的封装方法在本文中不再赘述;
(2)APB REG:这个模块是熊猫君自己写的,目的是解析APB总线数据并将其分发到工程中的各个模块,图2中只画了一个I2CFIFO,实际上挂了很多IP的;
(3)LMMI Master:这个模块是熊猫君自己写的,目的是为了实现LMMI总线访问逻辑,用于配置I2CFIFO。
其实,在本案中最关键的一步是如何将I2CFIFO模块挂载到APB总线上去,让RISC-V处理器可以访问它。
2.1 第一步:搭建Propel工程
软件设计的第一步是在Propel下搭建一个RISC-V软核工程,搭建的流程可以参考本系列分享3,搭建好的工程见下图3所示。
图3搭建好的Propel工程图
如图3所示,标记红色方框的就是自定义封装的一个APB接口IP,里面没有什么逻辑,就是将APB总线的若干信号引出SOC工程供顶层的Radiant模块调用,并且定义了地址空间范围,如下图4所示。
图4 给APB总线接口分配地址空间
如果做到这图4这一步,那么恭喜你,RISC-V处理器核已经可以正常访问该段地址对应的外设寄存器了,这个应用也就成功了一多半。
2.2第二步:例化I2CFIFO模组
Lattice I2CFIFO模组是无需额外的License授权的,在Radiant开发环境下可以免费使用,我们把这个模组例化成为FIFO模式,总线速率400KHz,例化配置见下图5所示,其他图中不所见的配置按默认即可。
图5  I2CFIFO例化配置界面
关于这个IP模块的使用请参见官方User Guide文档,配置成为FIFO模式的操作比较简单,软件编程时只需按照下图6所示的步骤配置寄存器即可完成I2C的读写访问。
图6 FIFO模式下I2C读写访问流程表
这里需要特别提醒注意的是:fifo模式下的寄存器是10bit位宽,需要分两次访问,如写fifo操作,bit[7:0]写入到偏移地址为0x12的寄存器中,bit[9:8]控制命令写入到偏移地址为0x13的寄存器中。
2.3第三步:搭建APBLMMI总线的转换逻辑
这一步在APB_REG模块和LMMI_Master模块中实现,当然也可以直接使用一个总线桥来做,但是这样做的话,这个APB总线接口就是专有的了,不利于系统的扩展使用。
这两个模块的代码如下:
//------------------------------------------------------------
//File Name: apb_reg_v1
//Project  : mipi dphy
//Module  :apb_reg
//Content  :
//Description : apb reg mapping      
//Spec.    :
//Author   : Hello,Panda
//------------------------------------------------------------
//History :
//20210205: V1.0 -Initial Creation
//------------------------------------------------------------
`timescale 1ns / 1ps
module apb_reg_v1 #(
  parameter     P_MAJOR_VERSION  =  4'd1   
,parameter     P_MINOR_VERSION   =   4'd0   
,parameter     P_BUILD_YEAR       =  12'd2021
,parameter     P_BUILD_MON       =  4'd2   
,parameter     P_BUILD_DAY        =   8'd6   
)(
  input  wire                   i_clk
,input  wire                   i_rst
/**************apbbus****************/
,input  wire                   i_apb_penable
,input  wire                   i_apb_psel
,input  wire                   i_apb_pwrite
,input  wire     [9 : 0]        i_apb_paddr
,input  wire     [31: 0]       i_apb_pwdata

,output wire                   o_apb_pready
,output wire                   o_apb_pslverr
,output wire      [31: 0]        o_apb_prdata
,output wire                   o_iicfifo_lmmi_start
,output wire                   o_iicfifo_lmmi_mode
,output wire      [7 : 0]        o_iicfifo_lmmi_offset
,output wire      [31: 0]        o_iicfifo_lmmi_wdata
,input  wire                   i_iicfifo_lmmi_busy
,input  wire                   i_iicfifo_lmmi_done
,input  wire     [31: 0]        i_iicfifo_lmmi_rdata
,output wire                   o_iicfifo_reset
);
reg                [9 : 0]         r_apb_addr       ;
reg                [31: 0]        r_apb_pwdata     ;
reg                            r_apb_reg_wren   ;  
reg                            r_apb_reg_rden   ;  
reg                            r_p1_apb_reg_wren;
reg                            r_p1_apb_reg_rden;
reg                            r_app_reg_rdout  ;
reg                [31 : 0]       r_apb_prdata     ;
reg                            r_iicfifo_reset  ;

reg                [31 : 0]       r_apb_slv_reg1   ;
reg                [31 : 0]       r_apb_slv_reg2   ;
reg                             r_apb_pready     ;

reg                             r_iicfifo_lmmi_start;
reg                            r_iicfifo_lmmi_rready;
reg                            r_iicfifo_lmmi_rflag ;
reg                [31 : 0]       r_iicfifo_lmmi_rdata;
reg                             r_iicfifo_lmmi_clean ;
wire            [3 : 0]            w_major_ver      ;
wire            [3 : 0]            w_minor_ver      ;
wire            [11: 0]            w_build_year     ;
wire            [3 : 0]            w_build_mon      ;
wire            [7 : 0]            w_build_day      ;
wire                            w_apb_reg_wren   ;
wire                            w_apb_reg_rden   ;
wire            [31: 0]            w_apb_slv_reg0   ;
wire            [31: 0]            w_apb_slv_reg1   ;   
assign  o_apb_pready  =   r_apb_pready   ;
assign  o_apb_pslverr  =   1'b0            ;
assign  o_apb_prdata  =   r_apb_prdata   ;
assign  w_major_ver   =   P_MAJOR_VERSION;
assign  w_minor_ver   =   P_MINOR_VERSION;
assign  w_build_year   =    P_BUILD_YEAR   ;
assign  w_build_mon   =   P_BUILD_MON  ;
assign  w_build_day    =   P_BUILD_DAY    ;  
assign  w_apb_slv_reg0  = {w_build_year[11:0],w_build_mon[3:0],
w_build_day[7:0],w_major_ver[3:0],w_minor_ver[3:0]};
assign  w_apb_reg_wren    = ((~r_p1_apb_reg_wren) & r_apb_reg_wren);
assign  w_apb_reg_rden    = ((~r_p1_apb_reg_rden) & r_apb_reg_rden);

assign  o_iicfifo_lmmi_start  = (r_apb_slv_reg1[9] &(~r_iicfifo_lmmi_start));
assign  o_iicfifo_lmmi_mode   = r_apb_slv_reg1[8];
assign  o_iicfifo_lmmi_offset =r_apb_slv_reg1[7:0];   
assign  o_iicfifo_lmmi_wdata  = r_apb_slv_reg2   ;
assign  o_iicfifo_reset       = (r_iicfifo_reset|r_apb_slv_reg1[10]);

assign  w_apb_slv_reg1    = {i_iicfifo_lmmi_busy,r_iicfifo_lmmi_rready,r_apb_slv_reg1[29:0]};

always @ (posedge i_clk)   
begin
           if(i_rst) begin      
                 r_apb_addr        <= 10'd0;
                 r_apb_reg_wren    <= 1'b0 ;
                 r_apb_reg_rden    <= 1'b0 ;   
                 r_p1_apb_reg_wren <= 1'b0;
                 r_p1_apb_reg_rden <= 1'b0;
                 r_apb_pwdata      <= 32'd0;
                 r_app_reg_rdout   <= 1'b0 ;
           end
           else begin
                 r_apb_reg_wren <= (i_apb_psel &i_apb_penable & i_apb_pwrite) ? 1'b1 : 1'b0 ;  
                 r_apb_addr    <= (i_apb_psel & (~i_apb_penable))  ? i_apb_paddr :
r_apb_addr    ; //latch addr
                 r_apb_reg_rden <= (i_apb_psel &(~i_apb_penable) & (~i_apb_pwrite)) ? 1'b1 : 1'b0 ;  
//begin read with wait
                 r_p1_apb_reg_wren <= r_apb_reg_wren ;
                 r_p1_apb_reg_rden <= r_apb_reg_rden ;  
                 r_apb_pwdata      <= (i_apb_psel & i_apb_penable& i_apb_pwrite &
r_apb_pready) ? i_apb_pwdata : r_apb_pwdata;
                 r_app_reg_rdout   <= w_apb_reg_rden ? 1'b1 : 1'b0;
           end
end

always @ (posedge i_clk)
begin                           
           if(i_rst) begin
              r_apb_pready           <= 1'b0;
           end
           else begin
                 if(i_apb_psel & (~i_apb_pwrite)) begin
                          if(~i_apb_penable) begin
                             r_apb_pready <= 1'b0;
                          end
                          else if(r_app_reg_rdout) begin
                                r_apb_pready <= 1'b1;
                          end
                 end
                 else begin
                          r_apb_pready <= 1'b1;
                 end
           end
end  

always @ (posedge i_clk)  
begin                     
           if(i_rst) begin   
                r_apb_slv_reg1  <= 32'h00000000;
                r_apb_slv_reg2  <= 32'h00000000;
           end
           else begin
                if(w_apb_reg_wren)begin
                         case(r_apb_addr[9:2])

                         8'd1: begin
                                   r_apb_slv_reg1   <= r_apb_pwdata;
                         end  

                         8'd2: begin
                                   r_apb_slv_reg2   <= r_apb_pwdata;  
                         end  

                         default: begin
                                   r_apb_slv_reg1   <= r_apb_slv_reg1;
                                   r_apb_slv_reg2   <= r_apb_slv_reg2;
                         end  

                         endcase   
                end
                elsebegin
                           r_apb_slv_reg1[10:9] <= 2'b00;
                end
           end
end   

always @ (posedge i_clk)  
begin                    
           if(i_rst) begin   
              r_apb_prdata         <= 32'd0;
              r_iicfifo_lmmi_clean <= 1'b0 ;
           end
           else begin
                 if(r_app_reg_rdout) begin
                         case (r_apb_addr[9:2])
                         8'd0 : begin r_apb_prdata <= w_apb_slv_reg0; end
                         8'd1 : begin r_apb_prdata <= w_apb_slv_reg1; end
                         8'd2 : begin r_apb_prdata <= r_apb_slv_reg2; end   
                         8'd3 : begin r_apb_prdata <= r_iicfifo_lmmi_rdata ;
                                             r_iicfifo_lmmi_clean <= 1'b1 ;
                         end

                         default : begin r_apb_prdata <= 32'd0; end

                   endcase
                 end  
                 else begin
                          r_iicfifo_lmmi_clean <= 1'b0;
                 end
           end
end   

always @ (posedge i_clk)
begin                  
           if(i_rst) begin
                r_iicfifo_lmmi_start  <= 1'b0;
                r_iicfifo_lmmi_rready<= 1'b0;  
                r_iicfifo_lmmi_rflag  <= 1'b0;
                r_iicfifo_lmmi_rdata  <= 32'd0;
                   r_iicfifo_reset       <= 1'b0;
           end
           else begin
                r_iicfifo_lmmi_start  <= r_apb_slv_reg1[9] ;
                   r_iicfifo_reset       <= r_apb_slv_reg1[10];

                if(r_apb_slv_reg1[9]& (~r_apb_slv_reg1[8])) begin
                          r_iicfifo_lmmi_rflag <= 1'b1;
                end
                elseif(i_iicfifo_lmmi_done) begin
                   r_iicfifo_lmmi_rflag   <= 1'b0;
                end

                if(r_iicfifo_lmmi_rflag& i_iicfifo_lmmi_done) begin
                          r_iicfifo_lmmi_rdata  <= i_iicfifo_lmmi_rdata;
                end

                if(r_iicfifo_lmmi_rflag& i_iicfifo_lmmi_done) begin
                          r_iicfifo_lmmi_rready  <= 1'b1;
                end
                elseif(r_iicfifo_lmmi_clean) begin
                          r_iicfifo_lmmi_rready  <= 1'b0;
                end

           end
end     
endmodule
//------------------------------------------------------------
//file name: lmmi_master_v1
//project  : mipi dphy
//module  :
//content  :
//description : generate lmmi acess logic            
//spec.    :
//author   : hello,panda
//------------------------------------------------------------
//history :
//20210205: v1.0 -initial creation
//------------------------------------------------------------
`timescale 1ns / 1ps
module lmmi_master_v1(
  input  wire                     i_clk
,input  wire                      i_rst

,input  wire      [7 : 0]          i_addr_offset   
,input  wire      [31: 0]          i_reg_wdata
,output wire       [31: 0]          o_reg_rdata
,input  wire                      i_access_start  
,input  wire                      i_access_mode     //1-write mode; 0-read mode
,output wire                      o_access_done
,output wire                      o_access_busy

,output wire                      o_lmmi_request
,output wire                      o_lmmi_wr_rdn
,output wire       [7 : 0]           o_lmmi_offset   
,output wire       [31: 0]          o_lmmi_wdata
,input  wire      [31: 0]          i_lmmi_rdata               
,input  wire                      i_lmmi_rdata_valid
,input  wire                      i_lmmi_ready
);

reg                               r_access_start;
reg                 [7 : 0]         r_addr_offset ;
reg                 [31: 0]          r_reg_wdata  ;
reg                 [31: 0]          r_reg_rdata  ;
reg                               r_access_mode ;
reg                               r_access_busy ;
reg                                r_access_done ;
reg                               r_lmmi_request;
reg               [3 : 0]            r_lmmi_state ;
reg                               r_lmmi_wr_rdn ;

localparam     st_idel   = 4'b0000;
localparam     st_wr     = 4'b0001;
localparam     st_rd     = 4'b0010;
localparam     st_wait   = 4'b0100;
localparam     st_done   = 4'b1000;        

assign  o_reg_rdata    = r_reg_rdata                   ;
assign  o_access_done  = r_access_done                 ;
assign  o_access_busy  = (r_access_start|r_access_busy);
assign  o_lmmi_request =  r_lmmi_request                ;
assign  o_lmmi_wr_rdn  = r_lmmi_wr_rdn                 ;
assign  o_lmmi_offset  = r_addr_offset                 ;
assign  o_lmmi_wdata   =  r_reg_wdata                   ;

always @ (posedge i_clk)
begin
            if(i_rst) begin
                r_access_start  <= 1'b0 ;
                r_addr_offset   <= 8'd0 ;
                r_reg_wdata     <= 32'd0;
                r_access_mode   <= 1'b0 ;
            end
            else begin
                if(i_access_start & (~r_access_busy))begin
                   r_addr_offset   <= i_addr_offset ;
                   r_reg_wdata     <= i_reg_wdata   ;
                      r_access_mode  <=  i_access_mode ;
                end
                r_access_start     <= i_access_start;
            end
end

always @ (posedge i_clk)   
begin                    
            if(i_rst) begin
                r_lmmi_state  <=  st_idel ;
                r_lmmi_request <= 1'b0     ;
                r_reg_rdata   <= 32'd0    ;  
                r_access_done <= 1'b0     ;
                     r_lmmi_wr_rdn <= 1'b0     ;
            end
            else begin
               case (r_lmmi_state)

               st_idel : begin
                         if(r_access_start) begin
                             r_lmmi_state <=  st_wr ;
                         end   
                         r_lmmi_request   <= 1'b0;
                         r_access_done    <= 1'b0;
                               r_lmmi_wr_rdn    <= 1'b0;
               end

               ST_WR : begin
                         if(i_lmmi_ready) begin
                            r_lmmi_state  <=  r_access_mode ? ST_WAIT :ST_RD;
                            r_lmmi_request <=  1'b1   ;
                                      r_lmmi_wr_rdn <=  r_access_mode;
                         end
               end

               ST_RD : begin
                        r_lmmi_request  <= 1'b0;
                              r_lmmi_wr_rdn   <= 1'b0;
                        if(i_lmmi_rdata_valid) begin
                             r_reg_rdata  <= i_lmmi_rdata;
                             r_lmmi_state<= ST_WAIT     ;
                        end
               end  

               ST_WAIT : begin
                         r_lmmi_request  <= 1'b0;
                               r_lmmi_wr_rdn   <= 1'b0;
                         if(i_lmmi_ready) begin
                             r_lmmi_state <= ST_DONE;  
                             r_access_done<= 1'b1   ;
                         end
               end

               ST_DONE : begin
                         r_lmmi_request  <= 1'b0;
                               r_lmmi_wr_rdn   <= 1'b0;
                         r_lmmi_state    <= ST_IDEL;
                         r_access_done   <= 1'b0;
               end

               default : begin
                         r_lmmi_request  <= 1'b0;
               r_lmmi_wr_rdn   <= 1'b0;                           
                         r_lmmi_state    <= ST_IDEL;  
                         r_access_done   <= 1'b0;
               end
              endcase
            end
end  

always @ (posedge i_clk)      
begin                          
            if(i_rst) begin   
                r_access_busy <= 1'b0;
            end
            else begin
                if(r_access_start) begin
                      r_access_busy <= 1'b1;
                end
                else if(r_lmmi_state == ST_DONE) begin
                      r_access_busy <= 1'b0;
                end              
            end
end            
endmodule
好了,这两个模块的代码已经贴出来了,这里就不再多作解释。
2.4第四步:在Propel SDK下编写C代码
这一步就是在软件访问I2CFIFO模块寄存,根据2.3第三步描述的逻辑,写好读写寄存器的底层函数即可,比如写I2CFIFO模块寄存器的语句如下:
void iiclmmiwrite(uint8_t offset,uint32_t wdata)
{
uint32_t reg_val;
    //check lmii bus idle
reg_val = (APB_REG->IIC_LMII_CTRL &IIC_LMII_CTRL_BUSY_MASK);
while(reg_val){
    reg_val = (APB_REG->IIC_LMII_CTRL &IIC_LMII_CTRL_BUSY_MASK);
}
//write wdata to register
APB_REG->IIC_LMII_WDATA = wdata;
//write ctrl to start
reg_val = offset;
reg_val |= IIC_LMII_CTRL_PWRITE_MASK;
reg_val |= IIC_LMII_CTRL_START_MASK ;
APB_REG->IIC_LMII_CTRL = reg_val;
}
至于后面的逻辑代码怎么写,调用底层的寄存器读写函数就可以了。
好了,今天的分享到此结束,欢迎大家加入QQ群或微信公众号交流讨论,同时也祝朋友们在新的一年里更上一层楼,牛得牛气冲天!


使用特权

评论回复

相关帖子

发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:Hello,Panda

29

主题

63

帖子

5

粉丝