FPGA 二进制数如何变成十进制显示:可背压的逐位 BCD 转换器

将 ADC 计数、测量结果或采样频率显示到数码管时,内部二进制值不能直接作为十进制位使用。这里实现无符号二进制到 packed BCD 的逐位转换:每个十进制位占 4 bit,最低 nibble 为个位,例如十进制 123 对应 12'h123,不是字符串,也不是二进制的 12'd123。

本文只负责数值表示转换,与既有数码管扫描/计数器文章不同:不含动态扫描、位选、段选和按键逻辑,可独立接到数码管或串口格式化链路。

先明确接口与容量

输入为 s_valid/s_ready/s_data,仅在上升沿二者同高时接收一个数。输出同理。W 支持 1~32,D 支持 1~10,并要求 10^D > 2^W-1。例如 W=12 要覆盖 4095,D 至少为 4;D=3 不能静默截掉千位。支持额外前导零位,不支持负数、补码或 ASCII 编码。

本实现刻意串行处理:接收边沿记为 t0,此后进行 W 次逐位迭代,t0 后第 W 个上升沿结束时设置 m_valid。若下游当时已就绪,最早在再下一个上升沿完成输出握手。结果等待期间 m_valid 和 m_data 必须保持,且不接收新输入。输出消费当拍也不同时接新输入,这是简单接口的有意吞吐代价,不是满流水线转换器。

为什么先加 3 再移位

从输入最高位开始,每一轮先检查 BCD 中的每个十进制位:大于等于 5 的位加 3;然后整体左移一位,并把当前二进制最高位放入最低位。加 3 校正抵消二进制 nibble 的 16 进位与十进制的 10 进位差异。最终恰好执行 W 轮,不能多移或少移。最后一轮输出必须取本轮 shifted,而不是非阻塞赋值更新前的旧 digits。

Microchip AN526 收录二进制与 BCD 等数值转换例程,可作问题背景资料。这里没有复制其中汇编或任何 GitHub 实现;本稿代码是单独编写的 RTL 候选,算法正确性仍以独立十进制除余 oracle 的实际 RTL 回归为准。

完整实现

rtl.sv:

