一、时钟配置与使用
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 、外部晶振 与 内部振荡器 的使用:
mcu 和 cpld 联合编程时,整颗芯片需要一颗外部晶振。
(芯片有内部振荡器,但误差较大,校准后 5%以内误差,参后续介绍)
该晶振是 mcu 和 cpld 共用的,(没必要为 cpld 再单独提供一颗晶振)。
晶振可以是有源的,也可以是无源的。
【注:这里的外部晶振配置,跟单纯使用 MCU 是一样的】
如果是无源晶体,频率限制在 4M~ 16M 。要接到芯片的 OSC_IN/OUT 引脚。然后VE 中直接定义主频多少 M 即可。如:

(这是目前开发板上使用的配置)
如果是有源晶体,频率无限制。根据接入点分为两种情况:
1) 如果接入到 OSC_IN 引脚:
ve 里定义同上(修改 HSECLK 项的值为有源晶振频率)。
同时要在 platformio.ini 里增加配置:-D BOARD_HSE_BYPASS=SYS_HSE_BYPASS_ON

注:上边的 -D 代表宏定义的意思,后边跟的内容是宏定义的内容。-D 后可加空格,可不加空格。等号两边不能加空格。
2) 如果接入到别的 IO 引脚(如 PIN_2):
VE 配置中,除了配置 HSECLK 项 外,还需要配置 PLL_CLKIN 项,如图:

同时,需要在 platformio.ini 里增加配置:-D BOARD_HSE_BYPASS=SYS_HSE_NONE

注:上边的 -D 代表宏定义的意思,后边跟的内容是宏定义的内容。-D 后可加空格,可不加空格。等号两边不能加空格。
如果使用内部振荡器:
校准后精度大约在 5%以内,节省成本且对时钟要求不高的话可以使用。使用方式:
在 VE 里增加:”PLL_CLKIN PIN_OSC” ,如下图:

(注:不用配置 HSECLK 项)
同时在 platformio.ini 里增加配置:-D BOARD_HSE_BYPASS=SYS_HSE_NONE

