Verilog实战:从基础移位寄存器到LFSR的进阶设计

张开发
2026/4/11 15:51:23 15 分钟阅读

分享文章

Verilog实战:从基础移位寄存器到LFSR的进阶设计
1. 移位寄存器基础从入门到实战第一次接触移位寄存器时我盯着教科书上的原理图看了半小时——不就是几个D触发器串联吗直到真正用Verilog实现时才发现简单的电路背后藏着不少门道。移位寄存器本质上是一组级联的触发器每个时钟周期将数据向相邻位传递就像工厂流水线传送带一样有序移动。1.1 4位基础移位寄存器实现先看一个最基础的4位右移寄存器实现支持异步复位和同步加载功能module shift_register_4bit( input clk, input areset, // 异步高电平复位 input load, // 同步加载使能 input ena, // 移位使能 input [3:0] data, // 并行加载数据 output reg [3:0] q ); always (posedge clk or posedge areset) begin if(areset) q 4b0; // 异步复位清零 else if(load) q data; // 同步加载数据 else if(ena) q {1b0, q[3:1]}; // 右移操作高位补0 end endmodule这个模块有三个关键点需要注意异步复位优先级最高无论时钟状态如何只要areset有效立即清零同步加载次优先在时钟上升沿时如果load有效则载入data值移位使能控制ena有效时执行右移操作注意Verilog的位拼接语法{1b0, q[3:1]}实测中发现一个常见坑点如果同时给load和ena高电平实际行为取决于代码中的条件判断顺序。建议在文档中明确优先级避免后续维护时的困惑。1.2 双向移位寄存器进阶实际项目中经常需要支持左右移位的通用寄存器。下面这个100位双向移位寄存器模块通过ena信号选择移动方向module bidirectional_shift_100bit( input clk, input load, input [1:0] ena, // 2b01右移, 2b10左移 input [99:0] data, output reg [99:0] q ); always (posedge clk) begin if(load) q data; else case(ena) 2b01: q {q[0], q[99:1]}; // 右移循环 2b10: q {q[98:0], q[99]}; // 左移循环 default: q q; // 保持状态 endcase end endmodule与基础版本相比这里有几个重要改进使用case语句实现多模式选择代码更易扩展移出位不再丢弃而是循环补到另一端工业中常用于CRC计算明确列出default case避免锁存器生成在FPGA上实测这个模块时发现一个有趣现象当位宽超过64位时综合工具会自动采用BRAM资源而非触发器这对时序和功耗都有显著影响。建议关键路径设计时做好位宽规划。2. 算术移位与逻辑移位的本质区别很多初学者容易混淆算术移位和逻辑移位其实它们的差异体现在符号位处理上。举个栗子8b1011_0011进行右移时逻辑右移高位补0 → 0101_1001算术右移高位补符号位 → 1101_1001Verilog实现时需要特别注意有符号数的处理module arithmetic_shift_64bit( input clk, input load, input ena, input [1:0] amount, // 移位量和方向 input [63:0] data, output reg [63:0] q ); always (posedge clk) begin if(load) q data; else if(ena) begin case(amount) 2b00: q {q[62:0], 1b0}; // 逻辑左移1位 2b01: q {q[55:0], 8b0}; // 逻辑左移8位 2b10: q {q[63], q[63:1]}; // 算术右移1位 2b11: q {{8{q[63]}}, q[63:8]}; // 算术右移8位 endcase end end endmodule关键技巧在于符号位扩展左移时空位补0算术/逻辑左移行为相同算术右移时使用{{8{q[63]}}}语法进行符号位复制综合后会产生专门的符号扩展电路在图像处理项目中我们曾用这种移位方式快速实现定点数缩放运算比直接乘法器节省30%的LUT资源。3. LFSR设计从原理到密码学应用线性反馈移位寄存器(LFSR)是移位寄存器的进阶形态通过引入反馈路径生成伪随机序列。它的核心结构如下图所示--- --- --- --- | D |-- | D |-- | D |-- | D | --- --- --- --- | | | | v v v v CLK CLK CLK CLK ^ ^ ^ ^ | | | | ---XOR- ---XOR-3.1 5位最大长度LFSR实现下面是一个典型的最大长度5位LFSR抽头位置在bit5和bit3module lfsr_5bit( input clk, input reset, output reg [4:0] q ); always (posedge clk) begin if(reset) q 5b00001; // 初始种子 else q {q[0]^1b0, q[4], q[3]^q[0], q[2], q[1]}; end endmodule这段代码有几个设计要点非零种子全零状态会导致LFSR锁死必须确保初始值非零抽头选择q[3]^q[0]实现特定位置的反馈并行赋值所有位同步更新避免时序问题实测这个电路时用逻辑分析仪捕获的输出序列如下00001 → 10100 → 01010 → 00101 → 10010 → ...确实在31个周期后开始循环最大长度2^5-1。3.2 两种LFSR结构对比LFSR主要有斐波那契(Fibonacci)和伽罗瓦(Galois)两种结构类型反馈结构速度资源占用斐波那契多抽头异或后驱动单寄存器较慢较少伽罗瓦单抽头驱动多寄存器异或较快较多伽罗瓦结构的32位LFSR实现示例module lfsr_galois_32bit( input clk, input reset, output [31:0] q ); always (posedge clk) begin if(reset) q 32hABCD1234; // 任意非零种子 else begin q[31:1] q[30:0]; q[0] q[31] ^ q[21] ^ q[1] ^ q[0]; end end endmodule在通信系统中我们常用这种LFSR作为加扰器。发送端和接收端使用相同种子通过异或操作实现数据加密// 加扰过程 scrambled_data original_data ^ lfsr_output; // 解扰过程 original_data scrambled_data ^ lfsr_output;4. 实战技巧与常见问题排查4.1 初始化与稳定性问题曾经在一个项目中LFSR偶尔会陷入全零状态。后来发现是异步复位信号存在毛刺导致的。解决方法添加复位同步器检测全零状态自动恢复改进后的安全版本module safe_lfsr_8bit( input clk, input async_reset, output reg [7:0] q ); reg reset_sync; always (posedge clk) reset_sync async_reset; always (posedge clk) begin if(reset_sync) q 8b10101010; // 强制非零 else begin q {q[6:0], q[7]^q[5]^q[4]^q[3]}; if(q 0) // 防锁死 q 8b11111111; end end endmodule4.2 性能优化技巧当需要高速LFSR时可以采用以下方法流水线化将长反馈路径拆分为多级多相时钟交错多个LFSR提升吞吐量预计算提前计算多步状态例如这个预计算4步的优化版本module pipelined_lfsr_16bit( input clk, input reset, output [15:0] q ); reg [15:0] state; wire [3:0] feedback; assign feedback[0] state[15] ^ state[13]; assign feedback[1] state[14] ^ state[12]; assign feedback[2] state[13] ^ state[11]; assign feedback[3] state[12] ^ state[10]; always (posedge clk) begin if(reset) state 16hFFFF; else state {state[11:0], feedback}; end assign q state; endmodule4.3 验证方法推荐完善的验证是LFSR设计的关键自动检查周期通过断言验证2^n-1周期覆盖率收集确保所有抽头组合被测试随机种子测试使用不同初始值验证下面是一个简单的周期检查断言initial begin automatic reg [15:0] first_state; first_state lfsr.q; forever begin (posedge clk); if(lfsr.q first_state) begin $display(Cycle completed at %t, $time); $finish; end end end在最近的一个PCIe项目里我们用LFSR生成测试码型时发现当同时启用128个通道时伪随机序列会出现重复。最终通过为每个通道设置不同的初始种子解决了这个问题。这也提醒我们理解伪随机数的伪特性非常重要。

更多文章