(原创)基于wishbone总线的IP核开发与验证
摘要:介绍了Wishbone总线规范;设计了一个基于aemb系统符合Wishbone总线规范的vga 输出显示的外设;设计了在vga显示器上显示ascii码字符的程序;利用uart设计了一个简单的控制shell来控制vga核的显示。
一、Wishbone总线介绍
由silicore公司提出,后移交给OpenCores组织维护。Wishbone总线规范是一种片上系统IP核互连体系结构。它定义了一种IP核之间公共的逻辑接口,减轻了系统组件集成的难度,提高了系统组件的可重用性、可靠性和可移植性,加快了产品市场化的速度。
Wishbone总线结构十分简单,它仅仅定义了一条高速总线。在一个复杂的系统中,可以采用两条Wishbone总线的多级总线结构:其一用于高性能系统部分,其二用于低性能外设部分,两者之间需要一个接口。这个接口虽然占用一些电路资源,但这比设计并连接两种不同的总线要简单多了。用户可以按需要自定义Wishbone标准,如字节对齐方式和标志位(TAG)的含义等,还可以加上一些其他的特性。
Wishbone总线有四种不同的IP核互连方式
Point-to-point Interconnection:用于两个IP核直接互连。
图表 1 Point-to-point Interconnection
Data Flow Interconnection:用于多个串行IP核之间的数据并发传输。
图表 2 Data Flow Interconnection
Shared Bus Interconnection:多个IP核共享一条总线。
图表 3 Shared Bus Interconnection
Crossbar Switch Interconnection:同时连接多个主从部件,提高系统地吞吐量。
图表 4 Crossbar Switch Interconnection
本文中用到的是第四种:Crossbar Interconnection (Crossbar Switch)
Wishbone总线的主要特征有:
Ø 所有应用适用于同一种总线体系结构;
Ø 是一种简单、紧凑的逻辑IP核硬件接口,只需很少的逻辑单元即可实现;
Ø 时序非常简单;
Ø 主/从结构的总线,支持多个总线主设备;
Ø 8~64位数据总线(可扩充);
Ø 单周期读/写;
Ø 支持所有常用的总线数据传输协议,如单字读/写周期、块传输周期、控制操作及其它的总线事务等;
Ø 支持多种IP核互连网络,如单向总线、双向总线、基于多路互用的互连网络、基于三态的互连网络等;
Ø 支持总线周期的正常结束、重试结束和错误结束;
Ø 使用用户自定义标记(TAG),确定数据传输类型、中断向量等;
Ø 仲裁器机制由用户自定义;
Ø 建立于硬件技术(FPGA、ASIC、bipolar、MOS等),IP核类型(软核、固核、硬核),综合工具,布局和布线技术等。
二、Wishbone总线信号和时序
Wishbone总线信号分为4类:系统控制信号、主从共有信号、主设备信号和从设备信号。
系统控制信号
Ø CLK_O 系统时钟输出。
Ø RST_O 复位输出。
主从共有信号
Ø CLK_I 时钟输入。
Ø DAT_I() 数据输入总线。
Ø DAT_O() 数据输出总线。
Ø RST_I() 复位输入。
主设备信号
Ø ACK_I 应答输入。
Ø CYC_O 循环输出。
Ø ERR_I 错误输入。
Ø LOCK_O 锁定输入。
Ø RTY_O 重试输入。
Ø SEL_O() 选择输出总线。
Ø STB_O() 选通输出。
Ø WE_O 写使能输出。
从设备信号
Ø ACK_O() 应答输出。当它有效时表明一个总线循环的正常结束。
Ø ADR_I() 地址输入总线。总线的高端由IP Core的地址总线的宽度决定,总线的低端边界由数据端口的宽度和粒度决定。
Ø CYC_I 循环输入。当它有效时,表明正在进行一个正确的总线循环。
Ø ERR_O() 错误输出。
Ø LOCK_I 锁定输入。当它有效时,表明当前的总线循环不能被打断。
Ø RTY_O 重试输出。指示接口没有准备好接收或发送数据,并且循环应当重试。
Ø SEL_I() 选择输入总线。
Ø STB_I 选通输入。当它有效时,指示这个从设备被选中。
Ø WE_I 写使能输入。指示当前的本地总线循环是READ循环还是WRITE循环。这个信号在READ循环中式无效的,在WRITE循环中是有效地。
三、Wishbone总线循环
典型的Wishbone总线循环有5中:SINGLE READ, SINGLE WRITE, BLOCK READ, BLOCK WRITE.
本文中用到的就是SINGLE READ/WRITE的方式。
以下是SINGLE READ/WRITE的时序图和SINGLE READ方式的说明,其实所有信号或信号组合都可以在Avalon总线信号找到对应。
SINGLE READ Cycle
Figure 3-3 shows a SINGLE READ cycle. The bus protocol works as follows:
CLOCK EDGE 0: MASTER presents a valid address on [ADR_O()] and [TGA_O()].
MASTER negates [WE_O] to indicate a READ cycle.
MASTER presents bank select [SEL_O()] to indicate where it expects data.
MASTER asserts [CYC_O] and [TGC_O()] to indicate the start of the cycle.
MASTER asserts [STB_O] to indicate the start of the phase.
SETUP, EDGE 1: SLAVE decodes inputs, and responding SLAVE asserts [ACK_I].
SLAVE presents valid data on [DAT_I()] and [TGD_I()].
SLAVE asserts [ACK_I] in response to [STB_O] to indicate valid data.
MASTER monitors [ACK_I], and prepares to latch data on [DAT_I()] and [TGD_I()].
Note: SLAVE may insert wait states (-WSS-) before asserting [ACK_I], thereby allowing it to throttle the cycle speed. Any number of wait states may be added.
CLOCK EDGE 1: MASTER latches data on [DAT_I()] and [TGD_I()].
MASTER negates [STB_O] and [CYC_O] to indicate the end of the cycle.
SLAVE negates [ACK_I] in response to negated [STB_O].
图表 6 Avalon simple Signal
四、基于Wishbone总线的自定义外设开发
Wishbone总线IP核的开发的过程中,除去具体逻辑不谈最重要的就是掌握Wishbone总线的时序,使处理器(本设计中是aemb)发送的数据或者信号能够正常与IP核交互。
本文中参考友晶VGA_binary的实例,创维特S3C2410开发板中一个LCD显示汉字的例子,以及一个清华培训的例子,做出了这样一个符合Wishbone总线规范的外设,它主要实现的功能有:
Ø Aemb处理器可以控制VGA接口显示器上任何一点像素的亮灭;
Ø Aemb处理器可以读出VGA缓存中任意一点像素的值;
Ø Aemb处理器可以控制VGA接口显示器上像素亮灭时的颜色;
Ø Aemb处理器可以在VGA接口显示器上任意一点开始显示一串ascii码字符。
Ø Aemb处理器可以由串口控制具体的在VGA显示器上的相关操作(类似于一个简单的shell)。
4.1硬件部分的设计
图表 7 Vga Binary Core
其中的RAM是用Altera的MegaWizard Plug-In Manager工具生成,这就注定了,它不可能足够大以至能存一帧全彩色图像(640 * 480 * 24 bit),但存一幅二值图像(640 * 480 = 307200 bit)是可以的。而二值图像的前景色和背景色可以通过一个Look up Taple来确定,其实别看它只存两个像素的值,其原理是和256色索引色图像表示方式,是一样的。本设计中把光标给禁掉了,因为并没有引入鼠标。
VGA Binary的低位的0x4B000的空间是留给VGA的帧缓存的地址空间,aemb可以操作这部分空间以实现VGA显示器上的像素的亮灭;从0x4B000到0x0x4B00B的这部分空间是控制寄存器的地址空间,aemb可以通过对这部分寄存器的控制实现对现实效果的控制。
在这部分的设计过程中,遇到的主要问题是,地址的对齐方式。在Avalon总线中有Native和Dynamic两种地址对齐方式。Native方式会把Avalon主端口的地址直接传送给Avalon从端口不做调整,而Dynamic方式,Avalon总线则会根据数据宽度自动调整。在Wishbone总线中的地址对齐方式属于后者(或者也可以配置,只不过我不知道)。
这就需要程序在相应的地址空间写数据时必须是往4的倍数的地址中写,vga对应的地址空间才有可能收到。如在aemb的程序中相应的操作都应定义为如下形式
#define Vga_Pixel_On_Color_R(base,value) (REG32(base + ((OSD_MEM_ADDR+6 ) << 2 )) = value)
而在vga核的地址的处理中则需把低两位忽略掉
.iWR_ADDR(wb_adr_i[20:2]),
其它Wishbone总线信号的处理,严格参照图表 5 SINGLE READ/WRITE中的时序进行操作即可。
4.2 软件部分的设计
实现最基本的打点的操作
#define Vga_Set_Pixel(base,x,y) (REG32(base + ((x*VGA_WIDTH+y ) << 2)) = 1)
#define Vga_Clr_Pixel(base,x,y) (REG32(base + ((x*VGA_WIDTH+y ) << 2)) = 0)
其它的所有的对VGA的高级一点的操作都是基于这两个宏。
实现对控制寄存器的操作
#define Vga_Write_Ctrl(base,value) IOWR(base, OSD_MEM_ADDR , value)
#define Vga_Cursor_X(base,value) IOWR(base, OSD_MEM_ADDR+1 , value)
#define Vga_Cursor_Y(base,value) IOWR(base, OSD_MEM_ADDR+2 , value)
#define Vga_Cursor_Color_R(base,value) IOWR(base, OSD_MEM_ADDR+3 , value)
#define Vga_Cursor_Color_G(base,value) IOWR(base, OSD_MEM_ADDR+4 , value)
#define Vga_Cursor_Color_B(base,value) IOWR(base, OSD_MEM_ADDR+5 , value)
#define Vga_Pixel_On_Color_R(base,value) IOWR(base, OSD_MEM_ADDR+6 , value)
#define Vga_Pixel_On_Color_G(base,value) IOWR(base, OSD_MEM_ADDR+7 , value)
#define Vga_Pixel_On_Color_B(base,value) IOWR(base, OSD_MEM_ADDR+8 , value)
#define Vga_Pixel_Off_Color_R(base,value) IOWR(base, OSD_MEM_ADDR+9 , value)
#define Vga_Pixel_Off_Color_G(base,value) IOWR(base, OSD_MEM_ADDR+10 , value)
#define Vga_Pixel_Off_Color_B(base,value) IOWR(base, OSD_MEM_ADDR+11 , value)
本设计中使用了创维特S3C2410开发板demo中的字库。Ascii码的字库在onchip-memory中是没问题的,但汉字库就不可能了,得存在片外的存储器中(ssram,sdram),这就需要用到bootloader了。
显示ASCII码字符就是,在库里找一个16*8的一个矩阵,根据它的值打点。具体的实现方法可参考博文(原创)用友晶的VGA控制器TERASIC_Binary_VGA_Controller显示汉字和ASCII码字符。
再有一部分就是写个与PC机上中端的交互程序了。主要是通过uart.c程序中的基本的打印函数实现,最基本的其实就这一点代码而已。
while(1){
uart_putc('$');
cnt = 0;
while(cnt<BUFSIZE-1){
msg[cnt] = uart_getc();
if((int)msg[cnt]!=13){
uart_putc(msg[cnt]);
}
//printf("%c",msg[cnt]);
if((int)msg[cnt]==13){
uart_print_str("\n");
break;
}
cnt++;
}
msg[cnt]='\0';
if(('c'==msg[0])&&('l'==msg[1])&&('r'==msg[2])){
Clear_Screen();
uart_print_str("clear screen\n");
}else if((msg[3]==',') && (msg[7] == ',')){
x_c[0]=msg[0];
x_c[1]=msg[1];
x_c[2]=msg[2];
y_c[0]=msg[4];
y_c[1]=msg[5];
y_c[2]=msg[6];
x = atoi(&x_c[0]);
y = atoi(&y_c[4]);
uart_print_str(&msg[8]);
uart_print_str("\n");
Glib_disp_ascii16x8(x,y,&msg[8]);
}else{
uart_print_str("Invalid command!!!\n");
}
}
它实现了两条控制命令,一条是clr清屏;另一条是在任意一点打印字符串。格式是
X坐标(3位),Y坐标(3位),AscII码字符串。在程序中调用了atoi函数把ascii码字符串转换成整型数坐标。
4.3 演示效果
现在演示一下我的这个小demo。
题外的工作:做一个初始化屏幕的一幅图像(640*480)。
图表 8 vga initial figure
写个C程序把图像处理成二值的图像,并写成altera ram支持的.mif的文件格式,.hex的太复杂了不好写。初始化VGA的帧缓存器。
将工程生成的sof文件下载到DE2-70的板子上
在hyperterminal上显示: start uart communication.并且换行显示”$”,等待输入命令。
在vga屏幕上显示:
图表 9 initial figure on screen
输入clr,(设计了回显的功能)
在hyperiterminal上显示:clear screen,换行显示”$”。
在vga屏幕上显示:
图表 10 clear screen
输入命令”240,160,Hello, My dear friend!!!”,(格式一定要正确)
在hyperterminal上显示:Hello My dear friend!!!,换行显示”$”。
在vga显示器上显示:
图表 11 print ascii string
输入clr,清屏,发现一个逻辑上的失误,输入clre也会执行清屏操作,是因为在程序中只判断了字符串的前3个字符。输入其它的字符串,会显示”Invalid command!!!”.
超级终端上的显示效果:
图表 12 hyperterminal display
五、总结
关于aemb sopc这个工作的一些想法:
现在真体会到了用不同的软核构建sopc所学到的东西aemb>or1200>niosII,
可以加一个Exit命令,让aemb进入到了控制gpio的循环,一旦用程序同时控制多个外设,简单程序就会捉襟见肘,这时候操作系统就该出场了。
下一步的计划,继续添加各个模块,实现bootoader,移植ucOS,之后设计一些多进程的程序。
尽量使这个系统完善,在google code上共享源码。
参考文献:
《开源软核处理器OpenRisc的SOPC设计》
《wbspec_b3.pdf》
《mnl_avalon_spec.pdf》