• 0
jcloiacon

PMODAMP3 in Verilog with Basys 3

Question

Greetings all!

I've been trying on and off since September to get a simple sawtooth to sound from the PMODAMP3, so that I can eventually make music with it.

Here are my intended settings:

JP5 unloaded: Standalone mode

JP6 unloaded: 0dB gain

JP3 loaded: i2s format

JP4 loaded: MCLK = 256*fs

JP2 loaded BCLK side.

BCLK = fs*64 = MCLK/4 = 2.5MHz, therefore

MCLK = 10MHz, fs = 39kHz

I've attached the code in 4 Verilog files: Basys3_Abacus_Top.v (name inherited from example project), sclk_div.v (generates BCLK from FPGA clk), i2s_tx.v (i2s transmitter), and oscillator.v (produces a simple 1Hz sawtooth), as well as test benches for i2s_tx and Basys3_Abacus_top. All of these files are relatively simple.

In addition, I've attached the constraint file, and an image of the output, taken with my DSO NANO V3.

Furthermore, I've zipped up the entire project and uploaded here: https://drive.google.com/file/d/0B_8d0gTEx0yqaDFyOUhCTGU0QW8/view?usp=sharing

Some thoughts:

Maybe SSM2518 doesn't know to generate MCLK as 4xBCLK?

Faulty chip?

 

Anyways, if one of you has the time and energy to either point out flaws in my verilog or hardware setup, I would be very grateful. 

TIA

 

oscillator.v

i2s_tx.v

sclk_div.v

i2stx_tb.v

Basys3_Master.xdc

Basys3_Abacus_Top.v

IMG_001.BMP

Share this post


Link to post
Share on other sites

12 answers to this question

Recommended Posts

  • 0

Hi jcloiacon,

I'm not able to speak towards your Verilog code, but I took a look at the Pmod portion with the SSM2518 and after going through the datasheet, I do not think the SSM2518 will adjust the MCLK rate, at least based on the Master and Bit Clock section on page 14 of the datasheet. The other thing I am unclear on is if your sample rate of 39 kHz is usable by the PmodAMP3. You have a MCLK ratio that is 256x more than the fs, which is valid and seems like it would likely work with the default setting (Setting 2), but on in Table 11 (page 24) 39 kHz isn't listed as an input sample rate on the left hand side; I don't know if those are just commonly used sample rates that are conveniently listed or if those are the only set of sample rates that the SSM2518 is capable of receiving; the datasheet isn't clear on this. Either way, based on what you said you have set up, you shouldn't need to mess with any of the default register values. 

Let me know if you have any other questions and I'll do my best to help answer them.

Thanks,
JColvin

Edited by JColvin

Share this post


Link to post
Share on other sites
  • 0

Hi jcloiacon,

I'm not able to speak towards your Verilog code, but I took a look at the Pmod portion with the SSM2518 and after going through the datasheet, I do not think the SSM2518 will adjust the MCLK rate, at least based on the Master and Bit Clock section on page 14 of the datasheet. The other thing I am unclear on is if your sample rate of 39 kHz is usable by the PmodAMP3. You have a MCLK ratio that is 256x more than the fs, which is valid and seems like it would likely work with the default setting (Setting 2), but on in Table 11 (page 24) 39 kHz isn't listed as an input sample rate on the left hand side; I don't know if those are just commonly used sample rates that are conveniently listed or if those are the only set of sample rates that the SSM2518 is capable of receiving; the datasheet isn't clear on this. Either way, based on what you said you have set up, you shouldn't need to mess with any of the default register values. 

Let me know if you have any other questions and I'll do my best to help answer them.

Thanks,
JColvin

JColvin, thank you for your quick reply! It does seem that the SSM2518 requires specific sampling frequencies, a revelation which has shaken my understanding of the device as simply a clocked shift register attached to a DAC. That said -- and provided this is the case -- an accurate high frequency clock is now required, which precludes clocking by simple division of the available 100MHz clock. It seems that use of the clock management tiles (CMTs) is necessary. No chance you can point me to code which generates, for example, a 5.6448 MHz clock?

Share this post


Link to post
Share on other sites
  • 0

Still no luck. Jumpers set as above. Here are my clock settings:

mclk : 12.288 MHz

