20181118 一些FPGA入门作业

这是一份作业,为了完成任务而写,学习嘛。
目标:1、学习Markdown的写法。2、暂时没想到。

参考资料:
http://ecdav.cuc.edu.cn/web_root/ShiYanJiaoXue/Verilog_Starter_Tutorial/content.htm


多路选择器

任务:做一个4选1的mux,并且进行波形仿真。

//4选1多路选择器
module mux4x1(//命名要求,全部大写表示端口
    SEL,
    IN0,
    IN1,
    IN2,
    IN3,
    OUT
);//模块端口声明部分
    //参数部分
    parameter WL = 8;//定义可变参数,便于模块扩展例化
    //这里有个问题:
    //这个INX怎么才能“参数化”,类似于C语言的“不定参数”,那不就更方便了么,可惜不得其法
    //端口描述部分,描述端口的方向
    input [WL-1:0] IN0,IN1,IN2,IN3;
    input [1:0]SEL;
    output [WL-1:0] OUT;
    //?这个叫啥?
    //语法贼麻烦
    reg [WL-1:0] OUT;
    //多路选择器的主要代码
    always @ (IN0 or IN1 or IN2 or IN3 or SEL) begin
        case (SEL)
        2'b00: OUT = IN0;
        2'b01: OUT = IN1;
        2'b10: OUT = IN2;
        2'b11: OUT = IN3;
        //所有情况写完了就不需要default了
        endcase
        /*//这种写法y也行,但是生成的RTL图不一样
        if(SEL==0)OUT = IN0;
        else
        if(SEL==1)OUT = IN1;
        else
        if(SEL==2)OUT = IN2;
        else
        OUT = IN3;
        */
    end
endmodule

扔到Quartus II 9.0里进行编译,仿真,截图如下:

按我所认识的组合逻辑电路来说,当SEL发生变化的时候,OUT也应该或者很快地发生变化才对。然而,从仿真结果可以看到,SEL变化和OUT变化相差大概8个ns,可能这就是所谓的线路延迟吧。图中的“毛刺”,估计是由一个值变化到另一个值所产生的“中间值”吧。


交叉开关

交叉开关是多路选择器mux的一种变形,它可以用SELx表示选择输入INn到输出OUTn。
任务:编写一个4X4路交叉开关的RTL。

//交叉开关,Crossbar Switch
module crossbarswitch4x4(
    IN0, IN1, IN2, IN3,
    SEL0, SEL1, SEL2, SEL3,
    OUT0, OUT1, OUT2, OUT3
);
    parameter WL = 16;
    input [WL-1:0] IN0,IN1,IN2,IN3;
    input [1:0] SEL0,SEL1,SEL2,SEL3;
    output reg [WL-1:0] OUT0,OUT1,OUT2,OUT3;
    //例化4个4选1多路选择器
    //这里有个例化时修改参数的语法
    mux4x1 #(.WL(WL)) mux4x1_u0(
        .IN0(IN0),.IN1(IN1),.IN2(IN2),.IN3(IN3),
        .SEL(SEL0),
        .OUT(OUT0)
    );
    mux4x1 mux4x1_u1(
        .IN0(IN0),.IN1(IN1),.IN2(IN2),.IN3(IN3),
        .SEL(SEL1),
        .OUT(OUT1)
    );
    mux4x1 mux4x1_u2(
        .IN0(IN0),.IN1(IN1),.IN2(IN2),.IN3(IN3),
        .SEL(SEL2),
        .OUT(OUT2)
    );
    mux4x1 mux4x1_u3(
        .IN0(IN0),.IN1(IN1),.IN2(IN2),.IN3(IN3),
        .SEL(SEL3),
        .OUT(OUT3)
    );f
endmodule

不仿真了。


优先编码器

常用的组合逻辑电路,如果多个条件同时成立,则按照最高优先级的条件输出。
任务:编写一个8位优先编码器,然后编译,看RTL View。

思路:以74148逻辑芯片为例,按照其真值表编写8-3优先编码器。

//8位优先编码器,Priority Encoder
module priorityencoder8x3(
    input [7:0]IN,
    output reg [2:0]OUT
);
    //生成的RTL View很难看……
    always @ (IN) begin
        casez(IN)
        //casez认为?可以代表0或1或z,而casex还包括x
        //据说,x为仿真初态,仿真会出问题?
        8'b0???????: OUT = 3'b000;
        8'b10??????: OUT = 3'b001;
        8'b110?????: OUT = 3'b010;
        8'b1110????: OUT = 3'b011;
        8'b11110???: OUT = 3'b100;
        8'b111110??: OUT = 3'b101;
        8'b1111110?: OUT = 3'b110;
        8'b11111110: OUT = 3'b111;
        default: OUT = 3'b111;
        endcase
    end
endmodule

图片太长,不好截图。(大概就是输入端接到一个8-256的译码器上,然后译码器的某些管脚做一个或运算,输出)


译码器

要求1:写一个4-16译码器。