注:上边的 -D 代表宏定义的意思,后边跟的内容是宏定义的内容。-D 后可加空格,可不加空格。等号两边不能加空格。
注: 自动校准目前有以下使用限制:
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 是为了防止 cp ld 部分速度跟不上 sy sclk 而设定的。
cp ld 中除了这两路(其实就是 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 目标值就是 100\50\60\80\60的最小公倍数,为 1200M。
如果使用到一些特殊频率,则可以调整其他频率往这个特殊频率的倍数上来凑。 (如果配置后不满足这里的条件,编译时会报错)
5 、cpld 可运行的最高频率:
mcu 的运行最高频率是 248M 。而 cpld 中没有标准的最高频率。
最大能跑多少 M ,取决于 cpld 里的设计。
如果是逻辑电路,则不存在时钟的概念。
如果是时序电路,则看设计中门电路的复杂程度。如果跑 100M 的时钟,每个上升沿之间就是 10 纳秒,在设计时,要保证 10 纳秒内对应的动作能全部执行完。
如果是简单电路,一般是可以跑到 200M 以上。
二、mcu 和 cpld 的交互:
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 中定义信号:
表示,用 mcu 的 gpio(gpio4_ 1)来输入信号到 cpld。
然后,prepare LOGIC 工程后,可以看到 analog_ip.v 接口中的信号:
这里的 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 中定义信号:
表示,用 mcu 的 gpio(gpio4_2)信号来源于 cpld 的 iocvt_chn。
然后,prepare LOGIC 工程后,可以看到 analog_ip.v 接口中的信号:
这里的 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 端,可以根据以上信号做如下处理:
//mcu 的读操作响应
//mcu 端用 C 语言:int value = *((int *)0x60000004);
reg [31:0] hrdata_reg;
always @(posedge sys_clock) begin if(mem_ahb_htrans == 2 ‘b10 &&
mem_ahb_hready &&
!mem ahb hwrite &&
mem_ahb_haddr[23:0] == ‘h04)
//定义32 位的hrdata_reg //clk 上升沿触发
//NONSEQ 状态,第一次传输
//master 已ready ,可以给数据线写入了//读 (0 读,1 写)
//读地址为0x60000004 (cpld 用相对偏移)
begin
hrdata_reg <= hwdata_reg; //把另一准备好的数据给到hrdata_reg
end
end
assign mem_ahb_hrdata = hrdata_reg; //绑定hrdata_reg 到读的数据线上

以上代码,加入到 analog_ip.v 的 module 下,就可以完成 cpld 对 mcu 读动作的响应。
比如,mcu 要写 0x60000000 的寄存器:
mcu 端直接 C 语言这样调用:*((int *)0x60000000) = value;
cpld 端,可以根据以上信号做如下处理:

//mcu 的写操作响应
//mcu 端用 C 语言:*((int *)0x60000000) = value;
reg [31:0] hwdata_reg;//定义32 位的hwdata_reg
always @(posedge sys_clock) begin//clk 上升沿触发
if(mem_ahb_htrans == 2 ‘b00 &&//lDLE 状态
mem_ahb_hreadyout &&//cpld 已ready 状态,ahb 上数据可以写过来
mem ahb hwrite &&//写 (0 读,1 写)
mem_ahb_haddr[23:0] == ‘h00)//写地址为0x60000000 (cpld 用相对偏移)
begin
hwdata_reg <= mem_ahb_hwdata; //把收到的数据给到hwdata_reg
end
end
//这个过程,是把mcu 写进来的数据收到hwdata_reg 中

这部分的实例代码,请参考网盘上 cpld 样例工程《5.mcu 读写 cpld 寄存器》。
注意:这里展示的,仅仅是基于 AHB 总线上的数据交互。
在实际应用中,比如要实现一个串口之类的,往往是慢速设备,这些是要挂载到 apb上的。慢速设备要经过 ahb 到 apb 的 bridge ,才能最终使用。请继续往下看。
三、mcu 通过 ahb 转 apb 后的数据交互:
上节讲述了 mcu 和 cpld 之间交互数据的实现方式。
但数据是在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 端用 C 语言:int value = *((int *)0x60000004);
reg [31:0] ardata_reg;
always @(posedge apb_clock) begin if (!apb_pwrite &&
apb_penable &&
apb_paddr[11:0] == ‘h04) begin
ardata_reg <= awdata_reg; end
end
//定义32 位的hrdata_reg //clk 上升沿触发
//读 (0 读,1 写) //是否准备好
//读地址为0x60000004 (cpld 内部用相对偏移) //把另一准备好的数据给到hrdata_reg
assign apb_prdata = ardata_reg; //绑定hrdata_reg 到读的数据线上
mcu 写操作时:
比如,mcu 要写 0x60000000 的寄存器:
mcu 端直接 C 语言这样调用:*((int *)0x60000000) = value; cpld 端,可以根据以上信号做如下处理:
//mcu 的写操作响应
//mcu 端用 C 语言:*((int *)0x60000000) = value;
reg [31:0] awdata_reg;//定义32 位的hwdata_reg
always @(posedge apb_clock) begin//clk 上升沿触发
if (apb_pwrite &&//写 (0 读,1 写)
apb_penable &&//是否准备好
apb_paddr[11:0] == ‘h00) //写地址为0x60000000(cpld 内部用相对偏移) begin
awdata_reg <= apb_pwdata; //把收到的数据给到hwdata_reg end
end
//这个过程,是把mcu 写进来的数据收到hwdata_reg 中
这个功能实现后,其实是个简单的“空外设 ”。可以用它做为实现复杂功能外设的基础。这部分的实例代码,请参考网盘上 cpld 样例工程《5.mcu 读写 cpld 寄存器》。
样例展示到这里,mcu 和 cpld 的交互上:交互信号、跟 ahb 交互数据、跟 apb 交互数据,基本的交互通路已经建立。
接下来,用户根据自己的需求,在 cpld 中交互到数据后,编写自己需要的功能即可。
四、ADC 样例与 UartTx 样例
这里借助 ADC 样例和 UartTx 样例,展示下更多的使用方法。
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 例程 ”。
五、dma 在 cpld 中的使用;
cpld 中实现 DMA 的逻辑:
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:
cpld 本身有自带的 ram ,为 4 个 M9K 块,每个 M9K 大小为 8192 bits。
(即:4 个 M9K 总空间为 4K bytes)
更详细信息,请参考《AGRV2K_Rev2.0. pdf》中的说明。
除了 cpld 自带的内存,cpld 还可以使用 mcu 的内存 sram。
AG32 整个芯片系列,内存 sram 大小都是 128K。
如果 mcu 用不了 128K ,希望分一些给 cpld 来用,比如,分出来 32K 给 cpld 。可以按照如下方式设置:
1. 限制 mcu 的使用,让 mcu 只使用前 96K;
限制 mcu 对 ram 的使用,需要修改 ld 配置(分散加载相关)来实现。
在路径:AgRV_pio\packages\framework-agrv_sdk\misc\devices 下,在文件
AgRV2K_mem.ld 中可以看到定义如图:
如果只用 96K ,则修改上边的 SRAM_SIZE = 96K 即可。
修改文件并保存后,需要重启 VSCODE 工程,让设置项使能。
2. cpld 中对后 32K 的使用;
cpld 使用后 32K ,起始地址是从 0x20000000 + 96K 的地址开始。即:从 0x20018000 开始,长度 32K ,到 0x20020000 结束。
cpld 中对于 sram 的寻址方式和 mcu 相同。
cpld 对 sram 的读写,请参考:
SDK 下的 examples\custom_ip\logic\ram2ahb.v 和 ahb2ram.
七、一些小技巧:
如何定义信号量数组:
如果 cpld 里想用信号量数组,并使每个元素对应到不同 PIN ,可以在 ve 里定义如下: mcu_a [0] PIN_31
mcu_a [ 1] PIN_32
mcu_a [2] PIN_33
mcu_a [3] PIN_34
…
如图:

那么,prepare LOGIC 后,将在自动生成的 cpld 框架.v 接口里,表示如下:

这里就表现为数组的形式了。