Learning VHDL: Processing some audio Part 1
Post date: Sep 02, 2015 10:14:9 PM
Video demonstration.
Here is the second entry to my series on learning VHDL! This project takes me a bit further from simply getting the buttons to affect how the lights blink. In this project, audio is sampled with a microphone and then written to a speaker. I was originally planning to incorporate some basic signal processing. However, I didn’t want to spend so much time on a single project and I thought writing the VHDL modules for driving the microphone and audio amplifier would suffice. Moreover, I start to include state machines to carry out the operations of more sophisticated modules.
Full setup.
Similar to the first project, the hardware is the Digilent Atlys evaluation board and the design environment is Xilinx ISE 14.7. More details on the tools I am using in this series are found in my first post, the Led Project. There are several new components involved in this project. The first component is the Digilent PmodMIC, the peripheral module (Pmod) from which each audio sample is taken. The second is the Digilent Vmod Module Interface Board (MIB), which extends the number of number Pmod ports of the Atlys board. The third component is the PmodAMP3, another Pmod which connects to the VmodMIB and is also where each audio sample is written. Finally, there is a fourth component, which is simply the speaker system that plugs directly into the PmodAMP3.
Digilent PmodMIC.
Digilent VmodMIB connected to the Digilent PmodAMP3.
Compared to the Led Project, this project turned out to be much greater in the amount of hardware descriptions needed to complete the project. However, as with any good system, there are a few key snippets of code that neatly describe the functionality of the system. I only present these snippets in this post.
The two behavioral blocks that demonstrate the functionality of the entire system are shown below. The first process block grabs an audio sample from the PmodMIC when the 48 KHz clock, the sample frequency (Fs), is high.
State machine that acquires an audio sample from the PmodMIC every Fs. The "clock" signal runs at 100 MHz.
The second process block simply takes the same audio sample and writes it to both channels of the PmodAMP3. The PmodAMP3 actually supports stereo, so the audio sample can be written to a right or left channel with time division multiplexing.
State machine that writes the audio sample directly to the PmodAMP3, which is configured to run at 48 KHz. The "clock" signal runs at 100 MHz.
Fortunately, the communication protocol for reading data from the PmodMIC is SPI, so writing the VHDL module for acquiring audio samples is very straightforward.
State machine from the module that drives the PmodMIC. The state machine executes according to a serial clock running at 12.5 MHz.
The PmodAMP3 took a good deal more effort, since the Reference Manual found on Digilent’s website does not describe all the information necessary to build a module to read the Pmod. However, the Reference Manual does state more information is found on the Pmod’s schematic, which describes the pinout, and datasheet of the SSM2518 chip, the digital stereo amplifier on the Pmod. The datasheet provides useful information, specifically the frequency of the master clock signal under reset configurations and format of the input stream.
Logic that serially writes the audio samples to the PmodAMP3, according to a 3.125 MHz bit clock. Not shown is the module that contains the "right_buff" and "left_buff" buffers which hold the audio samples for each channel.
OBSERVATIONS / CONCLUDING THOUGHTS. The first outstanding observation is many VHDL modules I have seen over the internet seem to rely on hard-coded values or have state names that don’t indicate the functionality of the state. Perhaps it’s my background in computer programming, but I’m firm believer any kind of high-level code, and I’m referring to both hardware descriptions and computer programming sources, should be written such that another programmer can almost read the code like a book. I admit I do insert numbers here and there in my descriptions, and I really ought to add more comments than I do. In general, though, those values tend to explain themselves in terms of their functionality, and I typically name my signals and variables with names that indirectly explain the purpose of my code.
Finally, my second outstanding observation is that I may be writing my modules very wrong, or perhaps just in a deplorable way. Specifically, I generally don’t try to define outputs for every possible branch, and I always write a single behavioral block for each state machine.
I have read many articles regarding proper behavioral synthesis design that say, in order to avoid regular D-latches from being inferred by the synthesizer, all outputs within a behavioral block should be assigned for all possible branches. An example of what I am referring can be found at this link.
So long as I make my behavioral blocks edge-triggered, I don’t run into this issue. Below is a snippet of the module whose purpose is to down sample a clock signal.
Logic that implements a simple module that down samples a clock signal.
Note there is no else block to account for the branch “counter/=0”. Now, here is the resultant RTL schematic.
RTL schematic showing the primitives generated from the behavioral code.
The “FDR” and “FDRE” are two of the primitives representing edge-triggered D-flip flops. The transparent D-latches would have been “LDCE” or “LDPE”. This is a simple case, I know, but I generally don’t see this problem with my larger modules. Then again, I generally try break up any large modules into smaller, manageable modules. I’m assuming the reason I don’t run into inferred D-latches is due to the fact my behavioral blocks are driven by a clock signal. But, is it even possible to infer D-latches from clock-driven behavioral blocks? If not, are there really instances for which a behavioral block should have a sensitivity list composed of regular signals, that is, signals that are not a clock signal? I have learned to always register the result of any combinational logic, and those registers are always edge-triggered by a clock.
Regarding state machines, my state machines don’t ever follow the Moor or Mealy models, both of which appear very cumbersome, hard to read, and as effective as how I build my state machines. With that said, however, I could very well be committing a major sin whose consequences only become real under certain contexts. As I have already said, I’ve only written descriptions for Xilinx FPGAs within their toolsets, so perhaps all my modules are non-applicable for another device such as an Altera FPGA. At some point, I will have to ask my questions on a forum and get answers from those who truly know synthesis from top to bottom.
For now, though, I will continue to learn more VHDL. As part of the next project, I will do some simple digital signal processing in order to remove some of the noise from my audio. Instead of including every hardware description, I only include the hardware descriptions for the peripherals.