PGL22G-324评估板Cortex-M1软核移植与调试要点
2026/9/12 19:10:46 网站建设 项目流程

简介:面向嵌入式与FPGA开发者的ARM Cortex-M1 SoC设计工程包,基于PGL22_324评估板,演示如何在FPGA中集成Cortex-M1处理器并搭建可灵活配置的片上系统。压缩包共616个文件,约27.81MB,以Verilog/VHDL源文件(.v、.vhdl、.vh)、Cortex-M1 IP核相关文件(.vp)、仿真与脚本配置(.do、.bat、.tcl)以及工程文档、PDF手册为主,覆盖从RTL编写、IP集成、综合实现到仿真验证的主要流程。已有488人学习下载,适合希望掌握FPGA+SoC设计方法、Cortex-M1处理器应用以及快速原型验证思路的硬件工程师、嵌入式开发者与相关专业学生。包内不仅包含完整的工程目录结构和示例设计,还提供测试脚本与说明文档,能够帮助使用者理解SoC架构和Cortex-M1与FPGA协同工作的细节,可直接基于此环境进行功能扩展与二次开发,是学习FPGA上处理器集成的高价值参考资源。

1. 为什么在 PGL22G-324 上把 Cortex-M1 软核当作独立项目来做

标题这种命名方式往往会出现在评估板项目的目录树里:pgr_ 是工程代号,Cortex_M1 是 ARM 提供的软核处理器,PGL22_324 指紫光同创 PGL22G 器件的 324 引脚封装,eval 则透露出这块板子的用途是评估验证。很多从 MCU 转过来的工程师拿到板子后第一件事是点灯,第二件事就是把一个软核 CPU 塞进 FPGA 里跑起来,而 Cortex-M1 正是面积小、能直接挂 AHB-Lite 总线的最小软核选择。这篇文章要解决的是:从一块 PGL22G-324 评估板的原理图出发,如何在国产 FPGA 工具链里把 M1 的时钟、复位、内存映射、GPIO/UART 外设和 SWD 调试链路打通,并指出过程中最容易让项目陷入僵局的几个细节。适合手里已经有板子、想验证软核方案的嵌入式或 FPGA 工程师,也适合用软核做外设控制器的原型开发人员。

2. 先看懂 PGL22G-324 的引脚和时钟,再决定 Cortex-M1 该落在哪个 bank

2.1 324 封装的 IO bank 与 M1 的固定信号

324 引脚的 PGL22G 器件在评估板上不会把所有 GPIO 都引出来,常见的做法是分成若干 8 位或 16 位的排针,每个排针对应一个 IO bank,并单独提供 VCCIO 电压。Cortex-M1 本身是软核,不占用物理引脚,它只通过 AHB-Lite 总线与片内逻辑连接,因此你真正需要提前锁定的引脚只有四类:时钟输入、复位输入、调试接口、以及一个用于启动后做基本观察的 UART/GPIO。我在布局时习惯先把晶振引脚和 SWD 引脚在原理图上标红,后续所有代码都围绕这个物理约束展开。

评估板的时钟常见为 50MHz 有源晶振,接在标有 GCLK 功能的全局时钟引脚上。M1 内核不挑时钟频率,但片内 SRAM 和外设总线都直接挂在同一个 HCLK 上,所以时钟规划实际上就是系统总线频率规划。如果之后要通过 PLL 倍频,注意 M1 的 AHB 接口对时钟占空比和抖动没有特殊要求,真正限速的是片内 RAM 的读写时序和 AHB 仲裁逻辑的组合逻辑深度。我开始做 PGL22 系列项目时,先把 50MHz 作为单一时钟跑通,再逐步加 PLL,这样遇到问题时能快速定位是内核还是外设侧的问题。

2.2 最小系统的电源轨与复位同步

