【FPGA数字信号处理】离散信源香农熵计算:原理详解 + 纯Verilog可综合实现 + 全套仿真代码

前言

信息熵(香农熵)是信息论的核心概念,用于量化离散随机序列的不确定性与信息量大小。在FPGA图像处理、ADC信号监测、数据压缩、真随机数校验等工程场景中,经常需要通过硬件电路实时计算序列熵值,辅助完成纹理判断、异常检测、压缩率预估等功能。

本文从数学原理出发,拆解FPGA硬件实现架构,提供全套可综合Verilog代码与仿真Testbench,开箱即用,可直接接入图像流水线、信号采集系统,实现帧级实时熵值计算。

一、离散信源熵的数学基础

1.1 核心定义公式

对于离散无记忆信源,符号集合为 X=\{x_1,x_2,...,x_N\},每个符号独立出现的概率为 p(x_i),且满足 \sum_{i=1}^N p(x_i)=1,则该信源的信息熵定义为:

H(X) = -\sum_{i=1}^{N} p(x_i) \cdot \log_2 p(x_i)

  • 单位:比特/符号(bit/symbol)

  • 约定:当 p(x_i)=0 时,0 \cdot \log_2 0 = 0,不参与求和计算

1.2 物理意义

  • 熵值越大:符号分布越均匀,序列随机性越强,包含的信息量越高,数据压缩难度越大

  • 熵值越小:符号分布越集中,序列规律性越强,冗余度越高,数据压缩潜力越大

  • 极端情况:序列中所有数据完全相同,熵值为0,不携带任何新信息量

1.3 核心性质

  1. 非负性:H(X) \ge 0,仅当某一符号出现概率为1时取等号

  2. 最大熵定理:所有符号等概率出现时,熵达到最大值 \log_2 N。例如8bit灰度图像的最大熵为8 bit/像素

  3. 凸函数性:熵是概率分布的上凸函数,存在唯一最大值

  4. 可加性:多个独立信源的联合熵,等于各信源熵值之和

二、FPGA硬件实现架构详解

FPGA无法直接进行浮点对数、浮点除法运算,因此采用定点数 + 查表法 + 串行迭代的工程实现方案,整体资源占用极低,可适配入门级FPGA器件。

2.1 整体计算流程

采用状态机控制的帧级计算架构,分为4个工作阶段:

  1. IDLE 空闲态:等待帧起始信号,复位内部寄存器

  2. STAT 统计态:输入离散符号流,实时统计直方图(每个符号出现的频次)与总数据量

  3. CALC 计算态:帧结束后,遍历所有符号,依次完成「概率计算 → log2查表 → 乘法 → 累加」

  4. DONE 输出态:输出最终熵值与有效标志,回到空闲态等待下一帧

2.2 模块拆分与功能

模块名称 核心功能 实现方式

直方图统计单元 统计每个符号的出现频次 双口BRAM构建计数表,输入有效时对应地址自动+1

串行除法器 计算符号出现概率 p = 频次/总数 移位相减法实现定点除法,不占用DSP资源

log2查表ROM 计算概率的对数值 预计算结果存入ROM,输入概率1拍输出结果

乘加累加单元 完成 与求和 调用DSP乘法器 + 累加寄存器

2.3 定点数精度方案

全链路采用定点数设计,兼顾精度与资源:

  • 概率值:Q12格式(小数位12位)

  • log2结果:Q12格式(含符号位,支持负数)

  • 最终熵值输出:Q8格式,实际物理值 = 输出数值 / 256

三、完整可综合Verilog代码

3.1 顶层模块 entropy_calculator.v

verilog

// 离散信源香农熵计算顶层模块

// 公式:H(X) = -Σ p(xi) * log2(p(xi))

module entropy_calculator #(

parameter DATA_WIDTH = 8, // 输入符号位宽,默认8bit(256种符号)

parameter CNT_WIDTH = 20, // 像素计数器位宽,支持单帧最大104万像素

parameter PROB_Q = 12, // 概率定点数小数位数 Q12

parameter LOG_Q = 12, // log2结果小数位数 Q12

