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
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.