Verilog-I2S协议 (地平线2019)

协议介绍

代码

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

从综合的角度看,第二种实现方式更具有硬件思维

posted @ 2020-04-28 22:01  笑着刻印在那一张泛黄  阅读(1866)  评论(1编辑  收藏  举报