文章目录
基本电路
1位全加器
module full_adder(
input a,
input b,
input cin,
output sum,
output cout
);
assign {cout,sum}=a+b+cin;
endmoudle
| a |b | cin |sum | cout|
|–|–|–|–|–|–|–|–|–|–|–|–|–|–|
| 0 |0 | 0 |0 | 0|
| 0 |0 | 1 |1| 0|
| 0 |1 | 0 |1| 0|
| 0 |1 | 1 |0| 1|
| 1 |0 | 0 |1 | 0|
| 1 |0 | 1 |0| 1|
| 1 |1 | 0 |0| 1|
| 1 |1 | 1 |1| 1|
module full_adder(
input a,
input b,
input cin,
output sum,
output cout
);
assign sum=a^b^cin;
assign cout=(a&b)
|(cin&b)
|(cin&b);
endmoudle
4位全加器
module four_full_adder(
input [3:0] a;
input [3:0] b,
input cin,
output [3:0] sum,
output cout
);
assign {cout,sum}=a+b+cin;
endmoudle
4-1多路选择器
module four_mux(
input [3:0] a,
input [3:0] b,
input [3:0] c,
input [3:0] d,
input [1:0] sel,
output [3:0] mux_out
);
always@(*)
begin
case(sel)
2'b00:mux_out=a;
2'b01:mux_out=b;
2'b10:mux_out=c;
2'b11:mux_out=d;
endcase
end
endmodule
分频器
奇数分频器
通过时钟 相或 实现奇数分频
module any_odd_div(
input clk,
input rst_n,
output div_clk
);
parameter odd = 5;
reg [3:0] cnt1 , cnt2;
reg clk_tem_pos , clk_tem_neg;
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n) begin
cnt1 <= 4'b00;
clk_tem_pos <= 1'b0;
end
else begin
cnt1 <= (cnt1 == odd-1)?4'b0:cnt1+1'b1;
clk_tem_pos <= (cnt1< (odd-1)/2)?1'b1:1'b0;
end
end
always @ (negedge clk or negedge rst_n)
begin
if (!rst_n) begin
cnt2 <= 4'b00;
clk_tem_neg <= 1'b0;
end
else begin
cnt2 <= (cnt2 == odd-1)?4'b0:cnt2+1'b1;
clk_tem_neg <= (cnt2< (odd-1)/2)?1'b1:1'b0;
end
end
assign div_clk = clk_tem_pos|clk_tem_neg;
endmodule
仿真:
`timescale 1ns / 1ps
module any_odd_div_tb( );
reg clk;
reg rst;
//reg clk_out;//concurrent assignment to a non-net 'clk_out' is not permitted
wire clk_out;
initial
begin
clk=0;
rst=0;
#50;
rst=1;
end
always
begin
#5
clk=~clk;
end
any_odd_div u1(.clk(clk),.rst_n(rst),.div_clk(clk_out));
endmodule
偶数分频器
module inst (
input clk,
output clk_out);
//10010 1-0-0-1-0
reg [4:0] ctr;
always@(posedge clk)
begin
if (ctr<4) begin
ctr <= ctr+1'b1;
end
else begin
ctr <= 4'b1;
end
end
assign clk_out=(ctr<=2)?1'b0:1'b1;
endmodule
边沿检测
检测输入信号的边沿,当检测到信号由低到高时(posdege)对应信号输出为高。
Warning:异步信号打三拍才是边沿检测,前两拍是为了同步异步信号
module edge_detection(
input x,
input clk,
output y_pos,y_neg);
reg x_fron;
always@(posedge clk)
begin
x_fron<=x;
end
assign y_pos=~x_fron&x;//posedge
assign y_neg=x_fron&~x;//nededge
endmodule
仿真
module edge_dec_tb();
reg clk;
reg x_in;
wire y_pos,y_neg;
initial
begin
clk=0;
x_in=0;
#40
x_in=1;
#40
x_in=0;
end
always #5 clk=~clk;
edge_detection u2(.x(x_in),.clk(clk),.y_pos(y_pos),.y_neg(y_neg));
endmodule
序列检测
题目来源于网上
序列检测:要求检测输入码流中的“10010”,检测到则data_out输出1,。同时需要排除重叠检测,例如:
输入:1-0-0-1-0_0-1-0-0-1-0-XXXXX
输出:0-0-0-0-1_0-0-1-0-0-1-XXXXX
输出码流中的第二个1,就是重叠输出,需要排除。
二段式状态机:
有两个always block,把时序逻辑和组合逻辑分隔开来。时序逻辑里进行当前状态和下一状态的切换,组合逻辑实现各个输入、输出以及状态判断。
三段式状态机:
coding style:cur_state and next_state;
有三个always block,一个时序逻辑采用同步时序的方式描述状态转移,一个采用组合逻辑的方式判断状态转移条件、描述状态转移规律,第三个模块使用同步时序的方式描述每个状态的输出。时序逻辑的输出解决了两段式组合逻辑的毛刺问题。
简要概括就是:
**Moore状态机:**最后一种状态是已完成状态。
**Mealy状态机:**比Moore状态机少一种状态,最后一个状态同时判断输出。
三段式状态机:
第一段时序逻辑写出cur_state与next_state之间的传递关系
第二段写case,每种状态向下一状态转变的条件
第三段写输出
二段式状态机:
第一段同上
第二段同时写状态传递和输出。
两段式状态机
与三段式有所不同的是,out需为reg型
`timescale 1ns / 1ps
module seq_dec(
input seq_in,
input clk,
input rst_n,
//procedural assignment to a non-register test_out is not permitted
output reg test_out);
//定义六种状态
parameter IDLE = 6'b000001,
fir_1 = 6'b000010,
sec_10 = 6'b000100,
thir_100 = 6'b001000,
fou_1001 = 6'b010000,
out_10010 = 6'b100000;
reg [5:0] cur_state;
reg [5:0] next_state;
always@(posedge clk or negedge rst_n)
if (!rst_n) begin
//test_out <= 1'b0;重复定义
cur_state <= IDLE;
//next_state <= IDLE;//多次定义
end
else begin
cur_state <= next_state;
end
always@(*)
begin
case(cur_state)
IDLE :begin
next_state = (seq_in)? fir_1 : IDLE;
test_out=1'b0;
end
fir_1 :begin
next_state = (seq_in)? fir_1 : sec_10;
test_out=1'b0;
end
sec_10 :begin
next_state = (seq_in)? fir_1 : thir_100;
test_out=1'b0;
end
thir_100 :begin
next_state = (seq_in)? fou_1001 : IDLE;
test_out=1'b0;
end
fou_1001 :begin
next_state = (seq_in)? fir_1 : out_10010;
test_out=1'b0;
end
out_10010 :begin
next_state = (seq_in)? fir_1 : IDLE ;
test_out=1'b1;
end
default :begin
next_state = IDLE ;
test_out=1'b0;
end
endcase
end
endmodule
三段式状态机
module seq_dec(
input seq_in,
input clk,
input rst_n,
output test_out);
//定义六种状态
parameter IDLE = 6'b000001,
fir_1 = 6'b000010,
sec_10 = 6'b000100,
thir_100 = 6'b001000,
fou_1001 = 6'b010000,
out_10010 = 6'b100000;
reg [5:0] cur_state;
reg [5:0] next_state;
always@(posedge clk or negedge rst_n)
if (!rst_n) begin
//test_out <= 1'b0;重复定义
cur_state <= IDLE;
//next_state <= IDLE;//多次定义
end
else begin
cur_state <= next_state;
end
always@(*)
begin
case(cur_state)
IDLE :next_state = (seq_in)? fir_1 : IDLE;
fir_1 :next_state = (seq_in)? fir_1 : sec_10;
sec_10 :next_state = (seq_in)? fir_1 : thir_100;
thir_100 :next_state = (seq_in)? fou_1001 : IDLE;
fou_1001 :next_state = (seq_in)? fir_1 : out_10010;
out_10010 :next_state = (seq_in)? fir_1 : IDLE ;
default :next_state = IDLE ;
endcase
end
assign test_out=(cur_state == out_10010)?1'b1:1'b0;
endmodule
Testbench
`timescale 1ns / 1ps
module seq_dec_tb( );
reg seq_in;
reg clk;
reg rst_n;
wire test_out;
initial
begin
clk=0;
seq_in=0;
rst_n=0;
#5
seq_in=1;
rst_n=1;//复位信号别忘了拉起来
#10
seq_in=0;
#10
seq_in=0;
#10
seq_in=0;
#10
seq_in=1;
#10
seq_in=0;
#10
seq_in=0;
#10
seq_in=1;
#10
seq_in=0;
#10
seq_in=0;
#10
seq_in=1;
end
always #5 clk=~clk;
seq_dec u1(.seq_in(seq_in),.clk(clk),.rst_n(rst_n),.test_out(test_out));
reg [127:0] next_state_name;
always@(*) begin
case(u1.cur_state)//直接用模块例化的名字
6'b000001: next_state_name= "IDLE" ;
6'b000010: next_state_name= "fir_1";
6'b000100: next_state_name= "sec_10";
6'b001000: next_state_name= "thir_100";
6'b010000: next_state_name= "four_1001";
6'b100000: next_state_name= "out_10010";
default : next_state_name= "IDLE" ;
endcase
end
endmodule
移位寄存器法
`timescale 1ns / 1ps
//用寄存器保存每次进入序列的值,当总长度为10010,输出为1
//同时要在序列为10010清空reg的值,避免重叠输出
module seq_dec(
input seq_in,
input clk,
input rst_n,
output test_out);
reg [4:0] ud_test;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
ud_test<=5'b0;
else
ud_test<=(ud_test==5'b10010)?{4'b0,seq_in}:{ud_test[3:0],seq_in};
end
assign test_out=(ud_test==5'b10010)?1'b1:1'b0;
endmodule
4×4查表乘法器
查表法的核心就是将乘法的所有可能结果存储起来,然后将两个相乘的数据组合起来作为地址,直接找到相应的结果。
4位乘法器的实现:调用2位乘法器,即将高位宽数据分解成低位宽的数据再调用查表乘法器。
对于 2N 位数据A,可分解为 A=A1×2^N+A0 , A1为高N位,A0为低N位
对于2位乘法器,A = 2×A1 + A0,B = 2×B1 + B0;
乘法展开:A×B = 4×A1×B1 + 2×A1×B0 + 2×A0×B1 + A0×B0;
对于4位乘法器,A = 4×A1+ A0 ,B = 4×B1 + B0;
乘法展开:A×B = 16×A1×B1 + 4×A1×B0 + 4×A0×B1 + A0×B0;
`timescale 1ns / 1ns
module inst(
input rst_n,
input [3:0] a,
input [3:0] b,
output wire [7:0] mul4
);
//A×B = 16×A1×B1 + 4×A1×B0 + 4×A0×B1 + A0×B0
//reg [3:0] a1b1;concurrent assignment to a non-net
wire [3:0] a1b1;
wire [3:0] a1b0;
wire [3:0] a0b1;
wire [3:0] a0b0;
//实列化
MULT2x2 u1(.rst_n(rst_n),.a(a[3:2]),.b(b[3:2]),.mul(a1b1));
MULT2x2 u2(.rst_n(rst_n),.a(a[3:2]),.b(b[1:0]),.mul(a1b0));
MULT2x2 u3(.rst_n(rst_n),.a(a[1:0]),.b(b[3:2]),.mul(a0b1));
MULT2x2 u4(.rst_n(rst_n),.a(a[1:0]),.b(b[1:0]),.mul(a0b0));
//assign mul4 = (!rst_n)? 8'b0:{a1b1,4'b0}+{a1b0,3'b0}+{a0b1,3'b0}+a0b0;
assign mul4 = (!rst_n)? 8'b0:{a1b1,4'b0}+{a1b0,2'b0}+{a0b1,2'b0}+a0b0;
endmodule
module MULT2x2(
input rst_n,
input [1:0] a,//A = 2×A1 + A0
input [1:0] b,//B = 2×B1 + B0
output reg[3:0] mul
);
always @(*) begin
if (!rst_n)
mul=4'd0;
else begin
case({a,b})//case后不跟begin
4'b0000:mul=4'd0;
4'b0001:mul=4'd0;
4'b0010:mul=4'd0;
4'b0011:mul=4'd0;
4'b0100:mul=4'd0;
4'b0101:mul=4'd1;
4'b0110:mul=4'd2;
4'b0111:mul=4'd3;
4'b1000:mul=4'd0;
4'b1001:mul=4'd2;
4'b1010:mul=4'd4;
4'b1011:mul=4'd6;
4'b1100:mul=4'd0;
4'b1101:mul=4'd3;
4'b1110:mul=4'd6;
4'b1111:mul=4'd9;
endcase
end
end
endmodule
testbench
`timescale 1ns / 1ns
module inst_tb();
reg [3:0]a;
reg [3:0]b;
reg rst_n;
wire [7:0] mul;
initial
begin
#5
rst_n=0;
#5
rst_n=1;
a=4'd2;
b=4'd3;
#5
a=4'd10;
b=4'd3;
#5
a=4'd9;
b=4'd3;
#5
a=4'd2;
b=4'd6;
#5
a=4'd2;
b=4'd4;
#5
a=4'd2;
b=4'd7;
#5
a=4'd2;
b=4'd5;
#5
a=4'd5;
b=4'd3;
end
inst zpz1 (.rst_n(rst_n),.a(a),.b(b),.mul4(mul));
endmodule
64bits检测为1的最低位
module low_bit_dec(
input [63:0] data,
output reg [5:0] out_nm
);
integer i;
always @(*)
begin
for (i=0; i < 63 ; i=i+1)
if (data[i] == 1'b1)
out_nm = i;
else
//wrong: out_nm= 6'bx;
out_nm= out_nm;
end
endmodule
两个8位数相乘
采用循环,一个数的每位与对应数相与后相移相加。
`timescale 1ns / 1ns
//
// Company:
// Engineer:
//
// Create Date: 2022/02/24 18:47:02
// Design Name:
// Module Name: inst
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module inst(
input [7:0] data1,
input [7:0] data2,
input rst_n,
output [15:0] mul_ans
);
reg [15:0] mid_ans;
reg [15:0]
reg [7:0] low_ans;
integer i;
always @(*) begin
if(!rst_n) begin
mid_ans=16'b0;
low_ans=8'b0;
end
else begin
for (i=1; i < 8 ; i=i+1)
mid_ans=(data1[i])? (mid_ans + (data2 << i)):mid_ans;
low_ans={8{data1[0]}}&data2;
end
end
assign mul_ans=mid_ans+low_ans;
endmodule
结果有误错误原因:每次的值未清零,包含上一次的值即182+32*13=598
改:
`timescale 1ns / 1ns
module 8x8mul(
input [7:0] data1,
input [7:0] data2,
input rst_n,
output [15:0] mul_ans
);
reg [15:0] mid_ans;
wire [15:0] ap_data2;
reg [7:0] low_ans;
assign ap_data2=data2;
integer i;
always @(*) begin
mid_ans=16'b0;//在同一个块中多次赋值不出错
if(!rst_n) begin
mid_ans=16'b0;
low_ans=8'b0;
end
else begin
for (i=1; i < 8 ; i=i+1)
mid_ans=(data1[i])? (mid_ans + (ap_data2 << i)):mid_ans;
low_ans={8{data1[0]}}&data2;
end
end
assign mul_ans=mid_ans+low_ans;
endmodule
十进制计数器
module 10counter(
input clk,
input rst_n,
output reg [3:0] ctr
);
always @(posedge clk) begin
if (!rst_n)
ctr=4'b0;
else
ctr=((ctr+1'b1)==4'b1010)?4'b0:ctr+1'b1;
end
endmodule
DFF描述
module DFF(
input clk,
input rst_n,
input d,
output reg q
);
always @(posedge clk) begin
if (!rst_n)
q<=1'b0;
else
q<=d;
end
endmodule
比较电路——保留最大值
module comparator(
input clk,
input rst_n,
input [7:0] a,
input [7:0] b,
output reg [1:0] q
);
always @(*) begin
if (a > b)
q <= 2'b01;
else if (a == b)
q <= 2'b00;
else
q <= 2'b10;
end
endmodule
独热码检测
给定一个4bit的信号A,设计逻辑来判断A是不是独热码,设输出为Y,如果A是独热码,则Y输出1,如果不是,则输出0.
思路是将各个位数取和
module one_hot(
input clk,
input rst_n,
input [3:0] a,
output reg q
);
always @(*) beginone_hot
if ((a[0]+a[1]+a[2]+a[3]) == 1)
q <= 1'b1;//one hot bit
else
q <= 1'b0;
end
endmodule
可置位七进制循环计数器
module load_cnt(
input clk,
input rst_n,
input load,
output reg [2:0] out
);
parameter load_N = 2;
always @(posedge clk) begin
if (!rst_n)
out='b0;
else if(load)
out = load_N;
else
out = ((out+1'b1)==7)?'b0:out+'b1;
end
endmodule
奇偶校验位
奇偶校验位是一个表示给定位数的二进制数中1的个数是奇数或者偶数的二进制数,奇偶校验位是最简单的错误检测码。
按位异或:偶校验
按位异或取反:奇校验
消除毛刺
出现毛刺的原因
当信号在FPGA器件内部通过连线和逻辑门时,一般都有一定的延时。延时的大小与连线的长短和门单元的数目有关,同时还受器件的制造工艺、工作电压、温度等条件的影响。此外,信号的高/低电平转换也需要一定的过渡时间。由于存在这些因素的影响,多路信号的电平值发生变化时,在信号变化的瞬间,组合逻辑的输出都有先后顺序,而并不是同时变化,这往往就会出现一些不正确的”毛刺”。
“毛刺”信号的电路有两种类型。第一种是输入信号经过FPGA内部布线以后产生不同延时的异步电路;第二种是由于在 编程时内部信号变化落后。在多个信号关联时,第一个信号变化如果发生落后情况,后面的信号将产生不确定状况,从而出现”毛刺”。 “毛刺”的存在说明该电路存在不稳定状况,因此,这很可能导致整个系统的误动作。
解决办法:
组合逻辑电路:在电路中加入同步时钟,即输入输出加上触发器,使输出信号跟随时钟同步变化。
程序中的内部信号的判别赋值并不是立即发生变化,而是存在一个延迟。这也是最容易产生”毛刺”的一种情况:采用加触发器的方法来消除”毛刺”
串并转换
module inst(
input clk,
input rst_n,
input a,
output reg [7:0] b
);
always @(posedge clk) begin
if (!rst_n)
b='b0;
else
b = {b[6:0],a};
end
endmodule
按键消抖15ms
机械按键在按下和弹起时会出现短时间抖动,抖动时间一般持续为15ms。
在按键较少的设计中,往往会进行按键的复用。常用的方法是根据键按下时间的长与短进行判断。因此按键复用经常是利用按键消抖后的下降沿(如果键按下是高电位的话)。
假设按键为key_in,设置一个采样寄存器sw_rst记录上一个时钟的值
判断key_in与上一个寄存器的值是否一致,如果不一致则表明可能产生按键的动作(上升沿下降沿检测),将按键动作 寄存到edge_en,edge_n = 1表明产生按键的动作
后启动15ms计数器计数,15ms延时后,将key_in值赋给out
module key (
input clk,
input rst_n,
input key_in,
output reg sw_out
);
reg sw_rst;//按键本身为1,按下后为0
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sw_rst <= 1'b1;
end
else begin
sw_rst <= key_in;
end
end
wire pos_ed,neg_ed;
assign pos_ed = !sw_rst & key_in;//上升沿
assign neg_ed = sw_rst & !key_in; //下降沿
wire edge_en;
assign edge_en = pos_ed | neg_ed; //检测键值是否发生变化(上升沿和下降沿)
reg [19:0] cout ;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
count <= 0;
end
else if(edge_en) begin
count <= 0;
end
else begin
count <= count + 1;
end
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
sw_out <= 1'b1;
else
sw_out <= key_in;
end
endmodule