parameter ENTROPY_Q = 8 // 熵值输出小数位数 Q8

)(

input clk,

input rst_n,

// 输入符号流接口

input DATA_WIDTH-1:0 din, // 输入离散符号

input din_vld, // 输入有效信号

input frame_start,// 帧起始:复位统计

input frame_end, // 帧结束:启动熵计算

// 熵值输出接口

output reg 15:0 entropy_out,// 熵值 Q8 格式

output reg entropy_vld // 输出有效标志

);

// 状态机定义

localparam IDLE = 2'd0;

localparam STAT = 2'd1;

localparam CALC = 2'd2;

localparam DONE = 2'd3;

reg 1:0 curr_state;

reg 1:0 next_state;

// 内部信号

reg DATA_WIDTH-1:0 addr_cnt;

reg CNT_WIDTH-1:0 total_cnt;

reg CNT_WIDTH-1:0 curr_cnt;

reg PROB_Q-1:0 prob_fixed;

reg signed LOG_Q:0 log2_val;

reg signed PROB_Q+LOG_Q:0 mult_val;

reg signed PROB_Q+LOG_Q+8:0 acc_sum;

reg div_start;

wire div_done;

//--------------------------

// 1. 直方图双口RAM

//--------------------------

reg CNT_WIDTH-1:0 hist_ram 0:2\*\*DATA_WIDTH-1;

reg CNT_WIDTH-1:0 ram_dout;

// 读端口

always @(posedge clk) begin

if(curr_state == CALC)

ram_dout <= hist_ramaddr_cnt;

else

ram_dout <= 'd0;

end

// 写端口:统计阶段计数

always @(posedge clk) begin

if(curr_state == STAT && din_vld) begin

hist_ramdin <= hist_ramdin + 1'b1;

end

end

//--------------------------

// 2. 总像素数统计

//--------------------------

always @(posedge clk or negedge rst_n) begin

if(!rst_n)

total_cnt <= 'd0;

else if(frame_start)

total_cnt <= 'd0;

else if(curr_state == STAT && din_vld)

total_cnt <= total_cnt + 1'b1;

end

//--------------------------

// 3. 串行除法器

//--------------------------

div_serial #(

.DIVIDEND_WIDTH(CNT_WIDTH + PROB_Q),

.DIVISOR_WIDTH(CNT_WIDTH),

