25. 基于以太网的音频传输

在“以太网数据环回实验”章节,我们设计并实现了以太网数据收发器,通过实验实现了PC端与板卡的数据交互;同时我们在“WM8978音频回环”章节,我们设计并验证了音频的播放功能。在本章节,我们使用已经通过验证的以太网数据收发器以及音频回环模块,实现基于以太网的板对板的音频传输。

25.1. 理论学习

本章节涉及到的音频以及以太网的相关知识在前面章节均有详细讲解,此处不再赘述。读者若有遗忘,请回顾相关章节进行学习巩固。

25.2. 实战演练

25.2.1. 实验目标

通过实验设计并实现基于以太网的音频传输。使用两块征途Pro FPGA开发板,这里我们暂且称为A板及B板。我们使用A板将WM8978音频接收的音频数据通过以太网数据收发器将音频数据发送到B板,B板通过以太网数据收发器接收并通过WM8978音频进行播放。

25.2.2. 硬件资源

硬件资源部分,读者可参阅“WM8978音频回环”和“以太网数据回环实验”章节的硬件资源小节。

25.3. 程序设计

25.3.1. 整体说明

实验目标已经明确,硬件资源也已经介绍完毕,我们开始程序设计部分的讲解。我们先来对实验工程进行一个整体说明,让读者了解整个实验工程的框架结构。实验工程整体框图,具体见图 75‑1;子功能模块简介,具体见表格 75‑1。

Enetvo002

图 75‑1 实验工程整体框图

表格 75‑1 子功能模块功能描述

模块名称

功能描述

rmii_to_mii

RMII接口转MII接口模块

eth_udp_mii

以太网数据收发器

mii_to_rmii

MII接口转RMII接口模块

audio_loopback

音频回环模块

audio_send_ctrl

音频发送控制模块

audio_rcv_ctrl

音频接收控制模块

eth_audio_transmission

顶层模块

由上述图表可知,实验工程包含7个子功能模块:RMII接口转MII接口模块(rmii_to_mii)、以太网数据收发器(eth_udp_mii)、MII接口转RMII接口模块(mii_to_rmii)、音频回环模块(audio_loopback)、音频发送控制模块(audio_send_ctrl)、音 频接收控制模块(audio_rcv_ctrl)和顶层模块(eth_audio_transmission)。

RMII接口转MII接口模块(rmii_to_mii),实现RMII接口到MII接口的转换;以太网数据收发器(eth_udp_mii),实现以太网数据收发操作;MII接口转RMII接口模块(mii_to_rmii),实现MII接口到RMII接口的转换;音频回环模块(audio_loopback),实 现音频的接收和播放;音频发送控制模块(audio_send_ctrl),控制以太网发送音频数据;音频接收控制模块(audio_rcv_ctrl),控制以太网接收音频数据;顶层模块(eth_audio_transmission),例化子功能模块,连接各自对应信号。

这一部分我们对实验工程各模块功能做了简单介绍,其中有些模块在前面章节已有所讲解,有些模块前面章节并未讲解,下面我们分模块为大家讲解说明。

25.3.1.1. RMII接口转MII接口模块

该模块与“以太网数据回环实验”章节中的RMII接口转MII接口模块是一致的,我们直接调用即可。该模块的详细说明大家可在前面“以太网数据回环实验”章节进行参阅,这里我们就不在过多叙述了。

25.3.1.2. 以太网数据收发器

该模块下还有子模块,模块框图如图 75‑2所示:

Enetvo003

图 75‑2 基于MII接口的UPD顶层模块

该模块与模块下的子模块与“以太网数据回环实验”章节中的“eth_udp_mii”模块与模块下的子模块都是一致的,我们直接调用即可。该模块的详细说明大家可在前面“以太网数据回环实验”章节进行参阅,这里我们就不在过多叙述了。

25.3.1.3. MII接口转RMII接口模块

该模块与“以太网数据回环实验”章节中的MII接口转RMII接口模块是一致的,我们直接调用即可。该模块的详细说明大家可在前面“以太网数据回环实验”章节进行参阅,这里我们就不在过多叙述了。

25.3.1.4. 音频回环模块

