协议介绍
代码
master
`timescale 1ns / 1ps
module I2S_master(
input clk_in,
input [15:0] data_in,
input rstn,
input enable,
output DATA,
output WS,
output clk,
output send_over
);
assign clk = clk_in;
localparam IDLE=2'b00;
localparam LEFT=2'b01;
localparam RIGHT=2'b11;
reg [4:0] cnt;
reg [1:0] state;
reg [1:0] next_state;
reg [17:0] data_send;
always @(posedge clk or negedge rstn) begin
if(!rstn) state <= IDLE;
else if(enable) state <= next_state;
else state <= IDLE;
end
// 状态
always @(*) begin
case(state)
IDLE: next_state = LEFT;
LEFT:
begin
if(cnt == 'd17) next_state = RIGHT;
else next_state = LEFT;
end
RIGHT:
begin
if(cnt == 'd17) next_state = LEFT;
else next_state = RIGHT;
end
default: next_state = IDLE;
endcase
end
// 发送比特计数
always @(posedge clk or negedge rstn) begin
if(!rstn) cnt <= 'd0;
else if((state != IDLE) && (cnt != 'd17)) cnt <= cnt + 1'b1;
else cnt <= 'd0;
end
always @(posedge clk or negedge rstn) begin
if(!rstn) data_send <= 'd0;
else if((state == IDLE) || (cnt == 'd17)) data_send <= {data_in,2'b00}; // 装载数据
else data_send <= {data_send[16:0],1'b0}; // 在发送状态下对data_send进行左移位
end
assign WS = (state == LEFT)? 1'b1 : 1'b0;
assign DATA = data_send[17]; // 数据线为data_send寄存器的最高位
assign send_over = (cnt == 'd15)? 1'b1 : 1'b0;
endmodule
slave
`timescale 1ns / 1ps
module I2S_slave(
input rstn,
input clk,
input WS,
input DATA,
output reg[15:0] L_DATA,
output reg[15:0] R_DATA,
output recv_over
);
localparam IDLE = 2'b00;
localparam GET_LEFT = 2'b01;
localparam GET_RIGHT = 2'b11;
reg [4:0] cnt;
reg [1:0] state,next_state;
reg WS_reg;
wire WS_en;
always @(negedge clk or negedge rstn) begin
if(!rstn) WS_reg <= 1'b0;
else WS_reg <= WS;
end
assign WS_en = (~WS_reg)&WS; // 通过判断WS上升沿开始接收数据
always @(negedge clk or negedge rstn) begin
if(!rstn) state <= IDLE;
else state <= next_state;
end
// cnt计数决定状态转移
always @(*) begin
case(state)
IDLE:
begin
if(WS_en) next_state = GET_LEFT;
else next_state = IDLE;
end
GET_LEFT:
begin
if(cnt == 'd17) next_state = GET_RIGHT;
else next_state = GET_LEFT;
end
GET_RIGHT:
begin
if(cnt == 'd17) next_state = IDLE;
else next_state = GET_RIGHT;
end
default: next_state = IDLE;
endcase
end
always @(negedge clk or negedge rstn) begin
if(!rstn) cnt <= 'd0;
else if(WS_en || (state!=IDLE)) begin // 注意这里从WS_en开始计数
if(cnt != 'd17)cnt <= cnt + 1'b1;
else cnt <= 'd0;
end
else cnt <= 'd0;
end
// 接收左声道数据
always @(negedge clk or negedge rstn) begin
if(!rstn) L_DATA <= 'd0;
else if(WS && (cnt < 'd16))
L_DATA <= {L_DATA[14:0],DATA}; // 左移位
else L_DATA <= L_DATA;
end
// 接收右声道数据
always @(negedge clk or negedge rstn) begin
if(!rstn) R_DATA <= 'd0;
else if(~WS && (cnt < 'd16))
R_DATA <= {R_DATA[14:0],DATA}; // 左移位
else R_DATA <= R_DATA;
end
assign recv_over = (cnt == 'd15)? 1'b1 : 1'b0;
endmodule
testbench
`timescale 1ns / 1ps
module I2S_master_tb;
// Inputs
reg clk_in;
reg [15:0] data_in;
reg rstn;
reg enable;
// Outputs
wire DATA;
wire WS;
wire clk;
wire send_over;
wire [15:0] L_DATA;
wire [15:0] R_DATA;
wire recv_over;
// Instantiate the Unit Under Test (UUT)
I2S_master u_I2S_master (
.clk_in(clk_in),
.data_in(data_in),
.rstn(rstn),
.enable(enable),
.DATA(DATA),
.WS(WS),
.clk(clk),
.send_over(send_over)
);
I2S_slave u_I2S_slave(
.rstn(rstn),
.clk(clk),
.WS(WS),
.DATA(DATA),
.L_DATA(L_DATA),
.R_DATA(R_DATA),
.recv_over(recv_over)
);
initial begin
// Initialize Inputs
clk_in = 0;
data_in = 0;
rstn = 0;
enable = 0;
// Wait 100 ns for global reset to finish
#100;
@(negedge clk);
rstn = 1;
enable = 1;
data_in = 16'b1010_0101_1010_0101;
@(negedge send_over);
data_in = 16'b0101_1010_0101_1010;
// Add stimulus here
end
always #20 clk_in = ~clk_in;
endmodule
仿真波形
总结
比较一下之前写的SPI协议,两者接收和发送数据方面有所不同,SPI是直接将计数值作为寄存器索引:
// 接收数据
always @(posedge clk or negedge rstn) begin
if(!rstn) rx_data <= 8'd0;
else begin
if(spi_state==TRANS && (~sclk)) rx_data[spi_count] <= miso;
else rx_data <= rx_data;
end
end
// 发送数据
assign mosi = (spi_state == TRANS)? tx_data[spi_count] : 1'bz; // 设为z态方便调试
而上面的I2S是采用计数加移位的方式:
//发送
always @(posedge clk or negedge rstn) begin
if(!rstn) data_send <= 'd0;
else if((state == IDLE) || (cnt == 'd17)) data_send <= {data_in,2'b00}; // 装载数据
else data_send <= {data_send[16:0],1'b0}; // 在发送状态下对data_send进行左移位
end
// 接收左声道数据
always @(negedge clk or negedge rstn) begin
if(!rstn) L_DATA <= 'd0;
else if(WS && (cnt < 'd16))
L_DATA <= {L_DATA[14:0],DATA}; // 左移位
else L_DATA <= L_DATA;
end
从综合的角度看,第二种实现方式更具有硬件思维