流水灯与移位寄存器实验

实验目的

(1) 熟悉移位寄存器的功能特性。

(2) 用HDL语言设计移位寄存器功能模块。

(3) 理解计算机中常见的移位运算。

实验内容1:流水灯

一组LED指示灯在某一时刻只有部分LED点亮,依次变换点亮的LED灯,并且不停地循环,这种效果通常称为流水灯或走马灯。

参考设计1

例 1给出了一个流水灯的设计,将循环左移移位寄存器的输出连接到LED指示灯上,当寄存器中存储的数据移位时,看起来就像LED在移动,形成流水灯的效果,虚拟面板如图 1

例 1. 流水灯的设计代码
/****** Replace input ports with internal signals *******/
wire reset = PB[0];
wire clk   = PB[1];

/************* The logic of this experiment *************/
logic [7:0]q;
always @ (posedge clk or posedge reset)
	if (reset)
		q <= 1;
	else
		q <= {q[6:0], q[7]};

/***** Internal signals assignment to output ports ******/
assign L[7:0] = q[7:0];

注:为节省篇幅,仅给出了核心逻辑。完整代码可从开源项目托管网站下载,下载方法见下载实验材料

image
图 1. 流水灯的虚拟面板

实验任务1

用HDL设计一个可并行装载的右移移位寄存器,并在实验系统上验证。具体要求如下。

  1. 设计右移移位寄存器

将右移移位寄存器设计为一个独立的模块,端口定义如下。

module RightShifter
#(parameter N = 4)
(
	input  wire  iClk,
	input  wire  iReset,
	input  wire  iLoad,
	input  wire  iEnable,
	input  wire  iShiftIn,
	input  wire  [N-1:0] iD,
	output logic [N-1:0] oQ
);

parameter N = 4 定义了一个参数N,若上层模块实例化时未传递该参数,默认取值为4。Reset是异步复位信号,Load和Enable是同步装数和使能信号,表 1是功能表。可以看出,这些控制信号是有优先级的;只要Reset有效,就会使寄存器清零,与Load和Enable无关;当Reset无效时,如果Load有效,在时钟上升沿到来时将输入数据D保存在寄存器中,与Enable是什么电平无关;如果Reset和Load都无效但Enable=1,则寄存器内容右移一位,并且将ShiftIn移入寄存器的最高位;否则寄存器内容保持不变。这种有优先级的结构适合用“if-else if”语句描述。

表 1. 右移移位寄存器的功能表
Reset Load Enable 功能

0

0

0

保持不变

0

0

1

右移一位(高位移入的数据来自ShiftIn)

0

1

X

并行装载(保存iD输入数据)

1

X

X

异步清零

ShiftIn是移入数据的端口,在上层模块实例化时将不同的数据或信号连接到ShiftIn,可以实现逻辑右移、算术右移和循环右移。

  1. 编写VirtualBoard模块

在VirtualBoard模块中实例化右移移位寄存器模块。可在实验系统中打开虚拟面板查看虚拟元件的序号,连接相应的端口。例 2给出了与虚拟面板一致的端口连接代码。

例 2. 右移移位寄存器任务面板对应的端口连接
/****** Replace input ports with internal signals *******/
wire reset =  PB[0];
wire clk   =  PB[1];
wire [7:0] data = S[7:0];
wire load    =  S[8];
wire enable  =  S[9];


/***** Internal signals assignment to output ports ******/
assign L[7:0] = q[7:0];
  1. 为构成循环右移,应将q[0]连接到ShiftIn(如图 2虚拟面板虚线所示);

  2. 实例化时传递参数N的值为8,产生8位的循环右移移位寄存器。

【思考】不改变RightShifter模块的设计,如何实现算术右移或逻辑右移。

  1. 验证设计

虚拟面板如图 2。除了Load和Enable各用一个开关控制外,还有8个开关提供并行装载的数据,用来设置灯的初始状态。

image
图 2. 可并行装载的右移移位寄存器的虚拟面板

代码编译成功后,加载FPGA,验证表 1各项功能,如装载、右移、保持和异步复位等。

实验内容2:桶形移位

