【程序】Altera FPGA NIOS实现Scatter-Gather DMA(SGDMA)收发回环测试,描述符和缓冲区全部放在同一块SDRAM里面

Quartus II 13.0工程下载地址:https://pan.baidu.com/s/1qjkqhd7-1IKNWZUaiZVW7Q(提取码:64wu)

【开发板】
开发板型号:小梅哥AC620
FPGA型号:EP4CE10F17C8N
SDRAM型号:W9812G6KH-6(容量为16MB)

【Qsys连线】

提示:sysid_qsys_0、uart_0、sgdma_0和sgdma_1不包含程序代码,不用连接到nios2的instruction_master上。 

【注意事项】

本人在调试这个例程的时候,遇到了以下两个卡了很久的问题。
问题一:为什么SGDMA发不出来数据?用SignalTap抓取信号,发现输出信号一直为低电平,startofpacket和endofpacket信号也没有产生,且alt_avalon_sgdma_do_sync_transfer函数的返回值为0x08不是0x0c。
回答:
返回值为0x08,即CHAIN_COMPLETED=1,DESCRIPTOR_COMPLETED=0,表示SGDMA读取发送描述符失败了,直接结束了传输。
返回值为0x0c,即CHAIN_COMPLETED=1,DESCRIPTOR_COMPLETED=1,才是正常的情况,发送成功。

这是因为Qsys里面SGDMA描述符的连线连错了,错把descriptor_read、descriptor_write和m_read的连线连到了csr上面,使SGDMA只能访问DMA本身的寄存器,访问不了onchip memory或SDRAM。
正确的连法是,描述符在哪个存储器上,descriptor_read和descriptor_write就应该连到哪里。描述符在SDRAM里面,就应该连到SDRAM的s1上面。
m_read也是如此,缓冲区在哪里就连哪里。

问题二:SGDMA描述符和接收缓冲区都位于同一块SDRAM里面。为什么SGDMA接收数据明明接收成功了,但是数据内容全零?或者部分接收正确,其余部分为零,丢字节非常厉害!是不是因为SDRAM要时不时刷新一下,SGDMA反应不过来造成数据丢失?
甚至在接收之前,用memset将接收缓冲区全部填充成0xcd,接收完成后,接收缓冲区的内容还是0xcd!

回答:其实我开始也是这么认为的,但是后来发现把描述符和接收缓冲区放到同一个onchip memory上,或者把描述符放一个onchip memory,接收缓冲区放另一个onchip memory,分开放,也存在同样的问题。你说SDRAM有可能读写慢,onchip memory总不会吧。
最后才发现实际上这个问题就是NIOS的data cache惹的祸,把data cache关了,问题就解决了。描述符和接收缓冲区是可以在同一块SDRAM上的,不会影响接收。

但是关闭data cache会使代码运行速度下降好几倍。如果不想关data cache的话,可以使用alt_dcache_flush函数清空一下dcache,也能解决问题。
接收前,用memset清空接收缓冲区后,必须要调用alt_dcache_flush清空data cache,然后才能开始DMA接收。因为memset实际上是写的是data cache,调用alt_dcache_flush函数迫使NIOS将data cache里面的数据回写到SDRAM里面。
发送前,在发送缓冲区里面填写好要发送的内容后,必须要调用alt_dcache_flush清空data cache,才能开始DMA发送。否则要发送的数据一直驻留在data cache里面,SDRAM里面真实内容为空,DMA发送的就是空数据!
接收完成后,必须要调用alt_dcache_flush清空data cache,才能去读数据,否则读到的是data cache里面的缓存,并不是SDRAM里面的真实数据。

之前出错的原因分析:
(1)接收前用memset将接收缓冲区填充为0xcd,实际是将data cache填充成了0xcd,并没有操作SDRAM。
(2)DMA开始接收,将收到的数据写入SDRAM。
(3)接收完成后调用dump_data打印缓冲区的内容,这个时候NIOS才把data cache里面的0xcd回写到SDRAM里面,正好也将DMA收到的数据覆盖掉了。然后开始串口打印,打印出来的就全是0xcd!

【Verilog程序】

