引言
本文档详细介绍了AG32开发中MCU与CPLD交互的具体方式、技术原理以及工程实践。作为工程师,理解并掌握这些交互机制对于充分发挥AG32平台的性能优势至关重要。
CPLD工程创建及编译的操作流程,可参考文档《AG32下fpga和cpld的使用入门》。在工程中,用户逻辑部分编写从analog_ip.v的接口开始。
一、MCU与CPLD交互方式概述
MCU和CPLD之间的交互可以分为以下几种方式:
- 1. MCU传递信号给CPLD(如MCU的GPIO传递高低信号到CPLD)
- 2. CPLD传递信号给MCU(如对MCU产生中断信号)
- 3. MCU读写数据到CPLD(通过AHB总线)
- 4. MCU通过AHB转APB后的数据交互(用于慢速外设)
- 5. DMA在CPLD中的应用(高效数据搬运)
重要说明:不建议CPLD作为主设备对MCU写入。在MCU和CPLD交互中,CPLD更像一个外设,这种设计模式确保了系统的稳定性和可控性。
二、MCU与CPLD直接信号交互:基础与进阶
2.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
会对应变化。
工程实践考量:
对于来自MCU的异步信号,CPLD内部应至少使用两级触发器进行同步处理,以避免亚稳态:
// 同步器设计
reg iocvt_chn_out_data_sync1;
reg iocvt_chn_out_data_sync2;
always @(posedge sys_clock) begin
iocvt_chn_out_data_sync1 <= iocvt_chn_out_data;
iocvt_chn_out_data_sync2 <= iocvt_chn_out_data_sync1;
end// 在CPLD内部逻辑中使用 iocvt_chn_out_data_sync2
关键设计要点:
- • 信号电平匹配:确保MCU和CPLD之间的IO电平兼容(3.3V或1.8V)
- • 信号完整性:对于高速信号,需要考虑信号线长度和阻抗匹配
- • 时序约束:在VE工具中设置合适的时序约束
除了GPIO信号,PWM输出信号等也可以输入到CPLD。具体样例可参考”logic样例3.mcu信号到cpld到pin”。
2.2 CPLD传递信号给MCU:中断与事件处理
基础配置:在VE中定义信号:
GPIO4_2 iocvt_chn:INPUT
代码实现:
output iocvt_chn_in_data,
当CPLD中控制iocvt_chn_in_data
信号高低时,MCU中的gpio4_2对应变化,可以触发MCU中断。
进阶中断处理设计:
// 中断信号生成逻辑
reg interrupt_pending;
reg [15:0] interrupt_counter;
always @(posedge sys_clock or negedge reset_n) begin
if (!reset_n) begin
interrupt_pending <= 1'b0;
interrupt_counter <= 16'h0;
end
else begin // 基于特定条件生成中断
if (data_ready && !interrupt_pending) begin
interrupt_pending <= 1'b1;
interrupt_counter <= 16'hFFFF; // 中断持续时间
end
else if (interrupt_pending && interrupt_counter > 0) begin
interrupt_counter <= interrupt_counter - 1;
end
else if (interrupt_counter == 0) begin
interrupt_pending <= 1'b0;
end
end
end
assign iocvt_chn_in_data = interrupt_pending;
实践考量:
- 中断触发方式:MCU中断可以是边沿触发或电平触发,CPLD输出信号应与MCU配置匹配
- 中断清除机制:设计合适的中断清除机制,避免重复触发
- 中断优先级:在MCU端合理设置中断优先级
三、MCU通过AHB总线读写CPLD:深度技术解析
3.1 AHB总线基础与地址映射
在地址设计中,CPLD的地址区间是:0x60000000 ~ 0x7FFFFFFF
当MCU对这个区间内的地址访问时,相当于访问了CPLD的”寄存器”。MCU端的操作非常简单:
// 读CPLDint cpRdReg = *((int *)0x60000000); // 写CPLD *((int *)0x60000004) = cpWtReg;
3.2 AHB总线协议深度解析
当MCU读写动作发生时,AHB总线会将动作拆解为读写信号,传递到analog_ip.v的接口。理解AHB总线协议是设计CPLD从设备的关键。
关键信号详解:
// AHB从设备接口信号
input Ahb_hclk; // AHB时钟
input Ahb_hresetn; // AHB复位,低有效
input [31:0] mem_ahb_haddr; // 地址总线
input [31:0] mem_ahb_hwdata; // 写数据总线
input mem_ahb_hwrite; // 写/读控制 (1=写, 0=读)
input [1:0] mem_ahb_htrans; // 传输类型
input mem_ahb_hready; // 主设备准备好
output [31:0] mem_ahb_hrdata; // 读数据总线
output mem_ahb_hreadyout; // 从设备准备好
output [1:0] mem_ahb_hresp; // 响应信号
传输类型详解:
- •
2'b00: IDLE
– 空闲状态 - •
2'b01: BUSY
– 忙状态 - •
2'b10: NONSEQ
– 非连续传输,新传输的开始 - •
2'b11: SEQ
– 连续传输
3.3 CPLD内部寄存器映射的完整实现
以下是一个完整的CPLD AHB从设备实现示例:
// CPLD内部寄存器定义
reg [31:0] reg_control; // 控制寄存器 (0x00偏移)
reg [31:0] reg_data; // 数据寄存器 (0x04偏移)
reg [31:0] reg_status; // 状态寄存器 (0x08偏移, 只读)
// AHB响应信号
reg hreadyout_reg;
reg [31:0] hrdata_mux; // 地址解码与寄存器操作逻辑
always @(posedge Ahb_hclk or negedge Ahb_hresetn) begin
if (!Ahb_hresetn) begin
reg_control <= 32'h0;
reg_data <= 32'h0;
reg_status <= 32'h0;
hreadyout_reg <= 1'b1;
end
else begin
hreadyout_reg <= 1'b1; // 默认准备好
// 有效传输检测
if (mem_ahb_hready && (mem_ahb_htrans == 2'b10 || mem_ahb_htrans == 2'b11)) begin
if (mem_ahb_hwrite) begin // 写操作
case (mem_ahb_haddr[23:2])
22'h0: reg_control <= mem_ahb_hwdata;
22'h1: reg_data <= mem_ahb_hwdata;
default: ; // 无效地址忽略
endcase
end
else begin // 读操作 - 准备读数据
case (mem_ahb_haddr[23:2])
22'h0: hrdata_mux <= reg_control;
22'h1: hrdata_mux <= reg_data;
22'h2: hrdata_mux <= reg_status;
default: hrdata_mux <= 32'hDEADBEEF; // 错误标识
endcase
end
end
// 状态寄存器更新逻辑(示例)
reg_status[0] <= data_ready_flag;
reg_status[1] <= error_flag; // ... 其他状态位
end
end
assign mem_ahb_hrdata = hrdata_mux;
assign mem_ahb_hreadyout = hreadyout_reg;
assign mem_ahb_hresp = 2'b00; // OKAY响应
原文提供的基础读写示例:
MCU读操作的CPLD响应:
// MCU端:
int value = *((int *)0x60000004);
reg [31:0] hrdata_reg;
always @(posedge sys_clock) begin
if (mem_ahb_htrans == 2'b10 && // NONSEQ状态
mem_ahb_hready && // master已ready
!mem_ahb_hwrite && // 读操作
mem_ahb_haddr[23:0] == 'h04) // 地址匹配 begin
hrdata_reg <= hwdata_reg; // 准备读数据
end
end
assign mem_ahb_hrdata = hrdata_reg;
MCU写操作的CPLD响应:
// MCU端:
*((int *)0x60000000) = value;
reg [31:0] hwdata_reg;
always @(posedge sys_clock) begin
if (mem_ahb_htrans == 2'b00 && // IDLE状态
mem_ahb_hreadyout && // CPLD已ready
mem_ahb_hwrite && // 写操作
mem_ahb_haddr[23:0] == 'h00) // 地址匹配 begin
hwdata_reg <= mem_ahb_hwdata; // 接收数据
end
end
四、MCU通过AHB转APB的数据交互:慢速外设桥接
4.1 AHB2APB桥接原理
对于串口、SPI、I2C等慢速设备,直接挂载到AHB总线效率较低且设计复杂。需要通过AHB到APB的桥接(ahb2apb.v模块)来实现。
ahb2apb.v模块功能:
- • 地址映射:将AHB地址空间映射到APB地址空间
- • 时序转换:将AHB的多周期、流水线式传输转换为APB的简单两周期传输
- • 信号转换:完成AHB信号到APB信号的协议转换
4.2 APB总线协议与信号详解
APB关键信号:
input apb_clock; // APB总线时钟
input apb_resetn; // APB复位信号
input apb_psel; // 外设选择信号
input apb_penable; // 传输使能(第二周期)
input apb_pwrite; // 传输方向 (1=写, 0=读)
input [31:0] apb_paddr; // 地址总线
input [31:0] apb_pwdata; // 写数据总线
output [31:0] apb_prdata; // 读数据总线
4.3 APB从设备的完整实现
APB读操作实现:
// MCU端:
int value = *((int *)0x60000004);
reg [31:0] ardata_reg;
always @(posedge apb_clock) begin
if (!apb_pwrite && // 读操作
apb_penable && // 第二周期
apb_psel && // 片选有效
apb_paddr[11:0] == 'h04) // 地址匹配 begin
ardata_reg <= awdata_reg; // 准备读数据
end
end
assign apb_prdata = ardata_reg;
APB写操作实现:
// MCU端:
*((int *)0x60000000) = value;
reg [31:0] awdata_reg;
always @(posedge apb_clock) begin
if (apb_pwrite && // 写操作
apb_penable && // 第二周期
apb_psel && // 片选有效
apb_paddr[11:0] == 'h00) // 地址匹配 begin
awdata_reg <= apb_pwdata; // 接收数据
end
end
完整的APB从设备实现:
// CPLD内部寄存器
reg [31:0] apb_reg_config; // 配置寄存器
reg [31:0] apb_reg_data; // 数据寄存器
reg [31:0] apb_reg_status; // 状态寄存器
always @(posedge apb_clock or negedge apb_resetn) begin
if (!apb_resetn) begin
apb_reg_config <= 32'h0;
apb_reg_data <= 32'h0;
apb_reg_status <= 32'h0;
end
else begin
if (apb_psel && apb_penable) begin
if (apb_pwrite) begin // 写操作
case (apb_paddr[11:2])
10'h00: apb_reg_config <= apb_pwdata;
10'h01: apb_reg_data <= apb_pwdata;
default: ; // 无效地址忽略
endcase
end
end // 状态寄存器更新
apb_reg_status[0] <= data_ready;
apb_reg_status[1] <= error_flag;
end
end
// APB读数据输出
assign apb_prdata = (apb_psel && apb_penable && !apb_pwrite) ?
(apb_paddr[11:2] == 10'h00 ? apb_reg_config :
apb_paddr[11:2] == 10'h01 ? apb_reg_data :
apb_paddr[11:2] == 10'h02 ? apb_reg_status : 32'hFFFFFFFF) : 32'h0;
五、DMA在CPLD中的应用:高效数据传输
5.1 DMA工作流程
DMA实现的基本逻辑:
- 1. MCU为master,CPLD为slave:MCU对CPLD的交互方式为存取寄存器
- 2. MCU配置DMA:设置DMA读取CPLD中准备好的数据
- 3. CPLD触发DMA:数据准备好后,触发DMA信号,DMA自动搬运到MCU指定的RAM
- 4. DMA完成反馈:搬运一次后,DMA给CPLD一个clear信号,完成一次DMA搬运
- 5. 循环执行:CPLD再次准备好数据时,重复上述过程
5.2 CPLD端DMA实现
对于CPLD来说,MCU来读取数据和DMA来读取数据是一致的,DMA读取时只是每次读完后会多给CPLD一个clear信号。
DMA触发逻辑的完整实现:
// DMA相关信号
input dma_clear_signal; // 来自MCU的DMA清除信号
output dma_request_signal; // 输出给MCU的DMA请求信号
// CPLD内部数据缓冲区
reg [31:0] data_buffer [0:63]; // 64个32位数据缓冲区
reg [5:0] write_ptr; // 写指针
reg [5:0] read_ptr; // 读指针
reg data_ready_flag; // 数据准备就绪标志
reg dma_request_reg; // DMA请求寄存器
// 数据写入逻辑(示例:来自外部数据源)
always @(posedge sys_clock) begin
if (external_data_valid) begin
data_buffer[write_ptr] <= external_data;
write_ptr <= write_ptr + 1; // 缓冲区满时标记数据就绪
if (write_ptr == 6'd63) begin
data_ready_flag <= 1'b1;
write_ptr <= 6'd0; // 环形缓冲区
end
end
end
// DMA请求生成逻辑
always @(posedge sys_clock or negedge dma_clear_signal) begin
if (!dma_clear_signal) begin // DMA完成,清除请求
data_ready_flag <= 1'b0;
read_ptr <= 6'd0;
dma_request_reg <= 1'b0;
end
else if (data_ready_flag && !dma_request_reg) begin // 数据准备好且未发起DMA请求
dma_request_reg <= 1'b1;
end
end
assign dma_request_signal = dma_request_reg; // 响应DMA读取(通过AHB接口)
always @(posedge Ahb_hclk) begin
if (mem_ahb_hready && !mem_ahb_hwrite &&
(mem_ahb_htrans == 2'b10 || mem_ahb_htrans == 2'b11)) begin // DMA读取时更新读指针
read_ptr <= read_ptr + 1;
end
end
// DMA数据输出
assign mem_ahb_hrdata = data_buffer[mem_ahb_haddr[7:2]];
5.3 MCU端DMA配置示例
// MCU端DMA配置示例
void setup_dma_from_cpld(void) { // 配置DMA通道
DMA_Channel->CPAR = 0x60000000; // CPLD数据地址
DMA_Channel->CMAR = (uint32_t)dma_buffer; // 目标内存地址
DMA_Channel->CNDTR = 64; // 传输数据量
// 配置DMA控制寄存器
DMA_Channel->CCR = DMA_CCR_EN | // 使能DMA
DMA_CCR_MINC | // 内存地址递增
DMA_CCR_PSIZE_1 | // 外设32位
DMA_CCR_MSIZE_1 | // 内存32位
DMA_CCR_TCIE; // 传输完成中断
}
六、AG32 CPLD开发流程与工具链
6.1 开发环境与工具
- 1. VE工具:AG32芯片开发套件中的CPLD逻辑设计集成开发环境
- • 工程创建与管理
- • IP核集成与配置
- • 引脚约束设置
- • 逻辑综合与布局布线
- 2. 硬件描述语言:Verilog/VHDL语言掌握
- 3. 仿真验证:功能仿真和时序仿真
- 4. 硬件调试:JTAG下载与调试
6.2 开发最佳实践
设计原则:
- • 合理的时钟域规划
- • 充分的时序约束
- • 完善的复位策略
- • 模块化设计思想
调试技巧:
- • 使用内部信号监测
- • 逻辑分析仪验证时序
- • 分阶段验证功能模块
七、实例工程参考
本文档涉及的完整代码示例可参考以下样例工程:
- 1. logic样例3.mcu信号到cpld到pin – MCU信号控制CPLD的基础示例
- 2. 5.mcu读写cpld寄存器 – AHB/APB总线交互的完整实现
- 3. 7.cpld中配合实现mcu的dma读取 – DMA应用的完整示例
总结
MCU与CPLD的交互是AG32平台的核心优势之一,它结合了MCU的灵活性和CPLD的并行处理能力。通过深入理解AHB/APB总线协议、掌握CPLD内部寄存器映射和逻辑设计,以及熟悉相关的开发工具和调试方法,工程师可以充分发挥AG32平台的潜力,开发出高性能、高可靠性的嵌入式系统。
关键要点回顾:
- • CPLD设计中的同步处理和时序考量
- • AHB/APB总线协议的正确实现
- • DMA机制的高效数据传输
- • 系统级的设计思考和优化策略
通过本文档的学习和实践,工程师将能够熟练运用AG32平台进行复杂嵌入式系统的开发。