之前我发过一个视频,演示的是蜂鸟E203在windows系统上以全图形界面软件开发与逻辑
仿真的流程,使用helloworld例程在vivado上仿真。
全图形界面ncleistudi+vivado联合仿真视频
当时这个视频发出来,我还是对一个东西抱有疑问:
为什么c语言中的printf可以把信息输出到逻辑仿真的终端?是如何实现?
经过一段时间的研究,以及
论坛上大佬的指点,终于搞懂了整套流程,现在我在这里分享给大家。
实现原理
在仿真的终端打印信息,是需要软件部分和RTL设计相互配合的。
软件层面
c语言里,printf函数可以说是人尽皆知。在pc端,由于操作系统提供了标准输出界面,因此pirntf可以直接输出信息。而在嵌入式领域,想要实现printf需要做一点工作。
以蜂鸟e203为例,它没有显示器,没有标准终端,想要实现printf最常见的方式是通过串口打印信息。但是,怎么让编译器知道知道printf应该用串口呢?
(1) 初始化
如果想要使用串口,那么首先需要对串口进行初始化,这一步在系统上电初始化的过程中完成了。参考代码:
./hbird_sdk/SoC/hbirdv2/Common/Source/system_hbirdv2.c
其中467-469行代码为:
#if ! defined(SIMULA
tiON_SPIKE) && ! defined(SIMULATION_XLSPIKE) gpio_iof_config(GPIOA, IOF_UART_MASK); uart_init(SOC_DEBUG_UART, 115200);SOC_DEBUG_UART是一个宏定义,表示UART0
这两行程序的功能显而易见,它配置了GPIO功能复用,对UART0进行了初始化。有了这两句,程序中就能使用UART0了
(2) printf重定向
./hbird_sdk/SoC/hbirdv2/Common/Source/Stubs/write.c
里面只有一个函数:
__WEAK ssize_t _write(int fd, const void* ptr, size_t len){ if (!isatty(fd)) { return -1; } const uint8_t *writebuf = (const uint8_t *)ptr; for (size_t i = 0; i < len; i++) { if (writebuf
== 'n') { uart_write(SOC_DEBUG_UART, 'r'); } uart_write(SOC_DEBUG_UART, writebuf); } return len;}从_write()函数体可以看出,它的作用是通过UART0发送数据。_write这个函数名是有特殊意义的,它会将printf的字符输出重定向到这里,通过这个函数传递参数、输出数据。
__WEAK表示这个函数是弱定义。如果我们想通过其他方式printf,例如spi,可以在程序中再定义一个_write()函数,里面写入想要的功能,而write.c中的函数会被忽略。
至此,printf的信息将由UART0输出
RTL层面
在RTL仿真过程中,想要把信息打印到终端,就需要调用$display、$fwrite等函数,蜂鸟e203能把串口信息打印到仿真器的终端就是用$fwrite实现的。
参考uart的RTL代码:
rtle203peripsapb_uartuart_tx.v
最后几行186-189:
always @(posedge clk_i or negedge rstn_i) begin if ((tx_valid_i & tx_ready_o) & rstn_i) $fwrite(32'h80000002, "%c", tx_data_i);end它在uart发送信息的同时,调用了$fwrite()函数。也就是说,串口输出什么,仿真器终端就打印什么。