Skip to content

Commit

Permalink
Add I2S module
Browse files Browse the repository at this point in the history
  • Loading branch information
ultraembedded committed Mar 30, 2016
1 parent b348763 commit 3613a4e
Show file tree
Hide file tree
Showing 13 changed files with 923 additions and 0 deletions.
22 changes: 22 additions & 0 deletions i2s/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
### I2S Master

Github: [http://github.com/ultraembedded/cores](https://github.com/ultraembedded/cores/tree/master/i2s)

This is a simple I2S master module written in Verilog.

The module requires clock source which is used to derive MCLK and BCLK for the I2S interface.

The audio_clk_i clock rate should be:
* 32KHz - 16.384MHz
* 44.1KHz - 22.5792MHz
* 48KHz - 24.576MHz

The frequency of clk_i must be more than 2 x audio_clk_i frequency.

The input interface expects 32-bits (2 x 16-bit audio samples) to be provided to it on 'sample_i' and held until 'sample_req_o' is pulsed (data pop request).

This allows connection to a simple FIFO for audio samples.

##### Testing

The supplied testbench requires the SystemC libraries and Icarus Verilog, both of which are available for free.
172 changes: 172 additions & 0 deletions i2s/rtl/i2s.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
//-----------------------------------------------------------------
// I2S Master
// V0.1
// Ultra-Embedded.com
// Copyright 2012
//
// Email: [email protected]
//
// License: GPL
// If you would like a version with a more permissive license for
// use in closed source commercial applications please contact me
// for details.
//-----------------------------------------------------------------
//
// This file is open source HDL; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of
// the License, or (at your option) any later version.
//
// This file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this file; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
//-----------------------------------------------------------------
module i2s
(
// Main clock (min 2x audio_clk_i)
input clk_i,
input rst_i,

// Audio clock (MCLK x 2):
// For 44.1KHz: 22.5792MHz
// For 48KHz: 24.576MHz
input audio_clk_i,
input audio_rst_i,

// I2S DAC Interface
output i2s_mclk_o,
output i2s_bclk_o,
output i2s_ws_o,
output i2s_data_o,

// Audio interface (16-bit x 2 = RL)
// (synchronous to clk_i)
input [31:0] sample_i,
output sample_req_o
);

//-----------------------------------------------------------------
// Registers
//-----------------------------------------------------------------
reg [4:0] bit_count_q;

// Registered audio input data
reg [31:0] sample_q;

// Xilinx: Place output flop in IOB
//synthesis attribute IOB of mclk_q is "TRUE"
//synthesis attribute IOB of ws_q is "TRUE"
//synthesis attribute IOB of bclk_q is "TRUE"
//synthesis attribute IOB of data_q is "TRUE"
reg mclk_q;
reg bclk_q;
reg ws_q;
reg data_q;

reg sample_req_q;
reg next_data_q;

//-----------------------------------------------------------------
// MCLK
//-----------------------------------------------------------------
reg [7:0] clock_div_q;

always @(posedge audio_clk_i or posedge audio_rst_i)
if (rst_i)
begin
mclk_q <= 1'b0;
clock_div_q <= 8'b0;
end
else
begin
mclk_q <= !mclk_q;
clock_div_q <= clock_div_q + 8'd1;
end

reg clk_en0_ms_q;
reg clk_en1_q;
reg clk_en2_q;

// Resync clk enable pulse to clk_i domain
always @(posedge clk_i or posedge rst_i)
if (rst_i)
begin
clk_en0_ms_q <= 1'b0;
clk_en1_q <= 1'b0;
clk_en2_q <= 1'b0;
end
else
begin
clk_en0_ms_q <= (clock_div_q == 8'd0);
clk_en1_q <= clk_en0_ms_q;
clk_en2_q <= clk_en1_q;
end

// BCLK is div256 of MCLK
wire bclk_en_w = !clk_en2_q && clk_en1_q;

//-----------------------------------------------------------------
// I2S Output Generator
//-----------------------------------------------------------------
always @(posedge clk_i or posedge rst_i)
begin
if (rst_i == 1'b1)
begin
sample_q <= 32'b0;
bit_count_q <= 5'd0;
data_q <= 1'b0;
ws_q <= 1'b0;
bclk_q <= 1'b0;
next_data_q <= 1'b0;
sample_req_q <= 1'b0;
end
else if (bclk_en_w)
begin
// BCLK 1->0 - Falling Edge
if (bclk_q)
begin
bclk_q <= 1'b0;

data_q <= next_data_q;
next_data_q <= sample_q[5'd31 - bit_count_q];

// Word select
ws_q <= bit_count_q[4];

// Increment bit position counter
bit_count_q <= bit_count_q + 5'd1;
end
// BCLK 0->1 - Rising Edge
else
begin
bclk_q <= 1'b1;

// Last bit in first half, buffer remainder and pop word
if (bit_count_q == 5'd0)
begin
sample_q <= {sample_i[15:0], sample_i[31:16]};
sample_req_q <= 1'b1;
end
end
end
else
sample_req_q <= 1'b0;
end

//-----------------------------------------------------------------
// I2S DAC Interface
//-----------------------------------------------------------------
assign i2s_mclk_o = mclk_q;
assign i2s_ws_o = ws_q;
assign i2s_bclk_o = bclk_q;
assign i2s_data_o = data_q;

assign sample_req_o = sample_req_q;

endmodule
66 changes: 66 additions & 0 deletions i2s/testbench/i2s_decoder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include "i2s_decoder.h"

//-----------------------------------------------------------------
// input: Handle rx data
//-----------------------------------------------------------------
void i2s_decoder::input(void)
{
do
{
wait();
}
while (rst_i.read());

bool first = true;
int bit_cnt = 0;
sc_uint <I2S_MAX_BITS> data = 0;
sc_uint <I2S_MAX_BITS> rev_data = 0;
bool ws;

while (true)
{
if (!first)
{
// Left
if (!ws)
{
sc_assert(bit_cnt < m_bits);

rev_data[bit_cnt] = i2s_data_i.read();

if (++bit_cnt == m_bits)
{
for (int i=0;i<m_bits;i++)
data[m_bits-i-1] = rev_data[i];

m_rx_fifo.write(data);
data = 0;
}
}
// Right
else
{
sc_assert(bit_cnt >= m_bits);
sc_assert(bit_cnt < (m_bits*2));

rev_data[bit_cnt-m_bits] = i2s_data_i.read();

if (++bit_cnt == (m_bits*2))
{
for (int i=0;i<m_bits;i++)
data[m_bits-i-1] = rev_data[i];

m_rx_fifo.write(data);
data = 0;
bit_cnt = 0;
}
}
}
else
first = false;

ws = i2s_ws_i.read();

wait();
}
}
45 changes: 45 additions & 0 deletions i2s/testbench/i2s_decoder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#ifndef I2S_DECODER_H
#define I2S_DECODER_H

#include <systemc.h>

#define I2S_MAX_BITS 24

//-------------------------------------------------------------
// i2s_decoder: Decoder for I2S interface
//-------------------------------------------------------------
SC_MODULE (i2s_decoder)
{
public:
// Clock and Reset
sc_in <bool> clk_i;
sc_in <bool> rst_i;

// I/O
sc_in <bool> i2s_mclk_i;
sc_in <bool> i2s_bclk_i;
sc_in <bool> i2s_ws_i;
sc_in <bool> i2s_data_i;

// Constructor
SC_HAS_PROCESS(i2s_decoder);
i2s_decoder(sc_module_name name): sc_module(name),
m_rx_fifo(1024)
{
m_bits = 16;
SC_CTHREAD(input, i2s_bclk_i.pos());
}

public:
sc_uint <I2S_MAX_BITS> read(void) { return m_rx_fifo.read(); }
bool read_ready(void) { return m_rx_fifo.num_available() > 0; }
void set_bit_width(int bits) { m_bits = bits; }

private:
void input(void);

sc_fifo < sc_uint<I2S_MAX_BITS> > m_rx_fifo;
int m_bits;
};

#endif
20 changes: 20 additions & 0 deletions i2s/testbench/i2s_driver.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include "i2s_driver.h"

//-----------------------------------------------------------------
// output: Drive tx data
//-----------------------------------------------------------------
void i2s_driver::output(void)
{
wait();
sc_assert(m_tx_fifo.num_available() > 0);

while (true)
{
sample_data_o.write(m_tx_fifo.read());

wait();

while (!sample_req_i.read())
wait();
}
}
38 changes: 38 additions & 0 deletions i2s/testbench/i2s_driver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#ifndef I2S_DRIVER_H
#define I2S_DRIVER_H

#include <systemc.h>

//-------------------------------------------------------------
// i2s_driver:
//-------------------------------------------------------------
SC_MODULE (i2s_driver)
{
public:
// Clock and Reset
sc_in <bool> clk_i;
sc_in <bool> rst_i;

// I/O
sc_in <bool> sample_req_i;
sc_out <sc_uint<32> > sample_data_o;

// Constructor
SC_HAS_PROCESS(i2s_driver);
i2s_driver(sc_module_name name): sc_module(name),
m_tx_fifo(2048)
{
SC_CTHREAD(output, clk_i.pos());
}

public:
void write(sc_uint <32> data) { m_tx_fifo.write(data); }
bool write_empty(void) { return m_tx_fifo.num_available() == 0; }

private:
void output(void);

sc_fifo < sc_uint<32> > m_tx_fifo;
};

#endif
Loading

0 comments on commit 3613a4e

Please sign in to comment.