systemverilog 复制代码
module bin2bcd #(parameter integer W=12,D=4)(
 input wire clk,rst,input wire s_valid,output wire s_ready,
 input wire [W-1:0] s_data,output reg m_valid,input wire m_ready,
 output reg [4*D-1:0] m_data);
 reg busy;reg[W-1:0] binary;
 reg[4*D-1:0] digits,adjusted,shifted;
 integer remaining,k;reg[63:0] capacity;integer p;
 // This reference supports W=1..32,D=1..10. Guard against truncation.
 initial begin
  capacity=1;for(p=0;p<D;p=p+1)capacity=capacity*10;
  if(W<1||W>32||D<1||D>10||capacity<=(64'd1<<W)-1)
   $fatal(1,"invalid W/D or insufficient decimal digits");
 end
 assign s_ready=!rst&&!busy&&!m_valid;
 always @* begin
  adjusted=digits;
  for(k=0;k<D;k=k+1)if(digits[4*k+:4]>=5)adjusted[4*k+:4]=digits[4*k+:4]+4'd3;
  shifted=(adjusted<<1);shifted[0]=binary[W-1];
 end
 always @(posedge clk)begin
  if(rst)begin busy<=0;binary<=0;digits<=0;remaining<=0;m_valid<=0;m_data<=0;end
  else begin
   if(m_valid&&m_ready)m_valid<=0;
   if(s_valid&&s_ready)begin busy<=1;binary<=s_data;digits<=0;remaining<=W;end
   else if(busy)begin
    digits<=shifted;binary<=binary<<1;remaining<=remaining-1;
    if(remaining==1)begin busy<=0;m_data<=shifted;m_valid<=1;end
   end
  end
 end
endmodule

不重复算法的自检测试台

测试台用 %10 与 /10 构造期望 BCD,不在 oracle 中重做 add-3/shift。对 W≤12 穷举全部输入;更宽参数执行 0~4095 加最大值、0、1,以及 2048 个覆盖高位的确定性 LFSR 序列值;同时检查范围内 10 的幂的前后边界,并不宣称穷举 16/32 bit。固定周期背压验证输出保持;默认 W=12 等多轮转换配置检查运算中复位,W=1 在该测试时序下已结束转换,不宣称覆盖 busy 中复位;输出停顿时复位也丢弃未完成事务。输入发送器在接收前保持 valid 和 data,测试台设超时避免挂死。

tb.sv:

systemverilog 复制代码
`timescale 1ns/1ps
module tb;
 parameter W=12, D=4;
 reg clk=0; always #5 clk=~clk;
 reg rst=1,sv=0,mr=0; reg [W-1:0] sd=0;
 wire sr,mv; wire [4*D-1:0] md;
 bin2bcd #(.W(W),.D(D)) dut(clk,rst,sv,sr,sd,mv,mr,md);
 reg [4*D-1:0] expected[0:20000]; integer wr=0,rd=0,cycles=0,j;
 reg [31:0] rng=32'h13579bdf; reg [63:0] power10;
 reg stalled=0; reg [4*D-1:0] held;
 function automatic [4*D-1:0] decimal_digits(input reg [W-1:0] v);
  reg [63:0] n; integer k;
  begin n=v; decimal_digits=0; for(k=0;k<D;k=k+1) begin decimal_digits[4*k+:4]=n%10; n=n/10; end end
 endfunction
 always @(posedge clk) begin
  if(rst) begin wr=0;rd=0;stalled=0;end
  else begin
   cycles=cycles+1;
   if(stalled && (!mv || md!==held)) $fatal(1,"output changed under backpressure");
   if(sv&&sr) begin expected[wr]=decimal_digits(sd);wr=wr+1;end
   if(mv&&mr) begin
    if(rd>=wr || md!==expected[rd]) $fatal(1,"BCD mismatch rd=%0d got=%h expected=%h",rd,md,expected[rd]);
    rd=rd+1;
   end
   stalled=mv&&!mr; held=md;
  end
 end
 task automatic reset_dut;
  begin @(negedge clk);rst=1;sv=0;mr=0;repeat(2) @(negedge clk);rst=0;end
 endtask
 task automatic send(input reg [W-1:0] n);
  begin @(negedge clk);sv=1;sd=n; do @(posedge clk); while(!sr); @(negedge clk);sv=0;end
 endtask
 // Deterministic backpressure, including runs of consecutive stalls.
 reg drive_ready=0; integer phase=0;
 always @(negedge clk) if(drive_ready) begin phase=phase+1;mr=(phase%13)>=5;end
 initial begin
  reset_dut();drive_ready=1;
  for(j=0;j<(W<=12?(1<<W):4096);j=j+1) send(j);
  send({W{1'b1}});send(0);send(1);
  if(W>12)begin
   for(j=0;j<2048;j=j+1)begin
    rng={rng[30:0],rng[31]^rng[21]^rng[1]^rng[0]};send(rng);
   end
  end
  power10=10;
  while(power10<=(64'd1<<W)-1)begin
   send(power10-1);send(power10);send(power10+1);power10=power10*10;
  end
  wait(rd==wr);@(negedge clk);drive_ready=0;mr=0;
  // Abort work; no result from the aborted transaction may leak after reset.
  send({W{1'b1}});reset_dut();drive_ready=1;send(7);wait(rd==wr);
  @(negedge clk);drive_ready=0;mr=0;
  send({W{1'b1}});wait(mv);repeat(4) @(negedge clk);reset_dut();
  drive_ready=1;send(0);wait(rd==wr);repeat(5) @(negedge clk);
  $display("PASS bin2bcd W=%0d D=%0d cycles=%0d",W,D,cycles);$finish;
 end
 initial begin #10000000;$fatal(1,"timeout");end
endmodule

接入数码管/串口的步骤

  1. 根据最大显示量选择 W、D,并先验证容量检查。
  2. 上游保存待转换数据,置 valid,握手后再撤销;不要只给一拍脉冲却忽视 ready。
  3. 下游仅在真正能存下整份 BCD 时给 ready;拿到结果后再从个位开始做译码,不把每个 nibble 当 ASCII。
  4. 复位中止当前转换;若业务不能丢结果,由上层记录事务并重发。
  5. 仿真完成后另做综合、时序与引脚约束检查。串行迭代不等于已经证明资源占用更低或频率更高。

当前验证边界与复现环境

本稿是待实测的工程候选稿,不能按已验证文章发布 。2026-10-27 云工作区仅完成文件准备和工具可用性检查;未发现 Icarus Verilog、Verilator 或 Vivado xsim。运行批量脚本得到 BLOCKED: iverilog not installed; no RTL executed。因此没有 RTL 编译成功、波形、仿真 PASS、综合资源、时序收敛或上板结果;也没有用 Python 结果代替 RTL 仿真。

代码使用 SystemVerilog,时钟为测试台中的 10 ns 周期,同步高有效复位。实际工程应替换为自己的时钟约束和复位同步方案。下列运行命令是待执行步骤,不代表工具已经运行。前置条件为已合法安装并可执行的 Icarus Verilog(支持 -g2012)及 vvp,本次不安装或购买软件。

在附件目录执行:

bash 复制代码
bash run.sh

脚本为每次运行创建独立 runs/run-* 目录,保存工具版本、编译输出和运行输出,避免覆盖旧证据,并要求进程成功且日志含 PASS。还需要人工检查日志没有 fatal/error、核对执行文件与源码哈希、进行独立复核。源码内参数范围检查供仿真提前暴露配置错误,不替代目标综合器的可综合性检查。

若已有 Vivado,可使用官方三阶段流程(在对应子目录执行,默认参数):

text 复制代码
xvlog -sv rtl.sv tb.sv
xelab work.tb -s tb_snapshot
xsim tb_snapshot -R

以上流程依据 AMD UG900:xelab、xvhdl、xvlog 和 xsim 命令选项。实际版本与日志待补,不据此宣称厂商工具已执行。

下一步验证和相关实践

待补证据:完整参数矩阵日志、非法 D 的预期失败、独立复核结论、实际使用版本。尤其应注入"阈值改为 >5""最后一轮取旧 digits""背压仍清 valid"等故障,确认测试确实能失败。未执行故障注入,不能宣称覆盖有效。

可继续阅读:逐位恢复整数平方根、ready/valid 握手与背压。你的显示刷新频率和数据产生频率分别是多少?是否允许一个结果等待时阻塞新输入,还是需要再加独立输入队列?

相关推荐
匠行科技1 小时前
基于VPX架构的4路10GSPS ADC信号采集处理板
fpga开发
幻智星科技1 天前
蒙哥马利模乘FPGA优化实战
fpga开发·密码学
星夜离尘561 天前
FPGA 多通道 DAC 目标码影子寄存器:成组提交、同拍写入与快照边界
fpga开发
t_niu_niu1 天前
基于多帧累加平均的图像传感器暗电流抑制方法与 FPGA 实现
fpga开发·ram·fpn·固定模式噪声·image sensor
幻智星科技1 天前
FPGA纯硬件UDP桥接:微秒级响应新范式
c++·fpga开发·硬件架构
小蘇的FPGA1 天前
基于FPGA实现PCIe H2C过程中TAG的一种管理方法
fpga开发
威嵌神州1 天前
一台加固平板,能替掉几台现场设备?
嵌入式硬件·fpga开发·电脑·平板
幻智星科技2 天前
树形架构破解FPGA扇出瓶颈
fpga开发·硬件架构
FPGA小迷弟2 天前
FPGA开发Vivado Block Design/Vitis/SDK/xlinx/开发教程,保姆级别,一看就会!
网络协议·tcp/ip·fpga开发·verilog·fpga