module main(
    input clock,
    input uart_rx,
    output uart_tx,
    output sdram_clk,
    output [11:0] sdram_addr,
    output [1:0] sdram_ba,
    output sdram_cas_n,
    output sdram_cke,
    output sdram_cs_n,
    inout [15:0] sdram_dq,
    output [1:0] sdram_dqm,
    output sdram_ras_n,
    output sdram_we_n
    );
    
    /* 产生复位信号 */
    wire nrst;
    Reset reset(clock, nrst);
    
    /* PLL倍频 */
    wire altpll_c0;
    wire altpll_locked;
    altpll_0 altpll_0(
        .areset(~nrst),
        .inclk0(clock),
        .c0(altpll_c0), // 频率等于clock, 但相位相差63度, 用来给SDRAM提供时钟
        .locked(altpll_locked)
    );
    
    /* NIOS II */
    assign sdram_clk = altpll_c0;
    nios u0(
        .clk_clk(clock),
        .reset_reset_n(altpll_locked),
        .uart_0_external_connection_rxd(uart_rx),
        .uart_0_external_connection_txd(uart_tx),
        .new_sdram_controller_0_wire_addr(sdram_addr),
        .new_sdram_controller_0_wire_ba(sdram_ba),
        .new_sdram_controller_0_wire_cas_n(sdram_cas_n),
        .new_sdram_controller_0_wire_cke(sdram_cke),
        .new_sdram_controller_0_wire_cs_n(sdram_cs_n),
        .new_sdram_controller_0_wire_dq(sdram_dq),
        .new_sdram_controller_0_wire_dqm(sdram_dqm),
        .new_sdram_controller_0_wire_ras_n(sdram_ras_n),
        .new_sdram_controller_0_wire_we_n(sdram_we_n)
    );
    
endmodule


module Reset(
    input clock,
    output nrst
    );
    
    reg [3:0] counter = 4'd15;
    assign nrst = (counter == 0);
    always @(posedge clock) begin
        if (!nrst)
            counter <= counter - 1'b1;
    end
    
endmodule

【C语言程序】

/*
 * "Hello World" example.
 *
 * This example prints 'Hello from Nios II' to the STDOUT stream. It runs on
 * the Nios II 'standard', 'full_featured', 'fast', and 'low_cost' example
 * designs. It runs with or without the MicroC/OS-II RTOS and requires a STDOUT
 * device in your system's hardware.
 * The memory footprint of this hosted application is ~69 kbytes by default
 * using the standard reference design.
 *
 * For a reduced footprint version of this template, and an explanation of how
 * to reduce the memory footprint for a given application, see the
 * "small_hello_world" template.
 *
 */

#include <altera_avalon_sgdma.h>
#include <stdio.h>
#include <string.h>
#include <sys/alt_cache.h>
#include <system.h>

static alt_sgdma_descriptor sgdma_rx_desc[2]; // 接收描述符
static alt_sgdma_descriptor sgdma_tx_desc[2]; // 发送描述符
static alt_sgdma_dev *sgdma_rx;
static alt_sgdma_dev *sgdma_tx;
// 缓冲区的首地址必须要4字节对齐, 所以这里选用alt_u32类型
static alt_u32 sgdma_rx_buffer[7]; // 接收缓冲区
static alt_u32 sgdma_tx_buffer[7]; // 发送缓冲区

static void delay(void)
{
	volatile int i;

	for (i = 0; i < 2000000; i++);
}

static void dump_data(const void *data, int len)
{
	const alt_u8 *p = data;

	while (len--)
		printf("%02X", *p++);
	printf("\r\n");
}

