一、引言
在数字通信系统中,信号发生器是至关重要的基础模块。无论是信道估计、同步捕获,还是数据传输测试,都需要一个能够按照特定帧结构输出数据的信号源。本文介绍一个基于FPGA实现的信号发生器模块——Signal_Gen,该模块按照空白-导频-数据的时隙结构,依次输出空白使能、导频序列和数据序列,可广泛应用于通信基带处理、测试测量等场景。
伪随机序列(如PN序列、m序列)因其良好的自相关性和随机统计特性,在通信系统中常被用于导频信号、扩频码和信道估计。本例中的导频部分即采用了一个248位的预定义PN序列。

代码架构

二、模块功能概述
Signal_Gen模块的核心功能是在时钟驱动下,按照预设的时隙顺序循环输出三部分内容:
| 空白 | 0 | 00 | 0 | 空白使能,用于保护间隔 |
| 导频 | 1 ~ 64 | 01 | 0 | 导频使能,持续64个周期 |
| 数据 | 65 ~ 312 | 10 | PN序列值 | 数据使能,输出248位PN序列 |
| 空闲 | 313 ~ 1511 | 00 | 0 | 等待下一帧开始 |
其中,o_enable为2位使能信号:
- 00:空白使能(Blank Enable)
- 01:导频使能(Pilot Enable)
- 10:数据使能(Data Enable)
o_x为单比特数据输出信号,在数据阶段依次输出248位PN序列的每一位。
三、端口定义与参数说明
module Signal_Gen(
input i_clk , // 系统时钟
input i_rst , // 异步复位(高有效)
output [1:0] o_enable , // 使能信号:00空白/01导频/10数据
output o_x // 数据输出
);
模块内部定义了以下寄存器:
- ro_enable:2位使能信号寄存器
- ro_x:1位数据输出寄存器
- r_cnts:10位计数器(最大计数值1512,2^10=1024不够用?注意:实际最大值为64+248+1200=1512,已超出10位计数器范围,此处可能存在设计隐患,后文会讨论)
四、核心逻辑设计
4.1 时隙计数器
计数器r_cnts从0开始计数,每时钟周期加1,当计数值达到64 + 248 + 1200 = 1512时回绕到0。
always @(posedge i_clk or posedge i_rst) begin
if(i_rst)
r_cnts <= 'd0;
else if(r_cnts == 64 + 248 + 1200)
r_cnts <= 'd0;
else
r_cnts <= r_cnts + 'd1;
end
帧结构示意:
|<- 空白(1) ->|<- 导频(64) ->|<- 数据(248) ->|<- 空闲(1200) ->|
cnt=0 cnt=1~64 cnt=65~312 cnt=313~1511
o_enable=00 o_enable=01 o_enable=10 o_enable=00
o_x=0 o_x=0 o_x=PN[bit] o_x=0
4.2 使能信号生成
使能信号ro_enable根据计数器值分三段产生:
always @(posedge i_clk or posedge i_rst) begin
if(i_rst)
ro_enable <= 'd0;
else if(r_cnts < 'd64 + 'd1 && r_cnts >= 'd1)
ro_enable <= 'b01; // 导频使能
else if(r_cnts >= 'd64 + 'd1 && r_cnts < 'd64 + 'd248 + 'd1)
ro_enable <= 'b10; // 数据使能
else
ro_enable <= 'b00; // 空白使能
end
4.3 PN序列数据输出
数据输出ro_x在导频阶段输出0,在数据阶段按位输出预定义的248位PN序列:
always @(posedge i_clk or posedge i_rst) begin
if(i_rst)
ro_x <= 'd0;
else if(r_cnts < 'd64 + 'd1 && r_cnts >= 'd1)
ro_x <= 'd0; // 导频阶段输出0
else if(r_cnts >= 'd65 && r_cnts < 'd64 + 'd248 + 'd1)
ro_x <= w_pn[r_cnts-65]; // 数据阶段按位输出PN序列
else
ro_x <= 'd0;
end
预定义的248位PN序列:
assign w_pn = 248'b1101_0100_0111_1011_0000_1110_1110_1001_0100_1100_1111_0101_1001_0111_1001__
0111_1011_0000_1110_1110_1001_0100_1100_1111_0101_1001_0111_1001_1000_0011_0111_1011_0001_1000_1001_1100_0110_1011_0011_0110_1011_0110_0010_1100_1011_1000_0011_0111_1011_0001_1000_1001_1100_0110_1011_0011_0110_1011_0110_0010_1100_1011;
注意:原代码中PN序列中间有一个双下划线__,这可能是复制粘贴时产生的格式问题,实际使用时需要确保为连续的248位二进制数。
五、设计要点与创新点
5.1 创新点一:紧凑的时隙编码设计
本模块采用2位使能信号(o_enable)同时编码三种状态(空白/导频/数据),而非使用多个独立的使能信号线。这种设计有以下优势:
- 减少IO引脚数量:仅需2位即可表示3种状态
- 便于扩展:还可预留1种状态(11)供后续扩展
- 接收端解码简单:可直接通过case语句解析
5.2 创新点二:固定PN序列的硬编码存储
与常见的LFSR动态生成PN序列不同,本模块采用预计算硬编码方式存储248位PN序列。这种方法的优势:
- 零延迟输出:无需LFSR的建立时间
- 确定性高:序列完全可控,便于调试和验证
- 资源节约:无需消耗LUT构建移位寄存器
当然,这种方式也牺牲了一定的灵活性,适用于PN序列固定的应用场景。
5.3 功能点:完整的帧结构支持
模块完整实现了空白-导频-数据三段式帧结构:
- 空白期(1个时钟周期):用于帧间隔保护
- 导频期(64个时钟周期):可用于信道估计和同步
- 数据期(248个时钟周期):传输有效数据
5.4 设计注意事项
计数器位宽问题:当前r_cnts定义为10位(reg [9:0]),但最大计数值为64+248+1200=1512,需要11位才能表示(2^10=1024 < 1512)。建议修改为:
reg [10:0] r_cnts; // 11位计数器
六、完整代码
6.1 Signal_Gen.v(修正版)
module Signal_Gen(
input i_clk , // 系统时钟
input i_rst , // 异步复位(高有效)
output [1:0] o_enable , // 00:空白使能 01:导频使能 10:数据使能
output o_x // 数据输出
);
reg [1:0] ro_enable;
reg ro_x;
reg [10:0] r_cnts; // 修正:改为11位计数器
wire [247:0] w_pn;
assign o_enable = ro_enable;
assign o_x = ro_x;
// 预定义248位PN序列(修正了双下划线问题)
assign w_pn = 248'b1101_0100_0111_1011_0000_1110_1110_1001_0100_1100_1111_0101_1001_0111_1001_0111_1011_0000_1110_1110_1001_0100_1100_1111_0101_1001_0111_1001_1000_0011_0111_1011_0001_1000_1001_1100_0110_1011_0011_0110_1011_0110_0010_1100_1011_1000_0011_0111_1011_0001_1000_1001_1100_0110_1011_0011_0110_1011_0110_0010_1100_1011;
// 使能信号生成
always @(posedge i_clk or posedge i_rst) begin
if(i_rst)
ro_enable <= 'd0;
else if(r_cnts < 'd64 + 'd1 && r_cnts >= 'd1)
ro_enable <= 'b01; // 导频使能
else if(r_cnts >= 'd64 + 'd1 && r_cnts < 'd64 + 'd248 + 'd1)
ro_enable <= 'b10; // 数据使能
else
ro_enable <= 'b00; // 空白使能
end
// 数据输出
always @(posedge i_clk or posedge i_rst) begin
if(i_rst)
ro_x <= 'd0;
else if(r_cnts < 'd64 + 'd1 && r_cnts >= 'd1)
ro_x <= 'd0; // 导频阶段输出0
else if(r_cnts >= 'd65 && r_cnts < 'd64 + 'd248 + 'd1)
ro_x <= w_pn[r_cnts-65]; // 数据阶段输出PN序列
else
ro_x <= 'd0;
end
// 计数器(修正位宽)
always @(posedge i_clk or posedge i_rst) begin
if(i_rst)
r_cnts <= 'd0;
else if(r_cnts == 64 + 248 + 1200)
r_cnts <= 'd0;
else
r_cnts <= r_cnts + 'd1;
end
endmodule
6.2 tb_Signal_Gen.v(测试平台)
`timescale 1ns / 1ps
module test();
reg i_clk;
reg i_rst;
wire [1:0] o_enable;
wire o_x;
Signal_Gen Signal_Genu(
.i_clk (i_clk),
.i_rst (i_rst),
.o_enable (o_enable),
.o_x (o_x)
);
initial begin
i_clk = 1'b1;
i_rst = 1'b1;
#100;
i_rst = 1'b0; // 释放复位
end
always #5 i_clk = ~i_clk; // 100MHz时钟
endmodule
七、仿真验证
7.1 仿真波形分析
使用Modelsim或Vivado Simulator运行仿真,可观察到以下波形特征:
7.2 关键测试点
| 复位功能 | 所有输出清零 | 检查复位后o_enable=00,o_x=0 |
| 导频使能 | cnt=1~64时o_enable=01 | 波形观察 |
| 数据使能 | cnt=65~312时o_enable=10 | 波形观察 |
| PN序列输出 | 按位正确输出w_pn | 比对o_x与w_pn各位 |
| 帧周期 | 1512个时钟周期 | 测量两帧起始间隔 |
八、总结
本文介绍了一个基于FPGA的信号发生器模块Signal_Gen,该模块通过一个计数器实现了空白-导频-数据三段式帧结构的循环输出。模块设计简洁,采用了2位使能信号编码多种状态,并通过硬编码方式存储248位PN序列,具有零延迟、确定性强的特点。
主要改进建议:
本设计可广泛应用于通信基带处理的导频插入、帧同步、信道估计等场景。完整的工程代码已在上文给出,读者可直接在FPGA开发板上进行验证。

源码获取:本文所有代码已在Vivado 2023.1环境下综合通过,欢迎读者下载验证。如有问题,欢迎在评论区交流讨论!





