Verilog设计实例(6)基于Verilog的各种移位寄存器实现「建议收藏」

Verilog设计实例(6)基于Verilog的各种移位寄存器实现「建议收藏」在数字电子产品中,移位寄存器是级联的触发器,其中一个触发器的输出引脚q连接到下一个触发器的数据输入引脚(d)。因为所有触发器都在同一时钟上工作,所以存储在移位寄存器中的位阵列将移位一个位置。

大家好,又见面了,我是你们的朋友全栈君。


写在前面


正文

在数字电子产品中,移位寄存器是级联的触发器,其中一个触发器的输出引脚q连接到下一个触发器的数据输入引脚(d)。 因为所有触发器都在同一时钟上工作,所以存储在移位寄存器中的位阵列将移位一个位置。
例如,如果一个5位右移寄存器的初始值为10110,并且将移位寄存器的输入绑定到O,则下一个模式将为01011,下一个模式将为00101。

5位移位寄存器示意图

移位寄存器的种类有很多,需要根据需求来设计,但万变不离其宗,都是每一个时钟,寄存器阵列移位一次,下面就盘点各种移位寄存器:

  1. 左移位寄存器
  2. 右移位寄存器
  3. 串行输入并行输出移位寄存器
  4. 并行输入串行输出移位寄存器

下面就分别认识下吧。

左移位寄存器

所谓的左移,这里约定成网高位移位,这是因为我们通常定义变量都是:

reg [MSB:LSB] VAR;

高位在左,底位在右,因此左移便是向高位移位。
其实这里还可以继续细分,是循环左移还是非循环的呢?

循环左移寄存器

所谓的循环左移,就是将最高位移位到最低位,次高位作为最高位,依次循环。

电路设计:

以四位循环左移为例,给出电路设计Verilog代码:

`timescale 1ns / 1ps
//
// Engineer: Reborn Lee
// Module Name: cycle_left_register
// Additional Comments:
// https://blog.csdn.net/reborn_lee
//


module cycle_left_register #(parameter MSB = 4)(
	input [MSB - 1 : 0] din,
	input i_rst,
	input i_load,
	input i_clk,
	output  [MSB - 1 : 0] dout
    );

	reg [MSB - 1 : 0] dout_mid;
	always@(posedge i_clk) begin
		if(i_rst) begin
			dout_mid <= 'd0;
		end
		else if(i_load) begin
			dout_mid <= din;
		end
		else begin
			dout_mid <= {dout_mid[MSB - 2 : 0], dout_mid[MSB - 1]};
		end
	end
	assign dout = dout_mid;


endmodule



注:里面添加了一个信号,叫装载信号i_load,这个信号有效的时候,将输入din赋值给中间寄存器dout_mid,这样才能实现每一个时钟上升沿来临时,都对输入左移一次。

测试代码

简单给出测试代码:

`timescale 1ns / 1ps
//
// Engineer: Reborn Lee
// Module Name: cycle_left_register_tb
// https://blog.csdn.net/reborn_lee
//


module cycle_left_register_tb(

    );
	parameter MSB = 4;

	reg [MSB - 1 : 0] din;
	reg i_rst;
	reg i_clk;
	reg i_load;
	wire [MSB - 1 : 0] dout;

	//generate clock
	initial begin
		i_clk = 0;
		forever begin
			#5 i_clk = ~i_clk;
		end
	end

	//generate rst and input data 
	initial begin
		i_rst = 1;
		din = 0;
		i_load = 0;

		# 22

		i_rst = 0;
		@(negedge i_clk) begin
		din = 'b1011;
		i_load = 1;
		end

		@(negedge i_clk) begin
			i_load = 0;
		end

		repeat(5) @(posedge i_clk);

		@(negedge i_clk) begin
			din = 'd0101;
			i_load = 1;
		end
		
		@(negedge i_clk) i_load = 0;

		repeat(4) @(posedge i_clk);

		#10 $finish;

	end

	initial
      $monitor (" i_rst = %0b, i_load = %0b, din = %b, dout = %b", i_rst, i_load, din, dout);

	cycle_left_register #(.MSB(MSB))inst_cycle_left_register(
		.i_clk(i_clk),
		.i_rst(i_rst),
		.i_load(i_load),
		.din(din),
		.dout(dout)
		);



endmodule


给出仿真结果:

左移位寄存器仿真结果

监控记录:

 i_rst = 1, i_load = 0, din = 0000, dout = xxxx
 i_rst = 1, i_load = 0, din = 0000, dout = 0000
 i_rst = 0, i_load = 0, din = 0000, dout = 0000
 i_rst = 0, i_load = 1, din = 1011, dout = 0000
 i_rst = 0, i_load = 1, din = 1011, dout = 1011
 i_rst = 0, i_load = 0, din = 1011, dout = 1011
 i_rst = 0, i_load = 0, din = 1011, dout = 0111
 i_rst = 0, i_load = 0, din = 1011, dout = 1110
 i_rst = 0, i_load = 0, din = 1011, dout = 1101
 i_rst = 0, i_load = 0, din = 1011, dout = 1011
 i_rst = 0, i_load = 0, din = 1011, dout = 0111
 i_rst = 0, i_load = 1, din = 0101, dout = 0111
 i_rst = 0, i_load = 1, din = 0101, dout = 0101
 i_rst = 0, i_load = 0, din = 0101, dout = 0101
 i_rst = 0, i_load = 0, din = 0101, dout = 1010
 i_rst = 0, i_load = 0, din = 0101, dout = 0101
 i_rst = 0, i_load = 0, din = 0101, dout = 1010
 i_rst = 0, i_load = 0, din = 0101, dout = 0101

非循环左移寄存器

非循环左移对于循环左移来说,就是最高位不移入最低位,而是丢弃,最低位补零。
修改其中移位语句即可:

dout_mid <= {dout_mid[MSB - 2 : 0], dout_mid[MSB - 1]};

改为:

dout_mid <= {dout_mid[MSB - 2 : 0],1'b0};

行为仿真波形

非循环的左移移位寄存器仿真图

 i_rst = 1, i_load = 0, din = 0000, dout = xxxx
 i_rst = 1, i_load = 0, din = 0000, dout = 0000
 i_rst = 0, i_load = 0, din = 0000, dout = 0000
 i_rst = 0, i_load = 1, din = 1011, dout = 0000
 i_rst = 0, i_load = 1, din = 1011, dout = 1011
 i_rst = 0, i_load = 0, din = 1011, dout = 1011
 i_rst = 0, i_load = 0, din = 1011, dout = 0110
 i_rst = 0, i_load = 0, din = 1011, dout = 1100
 i_rst = 0, i_load = 0, din = 1011, dout = 1000
 i_rst = 0, i_load = 0, din = 1011, dout = 0000
 i_rst = 0, i_load = 1, din = 0101, dout = 0000
 i_rst = 0, i_load = 1, din = 0101, dout = 0101
 i_rst = 0, i_load = 0, din = 0101, dout = 0101
 i_rst = 0, i_load = 0, din = 0101, dout = 1010
 i_rst = 0, i_load = 0, din = 0101, dout = 0100
 i_rst = 0, i_load = 0, din = 0101, dout = 1000
 i_rst = 0, i_load = 0, din = 0101, dout = 0000

注意事项:

循环移位语句:

dout_mid <= {dout_mid[MSB - 2 : 0],1'b0};

其中的最低位一定要写成1’b0,如果写成了0,即:

dout_mid <= {dout_mid[MSB - 2 : 0],0};

则仿真结果变为:

非循环左移的4位移位寄存器仿真图异常情况

这意味着,直接赋值0给dout_mid了。

右移位寄存器

右移位寄存器和左移位寄存器是对称的,就是每一个时钟上升沿到来,都向低位移动一次,这里也必要重新写了,我们只需要改其中某条移位语句即可。

这里又分为循环与不循环,分别点出。

循环右移位

即:

dout_mid <= {dout_mid[MSB - 2 : 0], dout_mid[MSB - 1]};

改为:

dout_mid <= {dout_mid[0], dout_mid[MSB - 1 : 1]};

为了照顾新手,还是给出完整设计代码:

`timescale 1ns / 1ps
//
// Engineer: Reborn Lee
// Module Name: cycle_left_register
// Additional Comments:
// https://blog.csdn.net/reborn_lee
//


module shift_register #(parameter MSB = 4)(
	input [MSB - 1 : 0] din,
	input i_rst,
	input i_load,
	input i_clk,
	output  [MSB - 1 : 0] dout
    );

	reg [MSB - 1 : 0] dout_mid;
	always@(posedge i_clk) begin
		if(i_rst) begin
			dout_mid <= 'd0;
		end
		else if(i_load) begin
			dout_mid <= din;
		end
		else begin
			// dout_mid <= {dout_mid[MSB - 2 : 0], 1'b0}; // normal left shift
			// dout_mid <= {dout_mid[MSB - 2 : 0], dout_mid[MSB - 1]}; //cycle left shift
			dout_mid <= {dout_mid[0],dout_mid[MSB - 1 : 1]};
		end
	end
	assign dout = dout_mid;


endmodule

仿真波形:

由于仿真文件和上述的循环左移一致,就改一下例化即可,这里就没必要给出了 ,直接给出仿真波形:

循环右移寄存器仿真波形

监控记录

 i_rst = 1, i_load = 0, din = 0000, dout = xxxx
 i_rst = 1, i_load = 0, din = 0000, dout = 0000
 i_rst = 0, i_load = 0, din = 0000, dout = 0000
 i_rst = 0, i_load = 1, din = 1011, dout = 0000
 i_rst = 0, i_load = 1, din = 1011, dout = 1011
 i_rst = 0, i_load = 0, din = 1011, dout = 1011
 i_rst = 0, i_load = 0, din = 1011, dout = 1101
 i_rst = 0, i_load = 0, din = 1011, dout = 1110
 i_rst = 0, i_load = 0, din = 1011, dout = 0111
 i_rst = 0, i_load = 0, din = 1011, dout = 1011
 i_rst = 0, i_load = 0, din = 1011, dout = 1101
 i_rst = 0, i_load = 1, din = 0101, dout = 1101
 i_rst = 0, i_load = 1, din = 0101, dout = 0101
 i_rst = 0, i_load = 0, din = 0101, dout = 0101
 i_rst = 0, i_load = 0, din = 0101, dout = 1010
 i_rst = 0, i_load = 0, din = 0101, dout = 0101
 i_rst = 0, i_load = 0, din = 0101, dout = 1010
 i_rst = 0, i_load = 0, din = 0101, dout = 0101

非循环右移寄存器

相对于循环右移寄存器来说,只需要改动为:最高位补零即可。
即:
将移位语句:


dout_mid <= {dout_mid[0],dout_mid[MSB - 1 : 1]};

改为:

dout_mid <= {1'b0,dout_mid[MSB - 1 : 1]};

仿真波形为:

非循环右移寄存器

监控记录

 i_rst = 1, i_load = 0, din = 0000, dout = xxxx
 i_rst = 1, i_load = 0, din = 0000, dout = 0000
 i_rst = 0, i_load = 0, din = 0000, dout = 0000
 i_rst = 0, i_load = 1, din = 1011, dout = 0000
 i_rst = 0, i_load = 1, din = 1011, dout = 1011
 i_rst = 0, i_load = 0, din = 1011, dout = 1011
 i_rst = 0, i_load = 0, din = 1011, dout = 0101
 i_rst = 0, i_load = 0, din = 1011, dout = 0010
 i_rst = 0, i_load = 0, din = 1011, dout = 0001
 i_rst = 0, i_load = 0, din = 1011, dout = 0000
 i_rst = 0, i_load = 1, din = 0101, dout = 0000
 i_rst = 0, i_load = 1, din = 0101, dout = 0101
 i_rst = 0, i_load = 0, din = 0101, dout = 0101
 i_rst = 0, i_load = 0, din = 0101, dout = 0010
 i_rst = 0, i_load = 0, din = 0101, dout = 0001
 i_rst = 0, i_load = 0, din = 0101, dout = 0000

串行输入并行输出移位寄存器

3位串行输出并行输出移位寄存器示意图

该移位寄存器设计具有五个输入和一个n位输出,并且使用参数MSB对设计进行参数化以表示移位寄存器的宽度。 如果MSB为4,则它成为4位移位寄存器。 如果MSB为8,则它成为8位移位寄存器。
该移位寄存器具有一些关键功能。

  • 可通过驱动设计的en信号来启用或禁用
  • 驱动dir时可左右移动
  • 如果将rstn拉为低电平,则会重置移位寄存器,输出将变为0
  • 移位寄存器的输入数据值可以通过d引脚控制

因此,这里没有必要再将左移位以及右移位分开来写了,合在一个设计里,通过一个信号dir控制,dir为0,左移,否则,右移!

设计代码


`timescale 1ns / 1ps
//
// Engineer: Reborn Lee
// Module Name: shift_register
// https://blog.csdn.net/reborn_lee
//


module shift_register#(parameter MSB = 8)(
	input i_clk,
	input i_rst,
	input i_dir,
	input i_en,
	input din,
	output reg [MSB - 1 : 0] dout

    );

	always @(posedge i_clk) begin
		if (i_rst) begin
			// reset
			dout <= 'd0;
		end
		else if (i_en) begin
			case(i_dir)
				0: begin //left shift
					dout <= {dout[MSB - 2: 0], din};
				end
				1: begin
					dout <= {din, dout[MSB - 1 : 1]};
				end
			endcase
			
		end
		else begin
			dout <= dout;
		end
	end

endmodule

仿真代码

`timescale 1ns/1ps
module shift_register_tb;
	parameter MSB = 8;

    reg i_clk;
	reg i_rst;
	reg i_dir;
	reg i_en;
	reg din;
	wire [MSB - 1 : 0] dout;

	initial begin
		i_clk = 0;
		forever begin
			# 5 i_clk = ~i_clk;
		end
	end

	initial begin
		i_rst = 1;
		i_en = 0;
		i_dir = 0;
		din = 0;

		# 18
		@(negedge i_clk) begin
			i_rst = 0;
			i_en = 1;
		end

		repeat(8) begin
			@(negedge i_clk) begin
				din = $random;
			end
		end

		@(negedge i_clk) begin
			i_rst  = 1;
      
		end

		#18
		i_rst = 0;
    i_dir = 1;

		repeat(8) begin
			@(negedge i_clk) begin
				din = $random;
			end
		end
		# 20 $finish;


	end



	shift_register #(.MSB(MSB))inst_shift_register (
		.i_clk(i_clk),
		.i_rst(i_rst),
		.i_dir(i_dir),
		.i_en(i_en),
		.din(din),
		.dout(dout)

		);





endmodule

仿真波形

左移

右移

并行输入串行输出移位寄存器

并行输入串行输出的原理图如下:

3位的并行输入串行输出移位寄存器示意图

该电路由三个串联的D触发器组成。 这意味着,一个D触发器的输出被连接为下一个D触发器的输入。 所有这些触发器彼此同步,因为相同的时钟信号被施加到每个触发器。

在该移位寄存器中,我们可以通过将Preset Enable(预置使能)设为1,将并行输入应用于每个D触发器。对于时钟信号的每个正沿触发,数据都会从一个级转移到下一个级。 因此,我们将从最右边的D触发器获取串行输出。

于此同时,我们仍然设置一个控制方向的使能信号i_dir,如果i_dir为0,则并行输出左移,取最高位作为串行输出;否则,右移,取最低位作为串行输出。

电路设计


//paralell input and serial output shift register
module shift_register#(parameter MSB = 4)(
	input i_clk,
	input i_load,
	input i_dir,
	input [MSB - 1 : 0] din,
	output dout
	);

	reg [MSB - 1 : 0] q_mid = 0;
	always@(posedge i_clk) begin
			if(i_load) begin
				q_mid <= din;
			end
			else begin
				case(i_dir)
				1'b0: begin
					q_mid <= {q_mid[MSB - 2 : 0], 1'b0}; //no cycle
				end
				1'b1: begin
					q_mid <= {1'b0, q_mid[MSB - 1 : 1]}; //no cycle
				end

				endcase
				
			end
	end

	assign dout = i_dir ? (q_mid[0]) : (q_mid[MSB - 1]);


endmodule

仿真文件

`timescale 1ns/1ps
module shift_register_tb;
	parameter MSB = 4;

    reg i_clk;
	reg i_dir;
	reg i_load;
	reg [MSB - 1 : 0] din;
	wire  dout;

	initial begin
		i_clk = 0;
		forever begin
			# 5 i_clk = ~i_clk;
		end
	end

	initial begin
		i_load = 0;
		i_dir = 0;
		din = $random;

		# 18
		@(negedge i_clk) begin
			i_load = 1;
		end

		@(negedge i_clk) i_load = 0;


		repeat(3) @(negedge i_clk); //finish shift output 


		din = $random;
		i_load = 1;

		@(negedge i_clk) i_load = 0;
		i_dir = 1;
		
		repeat(3) begin
			@(negedge i_clk);
		end
		# 20 $finish;


	end

// Monitor values of these variables and print them into the logfile for debug
   initial
      $monitor ("i_load=%0b, i_dir=%0b, din=%b,   dout = %0b", i_load, i_dir, din, dout);

	shift_register #(.MSB(MSB))inst_shift_register(
		.i_clk(i_clk),
		.i_dir(i_dir),
		.i_load(i_load),
		.din(din),
		.dout(dout)

		);





