0
由于项目需要,需要由FPGA把4路由ADC芯片采集的I2S数据进行合并成1路I2S,最后输出到主控CPU芯片,FPGA在这里起到数据中转的作用。项目的整体结构如下图
(待画)
1.I2S协议
参考文档:I2S bus specification.pdf 见下面博客
参考博客:I2S接口规范时序以及其同DSP的连接
I2S音频总线学习中的一、二、四
2.实现方案
1.方案一:FIFO的思想
即采用8个FIFO,分成两组,组1的4个FIFO负责存储4路采样数据的左声道的32bit,组二的4个FIFO负责存储4路采样数据的左声道的32bit;然后使用乒乓操作进行2个组的读和写的切换,即读组1的同时对组2进行写,读组2的同时对组1进行写,读时钟的频率是写时钟的4倍,这样可以保证写完4路数据的时间和读完4路并输出成1路的数据相同。
2.方案二:RAM的思想
采用2个32X4(地址深度32,数据宽度为4,)的RAM分别存储4路采样数据的左右声道数据(32bit)。其中RAM1的地址n中存储的是4路左声道数据的第n位,正好4位,其中RAM2的地址n中存储的是4路右声道数据的第n位。写时钟是
Fbclk1=32X2X16K(bit时钟),
Flsclk1=16k(采样频率=帧时钟);读时钟Fbclk0=32X2X4X16K(主控CPU的bit时钟),
Flsclk0=4X16k(主控CPU帧时钟)。读和写RAM采用乒乓操作进行切换。其中RAM采用SDP(伪双端口RAM),这里写代码需要参考SDP的具体手册。
主要参数的时序:Fbclk0、Flsclk0、Fbclk1、Flsclk1、data0、data1、data2、data3、data4(待续)
3.硬件实现
1.FPGA最小系统
1.Micro USB 供电
2.LDO生成核心电压VCC=1.2V,所有的VCCIO=3.3V
3.JTAG下载电路,复位引脚
2.I2S输入和输出接口
1.主控CPU输入Fbclk0、Flsclk0;FPGA输出到主控时钟CPU:data0;
2.FPGA输出到ADC芯片:Fbclk1、Flsclk1、Fmclk;ADC输入到FPGA:data1、data2、data3、data4
3.I2C接口
1.FPGA留出I2C接口,用来进行通过FPGA对ADC进行配置
4.代码结构及实现
1.整体端口
module I2S_64K
(
//main controller
input i_rst,
input i_bit_clk,
input i_fs_clk,
output o_data,
//adc
output reg o_bit_clk,
output reg o_fs_clk,
output o_main_clk, //---------------------------------->now, it have not be usedd
input i_data0,
input i_data1,
input i_data2,
input i_data3,
//input i_data4,
//input i_data5
//i2c_config_done
//input i2c_config_done
);
2.生成ADC芯片的Fbclk1和Flsclk1
对输入的主控CPU的I2S时钟(Fbclk0、Flsclk0)进行4分频生成ADC的Fbclk1、Flsclk1
参见(奇偶分频、小数分频),这里计数器分频
parameter DIVIDE=4; //共4路数据,所以4分频
//**********************************************************************************************************************
//************************ o_bit_clk = i_bit_clk / DIVIDE ; o_fs_clk = i_fs_clk / DIVIDE *************************
//**********************************************************************************************************************
//1.o_bit_clk = i_bit_clk / DIVIDE
reg [1:0] cnt_bit_divide;
always@(negedge i_bit_clk or negedge i_rst) //下降沿时钟触发是为了下降沿和o_fs_clk对齐
if(!i_rst)
begin
cnt_bit_divide <= 2'd0;
o_bit_clk <= 1'b0;
end
else if(cnt_bit_divide == (DIVIDE/2-1))
begin
cnt_bit_divide <= 2'd0;
o_bit_clk <= ~o_bit_clk;
end
else
begin
cnt_bit_divide <= cnt_bit_divide + 1'b1;
o_bit_clk <= o_bit_clk;
end
//**********************************************
//2.o_fs_clk = i_fs_clk / DIVIDE
reg [1:0] cnt_fs_divide;
always@(negedge i_fs_clk or negedge i_rst)
if(!i_rst)
begin
cnt_fs_divide <= 2'd0;
o_fs_clk <= 1'b1;
end
else if(cnt_fs_divide == (DIVIDE/2-1))
begin
cnt_fs_divide <= 2'd0;
o_fs_clk <= ~o_fs_clk;
end
else
begin
cnt_fs_divide <= cnt_fs_divide + 1'b1;
o_fs_clk <= o_fs_clk;
end
3.生成ADC芯片的Fmclk
用PLL对主控时钟Fbclk0进行倍频生成Fmclk
wire clkout_o,lock_o; GW_PLL PLL_3( .clkout(clkout_o), //output clkout .lock(lock_o), //output lock .clkin(i_bit_clk) //input clkin ); assign o_main_clk = (lock_o == 1'b1) ? clkout_o : 1'b0;
4.I2S接收电路和4路并行数据写入RAM(写入时钟Fbclk1)
接收电路参考文档:I2S bus specification.pdf。这个文档的figure5、figure6和figure7有详细的RTL图,这个是我写这段代码的基础。下面的是上升沿(时钟)边沿检测,并触发了下降沿(时钟)的数据发送就是从这里参考的
1.接收电路上升沿时钟便要检测和下降沿时钟开始接收数据
// dectect o_fs_clk edge change
reg o_fs_clk1,o_fs_clk2;
always@(posedge o_bit_clk or negedge i_rst)
if(!i_rst)
begin
o_fs_clk1 <= 1'b0;
o_fs_clk2 <= 1'b0;
end
else
begin
o_fs_clk1 <= o_fs_clk;
o_fs_clk2 <= o_fs_clk1;
end
wire wsp = o_fs_clk1 ^ o_fs_clk2; // edge detect
2.接收数据写入RAM
这里需要计数器对32bit的写入进行计数(即计数最大值31)
//*******************************************
// SRAM store count register
reg [4:0] cnt_rec; //SRAM store count register
always@(negedge o_bit_clk or negedge i_rst) //下降沿时钟,wsp上升沿开始计时(see the I2S bus specification.pdf,the last two figure)
if(!i_rst)
begin
cnt_rec <= 5'd0;
end
else if(wsp) //
begin
cnt_rec <= 5'd1;
end
else
begin
cnt_rec <= cnt_rec + 1'b1;
end
//*******************************************
// SRAM write enable signal
wire cea1 = o_fs_clk;
wire cea2 = ~ o_fs_clk;
RAM数据写入并编通道号
//*******************************************
//ADC data receive ,then store the data in SRAM ,
wire [3:0] data_adc;
//assign data_adc = {i_data0,i_data1,i_data2,i_data3};
assign data_adc = ((cnt_rec >= 5'd25) | (cnt_rec <= 5'd20)) ? {i_data0,i_data1,i_data2,i_data3} :
((cea1 == 1'b1) & (cnt_rec == 5'd21)) ? (4'b0000) :
((cea1 == 1'b1) & (cnt_rec == 5'd22)) ? (4'b0001) :
((cea1 == 1'b1) & (cnt_rec == 5'd23)) ? (4'b0110) :
((cea1 == 1'b1) & (cnt_rec == 5'd24)) ? (4'b1010) :
((cea1 == 1'b0) & (cnt_rec == 5'd21)) ? (4'b0001) :
((cea1 == 1'b0) & (cnt_rec == 5'd22)) ? (4'b1110) :
((cea1 == 1'b0) & (cnt_rec == 5'd23)) ? (4'b0110) :
((cea1 == 1'b0) & (cnt_rec == 5'd24)) ? (4'b1010) : (4'b0000);
5.主控CPU读出RAM(读RAM时钟Fbclk0)
1.读时钟边沿检测
reg i_fs_clk1,i_fs_clk2;
always@(posedge i_bit_clk or negedge i_rst)
if(!i_rst)
begin
i_fs_clk1 <= 1'b0;
i_fs_clk2 <= 1'b0;
end
else
begin
i_fs_clk1 <= i_fs_clk;
i_fs_clk2 <= i_fs_clk1;
end
wire wsp3 = i_fs_clk1 ^ i_fs_clk2; //边沿检测
2.读RAM的计数器
reg [4:0] cnt_read; //计数器只是用来计数,当o_bit_clk变化时它就清零,然后依次加一
always@(negedge i_bit_clk or negedge i_rst) //the same resson as 99 line
if(!i_rst)
begin
cnt_read <= 5'd0;
end
else if(wsp3) //i_fs_clk edge detect
begin
cnt_read <= 5'd3; // attention !!! here is important, to keep same with the left-justified audio data
end
else
begin
cnt_read <= cnt_read + 1'b1;
end
3.读出的4个通道数据区分
因为RAM的bypass输出需要两个周期,并且需要左对齐输出,
reg [3:0] cnt_read_cnt; //这个累加器是为了把o_fs_clk分成四份,用来区别通道数的
always@(negedge i_bit_clk or negedge i_rst) //the same resson as 99 line
if(!i_rst)
begin
cnt_read_cnt <= 4'b0000;
end
else if((cnt_read == 5'd0) & (cnt_rec != 5'd31)) //
begin
cnt_read_cnt <= cnt_read_cnt << 1;
end
else if((cnt_read == 5'd0) & (cnt_rec == 5'd31)) //
begin
cnt_read_cnt <= 4'b0001; //attention !!! here is delay for a i_bit_clk period !
end
else
begin
cnt_read_cnt <= cnt_read_cnt;
end
4.读RAM使能
因为RAM的bypass输出需要两个周期,并且需要左对齐输出,
reg ceb11,ceb22;
always@(negedge i_bit_clk or negedge i_rst) //the same resson as 99 line
if(!i_rst)
begin
ceb11 <= 1'b0;
ceb22 <= 1'b0;
end
else if((cnt_read == 5'd30) & (cnt_rec == 5'd31) & (o_fs_clk == 1'b1) ) // here is good ,and there are no delay ,
begin
ceb11 <= 1'b1;
ceb22 <= 1'b0;
end
else if((cnt_read == 5'd30) & (cnt_rec == 5'd31) & (o_fs_clk == 1'b0) ) //
begin
ceb11 <= 1'b0;
ceb22 <= 1'b1;
end
else
begin
ceb11 <= ceb11;
ceb22 <= ceb22;
end
6.两个RAM例化
RAM1存储左声道数据,RAM2储存右声道数据
GW_SDP sram_left(
.dout(data_sram1), //output [3:0] dout read
.clka(o_bit_clk), //input clka write
.cea(cea1), //input cea write
.reseta(!i_rst), //input reseta write
.clkb(!i_bit_clk), //input clkb read
.ceb(ceb11), //input ceb read
.resetb(!i_rst), //input resetb read
.oce(1'b0), //input oce
.ada(cnt_rec), //input [4:0] ada write
.din(data_adc), //input [3:0] din write
.adb(cnt_read) //input [4:0] adb read
);
GW_SDP sram_right(
.dout(data_sram2), //output [3:0] dout read
.clka(o_bit_clk), //input clka write
.cea(cea2), //input cea write
.reseta(!i_rst), //input reseta write
.clkb(!i_bit_clk), //input clkb read
.ceb(ceb22), //input ceb read
.resetb(!i_rst), //input resetb read
.oce(1'b0), //input oce
.ada(cnt_rec), //input [4:0] ada write
.din(data_adc), //input [3:0] din write
.adb(cnt_read) //input [4:0] adb read
);
7.I2S数据输出
assign o_data = (ceb11 & (cnt_read_cnt == 4'b0001))? data_sram1[3] :
(ceb11 & (cnt_read_cnt == 4'b0010))? data_sram1[2] :
(ceb11 & (cnt_read_cnt == 4'b0100))? data_sram1[1] :
(ceb11 & (cnt_read_cnt == 4'b1000))? data_sram1[0] :
(ceb22 & (cnt_read_cnt == 4'b0001))? data_sram2[3] :
(ceb22 & (cnt_read_cnt == 4'b0010))? data_sram2[2] :
(ceb22 & (cnt_read_cnt == 4'b0100))? data_sram2[1] :
(ceb22 & (cnt_read_cnt == 4'b1000))? data_sram2[0] : 1'b0;
5.modelsim仿真
主要参数的时序:
i_bit_clk、i_fs_clk、o_bit_clk、o_fs_clk、o_data、i_data0、i_data1、i_data2、i_data3
6.总结
1.知识点
1.I2S协议:I2S bus specification.pdf
2.modelsim独立仿真的流程
3.SDP(伪双端口RAM的具体使用细节和读写的时序查看细节)
4.本代码I2S接收是normal格式,发送时I2S的左对齐模式