simulationverilogfpgamodulation

Sinusoidal Pulse Width Modulation in FPGA Device - OK in Simulation, Unmodulated in Device


Below is my top-level module:

module spwm(clk, p1, sine_a, tri_out);

input clk;      //16MHz
reg tick = 0;

reg  [7:0] theta_a = 8'd0;
reg  [7:0] theta_tri = 8'd0;
output [8:0] sine_a;
output [9:0] tri_out;

integer div = 42;
integer r = 0;

output p1;
reg p1;


always@(posedge clk) begin
    if(r == div) begin
        tick = ~tick;
        r = 1;
    end
    else begin
        r = r + 1;
    end
end

always@(tick) begin
    theta_a = (theta_a + 8'b1) % 8'd255;
end


always@(posedge clk) begin
    theta_tri = (theta_tri + 8'b1) % 8'd255;
end

//generate spwm
always@(clk) begin
    if ($signed(sine_a) > $signed(tri_out))
        p1 = 1'b1;
    else if ($signed(sine_a) < $signed(tri_out))
        p1 = 1'b0;
                
end

SINE_LUT lut_a(theta_a, sine_a);
TRI_LUT lut_tri(theta_tri, tri_out);
endmodule

My Sine Wave look-up table:

module SINE_LUT (THETA, SINE_OUT);

input  [7:0] THETA;         //Input THETA
output [8:0] SINE_OUT;      //Output SINE_OUT

reg [5:0] THETA_TMP;        //Lower bits of THETA (counting up or counting down)
reg [6:0] THETA_HLP;        //Helper for reversing lower bits of counting direction for theta
reg [8:0] SINE_TMP;         //Temporary holder for output (two's compliment)
reg [8:0] SINE_OUT;         //Output Sine in two's compliment

always @ (THETA) begin                      //Combinatorial Logic Look Up Table

    if (THETA[6:0] == 7'd64) begin          //At 90 degrees and 270 degrees
        SINE_TMP = 9'd255;
    end
    else begin
        if (THETA[6]) begin                 //If counting down should begin reverse counting order
            THETA_HLP = 7'd64 - {1'd0, THETA[5:0]};
            THETA_TMP = {THETA_HLP[5:0]};
        end
        else begin                          //Continue counting up by default
            THETA_TMP = {THETA[5:0]};
        end
        case (THETA_TMP)                    //Look Up Table (quarter wave)
        6'd0: SINE_TMP = 9'd0;
        6'd1: SINE_TMP = 9'd6;
        6'd2: SINE_TMP = 9'd13;
        6'd3: SINE_TMP = 9'd19;
        6'd4: SINE_TMP = 9'd25;
        6'd5: SINE_TMP = 9'd31;
        6'd6: SINE_TMP = 9'd37;
        6'd7: SINE_TMP = 9'd44;
        6'd8: SINE_TMP = 9'd50;
        6'd9: SINE_TMP = 9'd56;
        6'd10: SINE_TMP = 9'd62;
        6'd11: SINE_TMP = 9'd68;
        6'd12: SINE_TMP = 9'd74;
        6'd13: SINE_TMP = 9'd80;
        6'd14: SINE_TMP = 9'd86;
        6'd15: SINE_TMP = 9'd92;
        6'd16: SINE_TMP = 9'd98;
        6'd17: SINE_TMP = 9'd103;
        6'd18: SINE_TMP = 9'd109;
        6'd19: SINE_TMP = 9'd115;
        6'd20: SINE_TMP = 9'd120;
        6'd21: SINE_TMP = 9'd126;
        6'd22: SINE_TMP = 9'd131;
        6'd23: SINE_TMP = 9'd136;
        6'd24: SINE_TMP = 9'd142;
        6'd25: SINE_TMP = 9'd147;
        6'd26: SINE_TMP = 9'd152;
        6'd27: SINE_TMP = 9'd157;
        6'd28: SINE_TMP = 9'd162;
        6'd29: SINE_TMP = 9'd167;
        6'd30: SINE_TMP = 9'd171;
        6'd31: SINE_TMP = 9'd176;
        6'd32: SINE_TMP = 9'd180;
        6'd33: SINE_TMP = 9'd185;
        6'd34: SINE_TMP = 9'd189;
        6'd35: SINE_TMP = 9'd193;
        6'd36: SINE_TMP = 9'd197;
        6'd37: SINE_TMP = 9'd201;
        6'd38: SINE_TMP = 9'd205;
        6'd39: SINE_TMP = 9'd208;
        6'd40: SINE_TMP = 9'd212;
        6'd41: SINE_TMP = 9'd215;
        6'd42: SINE_TMP = 9'd219;
        6'd43: SINE_TMP = 9'd222;
        6'd44: SINE_TMP = 9'd225;
        6'd45: SINE_TMP = 9'd228;
        6'd46: SINE_TMP = 9'd231;
        6'd47: SINE_TMP = 9'd233;
        6'd48: SINE_TMP = 9'd236;
        6'd49: SINE_TMP = 9'd238;
        6'd50: SINE_TMP = 9'd240;
        6'd51: SINE_TMP = 9'd242;
        6'd52: SINE_TMP = 9'd244;
        6'd53: SINE_TMP = 9'd246;
        6'd54: SINE_TMP = 9'd247;
        6'd55: SINE_TMP = 9'd249;
        6'd56: SINE_TMP = 9'd250;
        6'd57: SINE_TMP = 9'd251;
        6'd58: SINE_TMP = 9'd252;
        6'd59: SINE_TMP = 9'd253;
        6'd60: SINE_TMP = 9'd254;
        6'd61: SINE_TMP = 9'd254;
        6'd62: SINE_TMP = 9'd255;
        6'd63: SINE_TMP = 9'd255;

        endcase
    end

    if (THETA > 8'd128) begin               //Any theta between 180 and 360 degrees should be negative
        SINE_OUT = (~SINE_TMP) + 1'd1;      //Create negative value in two's compliment
    end
    else begin                              //Any theta between 0 and 180 degrees should be positive
        SINE_OUT = SINE_TMP;
    end

end

endmodule

I also used lookup table for triangular wave:

module TRI_LUT (THETA, TRI_OUT);

input  [7:0] THETA;         
output [9:0] TRI_OUT;       

reg [5:0] THETA_TMP;        
reg [6:0] THETA_HLP;        
reg [9:0] TRI_TMP;          
reg [9:0] TRI_OUT;          

always @ (THETA) begin          

    if (THETA[6:0] == 7'd64) begin  
        TRI_TMP = 10'd364;
    end
    else begin
        if (THETA[6]) begin             
            THETA_HLP = 7'd64 - {1'd0, THETA[5:0]};
            THETA_TMP = {THETA_HLP[5:0]};
        end
        else begin                  
            THETA_TMP = {THETA[5:0]};
        end
        case (THETA_TMP)            
            6'd0: TRI_TMP = 10'd0;
            6'd1: TRI_TMP = 10'd6;
            6'd2: TRI_TMP = 10'd12;
            6'd3: TRI_TMP = 10'd17;
            6'd4: TRI_TMP = 10'd23;
            6'd5: TRI_TMP = 10'd29;
            6'd6: TRI_TMP = 10'd35;
            6'd7: TRI_TMP = 10'd40;
            6'd8: TRI_TMP = 10'd46;
            6'd9: TRI_TMP = 10'd52;
            6'd10: TRI_TMP = 10'd58;
            6'd11: TRI_TMP = 10'd64;
            6'd12: TRI_TMP = 10'd69;
            6'd13: TRI_TMP = 10'd75;
            6'd14: TRI_TMP = 10'd81;
            6'd15: TRI_TMP = 10'd87;
            6'd16: TRI_TMP = 10'd92;
            6'd17: TRI_TMP = 10'd98;
            6'd18: TRI_TMP = 10'd104;
            6'd19: TRI_TMP = 10'd110;
            6'd20: TRI_TMP = 10'd116;
            6'd21: TRI_TMP = 10'd121;
            6'd22: TRI_TMP = 10'd127;
            6'd23: TRI_TMP = 10'd133;
            6'd24: TRI_TMP = 10'd139;
            6'd25: TRI_TMP = 10'd144;
            6'd26: TRI_TMP = 10'd150;
            6'd27: TRI_TMP = 10'd156;
            6'd28: TRI_TMP = 10'd162;
            6'd29: TRI_TMP = 10'd168;
            6'd30: TRI_TMP = 10'd173;
            6'd31: TRI_TMP = 10'd179;
            6'd32: TRI_TMP = 10'd185;
            6'd33: TRI_TMP = 10'd191;
            6'd34: TRI_TMP = 10'd196;
            6'd35: TRI_TMP = 10'd202;
            6'd36: TRI_TMP = 10'd208;
            6'd37: TRI_TMP = 10'd214;
            6'd38: TRI_TMP = 10'd220;
            6'd39: TRI_TMP = 10'd225;
            6'd40: TRI_TMP = 10'd231;
            6'd41: TRI_TMP = 10'd237;
            6'd42: TRI_TMP = 10'd243;
            6'd43: TRI_TMP = 10'd248;
            6'd44: TRI_TMP = 10'd254;
            6'd45: TRI_TMP = 10'd260;
            6'd46: TRI_TMP = 10'd266;
            6'd47: TRI_TMP = 10'd272;
            6'd48: TRI_TMP = 10'd277;
            6'd49: TRI_TMP = 10'd283;
            6'd50: TRI_TMP = 10'd289;
            6'd51: TRI_TMP = 10'd295;
            6'd52: TRI_TMP = 10'd300;
            6'd53: TRI_TMP = 10'd306;
            6'd54: TRI_TMP = 10'd312;
            6'd55: TRI_TMP = 10'd318;
            6'd56: TRI_TMP = 10'd324;
            6'd57: TRI_TMP = 10'd329;
            6'd58: TRI_TMP = 10'd335;
            6'd59: TRI_TMP = 10'd341;
            6'd60: TRI_TMP = 10'd347;
            6'd61: TRI_TMP = 10'd352;
            6'd62: TRI_TMP = 10'd358;
            6'd63: TRI_TMP = 10'd364;

        endcase
    end

    if (THETA > 8'd128) begin
        TRI_OUT = (~TRI_TMP) + 1'd1;
    end
    else begin                      
        TRI_OUT = TRI_TMP;
    end

end

endmodule

The above SPWM works in ModelSim PE Student Edition 10.3. enter image description here

When loaded in GFEC Max II Starter Kit, the signal p1 is not modulated. Any help. Thanks...


Solution

  • theta_a and p1 are inferring latching logic. This is usually synthesizes to be big, complex, brittle logic. Making them flip-flops should remove your issue.

    always@(posedge clk) begin
        if(r == div) begin
            //tick <= ~tick; // tick can be optimized out
            theta_a <= (theta_a + 8'b1) % 8'd255; // theta_a is flopped
            r <= 1;
        end
        else begin
            r <= r + 1;
        end
    end
    
    always@(posedge clk) begin
        theta_tri <= (theta_tri + 8'b1) % 8'd255;
    end
    
    //generate spwm
    always@(posedge clk) begin // latch to flip-flop
        if ($signed(sine_a) > $signed(tri_out))
            p1 <= 1'b1;
        else if ($signed(sine_a) < $signed(tri_out))
            p1 <= 1'b0;
    end

    FYI: you may also want to check if there are latches in your SINE_LUT and TRI_LUT. If your synthesizer has a decent optimization phase, then THETA_HLP and THETA_TMP will be combinatorial logic. A brute force synthesizer could create latches. To guaranty combinatorial logic, make sure THETA_HLP and THETA_TMP are always assigned a value.

    Convert:

    if (THETA[6:0] == 7'd64) begin
        ...
    end
    else begin
        if (THETA[6]) begin
            THETA_HLP = 7'd64 - {1'd0, THETA[5:0]};
            THETA_TMP = {THETA_HLP[5:0]};
        end
        else begin
            THETA_TMP = {THETA[5:0]};
        end
        case (THETA_TMP)
        ...
        endcase
    end

    to:

    THETA_HLP = 7'd64 - {1'd0, THETA[5:0]};
    THETA_TMP = THETA[6] ? THETA_HLP[5:0] : THETA[6];
    if (THETA[6:0] == 7'd64) begin
        ... 
    end
    else begin
        case (THETA_TMP)
        ...
        endcase
    end

    THETA_HLP and THETA_TMP are continuously assigned combinatorial logic and functionally equivalent to your original.