Lattice CrossLinkNx LIFCL-40应用连载4-RISC-V处理器访问I2CFIFO
作者:Hello,Panda
熊猫君这次分享的是如何通过CrosslinkNX的RISC-V软核来访问I2CFIFO软核。IIC是最常用的外设总线,通常一块板子上要挂好些个IIC器件,如果完全使用RTL状态机来实现总线控制的话,无疑非常的繁琐且非常的不友好,调试和使用起来也很不方便。熊猫君的这次分享就是为了解决这个问题,将Lattice的I2CFIFO模块挂到RISC-V软核的APB总线上,通过软件来实现IIC总线,I2CFIFO模块就相当于是RISC-V处理器的一个外设,按地址访问就行了。
在阅读这个例子之前,读者需要了解以下基本知识:
- lattice 基于RISC-V处理器的SOC基本开发流程;
- Arm的APB总线协议《AMBA 3 APB Protocol Specification》;
- Lattice的lmmi总线协议《Lattice Memory Mapped Interface and Lattice Interrupt Interface》,lattice内部文档编码UG-02039;
- 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 软件系统结构框图
- SoC方框内所有模块是在Propel中搭建的,其中APB IF是一个自定义的接口IP,目的是将APB总线引出并给其在RISC-V处理器中分配访问地址,自定义的IP的封装方法在本文中不再赘述;
- APB REG:这个模块是熊猫君自己写的,目的是解析APB总线数据并将其分发到工程中的各个模块,图2中只画了一个I2CFIFO,实际上挂了很多IP的;
- LMMI Master:这个模块是熊猫君自己写的,目的是为了实现LMMI总线访问逻辑,用于配置I2CFIFO。
其实,在本案中最关键的一步是如何将I2CFIFO模块挂载到APB总线上去,让RISC-V处理器可以访问它。
2.1 第一步:搭建Peopel工程
软件设计的第一步是在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第三步:搭建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 [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群或微信公众号交流讨论,同时也祝朋友们在新的一年里更上一层楼,牛得牛气冲天!另外,博主可提供基于Lattice Crosslink-nx全系列器件的软硬件方案和设计定制服务,也欢迎大家多多咨询!
博主可提供的基于Crosslink NX器件的软硬件方案定制服务包括:(1)MIPI转LVDS和LVDS转MIPI桥接;(2)MIPI接口的(CSI)的CMOS解串接收和显示屏驱动(DSI);(3)MIPI、LVDS、SubLVDS、Hispi接口的工业相机(USB3.0或PCIe接口输出);(4)MIPI、LVDS转USB3.0等。可直接定制的方案和应用见下表。
Lattice CrossLinkNx LIFCL-40应用连载4-RISC-V处理器访问I2CFIFO相关推荐
- Lattice CrossLinkNx LIFCL-40应用连载1:新建一个工程
作者:Hello,Panda 1.目的 编写Lattice CrossLinkNX此文目的是为了帮助新手利用Lattice Radiant Software创建第一个新工程. (1)软件开发平台:La ...
- Lattice CrossLinkNx LIFCL-40应用连载2-使用Reveal Inserter逻辑分析工具
作者:Hello,Panda Lattice系列工具使用在线调试器的方法比Xilinx要复杂,和Altera的类似,但还是要稍微复杂一些.本例在上一个产生50Hz方波的工程基础上进行说明. 1.添加L ...
- Lattice CrossLinkNx LIFCL-40应用连载3-使用RISC-V软核
作者:Hello,Panda Lattice CrosslinkNX内部集成了RISC-V指令集,可以支持RISC-V软核处理器,因此能够进行SoC的开发.RISC-V软核可以运行C程序,实现一些控制 ...
- 关于基于Lattice Crosslink-NX系列FPGA用于图像采集、桥接和处理的应用总结分享
作者:Hello,Panda Lattice Crosslink-NX系列FPGA的定位仍然还是主要面向接口桥接类的应用,虽然它的逻辑已经到了39K的规模,但其处理能力仍然偏弱,特别是存在以下几个很大 ...
- Lattice Crosslink-NX器件(LIFCL-40-7MG121I)用作视频输入桥接时支持的CMOS型号及可达性能汇总
作者:Hello,Panda 大家早上好.中午好.晚上好. 熊猫君前几天回老家了,手上没得啥新素材码字,决定水一文,将Lattice Crosslink-NX系列器件中熊猫君前一阵子用得比较多的那个型 ...
- risc系统服务器,精简的高端 解析四大RISC服务器处理器
也许您很难相信,作为我们今天仍在广泛使用的诸如"扣肉"之类的最新双核乃至是CPU(Center Prosessing Unit中央处理器),都是基于始创在上世纪60年代的CISC指 ...
- RISC V (RV32+RV64) 架构 整体介绍
文章目录 riscv 市场 芯片介绍 软件介绍 开发板介绍 PC介绍 riscv 架构 编程模型(指令集/寄存器/ABI/SBI) 运行状态 指令集 寄存器 riscv32和riscv64两者的区别 ...
- 计组学习笔记2(RISC v版)
指令集解释 (规定:R[r]表示通用寄存器r的内容,M[addr]表示存储单元addr的内容,SEXT[imm]表示对imm进行符号扩展,ZEXT[imm]表示对imm进行零扩展) 整数运算类 -U型 ...
- Lattice CrosslinkNx LIFCL-40应用连载5:如何使用MIPI D-PHY硬核IP
作者:Hello,Panda 一.目标 配置CMOS Sensor OV9734,并通过MIPI DPHY硬核接收数据. 使用一个RISC-V软核,实通过AHB总线访问I2C FIFO,通过I2C总线 ...
最新文章
- LifeCycle原理分析
- 15.selenium_case01
- [C++基础]018_常量指针和指向常量的指针
- java查看对象地址_如何获取到JAVA对象所在的内存地址
- python3.7安装pip问题_python3.7安装, 解决pip is configured with locations that require TLS/SSL问题...
- 【数据竞赛】可能是全网特征工程实操最通透的...
- JS判断一个数组中是否有重复值的三种方法
- Android跟web哪个好,比系统自带的WebView更好用 | AgentWeb
- 将tensor转array
- php __FILE__和$_SERVER['SCRIPT_FILENAME']区别
- 2020年什么编程语言最受欢迎,待遇最高?
- 关于MATLAB2014b不能并行计算的原因及解决方法
- 机器人学随堂笔记(1)ᝰ机器人简介、构成和分类
- 达梦数据库SQL语句执行
- 构建工具 | Gulp压缩JavaScript文件
- python re 正则提取中文
- w ndows10摄像头设置,windows10系统电脑摄像头怎么打开
- 【奥特曼迪迦表情包】
- VMware虚拟机配置IP地址
- 研报实现——抓“妖股”就那么容易吗?
热门文章
- Holographic MIMO Surfaces for 6G Wireless Networks: Opportunities, Challenges, and Trends阅读
- TypeError: load() missing 1 required positional argument: ‘Loader‘?
- LeetCode 807. 保持城市天际线 / 630. 课程表 III(贪心+优先队列)/ 851. 喧闹和富有(拓扑排序)
- Rmarkdown对电影数据集进行统计分析
- shell命令sh、dash、bash、rbash的区别
- JSP 弹出对话框的方式总结
- 不穷:阿里企业大脑最佳实践
- php使用递归完成以下图形,php试题与答案(一)
- python中的多态和鸭子模型
- VScode如何配置C语言编译环境