6.111 Lab #4

Goal: Build a voice recorder that records and plays back 8-bit digital audio samples.

Useful links

Checkoff List

Please be ready with the following when checking off Lab #4:

  1. Have your Verilog code ready to be examined on the computer monitor.

  2. Have a sketch of a timing diagram showing how your circuit works during record and playback. Show what happens in each mode over a sequences of eight ready cycles.

  3. Be prepared to demonstrate your recorder without the interpolating filter and show the to_ac97_data on the logic analyzer.

  4. Be prepared to demonstrate your recorder with the interpolating filter and show the to_ac97_data on the logic analyzer.

During checkoff you may be asked to discuss one or more of the following questions:

  1. The linear interpolator is a simple example of digital signal processing (DSP). How can the FPGA be used to implement more sophisticated DSP functions, e.g., how would you add echo (reverb) to your playback audio signal?

  2. How would you use the labkit's on-board ZBT memory for audio signal storage, instead of the Xilinx's BRAM? What is the longest recording sample you could have using all the ZBT memory? (See the labkit page for info about the ZBT configuration.)

  3. Can you use the labkit's Flash ROM for storing audio data? (See the Flash ROM datasheet on the labkit page for info about how that components works.)

When you're done remember to upload your Verilog files to the course website so that they can be reviewed.

Digital Audio Interface

Our labkit is equipped with an AC97 Audio Codec chip (a National Semiconductor LM4550) which serves as an interface between the analog world of traditional audio components (e.g., headphones and microphones) and the digital world of the FPGA. The block diagram of the LM4550 shown below has been marked up to show the processing paths we'll be using for this lab:

Incoming audio from microphone (top arrow, pointing left-to-right): the incoming audio signal from the microphone is boosted by +20dB by an on-chip amplifier and then selected as the input source for the two (one for each of the stereo channels) 18-bit sigma- delta analog-to-digital converters (ΣΔADCs). The ADCs sample the analog waveforms at 48kHz, digitize the sampled voltages, and output sequences of 18-bit two's complement numbers (referred to as the pulse-code modulated or PCM data). Each pair (left and right channel) of PCM samples is packaged along with other status data into a 256-bit frame which is then transmitted serially at 12.288Mhz (= 256 * 48Khz) to the FPGA via the SDATA-IN pin.

Outgoing audio to headphones (bottom arrow, pointing right-to-left): the FPGA transmits a 256-bit frame of serial data to the AC97 chip via the SDATA-OUT pin. Each frame contains two 18-bit fields with PCM data for the left and right audio channels. The PCM data is converted to two 48kHz analog waveforms by the sigma-delta digital-to- analog converters (ΣΔDACs). The analog waveforms are amplified and sent to the stereo headphones.

So 48,000 times per second the AC97 codec provides two stereo PCM samples from the microphone and accepts two stereo PCM samples for the headphones. (Actually the microphone is a monaural source and so the same data appears on both the left and right incoming data streams.) It's the FPGA's job to keep up with the codec's data rates since the codec does not have on-chip buffering for either the incoming or outgoing data streams.

Voice recorder

The goal of this lab is to implement a voice recorder using the labkit's AC97 codec and FPGA. The top-level plan is pretty simple -- when recording, store the stream of incoming samples in a memory, when playing back feed the stored data stream back to the codec.

There are (of course) some interesting details:

Implementation steps

  1. Download lab4.v by clicking right on the link and selecting "Save As". This file contains several Verilog modules:

    lab4: a modified version of labkit.v that includes instances of the audio and recorder modules, hooking them up appropriately to each other and the pins connecting to the AC97 codec. The labkit's ENTER pushbutton is used as the record/playback button (push to record). You shouldn't need to modify this module.

    debounce: used to debounce and synchronize pushbuttons.

    audio: a wrapper around the ac97 and ac97commands modules which implement the low-level interface to codec. This module has three ports of interest to us: a ready output that signals users of this module that a new sample is ready, and two 8-bit data ports, one for incoming monaural PCM data and one for outgoing monaural PCM data. You shouldn't need to modify this module.

    ac97: interfaces with the AC97 codec, transmitting and receiving the 256-bit serial data streams. It has ports for both incoming and outgoing 18-bit stereo PCM data. You shouldn't need to modify this module.

    ac97commands: generates a repeating sequence of writes to the AC97 command registers that perform the appropriate initialization. In this case, that includes selecting the microphone as the input source, setting the correct amplifier gains, etc. You shouldn't need to modify this module.

    tone750hz: supplies a 20-bit PCM stream which if played at 48kHz produces a 750Hz sinewave. You shouldn't need to modify this module.

    recorder: You'll be modifying this module to implement the necessary functionality. The supplied module tests the basic functionality of the microphone and headphones. In playback mode, this dummy module sends a 750Hz tone to the headphones. In record mode, it loops incoming samples back to the outgoing data stream, so you should hear your voice in the headphones. The module has the following ports all of which are synchronous with clock_27mhz:

    clock_27mhz input system clock
    reset input 1 to reset the module to its initial state
    playback input 1 for playback, 0 for record
    ready input transitions from 0 to 1 when a new sample is available
    from_ac97_data[7:0] input 8-bit PCM data from the microphone (signed, two's complement)
    to_ac97_data[7:0] output 8-bit PCM data to the headphones (signed, two's complement)

  2. Using the Xilinx tools, build a lab4 project, compile lab4.v and load lab4.bit into the labkit. Plug in the headphone and microphone plugs from the headset into the appropriate jacks on the left-hand side of the labkit. You should hear a 750Hz tone in the headset. Pushing the ENTER pushbutton should silence the tone and instead you should hear sounds picked up by the microphone.

    You can adjust the volume of the playback by pressing the UP and DOWN buttons (the top and bottom buttons in the diamond group of buttons to the left of the ENTER button). Each press will change the volume by 1 step over the range of 32 steps. During playback the current volume is displayed in the low-order 5 bits of the LEDs.

  3. Using the steps outlined in lecture, build a single-port 64K x 8 memory component using BRAMs. The BRAM generation phase can take up to 6-8 minutes, so be patient! (You'll see a little spinning progress indicator in the lower right hand corner of the ISE window to let you know that it's noodling away at generating the appropriate design file.)

  4. Modify the recorder module to implement basic record and playback functionality.

    Record mode: When entering record mode, reset the memory address. When the ready input is asserted, a new sample from the microphone is available on the from_ac97_data[7:0] inputs at the rising edge of clock_27mhz. Store every eighth sample in the memory, incrementing the memory address after each write. You should also keep track of the highest memory address that's written. If you fill up memory, you should stop recording new samples.

    Playback mode: When entering playback mode, reset the memory address. When the ready input is asserted, supply a 8-bit sample on the to_ac97_data[7:0] outputs and hold it there until the next sample is requested. For now, read a new sample from the memory every eight transitions of ready and send it to the AC97 eight times in a row (i.e., upsample the 6kHz samples to 48kHz using simple replication). When you reach the last stored sample (compare the memory address to the highest memory address written which you saved in record mode), reset the address to 0 and continue -- this will loop through the saved data again and again.

    Test your code. If you have good ears you may hear a 6kHz tone (and its overtones) introduced by the simple upsampling by replication.

  5. Modify your recorder module to generate 48kHz playback samples using an 8-step linear interpolation between successive 6kHz samples in memory. Use one of the switches to control if your interpolator is used during playback.

    If the successive samples are S1 and S2 (S1 being the older of the two), then your module should output the following data over the course of 8 cycles of ready:

           S1             = (8 * S1) >> 3     (the right shift by 3 divides by 8) 
    .875 * S1 + .125 * S2 = (7 * S1 + S2) >> 3 
    .750 * S1 + .250 * S2 = (6 * S1 + 2 * S2) >> 3 
    .625 * S1 + .375 * S2 = (5 * S1 + 3 * S2) >> 3 
    .500 * S1 + .500 * S2 = (4 * S1 + 4 * S2) >> 3 
    .375 * S1 + .625 * S2 = (3 * S1 + 5 * S2) >> 3 
    .250 * S1 + .750 * S2 = (2 * S1 + 6 * S2) >> 3 
    .125 * S1 + .875 * S2 = (S1 + 7 * S2) >> 3 
    

    In general, on cycle i (for i from 0 to 7) you should output ((8-i)*S1 + i*S2) >> 3.

    At this point S1 <= S2 and S2 <= new memory sample, and the whole process repeats for another eight cycles of ready.

    NOTE: the samples from the AC97 are in 8-bit two's complement format, i.e., they range in value from -128 to +127. BUT the default Verilog data type is unsigned and multiplication circuits for unsigned operands are different than multiplication circuits for two's complement operands.

    Using a feature introduced in the Verilog 2001 specification (which, happily, is supported by the Xilinx tools), you can solve this problem by adding a signed modifier to your register declarations so that Verilog knows that those quantities are in two's complement format, e.g.,

    reg signed [7:0] s1, s2; 
    

    Now when, say, you use the "*" operator in your Verilog code, the Xilinx tools will generate the circuitry for signed multiplication instead of unsigned multiplication.

  6. Can you hear the difference in playback when switching between the replicated and linear interpolation modes? See the paragraph in Implementation Tips below on how to use the logic analyzer to capture the playback data being sent to the AC97. During checkoff, show how the playback data changes when you switch interpolation on and off.

  7. [optional] Have your recorder module record continuously when in record mode and then playback the last 11 seconds when you switch to playback mode -- sort of an instant reply of the most recent part of a conversation.

Implementation Tips

After coding, examining the waveforms in simulation before attempting to program everything onto the FPGA can save you a lot of time. In particular, closely examine what happens when processing an incoming sample and generating a new outgoing sample (i.e., what your logic does on ready cycles). It's pretty easy to generate a known sequence of from_ac97_data values and ensure that they get written to your memory in record mode and get played back correctly in playback mode. Check that all control signals rise and fall as you would expect them to. Another good time to use the simulator: examining the values produced by your linear interpolator -- you should see the appropriate intermediate values created over 8 ready cycles using two successive stored values.

If your circuit seems to work under simulation but not when loaded into the labkit, try bringing critical signals out to the logic analyzer connectors, e.g., the signals for your 64Kx8 memory.

A good way to debug the interpolator is to use the logic analyzer to display your results. lab4.v includes code that outputs clock_27mhz to analyzer3_clock, to_ac97_data to analyzer3_data[7:0], and ready to analyzer3_data[8]. Configure the analyzer to sample the data on the rising edge of the clock if ready is 1. You can display the 8-bit data as a "magnitude waveform" in which the logic analyzer will plot the captured data values as a waveform. Zooming in, you should see the waveform as short straight line segments each made up of 8 points as your linear interpolator interpolates between the stored samples. There shouldn't be any big jumps between one captured value and the next if your interpolator is doing its job correctly.

In general, using the logic analyzer to examine what's happening is a quick way to "see inside" your chip and get some idea of what's going on.