上面的设计是一个时钟移一位,如果需要移动多位,就需要多个时钟周期。但是现代计算机的移位指令,通常需要在一个时钟周期移动多位。实现多位移位的组合逻辑称作桶形移位器(barrel shifter),一般的实现方法是使用多路器阵列。图 3是一个四位的循环左移桶形移位器,由4个4选1多路器构成;DI3-0是4位输入,DO3-0是移位的输出,移位的位数由N1N0决定,一次可移位0~3位。N1N0实际是4选1多路器的选择信号,当N1N0=00时,DO3-0输出选择的是对应的DI3-0输入,即DO3-0=DI3-0,不进行移位;当N1N0=01时,DO3-1=DI2-0,即DI左移1位,而DO0=DI3,所以是循环左移1位;当N1N0=10时,DO3-2=DI1-0,DO1-0=DI3-2,循环左移2位;类似地,当N1N0=11时,循环左移3位。

barrel-shifter
图 3. 四位循环左移桶形移位原理图

由上可知,构造一个M位的桶形移位器需要M个M选1的多路器,最大可移位位数为M-1位。如果M较大,可采用分层的多路器阵列,以避免扇入数太大。

参考设计2

桶形移位器是一个组合逻辑,要实现桶形移位寄存器,还需要增加一个数据寄存器。例 3给出了一个8位的循环左移桶形移位寄存器的设计,该电路分为组合逻辑和时序逻辑两个部分,组合逻辑实现桶形移位功能,而时序逻辑就是一个并行数据寄存器,用来将移位的结果保存在寄存器中。

例 3. 流水灯的设计代码
logic [7:0] q, shiftOut;
always_comb
    case (num)
        3'b000: shiftOut = q[7:0];
        3'b001: shiftOut = {q[6:0],q[7]};
        3'b010: shiftOut = {q[5:0],q[7:6]};
        3'b011: shiftOut = {q[4:0],q[7:5]};
        3'b100: shiftOut = {q[3:0],q[7:4]};
        3'b101: shiftOut = {q[2:0],q[7:3]};
        3'b110: shiftOut = {q[1:0],q[7:2]};
        3'b111: shiftOut = {q[0],q[7:1]};
        default:shiftOut = 8'hxx;
	endcase
/*  
    循环左移桶形移位的另一种实现方法
    logic [7:0] temp;
    assign {shiftOut, temp} = {q,q}<<num;
*/
always @ (posedge clk or posedge reset)
	if (reset)
		q <= 1;
	else
		q <= shiftOut;

注:为节省篇幅,仅给出了核心逻辑。完整代码可从开源项目托管网站下载,下载方法见下载实验材料

电路框图如图 4虚拟面板所示,复位时数据寄存器的初值为00000001,寄存器的输出Q连到桶形移位逻辑的数据输入DI,Num决定移位位数,DO是移位输出。如果Num大于1,DI左移Num位,当点击Clk按钮时,此前的移位输出保存到寄存器并且作为移位器新的输入,连续点击Clk,LED的视觉效果是一个跳跃的流水灯。

image
图 4. 左移的桶形移位寄存器的虚拟面板

实验任务2

用HDL设计一个具有多种移位功能的桶形移位器,电路框图如图 5虚拟面板所示。

image
图 5. 多功能桶形移位器的虚拟面板

该桶形移位器是一个组合逻辑(不包含寄存器),可以对并行输入数据进行左移、逻辑右移、算术右移、并行传送以及输出零。Num指定移位的位数,SM0和SM1控制移位方式,具体含义见表 2。AM控制是否算术移位,若AM=1,进行算术移位,否则进行逻辑移位;只有右移受AM控制,左移始终是逻辑移位。

表 2. 桶形移位器功能表
SM1 SM0 功能

0

0

输出零(DO=0)

0

1

右移Num位

1

0

左移Num位

1

1

传送(DO=DI)

例 4给出了与虚拟面板一致的端口连接代码。

例 4. 桶形移位器任务面板对应的端口连接
/****** Replace input ports with internal signals *******/
wire [7:0] data = S[7:0];
wire [1:0] shift_mode =  S[9:8];
wire [2:0] num  = S[12:10];
wire arith_mode = S[13]; //0:Logic, 1:Arithmetic


/***** Internal signals assignment to output ports ******/
assign L[7:0] = shiftOut[7:0];
  • 可以使用Verilog的移位运算符。

  • logic、reg等类型的逻辑变量默认是无符号数,可使用系统函数$signed()强制转换为带符号数,之后再做算术移位。

  • 本实验任务的设计可直接用于后面ALU实验的移位运算。