.QUOTIENT_WIDTH(PROB_Q)

) u_div (

.clk(clk),

.rst_n(rst_n),

.start(div_start),

.dividend({curr_cnt, {PROB_Q{1'b0}}}),

.divisor(total_cnt),

.quotient(prob_fixed),

.done(div_done)

);

//--------------------------

// 4. log2查表ROM

//--------------------------

log2_lut #(

.ADDR_WIDTH(PROB_Q),

.DATA_WIDTH(LOG_Q)

) u_log2_lut (

.clk(clk),

.addr(prob_fixed),

.dout(log2_val)

);

//--------------------------

// 5. 状态机主体

//--------------------------

always @(posedge clk or negedge rst_n) begin

if(!rst_n)

curr_state <= IDLE;

else

curr_state <= next_state;

end

always @(*) begin

case(curr_state)

IDLE: next_state = frame_start ? STAT : IDLE;

STAT: next_state = frame_end ? CALC : STAT;

CALC: next_state = (addr_cnt == {DATA_WIDTH{1'b1}} && div_done) ? DONE : CALC;

DONE: next_state = IDLE;

default: next_state = IDLE;

endcase

end

// 地址计数器与除法启动控制

always @(posedge clk or negedge rst_n) begin

if(!rst_n) begin

addr_cnt <= 'd0;

div_start <= 1'b0;

curr_cnt <= 'd0;

end

else if(curr_state == CALC) begin

if(div_done) begin

addr_cnt <= addr_cnt + 1'b1;

curr_cnt <= ram_dout;

div_start <= 1'b1;

end

else begin

div_start <= 1'b0;

end

end

else begin

addr_cnt <= 'd0;

div_start <= 1'b0;

curr_cnt <= 'd0;

end

end

//--------------------------

// 6. 乘加流水线

//--------------------------

always @(posedge clk) begin

if(div_done) begin

mult_val <= $signed({1'b0, prob_fixed}) * log2_val;

end

end

always @(posedge clk or negedge rst_n) begin

if(!rst_n)

acc_sum <= 'd0;

else if(frame_start)

acc_sum <= 'd0;

else if(div_done)

acc_sum <= acc_sum + mult_val;

end

//--------------------------

// 7. 结果输出

//--------------------------

always @(posedge clk or negedge rst_n) begin

if(!rst_n) begin

entropy_out <= 'd0;

entropy_vld <= 1'b0;

end

else if(curr_state == DONE) begin

entropy_out <= -acc_sumPROB_Q+LOG_Q+8:PROB_Q+LOG_Q-ENTROPY_Q;

entropy_vld <= 1'b1;

end

else begin

entropy_vld <= 1'b0;

end

end

endmodule

3.2 串行除法器 div_serial.v

verilog

// 串行移位除法器(无符号)

module div_serial #(

parameter DIVIDEND_WIDTH = 32,

parameter DIVISOR_WIDTH = 20,

parameter QUOTIENT_WIDTH = 12

)(

input clk,

input rst_n,

input start,

input DIVIDEND_WIDTH-1:0 dividend,

input DIVISOR_WIDTH-1:0 divisor,

output reg QUOTIENT_WIDTH-1:0 quotient,

output reg done

);

reg QUOTIENT_WIDTH-1:0 cnt;

reg DIVIDEND_WIDTH-1:0 rem_reg;

reg working;

always @(posedge clk or negedge rst_n) begin

if(!rst_n) begin

quotient <= 'd0;

rem_reg <= 'd0;

cnt <= 'd0;

done <= 1'b0;

working <= 1'b0;

end

else if(start) begin

rem_reg <= dividend;

cnt <= 'd0;

done <= 1'b0;

working <= 1'b1;

end

else if(working) begin

if(cnt == QUOTIENT_WIDTH - 1'b1) begin

done <= 1'b1;

working <= 1'b0;

end

cnt <= cnt + 1'b1;

if(rem_reg >= ({divisor, {QUOTIENT_WIDTH{1'b0}}} >> cnt)) begin

rem_reg <= rem_reg - ({divisor, {QUOTIENT_WIDTH{1'b0}}} >> cnt);

quotientQUOTIENT_WIDTH-1-cnt <= 1'b1;

end

else begin

quotientQUOTIENT_WIDTH-1-cnt <= 1'b0;

end

end

else begin

done <= 1'b0;

end

end

endmodule

3.3 log2查表模块 log2_lut.v

verilog

// log2(p) 查表ROM

// 输入Q12无符号概率,输出Q12有符号log2结果

module log2_lut #(

parameter ADDR_WIDTH = 12,

parameter DATA_WIDTH = 12

)(

input clk,

input ADDR_WIDTH-1:0 addr,

output reg signed DATA_WIDTH:0 dout

);

reg signed DATA_WIDTH:0 lut 0:2\*\*ADDR_WIDTH-1;

// 编译时自动初始化LUT表

initial begin

integer i;

real p_val, log2_val;

for(i = 0; i < 2**ADDR_WIDTH; i = i + 1) begin

if(i == 0) begin

luti = 0;

end

else begin

p_val = i / (2.0**ADDR_WIDTH);

log2_val = log10(p_val) / log10(2.0);

luti = $rtoi(log2_val * (2.0**DATA_WIDTH));

end

end

end

always @(posedge clk) begin

dout <= lutaddr;

end

endmodule

四、仿真Testbench与结果验证

4.1 仿真测试代码

verilog

`timescale 1ns / 1ps

module tb_entropy;

reg clk;

reg rst_n;

reg 7:0 din;

reg din_vld;

reg frame_start;

reg frame_end;

wire 15:0 entropy_out;

wire entropy_vld;

entropy_calculator u_entropy (

.clk(clk),

.rst_n(rst_n),

.din(din),

.din_vld(din_vld),

.frame_start(frame_start),

.frame_end(frame_end),

.entropy_out(entropy_out),

.entropy_vld(entropy_vld)

);

// 50MHz时钟

initial begin

clk = 0;

forever #10 clk = ~clk;

end

initial begin

rst_n = 0;

din = 0;

din_vld = 0;

frame_start = 0;

frame_end = 0;

#20 rst_n = 1;

// 启动一帧

#20 frame_start = 1;

#20 frame_start = 0;

// 输入2560个随机8bit数据

repeat(2560) begin

@(posedge clk);

din = $random % 256;

din_vld = 1;

end

// 帧结束,启动计算

@(posedge clk);

din_vld = 0;

frame_end = 1;

#20 frame_end = 0;

// 等待计算完成并打印结果

wait(entropy_vld);

$display("========================================");

$display("熵值计算完成");

$display("Q8格式输出值:%d", entropy_out);

$display("实际熵值:%f bit/符号", entropy_out / 256.0);

$display("========================================");

#100 $stop;

end

endmodule

4.2 结果说明

对于完全随机的8bit序列,理论最大熵为8 bit/符号。仿真输出结果应在7.9~8.0区间内,误差来自定点数截断,完全满足工程精度要求。

五、工程使用与优化指南

5.1 系统接入方法

  • 图像处理场景:直接挂载在灰度化模块输出端, din 接8bit灰度像素,帧起止信号同步VGA/HDMI的场同步信号

  • 信号采集场景:对接ADC量化输出, din_vld 同步采样有效信号,可按固定采样点数触发一次熵值计算

5.2 精度与资源权衡

  • 追求高精度:增大 PROB_Q 、 LOG_Q 参数,同时扩大LUT表深度与乘法器位宽

  • 追求低资源:降低到Q10精度,二值化等小位宽场景可直接用寄存器替代BRAM

5.3 工程避坑点

  1. RAM清零:实际工程中建议增加专门的清零状态,帧起始时遍历RAM地址清零,避免上一帧数据干扰

  2. 零概率处理:LUT表中已对0地址做特殊处理,避免对数运算溢出

  3. 时序优化:可在乘法、累加路径插入寄存器打拍,提升系统最高运行频率

六、典型FPGA应用场景

  1. 图像纹理特征提取:计算图像局部区域熵值,区分路面、障碍物、噪点区域,辅助车道线检测、目标识别

  2. 信号异常监测:实时监测ADC采样序列的熵值,熵值突变时判定信号出现干扰或故障

  3. 图像压缩预估:通过整图熵值预判压缩极限,动态调整编码参数

  4. 真随机数质量校验:实时计算随机数序列熵值,评估随机数发生器的随机性

总结

离散信源熵计算是FPGA数字信号处理的基础通用模块,采用「直方图统计 + 定点查表 + 串行乘加」的架构,在资源消耗极低的前提下,实现了高精度的实时熵值计算,可广泛应用于图像处理、信号检测、数据压缩等场景。

本文提供的全套代码可直接综合上板,参数化设计也方便快速适配不同位宽、不同精度的项目需求。

相关推荐
FPGA小迷弟2 天前
FPGA开发Vivado Block Design/Vitis/SDK/xlinx/开发教程,保姆级别,一看就会!
网络协议·tcp/ip·fpga开发·verilog·fpga
FPGA小迷弟2 天前
基于FPGA实现智能网卡的概念,什么是智能网卡
网络协议·tcp/ip·fpga开发·verilog·fpga
hi冻鱼3 天前
PCIe + DDR架构(一):实测——块长从4 KiB 换 1 MiB,带宽从133 MB/s 涨到 3179 MB/s
性能测试·fpga·pcie·mig·xdma·1024程序员节·ddr3
FPGA小迷弟3 天前
用 Claude Code 重构遗留系统:从评估到落地的完整指南
fpga开发·dsp开发·fpga·1024程序员节·sdr·无线电
十@八九4 天前
VS Code 接入 AMD Vivado 2026.1 AI 助手:从 VSIX 到 MCP 实战
人工智能·fpga·vs code·vivado·mcp
Enclustra_China4 天前
FPGA设计的隐藏难题:算力、功耗与散热如何平衡?
fpga开发·fpga·fpga解决方案
泛联新安11 天前
VHawk-CDC:FPGA跨时钟域检查怎么选?
fpga·亚稳态·fpga验证
星夜离尘5613 天前
流水线 valid 对齐自检:输入气泡和中途复位怎么测
fpga·systemverilog·流水线·vivado·testbench
星夜离尘5614 天前
FPGA实现CRC-8/SMBUS:从参数和位序核对到1041次自检
fpga开发·verilog·fpga·crc