前言
信息熵(香农熵)是信息论的核心概念,用于量化离散随机序列的不确定性与信息量大小。在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 核心性质
-
非负性:H(X) \ge 0,仅当某一符号出现概率为1时取等号
-
最大熵定理:所有符号等概率出现时,熵达到最大值 \log_2 N。例如8bit灰度图像的最大熵为8 bit/像素
-
凸函数性:熵是概率分布的上凸函数,存在唯一最大值
-
可加性:多个独立信源的联合熵,等于各信源熵值之和
二、FPGA硬件实现架构详解
FPGA无法直接进行浮点对数、浮点除法运算,因此采用定点数 + 查表法 + 串行迭代的工程实现方案,整体资源占用极低,可适配入门级FPGA器件。
2.1 整体计算流程
采用状态机控制的帧级计算架构,分为4个工作阶段:
-
IDLE 空闲态:等待帧起始信号,复位内部寄存器
-
STAT 统计态:输入离散符号流,实时统计直方图(每个符号出现的频次)与总数据量
-
CALC 计算态:帧结束后,遍历所有符号,依次完成「概率计算 → log2查表 → 乘法 → 累加」
-
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 工程避坑点
-
RAM清零:实际工程中建议增加专门的清零状态,帧起始时遍历RAM地址清零,避免上一帧数据干扰
-
零概率处理:LUT表中已对0地址做特殊处理,避免对数运算溢出
-
时序优化:可在乘法、累加路径插入寄存器打拍,提升系统最高运行频率
六、典型FPGA应用场景
-
图像纹理特征提取:计算图像局部区域熵值,区分路面、障碍物、噪点区域,辅助车道线检测、目标识别
-
信号异常监测:实时监测ADC采样序列的熵值,熵值突变时判定信号出现干扰或故障
-
图像压缩预估:通过整图熵值预判压缩极限,动态调整编码参数
-
真随机数质量校验:实时计算随机数序列熵值,评估随机数发生器的随机性
总结
离散信源熵计算是FPGA数字信号处理的基础通用模块,采用「直方图统计 + 定点查表 + 串行乘加」的架构,在资源消耗极低的前提下,实现了高精度的实时熵值计算,可广泛应用于图像处理、信号检测、数据压缩等场景。
本文提供的全套代码可直接综合上板,参数化设计也方便快速适配不同位宽、不同精度的项目需求。