串行数据与并行数据转换(1)
实验目标
串并转换作为在FPGA中较为常用的操作,熟练的掌握其思想和用法是十分重要的。这也是很多 FPGA 项目中都会用到的基础模块,例如:LVDS/SerDes 数据处理、SPI/I2C/串口输入采样、摄像头/图像处理像素拼接等。
本文使用Verilog语言编写一个串行数据转并行数据的模块,并通过仿真来验证模块功能的正确性
核心要点
数据缓存方式(移位寄存器 / FIFO)
串行数据进入后必须按照正确的顺序存入寄存器,这里分别实现了:
高位拼接(msb → 左移)
低位拼接(lsb → 右移)
让用户可以根据协议选择不同的 bit 序。
实验代码
C
module SerialToParallel
#(
parameter P_SERIAL_DATA_WIDTH = 4 ,
parameter P_PARALLEL_DATA_WIDTH = 8
)
(
input i_clk ,
input i_rst ,
input i_trig ,
input i_serial_valid ,
input [P_SERIAL_DATA_WIDTH - 1 : 0] i_serial_data ,
input i_serial_last ,
output [P_PARALLEL_DATA_WIDTH - 1 : 0] o_parallel_data_msb ,
output [P_PARALLEL_DATA_WIDTH - 1 : 0] o_parallel_data_lsb ,
output o_parallel_valid
);
localparam P_NUM = P_PARALLEL_DATA_WIDTH / P_SERIAL_DATA_WIDTH ;
reg ri_serial_valid ;
reg [P_SERIAL_DATA_WIDTH - 1 : 0] ri_serial_data ;
reg ri_serial_last ;
reg [P_PARALLEL_DATA_WIDTH - 1 : 0] ro_parallel_data_msb ;
reg [P_PARALLEL_DATA_WIDTH - 1 : 0] ro_parallel_data_lsb ;
reg ro_parallel_valid ;
reg r_run ;
reg [15:0] r_num_cnt ;
assign o_parallel_data_msb = ro_parallel_data_msb ;
assign o_parallel_data_lsb = ro_parallel_data_lsb ;
assign o_parallel_valid = ro_parallel_valid ;
always @(posedge i_clk)
begin
if(i_rst) begin
ri_serial_data <= 'd0;
ri_serial_last <= 'd0;
ri_serial_valid <= 'd0;
end else begin
ri_serial_data <= i_serial_data ;
ri_serial_last <= i_serial_last ;
ri_serial_valid <= i_serial_valid;
end
end
always @(posedge i_clk or posedge i_rst)
begin
if(i_rst)
r_run <= 'd0;
else if(ri_serial_last)
r_run <= 'd0;
else if(i_trig)
r_run <= 'd1;
else
r_run <= r_run;
end
always @(posedge i_clk or posedge i_rst)
begin
if(i_rst)
r_num_cnt <= 'd0;
else if(r_num_cnt == P_NUM - 1 && r_run)
r_num_cnt <= 'd0;
else if(ri_serial_valid && r_run)
r_num_cnt <= r_num_cnt + 1'b1;
else
r_num_cnt <= r_num_cnt;
end
//高位
always @(posedge i_clk or posedge i_rst)
begin
if(i_rst)
ro_parallel_data_msb <= 'd0;
else if(ri_serial_last)
ro_parallel_data_msb <= 'd0;
else if(ri_serial_valid)
ro_parallel_data_msb <= {ri_serial_data , ro_parallel_data_msb[P_PARALLEL_DATA_WIDTH - 1 : 0 + P_SERIAL_DATA_WIDTH]};
else
ro_parallel_data_msb <= ro_parallel_data_msb;
end
//低位
always @(posedge i_clk or posedge i_rst)
begin
if(i_rst)
ro_parallel_data_lsb <= 'd0;
else if(ri_serial_last)
ro_parallel_data_lsb <= 'd0;
else if(ri_serial_valid)
ro_parallel_data_lsb <= {ro_parallel_data_lsb[P_PARALLEL_DATA_WIDTH - 1 - P_SERIAL_DATA_WIDTH : 0],ri_serial_data};
else
ro_parallel_data_lsb <= ro_parallel_data_lsb;
end
always @(posedge i_clk or posedge i_rst)
begin
if(i_rst)
ro_parallel_valid <= 'd0;
else if(r_num_cnt == P_NUM - 1 && r_run)
ro_parallel_valid <= 'd1;
else
ro_parallel_valid <= 'd0;
end
endmodule
核心逻辑解析
- 输入寄存(打一拍)
C
ri_serial_data <= i_serial_data ;
ri_serial_last <= i_serial_last ;
ri_serial_valid <= i_serial_valid;
对外部输入打一拍,提高时序稳定性。
- 运行控制
C
else if(i_trig)
r_run <= 1;
else if(ri_serial_last)
r_run <= 0;
只要触发信号有效,模块就开始收数据;遇到 last,立即终止数据拼接。
- 串行 → 并行拼接逻辑
-
高位拼接(MSB-first)
ro_parallel_data_msb <= {ri_serial_data , ro_parallel_data_msb[P_PARALLEL_DATA_WIDTH - 1 : 0 + P_SERIAL_DATA_WIDTH]};
数据从左边进入,老数据右移。
-
低位拼接(LSB-first)
ro_parallel_data_lsb <= {ro_parallel_data_lsb[P_PARALLEL_DATA_WIDTH - 1 - P_SERIAL_DATA_WIDTH : 0],ri_serial_data};
数据从右边进入,老数据左移。
C
`timescale 1ns/1ps
module tb_SerialToParallel();
parameter P_SERIAL_DATA_WIDTH = 4 ;
parameter P_PARALLEL_DATA_WIDTH = 32 ;
reg clk ;
reg rst ;
reg trig ;
reg serial_valid ;
reg [P_SERIAL_DATA_WIDTH - 1 : 0] serial_data ;
reg serial_last ;
wire [P_PARALLEL_DATA_WIDTH - 1 : 0] w_parallel_data_msb ;
wire [P_PARALLEL_DATA_WIDTH - 1 : 0] w_parallel_data_lsb ;
wire w_parallel_valid ;
initial begin
clk = 0;
rst = 1;
#200
rst = 0;
end
always #5 clk = ~clk;
task gen_serial_data();
integer i;
begin
for(i = 0 ; i < P_PARALLEL_DATA_WIDTH ; i = i + 1)
begin
serial_data = (i % (1 << P_SERIAL_DATA_WIDTH)) ;
if(i == P_PARALLEL_DATA_WIDTH - 1 ) serial_last = 1;
else serial_last = 0;
serial_valid = 1;
@(posedge clk);
end
serial_data = 0;
serial_last = 0;
serial_valid = 0;
@(posedge clk);
end
endtask
task gen_trig();
begin
trig = 1;
@(posedge clk);
trig = 0;
end
endtask
initial begin
serial_data = 0;
serial_last = 0;
serial_valid = 0;
trig = 0;
wait(~rst);
repeat(20) @(posedge clk) ;
gen_trig();
gen_serial_data();
end
SerialToParallel # (
.P_SERIAL_DATA_WIDTH (P_SERIAL_DATA_WIDTH ),
.P_PARALLEL_DATA_WIDTH (P_PARALLEL_DATA_WIDTH )
)
SerialToParallel_inst (
.i_clk (clk ),
.i_rst (rst ),
.i_trig (trig ),
.i_serial_valid (serial_valid ),
.i_serial_data (serial_data ),
.i_serial_last (serial_last ),
.o_parallel_data_msb (w_parallel_data_msb ),
.o_parallel_data_lsb (w_parallel_data_lsb ),
.o_parallel_valid (w_parallel_valid )
);
endmodule
仿真验证
输入的串行数据用valid 信号来标志输入数据有效,用last 信号来标志输入数据的最后一个数据。

可以看到,在r_num_cnt 计满清零后,parallel_valid 信号被拉高,输出拼接好的并行数据

小结
本实验实现了一个可复用的串行转并行模块,支持:
-
任意串行宽度
-
任意并行宽度
-
高位拼接与低位拼接两种模式
-
触发机制保证模块可控
不足之处:串行宽度与并行宽度必须是整除的关系,模块并未解决二者需要整除的情况