该模块与“WM8978音频回环实验”章节的顶层模块有些小改动,该模块下的子模块并无改变。模块框图如图 75‑3所示:

Enetvo004

图 75‑3 音频回环整体模块

该模块与“WM8978音频回环实验”章节多了一路输入信号以及三路输出信号,具体的变化我们通过代码为大家讲解,该顶层模块参考代码如代码清单 75‑1所示:

代码清单 75‑1 音频回环顶层模块参考代码(audio_loopback.v)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
module audio_loopback
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //系统复位,低电平有效
input wire audio_bclk , //WM8978输出的位时钟
input wire audio_lrc , //WM8978输出的数据左/右对齐时钟
input wire audio_adcdat, //WM8978ADC数据输出

output wire scl , //输出至wm8978的串行时钟信号scl
 output wire audio_mclk , //输出WM8978主时钟,频率12MHz
 output wire audio_dacdat, //输出DAC数据给WM8978

 inout wire sda , //输出至wm8978的串行数据信号sda

 output wire [23:0] adc_data , //一次接收的数据
 output wire rcv_done , //一次数据接收完成
 input wire [23:0] dac_data , //往WM8978发送的数据
 output wire send_done //一次数据发送完成

 );

 ////
 //\* Instantiation \//
 ////

 //------------------- clk_gen_inst ----------------------
 clk_gen clk_gen_inst
 (
 .areset (~sys_rst_n ), //异步复位,取反连接
 .inclk0 (sys_clk ), //输入时钟(50MHz)

 .c0 (audio_mclk ), //输出时钟(12MHz)
 .locked () //输出稳定时钟标志信号

 );

 //------------------- audio_rcv_inst -------------------
 audio_rcv audio_rcv_inst
 (
 .audio_bclk (audio_bclk ), //WM8978输出的位时钟
 .sys_rst_n (sys_rst_n ), //系统复位,低有效
 .audio_lrc (audio_lrc ), //WM8978输出的数据左/右对齐时钟
 .audio_adcdat (audio_adcdat), //WM8978ADC数据输出

 .adc_data (adc_data ), //一次接收的数据
 .rcv_done (rcv_done ) //一次数据接收完成

 );

 //------------------ audio_send_inst ------------------
 audio_send audio_send_inst
 (
 .audio_bclk (audio_bclk ), //WM8978输出的位时钟
 .sys_rst_n (sys_rst_n ), //系统复位,低有效
 .audio_lrc (audio_lrc ), //WM8978输出数据左/右对齐时钟
 .dac_data (dac_data ), //往WM8978发送的数据

 .audio_dacdat (audio_dacdat), //发送DAC数据给WM8978
 .send_done (send_done ) //一次数据发送完成

 );

 //----------------- wm8978_cfg_inst --------------------
 wm8978_cfg wm8978_cfg_inst
 (
 .sys_clk (sys_clk ), //系统时钟,频率50MHz
 .sys_rst_n (sys_rst_n ), //系统复位,低电平有效

 .i2c_scl (scl ), //输出至wm8978的串行时钟信号scl
 .i2c_sda (sda ) //输出至wm8978的串行数据信号sda

 );

 endmodule

相对“WM8978音频回环实验”章节工程顶层模块进行讲解:如上代码15~18行所示,我们多引出了四路信号,其中“rcv_done”与“send_done”输出信号在“WM8978音频回环实验”章节工程顶层模块中并没有引出,而这里我们需要用到这两个信号供其它模块使用,所以我们将其引出;而“adc_da ta”与“dac_data”这两路信号在“WM8978音频回环实验”章节工程顶层模块中是连接在一起的,因为在“WM8978音频回环实验”章节中FPGA接收的音频数据“adc_data”与通过FPGA传给WM8978播放的音频数据“dac_data”是一致的。而在本次实验中我们播放的音频数据是从另一块 开发板中传输过来的,而当前开发板收集的音频数据也是传给另一块开发板就行播放的,所以这里我们同样需要将这两路信号引出。

25.3.1.5. 音频发送控制模块

模块框图

该模块是将WM8978芯片采集的音频数据进行处理,然后再通过以太网模块进行发送。模块框图,具体见图 75‑4;输入输出信号简介,具体见表格 75‑2。

