这是一份作业,为了完成任务而写,学习嘛。
目标: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; */ endendmodule
扔到Quartus II 9.0里进行编译,仿真,截图如下:
按我所认识的组合逻辑电路来说,当SEL发生变化的时候,OUT也应该或者很快地发生变化才对。然而,从仿真结果可以看到,SEL变化和OUT变化相差大概8个ns,可能这就是所谓的线路延迟吧。图中的“毛刺”,估计是由一个值变化到另一个值所产生的“中间值”吧。
交叉开关
交叉开关是多路选择器mux的一种变形,它可以用SELx表示选择输入INn到输出OUTn。
任务:编写一个4X4路交叉开关的RTL。//交叉开关,Crossbar Switchmodule 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) );fendmodule
不仿真了。
优先编码器
常用的组合逻辑电路,如果多个条件同时成立,则按照最高优先级的条件输出。
任务:编写一个8位优先编码器,然后编译,看RTL View。思路:以74148逻辑芯片为例,按照其真值表编写8-3优先编码器。
//8位优先编码器,Priority Encodermodule 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 endendmodule
图片太长,不好截图。(大概就是输入端接到一个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 endendmodule
要求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; endendmodule
要求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; endendmodule
要求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; endendmodule
观察波形图如下:
确实要比组合逻辑实现的好看多了。
乘法器
乘法器是非常消耗逻辑器件的组合逻辑!尤其是对于那些内部木有乘法器的FPGA芯片而言。
(实验太多了……直接上代码吧)module multiplier( input [8:0] IN1, input [8:0] IN2, output reg [15:0] OUT); always@(IN1 or IN2) begin OUT = IN1 * IN2; endendmodule
选择一个没有乘法器的芯片,观察资源消耗。
另一个自带乘法器的芯片,消耗资源为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
由图可知,没来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
状态机
要点: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 endend//计算输出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;endendmodule
在计算下一状态部分,我也尝试过使用以下写法:
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; endendmodule
波形图如下:
输出了1010……,就这样吧。