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

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

相关推荐
萨文 摩尔杰14 小时前
GPS原理学习
学习·fpga开发
Huangichin14 小时前
跟着Gemini学System Verilog
fpga开发
LCMICRO-1331084774617 小时前
长芯微LDC90810完全P2P替代ADC128D818,是一款八通道系统监控器,专为监控复杂系统状态而设计。
stm32·单片机·嵌入式硬件·fpga开发·硬件工程·模数转换芯片adc
s090713619 小时前
保姆级教程十二:USB摄像头接入!ZYNQ+OpenCV+FPGA硬件加速图像处理实战(视觉终极篇)
图像处理·opencv·fpga开发·zynq·硬件加速
CoderIsArt1 天前
FPGA-based 量子电路仿真
fpga开发
碎碎思1 天前
升级版流水灯:用FPGA控制上千颗RGB LED
fpga开发
FPGA-ADDA1 天前
第二篇:Xilinx 7系列FPGA详解——从Spartan到Virtex
fpga开发·fpga·sdr·rfsoc
逐步前行2 天前
STM32_SysTick_寄存器操作
stm32·嵌入式硬件·fpga开发
良许Linux2 天前
FPGA的选型和应用
数据库·图像处理·计算机视觉·fpga开发
上班最快乐2 天前
基于FPGA的APS6404L-3SQR QSPI PSRAM驱动设计(3)
fpga开发