Enetvo005

图 75‑4 音频发送控制模块框图

表格 75‑2 输入输出端口简介

信号

位宽

类型

功能描述

sys_rst_n

1bit

input

复位信号,低有效

adc_data

24bit

input

一次接收的音频数据

eth_tx_clk

1bit

input

mii时钟,发送

read_data_req

1bit

input

读数据请求信号

audio_bclk

1bit

input

音频位时钟

rcv_done

1bit

input

一次音频数据接收完成信号

send_end

1bit

input

单包数据发送完成信号

send_en

1bit

output

开始发送信号

send_data

32bit

output

发送数据

send_data_num

16bit

output

数据包发送有效数据字节数

由图 75‑4所示,由于音频的读取时钟与以太网的发送时钟并不相同,所以这里我们调用了一个异步FIFO IP核进行跨时钟域处理以及数据缓存,下面对我们调用FIFO的配置进行说明:

首先是读写时钟,上面说到音频数据采集时钟和以太网数据传输时钟并不相同,所以我们需调用异步FIFO,使用不同的读写时钟进行数据的写入与读取。

读写数据的位宽和深度:由于我们采集的音频数据的位宽是24位的,所以这里我们将位宽设置为24位;深度设置大于我们设计的缓存容量即可,这里我们设置为512位。

同时我们勾选上异步复位信号以及同步于读写时钟的FIFO内数据个数信号:rdusedw(同步于读时钟),wrusedw(同步于写时钟)。

具体的调用步骤可参考“快速开发的法宝—IP核”章节,这里就不再过多叙述。

波形图绘制

在模块框图小节,我们对调用的异步FIFO做了讲解,接下来我们通过波形图的绘制,为读者说明模块各信号的设计与实现方法,以及模块的功能实现。模块整体波形图,具体见图 75‑5所示。

Enetvo006

图 75‑5 音频发送控制模块波形图

如上图所示:

audio_bclk:音频位时钟,因为我们写入的是音频数据,所以我们需使用该时钟作为FIFO的写入时钟。

rcv_done:一次音频数据接收完成信号。该信号由音频回环模块传来,当该信号为高时,说明一个音频数据接收完成,此时我们就可将该完成的音频数据存入FIFO中,所以我们将该信号作为写FIFO使能信号。

adc_data:一次接受的音频数据。由音频回环模块传来,作为写入FIFO的音频数据。

eth_tx_clk:以太网发送时钟。该时钟作为FIFO的读时钟,同时使用该时钟对以太网开始发送数据信号(send_en)进行赋值。

read_data_req:读数据请求信号。该信号由以太网模块传来,作为FIFO的读使能信号,读取FIFO内存储的音频数据。

send_end:单包数据发送完成信号。该信号由以太网模块传来,其用法在下个信号进行说明。

eth_send_flag:发送状态标志信号。由于以太网的传输速度要大于音频的传输速率,同时以太网是按数据包进行发送的,所以这里我们先缓存到一包数据再进行发送,而数据包发送有效字节数是由我们自己控制的,这里我们设置一次发送256个数据,所以这里当FIFO内缓存数据大于256个时再开始发送,这样不管以 太网的传输速度快于音频传输数据多少,都能保证一次256个数据的发送量。所以这里当FIFO内数据量(data_cnt)大于256个时,拉高eth_send_flag信号,同时当send_end(单包数据发送完成信号)为高时,拉低eth_send_flag信号。

data_cnt:FIFO内数据量。因为该信号是使用以太网发送时钟进行采样的,所以我们引出FIFO内数据个数信号:rdusedw(同步于读时钟)。

rd_data:FIFO读出信号。FIFO中读出的数据,作为以太网的发送数据,而以太网的发送数据位宽为32位,而我们读取的音频数据只有24位,这个问题该如何解决呢?

send_data:发送数据。上面说到发送的数据与读取的数据位宽不一致,所以这里我们将读取的音频数据作为发送数据的低24,高八位补零,在接收端我们将数据进行处理即可。

send_en:开始发送信号。通过以太网收发器模块我们知道,当拉高一个时钟的开始发送信号,那么以太网收发器就会开始发送数据。所以这里当eth_send_flag为低(不在发送状态)以及FIFO内数据大于255时拉高即可。

