一、 时钟配置与使用
1. 外部晶振与内部振荡器;
2. PLL倍频与分频;
3. cpld可用的时钟;
4. 几个时钟的设置限制;
5. cpld的最高频率;
二、mcu与cpld的交互
1. mcu传递信号给cpld;
2. cpld传递信号给mcu;
3. mcu从cpld里读写数据;
三、 mcu与cpld的apb数据交互
四、 ADC样例与UartTx样例
五、 dma在cpld中的使用
六、 cpld中使用ram
七、 一些小技巧:如何定义信号量数组
1
1、 外部晶振 与 内部振荡器 的使用:
mcu和cpld联合编程时,整颗芯片需要一颗外部晶振。(芯片有内部振荡器,但误差较大,校准后有5%以内误差,参考后续介绍)该晶振是mcu和cpld公用的(没必要为cpld再单独提供一颗晶振)。
晶振可以是有源得,也可以是无源得。
【注:这里得外部晶振配置,跟单纯使用MCU是一样的】
如果是无源晶振。频率限制再4M~16M.要接到芯片得OSC_IN/OUT引脚。VE中直接定义主频多少M即可。如:
(这是目前开发板上使用的配置)
如果是有源晶振,频率无限制。根据接入点分为两种情况:
1)如果接入倒OSC_IN引脚:
ve里定义同上(修改HSECLK项的值为有源晶振频率)。
同时,需要在platformio.ini里增加配置:
BOARD_HSE_BYPASS=SYS_HSE_BYPASS_ON,
2)如果接入到别的IO引脚(如PIN_2):
VE配置中,除了配置HSECLK项外,还需要配置PLL_CLKIN项,如图:
同时,需要在platformio.ini里增加配置:
BOARD_HSE_BYPASS=SYS_HSE_NONE
如果使用内部振荡器:
校准后精度大约在5%以内,节省成本且对时钟要求不高的话可以使用。
使用方式:
在VE里增加:"PLL_CLKIN PIN_OSC",如下图:
(注:不用配置HSECLK项)
同时在platformio.ini里增加配置:
BOARD_HSE_BYPASS=SYS_HSE_NONE
注:自动校准目前有以下使用限制:
a)逻辑部分要压缩,platformio.ini中配置:
board_logic.compress = true
b)校准动作是在烧录时进行的。
烧录时,需要使用swd方式且通过我们的软件烧录,uart不支持。
(即:出厂烧录不支持uart方式)
目前测试过jlink和dap校准结果都还不错,但是也出现过一个客户使用其他烧录器校准结果差很多。
(对一个全新的或是wipe过后的芯片烧录会看到校准信息)
2、PLL倍频及分频:
整颗芯片只有一个PLL倍频模块(mcu和cpld共用)。
倍频分频操作是封装在系统内部的(用户无须也不能控制这个时钟树)。
实现原理:
A.系统会根据所有用到的频率项(mcu和cpld要用到的全部频率),计算出他们的最小公倍数。该数值就是要倍频到的目标值;
B.以外部时钟作为输入,PLL倍频到这个目标值,然后再以这个目标值为基准,分频给mcu各外设和cpld来使用。
C.倍频和分频,无须开发者关注。
开发者只要设置好自己需要的各个时钟频率即可。
开发者可设置的频率分为mcu部分和cpld部分。
mcu部分,只需要关注系统主频。
主频是在VE里通过SYSCLK项配置,该主频是mcu的工作频率。
外设频率则基于这个主频再分频(参考各个外设的驱动部分)。
cpld部分,cpld最多可以输入5路不同频率的时钟。
默认情况下,cpld工程接口中输入到cpld的sys_clock,就是跟mcu同频的SYSCLK系统时钟(由VE决定多少M)。
Bus_clock则是在SYSCLK基础上进行分频的另一路时钟(其实就是后续的PLLCLK3)。
Bus_clock在VE中频率定义(必须是SYSCLK的整数倍分频):
如果ve里没有定义BUSCLK,则bus_clock和sys_clock同频。
bus_clock是为了防止cpld部分速度跟不上sysclk而设定的。
cpld中除了这两路(其实就是0路和第3路),还有3路可以使用。
3、 cpld可用的时钟(除去SYSCLK的另外4路):
cpld的时钟除了以上输入的sys_clock,还有4路可以独立使用。
参考《AGRV2K_逻辑设置.pdf》,如下图:
这里的PLLCLK1、PLLCLK2、PLLCLK3、PLLCLK4就是可使用的独立时钟。
注意:当mcu中使用USB时,PLLCLK1自动给了USB,不能再使用;当 mcu中使用了MAC时,PLLCLK2自动给了MAC,不能再使用。另外,上述的BUSCLK对应的是这里的PLLCLK3。如果用了BUSCLK的名字,这里的PLLCLK3就不能再用。
这里整理下5路时钟:
PLLCLK0:就是SYSCLK(名字使用SYSCLK)
PLLCLK1:开USB时,这路时钟给USB用(60M),不开USB时给用户用;
PLLCLK2:开MAC时,这路时钟给MAC用(25/50M),不开MAC时给用户用;
PLLCLK3:用BUSCLK时(只能是sysclk整数分频)不能用 PLLCLK3,否则可用;
PLLCLK4:独立给用户使用;
以PLLCLK3和PLLCLK4为例,说明怎么使用该时钟。
在VE里配置如下:
PLLCLK3 40 # 40MHz
PLLCLK4 60 # 60MHz
PLL_CLKOUT3 pll_clk3
PLL_CLKOUT4 pll_clk4
则可定义pllclk3为40M输入,pllclk4为60M输入。
在生成的cpld入口处,分别对应信号pll_clk3和pll_clk4,如图:
输入的时钟,可以跟sys_clock一样使用。
4、 几个时钟的设置限制及计算方式:
上边提到的倍频后PLL目标值,其数值关系需要满足:
A. PLL=HSE*X/Y,X,Y皆为整数
B. PLL小于1200MHZ。
C. 所有的设置频率必须能被这个最终PLL整除。
举例:mcu主频100M,系统用了MAC(50M),系统用了USB(60M),cpld自定义了
PLLCLK3为80M,cpld自定义了PLLCLK4为60M。则,PLL目标值就是10050608060的最小公倍数,为1200M。
如果使用到一些特殊频率,则可以调整其他频率往这个特殊频率的倍数上来凑。
(如果配置后不满足这里的条件,编译时会报错)
5、 cpld可运行的最高频率:
mcu的运行最高频率是248M。而cpld中没有标准的最高频率。
最大能跑多少M,取决于cpld里的设计。
如果是逻辑电路,则不存在时钟的概念。
如果是时序电路,则看设计中门电路的复杂程度。如果跑100M的时钟,每个上升沿之间就是10纳秒,在设计时,要保证10纳秒内对应的动作能全部执行完。
如果是简单电路,一般是可以跑到200M以上。
cpld工程创建及编译的操作流程,参考文档《AG32下fpga和cpld的使用入门》
在工程中,用户逻辑部分编写是从analog_ip.v的接口下开始的。
mcu和cpld之间的交互,可以分为:
1. mcu传递信号给cpld;(如mcu的gpio传递高低信号到cpld)
2. cpld传递信号给mcu;(如:对mcu产生中断信号)
3. mcu读写数据到cpld;
4. 不建议,cpld做为主设备对mcu写。
也就是说,在mcu和cpld交互中,cpld更像一个外设。
其中,前两种较为简单。后两种要使用AHB总线来操作。
下边针对四种情况分别说明:
1. mcu传递信号给cpld;
这种使用较简单。步骤如下:
在ve中定义信号:
GPIO4_1 iocvt_chn:OUTPUT
表示,用mcu的gpio(gpio4_1)来输入信号到cpld。
然后,prepare LOGIC工程后,可以看到analog_ip.v接口中的信号:
input iocvt_chn_out_data,
input iocvt_chn_out_en,
这里的iocvt_chn_out_data,就是对接到mcu的gpio4_1的信号。
当控制mcu的gpio4_1高低切换时,cpld中的iocvt_chn_out_data,会对应来变化。
具体样例,可以参考网盘“logic样例3.mcu信号到cpld到pin”的样例,该样例中,展示了mcu控制cpld继续控制led的过程。
除了gpio信号输出到cpld,其他比如pwm输出信号等,都可以输入到 cpld。
2. cpld传递信号给mcu;
这种方式和1相近,只不过是反向。
可以在mcu中定义gpio4_2为输入并使能中断,则cpld中设置信号高低时,将触发mcu 的中断。
在VE中定义信号:
GPIO4_2 iocvt_chn:INPUT
表示,用mcu的gpio(gpio4_2)信号来源于cpld的iocvt_chn。
然后,prepare LOGIC工程后,可以看到analog_ip.v接口中的信号:
output iocvt_chn_in_data,
这里的iocvt_chn_in_data,就是对接到mcu的gpio4_2的信号。
当cpld中控制iocvt_chn_in_data信号高低时,mcu中的gpio4_2对应变化。
这里不再举例。
3. mcu读写数据到cpld;
在地址设计中,cpld的地址区间是:
0x60000000 ~ 0x7FFFFFFF
当mcu对这个区间内的地址访问时,相当于访问了cpld的“寄存器”。
mcu是全局寻址,对这个空间的访问和对ram(0x20000000 起)空间的访问是一样的方式,在C代码中,可以这样写:
读cpld:int cpRdReg = *((int *)0x60000000);
写cpld:*((int *)0x60000004) = cpWtReg;
MCU端读写cpld较为简单,直接通过上述语句就可以了。
当mcu读写动作发生时,cpld端是如何反应的?
当上述mcu读写动作发生时,AHB总线会把动作拆解为读写信号,传递到analog_ip.v的接口,用户cpld程序需要响应该信号。
以下,以写动作 *((int *)0x60000004) = cpWtReg 为例,描述 cpld端会发生的事情。
回顾下analog_ip.v中的接口部分:
其中slave_ahb_开头的一组信号,是cpld作为主端时用的,暂时不用理会。
Mem_ahb_开头的一组信号,是cpld作为从端使用的。
当mcu有读写操作时,mem_ahb_这组信号将发生变化。
这部分是遵循标准的AHB总线协议的。如果对AHB总线印象不深,请自行百度。可参考的讲解:
https://blog.csdn.net/weixin_46022434/article/details/104987905
几个信号的概述(更详细的讲解请自行百度):
Ahb_htrans:当前传输类型(00:IDLE、01:BUSY、10:NONSEQ、11:SEQ)
Ahb_ready:mcu读时要mcu要准备好cpld才会写
Ahb_hwrite: 要读还是要写(1为写,0为读)
Ahb_haddr[32]: 要操作的地址
Ahb_hsize:transfer的大小,以字节为单位
Ahb_hburst:批量传输
Ahb_hwdata[32]:写的数据,32位
Ahb_hreadyout:输出信号,mcu写时cpld是否准备好
Ahb_hresp:输出信号,响应信号(OK、retry、error、split)
Ahb_hrdata[32]:读的数据,32位
根据AHB时序,在一次传输中,cpld( slave 端)会先拿到 addr 地址,读或写的标记,然后交互 ready 信号后,开始数据传输。
大致如下图(无等待类型的图):
比如,mcu要读0x60000004的寄存器:
mcu端直接C语言这样调用:
int cpRdReg = *((int *)0x60000004);
cpld端,可以根据以上信号做如下处理:
以上代码,加入到analog_ip.v的module下,就可以完成cpld对 mcu读动作的响应。
比如,mcu要写0x60000000的寄存器:
mcu端直接C语言这样调用:
*((int *)0x60000000) = value;
cpld端,可以根据以上信号做如下处理:
这部分的实例代码,请参考网盘上cpld样例工程《5.mcu读写cpld 寄存器》。
注意:这里展示的,仅仅是基于AHB总线上的数据交互。
在实际应用中,比如要实现一个串口之类的,往往是慢速设备,这些是要挂载到apb上的。慢速设备要经过ahb到apb的bridge,才能最终使用。请继续往下看。
但数据是在 ahb 层面的响应,慢速设备不能直接使用。
慢速设备需要 ahb 转为 apb 后,使用 apb 的信号来交互。这种情况,转变为mcu和apb之间的交互。
mcu和apb之间的交互,相比mcu和aph之间的交互,多了一层 ahb到apb的转换。这个转换是借助于ahb2apb.v模块来实现的(在 example/analog下找该.v文件)。
该模块:输入是ahb的一组信号,输出是apb的一组信号。使用如下图:
如果实现mcu和apb的交互,则需要操作的是转换后的这组apb信号。
关于 apb 总线的使用,更多信息请自行百度。
这里只是简述下apb信号列表(与ahb略有不同):
apb_psel:片选
apb_penable:表示传输进入第二周期(准备好了读/写)
apb_pwrite:传输方向(1-写;0-读)
apb_paddr[32]:地址总线,要操作的地址
apb_pwdata[32]:写的数据,32 位
apb_prdata[32]:读的数据,32 位
以下展示在apb下如何实现跟mcu的交互,仍以ahb的两个寄存器为例。
1. 首先需要增加 ahb 转 apb 的信号关联;
如上图。
Ahb2apb模块会把ahb信号转换为apb信号。接下来操作apb信号即可。
2. 在转换后的apb信号中,实现写和读的操作。
mcu 读操作时:
比如,mcu要读0x60000004的寄存器:
mcu 端直接 C 语言这样调用:
int cpRdReg = *((int *)0x60000004);
cpld 端,可以根据以上信号做如下处理:
mcu 写操作时:
比如,mcu要写0x60000000的寄存器:
mcu 端直接 C 语言这样调用:
*((int *)0x60000000) = value;
cpld 端,可以根据以上信号做如下处理:
这个功能实现后,其实是个简单的“空外设”。可以用它做为实现复杂功能外设的基础。这部分的实例代码,请参考网盘上 cpld 样例工程《5.mcu 读写 cpld 寄存器》。
样例展示到这里,mcu和cpld的交互上:交互信号、跟ahb交互数据、跟apb交互数据,基本的交互通路已经建立。
接下来,用户根据自己的需求,在 cpld 中交互到数据后,编写自己需要的功能即可。
ADC和UartTx,都是接到apb的。
功能方面,都包括:设置寄存器,读取寄存器。ADC 样例:
SDK 安装后的example/analog工程,展示了ADC模块做为外设,与 mcu 之间的数据交互(ADC 采集后的数据,被mcu读取)。
这里的ADC模块,在cpld里做的工作,是把串行数据转换成并行数据。
因为 AG32 芯 片中集成的 ADC 硬核是串行数据输出的,而 mcu 访问数据都是并行的。为了实现mcu端访问数据的统一,这里增加了ADC 的 cpld 代码,实现该串行转并行的功能。
相当于:ADC硬核+ADC的cpld逻辑,实现了一个完整的“ADC 外设”。
ADC的用户cpld代码,都是在 analog_ip.v中实现的。
直接以analog_ip.v中的实现来说明:
1. 在analog_ip的接口下边,首先是ahb2apb的信号转换。
转换后,后续的ADC/DAC/CMP,都是以 apb 信号线来操作。
2. 定义出信号线数组,用于连接实例化的6个对象:3个ADC/2个DAC/1个CMP;
这个里边,wire [PER_CNT-1:0] select 是定义片选,注意它的取位,这里取值后的数组,分别对应到
’h000,’h1000,’h2000,’h3000,’h4000,’h5000。
其他数组,都分别对应 6 个实例化以后的连线。
3. 然后用 for 循环 genarate 实例化 ADC/DAC/CMP;
包含 3 个 ADC,2 个 DAC,1 个 CMP。
实例化时,连线都对应到上边定义的数组中去了。
4. 接下来的 pr_select,是记录当前片选是哪个。
索引值:0 ADC0;1 ADC1;2 ADC2;3 DAC0;4 DAC1;5 CMP
5. 接下来的 apb_prdata,
6. 再接下来,就是 3 个模块 ADC/DAC/CMP 各自的实现了。
具体内容不再解析。
注意几个点:
A. mcu中设置寄存器时,都是值下来后,在ADC/DAC/CMP中缓存到reg中;
如:0x60005004是CMP的CHANNAL寄存器,mcu对这里寄存器设置值时,
最终cpld里执行:
B. mcu中要获取的值,也类似传递。
UartTx 样例:
这个样例比ADC样例简单些。
这个样例中,实现一个串口功能(只有TX功能,固定为115200频率)。
对mcu来说,它也是cpld实现的一个“外设”。
它的 cpld 实现逻辑,跟上边的ADC样例思路是相同的,都是先 ahb 转 apb,然后实例化外设,mcu 写数据时 apb 接收后处理,mcu 读数据时 apb 触发后处理。
读的时候,是读的串口的 state 状态;
写的时候,会把写的数据继续丢给 uart 模块处理(转化为 IO 的高低波形输出)
mcu 端,在 while(1)里边:
查询 cpld的写状态,当状态合适时,发数据给 cpld,cpld 根据时序转换为波形输出到定义的PIN。
更多细节,请参考 cpld 工程中的代码和注释。
该工程位于网盘上“logic样例/6.UartTx 例程”。
1. MCU为 master,cpld为slave,mcu对cpld的交互方式为存取寄存器的方式;
2. mcu中配置好DMA(读取 cpld 中准备好的数据);
3. cpld中准备好数据后,触发dma信号,dma自动搬运到mcu指定的 ram;
4. 搬运一次后,dma给cpld一个clear信号,完成一次dma搬运;
5. 等到cpld中再次准备好数据,将再次触发dma信号,重复3和4;
对于cpld来说,mcu来读取数据和dma来读取数据,是一致的。
dma来读取时,只是每次读完后会多给cpld一个clear信号。更多细节,请参考网盘上《7.cpld 中配合实现 mcu 的 dma 读取》部分的样例。
在这个样例中,展示了两部分代码:
1. mcu中,配置dma读取;为了测试,mcu会在另一地址给cpld写数据;
2. cpld中,会对mcu写进来的数据缓存,缓存后触发dma的信号,让dma来读取数据。而dma从cpld里读取数据后会给cpld一个clear 信号,标志一次 dma 交互完成。
cpld本身有自带的ram,为4个M9K块,每个M9K大小为8192 bits。
(即:4个M9K 总空间为4Kbytes)
更详细信息,请参考《AGRV2K_Rev2.0.pdf》中的说明。
AG32整个芯片系列,内存sram大小都是128K。
如果mcu用不了128K,希望分一些给cpld来用,比如,分出来32K给 cpld。可以按照如下方式设置:
1. 限制mcu的使用,让mcu只使用前96K;
限制mcu对ram的使用,需要修改ld配置(分散加载相关)来实现。
在路径:AgRV_piopackagesframework-agrv_sdkmiscdevices下,在文件AgRV2K_mem.ld中可以看到定义如图:

修改文件并保存后,需要重启VSCODE工程,让设置项使能。
2. cpld中对后32K的使用;
cpld 使用后 32K,起始地址是从 0x20000000 + 96K 的地址开始。
即:从 0x20018000开始,长度32K,到 0x20020000 结束。
cpld 中对于 sram 的寻址方式和 mcu 相同。
cpld对sram的读写,请参考:
SDK 下的 examplescustom_iplogicram2ahb.v 和 ahb2ram.v

mcu_a[0] PIN_31
mcu_a[1] PIN_32
mcu_a[2] PIN_33
mcu_a[3] PIN_34
...
如图:
那么,prepar LOGIC后,将在自动生成的cpld框架.v接口里,表示如下:
这里就表现为数组的形式了。

手机号:15800607785
邮件:sales@agmcn.com
或直接扫码加工作人员微信咨询。
<