bclk : 3.072 MHz (mclk/4)

fs = lrclk: 48kHz (mclk / 256, tested with DSO Nano v3 Oscilloscope)

sclk and lrclk synced to bclk falling edge

The output is a noisy inverse saw, which peaks at about ±1.7V, which is probably the analog limit of the device (~3.3/2). The image is attached.

Do you suspect there are problems with having a 12MHz signal on an output pin? My oscilloscope is too slow to check.

IMG_007.BMP

Edited by jcloiacon

Share this post


Link to post
Share on other sites
  • 0

Hi!

I've got the same setup, and am getting a 1Hz ticking. How can I help you debug yours? Email me/PM me your email address, and I'll send photos...

Share this post


Link to post
Share on other sites
  • 0

I've got some sensible noise out of it. Will post code later on tonight.... strangly enough, the speaker output needs to be slightly unseated.

But it definately is a sawtooth sounding buzz. 

Share this post


Link to post
Share on other sites
  • 0

Hi again.

The real problem (as you no doubt have worked out) is that for standalone mode without a separate MCLK signal BCLK needs to be 256x the frequency of the LRCLK. and BCLK needs to be between 2.048 MHz and 6.144 MHz (as per datasheet). This limits sample rate to 8 kHz and 24 kHz when operated in this mode.

The output now sounds right, but may need to have a low impedance load on the amp for it to look right on the scope - the Class D amp makes the output look very noisy on the scope. Oh, with this code I also have to move the mclk/bclk jumper to the mclk position (as a separate mclk is being supplied)

Here are my code changes and a commentary - this is just what I did. It might not be better, just different...

sclk_div.v: Added a MMCM. Set up the clocking as 100 * 7 / 57 = 12.280 MHz.Generated the bclk and lrclk signal in this module, and they are the mclk clock domain (1.535MHz bit clock, 47,971 Hz sample rate - within 0.1% of 48,000Hz).

oscillator.v: Put everything into the mclk clock domain, uses the transition of LRCLK to signal when to advance the waveform, rather than being clocked by directly by LRCLK

i2s_tx.v: Changed to sending 32 bit frames (the high 16 bits of each channel). Moved all logic into the mclk clock domain, and it watches bclk for a transition (rather than being clocked by BCLK). LRCLK is now supplied externally to this module rather than generating within it. There now is a bug as the bit counter is no longer synced with the transitions of LRCLK - it relies on the initial conditions to be correct, which is reasonably poor form on my part!

basys3_abacus_top.v: Added extra plumbing for mclk, lrclk and bclk signals. The sending out of mclk isn't really done right. It should be forwarded out with using a DDR register. Removed unused inputs and outputs - I also removed the unused constraints.

 

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Engineer: JUIXXXE
//////////////////////////////////////////////////////////////////////////////////
module sclk_div(
    input clk,
    output wire mclk,
    output wire bclk,
    output wire lrclk
    );
wire clkfb;
reg [13:0] mclk_count = 14'b0;
MMCME2_BASE #(
      .BANDWIDTH("OPTIMIZED"),   // Jitter programming (OPTIMIZED, HIGH, LOW)
      .CLKFBOUT_MULT_F(7.0),     // Multiply value for all CLKOUT (2.000-64.000).
      .CLKFBOUT_PHASE(0.0),      // Phase offset in degrees of CLKFB (-360.000-360.000).
      .CLKIN1_PERIOD(10.0),       // Input clock period in ns to ps resolution (i.e. 33.333 is 30 MHz).
      // CLKOUT0_DIVIDE - CLKOUT6_DIVIDE: Divide amount for each CLKOUT (1-128)
      .CLKOUT0_DIVIDE_F(57.0),    // Divide amount for CLKOUT0 (1.000-128.000).
      .CLKOUT1_DIVIDE(14),
      .CLKOUT2_DIVIDE(14),
      .CLKOUT3_DIVIDE(14),
      .CLKOUT4_DIVIDE(14),
      .CLKOUT5_DIVIDE(14),
      .CLKOUT6_DIVIDE(14),
      // CLKOUT0_DUTY_CYCLE - CLKOUT6_DUTY_CYCLE: Duty cycle for each CLKOUT (0.01-0.99).
      .CLKOUT0_DUTY_CYCLE(0.5),
      .CLKOUT1_DUTY_CYCLE(0.5),
      .CLKOUT2_DUTY_CYCLE(0.5),
      .CLKOUT3_DUTY_CYCLE(0.5),
      .CLKOUT4_DUTY_CYCLE(0.5),
      .CLKOUT5_DUTY_CYCLE(0.5),
      .CLKOUT6_DUTY_CYCLE(0.5),
      // CLKOUT0_PHASE - CLKOUT6_PHASE: Phase offset for each CLKOUT (-360.000-360.000).
      .CLKOUT0_PHASE(0.0),
      .CLKOUT1_PHASE(0.0),
      .CLKOUT2_PHASE(0.0),
      .CLKOUT3_PHASE(0.0),
      .CLKOUT4_PHASE(0.0),
      .CLKOUT5_PHASE(0.0),
      .CLKOUT6_PHASE(0.0),
      .CLKOUT4_CASCADE("FALSE"), // Cascade CLKOUT4 counter with CLKOUT6 (FALSE, TRUE)
      .DIVCLK_DIVIDE(1),         // Master division value (1-106)
      .REF_JITTER1(0.0),         // Reference input jitter in UI (0.000-0.999).
      .STARTUP_WAIT("FALSE")     // Delays DONE until MMCM is locked (FALSE, TRUE)
   )