send_data_num:数据包发送有效数据字节数。我们发送的数据为32位,即4个字节;而我们前面说到我们一包数据发送256个数据,这里我们将256定义为参数“DATA_CNT_NUM”方便更改,所字节数即为:{ DATA_CNT_NUM ,2’b0}。

代码编写

参照绘制波形图,编写模块参考代码。模块参考代码具体见代码清单 75‑2。

代码清单 75‑2 音频发送控制模块参考代码(audio_send_ctrl.v)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
module audio_send_ctrl
(
input wire audio_bclk , //音频位时钟
input wire sys_rst_n , //复位信号
input wire rcv_done , //一次音频数据接受完成
input wire [23:0] adc_data , //一次接受的音频数据
input wire eth_tx_clk , //mii时钟,发送
input wire read_data_req , //读数据请求信号
input wire send_end , //单包数据发送完成信号

 output reg send_en , //开始发送信号
 output wire [15:0] send_data_num , //数据包发送有效数据字节数
 output wire [31:0] send_data //发送数据

 );

 ////
 //\* Parameter and Internal Signal \//
 ////

 //parameter define
 parameter DATA_CNT_NUM = 9'd256; //FIFO中存储个数为此值时开始发送

 //wire define
 wire [8:0] data_cnt; //FIFO中存储个数
 wire [23:0] rd_data ; //fifo读出数据

 //reg define
 reg eth_send_flag ; //发送状态标志信号

 ////
 //\* Main Code \//
 ////

 //单包数据发送有效字节数:一个发送数据为32bit(4字节)
 assign send_data_num = {DATA_CNT_NUM,2'b0};

 //音频数据为24位,以太网发送数据为32位,往高八位补零即可
 assign send_data = {8'd0,rd_data};

 //发送状态标志信号:fifo内数据大于等于256时拉高,单包数据发送完成后拉低
 always@(posedge eth_tx_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 eth_send_flag <= 1'b0;
 else if(data_cnt >= DATA_CNT_NUM)
 eth_send_flag <= 1'b1;
 else if(send_end == 1'b1)
 eth_send_flag <= 1'b0;

 //当FIFO内数据大于单包发送字节数时且不在发送状态时,拉高开始发送信号
 always@(posedge eth_tx_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 send_en <= 1'b0;
 else if(data_cnt >= DATA_CNT_NUM && eth_send_flag == 1'b0)
 send_en <= 1'b1; //拉高一个时钟发送信号
 else
 send_en <= 1'b0;

 ////
 //\* Instantiation \//
 ////

 //------------ dcfifo_512x24_inst -------------
 //例化的FIFO为:512深度24bit位宽的异步fifo
 dcfifo_512x24 dcfifo_512x24_inst1
 (
 .aclr (~sys_rst_n ), //异步复位信号
 .data (adc_data ), //写入FIFO数据
 .rdclk (eth_tx_clk ), //读FIFO时钟
 .rdreq (read_data_req ), //读FIFO使能
 .wrclk (audio_bclk ), //写FIFO时钟
 .wrreq (rcv_done ), //写FIFO使能
 .q (rd_data ), //读FIFO数据
 .rdusedw(data_cnt ), //FIFO中存储个数(读时钟采样)
 .wrusedw( ) //FIFO中存储个数(写时钟采样)

 );

 endmodule

25.3.1.6. 音频接收控制模块

模块框图

该模块是对以太网传输过来的数据进行处理,然后在通过音频进行输出播放。模块框图,具体见图 75‑6;输入输出信号简介,具体见表格 75‑3。

Enetvo007

图 75‑6 音频接收控制模块框图

表格 75‑3 输入输出端口简介

信号

位宽

类型

功能描述

sys_rst_n

1bit

input

复位信号,低有效

rec_end

1bit

input

单包数据接收完成信号

rec_data

32bit

input

接收数据

audio_bclk

1bit

input

音频位时钟

audio_send_done

1bit

input

音频接收使能信号

eth_rx_clk

1bit

input

mii时钟,接收

rec_en

1bit

input

接收数据使能信号

audio_dac_data

24bit

output

音频播放数据

由图 75‑6所示,这里我们同样使用了一个FIFO模块进行跨时钟域以及数据缓存,这里使用的FIFO IP核配置与音频发送控制模块使用的FIFO IP核配置相同,所以我们直接例化即可。

波形图绘制

接下来我们通过波形图的绘制,为读者说明模块各信号的设计与实现方法,以及模块的功能实现。模块整体波形图,具体见图 75‑7所示。

Enetvo008

图 75‑7 音频接收控制模块波形图

如上图所示:

eth_rx_clk:mii时钟,接收。因为我们需要将接收的数据存入FIFO之中,所以该时钟我们需作为FIFO的写时钟。

audio_bclk:音频位时钟。因为我们需要将以太网传输过来的音频数据进行播放,所以我们需使用该时钟作为FIFO的读时钟。

audio_send_done:音频数据接收使能信号。该信号由音频回环模块传来,当该信号为高时说明一个音频数据接收完成,即可以开始接收下一个音频数据了。

rec_en:接收数据使能信号。该信号由以太网模块传来,作为FIFO的写数据使能信号。

rec_end:单包数据接收完成信号。该信号由以太网模块传来,用于控制以太网数据的读取播放。

rec_data:以太网接收数据。该32位数据的低24位即是我们传输的音频数据。

wr_data:写FIFO数据。我们将以太网接收数据的低24位写入FIFO中,即将音频数据存入FIFO中。

rcv_flag:数据包接收完成标志信号。当我们将以太网接收的标志信号存入FIFO之中后,那我们该什么时候读取FIFO中的数据呢?所以这里我们使用了rcv_flag信号,当一包数据完成(rec_end信号为高时)之后,我们拉高该信号,表示可以开始接收。

rd_en:读FIFO使能信号。当rcv_flag信号为高时说明可以读取FIFO内的数据了,此时我们将audio_send_done信号的值赋给rd_en信号作为读FIFO使能信号。

audio_dac_data:音频播放数据。将读FIFO数据发送给音频回环模块播放即可。

代码编写

参照绘制波形图,编写模块参考代码。模块参考代码具体见代码清单 75‑3。

代码清单 75‑3 音频接收控制模块参考代码(audio_rcv_ctrl.v)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
module audio_rcv_ctrl
(
input wire eth_rx_clk , //mii时钟,接收
input wire sys_rst_n , //复位信号,低电平有效
input wire audio_bclk , //音频位时钟
input wire audio_send_done , //wm8978音频接收使能信号
input wire rec_end , //单包数据接收完成信号
input wire rec_en , //接收数据使能信号
input wire [31:0] rec_data , //接收数据

 output wire [23:0] audio_dac_data //往wm8978发送的音频播放数据

 );

 ////
 //\* Parameter and Internal Signal \//
 ////

 //wire define
 wire [23:0] wr_data ; //写fifo数据
 wire rd_en ; //读fifo使能

 //reg define
 reg rcv_flag ; //数据包接收完成标志信号

 ////
 //\* Main Code \//
 ////

 //接收数据的前24位即为音频传输数据
 assign wr_data = rec_data[23:0];

 //数据包接收完成之后,将音频接收使能信号作为读fifo使能
 assign rd_en = audio_send_done & rcv_flag;

 //当数据包接收完成之后拉高接收标志信号
 always@(posedge eth_rx_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 rcv_flag <= 1'b0;
 else if(rec_end == 1'b1)
 rcv_flag <= 1'b1;

 //------------ dcfifo_512x24_inst -------------
 //例化的FIFO为:512深度24bit位宽的异步fifo
 dcfifo_512x24 dcfifo_512x24_inst2
 (
 .aclr (~sys_rst_n ), //异步复位信号
 .data (wr_data ), //写入FIFO数据
 .rdclk (audio_bclk ), //读FIFO时钟
 .rdreq (rd_en ), //读FIFO使能
 .wrclk (eth_rx_clk ), //写FIFO时钟
 .wrreq (rec_en ), //写FIFO使能
 .q (audio_dac_data ), //读FIFO数据
 .rdusedw( ), //FIFO中存储个数(读时钟采样)
 .wrusedw( ) //FIFO中存储个数(写时钟采样)

 );

 endmodule

25.3.1.7. 顶层模块

模块框图

顶层模块作用较为简单,内部实例化各子功能模块,连接子模块对应信号;引出外部输入输出信号。在此我们只对顶层模块的整体框图和参考代码做一下介绍,无需绘制波形图。模块框图和输入输出信号简介,具体见图 75‑8、表格 75‑4。

Enetvo009

图 75‑8 顶层模块框图

表格 75‑4 输入输出信号功能描述

信号

位宽

类型

功能描述

sys_clk

1Bit

Input

板卡晶振输入时钟,50MHz

sys_rst_n

1Bit

Input

系统复位,低电平有效

audio_lrc

1Bit

Input

音频对齐时钟

audio_bclk

1Bit

Input

音频位时钟

audio_adcdat

1Bit

Input

输入ADC音频数据

eth_clk

1Bit

Input

PHY芯片时钟信号

eth_rxdv_r

1Bit

Input

PHY芯片输入数据有效信号

eth_rx_data_r

2Bit

Input

PHY芯片输入数据

eth_tx_en_r

1Bit

Output

PHY芯片输出数据有效信号

eth_tx_data_r

2Bit

Output

PHY芯片输出数据

audio_dacdat

1Bit

Output

输出DAC音频数据

scl

1Bit

Output

I2C串行时钟信号

sda

1Bit

Inout

I2C数据信号

audio_mclk

1Bit

Output

输出音频主时钟,12MHz

eth_rst_n

1Bit

Output

PHY芯片复位信号,低电平有效

代码编写

顶层模块参考代码,具体见代码清单 75‑4。

代码清单 75‑4 顶层模块参考代码(eth_audio_transmission.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
module eth_audio_transmission
(
input wire sys_clk , //系统时钟
input wire sys_rst_n , //系统复位,低电平有效
//Ethernet
input wire eth_clk , //PHY芯片时钟信号
input wire eth_rxdv_r , //PHY芯片输入数据有效信号
input wire [1:0] eth_rx_data_r, //PHY芯片输入数据
output wire eth_tx_en_r , //PHY芯片输出数据有效信号
output wire [1:0] eth_tx_data_r, //PHY芯片输出数据
output wire eth_rst_n , //PHY芯片复位信号,低电平有效
//audio
input wire audio_bclk , //WM8978输出的位时钟
input wire audio_lrc , //WM8978输出的数据左/右对齐时钟
input wire audio_adcdat, //WM8978ADC数据输出
output wire audio_mclk , //输出WM8978主时钟,频率12MHz
output wire audio_dacdat, //输出DAC数据给WM8978
output wire scl , //输出至wm8978的串行时钟信号scl
inout wire sda //输出至wm8978的串行数据信号sda

);

////
//\* Parameter and Internal Signal \//
////

parameter BOARD_MAC = 48'hff_ff_ff_ff_ff_ff ; //板卡MAC地址
parameter BOARD_IP = 32'hff_ff_ff_ff ; //板卡IP地址
parameter BOARD_PORT = 16'd1234 ; //板卡端口号
parameter PC_MAC = 48'hff_ff_ff_ff_ff_ff ; //PC机MAC地址
parameter PC_IP = 32'hff_ff_ff_ff ; //PC机IP地址
parameter PC_PORT = 16'd1234 ; //PC机端口号

//wire define
wire eth_rxdv ; //输入数据有效信号(mii)
wire [3:0] eth_rx_data ; //输入数据(mii)
wire eth_tx_en ; //输出数据有效信号(mii)
wire [3:0] eth_tx_data ; //输出数据(mii)
wire eth_tx_clk ; //mii时钟,发送
wire read_data_req ; //读数据请求信号
wire send_end ; //单包数据发送完成信号
wire send_en ; //开始发送信号
wire [15:0] send_data_num ; //发送有效数据字节数
wire [31:0] send_data ; //发送数据
wire rec_end ; //单包数据接收完成信号
wire rec_en ; //接收数据使能信号
wire [31:0] rec_data ; //接收数据
wire send_done ; //音频一次数据发送完成信号
wire rcv_done ; //一次数据接受完成
wire [23:0] adc_data ; //一次接受的数据
wire [23:0] dac_data ; //往音频发送的播放数据

//reg define
reg clk_25m ; //mii时钟

////
//\* Main Code \//
////

//clk_25m:mii时钟
always@(negedge eth_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
clk_25m <= 1'b1;
else
clk_25m <= ~clk_25m;

////
//\* Instantiation \//
////

//------------- rmii_to_mii_inst -------------
rmii_to_mii rmii_to_mii_inst
(
.eth_rmii_clk (eth_clk ), //rmii时钟
.eth_mii_clk (clk_25m ), //mii时钟
.sys_rst_n (sys_rst_n ), //复位信号
.rx_dv (eth_rxdv_r ), //输入数据有效信号(rmii)
.rx_data (eth_rx_data_r ), //输入数据(rmii)

.eth_rx_dv (eth_rxdv ), //输入数据有效信号(mii)
.eth_rx_data (eth_rx_data ) //输入数据(mii)
);

//------------- mii_to_rmii_inst -------------
mii_to_rmii mii_to_rmii_inst
(
.eth_mii_clk (clk_25m ), //mii时钟
.eth_rmii_clk (eth_clk ), //rmii时钟
.sys_rst_n (sys_rst_n ), //复位信号
.tx_dv (eth_tx_en ), //输出数据有效信号(mii)
.tx_data (eth_tx_data ), //输出有效数据(mii)

.eth_tx_dv (eth_tx_en_r ), //输出数据有效信号(rmii)
.eth_tx_data (eth_tx_data_r ) //输出数据(rmii)
);

//------------- eth_udp_mii_inst -------------
eth_udp_mii
#(
 .BOARD_MAC (BOARD_MAC ), //板卡MAC地址
 .BOARD_IP (BOARD_IP ), //板卡IP地址
 .BOARD_PORT (BOARD_PORT), //板卡端口号
 .PC_MAC (PC_MAC ), //PC机MAC地址
 .PC_IP (PC_IP ), //PC机IP地址
 .PC_PORT (PC_PORT ) //PC机端口号
 )
 eth_udp_mii_inst
 (
 .eth_rx_clk (clk_25m ), //mii时钟,接收
 .sys_rst_n (sys_rst_n ), //复位信号,低电平有效
 .eth_rxdv (eth_rxdv ), //输入数据有效信号(mii)
 .eth_rx_data (eth_rx_data ), //输入数据(mii)
 .eth_tx_clk (clk_25m ), //mii时钟,发送
 .send_en (send_en ), //开始发送信号
 .send_data (send_data ), //发送数据
 .send_data_num (send_data_num ), //发送有效数据字节数

 .send_end (send_end ), //单包数据发送完成信号
 .read_data_req (read_data_req ), //读数据请求信号
 .rec_end (rec_end ), //单包数据接收完成信号
 .rec_en (rec_en ), //接收数据使能信号
 .rec_data (rec_data ), //接收数据
 .rec_data_num ( ), //接收有效数据字节数
 .eth_tx_en (eth_tx_en ), //输出数据有效信号(mii)
 .eth_tx_data (eth_tx_data ), //输出数据(mii)
 .eth_rst_n (eth_rst_n ) //复位信号,低电平有效
 );

 //------------- audio_rcv_ctrl_inst -------------
 audio_rcv_ctrl audio_rcv_ctrl_inst
 (
 .eth_rx_clk (clk_25m ), //mii时钟,接收
 .sys_rst_n (sys_rst_n ), //复位信号,低电平有效
 .audio_bclk (audio_bclk ), //音频位时钟
 .audio_send_done (send_done ), //音频一次数据发送完成信号
 .rec_end (rec_end ), //单包数据接收完成信号
 .rec_en (rec_en ), //接收数据使能信号
 .rec_data (rec_data ), //接收数据

 .audio_dac_data (dac_data ) //往音频发送的播放数据

 );

 //------------- audio_send_ctrl_inst -------------
 audio_send_ctrl audio_send_ctrl_inst
 (
 .audio_bclk (audio_bclk ), //音频位时钟
 .sys_rst_n (sys_rst_n ), //复位信号
 .rcv_done (rcv_done ), //一次数据接受完成
 .adc_data (adc_data ), //一次接受的数据
 .eth_tx_clk (clk_25m ), //mii时钟,发送
 .read_data_req (read_data_req), //读数据请求信号
 .send_end (send_end ), //单包数据发送完成信号

 .send_en (send_en ), //开始发送信号
 .send_data_num (send_data_num), //发送有效数据字节数
 .send_data (send_data ) //发送数据

 );

 //------------- audio_loopback_inst -------------
 audio_loopback audio_loopback_inst
 (
 .sys_clk (sys_clk ), //系统时钟,频率50MHz
 .sys_rst_n (sys_rst_n ), //系统复位,低电平有效
 .audio_bclk (audio_bclk ), //WM8978输出的位时钟
 .audio_lrc (audio_lrc ), //WM8978输出的数据左/右对齐时钟
 .audio_adcdat(audio_adcdat ), //WM8978ADC数据输出

 .scl (scl ), //输出至wm8978的串行时钟信号scl
 .audio_mclk (audio_mclk ), //输出WM8978主时钟,频率12MHz
 .audio_dacdat(audio_dacdat ), //输出DAC数据给WM8978

 .sda (sda ), //输出至wm8978的串行数据信号sda

 .adc_data (adc_data ), //一次接收的数据
 .rcv_done (rcv_done ), //一次数据接收完成
 .dac_data (dac_data ), //往WM8978发送的数据
 .send_done (send_done ) //一次数据发送完成

 );

 endmodule

如上代码27~32行所示,由于我们是板对板进行通信,我们把地址设为广播码即可。

25.4. 上板调试

25.4.1. 引脚约束

仿真验证通过后,准备上板验证,上板验证之前先要进行引脚约束。工程中各输入输出信号与开发板引脚对应关系如表格 75‑5所示。

表格 75‑5 引脚分配表

信号名

信号类型

对应引脚

备注

sys_clk

Input

E1

板卡晶振输入时钟,50MHz

sys_rst_n

Input

M15

系统复位,低电平有效

audio_lrc

Input

E9

音频对齐时钟

audio_bclk

Input

D12

音频位时钟

audio_adcdat

Input

C14

输入ADC音频数据

eth_clk

Input

C3

PHY芯片时钟信号

eth_rxdv_r

Input

D3

PHY芯片输入数据有效信号

eth_rx_data_r[0]

Input

A4

PHY芯片输入数据

eth_rx_data_r[1]

Input

D8

PHY芯片输入数据

eth_tx_en_r

Output

D5

PHY芯片输出数据有效信号

eth_tx_data_r[0]

Output

C8

PHY芯片输出数据

eth_tx_data_r[1]

Output

D6

PHY芯片输出数据

audio_dacdat

Output

D11

输出DAC音频数据

scl

Output

P15

I2C串行时钟信号

sda

Inout

N14

I2C数据信号

audio_mclk

Output

D14

输出音频主时钟

eth_rst_n

Output

B3

PHY芯片复位信号

下面进行管脚分配,管脚的分配方法在前面章节已有所讲解,在此就不再过多叙述,管脚的分配如下图 75‑9所示。

Enetvo010

图 75‑9 管脚分配

25.4.1.1. 结果验证

如所示,开发板连接12V直流电源、USB-Blaster下载器JTAG端口、网线以及VGA显示器,网线另一端连接路由器;PC机也使用另一条网线与路由器相连。线路正确连接后,打开开关为板卡上电。(下图需更换)

Enetvo011

图 75‑10 程序下载连线图

如图 75‑11所示,重新编译工程,使用“Programmer”分别为两块开发板下载程序。

Enetvo012

图 75‑11 程序下载图

程序都下载完成后,使用音频线将开发板与电脑或手机连接,然后我们将耳机线或喇叭插入另外一块开发板,若可从耳机或喇叭听到手机或电脑播放的音频,则说明验证通过,设计正确。

25.5. 章末总结

本章节中,我们将以太网、音频的相关知识结合起来,设计并实现了基于RMII接口UDP协议的以太网音频板对板传输的实验工程,目的是为了加深读者对以太网相关知识的理解,同时也是为了巩固前面章节音频部分的相关知识,望读者务必掌握扎实,如有遗忘,可回顾相关章节重新学习巩固。