串行进位加法器
`timescale 1ns/1ns
`timescale 1ns/1ns
module add_half(
input A ,
input B ,
output wire S ,
output wire C
);
assign S = A ^ B;
assign C = A & B;
endmodule
/***************************************************************/
module add_full(
input A ,
input B ,
input Ci ,
output wire S ,
output wire Co
);
wire S_tmp, C_tmp1,C_tmp2;
add_half u_add_half1(
.A(A) ,
.B(B) ,
.S(S_tmp) ,
.C(C_tmp1)
);
add_half u_add_half2(
.A(S_tmp) ,
.B(Ci) ,
.S(S) ,
.C(C_tmp2)
);
assign Co = C_tmp1 | C_tmp2;
endmodule
module add_4(
input [3:0] A ,
input [3:0] B ,
input Ci ,
output wire [3:0] S ,
output wire Co
);
wire [4:0] C_tmp;
assign C_tmp[0] = Ci;
genvar i;
generate
for(i=0;i<=3;i=i+1) begin: add_serial
add_full u_add_full(.A(A[i]), .B(B[i]), .Ci(C_tmp[i]), .S(S[i]), .Co(C_tmp[i+1]));
end
endgenerate
assign Co = C_tmp[4];
endmodule
异步复位同步释放
复位可以在任何时候发生,表面上看跟时钟没有关系,但真实情况是异步复位也需考虑时钟跳变沿,因为时钟沿变化和异步复位都可以引起Q端数据变化。同步复位虽然解决了当时钟的有效沿来临的时候rst的边沿也正好来临所出现的冒险与竞争。但是从综合的电路上可以看出,多了一个组合逻辑MUX。如果设计中所有的复位都是这样的,那会增加很多的资源,导致芯片面积很大,而异步复位同步释放机制可以解决这一问题。
异步复位同步释放的主要目的就是在减少资源损耗的同时避免毛刺的出现。其机制如下:
异步复位会出现亚稳态是因为无法保证clk的上升沿时,rst释放信号能够满足其建立保持时间,触发器无法判断rst有效还是无效,因而出现亚稳态;而同步释放的处理机制可以保证rst释放信号的建立时间点与clk的posedge同步,在当前clk的posedge检测到rst稳定的低电平,在下一个clk的posedge可以检测到已经稳定的rst高电平,满足了rst的建立保持时间要求,从而不会出现亚稳态。
所谓“异步复位”是针对D触发器的复位端口,它是异步的,所谓“同步释放”,实际上是由于设计了同步逻辑电路,外部复位信号不会在出现释放时与clk信号竞争,整个系统将与全局时钟clk信号同步。
`timescale 1ns/1ns
module ali16(
input wire clk,
input wire rst_n,
input wire d,
output reg dout
);
reg rst0,rst1;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
rst0 <= 0;
rst1 <= 0;
end
else begin
rst0 <= 1;
rst1 <= rst0;
end
end
always @ (posedge clk or negedge rst1)begin
if(!rst1) begin
dout <= 1'b0;
end
else begin
dout <= d;
end
end
endmodule
实际写起来和按键消抖差不多
求最小公倍数
任意奇数倍时钟分频
题目要求实现任意奇数倍数的分频,实现分频的基本方法是采用计数器,使用输入时钟信号驱动计数器。例如实现偶数倍的n分频时,每当计数器从0计数到n/2 - 1时,输出时钟信号跳变,同时计数器归零从新开始计数。
题目要求的是奇数倍分频,且要求占空比为50%,则需要稍加调整:以五分频为例,需要输出时钟信号保持2.5个输入时钟的1,然后保持2.5个时钟的0。比如在第一个时钟的上升沿跳变为1,则在第三个时钟的下降沿跳变为0,在第六个时钟的上升沿再一次跳变为1。即需要对上升/下降两个时钟沿的进行计数。
由此得出实现奇数倍数分频的方法:加入两个中间信号flag_1,flag_2,分别使用输入时钟信号的上升/下降沿驱动。以五分频为例,flag_1的变化由上升沿驱动,flag_2的变化由下降沿驱动。当计数器计数到(n-1)/2=2和(n-1)=4时,flag_1、flag_2发生跳变(即取反)。再将flag_1,flag_2相或即可得到分频之后的时钟信号。
`timescale 1ns/1ns
module clk_divider
#(parameter dividor = 5)
( input clk_in,
input rst_n,
output clk_out
);
//定义计数器的位宽,$clog2()为取对数操作,在编译过程中执行完成。因此在模块运行过程中CNT_WIDTH是一个确定的数值。
parameter CNT_WIDTH = $clog2(dividor-1);
reg flag_1;
reg flag_2;
reg [CNT_WIDTH :0] cnt;
always @(posedge clk_in or negedge rst_n)
if (!rst_n)
cnt <= 0;
else if(cnt == dividor-1)
cnt <= 0;
else cnt <= cnt + 1'd1;
always @(posedge clk_in or negedge rst_n)
if (!rst_n)
flag_1 <= 0;
else if(cnt == (dividor-1>>1))
flag_1 <= ~flag_1;
else if(cnt == dividor-1)
flag_1 <= ~flag_1;
else flag_1 <= flag_1;
always @(negedge clk_in or negedge rst_n)
if (!rst_n)
flag_2 <= 0;
else if(cnt == (dividor-1 >>1))
flag_2 <= ~flag_2;
else if(cnt == dividor-1)
flag_2 <= ~flag_2;
else flag_2 <= flag_2;
assign clk_out = flag_1 || flag_2;
endmodule
编写乘法器求解算法表达式
注意一点,+优先级高于<<,因此别写成a<<3 + a<<2就行,否则逻辑出错。
12为1100,可以为10000-1-1-10,也可为1000+100
5为0101
全加器
① 请用题目提供的半加器实现全加器电路①
半加器的参考代码如下,可在答案中添加并例化此代码。
module add_half( input A , input B , output wire S , output wire C ); assign S = A ^ B; assign C = A & B; endmodule
s代表本位,C代表进位
对于半加器,只有输入a,b,输出和进位表示为:
S=a^b; ①
C=a&b; ②
全加器,在a,b的基础上增加了进位ci输入:
S=a^b^ci; ③三个异或在一起,为本位
C=a&b+ci(a^b);④
先将全加器中的a,b放入半加器中例化,将输入分别代入①②得到:
S1=a^b; ⑤
C1=a&b; ⑥
接下来将S1和Ci放入半加器进行例化,将输入分别代入①②得到:
S2=S1^ci=a^b^ci; ⑦
C2=S1&ci=(a^b)&ci; ⑧
⑦⑧为例化两次的输出,③④是真正全加器的输出,③和⑦完全相等,因此例化两次后的输出可以作为全加器的输出;
⑧是全加器④的后半部分,还需要加上⑥,才组成一个完整的全加器进位输出,这就是为什么最后C需要把前两次的进位结果或在一起;
①先将A和B用半加器加起来,生成和是s[0]和进位信号c[0];
②然后第一个半加器生成的和s[0]与Ci用半加器相加,得到的和s[1]即为全加器的和S;
③全加器进位信号的生成是这样:两次半加器产生的进位信号有一个为1,则全加器的进位信号Co为1。
乘法与位运算
本题考查的是简单的位运算。首先,考虑11111011是什么,其值为251,在乘法运算中可以将其分解为256 -4 -1,那么为什么是减去4和1呢 因为它们分别对应8‘b100和8’b1。
在位运算中,2的整数倍运算可以直接使用移位进行,这样可以极大的减少资源占用
观察乘数的特点: 1111_1011 = 1_0000_0000 - 1 - 100;因为1_0000_0000 - 1 = 1111_1111,再减去100,故为题目中的乘数。
“-”的优先级比“<<”高'
移8位就意味着二进制后面跟着8个0
向左移8位,就是在右侧填充8个0
序列检测器(Moore型)
实现相同的功能时,Mealy型比Moore型能节省一个状态(大部分情况下能够节省一个触发器资源),Mealy型比Moore型输出超前一个时钟周期。
`timescale 1ns/1ns
module det_moore(
input clk ,
input rst_n ,
input din ,
output reg Y
);
parameter idle=0,s1=1,s2=2,s3=3,s4=4;
reg[2:0]cs;
reg[2:0]ns;
always@(posedge clk,negedge rst_n)begin
if(!rst_n)
cs<=idle;
else
cs<=ns;
end
always@(*)begin
case(cs)
idle:ns=din==1?s1:idle;
s1:ns=din==1?s2:idle;
s2:ns=din==0?s3:idle;
s3:ns=din==1?s4:idle;
s4:ns=din==1?s1:idle;
endcase
end
always@(posedge clk,negedge rst_n)begin
if(!rst_n)
Y<=0;
else
Y<=cs==s4;
end
endmodule
脉冲同步器(快到慢)
`timescale 100ps/100ps
module pulse_detect(
input clka ,
input clkb ,
input rst_n ,
input sig_a ,
output sig_b
);
reg data1,data2,data3,data4;
always@(posedge clka,negedge rst_n)begin
if(!rst_n)
data1<=0;
else
data1<=sig_a?!data1:data1;
end
always@(posedge clkb,negedge rst_n)begin
if(!rst_n)begin
data2<=0;
data3<=0;
data4<=0;
end
else begin
data2<=data1;
data3<=data2;
data4<=data3;
end
end
assign sig_b=data3^data4;
endmodule
同步FIFO
设计FIFO的时候一般需要考虑的有两点:
1. FIFO的大小 FIFO的大小指就是双端口ram的大小,这个可以根据设计需要来设置。
2. FIFO空满状态的判断 FIFO空满状态的判断通常有两种方法。
a、FIFO中的ram一般是双端口ram,所以有独立的读写地址。因此可以一种是设置读,写指针,写指针指向下一个要写入数据的地址,读指针指向下一个要读的地址,最后通过比较读指针和写指针的大小来确定空满状态。
b、设置一个计数器,当写使能有效的时候计数器加一;当读使能有效的时候,计数器减一,将计数器与ram的size进行比较来判断fifo的空满状态。这种方法设计比较简单,但是需要的额外的计数器,就会产生额外的资源,而且当fifo比较大时,会降低fifo最终可以达到的速度。
module dual_port_RAM #(parameter DEPTH = 16,
parameter WIDTH = 8)(
input wclk
,input wenc
,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。
,input [WIDTH-1:0] wdata //数据写入
,input rclk
,input renc
,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。
,output reg [WIDTH-1:0] rdata //数据输出
);
reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
always @(posedge wclk) begin
if(wenc)
RAM_MEM[waddr] <= wdata;
end
always @(posedge rclk) begin
if(renc)
rdata <= RAM_MEM[raddr];
end
endmodule
module sfifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input clk ,
input rst_n ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,
output reg wfull ,
output reg rempty ,
output wire [WIDTH-1:0] rdata
);
parameter ADDR_WIDTH = $clog2(DEPTH);
/**********************addr bin gen*************************/
reg [ADDR_WIDTH:0] waddr;
reg [ADDR_WIDTH:0] raddr;
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
waddr <= 'd0;
end
else if(!wfull && winc)begin
waddr <= waddr + 1'd1;
end
end
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
raddr <= 'd0;
end
else if(!rempty && rinc)begin
raddr <= raddr + 1'd1;
end
end
/**********************full empty gen*************************/
wire [ADDR_WIDTH : 0] fifo_cnt;
assign fifo_cnt = (waddr[ADDR_WIDTH] == raddr[ADDR_WIDTH]) ? (waddr[ADDR_WIDTH:0] - raddr[ADDR_WIDTH:0]) :
(DEPTH + waddr[ADDR_WIDTH-1:0] - raddr[ADDR_WIDTH-1:0]);
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
wfull <= 'd0;
rempty <= 'd0;
end
else if(fifo_cnt == 'd0)begin
rempty <= 1'd1;
end
else if(fifo_cnt == DEPTH)begin
wfull <= 1'd1;
end
else begin
wfull <= 'd0;
rempty <= 'd0;
end
end
/**********************RAM*************************/
wire wen ;
wire ren ;
wire wren;//high write
assign wen = winc & !wfull;
assign ren = rinc & !rempty;
dual_port_RAM #(.DEPTH(DEPTH),
.WIDTH(WIDTH)
)dual_port_RAM(
.wclk (clk),
.wenc (wen),
.waddr(waddr[ADDR_WIDTH-1:0]), //深度对2取对数,得到地址的位宽。
.wdata(wdata), //数据写入
.rclk (clk),
.renc (ren),
.raddr(raddr[ADDR_WIDTH-1:0]), //深度对2取对数,得到地址的位宽。
.rdata(rdata) //数据输出
);
endmodule
双端口RAM
深度代表RAM的数量,即存储数据的个数,宽度表示存储数据的宽度
然后含有两个读写使能,读写操作,由读写时钟控制
十六进制计数器
`timescale 1ns/1ns
module counter_16(
input clk ,
input rst_n ,
output reg [3:0] Q
);
always@(posedge clk,negedge rst_n)begin
if(!rst_n)
Q<=0;
else
Q<=Q+1;
end
endmodule
之所以可以直接加,是因为Q被限制为4位,所以到1111再加1后直接归0
超前进位加法器
原来的串行进位加法器需要每一位计算以后,进位端输出端CO 连接到下一位的进位输入端CI来进行计算。
这样做的话一个运算需要经过4个一位全加器的延迟时间。
而超前进位加法器与串行进位加法器的不同之处在于,超前进位加法器的进位是事先通过逻辑电路就可以的出每一位全加器的进位输入CI。
全加器的逻辑表达式为:
S = A ^ B ^ CI ;
CO = AB + (A+B)CI
令G=AB 作为进位生成函数G ,P=A+B 作为进位传送函数P 那么CO可以改写为:
CO = G +P CI ;
那么对于每一位的进位可以展开为:
第一步是CLA_4通过输入的数值,一次性算出四个加法器的进位。第二步,由于已经确定了每位的进位数值,ADD1分别例化四次,对四位同时进行加法。
状态机与时钟分频
因为是1/4分频,1/4占空比,所以直接在某一个周期亮起就ok了
二段式
`timescale 1ns/1ns
module huawei7(
input wire clk ,
input wire rst ,
output reg clk_out
);
//*************code***********//
parameter [1:0] s0 = 2'b00,
s1 = 2'b01,
s2 = 2'b10,
s3 = 2'b11;
reg [1:0] state, next_state;
always @ (posedge clk, negedge rst) begin
if(!rst)
state <= s0;
else
state <= next_state;
end
always @ (state) begin
case(state)
s0: begin
next_state <= s1;
clk_out <= 1'b0;
end
s1: begin
next_state <= s2;
clk_out <= 1'b1;
end
s2: begin
next_state <= s3;
clk_out <= 1'b0;
end
s3: begin
next_state <= s0;
clk_out <= 1'b0;
end
default: begin
next_state <= s0;
clk_out <= 1'b0;
end
endcase
end
//*************code***********//
endmodule
三段式
`timescale 1ns/1ns
module huawei7(
input wire clk ,
input wire rst ,
output reg clk_out
);
//*************code***********//
parameter s0=0,s1=1,s2=2,s3=3;
reg[1:0]cs;
reg[1:0]ns;
always@(posedge clk,negedge rst)begin
if(!rst)
cs<=s0;
else
cs<=ns;
end
always@(*)begin
case(cs)
s0:ns<=s1;
s1:ns<=s2;
s2:ns<=s3;
s3:ns<=s0;
endcase
end
always@(posedge clk,negedge rst)begin
if(!rst)
clk_out<=0;
else
clk_out<=ns==s1;
end
//*************code***********//
endmodule
时钟切换
`timescale 1ns/1ns
module huawei6(
input wire clk0 ,
input wire clk1 ,
input wire rst ,
input wire sel ,
output wire clk_out
);
reg q0, q1;
always@(negedge clk0 or negedge rst)
if(!rst)
q0 <= 0;
else
q0 <= ~sel & ~q1;
always@(negedge clk1 or negedge rst)
if(!rst)
q1 <= 0;
else
q1 <= sel & ~q0;
assign clk_out = (q0 & clk0) | (q1 & clk1);
endmodule
电路图
两个D触发器,四个与门,1个或门
这个代码生成确定Q0的D触发器,然后D信号为通过两个非门后的信号再通过一个与门,之后接到D信号上,输出Q0这个代码生成确定Q1的D触发器,
即上面的D触发器确定Q1,下面的D触发器确定Q0,
最后这个ASSIGN为逻辑电路,生成两个与门,一个或门
并串转换
设计一个模块进行并串转换,要求每四位d输为转到一位dout输出,输出valid_in表示此时的输入有效
串并转换操作是非常灵活的操作,核心思想就是移位。串转并就是把1位的输入放到N位reg的最低位,然后N位reg左移一位,在把1位输入放到左移后的reg的最低位,这样循环,就可以得到,以最高位开始传输,最低位传输完成的N位数据了;并转串就是把并行的N位数据的最高位给1位输出,然后类似的循环左移就可以了。
`timescale 1ns/1ns
module huawei5(
input wire clk ,
input wire rst ,
input wire [3:0]d ,
output wire valid_in ,
output wire dout
);
//*************code***********//
reg [3:0] data = 'd0;
reg [1:0]cnt;//计数
reg valid;
assign dout = data[3];//data的最高位接输出线
assign valid_in =valid;
always @(posedge clk or negedge rst) begin
if(!rst)begin
data<= 'd0;
cnt <= 'd0;
valid <= 'd0;
end
else begin
if (cnt == 'd3) begin
data <= d;//这里d是在cnt清零时给到data上传的
cnt <= 'd0;
valid <= 1;
end
else begin
cnt <= cnt + 'd1;
valid <= 0;
data <= {data[2:0],data[3]};//核心也是循环左移
end
end
end
//*************code***********//
endmodule
并转串就是输出移位寄存器的最高位,串转并是不断输入到移位寄存器的最低位
序列检测器
`timescale 1ns/1ns
module sequence_generator(
input clk,
input rst_n,
output reg data
);
reg[5:0]ram;
always@(posedge clk,negedge rst_n)begin
if(!rst_n)
ram<=0;
else
ram<={ram[4:0],data};
end
always@(posedge clk,negedge rst_n)begin
if(!rst_n)
data<=0;
else
data<=ram==6'b001011;
end
endmodule
序列发生器
用一个数据存储序列,不断移位,然后一直发送某一位
`timescale 1ns/1ns
module sequence_generator(
input clk,
input rst_n,
output reg data
);
reg[5:0]ram;
always@(posedge clk,negedge rst_n)begin
if(!rst_n)
ram<=6'b001011;
else
ram<={ram[4:0],ram[5]};
end
always@(posedge clk,negedge rst_n)begin
if(!rst_n)
data<=0;
else
data<=ram[5];
end
endmodule
自动售卖机
MEALY机
inp的前一位表示买的饮料,后2位表示投入的钱的数量
s1表示售卖机已经有了5块钱,如果买饮料1,那么一定不用继续投
输出信号并起来的,前两位表示饮料种类,后1位表示找0数量
如果不投钱,那么S1保持为S1,除了初始状态下投钱,S1状态下投钱都会返回到初始状态,
`timescale 1ns/1ns
module sale(
input clk ,
input rst_n ,
input sel ,//sel=0,5$dranks,sel=1,10&=$drinks
input [1:0] din ,//din=1,input 5$,din=2,input 10$
output reg [1:0] drinks_out,//drinks_out=1,output 5$ drinks,drinks_out=2,output 10$ drinks
output reg change_out
);
parameter IDLE = 'd0;
parameter S0 = 'd1;
reg n_state,c_state;
wire [2:0] inp;
assign inp = {sel,din};
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
c_state <= IDLE;
else
c_state <= n_state;
end
always@(*)
case (c_state)
IDLE : n_state = (inp=='b101) ? S0 : IDLE;
S0 : n_state = (din=='b0) ? S0 : IDLE;
default:n_state = IDLE;
endcase
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
{drinks_out,change_out} <= 'b00;
else begin
if(c_state== S0) begin
if(inp=='b101)
{drinks_out,change_out} <= 'b100;
else if(inp == 'b110)
{drinks_out,change_out} <= 'b101;
else
{drinks_out,change_out} <= 'b0;
end
else begin
if(inp=='b001)
{drinks_out,change_out} <= 'b010;
else if(inp == 'b010)
{drinks_out,change_out} <= 'b011;
else if(inp == 'b110)
{drinks_out,change_out} <= 'b100;
else
{drinks_out,change_out} <= 'b0;
end
end
end
endmodule
MOORE机
`timescale 1ns/1ns
module sale(
input clk ,
input rst_n ,
input sel ,//sel=0,5$dranks,sel=1,10&=$drinks
input [1:0] din ,//din=1,input 5$,din=2,input 10$
output reg [1:0] drinks_out,//drinks_out=1,output 5$ drinks,drinks_out=2,output 10$ drinks
output reg change_out
);
//------------------------//
//这个题目有个坑,就是当sel=1,din=1时,即选择购买B饮料且投币5元时,正常输出应该是
//drinks_out=0,change_out=1;即无饮料输出且找零5元(退钱)。
//但是,这个题目要求钱不够时,必须要继续投币,继续购买饮料。
//因此当sel=1,din=1时,输出drinks_out=0,change_out=0;注意此时售卖机内还有5元,
//那么就有可能出现输出B饮料同时找零5元这种情况
//我的思路时按输出结果,划分成6种状态,分别是:
//A:没有饮料输出且没有找零,这个状态也可以作为默认初始状态;
//B:输出A饮料且没有找零;
//C: 输出A饮料且找零;
//D: 输出B饮料且没有找零;
//E: 没有饮料输出且没有找零,但是售卖机内还有5元;
//F: 输出B饮料且找零。
//------------------------//
reg [2:0] state, next_state;
parameter A = 0, B = 1,C = 2, D = 3, E = 4, F = 5;
always @(posedge clk, negedge rst_n) begin
if(!rst_n) begin
state <= A;
end else begin
state <= next_state;
end
end
always @(*) begin
case(state)
A: begin
case({sel,din})
3'b000: next_state <= A;
3'b001: next_state <= B;
3'b010: next_state <= C;
3'b100: next_state <= A;
3'b101: next_state <= E;
3'b110: next_state <= D;
default: next_state <= A;
endcase
end
B: begin
case({sel,din})
3'b000: next_state <= A;
3'b001: next_state <= B;
3'b010: next_state <= C;
3'b100: next_state <= A;
3'b101: next_state <= E;
3'b110: next_state <= D;
default: next_state <= A;
endcase
end
C: begin
case({sel,din})
3'b000: next_state <= A;
3'b001: next_state <= B;
3'b010: next_state <= C;
3'b100: next_state <= A;
3'b101: next_state <= E;
3'b110: next_state <= D;
default: next_state <= A;
endcase
end
D: begin
case({sel,din})
3'b000: next_state <= A;
3'b001: next_state <= B;
3'b010: next_state <= C;
3'b100: next_state <= A;
3'b101: next_state <= E;
3'b110: next_state <= D;
default: next_state <= A;
endcase
end
E: begin
case({sel,din})
3'b100: next_state <= E;
3'b101: next_state <= D;
3'b110: next_state <= F;
default: next_state <= A;
endcase
end
F: begin
case({sel,din})
3'b000: next_state <= A;
3'b001: next_state <= B;
3'b010: next_state <= C;
3'b100: next_state <= A;
3'b101: next_state <= E;
3'b110: next_state <= D;
default: next_state <= A;
endcase
end
endcase
end
always @(*) begin
case(state)
A: drinks_out <= 2'd0;
B: drinks_out <= 2'd1;
C: drinks_out <= 2'd1;
D: drinks_out <= 2'd2;
E: drinks_out <= 2'd0;
F: drinks_out <= 2'd2;
default: drinks_out <= 2'd0;
endcase
end
always @(*) begin
case(state)
A: change_out <= 1'b0;
B: change_out <= 1'b0;
C: change_out <= 1'b1;
D: change_out <= 1'b0;
E: change_out <= 1'b0;
F: change_out <= 1'b1;
default: change_out <= 1'b0;
endcase
end
endmodule
交通灯
`timescale 1ns/1ns
module triffic_light
(
input rst_n, //异位复位信号,低电平有效
input clk, //时钟信号
input pass_request,
output wire[7:0]clock,
output reg red,
output reg yellow,
output reg green
);
parameter idle = 2'd0,
s1_red = 2'd1,
s2_yellow = 2'd2,
s3_green = 2'd3;
reg [7:0] cnt;
reg [1:0] state;
reg p_red,p_yellow,p_green; //用于缓存信号灯的前一时刻的数值,判断上升沿
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
state <= idle;
p_red <= 1'b0;
p_green <= 1'b0;
p_yellow <= 1'b0;
end
else case(state)
idle:
begin
p_red <= 1'b0;
p_green <= 1'b0;
p_yellow <= 1'b0;
state <= s1_red;
end
s1_red:
begin
p_red <= 1'b1;
p_green <= 1'b0;
p_yellow <= 1'b0;
if (cnt == 3)
state <= s2_yellow;
else
state <= s1_red;
end
s2_yellow:
begin
p_red <= 1'b0;
p_green <= 1'b0;
p_yellow <= 1'b1;
if (cnt == 3)
state <= s3_green;
else
state <= s2_yellow;
end
s3_green:
begin
p_red <= 1'b0;
p_green <= 1'b1;
p_yellow <= 1'b0;
if (cnt == 3)
state <= s1_red;
else
state <= s3_green;
end
endcase
end
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt <= 7'd10;
else if (pass_request&&green&&(cnt>10))
cnt <= 7'd10;
else if (!green&&p_green)
cnt <= 7'd60;
else if (!yellow&&p_yellow)
cnt <= 7'd5;
else if (!red&&p_red)
cnt <= 7'd10;
else cnt <= cnt -1;
assign clock = cnt;
always @(posedge clk or negedge rst_n)
if(!rst_n)
begin
yellow <= 1'd0;
red <= 1'd0;
green <= 1'd0;
end
else
begin
yellow <= p_yellow;
red <= p_red;
green <= p_green;
end
endmodule
格雷码计数器
格雷码计数器,分为三部分进行设计,格雷码转二进制、加法器、二进制转格雷码。
格雷码转二进制将格雷码转换为二进制,并将值输出用于加法器进行加法运算,然后将加法运算结果通过二进制转格雷码转换为格雷码,最后将格雷码进行输出,同时将结果输出到格雷码转二进制作为输入,形成一个计数功能。
格雷码转二进制码的基本思路:
格雷码转二进制是从左边第二位起,将每位与左边一位二进制码的值异或,作为该位二进制码后的值(最左边一位依然不变)。
二进制码转格雷码的基本思路:
从最右边一位起,依次将每一位与左边一位异或(XOR),作为对应格雷码该位的值,最左边一位不变
module gray_counter(
input clk,
input rst_n,
output reg [3:0] gray_out
);
//格雷码转二进制
reg [3:0] bin_out;
wire [3:0] gray_wire;
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0) begin
bin_out <= 4'b0;
end
else begin
bin_out[3] = gray_wire[3];
bin_out[2] = gray_wire[2]^bin_out[3];
bin_out[1] = gray_wire[1]^bin_out[2];
bin_out[0] = gray_wire[0]^bin_out[1];
end
end
//二进制加一
reg [3:0] bin_add_wire;
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0) begin
bin_add_wire <= 4'b0;
end
else begin
bin_add_wire <= bin_out + 1'b1;
end
end
//二进制转格雷码
assign gray_wire = (bin_add_wire >> 1) ^ bin_add_wire;
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0) begin
gray_out <= 4'b0;
end
else begin
gray_out <= gray_wire;
end
end
endmodule
二进制转格雷码是从最右边开始,最左边的一个不变,格雷码转二进制是从最左边
二进制原码向右移位一位,然后和原码做异或操作,得到格雷码
就是先依据格雷码得到二进制码,格雷码转二进制,保留最左边不变;二进制转格雷码,保持最左边不变。就是最左边都是不变的,转换方式也都一样
转换到格雷码是和自己的原码做异或,转换到二进制是和前一位二进制做异或,前一位是指开始的地方,即左边第一位,方式是一样,但是对象不一样
即思路为,由此时的格雷码得到二进制原码,然后由二进制原码+1完成次态转换,然后再由这个新的码转换为新的格雷码
在得到二进制原码时,是要和转换后的二进制码前一位做异或,所以用的是=,而不是<=,不然的话做异或就是和上一状态而不是现在这个状态了
多bit MUX同步器
触发器都是D触发器,下面一堆D触发器,第一个是说输入的使能EN信号为D信号,然后时钟A为控制触发器信号,然后输出Q信号(触发器方程Q<=D),然后再在时钟B下,
`timescale 1ns/1ns
module mux(
input clk_a ,
input clk_b ,
input arstn ,
input brstn ,
input [3:0] data_in ,
input data_en ,
output reg [3:0] dataout
);
reg[3:0]data_in1;
reg data_ena1,data_enb1,data_enb2;
always@(posedge clk_a or negedge arstn)begin
if(!arstn)begin
data_in1 <= 0;
data_ena1 <= 0;
end
else begin
data_in1 <= data_in;
data_ena1 <= data_en;
end
end
always@(posedge clk_b or negedge brstn)begin
if(!brstn)begin
data_enb1 <= 0;
data_enb2 <= 0;
end
else begin
data_enb1 <= data_ena1;
data_enb2 <= data_enb1;
end
end
always@(posedge clk_b or negedge brstn)begin
if(~brstn)
dataout <= 0;
else
dataout <= data_enb2? data_in1: dataout;
end
endmodule
电路图
这个结构会生成两个D触发器,一个用来控制输出ENB1(即第一个等式),一个用来控制输出ENB2,(第二个等式),即这两个触发器的输出Q信号分别为ENB1以及ENB2
这个结构生成两个D触发器(有几个<=等式,就有几个D触发器),上面那一行生成IN1,即输出的Q信号确定为IN1,下面那一行输出ENA1,即下面那个D触发器
这个用来确定DATA_OUT,由于有三目表达式,所以会生成一个二路选择器,为1时,接通1的信号,即DATA_IN,1处接这个信号,不然保持原样,就接回Q信号
脉冲同步电路
总体思路是将A时钟域的脉冲信号转换为电平信号,打两拍后再转换为B时钟域的脉冲信号。
和消抖的原理差不多
`timescale 1ns/1ns
module pulse_detect(
input clk_fast ,
input clk_slow ,
input rst_n ,
input data_in ,
output dataout
);
reg data_level, data_level1, data_level2, data_level3;
// 脉冲信号转电平信号
always@(posedge clk_fast or negedge rst_n) begin
if(~rst_n)
data_level <= 0;
else
data_level <= data_in? ~data_level: data_level;
end
// 电平信号打两拍再转为脉冲信号
always@(posedge clk_slow or negedge rst_n) begin
if(~rst_n) begin
data_level1 <= 0;
data_level2 <= 0;
data_level3 <= 0;
end
else begin
data_level1 <= data_level;
data_level2 <= data_level1;
data_level3 <= data_level2;
end
end
assign dataout = data_level3^data_level2;
endmodule
电路图
就是注意CLK端,然后输入信号由于有三目表达式,所以用二路选择器,注意选择信号(就是三目表达式里的判断式子),然后接上01信号,最后输出Q,以及复位信号,时钟信号的输入都标对
另外,
这个实际上就是相当于一次反转,即有效时,就反转一下当前的信号,不有效时就保持,所以实际上是和T触发器差不多的,即这个传入DATA_IN信号,要用D触发器时,要选择一下,处理一下才能作为D信号,而如果是T触发器,就直接把DATA_IN作为T信号即可,为高电平时反转就行
这个结构为三级D触发器,由三个连续的<=生成,就是注意先后顺序,以及时钟端,复位端即可,
最后的输出信号,是个组合逻辑,组合逻辑不用触发器,直接用逻辑门组合即可,就是确定好相应的输入信号,然后输出门信号即可
根据RTL图写程序
两个D触发器,一个与门
`timescale 1ns/1ns
module RTL(
input clk,
input rst_n,
input data_in,
output reg data_out
);
reg data1;
wire data2;
always@(posedge clk,negedge rst_n)begin
if(!rst_n)
data1<=0;
else
data1<=data_in;
end
assign data2=(!data1)&data_in;
always@(posedge clk,negedge rst_n)begin
if(!rst_n)
data_out<=0;
else
data_out<=data2;
end
endmodule