int main()
{
	alt_u8 *ptx = (alt_u8 *)sgdma_tx_buffer;
	alt_u8 status;
	alt_u8 j = 0;
	int i;

	printf("Hello from Nios II!\r\n");

	printf("sgdma_rx_desc=%p, sgdma_tx_desc=%p\r\n", sgdma_rx_desc, sgdma_tx_desc);
	printf("sgdma_rx_buffer=%p, sgdma_tx_buffer=%p\r\n", sgdma_rx_buffer, sgdma_tx_buffer);

	sgdma_tx = alt_avalon_sgdma_open(SGDMA_0_NAME);
	sgdma_rx = alt_avalon_sgdma_open(SGDMA_1_NAME);

	while (1)
	{
		// 清空接收缓冲区
		memset(sgdma_rx_buffer, 0, sizeof(sgdma_rx_buffer));
		alt_dcache_flush(sgdma_rx_buffer, sizeof(sgdma_rx_buffer));

		// 开始接收
		// sgdma_rx_desc[1]是结束描述符, 其作用类似于字符数组里面的'\0'
		alt_avalon_sgdma_construct_stream_to_mem_desc(&sgdma_rx_desc[0], &sgdma_rx_desc[1], sgdma_rx_buffer, sizeof(sgdma_rx_buffer), 0);
		alt_avalon_sgdma_do_async_transfer(sgdma_rx, &sgdma_rx_desc[0]);

		// 设置要发送的内容
		for (i = 0; i < sizeof(sgdma_tx_buffer); i++)
		{
			ptx[i] = j;
			j++;
		}
		alt_dcache_flush(sgdma_tx_buffer, sizeof(sgdma_tx_buffer));

		// 开始发送
		alt_avalon_sgdma_construct_mem_to_stream_desc(&sgdma_tx_desc[0], &sgdma_tx_desc[1], sgdma_tx_buffer, sizeof(sgdma_tx_buffer), 0, 1, 1, 0);
		status = alt_avalon_sgdma_do_sync_transfer(sgdma_tx, &sgdma_tx_desc[0]);
		if (status == 0x0c)
			printf("SGDMA Tx complete!\r\n");
		else
		{
			printf("SGDMA Tx failed! status=%u\r\n", status);
			if (status == 0x08)
				printf("SGDMA could not access Tx descriptor!\r\n");
		}

		// 等待接收完毕
		while (alt_avalon_sgdma_check_descriptor_status(&sgdma_rx_desc[0]) != 0);
		printf("SGDMA Rx complete!\r\n");

		// 显示接收到的数据内容
		alt_dcache_flush(sgdma_rx_buffer, sizeof(sgdma_rx_buffer));
		dump_data(sgdma_rx_buffer, sizeof(sgdma_rx_buffer));

		// 判断收到的内容是否正确
		if (memcmp(sgdma_rx_buffer, sgdma_tx_buffer, sizeof(sgdma_rx_buffer)) != 0)
			printf("SGDMA Rx content incorrect!\r\n"); // 不正确

		delay();
	}
}

【程序运行结果】

Hello from Nios II!
sgdma_rx_desc=0x200eb60, sgdma_tx_desc=0x200eba0
sgdma_rx_buffer=0x200ebe0, sgdma_tx_buffer=0x200ebfc
SGDMA Tx complete!
SGDMA Rx complete!
000102030405060708090A0B0C0D0E0F101112131415161718191A1B
SGDMA Tx complete!
SGDMA Rx complete!
1C1D1E1F202122232425262728292A2B2C2D2E2F3031323334353637
SGDMA Tx complete!
SGDMA Rx complete!
38393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F50515253
SGDMA Tx complete!
SGDMA Rx complete!
5455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F
SGDMA Tx complete!
SGDMA Rx complete!
707172737475767778797A7B7C7D7E7F808182838485868788898A8B
SGDMA Tx complete!
SGDMA Rx complete!
8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7
SGDMA Tx complete!
SGDMA Rx complete!
A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3
SGDMA Tx complete!
SGDMA Rx complete!
C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDF
SGDMA Tx complete!
SGDMA Rx complete!
E0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFB
SGDMA Tx complete!
SGDMA Rx complete!
FCFDFEFF000102030405060708090A0B0C0D0E0F1011121314151617
SGDMA Tx complete!
SGDMA Rx complete!
18191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233
SGDMA Tx complete!
SGDMA Rx complete!
3435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F
SGDMA Tx complete!
SGDMA Rx complete!
505152535455565758595A5B5C5D5E5F606162636465666768696A6B
SGDMA Tx complete!
SGDMA Rx complete!
6C6D6E6F707172737475767778797A7B7C7D7E7F8081828384858687

 

 

接下来我们说一下SGDMA里面的error信号。使能了error信号之后,若SGDMA接收出错(如以太网数据包CRC错误),alt_avalon_sgdma_check_descriptor_status返回-EIO,这个时候代码如果没有正确清除标志位,则状态寄存器里面的Bit 4 BUSY位将一直为1,无法清除,alt_avalon_sgdma_do_async_transfer就无法启动新的DMA传输。
正确的处理方法是,先清除状态寄存器的Bit 0 ERROR位,再调用alt_avalon_sgdma_stop函数清除控制寄存器的Bit 5 RUN位,这样就能成功清除BUSY位。请看下面的代码:

#include <altera_avalon_sgdma.h>
#include <altera_avalon_sgdma_regs.h>
#include <stdio.h>
#include <string.h>
#include <sys/alt_cache.h>

static alt_sgdma_descriptor dma_rx_desc[2];
static alt_sgdma_dev *dma_rx;
static alt_u32 dma_rx_buf[7];

static void dump_data(const void *data, int len)
{
	const alt_u8 *p = data;

	while (len--)
		printf("%02X", *p++);
	printf("\r\n");
}

