串行数据与并行数据转换(1)

串行数据与并行数据转换(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

核心逻辑解析

  1. 输入寄存(打一拍)
C 复制代码
ri_serial_data  <= i_serial_data ;
ri_serial_last  <= i_serial_last ;
ri_serial_valid <= i_serial_valid;

对外部输入打一拍,提高时序稳定性。

  1. 运行控制
C 复制代码
else if(i_trig)
    r_run <= 1;
else if(ri_serial_last)
    r_run <= 0;

只要触发信号有效,模块就开始收数据;遇到 last,立即终止数据拼接。

  1. 串行 → 并行拼接逻辑
  • 高位拼接(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 信号被拉高,输出拼接好的并行数据

小结

本实验实现了一个可复用的串行转并行模块,支持:

  1. 任意串行宽度

  2. 任意并行宽度

  3. 高位拼接与低位拼接两种模式

  4. 触发机制保证模块可控

不足之处:串行宽度与并行宽度必须是整除的关系,模块并未解决二者需要整除的情况

相关推荐
FPGA小迷弟4 小时前
FPGA 与卫星:从星载硬件到在轨智能的完整技术解析
fpga开发·架构·fpga·1024程序员节·无线电
NamoAmitabha1284 小时前
CPU 内部结构是什么?---deepseek
fpga开发
abc light5 小时前
ARM 裸机开发(6)——时钟树与定时器
arm开发·单片机·fpga开发
1560820721920 小时前
JFMZQ28DR、JFMZQ68DR、RFsoc XCZU47DR、XCZU48DR 信号处理板
fpga开发·信号处理
XMAIPC_Robot1 天前
-40~85℃宽温实测:RK3588 机器人控制器硬件底座,长时间 SLAM 定位稳定性验证
人工智能·fpga开发·机器人·边缘计算·arm+fpga·rk3588+fpga
樱木花道長1 天前
AMD Ross 深度系列 8:FAQ 七问七答(文末附网盘链接)
人工智能·fpga开发
FPGA小迷弟1 天前
FPGA 与无人机:从飞控实时性到智能载荷的硬件加速实践
人工智能·ai·fpga开发·架构·无人机·ai编程
低慢小uav1 天前
(已解决)Zynq芯片lwip协议栈,使用tcp作为服务器Service,接收大量数据时,长度接收正常,接收对数据校验,校验和异常分析
网络协议·tcp/ip·fpga开发
szxinmai主板定制专家1 天前
ARM+FPGA 数控机床控制器:选 CODESYS SoftMotion CNC,还是基于 LinuxCNC 自研搭建?架构、授权、实时性、量产风险深度对比
arm开发·fpga开发·codesys·数据采集卡·linuxcnc·rk3576
女神下凡1 天前
芯参谋(79):嵌入式学习进阶_(硬件与软件)深入实践指南
arm开发·单片机·嵌入式硬件·学习·fpga开发·设计规范