评估板一般已经把电源树画好,但做软核原型时仍然要确认三个关键电源轨:VCCINT 负责 FPGA 内部逻辑,包含 M1 核;VCCIO 由 bank 分组决定,直接决定 GPIO 电平标准;VCCPLL 给模拟锁相环使用,必须在原理图上确认是否通过磁珠隔离。M1 对复位的要求比普通状态机高,因为内核在复位释放后的前几个周期就要取向量表,如果 HRESETN 解除时刻与 HCLK 沿没有对齐,M1 会进入未知状态。我的做法是加一个专用复位同步模块,把外部按键复位通过两级触发器同步后再作为 HRESETN 输出,并且让这个复位信号同时覆盖所有 AHB 外设和片内 SRAM 的复位端口。

module reset_sync ( input wire clk, input wire rst_n_ext, output wire rst_n_sync ); reg [3:0] rst_sr; always @(posedge clk or negedge rst_n_ext) begin if (rst_n_ext == 1'b0) begin rst_sr <= 4'b0000; end else begin rst_sr <= {rst_sr[2:0], 1'b1}; end end assign rst_n_sync = rst_sr[3]; endmodule

上面这个模块用四级移位寄存器实现异步复位、同步释放。rst_n_ext 是外部按钮经过消抖动后的低有效复位,rst_sr 的低 3 位在复位释放后逐拍拉高,第四拍才会把 rst_n_sync 真正释放给内部逻辑。这样做的好处是让 M1、总线矩阵和所有外设看到的是同一个延迟释放的复位信号,避免在复位释放瞬间出现高组态毛刺。CLK 频率在 50MHz 时,四级同步延迟只有 4 个周期,对内核启动没有影响,但能显著降低时序分析中复位释放路径上的亚稳态风险。

2.3 用工具链把引脚约束与 bit 流生成串起来

PGL22G 系列的开发软件通常提供图形化引脚规划界面,但命令行方式更容易在版本控制里体现差异。常见流程是先把 RTL 和约束文件加入工程,再指定顶层模块,然后依次执行综合、布局、布线并生成 bit 流。工程脚本大致如下:

create_project -name m1_pgl22 -dir ./prj set_device PGL22G-324 set_top cortex_m1_top add_files -global_include ./rtl add_files -constraints ./m1_top.fdc run synthesis run place run route generate_bitstream

这段脚本里的set_top指明了设计顶层是cortex_m1_top,后续所有引脚分配都在顶层接口上体现。add_files -constraints导入的约束文件至少需要包含时钟周期、引脚位置和 IO 电平标准三部分。PGL22 的引脚命名通常由工具自动从原理图导入,如果你的板子没有配套约束模板,需要手工对照原理图把clk_inuart_txuart_rxswclkswdio这五个引脚先固定下来。生成 bit 流后,下载到评估板并检查时钟引脚的 LED 是否按预期翻转,这一步能确认综合后的工程确实落到目标器件上。

3. 把 Cortex-M1 例化进 FPGA:AHB-Lite 总线与内存映射的设计细节

3.1 软核 M1 与硬核 Cortex-M 的差异

Cortex-M1 是被 ARM 设计成可综合软核的 M 系列内核,它和芯片里硬核 Cortex-M 的处理能力类似,但有一个关键区别:M1 的所有总线接口都以 AHB-Lite 从/主接口形式暴露在 RTL 里,你可以在 FPGA 中自由决定哪个地址放 SRAM、哪个地址放外设,甚至可以把外设直接映射到统一地址空间。这让 M1 更像一个可裁剪的 CPU 实例,而不是固定地址映射的 MCU。实际工程中,我通常使用 DesignStart 评估授权方式获得 M1 的 RTL,拿到后先不做任何修改,在顶层直接例化,把所有 AHB 信号引到一个总线互联模块。

使用软核时需要关注占用资源,PGL22G 这类中小型器件上 M1 核加上最小 SRAM 和 UART/GPIO,LUT 和寄存器消耗通常在百分之十几到百分之三十之间,具体取决于是否包含调试逻辑和乘法指令。减少资源占用的常见做法是关闭不用的指令扩展,例如不启用单周期乘法器,但这会影响后续跑算法时的性能。我一般先保留默认配置,等功能稳定后再针对性关掉权重较大的扩展选项,直到综合报告里的资源占用落入项目预算内。

3.2 M1 顶层例化与 AHB 信号命名约定

ARM 提供的 M1 RTL 接口有几十个信号,直接从顶层连出去很容易出错。我会在 M1 外面包一层cm1_wrapper,把 HCLK、HRESETN、HADDR、HTRANS、HSIZE、HWRITE、HWDATA、HRDATA、HREADY、HSEL、HSELPP 等信号先整理成统一的内部总线命名,再交给一个简单的 AHB-Lite 互联模块。这样做的好处是,如果以后换用别的 AHB 主设备,只需要改这一层 wrapper。下面是一个手写的最小例化片段:

CORTEXM1 #( `define INSTR_REPLACE_BITS 0 ) u_m1 ( .HCLK (m1_hclk), .HRESETN (m1_reset_n), .HADDR (ahb_haddr), .HTRANS (ahb_htrans), .HSIZE (ahb_hsize), .HWRITE (ahb_hwrite), .HWDATA (ahb_hwdata), .HRDATA ({AHB_WIDTH{1'b0}}), .HREADY (1'b1), .HREADYOUT (), .HSEL (1'b1), .HSELPP (1'b1), .HRESP_S (1'b0), .HRDATA_RX (ahb_hrdata), .HREADYIN (ahb_hready), .HMASTLOCK (), .HEXCLK (1'b0), .NMI (1'b1), .IRQ_DWT (1'b0), .IRQ_EXT0 (1'b0), .IRQ_EXT1 (1'b0), .IRQ_CTI (1'b0), .IRQ_PMU (1'b0), .IRQ_PMU_OVFL (1'b0), .IRQ_SYSTICK (1'b0), .IRQ_SW (1'b0) );

这段代码的关键是把没有用到的只读数据总线接成常量,同时把HREADYHSEL先固定为高电平,让 M1 在单主设备场景下直接访问整个地址空间。HRDATA_RX才是真正用于回读的写数据返回路径,HREADYIN是外部总线给出的等待信号,当外设比较慢时必须由互联模块拉低,否则片内 RAM 读写跟不上时会丢数据。对于第一次上板验证,不建议立即加多主设备,固定HSEL=1可以让 M1 的启动流程不受其他主设备仲裁干扰。

3.3 SRAM、外设的地址映射与启动入口

PGL22G 的片内 RAM 通过厂商的存储器 IP 生成器产生,接口可以配置为 AHB-Lite 或者简单的双端口 RAM。为了让 M1 复位后能直接取指令,需要把 SRAM 的起始地址放在0x00000000,并将向量表和启动代码烧录到该地址。常见的做法是用分散加载文件把只读代码段定位到 SRAM 起始地址,固件编译好后通过调试器写入片内 RAM,再由复位释放后进入启动流程。映射关系建议如下:

地址范围用途
0x00000000 - 0x0000FFFF片内 SRAM,存放向量表和程序
0x40000000 - 0x40000FFFUART 控制寄存器
0x40001000 - 0x40001FFFGPIO 数据/方向寄存器
0xE000E010 - 0xE000EFFFCortex-M 内核私有外设区

内核私有外设区包括 SysTick、NVIC 和调试组件,它在 M1 硬核内部已经实现,不需要额外挂到 AHB 总线上。而 SRAM 和 GPIO/UART 都在总线互联模块中通过地址译码器分配HSEL信号。如果后续加上外部 SDRAM,需要把该区域地址安排到不与 SRAM 冲突的位置,并在 AHB 互联模块里插入等待周期,这也是 M1 在国产 FPGA 上最容易出现访问超时的地方。

4. 写外设与中断:GPIO、UART 和 NVIC 之间的坑

4.1 一个可写可读的 GPIO AHB-Lite 从模块

外设寄存器挂在 AHB-Lite 上时必须遵守其传输规则:当HTRANS为非 2'b00 且HWRITE=1时写入 HWDATA,当HWRITE=0时把数据驱动到 HRDATA,并且用 HREADY 表示传输完成。M1 会连续发起多个传输,若外设没有等待机制,则必须保证一拍完成读写。最稳妥的做法是在外设内部把所有寄存器都用寄存器输出,并让组合逻辑地址译码只产生寄存器写使能这一个信号。下面是 GPIO 外设的简化实现:

module gpio_ahb ( input wire HCLK, input wire HRESETN, input wire HSEL, input wire [11:0] HADDR, input wire [1:0] HTRANS, input wire HWRITE, input wire [31:0] HWDATA, output reg [31:0] HRDATA, output wire [7:0] gpio_out ); reg [7:0] gpio_data; wire reg_sel = HSEL && (HTRANS[0]); wire wr_sel = reg_sel && HWRITE; always @(posedge HCLK or negedge HRESETN) begin if (!HRESETN) begin gpio_data <= 8'b0000_0000; end else if (wr_sel) begin gpio_data <= HWDATA[7:0]; end end always @(posedge HCLK or negedge HRESETN) begin if (!HRESETN) begin HRDATA <= 32'b0; end else if (reg_sel && !HWRITE) begin HRDATA <= {24'b0, gpio_data}; end end assign gpio_out = gpio_data; endmodule

这段 Verilog 里,reg_sel用 HSEL 与 HTRANS[0] 组合,可以避免在 IDLE 总线周期产生多余的写操作。wr_sel只在写传输周期有效,这样地址、数据和写使能天然对齐到 HCLK 上升沿。读取路径在非写周期把寄存器的当前值放到 HRDATA 上,M1 在下一个时钟上升沿采集总线上的数据。如果你的外设被 AHB 仲裁器分配了较宽的地址间距,比如 4KB,那么HADDR只需取地址译码所需的高位,不需要完整接入每个外设模块,这样可以省下不少 LUT。

GPIO 外设在设计时需要注意与 MCU 上 GPIO 的差异:FPGA 侧方向控制需要额外寄存器实现。上面的代码是一个只读/只写的简单寄存器,真实项目还会再加入方向寄存器和上拉使能寄存器。M1 没有像片上硬核那样直接给出 GPIO 启动状态,复位瞬间所有 IO 都受 FPGA 约束控制,默认应该是内部下拉或高阻,避免驱动外部设备导致电流倒灌。

4.2 UART 波特率产生与 AHB 状态机

UART 外设比 GPIO 复杂,因为它需要产生一个与系统时钟不同频率的波特率时钟。PGL22 评估板上如果用 50MHz 作为 HCLK,那么 115200 波特率的分频数约等于 434 次整数溢出,实际误差可以控制在允许范围内。UART 的 AHB 接口建议采用寄存器组方式,软件通过写发送数据寄存器触发移位,通过读状态寄存器判断忙标志。这里需要特别注意的是,不要把波特率产生逻辑放在 AHB 总线的关键路径上,而应当独立成一个计数器,用 HCLK 沿触发,这样不会导致回读时序延迟过长。

reg [15:0] baud_div; reg [15:0] baud_cnt; wire baud_tick = (baud_cnt == 16'd0); always @(posedge HCLK or negedge HRESETN) begin if (!HRESETN) begin baud_cnt <= 16'd0; end else if (baud_tick) begin baud_cnt <= baud_div - 16'd1; end else begin baud_cnt <= baud_cnt - 16'd1; end end

这段代码实现了一个可重装的波特率分频器。每次baud_tick为高时,计数器装载一次分频值,等于在 AHB 时钟基础上产生一个窄脉冲,这个脉冲可以分别用于发送移位寄存器和接收采样计数器。baud_div的值需要由软件在初始化阶段写入,计算公式是HCLK_FREQ / baud_rate。由于 M1 的软件启动最早会从复位向量执行,建议把 UART 初始化放到系统启动代码中,并用轮询方式等待发送完成,以免在中断未使能时丢失第一个字符。

4.3 把中断控制器接到 NVIC 而不是自己写优先级

M1 内部包含 NVIC,但外部外设的中断请求需要通过IRQ_EXT0IRQ_EXT1或者更多外部中断引脚送入内核。实际项目里通常把 UART 接收中断和定时器中断汇聚成多个向量,通过写 NVIC 使能寄存器来开启。这里最常犯的错误是,在 AHB 外设内部自行判断中断优先级,再统一给 M1 一个中断信号,这样会导致 NVIC 无法统计多个挂起中断,也无法正确处理抢占。正确做法是让每个需要中断的外设都输出一个独立的中断请求信号,在顶层把这些信号直接接到 M1 的中断输入上,NVIC 的优先级完全由软件配置。

中断在 M1 上还有一个特点:外部中断默认是电平触发,如果外设的中断源在中断服务程序还没读取状态寄存器时就一直被拉高,NVIC 会认为事件仍未处理完成,导致同一个中断反复进入。因此写外设时最好让中断请求信号读后自动清零,或者提供一个写 1 清 0 的中断状态位。我用 UART 接收时,会让接收数据有效标志同时作为中断源,软件读走数据寄存器后硬件自动拉低,这样中断服务程序结束前不需要额外写操作。

5. 调试与验证:把 M1 的启动状态从总线上“看”出来

在 PGL22 评估板上调试 M1,我通常会先用 GPIO 观察启动是否活着,再连调试器。由于软核的 SWD 调试接口不是通过芯片内置调试访问端口直接暴露的,而是由 ARM CoreSight 调试逻辑综合到 FPGA 中,因此常见错误名no cortex-m sw device found往往不是调试器故障,而是 M1 核心复位没有释放或调试时钟与内核时钟不是同一相位。调试器连接失败时,先用逻辑分析仪抓 SWCLK 和 SWDIO 的电平变化,确认目标板上确实有 50MHz 时钟,并且 SWD 引脚没有被误设为其他功能。

验证内核最直接的技巧是读取 CPUID 寄存器。M1 的 CPUID 地址固定在0xE000ED00,复位后该寄存器会自动返回 ARM 定义的处理器 ID,不需要安装额外外设。下面是一条可以在调试器命令行中执行的伪汇编,通过 DAP 总线读取该地址并写入 GPIO 数据寄存器:

dap apreg write dp-sel 0x00000000 dap apreg write ap-sel 0x00000000 dap apreg read 0xE000ED00

执行这条命令前,需要确保调试器能够进入 JTAG/SWD 模式并且 M1 的调试组件已经上电。读取回来的值如果符合 M1 的 CPUID 编码,说明内核时钟、复位和 AHB 总线三者已经完全正常工作。此时再用内存写命令向0x20000000写入一个简单的跳转指令,配合复位向量表里的SPPC值,就能验证取指路径是否畅通。这个方法比直接看示波器上的 GPIO 翻转更准确,因为它直达 CPU 内部。

最后建议在综合时为 AHB 总线的HADDRHWDATAHREADY这三个信号加一两个观测点,不需要抓完整总线,就能在工具自带的波形窗口里看到 M1 复位后第一次访问内存时的地址序列。如果HADDR在复位后停留在0x00000000不再变化,基本可以确定向量表内容没有正确写入 SRAM;如果HREADY长期为低,则需要回头检查 AHB 互联模块里的等待状态信号拼接,优先级高于任何外设代码改动。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询