原创 lattice CrosslinkNx LIFCL-40应用连载4-RISC-V处理器访问I2CFIFO

2021-2-18 10:18 2509 24 4 分类: FPGA/CPLD 文集: Lattice
作者:Hello,Panda

熊猫君这次分享的是如何通过CrosslinkNXRISC-V软核来访问I2CFIFO软核。IIC是最常用的外设总线,通常一块板子上要挂好些个IIC器件,如果完全使用RTL状态机来实现总线控制的话,无疑非常的繁琐且非常的不友好,调试和使用起来也很不方便。熊猫君的这次分享就是为了解决这个问题,将LatticeI2CFIFO模块挂到RISC-V软核的APB总线上,通过软件来实现IIC总线,I2CFIFO模块就相当于是RISC-V处理器的一个外设,按地址访问就行了。

在阅读这个例子之前,读者需要了解以下基本知识:

(1)lattice 基于RISC-V处理器的SOC基本开发流程;

(2)ArmAPB总线协议《AMBA 3 APB Protocol Specification》;

(3)Latticelmmi总线协议《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

 /**************apb bus****************/

 ,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

                else begin

                           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

                else if(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

                else if(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群或微信公众号交流讨论,同时也祝朋友们在新的一年里更上一层楼,牛得牛气冲天!

作者: panda君, 来源:面包板社区

链接: https://mbb.eet-china.com/blog/uid-me-3403128.html

版权声明:本文为博主原创,未经本人允许,禁止转载!

文章评论1条评论)

登录后参与讨论

curton 2021-3-8 22:14

学习了
相关推荐阅读
panda君 2023-01-06 10:43
Lattice CrossLink-Nx LIFCL-40应用连载8:MIPI DSI接口驱动LCD显示器
作者:Hello,Panda  一、案例需求 使用LIFCL-40 FPGA的MIPI DSI硬核接口驱动7寸LCD显示器: (1)    显示器:7寸,...
panda君 2022-10-30 11:03
Lattice Crosslink-NX器件(LIFCL-40-7MG121I)用作视频输入桥接时支持的CMOS型号及可达性能汇总
作者:Hello,Panda大家早上好、中午好、晚上好。 熊猫君前几天回老家了,手上没得啥新素材码字,决定水一文,将Lattice Crosslink-NX系列器件中熊猫君前一阵子用得比较多的那个型...
panda君 2022-10-16 21:26
关于Radiant软件下Crosslink-NX物理层IP核MIPI_DPHY无法产生正确的非连续时钟时序的BUG修复办法
作者:Hello,Panda 一、问题描述: (1)器件:Lattice Crosslink-NX LIFCL-40-7MG121I ; (2)软件:Radiant 3.1; (3)MIPI_...
panda君 2022-10-12 16:21
易灵思Ti60 FPGA专题(1)-器件和需求介绍
作者:Hello,Panda各位朋友们,先生们,女士们,大家早上好,中午好,晚上好。熊猫君最近正在折腾HK的易灵思FPGA芯片做一个Camera,现在弄得差不多了,计划分享一期易灵思的专题,大概有6集...
panda君 2022-10-07 13:52
关于Xilinx ZYNQ Ultrascale+ MPSoC使用原生PS端DP接口实现Live模式输出的经验分享
作者:Hello,Panda今天熊猫君要分享的是如何使用Xilinx  ZYNQ Ultrascale+ MPSoC的PS端原生DP接口实现LIVE模式输出3860*2160,30Hz视频。...
panda君 2022-08-11 12:04
Crosslink-NX器件应用案例(2): MIPI的多源合成(MUX)与分发(DeMUX)
作者:Hello,Panda好久没有码文章了,今天讨论讨论MIPI多通道合成输出(MUX)和输入分发输出(DeMUX)的问题。这也是类似于Crosslink-NX此类器件的一个典型应用方向,通过增加传...
我要评论
1
24
关闭 站长推荐上一条 /2 下一条