endmodule

4bit并行输入串行输出移位寄存器

监控数据

i_load=0, i_dir=0, din=0100,   dout = 0
i_load=1, i_dir=0, din=0100,   dout = 0
i_load=0, i_dir=0, din=0100,   dout = 0
i_load=0, i_dir=0, din=0100,   dout = 1
i_load=0, i_dir=0, din=0100,   dout = 0
i_load=1, i_dir=0, din=0001,   dout = 0
i_load=0, i_dir=1, din=0001,   dout = 1
i_load=0, i_dir=1, din=0001,   dout = 0

参考资料


交个朋友

  • 个人微信公众号:FPGA LAB,左下角二维码;

  • 知乎:李锐博恩,右下角二维码。
    在这里插入图片描述

  • FPGA/IC技术交流2020

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/160225.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • Unified Functional Testing12.02(UFT)安装教程

    Unified Functional Testing12.02(UFT)安装教程UnifiedFunctionalTesting12.02安装教程相关说明​ UnifiedFunctionalTesting(UTF)是QuickTestProfessional(QTP)11.5版本以后的名称实验证明教程中的《安装UnifiedFunctionalTesting的Update》跳过也行,安装完MicrosoftScriptDebugger之后就可以…

  • java用位运算实现加减乘除的过程_java四则运算

    java用位运算实现加减乘除的过程_java四则运算我们经常使用的加减乘除,我们所看到的只是表面的效果,那么加减乘除在底层究竟是怎么实现的?今天就让我们一探究竟.今天用位运算实现的加减乘除不使用任何的加减乘除符号.

    2022年10月30日
  • 开始画PAD图了_ipad如何画图

    开始画PAD图了_ipad如何画图这篇日志本来是应该昨天就写的,但是昨天回去的时候有点累,上了会网,把这个事给忘了。从昨天开始我就要开始画PAD图了,所谓的PAD图就是类似程序流程图的样子,用来帮助程序员快速掌握业务流程的一种方式。组长说先看结算业务主流程,把PAD图画上个两三遍,到下周的时候就要给我分配小任务了,不能再让我像现在这个样子,整天学习一点任务都不做了。尽管听上去不是很爽,但是我内心是十分想做任务的,因为我早就厌烦…

  • Ajax 跨域,这应该是最全的解决方案了

    Ajax 跨域,这应该是最全的解决方案了

    2021年10月13日
  • MySQL数据库免安装版配置教程及常见问题[通俗易懂]

    MySQL数据库免安装版配置教程及常见问题[通俗易懂]MySQL是一款安全、跨平台、高效的,并与PHP、Java等主流编程语言紧密结合的数据库系统。以下教程以windows1064位计算机为例(其他windows系统类似)01下载官方网站下载https://dev.mysql.com/downloads/mysql/根据自己的电脑配置选择对应版本.zip压缩包格式下载。无需登录,直接下载百度云快捷下载链接:https://pan.baidu.com/s/1hcEKFAQ6Fq7kFLG8x7SQCQ提取码:2bfg02配

  • mac系统安装win10双系统「建议收藏」

    mac系统安装win10双系统「建议收藏」一个月前,为了在家里学习单片机,在macbookair系统基础上,安装了win10,搞了一个双系统。在安装之前,看了很多资料,基本上提到两点,一个是准备win10镜像,一个是准备win10安装启动程序,而且至少需要一个U盘,有的也说需要两个,一个用来装win10镜像,一个用来装win10启动程序。最后动手安装的时候,一个U盘也没有使用,直接把win10镜像下载到mac系统中,然后启动mac上的磁盘管理工具,按照提示,傻瓜式的进行下一步。有两个地方需要我们手动设…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号