1. 相控阵系统开发实战:从FPGA代码到波束控制
在雷达和无线通信领域,相控阵技术正变得越来越重要。作为一名长期从事FPGA开发的工程师,我最近完成了一个相控阵系统的核心代码实现,今天就来分享其中的技术细节和实战经验。这个系统包含串口通信、角度解算、Flash存储管理以及SPI设备驱动等关键功能模块,全部基于Verilog HDL在FPGA上实现。
相控阵系统的核心在于通过精确控制每个天线单元的相位来实现波束的快速扫描和定向。与传统的机械扫描天线相比,这种电子扫描方式具有响应速度快、波束控制灵活等显著优势。但实现这些优势的背后,是一系列复杂的数字逻辑设计和信号处理算法。
2. 系统架构与核心模块设计
2.1 整体系统架构
我们的相控阵系统采用分层设计,顶层架构如下图所示:
code复制[系统架构示意图]
相控阵控制器(FPGA)
├── 通信接口层
│ ├── UART串口
│ └── SPI接口
├── 数据处理层
│ ├── 角度解算模块
│ └── 波控参数计算
└── 存储管理层
├── Flash控制器
└── 配置存储器
这种分层设计使得各功能模块职责明确,便于单独开发和测试。通信接口层负责与上位机和其他外设的数据交互;数据处理层完成核心的波束形成算法;存储管理层则负责系统参数的保存和加载。
2.2 关键模块选型考量
在模块选型上,我们做了以下技术决策:
-
串口通信:选择标准的UART协议而非更高速的USB,主要考虑:
- 与多数测试设备的兼容性
- FPGA资源占用较少
- 足够满足控制命令传输的需求(通常波特率115200bps)
-
角度解算:采用CORDIC算法实现三角函数计算,因为:
- 非常适合FPGA的流水线实现
- 无需使用乘法器等大资源模块
- 可配置精度满足系统要求
-
存储方案:使用SPI Flash而非并行Flash,因为:
- 引脚占用少,布线简单
- 现有IP核成熟稳定
- 容量足够存储波控参数表
3. 通信接口实现细节
3.1 UART串口收发模块
串口模块是系统与外界交互的主要通道,我们实现了完整的收发功能。以下是接收模块的核心代码:
verilog复制module uart_rx (
input wire clk,
input wire rst_n,
input wire rx,
output reg [7:0] rx_data,
output reg rx_done
);
// 状态定义
typedef enum reg [2:0] {
IDLE = 3'b000,
START = 3'b001,
DATA = 3'b010,
STOP = 3'b011
} state_t;
state_t state, next_state;
reg [3:0] bit_count;
reg [15:0] baud_counter;
reg sample_point;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= IDLE;
baud_counter <= 16'd0;
sample_point <= 1'b0;
end else begin
// 波特率时钟生成
if (baud_counter == BAUD_DIVIDER-1) begin
baud_counter <= 16'd0;
sample_point <= 1'b1;
end else begin
baud_counter <= baud_counter + 1;
sample_point <= 1'b0;
end
// 状态机转移
if (sample_point) begin
state <= next_state;
case (state)
IDLE: if (!rx) next_state <= START;
START: next_state <= DATA;
DATA: begin
rx_data[bit_count] <= rx;
if (bit_count == 4'd7)
next_state <= STOP;
end
STOP: next_state <= IDLE;
endcase
end
end
end
endmodule
关键点说明:
- 使用三段式状态机实现,确保时序清晰
- 波特率时钟通过分频产生,提高灵活性
- 在数据位中间采样,提高抗干扰能力
3.2 SPI驱动实现
SPI接口用于连接Flash存储和其他外设,我们实现了可配置的SPI主设备:
verilog复制module spi_master #(
parameter CLK_DIV = 8
)(
input wire clk,
input wire rst_n,
input wire start,
input wire [7:0] tx_data,
output reg [7:0] rx_data,
output reg busy,
output reg sclk,
output reg mosi,
input wire miso,
output reg cs_n
);
reg [2:0] bit_cnt;
reg [7:0] shreg;
reg [3:0] clk_cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sclk <= 1'b0;
cs_n <= 1'b1;
busy <= 1'b0;
end else if (start && !busy) begin
cs_n <= 1'b0;
shreg <= tx_data;
bit_cnt <= 3'd7;
busy <= 1'b1;
end else if (busy) begin
if (clk_cnt == CLK_DIV-1) begin
clk_cnt <= 0;
sclk <= ~sclk;
if (!sclk) begin // 下降沿采样
rx_data[bit_cnt] <= miso;
if (bit_cnt == 0) begin
cs_n <= 1'b1;
busy <= 1'b0;
end else begin
bit_cnt <= bit_cnt - 1;
end
end else begin // 上升沿输出
mosi <= shreg[bit_cnt];
end
end else begin
clk_cnt <= clk_cnt + 1;
end
end
end
endmodule
实现技巧:
- 参数化时钟分频,适配不同设备速度要求
- 双缓冲设计,支持连续传输
- 精确的时钟边沿控制,确保数据稳定
4. 角度解算与波束控制
4.1 基于CORDIC的角度解算
相控阵的核心是精确计算每个天线单元的相位偏移。我们采用CORDIC算法实现高效的角度计算:
verilog复制module cordic #(
parameter ITER = 16,
parameter WIDTH = 16
)(
input wire clk,
input wire rst_n,
input wire start,
input wire [WIDTH-1:0] angle,
output reg [WIDTH-1:0] sin,
output reg [WIDTH-1:0] cos,
output reg done
);
// 预计算的arctan表
localparam [WIDTH-1:0] atan_table[0:ITER-1] = {
16'h2000, 16'h12E4, 16'h09FB, 16'h0511,
16'h028B, 16'h0145, 16'h00A2, 16'h0051,
16'h0028, 16'h0014, 16'h000A, 16'h0005,
16'h0002, 16'h0001, 16'h0000, 16'h0000
};
reg [3:0] iter;
reg [WIDTH-1:0] x, y, z;
reg running;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
running <= 1'b0;
done <= 1'b0;
end else if (start && !running) begin
x <= 16'h26DD; // 0.60725 * 2^14
y <= 0;
z <= angle;
iter <= 0;
running <= 1'b1;
done <= 1'b0;
end else if (running) begin
if (iter == ITER) begin
sin <= y;
cos <= x;
done <= 1'b1;
running <= 1'b0;
end else begin
reg [WIDTH-1:0] x_shr, y_shr;
x_shr = x >>> iter;
y_shr = y >>> iter;
if (z[WIDTH-1]) begin // 负角度
x <= x + y_shr;
y <= y - x_shr;
z <= z + atan_table[iter];
end else begin // 正角度
x <= x - y_shr;
y <= y + x_shr;
z <= z - atan_table[iter];
end
iter <= iter + 1;
end
end
end
endmodule
4.2 波控参数计算
根据目标方向和天线阵列几何结构,计算每个单元的相位延迟:
verilog复制module beam_steering #(
parameter ANTENNA_COUNT = 8,
parameter WIDTH = 16
)(
input wire clk,
input wire rst_n,
input wire [WIDTH-1:0] target_azimuth,
input wire [WIDTH-1:0] target_elevation,
output reg [ANTENNA_COUNT-1:0][WIDTH-1:0] phase_offsets,
output reg done
);
// 天线位置参数(以波长λ为单位)
localparam [WIDTH-1:0] ant_pos[0:ANTENNA_COUNT-1] = {
-3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5
};
wire [WIDTH-1:0] sin_az, cos_az;
wire cordic_done;
cordic azimuth_calc (
.clk(clk),
.rst_n(rst_n),
.start(!done),
.angle(target_azimuth),
.sin(sin_az),
.cos(cos_az),
.done(cordic_done)
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
done <= 1'b0;
end else if (cordic_done) begin
for (int i=0; i<ANTENNA_COUNT; i++) begin
// 计算波程差引起的相位差
phase_offsets[i] <= ant_pos[i] * sin_az;
end
done <= 1'b1;
end
end
endmodule
注意事项:
- 实际应用中需要考虑天线互耦效应
- 宽带信号需要做时延补偿而不仅是相位调整
- 大型阵列需要分块计算以节省资源
5. Flash存储管理实现
5.1 Flash控制器设计
系统参数需要非易失存储,我们实现了SPI Flash的完整控制器:
verilog复制module flash_ctrl #(
parameter PAGE_SIZE = 256,
parameter SECTOR_SIZE = 4096
)(
input wire clk,
input wire rst_n,
input wire [23:0] addr,
input wire [7:0] wr_data,
output reg [7:0] rd_data,
input wire wr_en,
input wire rd_en,
output reg busy,
output reg error,
// SPI接口
output reg sclk,
output reg mosi,
input wire miso,
output reg cs_n
);
typedef enum reg [3:0] {
IDLE,
CMD_WREN,
CMD_PAGE_PROG,
WRITE_DATA,
CMD_READ,
READ_DATA,
CMD_SECTOR_ERASE,
WAIT_BUSY
} state_t;
state_t state;
reg [7:0] cmd;
reg [23:0] addr_reg;
reg [7:0] data_buf[0:PAGE_SIZE-1];
reg [8:0] byte_cnt;
spi_master spi (
.clk(clk),
.rst_n(rst_n),
.start(spi_start),
.tx_data(spi_tx),
.rx_data(spi_rx),
.busy(spi_busy),
.sclk(sclk),
.mosi(mosi),
.miso(miso),
.cs_n(cs_n)
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= IDLE;
busy <= 1'b0;
end else begin
case (state)
IDLE: begin
if (wr_en) begin
addr_reg <= addr;
data_buf[addr[7:0]] <= wr_data;
state <= CMD_WREN;
busy <= 1'b1;
end else if (rd_en) begin
addr_reg <= addr;
state <= CMD_READ;
busy <= 1'b1;
end
end
CMD_WREN: begin
cmd <= 8'h06; // WREN
if (!spi_busy) state <= WAIT_WREN_DONE;
end
// 其他状态处理...
endcase
end
end
endmodule
5.2 存储优化策略
针对相控阵系统的特点,我们采用了以下存储优化方案:
-
参数分块存储:将波控参数按方位角分扇区存储,每个扇区包含:
- 方位角中心值(2字节)
- 各天线相位值(8×2字节)
- CRC校验(2字节)
-
写缓存机制:在FPGA内部实现一个PAGE_SIZE的写缓存,只有整页数据准备好才写入Flash,减少擦写次数。
-
坏块管理:保留部分扇区作为备用,通过状态标志识别坏块并重映射。
6. 仿真验证与调试技巧
6.1 模块级仿真
每个关键模块都配备了完善的测试平台,以UART接收模块为例:
verilog复制module uart_rx_tb;
reg clk = 0;
reg rst_n = 0;
reg rx = 1;
wire [7:0] rx_data;
wire rx_done;
uart_rx dut (
.clk(clk),
.rst_n(rst_n),
.rx(rx),
.rx_data(rx_data),
.rx_done(rx_done)
);
always #5 clk = ~clk;
task send_byte(input [7:0] data);
integer i;
begin
rx = 0; // 起始位
#8680;
for (i=0; i<8; i++) begin
rx = data[i];
#8680;
end
rx = 1; // 停止位
#8680;
end
endtask
initial begin
#100 rst_n = 1;
send_byte(8'h55);
@(posedge rx_done);
if (rx_data !== 8'h55) $display("Error 55");
send_byte(8'hAA);
@(posedge rx_done);
if (rx_data !== 8'hAA) $display("Error AA");
$finish;
end
endmodule
6.2 系统级联合仿真
使用Verilator搭建了系统级测试环境,模拟上位机交互:
cpp复制class SystemTest {
public:
Vtop* dut;
void uart_send(uint8_t data) {
dut->uart_rx = 0; // 起始位
for (int i=0; i<8; i++) {
dut->uart_rx = (data >> i) & 1;
}
dut->uart_rx = 1; // 停止位
}
void test_beam_steering() {
uint16_t azimuth = 45 * 65536 / 360; // 45度
uart_send(0x01); // 命令字
uart_send(azimuth >> 8);
uart_send(azimuth & 0xFF);
while (!dut->response_ready) dut->eval();
printf("Response: %02X\n", dut->response_data);
}
};
6.3 实测调试技巧
在实际硬件调试中,总结了以下经验:
-
信号完整性检查:
- 使用示波器检查时钟信号的抖动
- 验证电源纹波在允许范围内
- 检查关键信号线的终端匹配
-
分层调试法:
- 先验证时钟和复位等基础信号
- 然后测试各模块独立功能
- 最后进行系统联调
-
常见问题排查:
- 数据错误:检查时序约束是否满足
- 功能异常:确认复位序列正确
- 性能不达标:分析关键路径时序
7. 硬件相关考量与优化
7.1 资源优化策略
针对FPGA资源限制,采取了以下优化措施:
- 时分复用:多个天线通道共享同一个CORDIC计算单元
- 位宽优化:通过仿真确定各信号的最小有效位宽
- 存储器重组:将多个小存储器合并为大块,提高利用率
7.2 时序收敛技巧
- 流水线设计:将大组合逻辑拆分为多级流水
- 寄存器复制:对高扇出信号进行局部复制
- 约束优化:对跨时钟域路径设置适当约束
7.3 天线适配考量
由于相控阵代码与具体硬件密切相关,我们设计了可配置的参数体系:
- 天线几何参数:阵元间距、排列方式等
- 射频特性参数:每个通道的增益、相位误差
- 校准数据:存储每个单元的校正值
这些参数通过配置文件在系统初始化时加载,使同一套代码可以适配不同的硬件设计。
