1.快时钟到慢时钟 检测单时钟脉冲信号
sig_a 是 clka(300M)时钟域的一个单时钟脉冲信号(高电平持续一个时钟clka周期),请设计脉冲同步电路,将sig_a信号同步到时钟域 clkb(100M)中,产生sig_b单时钟脉冲信号(高电平持续一个时钟clkb周期)输出。请用 Verilog 代码描述。
clka时钟域脉冲之间的间隔很大,无需考虑脉冲间隔太小的问题
不考虑间隔的问题,将源时钟域的脉冲信号转换成电平信号。对同步的信号判断,上升沿。
1 //
2 module pulse_detect(
3 output sig_b,
4 input clka, //fast 300M
5 input clkb, //slow 100M
6 input rst_n,
7 input sig_a);
8
9 reg [2:0] sig_b_reg;
10 reg sig_a_reg;
11
12 always@(posedge clka or negedge rst_n) begin
13 if(!rst_n)
14 sig_a_reg <= 0;
15 else if(sig_a)
16 sig_a_reg <= ~sig_a_reg;
17 end
18
19 always@(posedge clkb or negedge rst_n) begin
20 if(!rst_n)
21 sig_b_reg <= 3'b000;
22 else
23 sig_b_reg <= {sig_b_reg[1:0],sig_a_reg};
24 end
25
26 assign sig_b = ^sig_b_reg[2:1];
27 endmodule
tb:
1 //`timescale 100ps/100ps
2 module tb;
3
4 reg clka,clkb,rst_n,sig_a;
5 wire sig_b;
6
7 pulse_detect dut(.clka(clka),
8 .clkb(clkb),
9 .rst_n(rst_n),
10 .sig_a(sig_a),
11 .sig_b(sig_b));
12
13 initial begin
14 clka = 0;
15 forever begin
16 #1.6ns clka = ~clka;
17 end
18 end
19 initial begin
20 clkb = 0;
21 forever begin
22 #5ns clkb = ~clkb;
23 end
24 end
25
26 initial begin
27 rst_n = 0;
28 sig_a = 0;
29
30 #10ns
31 rst_n = 1;
32 sig_a = 1;
33 #3.2ns
34 sig_a = 0;
35 #200ns
36 $stop();
37 end
38
39 initial begin
40 // $fsdbDumpfile("pulse_detect.fsdb");
41 // $fsdbDumpvars;
42 $vcdpluson;
43 end
44
45
46
47
48 endmodule : tb
~
~
波形
1.2 带有握手信号的脉冲同步
如果两个脉冲的间隔短,会丢失信号。需要使用握手信号确保。[refer]
module HANDSHAKE_PULSE_SYNC
(
src_clk , //source clock
src_rst_n , //source clock reset (0: reset)
src_pulse , //source clock pulse in
src_sync_fail , //source clock sync state: 1 clock pulse if sync fail.
dst_clk , //destination clock
dst_rst_n , //destination clock reset (0:reset)
dst_pulse //destination pulse out
);
//PARA DECLARATION
//INPUT DECLARATION
input src_clk ; //source clock
input src_rst_n ; //source clock reset (0: reset)
input src_pulse ; //source clock pulse in
input dst_clk ; //destination clock
input dst_rst_n ; //destination clock reset (0:reset)
//OUTPUT DECLARATION
output src_sync_fail ; //source clock sync state: 1 clock pulse if sync fail.
output dst_pulse ; //destination pulse out
//INTER DECLARATION
wire dst_pulse ;
wire src_sync_idle ;
reg src_sync_fail ;
reg src_sync_req ;
reg src_sync_ack ;
reg ack_state_dly1 ;
reg ack_state_dly2 ;
reg req_state_dly1 ;
reg req_state_dly2 ;
reg dst_req_state ;
reg dst_sync_ack ;
//--========================MODULE SOURCE CODE==========================--
//--=========================================--
// DST Clock :
// 1. generate src_sync_fail;
// 2. generate sync req
// 3. sync dst_sync_ack
//--=========================================--
assign src_sync_idle = ~(src_sync_req | src_sync_ack );
//report an error if src_pulse when sync busy ;
always @(posedge src_clk or negedge src_rst_n)
begin
if(src_rst_n == 1'b0)
src_sync_fail <= 1'b0 ;
else if (src_pulse & (~src_sync_idle))
src_sync_fail <= 1'b1 ;
else
src_sync_fail <= 1'b0 ;
end
//set sync req if src_pulse when sync idle ;
always @(posedge src_clk or negedge src_rst_n)
begin
if(src_rst_n == 1'b0)
src_sync_req <= 1'b0 ;
else if (src_pulse & src_sync_idle)
src_sync_req <= 1'b1 ;
else if (src_sync_ack)
src_sync_req <= 1'b0 ;
end
always @(posedge src_clk or negedge src_rst_n)
begin
if(src_rst_n == 1'b0)
begin
ack_state_dly1 <= 1'b0 ;
ack_state_dly2 <= 1'b0 ;
src_sync_ack <= 1'b0 ;
end
else
begin
ack_state_dly1 <= dst_sync_ack ;
ack_state_dly2 <= ack_state_dly1 ;
src_sync_ack <= ack_state_dly2 ;
end
end
//--=========================================--
// DST Clock :
// 1. sync src sync req
// 2. generate dst pulse
// 3. generate sync ack
//--=========================================--
always @(posedge dst_clk or negedge dst_rst_n)
begin
if(dst_rst_n == 1'b0)
begin
req_state_dly1 <= 1'b0 ;
req_state_dly2 <= 1'b0 ;
dst_req_state <= 1'b0 ;
end
else
begin
req_state_dly1 <= src_sync_req ;
req_state_dly2 <= req_state_dly1 ;
dst_req_state <= req_state_dly2 ;
end
end
//Rising Edge of dst_state generate a dst_pulse;
assign dst_pulse = (~dst_req_state) & req_state_dly2 ;
//set sync ack when src_req = 1 , clear it when src_req = 0 ;
always @(posedge dst_clk or negedge dst_rst_n)
begin
if(dst_rst_n == 1'b0)
dst_sync_ack <= 1'b0;
else if (req_state_dly2)
dst_sync_ack <= 1'b1;
else
dst_sync_ack <= 1'b0;
end
endmodule
2. 异步复位同步释放
使用异步复位同步释放来将输入数据a存储到寄存器中
module reset_release(
input clk,
input rst_n,
input d,
output reg dout
);
reg [1:0] rst_reg;
//two level reg to syn rst_n=1
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
rst_reg <= 2'b0;
else
rst_reg <= {rst_reg[0],1'b1};
end
always@(posedge clk or negedge rst_reg[1]) begin
if(!rst_reg[1])
dout <= 1'b0;
else
dout <= d;
end
endmodule
module tb;
reg clk,rst_n,d;
wire dout;
reset_release dut(.clk(clk),
.rst_n(rst_n),
.d(d),
.dout(dout));
initial begin
clk = 0;
forever begin
#5
clk = ~clk;
end
end
initial begin
rst_n = 0;
#8
rst_n = 1;
#200
$stop();
end
initial begin
d = 0;
#10 d = 1;
#10 d = 0;
#15 d = 1;
end
endmodule : tb
3.握手信号跨时钟域
module data_driver(
input clk_a,
input rst_n,
input data_ack,
output reg [3:0]data,
output reg data_req
);
reg [2:0] ack_reg;
reg [2:0] count;
//跨时钟域,此处打三拍避免亚稳态
always@(posedge clk_a or negedge rst_n) begin
if(!rst_n)
ack_reg <= 3'b0;
else
ack_reg <= {ack_reg[1:0],data_ack};
end
//ack上升沿
assign ack_posedge = !ack_reg[2] && ack_reg[1];
always@(posedge clk_a or negedge rst_n) begin
if(!rst_n)
count <= 3'b0;
else if(ack_posedge)
count <= 3'b0;
else
count <= count + 1'b1;
end
always@(posedge clk_a or negedge rst_n) begin
if(!rst_n) begin
data <= 3'b000;
data_req <= 1'b0;
end
else if(count == 3'b100)
data_req <= 1'b1;
else if(ack_posedge) begin
data_req <= 1'b0;
data <= data + 1'b1;
end
end
endmodule
module data_receiver(
input clk_b,
input rst_n,
input data_req,
input [3:0] data,
output reg data_ack
);
reg [3:0] data_reg;
reg [2:0] req_reg;
always@(posedge clk_b or negedge rst_n) begin
if(!rst_n)
req_reg <= 3'b0;
else
req_reg <= {req_reg[1:0],data_req};
end
//req上升沿
assign req_posedge = !req_reg[2] && req_reg[1];
always@(posedge clk_b or negedge rst_n) begin
if(!rst_n) begin
data_ack <= 1'b0;
end
else if(req_reg[1]) begin
data_ack <= 1'b1;
else
data_ack <= 1'b0;
end
end
always@(posedge clk_b or negedge rst_n) begin
if(!rst_n) begin
data_reg <= 4'b0;
end
else if(req_posedge) begin
data_reg <= data;
end
end
endmodule
tb:
reg clk_a,clk_b;
reg rst_n;
data_driver dut_drv(.clk_a(clk_a),
.rst_n(rst_n),
.data_ack(data_ack),
.data(data),
.data_req(data_req));
data_receiver dut_rec(.clk_b(clk_b),
.rst_n(rst_n),
.data_ack(data_ack),
.data(data),
.data_req(data_req));
initial begin
clk_a = 0;
forever begin
#5ns clk_a = ~clk_a;
end
end
initial begin
clk_b = 0;
forever begin
#10ns clk_b = ~clk_b;
end
end
initial begin
rst_n = 0;
#20ns
rst_n = 1;
#1us
$stop();
end
endmodule : tb
4.小数分频
牛客
描述
请设计一个可以实现任意小数分频的时钟分频器,比如说8.7分频的时钟信号
注意rst为低电平复位
提示:
其实本质上是一个简单的数学问题,即如何使用最小公倍数得到时钟周期的分别频比。
设小数为nn,此处以8.7倍分频的时钟周期为例。
首先,由于不能在硬件上进行小数的运算(比如2.1个时钟这种是不现实的,也不存在3.3个寄存器),小数分频不能做到分频后每个时钟周期都是源时钟的nn倍,也无法实现占空比为1/2,因此,考虑小数分频,其实现方式应当为53个clkout时钟周期是10个clkin时钟周期的8.7倍。
信号示意图:
8.7分频,即周期扩大8.7倍。原来87个周期,现在10个周期。
则 x+y = 10
Mx+Ny = 87
题目所给M = 8 N = 9 ,所以可以由三个8分频的时钟和七个9分频的时钟组合而成。
设置一个div_flag,在计数27 87翻转,完成时钟的切换。
module div_M_N(
input wire clk_in,
input wire rst,
output wire clk_out
);
parameter M_N = 8'd87;
parameter c89 = 8'd24; // 8/9时钟切换点
parameter div_e = 5'd8; //偶数周期
parameter div_o = 5'd9; //奇数周期
//*************code***********//
reg div_flag;
reg [6:0] clk_cnt;
reg [3:0] cnt;
reg clk_out_r;
always@(posedge clk_in or negedge rst) begin
if(!rst)
div_flag <= 'b0;
else
div_flag <= (clk_cnt== c89-1 || clk_cnt==M_N-1)? ~div_flag : div_flag;
end
always@(posedge clk_in or negedge rst) begin
if(!rst)
cnt <= 'b0;
else if(!div_flag)
cnt <= (cnt == div_e - 1? 0 : cnt + 1);
else
cnt <= (cnt == div_o - 1? 0 : cnt + 1);
end
always@(posedge clk_in or negedge rst) begin
if(!rst)
clk_cnt <= 'b0;
else
clk_cnt <= (clk_cnt == M_N-1) ? 'b0 : clk_cnt+1;
end
always@(posedge clk_in or negedge rst) begin
if(!rst)
clk_out_r <= 'b0;
else if(!div_flag)
clk_out_r <= cnt<=(div_e>>1-1);
else
clk_out_r <= cnt<=(div_o>>1-1);
end
assign clk_out = clk_out_r;
//*************code***********//
endmodule
5 状态机
5.1 售货机
牛客
描述
请设计状态机电路,实现自动售卖机功能,A饮料5元钱,B饮料10元钱,售卖机可接收投币5元钱和10元钱,每次投币只可买一种饮料,考虑找零的情况。
电路的接口如下图所示。sel信号会先于din信号有效,且在购买一种饮料时值不变。
sel为选择信号,用来选择购买饮料的种类,sel=0,表示购买A饮料,sel=1,表示购买B饮料;
din表示投币输入,din=0表示未投币,din=1表示投币5元,din=2表示投币10元,不会出现din=3的情况;
drinks_out表示饮料输出,drinks_out=0表示没有饮料输出,drinks_out=1表示输出A饮料,drinks_out=2表示输出B饮料,不出现drinks_out =3的情况,输出有效仅保持一个时钟周期;
change_out表示找零输出,change_out=0表示没有找零,change_out=1表示找零5元,输出有效仅保持一个时钟周期。
接口电路图如下:
使用Mealy型描述,所需要的状态数目会少很多,相对本题来说。
`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