引言

本文档详细介绍了AG32开发中MCU与CPLD交互的具体方式、技术原理以及工程实践。作为工程师,理解并掌握这些交互机制对于充分发挥AG32平台的性能优势至关重要。

CPLD工程创建及编译的操作流程,可参考文档《AG32下fpga和cpld的使用入门》。在工程中,用户逻辑部分编写从analog_ip.v的接口开始。

一、MCU与CPLD交互方式概述

MCU和CPLD之间的交互可以分为以下几种方式:

  1. 1. MCU传递信号给CPLD(如MCU的GPIO传递高低信号到CPLD)
  2. 2. CPLD传递信号给MCU(如对MCU产生中断信号)
  3. 3. MCU读写数据到CPLD(通过AHB总线)
  4. 4. MCU通过AHB转APB后的数据交互(用于慢速外设)
  5. 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. 1. MCU为master,CPLD为slave:MCU对CPLD的交互方式为存取寄存器
  2. 2. MCU配置DMA:设置DMA读取CPLD中准备好的数据
  3. 3. CPLD触发DMA:数据准备好后,触发DMA信号,DMA自动搬运到MCU指定的RAM
  4. 4. DMA完成反馈:搬运一次后,DMA给CPLD一个clear信号,完成一次DMA搬运
  5. 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. 1. VE工具:AG32芯片开发套件中的CPLD逻辑设计集成开发环境
    • • 工程创建与管理
    • • IP核集成与配置
    • • 引脚约束设置
    • • 逻辑综合与布局布线
  2. 2. 硬件描述语言:Verilog/VHDL语言掌握
  3. 3. 仿真验证:功能仿真和时序仿真
  4. 4. 硬件调试:JTAG下载与调试

6.2 开发最佳实践

设计原则:

  • • 合理的时钟域规划
  • • 充分的时序约束
  • • 完善的复位策略
  • • 模块化设计思想

调试技巧:

  • • 使用内部信号监测
  • • 逻辑分析仪验证时序
  • • 分阶段验证功能模块

七、实例工程参考

本文档涉及的完整代码示例可参考以下样例工程:

  1. 1. logic样例3.mcu信号到cpld到pin – MCU信号控制CPLD的基础示例
  2. 2. 5.mcu读写cpld寄存器 – AHB/APB总线交互的完整实现
  3. 3. 7.cpld中配合实现mcu的dma读取 – DMA应用的完整示例

总结

MCU与CPLD的交互是AG32平台的核心优势之一,它结合了MCU的灵活性和CPLD的并行处理能力。通过深入理解AHB/APB总线协议、掌握CPLD内部寄存器映射和逻辑设计,以及熟悉相关的开发工具和调试方法,工程师可以充分发挥AG32平台的潜力,开发出高性能、高可靠性的嵌入式系统。

关键要点回顾:

  • • CPLD设计中的同步处理和时序考量
  • • AHB/APB总线协议的正确实现
  • • DMA机制的高效数据传输
  • • 系统级的设计思考和优化策略

通过本文档的学习和实践,工程师将能够熟练运用AG32平台进行复杂嵌入式系统的开发。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注