拓冰建站拓冰建站
首页 / 资讯中心 / 正文

PGL22 FPGA部署ARM Cortex-M1软核:启动流程、调试与应用解析

简介这是一份面向嵌入式设计师与硬件工程师的FPGA工程包基于ARM Cortex-M1处理器核构建片上系统SoC原型用于快速验证低功耗、高性能的嵌入式方案设计结合可重构逻辑便于按需裁剪外设。压缩包共616个文件整体约27.81MB内容包含Verilog与VHDL硬件描述代码、预封装IP核、初始化参数文件、引脚约束、仿真测试脚本、工程日志及PDF设计文档覆盖从寄存器传输级设计到系统集成的各阶段。已有488人学习下载。通过该工程读者可以系统学习在FPGA上例化Cortex-M1、配置总线与存储器接口、编写测试平台并完成综合与仿真对于物联网设备、传感器采集等成本敏感的嵌入式场景工程中的示例和测试脚本也可作为二次开发的起点。资源文件数量多但分类清晰适合作为SoC设计流程的实验参考帮助初学者从工程出发理解Cortex-M1软核与FPGA协同设计的方法。1. 一块PGL22_324_eval为什么要把Cortex-M1放进去拿到PGL22_324_eval评估板的人最容易先点亮LED、抓MIPI信号或调一个图像接口。但真正让这块板子产生“CPU感”的用法是在可编程逻辑里塞进一个ARM Cortex-M1软核。Cortex-M1是ARM专门为FPGA推出的32位内核不占用厂商的硬核而是用LUT、FF和BRAM做成一个处理器。PGL22级别的器件完全能容纳最小系统配套的AHB-Lite接口又可以和FPGA里自定义的采集、直方图、图像处理逻辑互连。这篇文章顺着“软核定位—启动流程—最小系统—调试排错—应用验证”这条线把整个过程展开。适合会用Verilog、想往SoC方向跨一步的工程师参考。2. Cortex-M1软核的定位PGL22_324的资源与启动流程2.1 Cortex-M1是一段RTL不是FPGA里的ARM硬宏Cortex-M1属于ARMv6-M架构只支持Thumb/Thumb-2指令集没有MMU中断控制器是NVIC整体面积很小。正因为它是软核交付物就是一组RTL文件既可以像普通Verilog一样综合也可以按需裁剪调试接口和总线接口。这一点和ZYNQ里的硬核ARM完全不同硬核的频率可以跑到几百MHz而Cortex-M1在中等密度FPGA上通常只能跑到几十MHz到一百多MHz但换来的是你可以把CPU放进任意一颗支持足够资源的FPGA里甚至改变它周围的总线拓扑。在PGL22_324_eval上部署Cortex-M1等价于把ARM处理器“移植”进紫光同创的逻辑资源里。你不需要在板上放一颗单独的MCU也不需要让FPGA状态机去模拟一条指令流水。常见做法是先获得Cortex-M1的RTL授权评估包然后把RTL加入Pango Design Suite工程跑完综合布局布线最后用JTAG/SWD调试器点复位、看PC指针。整个过程和集成一个外设IP不同你要同时处理的是CPU、内存映射、调试端口和启动代码。2.2 PGL22_324的资源分配参考PGL22并不是一颗大型FPGA但它面对Cortex-M1刚好处于“够用但不宽裕”的位置。以PGL22G级别器件的公开逻辑规模看它的逻辑单元、Block RAM和DSP数量适合做单核ARM加少量外设不适合再塞第二个完整处理器或很大的GPU类电路。表里这份资源预估不是某个官方数字而是我在类似软核工程里常用的分配思路具体以你在PDS中综合后的报告为准。资源类型最小M1系统典型占用剩余可给应用逻辑Logic Cells / LUT几千级通常不到器件4分之1可再做FIFO、DMA、图像行缓存Flip-Flop / Register与LUT按比例增长剩余足以支撑状态机和跨时钟域同步Block RAM至少一块32位RAM加一块ROM向量表可扩TCM、帧缓冲、直方图存储DSP Slice0到少量留给乘加、滤波、反正切等运算一个常见的误判是认为软核会吃掉大量DSP实际上Cortex-M1的乘法器在PGL22里可以用LUT实现真正吃资源的是带多主从端口的AHB互联和Debug模块。所以我一般建议先把不带外设的M1放进去跑一遍综合RTL定制时关闭不用的调试特性等资源报告出来后再决定外设规模。2.3 Cortex-M内核与启动流程向量表是CPU的第一个脚印Cortex-M内核的启动流程高度一致硬件复位后从地址0x00000000读取初始栈指针从0x00000004读取复位中断向量然后跳到Reset_Handler执行。Cortex-M1虽然是软核这条规则一点没变。在FPGA中难点在于CPU上电后必须立刻从可读存储器取指常见做法是用Block RAM做一个BootROM把向量表和启动代码一起塞进去。下面是一段符合ARM嵌入式标准的简化启动汇编后半段会编译进PGL22的BootROM。.syntax unified .cpu cortex-m1 .thumb .section .vectors .word 0x20002000 /* 栈顶由链接脚本堆栈段决定 */ .word Reset_Handler /* 复位向量 */ .word NMI_Handler /* NMI */ .word HardFault_Handler /* 硬件错误 */ .word 0 /* 保留 */ .word 0 .word 0 .word 0 .word 0 .word 0 .word 0 /* SVCall */ .word 0 /* 调试监控 */ .word 0 /* 保留 */ .word 0 /* PendSV */ .word SysTick_Handler /* SysTick */ .section .text .thumb_func .global Reset_Handler Reset_Handler: LDR R0, _sdata LDR R1, _edata LDR R2, _sidata B copy_loop copy_loop: CMP R0, R1 BGE init_bss LDR R3, [R2], #4 STR R3, [R0], #4 B copy_loop init_bss: LDR R0, _sbss LDR R1, _ebss B bss_loop bss_loop: CMP R0, R1 BGE jump_main MOVS R3, #0 STR R3, [R0], #4 B bss_loop jump_main: BL main B .这段代码的关键在于向量表要用word而不是普通long以保证地址对齐。Reset_Handler里复制.data、清零.bss然后跳到C语言的main。PGL22里的BootROM初始化文件必须加载这段汇编生成的hex否则M1取到的不是有效指令ICODE_HADDR会一直乱跳。配套的链接脚本把向量表固定在地址0MEMORY { ROM (rx) : ORIGIN 0x00000000, LENGTH 4K RAM (rw) : ORIGIN 0x20000000, LENGTH 8K } SECTIONS { .vectors : { *(.vectors) } ROM .text : { *(.text*) } ROM .data : { *(.data*) } RAM AT ROM .bss : { *(.bss*) } RAM _estack ORIGIN(RAM) LENGTH(RAM); }把BootROM放在地址0把可读写的RAM放在0x20000000这就构成了一个能启动的最小Cortex-M1内存映射。很多人在PGL22上跑不起来不是M1本身的问题而是BootROM没有初始化或者地址没对齐。3. 在PGL22_324_eval上搭最小Cortex-M1系统从RTL到AHB外设3.1 获取Cortex-M1 RTL并组织工程目录ARM面向FPGA软核的常见获取路径是DesignStart评估它包含带时间限制和功能限制的Cortex-M1可综合RTL以及完整的AMBA接口文档。拿到之后先别急着打开PDS我会先把RTL整理成独立目录方便写Tcl脚本批量添加源文件。PGL22_324_eval的工程目录一般是这样的cortex_m1_eval/ ├ rtl/ │ ├ cortex_m1.v # ARM原始顶层/包装 │ ├ cortex_m1_intr.v │ ├ m1_design_top.v # 你写的顶层 │ └ clk_rst_gen.v ├ ip/ │ ├ bootrom_4k.hex # 启动向量表初始化代码 │ └ main_ram_8k.v # PGL22内部RAM封装 ├ script/ │ └ build.tcl # PDS批处理脚本 └ firmware/ ├ startup.s ├ main.c └ Makefile顶层设计里不只实例化CPU还要同时处理晶振输入、全局复位、AHB互联和LED/GPIO外设。PDS在综合前会检查所有源文件是否都在工程里漏掉cortex_m1.v是最低级的卡住原因。3.2 实例化Cortex-M1与AHB-Lite互联Cortex-M1对外接口是AHB-Lite常见配置包含ICode、DCode和System三组主接口。ICode负责取指DCode负责数据读写System可以在需要非阻塞访问时使用。在最小系统里可以把ICode和DCode合并到一个AHB从设备侧也可以只使用System接口桥接所有访问。实例化时不能凭空猜测端口名要以ARM提供的wrapper文件为准下面是去掉大量信号后的简化示意// 端口和参数仅表示连接关系请对照M1 release包命名 cortex_m1 #( .LITTLEENDIAN(1) ) u_cpu ( .HCLK(clk_50m), .HRESETn(rst_n), // ICode取指总线 .ICODE_HADDR(icode_haddr[31:0]), .ICODE_HTRANS(icode_htrans[1:0]), .ICODE_HREADY(icode_hready), .ICODE_HRDATA(icode_hrdata[31:0]), // DCode数据总线 .DCODE_HADDR(dcode_haddr[31:0]), .DCODE_HTRANS(dcode_htrans[1:0]), .DCODE_HREADY(dcode_hready), .DCODE_HRDATA(dcode_hrdata[31:0]), // 系统总线和调试 .SYS_HADDR(sys_haddr[31:0]), .SYS_HTRANS(sys_htrans[1:0]), .SYS_HWRITE(sys_hwrite), .SYS_HWDATA(sys_hwdata[31:0]) );HCLK必须由PLL输出驱动不能把外部晶振直接接上去。HRESETn要有同步释放逻辑避免CPU在复位撤销时出现亚稳态。HTRANS用来区分IDLE、BUSY、NONSEQ、SEQ四种传输类型AHB互联靠它判断是否真正占用总线如果外设地址不命中默认返回0x00000000也能让CPU跑起来但读状态会是垃圾值。3.3 用BRAM做BootROM和Main RAMPGL22内部BRAM可以通过IP定制工具生成但对软核调试来说最好让BRAM具备初始化能力。BootROM用单端口ROM位宽32位深度4KByteMain RAM用双端口RAM地址从0x20000000开始。AHB总线位宽与BRAM数据位宽都设成32地址按32位字对齐。创建后的模块在顶层里像普通RAM一样实例化BootROM端口读出向量表Main RAM端口给CPU读写。下面的参数表适合填进PDS的RAM IP配置页配置项BootROM取值Main RAM取值说明数据位宽3232与AHB总线一致地址深度10244KB20488KB按需求调整初始化文件bootrom.hex无只有ROM需要加载读延迟1拍1拍匹配AXI/AHB等待状态输出寄存器开/关关可改善时序但会多一拍调试阶段不要让Main RAM开出输出寄存器否则CPU访问内存会多等一拍并导致总线没有HREADY响应。更稳健的做法是把AHB到RAM的从设备包装成一个简单的单拍读、单拍写状态机确保HREADY在第一个等待周期内返回。3.4 最小C工程交叉编译与烧录Cortex-M1工具链可选标准嵌入式GCC编译参数要指定-mcpucortex-m1这会自动选择Thumb指令集并调整优化策略。Makefile里最关键的目标是把ELF转换成hex和bin前者给调试器后者用来初始化BootROM。PREFIXarm-none-eabi- CC$(PREFIX)gcc AS$(PREFIX)gcc OBJCOPY$(PREFIX)objcopy CFLAGS-mcpucortex-m1 -mthumb -O2 -g -Wall -ffreestanding LDFLAGS-T link.ld -nostdlib OBJSstartup.o main.o firmware.elf: $(OBJS) $(CC) $(CFLAGS) $(LDFLAGS) -o $ $(OBJS) firmware.bin: firmware.elf $(OBJCOPY) -O binary $ $ %.o: %.c $(CC) $(CFLAGS) -c -o $ $ %.o: %.s $(AS) $(CFLAGS) -c -o $ $ clean: rm -f *.o firmware.elf firmware.bin firmware.hex-ffreestanding告诉编译器不要假设存在标准库因为你的main循环里只操作寄存器。编译完成后把firmware.bin转换成BootROM可接受的格式。PDS的初始化文件一般是hex或coe格式可以用objcopy生成firmware.hex再按BRAM字节顺序填入。最小系统里可以只让main操作GPIO寄存器#define LED_BASE 0x40000000 void delay(volatile int n) { while (n--) { __asm volatile(nop); } } int main(void) { volatile unsigned int *led (volatile unsigned int *)LED_BASE; while (1) { *led 0x1; delay(500000); *led 0x0; delay(500000); } }注意LED_BASE必须和你在PGL22顶层里挂到AHB上的GPIO从设备地址相等。如果地址不匹配CPU写操作会被AHB互联丢弃LED引脚永远不变这时我们会先用逻辑分析仪抓写地址。4. Cortex-M1调试链路与“no cortex-m sw device found”排错4.1 选择JTAG还是SWD调试Cortex-M1软核Cortex-M1内部通常包含SWJ-DP既支持JTAG也支持串行线调试。在PGL22_324_eval评估板上调试接口可以接到ARM调试器但有一个容易忽略的前提PGL22的普通IO可能被配置成调试复用引脚也可能被综合成普通逻辑必须回到约束文件里检查引脚分配。调试方式引脚占用连接对象适用场景JTAGTMS、TCK、TDI、TDO通用JTAG调试器刷FPGA配置与CPU调试可共用SWDSWDIO、SWCLKCMSIS-DAP/J-Link引脚少软核调试最常用UART打印TX、RX串口助手无调试器时看打印日志很多人一上来就报“no cortex-m sw device found”问题往往不在调试器而在Cortex-M1的调试TAP没有被接通。软核Debug域需要独立的调试时钟和调试复位如果DBGEN和NIDEN信号没接对SWD扫描永远看不到设备。4.2 no cortex-m sw device found的排查顺序这个错误信息在MDK、OpenOCD和J-Link里都会出现。按下面顺序排查可以避免在最外围的线缆问题上浪费半天。第一步看开发板的电路图确认调试器端口的SWDIO和SWCLK确实连到了FPGA侧引脚而不是FPGA用于配置烧录的专用TDI/TDO。第二步在PDS引脚约束里确认这两个引脚没有被复用成其他外设尤其是LED、按键或MIPI引脚。第三步把调试时钟频率压低到1MHz以下软核调试逻辑在低速时钟下更容易扫描到。第四步确认上电时序FPGA配置完成后Cortex-M1要收到了复位释放SWD扫描才能识别到DAP。最后用官方样例或最简单的软核工程验证调试器链路。一个常用的OpenOCD启动命令如下openocd -f interface/cmsis-dap.cfg \ -f target/cortex_m.cfg \ -c adapter speed 1000 \ -c transport select swd \ -c init \ -c reset halt这里cortex_m.cfg定义的是标准Cortex-M调试寄存器组。Cortex-M1是ARMv6-M目标很多Cortex-M0配置也可以用如果你的调试器固件特别旧可能无法识别M1的IDCODE这时需要手动在openocd脚本里写set CHIPNAME cortex_m1并跳过IDCODE检查。adapter speed 1000单位是kHz先压到500甚至200再测试。4.3 用PGL22逻辑分析仪确认CPU是否在跑如果SWD仍然找不到设备最常见的答案已经变成“CPU根本没跑起来”。这时候别纠结调试器切到PDS内置逻辑分析仪抓内部信号。首先在线运行FPGA把HCLK、HRESETn、ICODE_HADDR、ICODE_HTRANS和ICODE_HREADY加进观察列表。复位释放后ICODE_HADDR应从0x00000000开始递增ICODE_HTRANS出现NONSEQ说明CPU正在取第一个向量。如果ICODE_HADDR一直停留在某些非法地址检查BootROM初始化的邻域。如果ICODE_HREADY长时间为低多半是AHB从设备没有响应复位被卡住。调试界面里触发条件可以设为“复位释放后的第一次NONSEQ”抓几十个周期就够定位了。需要特别说明的是Cortex-M1的SWD扫描和CPU运行可以不同步。你完全可能在CPU跑得正欢时遇到“no cortex-m sw device found”这时先检查调试复位是不是被错误地接成了系统复位或者调试时钟是否独立供给。很多评估板的调试器复位引脚连的是FPGA整体复位一按复位就把FPGA逻辑全部清掉SWD自然就断了。提示如果板上有两颗芯片一颗处理USB转调试、一颗是FPGA先确认调试器固件版本和PGL22的JTAG链是否被配置软件占用。5. 用Cortex-M1接管FPGA控制通路从eval板到图像/TDC应用把Cortex-M1当成一个可编程控制核比用它做重计算更有意义。PGL22_324_eval的未来应用往往是FPGA图像处理或TDC直方图统计数据通路完全由可编程逻辑完成而Cortex-M1只负责下发参数、读取结果、调整阈值。为了让两边不互相干扰我会在AHB总线上挂一个“命令寄存器组”用Cortex-M1写控制字FPGA逻辑轮询控制字后自动开始采集。#define CTRL_BASE 0x50000000 #define CTRL_START (*(volatile unsigned int *)(CTRL_BASE 0x00)) #define CTRL_MODE (*(volatile unsigned int *)(CTRL_BASE 0x04)) #define CTRL_CLR (*(volatile unsigned int *)(CTRL_BASE 0x08)) // 下发一次TDC采集 CTRL_MODE 0x00000003; // 连续模式直方图累加 CTRL_CLR 0x1; // 清空直方图 CTRL_START 0x1; // 启动采集FPGA侧的控制状态机检测到CTRL_START上升沿后把内部使能拉高直方图累加器开始统计。检测到直方图溢出时写回状态寄存器Cortex-M1再决定是否搬数据到主RAM。这里最值得验证的动作是“回读确认”不能只写控制字还要写一个只读状态寄存器让CPU读回被写进去的模式确认AHB地址译码没有把两个外设映射到同一块空间。// 回读验证AHB外设是否存在 #define STATUS_BASE 0x50001000 #define STATUS_RDATA (*(volatile unsigned int *)(STATUS_BASE 0x00)) if (STATUS_RDATA ! 0x5A5A0001) { // 说明外设地址映射错误打印或点亮错误LED led_error(); }我在类似设计里还会在FPGA侧加一个计数器每次Cortex-M1写控制字时数值加一CPU读回后再和本地计数比对。这一步能同时验证写通路和读通路比单一读写更可靠。最后把固件固化到PGL22的SPI Flash时记住BootROM要放在NOR Flash的前4KB配置位流放在后面否则上电先把FPGA配置起来后CPU取指会赶到空区域。固化后断电重上电用之前搭建的SWD链路检查PC是否停在Reset_Handler再用逻辑分析仪确认直方图累加使能的跳变是否来自Cortex-M1写入的控制位。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门