module decoder4x16(
    input  [3:0] IN,
    output reg [15:0] OUT
);
    always @ (IN) begin
        case(IN)
            4'b0000: OUT = 8'b0000000000000001;
            4'b0001: OUT = 8'b0000000000000010;
            4'b0010: OUT = 8'b0000000000000100;
            4'b0011: OUT = 8'b0000000000001000;
            4'b0100: OUT = 8'b0000000000010000;
            4'b0101: OUT = 8'b0000000000100000;
            4'b0110: OUT = 8'b0000000001000000;
            4'b0111: OUT = 8'b0000000010000000;
            4'b1000: OUT = 8'b0000000100000000;
            4'b1001: OUT = 8'b0000001000000000;
            4'b1010: OUT = 8'b0000010000000000;
            4'b1011: OUT = 8'b0000100000000000;
            4'b1100: OUT = 8'b0001000000000000;
            4'b1101: OUT = 8'b0010000000000000;
            4'b1110: OUT = 8'b0100000000000000;
            4'b1111: OUT = 8'b1000000000000000;
        endcase
    end
endmodule

要求2:对比资源开销。

要求3:观看RTL图。

可见,代码生成了一个很漂亮的译码器。


加法器

加法器是很常见的电路,主要包括无符号加法器和补码加法器。

  • 无符号加法器,不包括符号位,常用于计数器累加和地址序号表示里
  • 补码加法器,最高位表示符号位(0+,1-),其余位置为数字的补码(正数不变,负数取反+1),便于数字电路进行加减法运算

无符号加法器

module unsignedadder3(
    input [3:0] IN1,
    input [3:0] IN2,
    output reg [4:0] OUT//多1位表示溢出
);
    always@(IN1 or IN2) begin
        OUT = IN1 + IN2;
    end
endmodule 

要求1:观察逻辑门电路造成的延迟,并说明,2比特的信号00翻转成11,能做到绝对的同时翻转么?

延迟大概比10ns少一点;很显然,不能同时翻转(万一运气好呢)。

要求2:将该加法器改成8位加法器,观察输出延迟。
代码略。

毛刺时间似乎增加了。想想也是,位数多了,就不靠谱了。

补码加法器

补码加法器的要点是:1、声明变量带一个signed,2、最高位为符号位,其余位为原数的补码。

module signedadder3(
    input signed [3:0] IN1,
    input signed [3:0] IN2,
    output reg signed [4:0] OUT//多1位表示溢出
);
    always@(IN1 or IN2) begin
        OUT = IN1 + IN2;
    end
endmodule 

要求1:把输出改成4位宽,问什么时候的输出才是正确的?
(这里采用不改代码,改仿真器的方法做实验。)

当且仅当,原来两数相加没有产生溢出时才是正确的,其他情况下都会出现奇怪的问题。图中,正常情况下,-2+-8=-10,然而最高符号位不见了,就变成了6……

要求2:把输入改成8位宽,对比波形图。

不会观察……反正毛刺持续时间更长了。

带流水线的加法器

要点,纯组合逻辑的延迟时间不一致,或者说过长。有时需要(基本上都要吧)让它们走上时序的“正轨”。——用D触发器等,把较大的组合逻辑变成小块的组合逻辑+时序逻辑。
示例代码如下:

module unsignedadderwithpipline(
    input [3:0] IN1,
    input [3:0] IN2,
    input CLK,
    output reg [4:0] OUT
);
    reg [3:0] in1_d1R, in2_d1R;
    reg [4:0] adder_out;

    always@(posedge CLK) begin // 生成D触发器的always块
        in1_d1R <= IN1;
        in2_d1R <= IN2;
        OUT     <= adder_out;
    end

    always@(in1_d1R or in2_d1R) begin // 生成组合逻辑的always 块
        adder_out = in1_d1R + in2_d1R;
    end
endmodule 

观察波形图如下:

确实要比组合逻辑实现的好看多了。


乘法器

乘法器是非常消耗逻辑器件的组合逻辑!尤其是对于那些内部木有乘法器的FPGA芯片而言。
(实验太多了……直接上代码吧)

module multiplier(
    input [8:0] IN1,
    input [8:0] IN2,
    output reg [15:0] OUT
);
    always@(IN1 or IN2) begin
        OUT = IN1 * IN2;
    end
endmodule

选择一个没有乘法器的芯片,观察资源消耗。

另一个自带乘法器的芯片,消耗资源为0。

RTL Viewer


计数器

计数器有这么一些接口功能:
1、计数溢出OV,2、计数时能EN,3、计数清零CLR,4、计数置数LOAD等。

实验要求:
1、设计一个最简单的计数器,输入为CLK,输出为OV,当计数到最大值时OV输出1。
代码如下:

module counter(
    input CLK,
    output reg OV,
    output reg [3:0] data
);
    reg [3:0] data_next;
    parameter MAX_VALUE = 9;

    always@(*) begin //下一状态的组合逻辑
		if(data<MAX_VALUE)
			data_next = data + 1;
		else
			data_next = 0;
    end

	always@(posedge CLK)begin //时序逻辑
		data <= data_next;
    end

    always@(data) begin //生成OV信号的组合逻辑
        if(data==MAX_VALUE)
            OV = 1;
        else
            OV = 0;
    end
