目的:通过nios II上的实现软核读写DDR2
相比之前的hello world项目的区别在于1. 在 Qsys 系统中添加 DDR2 的 IP 核,以及DDR2 IP 核的参数配置;2. 在 Eclipse开发环境里对 DDR2 的存储空间进行读写数据和校验程序不同。
下面是针对本项目实现过程中一些疑惑点的见解:
1. 为何需要在QSYS中添加DDR2的IP核?为何不能直接访问外部DDR2?
无论是通过FPGA程序读写DDR2还是通过内嵌的CPU软核来读取,都无法直接简单的控制DDR2的读写。DDR2的IP核,相当于对DDR2进行了一层操作的包装,用户可以通过的简单操作来完成对DDR2的控制。而DDR2 IP核放在FPGA代码里或者内嵌的NIOS里均可以,只不过放到内核中,就可以把DDR2当作内部存储器访问,更为方便。
2. 工程移植
将之前的工程文件都复制到新的文件夹下,然后删除software目录下的文件。这样就可以直接在NIOS中修改内核的参数,然后重新新建软件工程,保存在software目录下。software目录下主要是对内核的编程,即在eclipse环境下的工程。
也可以将复制过来的文件夹保持不动,修改NIOS ii后,需要在原eclipse文件中重新生成BSP包(相当于更新,添加新增的IP核对应的底层支持文件,包含寄存器描述、函数、宏定义等)。操作为:
选择BSP项目,右键-->Nios ii -->generate BSP. (但这种方法可能会在编译时出现一些未知错误,不推荐使用)
在打开Qsys时,需要重新选择kernel.qsys文件,因为项目移植后,这个文件的位置也发生了变化,需要重新选中本项目对应的software文件。然后根据上面的两种方法编写代码,要么新建工程,要么更新BSP包。
3. 在本项目中,不使用RAM核,MCU直接将程序和数据存储在DDR2中,因此使用外部存储器,可以节省FPGA资源。
4. 配置DDR2 IP核,包含以下内容:
1)Device family
2)Speed grade
3)输入参考时钟频率
4)DDR2 时钟频率
5)Controller data rate: FULL---verilog 逻辑部分数据位宽 X2,速度/2,达到了降频的目的。
6) Memory Presets List 里双击选择 Custom(Micro MT47H64M16)。
若没有,修改现有的IC的参数然后生成我们需要IC。在Memory Presets List里选择Memory vendor为Micron, Memory fomart为Dicrete Device, 这样在右边的 Memory Presets 列表中会出现配置好的 DDR2 芯片,选择型号差不多的 MT47H32M16-5E, 再点击 Modify parameters..按钮。 打开 Preset Editor 设置 DDR 的参数,这里的 DDR2 的参数设置分为 3 种:DDR2 SDRAM属性设置,DDR2 SDRAM 初始化选项和DDR2 SDRAM 时序参数设置。其中All Parameters里包含这三项的全部内容。 Parameters 里设置的参数需要参考 DDR2(MT47H64H16)的 datasheet, 我们这里只需要修改 Row address width 行的参数为 13,其它参数都不需要修改,因为这些参数MT47H64H16 跟MT47H32M16-5E 芯片都是一样的。
参数配置可以参考实现和参数化存储器IP.pdf
5. 生成的NIOS II软核会自动将DDR2相关的时序文件添加进工程。如下:
6. 打开TCL Scripts,可以看到关于DDR2的相关pin .tcl文件,运行后打开Pin planning可以发现所有的ddr2的PIN的属性均被配置过了,除了需要自己手动根据IC型号和板子去连接PIN脚。
7. 在eclipse中新建BSP模板的项目,为何需要选择.sopcinfo文件?这个文件主要是干嘛的?
info文件主要是关于软件的信息,软件的用途、名称、操作系统、需要的磁盘空间等。因此,.sopcinfo主要包含了我们生成的软核的一些信息。添加此文件,就相当于限定我们的编译环境,使编译的程序能直接运行在软核内。
7. 通过调用memtest_small模板中代码来实现功能。
int main(void)
{
int ch;
/* Print the Header */
MenuHeader();
while (1)
{
printf("\nPress enter to continue or 'q' to quit.\n");
ch = alt_getchar();
putchar(ch);
if(ch == 'q' || ch == 'Q')
{
printf( "\nExiting from Memory Test.\n");
break;
}
else if (ch == '\n')
{
TestRam();
}
}
return (0);
}
从上面代码看出,一共使用了4个函数:
1)MenuHeader(),打印出本代码的功能
2)ch =alt_getchar(),读取用户键盘按下的字符
3)putchar(ch),该函数以无符号 char 强制转换为 int 的形式返回写入的字符,如果发生错误则返回 EOF。
4)TestRam(),实现ddr2的测试,如下:
static void TestRam(void)
{
int memory_base, memory_end, memory_size;
int ret_code = 0x0;
/* Find out what range of memory we are testing */
MemGetAddressRange(&memory_base, &memory_end);
memory_size = (memory_end - memory_base);
printf("\n");
printf("Testing RAM from 0x%X to 0x%X\n", memory_base, (memory_base + memory_size));
/* Test Data Bus. */
ret_code = MemTestDataBus(memory_base);
if (ret_code)
printf(" -Data bus test failed at bit 0x%X", (int)ret_code);
else
printf(" -Data bus test passed\n");
/* Test Address Bus. */
if (!ret_code)
{
ret_code = MemTestAddressBus(memory_base, memory_size);
if (ret_code)
printf(" -Address bus test failed at address 0x%X", (int)ret_code);
else
printf(" -Address bus test passed\n");
}
/* Test byte and half-word access. */
if (!ret_code)
{
ret_code = MemTest8_16BitAccess(memory_base);
if (ret_code)
printf(" -Byte and half-word access test failed at address 0x%X", (int)ret_code);
else
printf(" -Byte and half-word access test passed\n");
}
/* Test that each bit in the device can store both 1 and 0. */
if (!ret_code)
{
printf(" -Testing each bit in memory device.");
ret_code = MemTestDevice(memory_base, memory_size);
if (ret_code)
printf(" failed at address 0x%X", (int)ret_code);
else
printf(" passed\n");
}
if (!ret_code)
printf("Memory at 0x%X Okay\n", memory_base);
}
问题:
1. 这个项目是通过使用模板工程来实现功能,即直接调用现有的程序。如果出现问题,那么要么是内核的配置问题,要么是软件的配置问题。