int main()
{
	int ret, status;

	printf("Hello from Nios II!\r\n");
	printf("dma_rx_desc=%p %p\r\n", &dma_rx_desc[0], &dma_rx_desc[1]);
	printf("dma_rx_buf=%p\r\n", dma_rx_buf);

	IOWR_ALTERA_AVALON_SGDMA_CONTROL(SGDMA_0_BASE, ALTERA_AVALON_SGDMA_CONTROL_SOFTWARERESET_MSK);
	IOWR_ALTERA_AVALON_SGDMA_CONTROL(SGDMA_0_BASE, 0);

	dma_rx = alt_avalon_sgdma_open(SGDMA_0_NAME);
	while (1)
	{
		memset(dma_rx_buf, 0, sizeof(dma_rx_buf));
		alt_dcache_flush(dma_rx_buf, sizeof(dma_rx_buf));

		alt_avalon_sgdma_construct_stream_to_mem_desc(&dma_rx_desc[0], &dma_rx_desc[1], dma_rx_buf, 0, 0);
		ret = alt_avalon_sgdma_do_async_transfer(dma_rx, &dma_rx_desc[0]);
		if (ret != 0)
		{
			printf("Failed to start transfer!\r\n");
			return -1;
		}
		do
		{
			status = alt_avalon_sgdma_check_descriptor_status(&dma_rx_desc[0]);
			if (status == -EIO)
			{
				// 出现错误
				printf("control=0x%02x, status=0x%02x\r\n", IORD_ALTERA_AVALON_SGDMA_CONTROL(SGDMA_0_BASE), IORD_ALTERA_AVALON_SGDMA_STATUS(SGDMA_0_BASE));
				IOWR_ALTERA_AVALON_SGDMA_STATUS(SGDMA_0_BASE, ALTERA_AVALON_SGDMA_STATUS_ERROR_MSK);
				printf("control=0x%02x, status=0x%02x\r\n", IORD_ALTERA_AVALON_SGDMA_CONTROL(SGDMA_0_BASE), IORD_ALTERA_AVALON_SGDMA_STATUS(SGDMA_0_BASE));
				alt_avalon_sgdma_stop(dma_rx);
				printf("control=0x%02x, status=0x%02x\r\n", IORD_ALTERA_AVALON_SGDMA_CONTROL(SGDMA_0_BASE), IORD_ALTERA_AVALON_SGDMA_STATUS(SGDMA_0_BASE));
			}
		}
		while (status == -EINPROGRESS);

		alt_dcache_flush(dma_rx_buf, sizeof(dma_rx_buf));
		printf("[Recv] len=%d, status=0x%02x\r\n", dma_rx_desc[0].actual_bytes_transferred, dma_rx_desc[0].status);
		dump_data(dma_rx_buf, sizeof(dma_rx_buf));
	}
}

程序运行结果:

control=0x60, status=0x17
control=0x60, status=0x16
control=0x40, status=0x0e
[Recv] len=24, status=0xa3
415F52585347444D3743382031304631503443450000004500000000
control=0x60, status=0x17
control=0x60, status=0x16
control=0x40, status=0x0e
[Recv] len=24, status=0xa3
415F52585347444D3743382031304631503443450000004500000000
control=0x60, status=0x17
control=0x60, status=0x16
control=0x40, status=0x0e
[Recv] len=24, status=0xa3
415F52585347444D3743382031304631503443450000004500000000
control=0x60, status=0x17
control=0x60, status=0x16
control=0x40, status=0x0e
[Recv] len=24, status=0xa3
415F52585347444D3743382031304631503443450000004500000000

最开始status=0x17,即BUSY=DESCRIPTOR_COMPLETED=EOP_ENCOUNTERED=ERROR=1,但CHAIN_COMPLETED=0(描述符链表没有处理完毕),我们必须设法清除掉第4位(BUSY)。
我们先清除第0位(ERROR),status变成0x16。
然后,调用alt_avalon_sgdma_stop函数清除控制寄存器的第5位(RUN),control变成了0x40(只剩下STOP_DMA_ER=1),同时status的第4位(BUSY)也被清除掉了,CHAIN_COMPLETED变为了1,这表明描述符链表处理完毕了。

假设sgdma_0_in_error的位宽为6位,那么如果传输没有出错(即sgdma_0_in_error一直为0x00),那么dma_rx_desc[0].status=0x80。
如果传输出错了,且出错代码为0x23(如下图所示),那么dma_rx_desc[0].status=0x80 | 0x23=0xa3。


传输出错时清除BUSY位是一个难点,因为官方手册上并没有说明怎么清除,需要自己去摸索。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巨大八爪鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值