作者: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 3 APB 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 写入到偏移地址为 0x12 的寄存器中, bit 控制命令写入到偏移地址为 0x13 的寄存器中。 2.3 第三步:搭建 APB 到 LMMI 总线的转换逻辑 这一步在 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 i_apb_paddr ,input wire i_apb_pwdata ,output wire o_apb_pready ,output wire o_apb_pslverr ,output wire o_apb_prdata ,output wire o_iicfifo_lmmi_start ,output wire o_iicfifo_lmmi_mode ,output wire o_iicfifo_lmmi_offset ,output wire o_iicfifo_lmmi_wdata ,input wire i_iicfifo_lmmi_busy ,input wire i_iicfifo_lmmi_done ,input wire i_iicfifo_lmmi_rdata ,output wire o_iicfifo_reset ); reg r_apb_addr ; reg 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 r_apb_prdata ; reg r_iicfifo_reset ; reg r_apb_slv_reg1 ; reg r_apb_slv_reg2 ; reg r_apb_pready ; reg r_iicfifo_lmmi_start; reg r_iicfifo_lmmi_rready; reg r_iicfifo_lmmi_rflag ; reg r_iicfifo_lmmi_rdata ; reg r_iicfifo_lmmi_clean ; wire w_major_ver ; wire w_minor_ver ; wire w_build_year ; wire w_build_mon ; wire w_build_day ; wire w_apb_reg_wren ; wire w_apb_reg_rden ; wire w_apb_slv_reg0 ; wire 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 ,w_build_mon , w_build_day ,w_major_ver ,w_minor_ver }; 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 & (~r_iicfifo_lmmi_start)); assign o_iicfifo_lmmi_mode = r_apb_slv_reg1 ; assign o_iicfifo_lmmi_offset = r_apb_slv_reg1 ; assign o_iicfifo_lmmi_wdata = r_apb_slv_reg2 ; assign o_iicfifo_reset = (r_iicfifo_reset|r_apb_slv_reg1 ); assign w_apb_slv_reg1 = {i_iicfifo_lmmi_busy,r_iicfifo_lmmi_rready,r_apb_slv_reg1 }; 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 ) 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 <= 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 ) 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 ; r_iicfifo_reset <= r_apb_slv_reg1 ; if(r_apb_slv_reg1 & (~r_apb_slv_reg1 )) 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 i_addr_offset ,input wire i_reg_wdata ,output wire 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 o_lmmi_offset ,output wire o_lmmi_wdata ,input wire i_lmmi_rdata ,input wire i_lmmi_rdata_valid ,input wire i_lmmi_ready ); reg r_access_start; reg r_addr_offset ; reg r_reg_wdata ; reg r_reg_rdata ; reg r_access_mode ; reg r_access_busy ; reg r_access_done ; reg r_lmmi_request; reg 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 IIC_LMII_CTRL & IIC_LMII_CTRL_BUSY_MASK); while (reg_val){ IIC_LMII_CTRL & IIC_LMII_CTRL_BUSY_MASK); } //write wdata to register 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 ; IIC_LMII_CTRL = reg_val; } 至于后面的逻辑代码怎么写,调用底层的寄存器读写函数就可以了。 好了,今天的分享到此结束,欢迎大家加入 QQ 群或微信公众号交流讨论,同时也祝朋友们在新的一年里更上一层楼,牛得牛气冲天!