需要软件
仿真软件
- Modelsim-Intel FPGA Starter Edition 10.5b
综合工具
- Xilinx FPGA: VIVADO 2018.2
- Intel FPGA: Quartus Prime 19.1
Windows Subsystem for Linux(WSL)
- make
- minicom
软件编译调试工具
- Keil uVision5
当前版本SoC架构
如下图所示,整个SoC基于CMSDK开发,除顶层模块与添加到自定义LCD外设模块外,无需添加或编写任何verilog代码。
当前版本SoC采用两级AHB总线:
- 第一级AHB总线矩阵上总共有5个SlavePort,分别连接CortexM3的ICODE、DCODE以及SYS总线Master,剩下两个SlavePort被设置为Default模式,预留给DMA与加速器;总共有5个MasterPort,分别连接I/D TCM总线接口、APB桥、AHB同步桥以及预留给DDR控制器的DefaultSlave。
- 第二级AHB总线矩阵只有1个SlavePort,连接AHB同步桥的Master接口;2个MasterPort分别连接预留给摄像头的DefaultSlave与自定义LCD屏幕的驱动硬件。
- APB桥总共支持16个APB外设,目前只挂载了1个UART,剩余15个被设置为Default模式。
使用CMSDK生成总线矩阵
- 第一步:设计总线矩阵地址映射关系,以第一级AHB总线矩阵为例,下图为其具体端口与映射关系。
这里采用了一种稀疏的地址映射关系(所有M端口无法覆盖整个32bit地址空间),同时也采用了一种固定的地址映射(无remap且M*
端口与任意S*
端口之间的trans地址是固定的,例如S2
与S3
均通过0x20000000-0x2000ffff
地址访问M1
端口)
- 第二步:根据地址映射关系,在
example2x3_sparse.xml
的基础上修改编写XML文件(DesignStart/cmsdk/logical/cmsdk_ahb_busmatrix/xml/yourbusmtx.xml
)编写每个S*
端口的连接状况,以上图S2
端口为例,S2
分别与M1
、M2
、M3
、M4
端口连接,则先声明与S2
连接的每个M*
端口名,再编写每个M*
端口的地址映射,如下所示的代码。
<!-- SYS SLAVE -->
<slave_interface name="S2">
<sparse_connect interface="M1"/>
<sparse_connect interface="M2"/>
<sparse_connect interface="M3"/>
<sparse_connect interface="M4"/>
<address_region interface="M1" mem_lo="20000000" mem_hi="2000ffff" remapping='none'/>
<address_region interface="M2" mem_lo="40000000" mem_hi="4fffffff" remapping='none'/>
<address_region interface="M4" mem_lo="50000000" mem_hi="5fffffff" remapping='none'/>
<address_region interface="M3" mem_lo="60000000" mem_hi="9fffffff" remapping='none'/>
</slave_interface>
编写每个M
端口的声明,总共有5个M
端口,则需要声明5次,如下代码所示。
<!-- Master interface definitions -->
<!-- ITCM MASTER -->
<master_interface name="M0"/>
<!-- DTCM MASTER -->
<master_interface name="M1"/>
<!-- APBBRIDGE MASTER -->
<master_interface name="M2"/>
<!-- DDR MASTER -->
<master_interface name="M3"/>
<!-- AHBBRIDGE MASTER -->
<master_interface name="M4"/>
- 第三步:在
cmsdk_ahb_busmatrix/
目录下编写makefile
,命令在cmsdk_ahb_busmatrix/README.txt
中有详细说明,如下代码所示。
all:
sudo bin/BuildBusMatrix.pl -xmldir xml -cfg cmsdk_ahb_busmatrix_l1.xml -over -verbose
- 第四步:在
cmsdk_ahb_busmatrix/
目录下打开WSL执行make
,如下图所示。
- 第五步:若执行通过,则会得到如下图所示的信息,至此,成功使用CMSDK生成了总线矩阵。
添加CMSDK APB外设
这里以添加cmsdk_apb_uart
模块为例,添加UART外设至APB总线上,并参照DesignStart中的样板工程完成外设的软件驱动编写,DesignStart/cmsdk/logical/apb_subsystem/
文件夹中提供了一个APB总线系统的样板,可以参照其完成硬件设计。
APB总线系统与APB SlaveMUX
cmsdk_apt_slave_mux
模块完成对多个APB Slave的地址译码,以及选择APB Slave的pready
、prdata
、pslverr
信号返回给APB Master,其互连结构如下图。
添加外设时,只需要将外设的paddr
、penable
、pwdata
、pwrite
这4个信号直接与APB Master(即cmsdk_ahb_to_apb
)端口对应的信号连接,将外设的psel
、pslverr
、pready
、prdata
这一组信号与cmsdk_apb_slave_mux
中的一组信号连接即可。 注意,cmsdk_ahb_to_apb
已经对AHB地址信号进行译码,省略了高16bit,APB Bridge在第一级AHB总线中的地址为0x40000000-0x4fffffff
(其中有效段为0x40000000-0x4000ffff
),DECODER4BIT中又对paddr[15:0]
的高4bit进行译码,即SLAVE PORT0(psel0
、pslverr0
、pready0
、prdata0
)这一组端口连接的外设,其PADDR地址范围是0x0000-0x0fff
,其在整个总线系统中的地址为0x40000000-0x40000fff
(同理,SLAVE PORT1所连接的外设paddr
地址为0x1000-0x1fff
,以及完整地址为0x40001000-0x40001fff
)。 完成外设驱动
- 第一步:在
DesignStart/m3designstart/software/
文件夹下找到_core\_cm3.h_、_core\_cmFunc.h_、_core\_cmInstr.h_三个头文件并复制到工程目录下,这三个头文件包含了CortexM3内部的PPB结构体定义与相关函数。 - 第二步:根据DTCM修改启动代码中的堆栈大小,在图2中,DTCM地址空间是
0x20000000-0x2000ffff
,那么设置堆栈大小都为0x8000
即可,如下.
Stack_Size EQU 0x00008000
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_Size EQU 0x00008000
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
- 第三步:移植
DesignStart/m3designstart/software/
文件夹中相关驱动程序以及头文件。在DesignStart/m3designstart/software/cmsis/Device/ARM/CM3DS/Include/
文件夹中找到3个头文件,将其3个头文件中相关外设驱动结构体、驱动程序代码复制到自己工程目录中对应的头文件中。在../Source/
文件夹下找到两个C文件,分别是驱动函数实现以及系统初始化程序,将其相关内容复制到自己工程目录下对应的文件内。整个工程所需要的软件如下图.
编写main进行测试
在进行测试之前,还有很大一部分工作:
- 完成硬件顶层编写
- SoC的启动仿真(系统能否正常启动、总线是否正常工作可以通过仿真检查)
- Keil工程建立设置与编译
- 使用EDA工具综合下载
这些工具的使用流程不再赘述。 在此工程中,除驱动程序移植外,还移植了_Retarget.c_程序,实现了printf
与scanf
重定向到uart,配合PC中的minicom软件进行uart读写,编写main函数如下所示:
int main(void) {
printf("******************\n");
printf("cortex-m3 startup!\n");
printf("******************\n");
uint32_t buf[10];
while(1) {
scanf("%s",buf);
printf("Double the input string is : %s %s\n",buf,buf);
}
return 0;
}
实现的功能是在SoC启动时打印启动信息,并且将uart接收到的字符串重新发送两边,测试截图如下.
添加自定义AHB外设
前言:特别感谢_@人生状态机_同学提供的AHB\LCD软硬件驱动代码,他在片上系统与人工智能领域有深入的研究,熟悉Cortex-M0、Cortex-M3片上系统搭建方法。 他的知乎专栏 :人生状态机的知乎专栏 他的极术社区主页人生状态机的极术社区主页
编写外设硬件代码
编写硬件代码之前,需要首先了解LCD屏幕接口信号及其时序要求,这次屏幕没有用到触控输入,只做了显示功能,那么只需要完成接口写功能即可,屏幕显示由16bit并口数据驱动,其时序图如。
可见,要实现屏幕接口写,就要完成对CS
、RES
、WR
、RD
、DATA[15:0]
这些信号的驱动,具体的模块连接图如下图所示。
在确定好接口信号之后,我们选择了使用寄存器直接驱动,即每个接口信号都分别对应一个寄存器,处理器能够直接对其读写,信号的时序则通过处理器读写+SysTick计时实现。这样做的好处就是硬件设计非常简单,只需要对AHB总线接口进行地址译码,直接映射到寄存器上,再将寄存器输出直接连接到屏幕即可;当然缺点也非常明显,这个屏幕由处理器直接驱动,因此需要占用大量处理器的时间,具体实现可以在附件_custom\_ahb\_lcd.v_文件中查看。
完成外设软件驱动
类似于cmsdk_apb_uart
,自定义外设的软件驱动也基本上可以通过移植实现,例如此LCD屏幕商家提供了详细的驱动软件,我们只需要将商家提供的软件驱动中底层的寄存器读写函数重新适配到我们的SoC中即可,移植步骤有一下:
- 第一步:在_CortexM3.h_中定义外设硬件寄存器结构体。
typedef struct {
volatile uint32_t LCD_CS;
volatile uint32_t LCD_RS;
volatile uint32_t LCD_WR;
volatile uint32_t LCD_RD;
volatile uint32_t LCD_RST;
volatile uint32_t LCD_BL_CTR;
volatile uint32_t LCD_DATA[16];
}LCDType;
#define LCD_BASE (0x50010000UL)
#define LCD ((LCDType *) LCD_BASE )
- 第二步:将屏幕驱动所有函数声明复制到_CortexM3\_driver.h_中,如下图.
- 第三步:复制屏幕驱动所有函数实现到_CortexM3\_driver.c_中,同时修改底层寄存器读写函数,以适配当前SoC,例如此处的
LCD_WriteReg();
void LCD_WR_REG( uint16_t data ) {
LCD_RS_CLR;
LCD_CS_CLR;
delay(500);
MakeData( data );
LCD_WR_CLR;
delay(500);
LCD_WR_SET;
LCD_CS_SET;
}
void MakeData( uint16_t data ) {
LCD->LCD_DATA[0] = ( data >> 0 ) & 1 ;
LCD->LCD_DATA[1] = ( data >> 1 ) & 1 ;
LCD->LCD_DATA[2] = ( data >> 2 ) & 1 ;
LCD->LCD_DATA[3] = ( data >> 3 ) & 1 ;
LCD->LCD_DATA[4] = ( data >> 4 ) & 1 ;
LCD->LCD_DATA[5] = ( data >> 5 ) & 1 ;
LCD->LCD_DATA[6] = ( data >> 6 ) & 1 ;
LCD->LCD_DATA[7] = ( data >> 7 ) & 1 ;
LCD->LCD_DATA[8] = ( data >> 8 ) & 1 ;
LCD->LCD_DATA[9] = ( data >> 9 ) & 1 ;
LCD->LCD_DATA[10] = ( data >> 10 ) & 1;
LCD->LCD_DATA[11] = ( data >> 11 ) & 1;
LCD->LCD_DATA[12] = ( data >> 12 ) & 1;
LCD->LCD_DATA[13] = ( data >> 13 ) & 1;
LCD->LCD_DATA[14] = ( data >> 14 ) & 1;
LCD->LCD_DATA[15] = ( data >> 15 ) & 1;
}
这就是软件驱动寄存器的方法,CPU直接修改每个寄存器的值,并通过systick实现的delay()
函数达到时序要求。
编写main函数进行测试
这里将UART功能加入,通过PC中的键盘输入ASDW,将字符通过UART传入SoC中,控制屏幕中的方块移动。
源码下载
由于知乎不提供附件功能,我也懒得上传到github了,所以源码可以极术社区的文章中下载:使用CMSDK搭建CortexM3SoC
END
知乎:https://zhuanlan.zhihu.com/p/365058686
更多内容请关注其实我是老莫的网络书场专栏