本文是关于基于安路FPGA EG4S20BG256移植Cortex M0内核的笔记。硬件平台使用硬木课堂的安路核心板,软件使用安路的TD5.6.2和keil5。(博主刚学FPGA不久,文中有不足之处请帮忙指出)
在移植之前,博主看了网上很多的文章、教程,感觉没有找到一篇手把手从头到尾移植的教程,或者缺少RAM IP核移植,或者缺少SWD、JTAG的引出,最后是结合了各家的代码和教程,以及ARM官方给的示例,移植完了M0内核。
在移植之前,笔者已经学习了AHB总线、ARM内核、单片机、FPGA verilog基础语法部分。
一、准备部分
1.1 DesignStart计划
ARM官方的DesignStart计划提供了Cortex-M0、Cortex-M3、Cortex-A5等内核,每一种IP也有不同的版本:Eval版,FPGA版,Pro版。其中Eval版本提供处理器核的网表形式的Verilog代码。
进入网站:https://developer.arm.com/,搜索“Cortex-M0 Designstart Eval”,就可以找到Eval版的资源。下载资源需要登录ARM账号。
如果注册ARM账号太麻烦,也可以在CSDN等其它平台上下载。
1.2 TD和Keil5准备
安路FPGA开发需要使用安路官方的TD(TangDynasty),笔者使用的是TD5.6.2,好像TD不同版本支持的芯片是不一样,读者可以认真阅读以下安路官网。
至于Keil5笔者就不再赘述了,开发ARM内核的必备,网上也有很多资源了。
下载了TD还需要下载TD的License,注册以后免费下载。
二、解析ARM DesignStart
解压之后,里面有文档说明,建议读者详细阅读ARM官方给的文档,里面说的很详细,虽然是全英文,可以带着翻译慢慢读,这对后面移植还是很有帮助的。
documentation/arm_cortex_m0_designstart_eval_user_guide.pdf里第14页,1.2节有关于Cortex-M0 DesignStart Eval的目录结构说明。
打开systems/cortex_m0_mcu/verilog/cmsdk_mcu.v,然后根据cmsdk_mcu.v中的模块,层层梳理,可以得到下图所示的结构。
将cmsdk_mcu.v看作一个顶层文件。
- AHB_RAM和AHB_ROM,即RAM和ROM模块。
- cmsdk_mcu_clkctrl:时钟控制模块。
- cmsdk_mcu_pin_mux:引脚复用模块。
- cmsdk_mcu_system:Cortex-M0系统的系统级设计。
- cmsdk_mcu_addr_decode:AHB地址解码。
- cmsdk_ahb_slave_mux:AHB从多路复用器。
- cmsdk_ahb_default_slave:AHB总线默认从属设备,当被选择传输时返回error。
- CORTEXM0INTEGRATION:Cortex-M0 DesignStart processor 集成级。
- Cortexm0ds_logic:Cortex-M0 DesignStart processor 逻辑级。
- cmsdk_ahb_cs_rom_table:该模块实现了一个通用的4入口的AHB CoreSight ROM Table。
- cmsdk_mcu_sysctrl:系统控制器。
- cmsdk_ahb_gpio:IOP GPIO外设。
- cmsdk_mcu_stclkctrl:系统嘀嗒信号控制。
- cmsdk_apb_subsystem:APB子系统
- cmsdk_apb_slave_mux:APB从多路复用器
- cmsdk_ahb_to_apb:AHB-APB桥
- cmsdk_apb_timer:定时器
- cmsdk_apb_dualtimers:两个32位向下计数器
- cmsdk_apb_watchdog:看门狗
- cmsdk_apb_uart:串口
- cmsdk_apb_test_slave:在APB子系统中测试等待状态和error回应的从设备。
三、移植
3.1 导入文件
根据第二章的结构分析,将下列文件从Cortex-M0 DesignStart中找到,添加到工程中
- cores/cortexm0_designstart_r2p0/logical/cortexm0_integration/verilog/cortexm0ds_logic.v
- cores/cortexm0_designstart_r2p0/logical/cortexm0_integration/verilog/CORTEXM0INTEGRATION.v
- systems/cortex_m0_mcu/cmsdk_ahb_cs_rom_table.v
- systems/cortex_m0_mcu/cmsdk_mcu.v
- systems/cortex_m0_mcu/cmsdk_mcu_addr_decode.v
- systems/cortex_m0_mcu/cmsdk_mcu_clkctrl.v
- systems/cortex_m0_mcu/cmsdk_mcu_defs.v
- systems/cortex_m0_mcu/cmsdk_mcu_pin_mux.v
- systems/cortex_m0_mcu/cmsdk_mcu_stclkctrl.v
- systems/cortex_m0_mcu/cmsdk_mcu_sysctrl.v
- systems/cortex_m0_mcu/cmsdk_mcu_system.v
- logic/cmsdk_ahb_default_slave/cmsdk_ahb_default_slave.v
- logic/cmsdk_ahb_gpio/cmsdk_ahb_gpio.v
- logic/cmsdk_ahb_gpio/cmsdk_ahb_to_iop.v
- logic/cmsdk_ahb_slave_mux/cmsdk_ahb_slave_mux.v
- logic/cmsdk_ahb_to_apb/cmsdk_ahb_to_apb.v
- logic/cmsdk_apb_dualtimers/cmsdk_apb_dualtimers.v
- logic/cmsdk_apb_dualtimers/cmsdk_apb_dualtimers_defs.v
- logic/cmsdk_apb_dualtimers/cmsdk_apb_dualtimers_frc.v
- logic/cmsdk_apb_slave_mux/cmsdk_apb_slave_mux.v
- logic/cmsdk_apb_subsystem/cmsdk_apb_subsystem.v
- logic/cmsdk_apb_subsystem/cmsdk_apb_test_slave.v
- logic/cmsdk_apb_timer/cmsdk_apb_timer.v
- logic/cmsdk_apb_uart/cmsdk_apb_uart.v
- logic/cmsdk_apb_watchdog/cmsdk_apb_watchdog.v
- logic/cmsdk_apb_watchdog/cmsdk_apb_watchdog_defs.v
- logic/cmsdk_apb_watchdog/cmsdk_apb_watchdog_frc.v
- logic/cmsdk_iop_gpio/cmsdk_iop_gpio.v
- logic/models/memories/cmsdk_ahb_memory_models_defs.v
- logic/models/memories/cmsdk_ahb_ram.v
- logic/models/memories/cmsdk_ahb_ram_beh.v
- logic/models/memories/cmsdk_ahb_rom.v
以上共计32个文件,加入到工程中,将cmsdk_mcu.v设置为顶层文件,然后直接进行编译。右键点击cmsdk_mcu->Set As Top。
控制台会报错误,循环次数不能大于10000,这是由于cmsdk默认设置的RAM和ROM时64KB,而这样建立工程的时候没有使用FPGA的RAM IP核,占用了大量的寄存器和逻辑单元。
3.2 修改RAM和ROM大小
在cmsdk_mcu.v文件里,找到cmsdk_ahb_rom和cmsdk_ahb_ram两个模块,将参数AW从16修改为8。并将cmsdk_ahb_rom里的filename参数给删除,如果需要烧录映像,可以将hex文件添加,这里暂时删除。
重新进行编译,这里就已经能够顺利编译完成了。
3.3 Keil读取内核
在能够顺利编译之后,根据引脚,自行分配SWD引脚,重新编译,并下载到开发板中,连接DAP仿真器,用Keil创建一个ARM CM0的工程,打开创建好的KEIL工程。
先在小魔法棒中检查是否能读取到ARM内核。如果能够成功读取到IDCODE,并且读取到ARM CoreSight SW-DP,表明内核已经可以工作了。
3.4 激活SWD
此时,还不能正常使用调试功能,需要在内核中将调试功能打开。找到cmsdk_mcu_system.v,找到278行附近:
wire EDBGRQ;
在后面添加以下代码:
wire cdbgpwrupack_wire;
wire cdbgpwrupreq_wire;
assign cdbgpwrupack_wire = cdbgpwrupreq_wire;
然后找到CORTEXM0INTEGRATION模块,POWER MANAGEMENT里的CDBGPWRUPREQ和CDBGPWRUPACK,分别填入cdbgpwrupreq_wire和cdbgpwrupack_wire。
重新编译,下载。
3.5 Keil进行内存调试
打开KEIL工程,取消勾选魔法棒->Debug->Load Application at Startup,取消勾选魔法棒->Debug->Settings->Reset after Cornnect,取消勾选魔法棒->Utilities->Update Target before Debugging。
点击调试。点击工具栏->View->Memory Windows->Memory 1。在内存窗口中,输入地址0x20000000,按下回车键,然后随意修改任意地址,观察是否能成功修改内存的值。如果能够成功修改,说明RAM和ROM是没有问题的。
四、RAM和ROM
4.1 自动RAM和ROM
ARM官方的代码生成的RAM和ROM不能够太大(笔者能力有限),笔者从网上抄了一份RAM的代码,由于时间久远,忘记出处了,无法标记原作者。
// AHB-Lite Memory Modulemodule EG4_AHBLite_AutoRAM#(parameter MEMWIDTH = 12) // Size = 4KB(input wire HSEL,input wire HCLK,input wire HRESETn,input wire HREADY,input wire [31:0] HADDR,input wire [1:0] HTRANS,input wire HWRITE,input wire [2:0] HSIZE,input wire [31:0] HWDATA,output wire HREADYOUT,output reg [31:0] HRDATA,output wire HRESP);assign HREADYOUT = 1'b1; // Always readyassign HRESP = 1'b0;// Memory Arrayreg [31:0] memory[0:(2**(MEMWIDTH-2)-1)];// Registers to store Adress Phase Signalsreg [31:0] hwdata_mask;reg we;reg [31:0] buf_hwaddr;// Sample the Address Phase always @(posedge HCLK or negedge HRESETn)beginif(!HRESETn)beginwe <= 1'b0;buf_hwaddr <= 32'h0;endelseif(HREADY)beginwe <= HSEL & HWRITE & HTRANS[1];buf_hwaddr <= HADDR;casez (HSIZE[1:0])2'b1?: hwdata_mask <= 32'hFFFFFFFF; // Word write2'b01: hwdata_mask <= (32'h0000FFFF << (16 * HADDR[1])); // Halfword write2'b00: hwdata_mask <= (32'h000000FF << (8 * HADDR[1:0])); // Byte writeendcaseendend// Read and Write Memoryalways @ (posedge HCLK)beginif(we) memory[buf_hwaddr[MEMWIDTH:2]] <= (HWDATA & hwdata_mask) | (HRDATA & ~hwdata_mask);HRDATA = memory[HADDR[MEMWIDTH:2]];endendmodule
4.2 IP核生成RAM和ROM
笔者尝试了自己使用安路TD的IP generate生成一个RAM IP核,然后接入到AHB总线中,代码如下,仅供参考
// AHB-Lite Memory Modulemodule EG4_AHBLite_BRAM
(input wire HCLK,input wire HRESETn,input wire HSEL,input wire HREADY,input wire [31:0] HADDR,input wire [1:0] HTRANS,input wire HWRITE,input wire [2:0] HSIZE,input wire [31:0] HWDATA,output wire HREADYOUT,output wire [31:0] HRDATA,output wire HRESP
);assign HREADYOUT = 1'b1;assign HRESP = 1'b0;reg [31:0] buf_hwaddr;reg ram_cs;reg [3:0] ram_we;wire [31:0] buf_hraddr;assign buf_hraddr = HADDR>>2;always @(posedge HCLK or negedge HRESETn) beginif (!HRESETn) beginbuf_hwaddr <= 32'd0;ram_cs <= 1'b0;ram_we <= 4'd0;endelse beginbuf_hwaddr <= HADDR>>2;ram_cs <= HSEL & HWRITE & HTRANS[1];if (HWRITE) begin casez (HSIZE[1:0])2'b1?: ram_we <= 4'b1111; // Word write2'b01: ram_we <= (4'b0011<< (HADDR[1]<<1)); // Halfword write2'b00: ram_we <= (4'b0001<< HADDR[1:0]); // Byte writeendcaseendelse beginram_we <= 4'd0;ram_cs <= 1'b0;endendendmcu_ram u_mcu_ram( .clka (HCLK), // clock.cea (ram_cs), // clock enable,active High// .rsta (1'b0), // reset,active High.addra (buf_hwaddr), // address input.dia (HWDATA), // data input.wea (ram_we), // write byte.clkb (HCLK),.ceb (1'b1),// .rstb (1'b0), // reset,active High.addrb (buf_hraddr), // address input.dob (HRDATA) // data output// .oceb (1'b1) // data output register enable);// parameter DATA_WIDTH_A = 32; // parameter ADDR_WIDTH_A = 12;// parameter DATA_DEPTH_A = 4096;// parameter DATA_WIDTH_B = 32;// parameter ADDR_WIDTH_B = 12;// parameter DATA_DEPTH_B = 4096;// parameter REGMODE_A = "NOREG";// parameter REGMODE_B = "NOREG";// parameter WRITEMODE_A = "NORMAL";// parameter WRITEMODE_B = "NORMAL";// EG_LOGIC_BRAM #( .DATA_WIDTH_A(DATA_WIDTH_A),// .DATA_WIDTH_B(DATA_WIDTH_B),// .ADDR_WIDTH_A(ADDR_WIDTH_A),// .ADDR_WIDTH_B(ADDR_WIDTH_B),// .DATA_DEPTH_A(DATA_DEPTH_A),// .DATA_DEPTH_B(DATA_DEPTH_B),// .BYTE_ENABLE(8),// .BYTE_A(4),// .BYTE_B(4),// .MODE("PDPW"),// .REGMODE_A(REGMODE_A),// .REGMODE_B(REGMODE_B),// .WRITEMODE_A(WRITEMODE_A),// .WRITEMODE_B(WRITEMODE_B),// .RESETMODE("SYNC"),// .IMPLEMENT("9K"),// .INIT_FILE("NONE"),// .FILL_ALL("NONE"))// inst(// .dia(HWDATA),// .dib({32{1'b0}}),// .addra(buf_hwaddr),// .addrb(buf_hraddr),// .cea(ram_cs),// .ceb(1'b1),// .ocea(1'b0),// .oceb(1'b0),// .clka(HCLK),// .clkb(HCLK),// .wea(1'b0),// .bea(ram_we),// .web(1'b0),// .beb(2'b00),// .rsta(1'b0),// .rstb(1'b0),// .doa(),// .dob(HRDATA));endmodule
五、结束
至此,已经能够跑通M0内核,并且能够下载程序和运行了,关于代码里带有的Uart、TIM定时器等外设,读者可以自行参考ARM官方的案例和手册。
关于自己写APB、AHB外设,读者可以参考ARM官方给的各种外设,是如何挂到总线上,如何例化,如何读写寄存器、中断等,ARM官方给的示例非常丰富,值得仔细学习。