写1:100质数SV中的约束,怎么将256个数从小到大排列,不用sort方法 分频奇数分频五分频 偶数分频任意占空比小数分频序列检测器(输出)序列检测异步复位,同步释放帧头检测边沿检测加法器串并转换跨时钟域同步FIFO格雷码转换参考资料
写1:100质数
c语言
//C
#include<stdio.h>
int main ()
{
int i;
int j;
int flag = 1;
for(i = 2; i <= 100; i++)
{
for(j = 2;j <= i/2; j++)
{
if(i % j == 0)
{
flag = 0;
break;
}
}
if(flag == 1)
{
printf("%d\n",i);
}
flag = 1;
}
return 0;
}
Matlab
for i = 2:100 %外层循环,i的初始值是2,终值是100
for j = 2:100 %内层循环,j的初值是2,终值100
if(~mod(i,j)) %i除以j取余后再取反
break; %跳出循环
end
end
if(j > (i/j))
fprintf('%d is prime \n ',i);
end
end
SV中的约束,怎么将256个数从小到大排列,不用sort方法
//分析题意,256=2^8,
constraint c {for(int i=0;i<7,i++)//定义一个i(0~8)
for(int j=i+1;j<8:i++)//定义一个j变量(0~8)
if(array[i]>array[j]) begin//如果队列第i个元素小于第j个
int temp = arrays[i];
arrays[i] = arrays[j];
arrays[j] = temp;//那么i和j调换位置
end
}
分频
分频器的原理:当计数器是分频系数的一半时,信号翻转。当复位信号变化时,就会变化。
思路:需要使用always判定计数器,输入有 复位信号,时钟信号,输出是寄存器。当复位信号不变时,且输入是0时,输出也是0.当复位信号不变,输入小于分频数时,计数器加1.0到3的计数器,到3翻转,就可以得到一个4分频的计数器。
奇数分频
分别写两个计数器和分频器(分别在上升沿计数、下降沿计数,上升沿分频和下降沿分频)
三分频
module divtest(
input clk,
input rst_n,
output clk_divider
);
reg [2:0] count_p;//最大计数到3
reg [2:0] count_n;
reg clk_p;
reg clk_n;
parameter N = 3;
//-----------------上升沿计数----------------
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
count_p <= 3'b0;//rst不变时为0
else if(count_p == N-1)
count_p <= 3'b0;
else
count_p <= count_p + 1'b1;
end
//-----------------上升沿分频----------------
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
clk_p <= 1'b1;
end
else begin
if(count_p == (N-1)/2) || (count_p == N-1)begin
clk_p <= ~clk_p;
end
end
end
//-----------------下降沿计数----------------
always @(negedge clk or negedge rst_n)begin
if(!rst_n)
count_n <= 3'b0;//rst后为0
else if(count_n == N-1)//重置为0
count_n <= 3'b0;
else
count_n <= count_n + 1'b1;//下降沿计数并加1
end
//-----------------下降沿分频----------------
always@(negedge clk or negedge rst_n)begin
if(!rst_n)begin
clk_n <= 1'b1;
end
else begin
if(count_n == (N-1)/2) || (count_n == N-1)begin //逻辑或运算
clk_n <= ~clk_n;
end
end
end
assgin clk_divider = clk_n | clk_p; 按位或,只要分频出现,clk_divider就会拉高
endmodule
五分频
由于奇分频需要保持分频后的时钟占空比为 50% ,所以不能像偶分频那样直接在分频系数的一半时使时钟信号翻转(高电平一半,低电平一半)。在此我们需要利用输入时钟上升沿和下降沿来进行设计。
接下来我们设计一个 5 分频的模块,设计思路如下:
采用计数器 cnt1 进行计数,在时钟上升沿进行加 1 操作,计数器的值为 0、1 时,输出时钟信号 clk_div 为高电平;计数器的值为2、3、4 时,输出时钟信号 clk_div 为低电平,计数到 5 时清零,从头开始计数。我们可以得到占空比为 40% 的波形 clk_div1。
采用计数器 cnt2进行计数,在时钟下降沿进行加 1 操作,计数器的值为 0、1 时,输出时钟信号 clk_div 为高电平;计数器的值为2、3、4 时,输出时钟信号 clk_div 为低电平,计数到 5 时清零,从头开始计数。我们可以得到占空比为 40% 的波形 clk_div2。
clk_div1 和clk_div2 的上升沿到来时间相差半个输入周期,所以将这两个信号进行或操作,即可得到占空比为 50% 的5分频时钟。程序如下:
module divider(
input clk,
input rst_n,
output clk_div
);
reg clk_div;
parameter N = 5;
reg[2:0] cnt1;
reg[2:0] cnt2;
reg clk_div1, clk_div2;
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt1 <= 0;
else if(cnt1 < N-1)
cnt1 <= cnt1 + 1'b1;
else
cnt1 <= 0;
always @(posedge clk or negedge rst_n)
if(!rst_n)
clk_div1 <= 1'b1;
else if(cnt1 < N/ 2)
clk_div1 <= 1'b1;
else
clk_div1 <= 1'b0;
always @(negedge clk or negedge rst_n)
if(!rst_n)
cnt2 <= 0;
else if(cnt2 < N-1)
cnt2 <= cnt2 + 1'b1;
else
cnt2 <= 0;
always @(negedge clk or negedge rst_n)
if(!rst_n)
clk_div2 <= 1'b1;
else if(cnt2 < N / 2)
clk_div2 <= 1'b1;
else
clk_div2 <= 1'b0;
assign clk_div = clk_div1 | clk_div2;
endmodule
//tb
module divider_tb();
reg clk;
reg rst_n;
wire clk_div;
parameter DELY=100;
divider U_divider(
.clk (clk ),
.rst_n (rst_n ),
.clk_div(clk_div)
);
always #(DELY/2) clk=~clk;//产生时钟波形
initial begin
$fsdbDumpfile("divider_odd.fsdb");
$fsdbDumpvars(0,U_divider);
end
initial begin
clk=0;rst_n=0;
#DELY rst_n=1;
#((DELY*20)) $finish;
end
endmodule
偶数分频
八分频代码,N分频就是N,偶数分频的时候写一个N位的计数器,再写一个分频器吗,在(N/2)-1和N-1的时候翻转
module divider8(
input clk,
input rst_n,
input dividerclk
);
reg [3:0] count_n;//位宽为3,计数最大为7
parameter N = 8;
//写一个计数器
always@(posedge clk negedge rst_n)begin
if(!rst_n)
count_n <= 1'b0;
else if(count_n == N-1)
count_n <= 1'b0;
else
count_n <= count_n + 1'b1;//从0开始计数,0~7总共八个上升沿
end
end
//分频器
always@(posedge clk or negedge rst_n)
if(!rst_n)begin
dividerclk <= 1'b1;
end
else if(count_n == N/2-1) | (count_n == N-1)begin//在第四上升沿和第八上升沿跳转
dividerclk <= ~dividerclk;
end
end
endmodule
六分频波形
任意占空比
现在在前面两个实验的基础上做一个简单的总结,实现对一个频率的任意占空比的任意分频。
比如: FPGA系统时钟是50M Hz,而我们要产生的频率是880Hz,那么,我们需要对系统时钟进行分频。很容易想到用计数的方式来分频:50000000/880 = 56818。
显然这个数字不是2的整幂次方,那么我们可以设定一个参数,让它到56818的时候重新计数就可以实现了。程序如下:
//rtl
module div(
clk,
rst_n,
clk_div
);
input clk,rst_n;
output clk_div;
reg clk_div;
reg [15:0] counter;
always @(posedge clk or negedge rst_n)
if(!rst_n)
counter <= 0;
else if(counter==56817)
counter <= 0;
else
counter <= counter+1;
assign clk_div = counter[15];
endmodule
下面我们来算一下它的占空比:
我们清楚地知道,这个输出波形在counter为0到32767(2的14次方)的时候为低,在32768到56817的时候为高,占空比为40%多一些,
如果我们需要占空比为50%,那么我们需要再设定一个参数,使它为56817的一半,使达到它的时候波形翻转,就可以实现结果了。
程序如下:28408=56818/2-1,计数到28408就清零,翻转,其余的计数期间,保持不变。
//rtl
module div(
clk,
rst_n,
clk_div
);
input clk,rst_n;
output clk_div;
reg clk_div;
reg [14:0] counter;
always @(posedge clk or negedge rst_n)
if(!rst_n)
counter <= 0;
else if(counter==28408)
counter <= 0;
else
counter <= counter+1;
always @(posedge clk or negedge rst_n)
if(!rst_n)
clk_div <= 0;
else if(counter==28408)
clk_div <= ~clk_div;
endmodule
继续让我们来看如何实现任意占空比,比如还是由50M分频产生880Hz,而分频得到的信号的占空比为30%。
56818×30%=17045
//rtl
module div(
clk,
rst_n,
clk_div,
counter
);
input clk,rst_n;
output clk_div;
reg clk_div;
output [15:0] counter;
reg [15:0] counter;
always @(posedge clk)
if(!rst_n)
counter <= 0;
else if(counter==56817)
counter <= 0;
else counter <= counter+1;
always @(posedge clk)
if(!rst_n)
clk_div <= 0;
else if(counter<17045)
clk_div <= 1;
else
clk_div <= 0;
endmodule
小数分频
输入频率f_in、输出频率 f_out,计数器位宽N、计数器步长M之间存在f_in/f_out=(2^N)/M的关系,以M为计数步长进行计数,当计数值大于等于(2^N)/2时为高电平否则为低电平。
always@(posedge clk or negedge rst_n)
if(~rst_n)
clk_cnt <= 'h0;
else if(&clk_cnt)
clk_cnt <= 'h0;
else
clk_cnt <= clk_cnt+32'haaaa_aaaa;
always@(posedge clk or negedge rst_n)
if(~rst_n)
clk_div <= 1'b0;
else if(clk_cnt>=32'h7fff_ffff)
clk_div<=1'b1;
else
clk_div<=1'b0;
序列检测器(输出)
周期性输出一个序列为00_1011_0111
module xlgen(
input clk,
input rst_n,
output Q
);
reg Q;
reg [9:0] Q_r;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
Q <= 1'b0;
Q_r <= 10'b0010110111;
end
else begin
Q <= Q_r[9];//最高位赋值给Q
Q_r <= Q_r<<1;//Q_r左移一位
Q_r[0] <= Q; //Q赋值给Q_r的最低位,实现输出循环
end
end
endmodule
module sequence(
input clk,
input rst_n,
output out
);
reg [9:0] sequence;
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
sequence <= 10'b0010110111;
else
sequence <= {sequence[8:0],sequence[9] };
end
assign out = sequence[9] ;
endmodule
序列检测
序列检测器的逻辑功能就是将一个指定的比特序列从一串较长的比特流中识别出来。
例如:针对一个较长的比特流010010010011110101010…,我们希望能将比特序列为“10010”的序列检测出来,并且每次检测到10010就将输出置“1”.
注意:如序列”100100100…",根据以上的介绍,会在第六个比特的时刻输出会置1,但是,在第9个比特时输出不会被置”1“,虽然第4-8比特也构成了”10010“,但是却与第一次的”10010“重叠,因此不能算作有效检测。
二段式FSM描述:
鉴于一段式描述的缺点,两段式描述使用2个always块,一个always块利用同步时序逻辑描述状态转移,一个always块利用组合逻辑描述状态转移条件以及输出;(但是输出一般使用组合逻辑描述,而组合逻辑易产生毛刺等不稳定因素,并影响系统执行速率)
三段式FSM描述:
三段式描述应运而生,通过使用3个always块,一个always块利用同步时序逻辑描述状态转移,一个always块利用组合逻辑描述状态转移条件,一个always块利用同步时序逻辑描述输出,避免了前两种描述方式的弊端;
module SeqDetecter(
clk,
reset_n,
bits,
Det_Done
);
input clk;
input reset_n;
input bits;
output reg Det_Done;
parameter S0 = 5'b00001;
parameter S1 = 5'b00010;
parameter S2 = 5'b00100;
parameter S3 = 5'b01000;
parameter S4 = 5'b10000;
reg [4:0] state;
always @(posedge clk or negedge reset_n)//同步时序描述状态转移和转移条件
if(!reset_n)
state <= 5'b00001;
else begin
case(state)
S0: if(bits == 0) state<=S0; else state<=S1;
S1: if(bits == 0) state<=S2; else state<=S1;
S2: if(bits == 0) state<=S3; else state<=S1;
S3: if(bits == 0) state<=S0; else state<=S4;
S4: if(bits == 0) state<=S0; else state<=S1;
default: state<=S0;
endcase
end
always @(posedge clk or negedge reset_n)//同步时序逻辑描述输出条件
if(!reset_n)
Det_Done<=0;
else begin
case(state)
S0: Det_Done <= 1'b0;
S1: Det_Done <= 1'b0;
S2: Det_Done <= 1'b0;
S3: Det_Done <= 1'b0;
S4: if(bits == 0) Det_Done<=1'b1; else Det_Done<=1'b0;
default:Det_Done <= 1'b0;
endcase
end
endmodule
异步复位,同步释放
一般来说,同步系统,都使用异步复位。这是因为同步复位比异步复位的电路实现要浪费更多电路资源。
module rstsync(
input clk,
input rst_async_n,//异步复位信号
output rst_sync_n //同步释放信号
);
reg rst_s1;
reg rst_s2;
always@(posedge clk or negedge rst_async_n)begin
if(!rst_async_n)begin
rst_s1 <= 1'b0;
rst_s2 <= 1'b0;
end
else begin
rst_s1 <= 1'b1;
rst_s2 <= rst_s1;
end
end
assign rst_sync_n = rst_s2;
endmodule
帧头检测
边沿检测
用verilog实现1bit信号边沿检测功能,输出一个周期宽度的脉冲信号
重点在上升沿,下降沿,上升沿或下降沿
定义了一个2位的寄存器变量data_r,data_r[0]用来存储当前的状态,data_r[1]用来存储上一拍的状态
每个时钟到来的时候,向左移位,相对于当前时钟的上上拍信号舍弃掉,只保留相对于当前时钟上一拍的状态,存储在data_r的高位上,即data_r[1]表示的是上一拍的状态
module EdgeDetect(
input clk,
input rst_n,
input data,
output pos_edge,
output neg_edge,
output data_edge
);
reg [1:0] data_r;
always@(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
data_r <= 2'b00;
end
else begin
data_r <= {data_r[0],data};//拼接出来高位为上一拍,低位为当前拍
end
end
assign pos_edge = ~data_r[1] & data_r[0]; //01
assign neg_edge = data_r[1] & ~data_r[0]; //10
assign data_edge = pos_edge | neg_edge;
//上升沿之前是0,现在为1,所以上个周期传输的signal[1]是0,应该取反。
endmodule
加法器
描述带进位输入、输出的两位全加器
先写一位全加器,然后再写个顶层模块,调用两个一位全加器,其中第一个加法器的cout作为第二个加法器的cin
//公式
S = a ^ b ^ cin;
Cout = a & b | a & cin | b & cin;
//一位全加器
module fulladder(
input a,
input b,
input cin,
output s,
output cout
);
assign s = a ^ b ^ cin;
assign cout = a & b | a & cin | b & cin;
endmodule
//2位全加器
module top(a,b,cin,s,cout);
input [1:0] a;
input [1:0] b;
input cin;
output [1:0] s;
output cout;
wire carry;
//采用结构化描述的方式实现一个2位加法器
fulladder m0(a[0],b[0],cin, s[0],carry);
fulladder m1(a[1],b[1],carry,s[1],cout );
endmodule
串并转换
verilog实现8位串行数据转并行数据
解串器(deserialize):串行信号转并行信号
输入信号有:时钟信号clk,复位信号rst,串行数据输入信号din。
输出信号为8bit并行信号dout。
每经过8个时钟周期,便把收到的8个串行信号合成并行信号并输出,等下8个时钟周期过后再输出下一个并行信号。
module deserialize(
input rst,clk,din,
output reg [7:0] dout
);
reg [7:0] data;
reg [2:0] cnt;
always@(posedge clk or negedge rst)begin
if(rst == 1'b0)begin
data <= 8'b0;
cnt <= 3'b0;
dout <= 8'b0;
end
else begin
if(cnt == 3'b111) begin //get all 8bit data,change dout
dout[7-cnt] <= din;//将串行信号输入dout
dout[7:1] <= data[7:1];
cnt <= 3'b0;
end
else begin//没有加满时,持续输入din总共7个数字,0—6
data[7-cnt] <= din;
cnt <= cnt+1;
end
end
end
endmodule
`timescale 1ns/1ns
module deseralize_tb;
reg RST,CLK,DIN;
wire[7:0] DOUT;
integer i,j;
deserialize U_deserialize(.rst(RST),.clk(CLK),.din(DIN),.dout(DOUT));
initial
begin
RST = 1;
#5 RST = 0;
#2 RST = 1;
end
initial
begin
CLK = 0;
for(i = 0;i<100000;i = i+1)
begin
#2 CLK = ~CLK;
end
end
initial
begin
#1 DIN = 0;
for(j = 0;j<10000;j = j+1)
begin//只有7位输入,可以造成相邻的dout不同
#4 DIN = 0;
#4 DIN = 1;
#4 DIN = 1;
#4 DIN = 0;
#4 DIN = 1;
#4 DIN = 0;
#4 DIN = 1;
end
end
endmodule
跨时钟域
单bit(慢时钟域到快时钟域):
用快时钟打两拍,直接采一拍大概率也是没问题的,两拍的主要目的是消除亚稳态;
慢时钟域信号到快时钟域信号,一般情况下都是打两拍(单比特传输)也叫两级寄存器同步
module clockdomain(
input clk,
input rst_n,
input signal_in,
output signal_out
);
reg [1:0] signal_r;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
signal_r <= 2'b00;
end
else begin
signal_r <= {signal_r[0],signal_in};//高位为之前状态,低位为当前输入状态
end
end
assign signal_out = signal_r[1];//输出为之前状态,第二拍
endmodule
单bit(快时钟域到慢时钟域):
握手、异步FIFO、异步双口RAM;
快时钟域的信号脉宽较窄,慢时钟域不一定能采到,可以通过握手机制让窄脉冲展宽,慢时钟域采集到信号后再“告诉”快时钟域已经采集到信号,确保能采集到;
快时钟域到慢时钟域(clka慢时钟域,clkb快时钟域)
- 将快的时钟域中的信号进行展宽
- 对展宽的信号在慢的时钟域2级同步
- 在慢时钟域中产生反馈信号来对快的展宽信号进行拉低处理
跨时钟域处理从快时钟域到慢时钟域,如果是下面第一个图,cklb则可以采样到signal_a_in,但是如果 只有单脉冲,如第二个图,则不能确保采样掉signal_a_in。这个时候用两级触发器同步是没有用的。
module Sync_Pulse(
input clka,
input clkb,
input rst_n,
input pulse_ina,
output pulse_outb,
output signal_outb
);
//-------------------------------------------------------
reg signal_a;
reg signal_b;
reg [1:0] signal_b_r;
reg [1:0] signal_a_r;
//-------------------------------------------------------
//在clka下,生成展宽信号signal_a
always @(posedge clka or negedge rst_n)begin
if(rst_n == 1'b0)begin
signal_a <= 1'b0;
end
else if(pulse_ina == 1'b1)begin
signal_a <= 1'b1;
end
else if(signal_a_r[1] == 1'b1)
signal_a <= 1'b0;
else
signal_a <= signal_a;
end
//-------------------------------------------------------
//在clkb下同步signal_a
always @(posedge clkb or negedge rst_n)begin
if(rst_n == 1'b0)begin
signal_b <= 1'b0;
end
else begin
signal_b <= signal_a;
end
end
//-------------------------------------------------------
//在clkb下生成脉冲信号和输出信号
always @(posedge clkb or negedge rst_n)begin
if(rst_n == 1'b0)begin
signal_b_r <= 2'b00;
end
else begin
signal_b_r <= {signal_b_r[0], signal_b};
end
end
assign pulse_outb = ~signal_b_r[1] & signal_b_r[0];
assign signal_outb = signal_b_r[1];
//-------------------------------------------------------
//在clka下采集signal_b[1],生成signal_a_r[1]用于反馈拉低signal_a
always @(posedge clka or negedge rst_n)begin
if(rst_n == 1'b0)begin
signal_a_r <= 2'b00;
end
else begin
signal_a_r <= {signal_a_r[0], signal_b_r[1]};
end
end
endmodule
多bit:
异步FIFO、异步双口RAM、握手、格雷码;
同步FIFO
深度16,位宽8
module Syn_fifo
#(
parameter DATA_WIDTH = 8,
parameter ADDR_WIDTH = 4,
parameter RAM_DEPTH = (1 << ADDR_WIDTH)
)
(
input clk,
input rst_n,
input [DATA_WIDTH-1:0] data_in,
input wr_en,
input rd_en,
output reg [DATA_WIDTH-1:0] data_out,
output empty, //fifo empty
output full //fifo full
);
reg [ADDR_WIDTH-1:0] wr_cnt;
reg [ADDR_WIDTH-1:0] rd_cnt;
reg [ADDR_WIDTH-1:0] status_cnt;
reg [DATA_WIDTH-1:0] data_ram;
//-------------------------------------------------------
assign full = (status_cnt == (RAM_DEPTH-1))? 1'b1: 1'b0;
assign empty = (status_cnt == 0)? 1'b1: 1'b0;
//Syn
reg rd_en_r;
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
rd_en_r <= 0;
end
else begin
rd_en_r <= rd_en;
end
end
//-------------------------------------------------------
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
wr_cnt <= 0;
end
else if(wr_cnt == RAM_DEPTH-1)
wr_cnt <= 0;
else if(wr_en)begin
wr_cnt <= wr_cnt + 1'b1;
end
else
wr_cnt <= wr_cnt;
end
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
rd_cnt <= 0;
end
else if(rd_cnt == RAM_DEPTH-1)
rd_cnt <= 0;
else if(rd_en)begin
rd_cnt <= rd_cnt + 1'b1;
end
else
rd_cnt <= rd_cnt;
end
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
data_out <= 0;
end
else if(rd_en_r)begin
data_out <= data_ram;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
status_cnt <= 0;
end
else if(rd_en && !wr_en && (status_cnt != 0))begin
status_cnt <= status_cnt - 1;
end
else if(wr_en && !rd_en && (status_cnt != RAM_DEPTH-1))
status_cnt <= status_cnt + 1;
else
status_cnt <= status_cnt;
end
//-------------------------------------------------------
//Syn_Dual_Port_RAM
integer i;
reg [DATA_WIDTH-1:0] register[RAM_DEPTH-1:0];
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
for(i = 0; i < RAM_DEPTH; i = i + 1)
register[i] <= 0;
end
else if(wr_en == 1'b1)
register[wr_cnt] <= data_in;
end
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
data_ram <= 0;
end
else if(rd_en == 1'b1)
data_ram <= register[rd_cnt];
else
data_ram <= data_ram;
end
endmodule
格雷码转换
二进制转换为格雷码
原数右移一位再与原数异或
module bin_to_gray(
bin_in,
gray_out
);
parameter data_width = 4;
input [data_width-1:0] bin_in;
output [data_width-1:0] gray_out;
assign gray_out = (bin_in >> 1)^bin_in;
endmodule
格雷码转换为二进制
二进制最高位取格雷码最高位,然后循环做二进制高位与低一位的格雷码异或
module gray_to_bin(
gray_in,
bin_out
);
parameter data_width = 4;
input [data_width-1:0] gray_in;
output [data_width-1:0] bin_out;
reg [data_width-1:0] bin_out;
always@(gray_in)begin
bin_out[3] = gray_in[3];
bin_out[2] = gray_in[2]^gray_in[3];
bin_out[1] = gray_in[1]^gray_in[2];
bin_out[0] = gray_in[0]^gray_in[1];
end
endmodule