verilogfpgasynthesisregister-transfer-level

Binary - BCD convertor works in sim, but does not work on FPGA


I defined a binary to BCD converter to use on a Basys 3 development board. In simulation, the results are as expected, and it follows the timing exactly.

I included the BCD converter in a top module, where I start the conversion process using an input pulse, created through another module.

On board, the results are strange, as most of the input values give complete zeros, the only exception being 1, which shows a BCD value of 15|15|15|14.

Because the problem is only present on-board, I believe there is something wrong with the synthesis of the modules, but I have not been able to find out.

Synthesis gives me the following warnings:

[Synth 8-567] referenced signal 'i' should be on the sensitivity list ["C:/Xilinx/VivadoProjects/BCD/BCD.srcs/sources_1/new/Binary2BCDTranscoder.v":80]

[Synth 8-567] referenced signal 'j' should be on the sensitivity list ["C:/Xilinx/VivadoProjects/BCD/BCD.srcs/sources_1/new/Binary2BCDTranscoder.v":80]

[Synth 8-567] referenced signal 'state' should be on the sensitivity list ["C:/Xilinx/VivadoProjects/BCD/BCD.srcs/sources_1/new/Binary2BCDTranscoder.v":150]

[Synth 8-567] referenced signal 'iEn' should be on the sensitivity list ["C:/Xilinx/VivadoProjects/BCD/BCD.srcs/sources_1/new/Binary2BCDTranscoder.v":150]

[Synth 8-567] referenced signal 'inner_in' should be on the sensitivity list ["C:/Xilinx/VivadoProjects/BCD/BCD.srcs/sources_1/new/Binary2BCDTranscoder.v":150]

[Synth 8-567] referenced signal 'jEn' should be on the sensitivity list ["C:/Xilinx/VivadoProjects/BCD/BCD.srcs/sources_1/new/Binary2BCDTranscoder.v":150]

[Synth 8-327] inferring latch for variable 'inner_out_reg' ["C:/Xilinx/VivadoProjects/BCD/BCD.srcs/sources_1/new/Binary2BCDTranscoder.v":155]

[Synth 8-7080] Parallel synthesis criteria is not met

The following is the BCD converter code:

