{"id":589,"date":"2015-03-23T10:12:42","date_gmt":"2015-03-23T18:12:42","guid":{"rendered":"http:\/\/www.beyond-circuits.com\/wordpress\/?page_id=589"},"modified":"2023-11-15T07:39:04","modified_gmt":"2023-11-15T15:39:04","slug":"tutorial17","status":"publish","type":"page","link":"https:\/\/www.beyond-circuits.com\/wordpress\/tutorial\/tutorial17\/","title":{"rendered":"Tutorial 17 \u2013 Starting Audio (or a really complicated wire)"},"content":{"rendered":"<p>In this tutorial we will cover talking to the Analog Devices AU1761 audio processing chip in the ZedBoard. This chip is also present on the ZYBO board so most of this tutorial will work for that board too. You just need to change some of the pin placement constraints.<\/p>\n<h1>Overview<\/h1>\n<p>The grand goal of this tutorial is to take audio from\u00a0an iPhone, iPod, or other device in on the audio line in input and pass that through to the headphone out output so you can hear the audio on some headphones. You heard that right. In this tutorial we will enable the ZedBoard to connect your headphones to your iPod. How cool is that?<\/p>\n<p>Of course, the real purpose of the tutorial is to learn some things and get us set up to do more interesting things in the next tutorial. But for now, we need a way to configure the ADAU1761 chip on the ZedBoard. It is fairly complex and does not start up processing audio. It needs to be configured. To do that we will make a SPI interface that will let the ARM processor read and write registers in the audio chip. We will then write some code to configure the chip. We will also use the clocking wizard to generate a 24MHz clock to send to the ADAU1761 chip for its operation. And finally we will receive digital audio from the ADAU1761 chip using the I2S protocol and send that audio back out to the chip so it can send it to the headphones.<\/p>\n<h1>The SPI interface<\/h1>\n<p>SPI is a very simple four wire serial protocol. The ADAU1761 chip can talk I2C or SPI, and by default is configured for I2C. If you already have an I2C controller, and the ZYNC has a couple, then you can configure that to talk to the ADAU1761 chip. And that is just what most people would do. However, this tutorial is intended to be more generic and teach something about logic design, so we will write our own interface code. I2C is more complicated than SPI, so we will put the ADAU1761 chip into SPI mode and program it that way.<\/p>\n<p>SPI interfaces use four wires for communication. One chip is always the master and decides when read or write operations will take place. There can be multiple slave devices which are controlled by the master. There is a clock generated by the master device, serial data from the master to the slave device, and serial data from the slave back to the master, and finally a chip select signal which tells the slave that the transaction is meant for it. You can read about the SPI interface in the ADAU1761 datasheet from the Analog Devices website. Look at page 41.<\/p>\n<p><a href=\"https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/2015\/03\/tut17fig1.png\"><img loading=\"lazy\" decoding=\"async\" class=\" size-medium wp-image-592 alignright\" src=\"https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/2015\/03\/tut17fig1-300x55.png\" alt=\"tut17fig1\" width=\"300\" height=\"55\" srcset=\"https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/2015\/03\/tut17fig1-300x55.png 300w, https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/2015\/03\/tut17fig1-500x92.png 500w, https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/2015\/03\/tut17fig1.png 828w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a>The figure to the right shows a timing diagram for a write cycle. The clock can be free running or run just during the transaction. <em>CDATA<\/em> and <em>CLATCHn<\/em>\u00a0need to be setup before the rising edge of <em>CCLK<\/em>. A transaction needs at least four bytes, and the bytes are sent most significant bit first. The first byte contains seven zero bits followed by the read\/<span style=\"text-decoration: overline;\">write<\/span>\u00a0bit. The second byte contains the most significant byte of the register 16-bit address and the third byte contains the least significant byte of the address. The fourth byte is the data byte to write to the address. More than one data byte can be written and those just keep following. The address will increment by one for each subsequent byte written.<\/p>\n<p><a href=\"https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/2015\/03\/tut17fig2.png\"><img loading=\"lazy\" decoding=\"async\" class=\" size-medium wp-image-595 alignright\" src=\"https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/2015\/03\/tut17fig2-300x82.png\" alt=\"tut17fig2\" width=\"300\" height=\"82\" srcset=\"https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/2015\/03\/tut17fig2-300x82.png 300w, https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/2015\/03\/tut17fig2-500x137.png 500w, https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/2015\/03\/tut17fig2.png 732w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a>Here is an example of a read cycle. It is basically the same as the write cycle but the read\/<span style=\"text-decoration: overline;\">write<\/span>\u00a0bit is set high. The value of the <em>CDATA<\/em> input is ignored in the data part of the transaction and instead the <em>COUT<\/em> output is driven by the ADAU1761 and contains the data being read. This data is properly setup to the rising edge of the <em>CCLK<\/em> signal.<\/p>\n<h2>Getting Started<\/h2>\n<p>From Vivado, select Tools\u2192Create and Package IP, like we have done in the other tutorials. Choose to <em>Create a new AXI4 peripheral<\/em>, and give it the name <code>adau1761_controller<\/code>. Make it an AXI4 Lite slave with six registers. Click <strong><em>Next<\/em><\/strong> and then select Edit IP and click <strong><em>Finish<\/em><\/strong>. I won&#8217;t walk through all the steps in detail as you should be familiar with them from previous tutorials, but here are the highlights.<\/p>\n<h2>The Big Picture<\/h2>\n<p><a href=\"https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/2015\/03\/tut17fig4.png\"><img loading=\"lazy\" decoding=\"async\" class=\" size-medium wp-image-617 alignright\" src=\"https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/2015\/03\/tut17fig4-300x152.png\" alt=\"tut17fig4\" width=\"300\" height=\"152\" srcset=\"https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/2015\/03\/tut17fig4-300x152.png 300w, https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/2015\/03\/tut17fig4-500x253.png 500w, https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/2015\/03\/tut17fig4.png 910w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>We are aiming to have an IP block which looks like the figure on the right. When we create the IP block in the IP Packager it will create the top module <em>adau1761_controller_v1_0<\/em> and the register block <em>adau1761_controller_v1_0_S00_AXI<\/em>. We will create the <em>adau1761_spi<\/em> module and connect its control and status signals to the register block.<\/p>\n<h2>Modify the Register Block<\/h2>\n<p>Modify the <em>adau1761_controller_v1_0_S00_AXI.v<\/em> file to have the following ports. These will connect to the control and status signals in our <em>adau1761_spi<\/em> module. These allow the ARM processor to control the SPI interface and to read status from the interface.<\/p>\n<pre> output wire reset,\r\n output wire start,\r\n output wire read,\r\n output wire [2:0] nbytes,\r\n output wire [15:0] address,\r\n output wire [63:0] write_data,\r\n input wire busy,\r\n input wire [63:0] read_data,<\/pre>\n<p>The <em>reset<\/em> output will reset the SPI state machine. The <em>start<\/em> output will start a SPI transaction when it rises. The <em>read<\/em> output specifies whether a read or write transaction is to occur, and the <em>nbytes<\/em> output specifies the number of bytes in a transaction. Actually it specifies one less than the number of bytes. A zero value implies one byte and a value of seven implies eight bytes. The <em>address<\/em> value is just the 16-bit address for the transaction. The <em>write_data<\/em> output specifies what data to write. Since we can write up to eight bytes of data, it is an eight byte value. However, if you send fewer than eight bytes the value needs to be justified into the most significant bytes. So for example, if you send just one byte the data needs to be in <em>write_data[63:56]<\/em>.<\/p>\n<p>The <em>busy<\/em> signal tells the ARM that the unit is still sending or receiving data. The <em>read_data<\/em> signal is the data that was read back from the device. The <em>read_data<\/em> value is aligned like the <em>write_data<\/em> value.<\/p>\n<p>At the end of the file add the following user logic.<\/p>\n<pre>\/\/ Add user logic here\r\nassign reset = slv_reg0[25];\r\nassign start = slv_reg0[24];\r\nassign address = slv_reg0[15:0];\r\nassign nbytes = slv_reg0[18:16];\r\nassign read = slv_reg0[19];\r\nassign slv_reg1 = {{C_S_AXI_DATA_WIDTH-1{1'b0}},busy};\r\nassign write_data[31:0] = slv_reg2;\r\nassign write_data[63:32] = slv_reg3;\r\nassign slv_reg4 = read_data[31:0];\r\nassign slv_reg5 = read_data[63:32];\r\n\/\/ User logic ends\r\n<\/pre>\n<p>This logic maps the register bits into the control and status wires that leave and enter the module. The control registers and readable and writeable by the ARM. These are <em>slv_reg0<\/em>, <em>slv_reg2<\/em>, and <em>slv_reg3<\/em>. We want the <em>reset<\/em> signal to come from bit 25 of <em>slv_reg0.<\/em> So the first assign makes that happen. Likewise for the other control signals. For the status signals, the assignment is the other way around. The ARM does not write to those registers but instead they are set by the status inputs to the register block. The assignment to <em>slv_reg1<\/em> says that it&#8217;s LSB is the <em>busy<\/em> input and the other bits are always zero. Similarly the <em>slv_reg4<\/em> and <em>slv_reg5<\/em> and made from the <em>read_data<\/em> bits.<\/p>\n<p>Register 0, 2, and 3 and readable and writeable. Registers 1, 4, and 5 are read only. You will need to change the declarations for <em>slv_reg1<\/em>, <em>slv_reg4<\/em>, and <em>slv_reg5<\/em> to be wires and remove the logic which writes to those registers.<\/p>\n<h2>Controller top-level<\/h2>\n<p>Now let&#8217;s focus on the <code>adau1761_controller_v1_0.v<\/code> file. Add the following ports which will be used to communicate with the ADAU1761 chip.<\/p>\n<pre> \/\/ Users to add ports here\r\n (* mark_debug = \"true\" *) output adau1761_cclk,\r\n (* mark_debug = \"true\" *) output adau1761_clatchn,\r\n (* mark_debug = \"true\" *) output adau1761_cdata,\r\n (* mark_debug = \"true\" *) input adau1761_cout,<\/pre>\n<p>Notice that we have them marked for debug. That will let us look at the interaction of the FPGA with the ADAU1761 chip later in the tutorial.<\/p>\n<p>We will also need to declare the register signals which will go from our register block into our SPI controller. Here are those, also with the debug attribute.<\/p>\n<pre> (* mark_debug = \"true\" *) wire reset;\r\n (* mark_debug = \"true\" *) wire start;\r\n (* mark_debug = \"true\" *) wire read;\r\n (* mark_debug = \"true\" *) wire [15:0] address;\r\n (* mark_debug = \"true\" *) wire [2:0] nbytes;\r\n (* mark_debug = \"true\" *) wire [63:0] write_data;\r\n (* mark_debug = \"true\" *) wire busy;\r\n (* mark_debug = \"true\" *) wire [63:0] read_data;<\/pre>\n<p>We added the control and status ports to the register block. Now we need to change the register block instance in the top level module to connect the wires we just defined to the new ports that we added in the previous section.<\/p>\n<h2>The SPI Controller<\/h2>\n<p>All that is left for this file is to instantiate the actual SPI controller that will do the work. \u00a0We will call the module <em>adau1761_spi<\/em> and it takes two parameters. The first is called <em>clk_period<\/em> and specifies how many of our clock cycles there are in a\u00a0SPI clock cycle. The assumption here is that the clock this logic is running off will be significantly faster than the SPI clock period. The maximum SPI clock period for the ADAU1761 is 100ns and we will be running off the 100MHz AXI clock. We actually probably want to run the SPI clock slower than 10MHz too, since there is no hurry and going slower is less likely to run into problems that could be difficult to debug. The second parameter is called <em>max_bytes<\/em> and it specifies the maximum number of bytes that are allowed in a SPI transaction. Because of our register layout the maximum number of bytes in this case is eight.<\/p>\n<p>Actually, while we are here, let&#8217;s create a parameter for this top-level module which specifies the SPI clock period. That way a user of this IP block can specify what they want since our clock frequency may not always be 100MHz. Make the parameter called <em>SPI_CLOCK_PERIOD<\/em>.<\/p>\n<p>In addition to all the control signals from the registers, our adau1761_spi block also has a clock input which is connected to the AXI clock <em>s00_axi_aclk<\/em>, and ports which connect to the ADAU1761 SPI interface. Don&#8217;t make the mistake of connecting the <em>clk<\/em> input of the <em>adau1761_spi<\/em> module to a signal called <em>clk<\/em>. There is no signal called <em>clk<\/em> at the top module. The clock at the top module is called <em>s00_axi_aclk<\/em>.<\/p>\n<p>Now, as we move lower into our hierarchy of modules, we want to try to make the design more generic. This lets us reuse the design in other places. The SPI signal names at this level are all prefixed with <em>adau1761_<\/em> and are not very generic. We will change those names in the adau1761_spi interface to have the names <em>spi_clk<\/em>, <em>spi_frame<\/em>, <em>spi_mosi<\/em>, and <em>spi_miso<\/em>. Here are the connections for that part of the instantiation.<\/p>\n<pre> .spi_clk(adau1761_cclk),\r\n .spi_frame(adau1761_clatchn),\r\n .spi_mosi(adau1761_cdata),\r\n .spi_miso(adau1761_cout)<\/pre>\n<p>The clock signal is obvious, but I prefer the term <em>frame<\/em> when dealing with what the ADAU1761 calls the <em>chip select<\/em>. This is really the signal which specifies that the data on the other signals is valid and should be paid attention to. This signal <em>frames<\/em> the transaction. Also, it starts to get very confusing when you use terms like data in and data out on a SPI interface. Those terms are relative to the master or the slave. It is better to use the acronym <em>MISO<\/em> and <em>MOSI<\/em> for master out slave in and master in slave out. Those are unambiguous.<\/p>\n<p>Now we are ready to tackle the SPI engine itself.<\/p>\n<h1>The SPI Engine<\/h1>\n<p>We need to first create the adau1761_spi.v file and make sure it is in the correct location in the IP repository. See my <a title=\"Putting New Files in the Right Place \u2013 The Vivado Edition\" href=\"https:\/\/www.beyond-circuits.com\/wordpress\/2015\/03\/putting-new-files-in-the-right-place-the-vivado-edition\/\">blog post<\/a>\u00a0on creating files in the correct location for IP packaging. If you just use the default location then Vivado will not do the right thing.<\/p>\n<p>OK, let&#8217;s first declare our module and its parameters and ports.<\/p>\n<pre>`timescale 1ns\/1ns\r\nmodule adau1761_spi #\r\n (\r\n parameter clk_period = 100,\r\n parameter max_bytes = 8\r\n )\r\n (\r\n input clk,\r\n input reset,\r\n input start,\r\n output reg busy,\r\n input read,\r\n input [$clog2(max_bytes)-1:0] nbytes,\r\n input [15:0] address,\r\n input [max_bytes*8-1:0] write_data,\r\n output [max_bytes*8-1:0] read_data,\r\n output reg spi_clk,\r\n output reg spi_frame,\r\n output reg spi_mosi,\r\n input spi_miso\r\n );<\/pre>\n<p>That should be pretty self-explanatory. Next I declare a local parameter called <em>clk_half_period<\/em>. A local parameter is just a parameter which cannot be overridden by instantiating modules. They are useful to hold computed values which will not change. In this module, <em>clk_period<\/em> may be changed by the calling module but what constitutes half of that value cannot be changed by the calling module. This sounds confusing but it is not. Here is the declaration.<\/p>\n<pre> localparam clk_half_period = clk_period\/2;<\/pre>\n<p>So, if the calling module says that <em>clk_period<\/em> is 100, then <em>clk_half_period<\/em> will be set to 50. If the calling module says that <em>clk_period<\/em> is 10 then <em>clk_half_period<\/em> will be 5. The calling module cannot say that <em>clk_period<\/em> is 100 and <em>clk_half_period<\/em> is 70. It can only change <em>clk_half_period<\/em> indirectly.<\/p>\n<p>Next we need to watch the start signals to see if it rises. That will indicate that we need to start a SPI transaction. Doing this is a common idiom that we have seen before.<\/p>\n<pre>reg [1:0] start_shift = 0;\r\nalways @(posedge clk)\r\nstart_shift &lt;= {start_shift,start};\r\nwire start_rise = start_shift == 2'b01;\r\n<\/pre>\n<p>Next we keep a counter which helps us create the SPI clock. The counter starts if we are not busy and we see a rise on the start signal. It counts if we are busy, and wraps when the count is equal to the clock period minus one.<\/p>\n<pre>  reg [$clog2(clk_period)-1:0] clk_count;\r\n  always @(posedge clk)\r\n    if (reset)\r\n      clk_count &lt;= 0;\r\n    else if (!busy &amp;&amp; start_rise)\r\n      clk_count &lt;= 0;\r\n    else if (busy)\r\n      if (clk_count == clk_period-1)\r\n\tclk_count &lt;= 0;\r\n      else\r\n\tclk_count &lt;= clk_count+1;\r\n<\/pre>\n<p>Now we look at the count values and make the SPI clock rise and fall at the correct times.<\/p>\n<pre>  wire clk_rise = clk_count == clk_half_period;\r\n  wire clk_fall = clk_count == 0;\r\n  always @(posedge clk)\r\n    if (clk_fall) spi_clk &lt;= 0;\r\n    else if (clk_rise) spi_clk &lt;= 1;\r\n<\/pre>\n<p>Next we need a shift register which will hold the data that we will be shifting out. This will also do double duty on read transactions and shift the read data back in.<\/p>\n<pre>  reg [8*max_bytes+24:0] shift_reg;\r\n<\/pre>\n<p>We will also need a counter to count the number of bits that we have shifted so that we know when to end the transaction.<\/p>\n<pre>  integer shift_count\r\n<\/pre>\n<p>Now we have an always block which will load the shift register and shift it on the <em>falling<\/em> edge of the SPI clock. Remember we need to keep the data stable around the rising edge of the clock. If you have a keen eye you will see that our shift register has one more bit than you would expect. This extra bit is used to hold the half cycle of data at the start of the transaction. Here is the always block in its entirety.<\/p>\n<pre>  always @(posedge clk)\r\n    if (reset)\r\n      busy &lt;= 0;\r\n    else if (!busy)\r\n      begin\r\n        if (start_rise)\r\n\t  begin\r\n\t    busy &lt;= 1;\r\n\t    shift_count &lt;= 0;\r\n\t    shift_reg &lt;= {8'b0,read,address,write_data};\r\n\t  end\r\n        end\r\n    else if (clk_rise)\r\n      shift_count &lt;= shift_count+1;\r\n    else if (clk_fall)\r\n      begin\r\n\tshift_reg &lt;= {shift_reg,spi_miso};\r\n\tif (shift_count == 32+nbytes*8)\r\n\t  busy &lt;= 0;\r\n      end\r\n<\/pre>\n<p>Basically the important parts are we reset busy when we get reset. If we are not busy when <em>start<\/em> rises, then we become busy, set our shift count to zero, and load the shift register. If we are busy and the SPI clock rises, then we increment our shift count. Otherwise if we are busy and the SPI clock falls then we shift the shift register and if we hit the total number of bytes to shift then we go back to the idle state.<\/p>\n<p>One little detail. Technically we should capture the <em>spi_miso<\/em> data on the rising edge of the SPI clock. We capture it here on the falling edge right before it changes. Because the SPI clock actually falls the cycle after <em>clk_fall<\/em> is true this is safe to do.<\/p>\n<p>We need to set the read_data output to the value that was shifted in to the shift register. This only needs to be stable when we are not shifting so we just pass the shift register bits back directly.<\/p>\n<pre>  assign read_data = shift_reg[max_bytes*8-1:0];\r\n<\/pre>\n<p>The <em>spi_frame<\/em> output is just the inverse of <em>busy<\/em>.<\/p>\n<pre>  always @(*) spi_frame = ~busy;\r\n<\/pre>\n<p>And of course the SPI output <em>spi_mosi<\/em> is just the most significant bit of the shift register.<\/p>\n<pre>  always @(*) spi_mosi = shift_reg[8*max_bytes+24];\r\n<\/pre>\n<p>And don&#8217;t forget to end your module!<\/p>\n<p>Now merge all the file changes and add the <em>SPI_CLOCK_PERIOD<\/em> parameter to the GUI view and package your IP.<\/p>\n<h1>Building the Full Chip<\/h1>\n<p>Now back in your original project go to the IP Integrator and instantiate an adau1761_controller IP component and the ZYNQ processor. Run all the block automations, and all the connection automations. Make the <em>adau1761_*<\/em> ports from the controller external and create the HDL wrapper by right-clicking on the <em>audio_controller<\/em> instance in the <em>Sources<\/em> window, and then run <em>Generate Block Design<\/em>.<\/p>\n<p>You then need to add the constraints for the SPI interface. For expediency, here they are. You will need to modify as appropriate for the ZYBO board.<\/p>\n<pre>set_property IOSTANDARD LVCMOS33 [get_ports adau1761_cclk]\r\nset_property IOSTANDARD LVCMOS33 [get_ports adau1761_clatchn]\r\nset_property IOSTANDARD LVCMOS33 [get_ports adau1761_cdata]\r\nset_property IOSTANDARD LVCMOS33 [get_ports adau1761_cout]\r\nset_property PACKAGE_PIN AB4 [get_ports adau1761_cclk]\r\nset_property PACKAGE_PIN AB1 [get_ports adau1761_clatchn]\r\nset_property PACKAGE_PIN Y5 [get_ports adau1761_cdata]\r\nset_property PACKAGE_PIN AB5 [get_ports adau1761_cout]\r\n<\/pre>\n<p>Now go ahead and <em>Run Synthesis<\/em> on the design. With that complete you should be able to <em>Set Up Debug<\/em>, found under the <em>Flow Navigator<\/em> under <em>Synthesized Design<\/em>,\u00a0to add your signals that were marked for debug. This will let us watch the SPI engine talk to the ADAU1761 chip.<\/p>\n<p>Ideally we would set up a simulation of the IP block using an AXI4 Lite <em>bus functional model (BFM)<\/em> and a model of the SPI interface on the ADAU1761 chip, The BFM allows the simulator to take the place of the ZYNQ processor and lets your test perform register reads and writes to stimulate the IP block. However, the BFM is a separate license from Xilinx and it is not free. I am working on a model which can be used in the IP Integrator but it is not ready yet.<\/p>\n<p>After the debug core is inserted, generate a bitstream file and export it to the SDK. Then start the SDK.<\/p>\n<h1>\u00a0The ARM Code<\/h1>\n<p>Once SDK starts up create a new empty application called <em>audio_control<\/em>. Add a source file main.c and start by including the <em>xparameters<\/em> and <em>adau1761_controller<\/em> header files.<\/p>\n<pre><b>#include<\/b> \"adau1761_controller.h\"\r\n<b>#include<\/b> \"xparameters.h\"\r\n<\/pre>\n<p>Then we define a shorter name for the base address of the controller, mnemonic names for the register offsets, and some position information for the register fields.<\/p>\n<pre>#define AUDIO_BASE XPAR_ADAU1761_CONTROLLER_0_S00_AXI_BASEADDR\r\n\r\n#define CTRL_ADDR ADAU1761_CONTROLLER_S00_AXI_SLV_REG0_OFFSET\r\n#define BUSY_ADDR ADAU1761_CONTROLLER_S00_AXI_SLV_REG1_OFFSET\r\n#define WRITE_LOW_DATA_ADDR ADAU1761_CONTROLLER_S00_AXI_SLV_REG2_OFFSET\r\n#define WRITE_HIGH_DATA_ADDR ADAU1761_CONTROLLER_S00_AXI_SLV_REG3_OFFSET\r\n#define READ_LOW_DATA_ADDR ADAU1761_CONTROLLER_S00_AXI_SLV_REG4_OFFSET\r\n#define READ_HIGH_DATA_ADDR ADAU1761_CONTROLLER_S00_AXI_SLV_REG5_OFFSET\r\n\r\n#define RESET_POS 25\r\n#define START_POS 24\r\n#define READ_POS 19\r\n#define NBYTES_POS 16\r\n#define ADDR_POS 0\r\n<\/pre>\n<p>Next we define the function that will perform a write transaction. It takes as a parameter the address, the data, and the number of bytes to write. The control word is composed of one less than the number of bytes and the address value shifted into the correct positions. We don&#8217;t need to worry about the <em>read<\/em> bit because that is zero for writes. Then the low and high portions of the data value. These need to be shifted into the most significant positions. Then the registers need to be written. Note that a rising edge on the <em>start<\/em> bit is what actually kicks off the transaction and it is located in the control register. So we write the control word, and then we write the control word again with the start bit set. Then we wait until the busy register returns zero. Here is the code.<\/p>\n<pre>void write_adau1761(uint32_t addr, uint64_t data, uint32_t nbytes)\r\n{\r\n  uint32_t busy;\r\n  uint32_t control_word = (nbytes-1)&lt;&lt;NBYTES_POS|\r\n    addr&lt;&lt;ADDR_POS;\r\n  uint32_t data_low = data&lt;&lt;(8*(8-nbytes));\r\n  uint32_t data_high = (data&lt;&lt;(8*(8-nbytes)))&gt;&gt;32;\r\n  ADAU1761_CONTROLLER_mWriteReg(AUDIO_BASE,WRITE_LOW_DATA_ADDR,\r\n    data_low);\r\n  ADAU1761_CONTROLLER_mWriteReg(AUDIO_BASE,WRITE_HIGH_DATA_ADDR,\r\n    data_high);\r\n  ADAU1761_CONTROLLER_mWriteReg(AUDIO_BASE,CTRL_ADDR,control_word);\r\n  ADAU1761_CONTROLLER_mWriteReg(AUDIO_BASE,CTRL_ADDR,control_word|\r\n    (1&lt;&lt;START_POS));\r\n  while ((busy = ADAU1761_CONTROLLER_mReadReg(AUDIO_BASE,BUSY_ADDR)));\r\n}\r\n<\/pre>\n<p>The read function is similar but does not take the data parameter and instead returns a 64-bit value. We again set the control word, this time with the <em>read<\/em> bit set. We write the control word and then write it again with the start bit set. We then wait for <em>busy<\/em> to be low, and then read the received data. The read data does not need to be aligned since it is shifter in to the shift register from the LSB. But it does need to be masked. Here is the read function.<\/p>\n<pre>uint64_t read_adau1761(uint32_t addr, uint32_t nbytes)\r\n{\r\n  uint32_t busy;\r\n  uint32_t control_word = 1&lt;&lt;READ_POS|(nbytes-1)&lt;&lt;NBYTES_POS|\r\n    addr&lt;&lt;ADDR_POS;\r\n  uint64_t data_low;\r\n  uint64_t data_high;\r\n  ADAU1761_CONTROLLER_mWriteReg(AUDIO_BASE,CTRL_ADDR,control_word);\r\n  ADAU1761_CONTROLLER_mWriteReg(AUDIO_BASE,CTRL_ADDR,control_word|\r\n    (1&lt;&lt;START_POS));\r\n  while ((busy = ADAU1761_CONTROLLER_mReadReg(AUDIO_BASE,BUSY_ADDR)));\r\n  data_low = ADAU1761_CONTROLLER_mReadReg(AUDIO_BASE,\r\n    READ_LOW_DATA_ADDR);\r\n  data_high = ADAU1761_CONTROLLER_mReadReg(AUDIO_BASE,\r\n    READ_HIGH_DATA_ADDR);\r\n  return ((data_high&lt;&lt;32)|data_low)&amp;((1LL&lt;&lt;8*nbytes)-1);\r\n}\r\n<\/pre>\n<p>Next we need a function to reset the SPI controller and place the ADAU1761 chip into SPI mode. By default the chip is in I2C mode and it needs to see the <span style=\"text-decoration: overline;\">CLATCH<\/span> signal fall three times to switch into SPI mode. Here is the reset code.<\/p>\n<pre>void reset_adau1761(void)\r\n{\r\n  ADAU1761_CONTROLLER_mWriteReg(AUDIO_BASE,CTRL_ADDR,1&lt;&lt;RESET_POS);\r\n  ADAU1761_CONTROLLER_mWriteReg(AUDIO_BASE,CTRL_ADDR,0);\r\n  read_adau1761(0,1);\r\n  read_adau1761(0,1);\r\n  read_adau1761(0,1);\r\n}\r\n<\/pre>\n<p>Finally we need to initialize the ADAU1761 part. This is not as easy as it sounds. There are over twenty different registers that need to be configured. There is a good tutorial on doing this written by Mike Field on his blog at <a title=\"ZedBoard Audio\" href=\"http:\/\/hamsterworks.co.nz\/mediawiki\/index.php\/Zedboard_Audio\">ZedBoard Audio<\/a>. I highly recommend giving that a read if you want to understand more about configuring the chip. You can also download the datasheet from Analog Devices and read more about the part. For the purposes of this tutorial however, we just need to write the registers with the magic values. Here&#8217;s the code.<\/p>\n<pre>void init_adau1761(void) {\r\n  volatile int i;\r\n  \/\/  setup PLL\r\n  write_adau1761(0x4000,0x0e,1);\r\n  \/\/  Configure PLL\r\n  write_adau1761(0x4002,0x007d000c2301,6);\r\n  \/\/  Wait for PLL to lock\r\n  while (!(read_adau1761(0x4002,6)&amp;2));\r\n  \/\/  Enable clock to core\r\n  write_adau1761(0x4000,0xf,1);\r\n  \/\/  delay\r\n  for (i=0; i&lt;1000000; i++);\r\n  \/\/  I2S master mode\r\n  write_adau1761(0x4015,0x01,1);\r\n  \/\/  left mixer enable\r\n  write_adau1761(0x400a,0x01,1);\r\n  \/\/  left 0db\r\n  write_adau1761(0x400b,0x05,1);\r\n  \/\/  right mixer enable\r\n  write_adau1761(0x400c,0x01,1);\r\n  \/\/  right 0db\r\n  write_adau1761(0x400d,0x05,1);\r\n  \/\/  Playback left mixer unmute, enable\r\n  write_adau1761(0x401c,0x21,1);\r\n  \/\/  Playback right mixer unmute, enable\r\n  write_adau1761(0x401e,0x41,1);\r\n  \/\/  Enable headphone output left\r\n  write_adau1761(0x4023,0xe7,1);\r\n  \/\/  Enable headphone output right\r\n  write_adau1761(0x4024,0xe7,1);\r\n  \/\/  Enable line out left\r\n  write_adau1761(0x4025,0xe7,1);\r\n  \/\/  Enable line out right\r\n  write_adau1761(0x4026,0xe7,1);\r\n  \/\/  Enable both ADCs\r\n  write_adau1761(0x4019,0x03,1);\r\n  \/\/  Enable playback both channels\r\n  write_adau1761(0x4029,0x03,1);\r\n  \/\/  Enable both DACs\r\n  write_adau1761(0x402a,0x03,1);\r\n  \/\/  Serial input L0,R0 to DAC L,R\r\n  write_adau1761(0x40f2,0x01,1);\r\n  \/\/  Serial output ADC L,R to serial output L0,R0\r\n  write_adau1761(0x40f3,0x01,1);\r\n  \/\/  Enable clocks to all engines\r\n  write_adau1761(0x40f9,0x7f,1);\r\n  \/\/  Enable both clock generators\r\n  write_adau1761(0x40fa,0x03,1);\r\n}\r\n<\/pre>\n<p>Then you just need a main() routine to call the reset and init functions.<\/p>\n<pre>int main(void) {\r\n  reset_adau1761();\r\n  init_adau1761();\r\n}\r\n<\/pre>\n<p>And there you have the C code to initialize the part. Now we will turn our attention to building the rest of the FPGA that can pass the audio through.<\/p>\n<h1>Generating the Audio Clock<\/h1>\n<p>The ADAU1761 chip needs to get a 24MHz clock from the FPGA. We will use the clocking wizard to create one for it to use. Go back to your FPGA project and open the <em>IP Integrator<\/em>. Click the <em>Add IP<\/em> icon and add a Clock Wizard component.<\/p>\n<p>The <em>Re-customize IP<\/em> window should pop up. If it does not right-click on the IP component and select <em>Re-customize IP<\/em>. In the <strong>Clocking Options<\/strong> tab<em>\u00a0<\/em> make sure the Primary clock input frequency is set to 100MHz. In the <strong>Output Clocks<\/strong> tab make sure the output frequency is set to 24MHz. Make sure that the <em>Clocking Feedback<\/em> source is set to <em>Automatic Control On-Chip<\/em>. Also, lower on that page make select <em>Active Low<\/em> for the Reset Type. Then click <strong><em>OK<\/em><\/strong>.<\/p>\n<p>Do not\u00a0run the connection automation. Connect the <em>FCLK_CLK0<\/em> clock output from the ZYNQ to the <em>clk_in1<\/em> input of the Clocking Wizard. Connect the <em>FCLK_RESET0_N<\/em> output from the ZYNQ to the <em>resetn<\/em> input of the Clocking Wizard. Right-click in the IP Integrator Diagram window and create an output port called <em>adau1761_mclk<\/em> and another called <em>led0<\/em>. Connect the <em>clk_out1<\/em> port of the Clocking Wizard to the <em>adau1761_mclk<\/em> port and the <em>locked<\/em> output to the <em>led0<\/em> port. This will let us know that the PLL has locked by looking at the LED.<\/p>\n<h1>Loopback the Digital Audio<\/h1>\n<p>Now we will construct the logic to loopback the digital audio input to the digital audio output.<\/p>\n<p>The audio data coming from and going to the ADAU1761 chip uses the I<sup>2<\/sup>S protocol. This protocol is similar to SPI in that it uses serial data and a clock. Unlike SPI however, the data never stops. There is no concept of a transaction, it just streams constantly. Rather than a frame signal, there is a channel select signal. When the channel select signal is low the data is for the left channel and when it is high, the data is for the right channel. The data is sent most significant bit first and the data is encoded using twos complement. One device is the master and generates the serial clock and word select signal. If it is transmitting the audio data from the master to the slave, then it also sends the serial data. If it is receiving then the slave sends the serial data.<\/p>\n<p>In our case, there is a transmit and receive stream of data and the ADAU1761 is the master. For all practical purposes that means we just need to take the data we receive and send it back to the master. In the next tutorial we will actually process the I<sup>2<\/sup>S\u00a0data.<\/p>\n<p>For now, create a new IP component. Give it an AXI4 Lite slave interface and eight registers. We won&#8217;t be using the registers for now. Call the IP an <em>adau1761_data<\/em> component.<\/p>\n<p>To the top level module add the following ports.<\/p>\n<pre>   \/\/ Users to add ports here\r\n   (* mark_debug = \"true\" *) input wire adau1761_bclk,\r\n   (* mark_debug = \"true\" *) input wire adau1761_lrclk,\r\n   (* mark_debug = \"true\" *) input wire adau1761_adc_sdata,\r\n   (* mark_debug = \"true\" *) output wire adau1761_dac_sdata,\r\n   \/\/ User ports ends\r\n<\/pre>\n<p>Then at the end of the file loopback the ADC data to the DAC.<\/p>\n<pre>  \/\/ Add user logic here\r\n  assign adau1761_dac_sdata = adau1761_adc_sdata;\r\n  \/\/ User logic ends\r\n<\/pre>\n<p>Go ahead and merge the changes and repackage the IP. Don&#8217;t worry about the registers for now. We will hook those up in the next tutorial.<\/p>\n<p><a href=\"https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/2015\/03\/tut17fig3.png\"><img loading=\"lazy\" decoding=\"async\" class=\" size-medium wp-image-611 alignright\" src=\"https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/2015\/03\/tut17fig3-300x123.png\" alt=\"tut17fig3\" width=\"300\" height=\"123\" srcset=\"https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/2015\/03\/tut17fig3-300x123.png 300w, https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/2015\/03\/tut17fig3-1024x419.png 1024w, https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/2015\/03\/tut17fig3-500x205.png 500w, https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/2015\/03\/tut17fig3.png 1239w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a>Back in your main project go ahead and run the connection automation. Then make the I<sup>2<\/sup>S ports external. When complete your design should look like the figure to the right. If everything looks good you should be able to generate the block design.<\/p>\n<p>We need to add some pin constraints to the design as well. Here are the ones I have.<\/p>\n<pre>set_property IOSTANDARD LVCMOS33 [get_ports adau1761_mclk]\r\nset_property PACKAGE_PIN AB2 [get_ports adau1761_mclk]\r\nset_property IOSTANDARD LVCMOS33 [get_ports led0]\r\nset_property PACKAGE_PIN T22 [get_ports led0]\r\n\r\nset_property IOSTANDARD LVCMOS33 [get_ports adau1761_adc_sdata]\r\nset_property IOSTANDARD LVCMOS33 [get_ports adau1761_dac_sdata]\r\nset_property IOSTANDARD LVCMOS33 [get_ports adau1761_bclk]\r\nset_property IOSTANDARD LVCMOS33 [get_ports adau1761_lrclk]\r\nset_property PACKAGE_PIN AA7 [get_ports adau1761_adc_sdata]\r\nset_property PACKAGE_PIN Y8 [get_ports adau1761_dac_sdata]\r\nset_property PACKAGE_PIN AA6 [get_ports adau1761_bclk]\r\nset_property PACKAGE_PIN Y6 [get_ports adau1761_lrclk]\r\n<\/pre>\n<p>Once the constraints are entered you should be able to synthesize the design.<\/p>\n<h2>Setting Up Debug<\/h2>\n<p>Run the Setup Debug tool to set up the embedded logic analyzer. We will use this to look at the SPI interface as well as the audio data. Since we have marked all the nets we are interested in just add all of them.Notice that some nets have an undefined clock domain. These correspond to the I<sup>2<\/sup>S interface signals. Also notice that there are only three signals and not four. That is because the ADC and DAC data signals are now the same. We need to define the clock domain to be used for the I<sup>2<\/sup>S signals. We could place them on the <em>FCLK_CLK0<\/em> clock coming from the ZYNQ processor, but instead let&#8217;s put them on the <em>mclk<\/em> audio clock domain. This will have the effect of creating a second logic analyzer unit.<\/p>\n<p>Right-clock on the red undefined label in the Clock Domain column and select <em>Select Clock Domain&#8230;<\/em> Then select the adau1761_mclk_OBUF clock. Do the same for the other undefined signals. You should then be able to clock <em><strong>Next<\/strong><\/em>. Select a sample data depth of 16384\u00a0and click <strong><em>Next<\/em><\/strong>. Then click <strong><em>Finish<\/em><\/strong>.<\/p>\n<p>You should now be able to generate the bitstream.<\/p>\n<h1>Putting it All Together<\/h1>\n<p>You should be able to program the FPGA and run the program to configure the ADAU1761. Note that when you first program the FPGA you will get a message saying there are no debug cores. This is because the ARM has not yet been configured to there is no clock going to the embedded logic analyzer. Once you run the software the ARM will be configured and Vivado will be able to see the logic analyzers.<\/p>\n<p>You should now be able to plug an iPod or other audio source into the line in input on the ZedBoard (the blue connector) and plug in some headphones (the black connector) and listen to your tunes. Use the embedded logic analyzer to see the digital audio as it passes through the FPGA. In the next tutorial we will actually receive the data and process it before sending it back out.<\/p>\n<h1>Files<\/h1>\n<p>Here are the files I have for this tutorial in case you get stuck or you just want to see how I implemented things.<\/p>\n<ul>\n<li><a href=\"https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/tutorials\/tutorial17\/adau1761_spi.v\">adau1761_spi.v<\/a><\/li>\n<li><a href=\"https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/tutorials\/tutorial17\/adau1761_controller_v1_0.v\">adau1761_controller_v1_0.v<\/a><\/li>\n<li><a href=\"https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/tutorials\/tutorial17\/adau1761_controller_v1_0_S00_AXI.v\">adau1761_controller_v1_0_S00_AXI.v<\/a><\/li>\n<li><a href=\"https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/tutorials\/tutorial17\/adau1761_controller.xdc\">adau1761_controller.xdc<\/a><\/li>\n<li><a href=\"https:\/\/www.beyond-circuits.com\/wordpress\/wp-content\/uploads\/tutorials\/tutorial17\/main.c\">main.c<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>In this tutorial we will cover talking to the Analog Devices AU1761 audio processing chip in the ZedBoard. This chip is also present on the ZYBO board so most of this tutorial will work for that board too. You just &hellip; <a href=\"https:\/\/www.beyond-circuits.com\/wordpress\/tutorial\/tutorial17\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"parent":226,"menu_order":17,"comment_status":"open","ping_status":"open","template":"","meta":{"footnotes":""},"class_list":["post-589","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.beyond-circuits.com\/wordpress\/wp-json\/wp\/v2\/pages\/589","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.beyond-circuits.com\/wordpress\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.beyond-circuits.com\/wordpress\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.beyond-circuits.com\/wordpress\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.beyond-circuits.com\/wordpress\/wp-json\/wp\/v2\/comments?post=589"}],"version-history":[{"count":31,"href":"https:\/\/www.beyond-circuits.com\/wordpress\/wp-json\/wp\/v2\/pages\/589\/revisions"}],"predecessor-version":[{"id":634,"href":"https:\/\/www.beyond-circuits.com\/wordpress\/wp-json\/wp\/v2\/pages\/589\/revisions\/634"}],"up":[{"embeddable":true,"href":"https:\/\/www.beyond-circuits.com\/wordpress\/wp-json\/wp\/v2\/pages\/226"}],"wp:attachment":[{"href":"https:\/\/www.beyond-circuits.com\/wordpress\/wp-json\/wp\/v2\/media?parent=589"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}