endmodule

由图可知,没来9个上升沿,OV就由0变1,但是,与CLK的上升沿相比有一定延迟。

2、设计一个较为复杂的计数器,带有多种信号,其中CLR优先级最高,EN次之,LOAD最低。
代码如下:

module counter(
    input CLK,
    input EN,
    input CLR,
    input LOAD,
    input [3:0] ldata,
    input RST,
    output reg OV,
    output reg [3:0] data
);
    reg [3:0] data_next;
    parameter MAX_VALUE = 9;

    always@(*) begin
		if(CLR) begin           //清零有效
			data_next = 0;
		end
		else begin              //清零无效
			if(EN) begin        //始能有效
				if(LOAD) begin  //置数有效
					data_next = ldata;
				end
				else begin      //置数无效
					if(data<MAX_VALUE)
						data_next = data + 1;
					else
						data_next = 0;
				end
			end
		end
    end

	always@(posedge CLK or posedge RST)begin
		if(RST)                 //复位信号
			data <= 0;
		else
			data <= data_next;
    end

    always@(data) begin
        if(data==MAX_VALUE)
            OV = 1;
        else
            OV = 0;
    end
endmodule

状态机

要点:1、相当于C语言中的ifelse,2、尽量使用三段式写法。对于规范写法的状态机,EDA工具可以识别出来并优化。

实验要求:
设计一个用于识别2进制序列“1011”的状态机。
基本要求:
电路每个时钟周期输入1比特数据,当捕获到1011的时钟周期,电路输出1,否则输出0
使用序列101011010作为测试序列
扩展要求:
给你的电路添加输入使能端口,只有输入使能EN为1的时钟周期,才从输入的数据端口向内部获取1比特序列数据。

代码如下:

module fsm3(
	input CLK,
	input RST,
	input IN,
	input EN,
	output reg OUT
);

reg [3:0] state;
reg [3:0] next_state;

//计算下一状态
always @ (EN or IN or state) begin
	if(EN) begin
		case(state)
		4'b0000:if(IN) next_state = 4'b1000; else next_state = 4'b0000;
		4'b0001:if(IN) next_state = 4'b1000; else next_state = 4'b0000;
		4'b0010:if(IN) next_state = 4'b1001; else next_state = 4'b0001;
		4'b0011:if(IN) next_state = 4'b1001; else next_state = 4'b0001;
		4'b0100:if(IN) next_state = 4'b1010; else next_state = 4'b0010;
		4'b0101:if(IN) next_state = 4'b1010; else next_state = 4'b0010;
		4'b0110:if(IN) next_state = 4'b1011; else next_state = 4'b0011;
		4'b0111:if(IN) next_state = 4'b1011; else next_state = 4'b0011;
		4'b1000:if(IN) next_state = 4'b1100; else next_state = 4'b0100;
		4'b1001:if(IN) next_state = 4'b1100; else next_state = 4'b0100;
		4'b1010:if(IN) next_state = 4'b1101; else next_state = 4'b0101;
		4'b1011:if(IN) next_state = 4'b1101; else next_state = 4'b0101;
		4'b1100:if(IN) next_state = 4'b1110; else next_state = 4'b0110;
		4'b1101:if(IN) next_state = 4'b1110; else next_state = 4'b0110;
		4'b1110:if(IN) next_state = 4'b1111; else next_state = 4'b0111;
		4'b1111:if(IN) next_state = 4'b1111; else next_state = 4'b0111;
		endcase
	end
end

//计算输出
always @ (state) begin
  if(state == 4'b1011) 
    OUT = 1'b1;
  else 
    OUT = 1'b0;
end

//状态变换
always @ (posedge CLK or posedge RST)begin
  if(RST)
    state <= 4'b0000;
  else
    state <= next_state;
end

endmodule

在计算下一状态部分,我也尝试过使用以下写法:

next_state = {IN,state[3:1]};

然而,这种写法会导致毛刺的出现(为什么呢?)


移位寄存器

要点:移位寄存器通常用于串入并出,或并入串出……比如UART、SPI、I2C等常见的串行通信协议,把数据从FPGA内部送到外部芯片中去。

实验要求:
设计一个如本节“电路描述”部分的“带加载使能和移位使能的并入串出”的移位寄存器,电路的RTL结构图如“电路描述”部分的RTL结构图所示。

代码如下:

module p2sshifter(
    input [3:0] IN,
    input CLK,
    input RST,
    input LOAD,
    input EN,
    output OUT
);
    reg [3:0] data;
    reg [3:0] data_next;

    assign OUT = data[0];

    always@(EN or LOAD)begin
        if(EN)begin
            if(LOAD)begin
                data_next = IN;
            end
            else begin
                data_next = {1'b0,data[3:1]};
            end
        end
    end

    always@(posedge CLK or posedge RST)begin
        if(RST)
            data <= 0;
        else
            data <= data_next;
    end
endmodule

波形图如下:

输出了1010……,就这样吧。

posted @ 2018-11-18 16:25  失忆的真人  阅读(373)  评论(0)    收藏  举报