`timescale 1ns / 1ps

module Binary2BCDTranscoder #(parameter INPUT_SIZE = 12, parameter OUTPUT_DIGITS = 4, parameter ICNTR_SIZE = 4, parameter JCNTR_SIZE = 2) (
        input clk,
        input rst,
        input convStart,
        input [INPUT_SIZE-1:0] data,
        output reg convDone,
        output reg [4 * OUTPUT_DIGITS - 1:0] convOut 
    );
    
    localparam WAIT = 2'b00;
    localparam CONV_CHK = 2'b01;
    localparam FINISHED = 2'b11;
    
    reg iRst, jRst, iEn, jEn;
    wire [ICNTR_SIZE-1:0] i;
    wire [JCNTR_SIZE-1:0] j;
    
    reg [1:0] state, next_state;
    reg [INPUT_SIZE-1:0] inner_in;
    reg [4 * OUTPUT_DIGITS - 1:0] inner_out;
    
    always @(posedge clk) begin
    
        if(!rst) state <= WAIT;
        
        else state <= next_state;
    
    end
    
    always @(posedge clk) begin
    
        if(!rst) inner_in <= 0;
    
        else if(convDone) inner_in <= data;
        
    end
    
    always @(posedge convDone) begin
    
        convOut <= inner_out;
    
    end
    
    counter #(.SIZE(ICNTR_SIZE)) iCounter (
        .clk(clk),
        .rst(iRst),
        .en(iEn),
        .out(i)
    );
    
    counter #(.SIZE(JCNTR_SIZE)) jCounter (
        .clk(clk),
        .rst(jRst),
        .en(jEn),
        .out(j)
    );
        
    always @(state, convStart) begin
    
        case(state)
        
            WAIT: begin
            
                convDone = 1;
                iRst = 0;
                iEn = 0;
                jRst = 0;
                jEn = 0;
                next_state = convStart ? CONV_CHK : WAIT;
            
            end
            
            CONV_CHK: begin
                
                if(i == INPUT_SIZE) begin
                    convDone = 1;
                    next_state = FINISHED;
                    iRst = 0;
                    jRst = 0;
                    iEn = 0;
                    jEn = 0;
                end
                
                else if(j == OUTPUT_DIGITS-1) begin
                    next_state = CONV_CHK;
                    convDone = 0;
                    iRst = 1;
                    jRst = 0;
                    iEn = 1;
                    jEn = 0;
                end
                
                else begin
                    next_state = CONV_CHK;
                    convDone = 0;
                    iRst = 1;
                    jRst = 1;
                    iEn = 0;
                    jEn = 1;
                end
            
            end
            
            FINISHED: begin
            
                next_state = WAIT;
                convDone = 1;
                iRst = 0;
                iEn = 0;
                jRst = 0;
                jEn = 0;
                
            end
            
            default: begin
                next_state = WAIT;
                convDone = 1;
                iRst = 0;
                iEn = 0;
                jRst = 0;
                jEn = 0;
            end
        
        endcase
    
    end
    
    always @(i,j) begin
    
        case(state)
        
            WAIT: begin
                inner_out = 0;
            end
        
            CONV_CHK: begin
                if(iEn) begin
                    inner_out = {inner_out[4*OUTPUT_DIGITS - 2 : 0], inner_in[INPUT_SIZE - 1 - i]};
                end
                
                else if(jEn) begin
                    inner_out = inner_out;
                    if(inner_out[4*j+:4] >= 5) inner_out[4*j+:4] = inner_out[4*j+:4] + 3;
                end
                
                else begin
                    inner_out = inner_out;
                end
            end
            
            FINISHED: begin
                inner_out = 0;
            end
        
            default: begin
                inner_out = 0;
            end 
        
        
        endcase
    
    end
endmodule

The top module is the following:

module top(
        input clk,
        input rst,
        input convStart,
        input [11:0] data,
        output convDone,
        output [15:0] convOut 
    );

    wire convPulse;
    wire [9:0] clkDivOut;
    
    counter #(.SIZE(10)) CLK_DIVIDER(
        .clk(clk),
        .rst(!rst),
        .en(1'b1),
        .out(clkDivOut)
    );
    
    pulseCreator #(.NUM_BITS(2)) convStartPulse (
        .clk(clkDivOut[9]),
        .in(convStart),
        .out(convPulse),
        .regOutput()
    );
    
    Binary2BCDTranscoder #(.INPUT_SIZE(12),.OUTPUT_DIGITS(4),.ICNTR_SIZE(4),.JCNTR_SIZE(2)) B2BCD (
        .clk(clkDivOut[9]),
        .rst(!rst),
        .convStart(convPulse),
        .data(data),
        .convDone(convDone),
        .convOut(convOut)
    );
    
endmodule

For completeness sake, here is the code for the counter and the pulse generator:

module counter #(parameter SIZE = 4) (
        input clk,
        input rst,
        input en,
        output reg [SIZE-1:0] out
    );
    
    always @(posedge clk) begin
    
        if(!rst)
            out <= 0;
        else
            if (en) out <= out + 1;
    
    end
    
endmodule

module serialRegister #(SIZE = 4)(
        input clk,
        input rst,
        input in,
        input en,
        output reg [SIZE-1:0] out
    );
    
    always @(posedge clk, negedge rst) begin
    
        if(!rst) begin
        
            out <= 0;
        
        end
        
        else begin
        
            if(en) out <= {out[SIZE-2:0],in};
            
        end

    end
    
endmodule

module pulseCreator #(NUM_BITS=3) (
        input clk,
        input in,
        output out,
        output [NUM_BITS-1:0] regOutput
    );
    
    //wire [NUM_BITS-1:0] regOutput;
    
    serialRegister #(.SIZE(NUM_BITS)) pulseReg(
        .clk(clk),
        .rst(in),
        .en(in),
        .in(in),
        .out(regOutput)
    );
    
    assign out = in & !regOutput[NUM_BITS-1];

endmodule

Solution

  • One potential synthesis issue I see is the sensitivity list in Binary2BCDTranscoder:

    always @(state, convStart) begin
    

    For combinational logic, the list should include all signals which change in the always block. However, the list is missing i and j.

    It is good coding practice to use the implicit sensitivity list, which will automatically include all the necessary signals (and only the necessary signals):

    always @* begin
    

    If you enable SystemVerilog features in your tools, use:

    always_comb begin
    

    This better conveys the design intent and implicitly enables further checks.

    The same is true for:

    always @(i,j) begin
    

    Here is another potential issue:

    always @(posedge convDone) begin
    

    convDone is the output of combinational logic, which means it could have glitches. It is good practice to only use sequential logic outputs for clock signals. Or, better yet, use an edge detector to determine when that signal goes high, based on clk:

    always @(posedge clk) begin
        if (convDone) ...
    

    With a design this small, there is no need for 2 clock domains; just all logic depend on the one clock: clk.


    Look in your synthesis log files for any warning or error messages.