MMCME2_BASE_inst (
      .CLKIN1(clk),       // 1-bit input: Clock
      .CLKOUT0(mclk),     // 1-bit output: CLKOUT0
      .CLKFBOUT(clkfb),   // 1-bit output: Feedback clock
      .CLKFBIN(clkfb),      // 1-bit input: Feedback clock
      .PWRDWN(1'b0),       // 1-bit input: Power-down
      .RST(1'b0)             // 1-bit input: Reset
   );
                    
   assign bclk  = mclk_count[2];  // mclk / 8
   assign lrclk = mclk_count[7]; // mclk / 256
                    
always@(posedge(mclk)) begin
    mclk_count = mclk_count + 1;
end
endmodule
 
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Engineer: JUIXXXE
//////////////////////////////////////////////////////////////////////////////////
`define CHANNELDEPTH 32
module oscillator(
    input mclk,
    input lrclk,
    output reg signed [`CHANNELDEPTH-1:0] sawtooth = 0
    );
    
reg lr_last;
always@(posedge(mclk)) begin
    if (lr_last == 1'b0) begin
        if(lrclk == 1'b1) begin 
            sawtooth = sawtooth + 10000000; 
        end
    end
    lr_last <= lrclk; 
end
endmodule
 
/*
 * Redistribution and use in source and non-source forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in non-source form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS WORK IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * WORK, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Modified by JUIXXXE Sept. 2015
 * Modified by JUIXXXE Jan. 2016
 */
`define CHANNELDEPTH 32
`define logCHANNELDEPTH 5
module i2s_tx(
    input            mclk,
    input            bclk,
    input            lrclk,
    output reg        sdata = 1'b0,
    input signed [`CHANNELDEPTH-1:0]    left_chan, 
    input signed [`CHANNELDEPTH-1:0]    right_chan
);
reg lrclk_delayed = 1'b1;
reg [`logCHANNELDEPTH-1:0] bit_cnt = `logCHANNELDEPTH'b0;
reg signed [`CHANNELDEPTH-1:0]        left;
reg signed [`CHANNELDEPTH-1:0]        right;
reg bclk_last = 1'b0;
    
always @(posedge mclk) begin
    //i2s requires the signal be delayed by one bclk cycle from the lr switch
    if(bclk_last == 1'b1 && bclk == 1'b0) begin
        if (bit_cnt == `logCHANNELDEPTH'b0) begin
            lrclk_delayed = ~lrclk_delayed;
            //read in channels at beginning of lr cycle
            if(lrclk_delayed) begin
                left = left_chan;
                right = right_chan;
            end
        end
   
       //assign proper chanel to sdata
        sdata = lrclk_delayed ? right[`CHANNELDEPTH-1 - bit_cnt] : left[`CHANNELDEPTH-1 - bit_cnt];
        //increment bit count
        if(bit_cnt == `logCHANNELDEPTH'b01111) begin
            bit_cnt = `logCHANNELDEPTH'b0;
        end else begin
            bit_cnt = bit_cnt + 1;
        end
    end
    bclk_last = bclk;
end
endmodule

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Engineer: JUIXXXE
//////////////////////////////////////////////////////////////////////////////////
`define CHANNELDEPTH 32
module Basys3_Abacus_Top(
     input clk,          
     output reg SD_2518 = 1'b1,   // Shut down - active low
     output wire bclk_2518,       // bit clock output for 2518 
     output wire lrclk_2518,      // lrclk aka word clock for 2518
     output wire sdata_2518,      // serial data output for 2518    
     output wire mclk_2518        // master clock for 2518
);
wire signed [`CHANNELDEPTH -1:0] sawtooth;
oscillator Osc1(
    .mclk(mclk_2518),
    .lrclk(lrclk_2518),
    .sawtooth(sawtooth)
);
i2s_tx i2stx(
    .bclk(bclk_2518),
    .lrclk(lrclk_2518),
    .mclk(mclk_2518),
    .sdata(sdata_2518),
    .left_chan(sawtooth),
    .right_chan(sawtooth)
);
sclk_div sclkdiv(
    .clk(clk),
    .mclk(mclk_2518),
    .bclk(bclk_2518),
    .lrclk(lrclk_2518)
);
endmodule
Edited by hamster

Share this post


Link to post
Share on other sites
  • 0

hamster, thank you for all your help! I've committed the project to Git at https://github.com/jcloiacon/synth . This is my first commit of this kind, so please lmk if I've screwed something up royally :) . I've made a few minor tweaks : firstly I've reduced the size of the sawtooth register to 16bits for simplicity. I've included the switch inputs so that they can be used later for demonstration / debugging.

But I'm still not convinced this code works properly. I've scoped the output in parallel with my guitar amplifier load (8 ohms I think), and it still looks wonky. Image is attached.

Anyways, I'm dying to get this thing to work. I feel that we are very close. May I invite you to edit the Git?

IMG_003.BMP

Share this post


Link to post
Share on other sites
  • 0

But I'm still not convinced this code works properly. I've scoped the output in parallel with my guitar amplifier load (8 ohms I think), and it still looks wonky.

Do you have the gain jumper set at +12db? As we are sending the full range of data. I think that the maths for +12db is to multiply the samples by 4, so will clip/saturate 3/4ths of the inputs value.

Edited by hamster

Share this post


Link to post
Share on other sites
  • 0

Do you have the gain jumper set at +12db? As we are sending the full range of data. I think that the maths for +12db is to multiply the samples by 4, so will clip/saturate 3/4ths of the inputs value.

Yes, JP6 was originally unloaded (supposedly corresponding to 0dB gain). Interestingly, loading JP6 eliminates most of the clipping, yielding the (still wonky) waveform attached below. I've been playing with the oscillator frequency, and the resultant waveform frequency varies predictably. Also, grounding the oscilloscope voids the signal for some reason? Also, what do you mean when you say "the speaker output needs to be slightly unseated"?

IMG_002.BMP

Share this post


Link to post
Share on other sites
  • 0

 Also, what do you mean when you say "the speaker output needs to be slightly unseated"?

 

When I am using powered USB speakers, inserting the 3.5mm plug all the way makes the output stop. When it is not inserted all the way it makes the correct sound. Insert it all the way and it just ticks. Now that I've moved to an unpowered speaker driver, this behavior goes away.

BOTHER - I've just realized the issue with my scope traces. I just looked at the schematic. The sleeve on the 3.5mm socket isn't GND, it s one side of a push-pull driver (with the tip being the other side).

So I guess I need to connect both probes on the scope, (one to each speaker terminal), ground clip to the board's GND, and then use the MATH function go show A minus B to get the actual trace.

You might be getting lucky as your pocket-sized DSO isn't ground-referenced.

Share this post


Link to post
Share on other sites
  • 0

Note for future readers:

Hamster and I have generated, in all likelihood, fully operational code.

Hamster's VHDL code is explained here: http://hamsterworks.co.nz/mediawiki/index.php/PMODamp3 (down atm)

My Verilog code is here: https://github.com/jcloiacon/synth

And here is a video demonstration of mine: https://www.youtube.com/watch?v=zsd7MRDOyek

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now