tepples wrote:
Let me rephrase: The APU generates a sample on every CPU cycle, or 315/176*1000000 = 1789773 Hz. This means at a 48000 Hz output, the APU generates 315/176*1000000/48000 = 37.3 samples. How do you turn the 37.3 samples that the APU makes into one sample that you send to the AC'97 codec?
(Apologies for the lateness of my reply.)
I honestly never really thought about this before because what I have now always just "seemed" to work/sound OK. But now that I've actually taken a few minutes to think about how I have it set up - it's pretty kludgy. Haha.
Hardware Layout------------------------------------------------------------------------------------
In order for my APU to write samples out to the AC97 codec I need to cross a clock domain. That is, I need to transfer data from the 1.79MHz clock domain to the 12.288MHz AC97 domain. Since these two clock domains are completely asynchronous from one another the typical solution in FPGA design is to insert a bisynchronous FIFO/RAM in between the 2 clock domains (otherwise you will get all sorts of insane timing errors). I use the RAM method.
The RAM block is a simple dual-port, that is, one side of the RAM is write-only (the APU side), while the other side is read-only (the AC97 side). The write-side is synchronous to the 1.79MHz clock while the read-side is sync to the 12.288MHz clock. Additionally, I simply tie the RAM's address lines to all zeros and only ever read/write from/to a single RAM address (this is not as big a waste as it seems because these internal FPGA RAMs are very small and plentiful). Essentially, I create a single bisynchronous register to use between the APU and AC97 clock domains.
Sampling Control------------------------------------------------------------------------------------
On each CPU cycle the current 16-bit value on the output of the APU's mixer is written into the bisync RAM register. There is no handshaking/waiting or communication to the AC97 controller. The bisync register is just updated every CPU cycle with the current mixer output without fail.
On the AC97 side of the bisync register (i.e. the read side) it is a bit more complex. The AC97 codec (i.e. the actual chip on the board) has a serial data input interface to receive audio samples. The 16-bit audio samples are shifted out 1 bit at a time by the AC97 *controller* (i.e. the HDL AC97 control module that resides in the FPGA and sends data to the codec chip).
However, it's actually much more complex than that because AC97 codecs utilize a communication protocol based on "slots" and "frames". That is to say, you can't just shift out the 16-bit audio sample all by itself and then move on to shifting out the next sample. What really happens is for every two 16-bit samples (2 samples needed for stereo audio) that is sent to the codec you must send 13 "slots". These 13 slots make up one frame (one stereo sample) including all codec control overhead.
Each AC97 "frame" is 256 clock cycles and is organized as follows:
- 16 cycles for Slot 0 (16 bits)
- 20 cycles each for slots 1-12 (20 bits each)
(Just FYI, slots 2&3 hold the left/right 16-bit audio samples with 4 bits of 0 appended to the end in order to fill up the 20-bit slot. Also, I simply copy the mono APU mixer output to both the left/right channels. All other slots are just codec control overhead.)
The total frame time is therefore [16 + 12*20 = 256 cycles]. With a bit-clock frequency of 12.288 MHz, the frame frequency is 48,000Hz (12.288MHz/256). This results in a frame period of 20.83us (48kHz^-1).
------------------------------------------------------------------------------------
Well, that was probably more than you ever wanted to know about my APU works. Haha. But it did help me to actually type out how it all works. I can already see where there are some issues.
So to answer tepples question about how I turn 37.3 (i.e. 20.83us/(1.79MHz^-1)) samples into 1 AC97 sample...the answer is...uh, I don't do anything. LMAO. And, yes, I know that's very bad and stupid, but when I originally wrote my codec interface I just implemented the absolute simplest method possible to get data out to the codec without consider what havoc I might be causing.
Hehe. I was much too eager to get working on the fun stuff!
Would like to hear anybody's thoughts on this implementation and how I might make it better. Tepples, I think your sample averaging would be helpful, along with an APU-to-AC97 "handshake" in between each audio sample...or something like that. I really appreciate your post. You really made me stop and think about the mess I'm actually making in my audio output. LOL
cpow wrote:
I'm not trying to hijack this thread, but my method is just to keep track of the APU-generated samples between each sound-card-needed sample and do a simple average of the APU-generated samples to get the sound-card-needed sample. That's probably not the best way, but it sounds alright. What is "the best way"?
You aren't hijacking the thread - what you said was completely on-topic. In fact, I think I might just implement what you have implemented (basically what tepples suggested) in software and recreate it in hardware. Should be pretty easy and it's about a 100x better than what I have now.