串行数据与并行数据转换(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. 触发机制保证模块可控

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

相关推荐
s1ckrain12 小时前
数字逻辑笔记—同步时序电路
笔记·fpga开发·嵌入式
s090713612 小时前
使用xilinx的fir IP核实现LFM信号匹配滤波的详细过程及原理
fpga开发·xilinx·fir·zynq·脉冲压缩
XINVRY-FPGA12 小时前
XC2C256-7VQG100I Xilinx CoolRunner-II CPLD FPGA
单片机·嵌入式硬件·fpga开发·硬件工程·dsp开发·fpga
黄埔数据分析12 小时前
chatgpt prompt for fpga
fpga开发·prompt
黄埔数据分析12 小时前
不同prompt 对比 async fifo
fpga开发·prompt
聊询QQ:688238861 天前
DENSO机器人二次开发:用C#读取和写入数据
fpga开发
s09071361 天前
ZYNQ7000关于JTAG电路设计注意事项
fpga开发·zynq·硬件设计设计
ARM+FPGA+AI工业主板定制专家1 天前
基于JETSON/RK3588+FPGA+AI农业机器人视觉感知方案
人工智能·计算机视觉·fpga开发·机器人
ARM+FPGA+AI工业主板定制专家1 天前
基于JETSON/RK3588+FPGA+AI商用自动割草机器人方案
人工智能·目标检测·计算机视觉·fpga开发·机器人