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……,就这样吧。