1.异步双端口ram
题目
用verilog实现一个异步双端口ram,深度16,位宽8bit。A口读出,B口写入。支持片选,读写请求,要求代码可综合
代码
module Dual_port_Sram
#(
parameter ADDR_WIDTH = 4,
parameter DATA_WIDTH = 8,
parameter DATA_DEPTH = 1 << ADDR_WIDTH
)
(
input rstn,
input cs,
input clka,
input [ADDR_WIDTH-1:0] addra,
output [DATA_WIDTH-1:0] dout,
input rd_en,
input clkb,
input [ADDR_WIDTH-1:0] addrb,
input [DATA_WIDTH-1:0] din,
input wr_en
)
integer i;
reg [DATA_WIDTH-1:0] register[DATA_DEPTH-1:0];
always @(posedge clkb)begin //写控制逻辑
if(!rstn)begin
for(i=0;i<DATA_DEPTH;i++)
register[i] <= 0;
end
else if(wr_en && cs)
register[addrb] <= din;
end
always @(posedge clka)begin
if(!rstn)
dout <= 0;
else if(rd_en && cs)
dout <= register[addra];
end
endmodule
2.用verilog实现三分频电路,要求输出50%占空比
设计思路
如果不限制占空比50%的话,那么用counter做3进制计数,每次counter=1拉高,counter=2拉低即可,但是这样的占空比是1/3。如果要做50占空比的三分频,则需要一个上升沿的六分频和一个下降沿的六分频组合来得到。类似的奇分频都可以这样实现。
大致有三种组合方式都可以得到:
与
或
异或
后续将上升沿采样记为clk_pos,下降沿采样记为clk_neg
与逻辑分频
- 用与逻辑来组合,需要clk_pos和clk_neg拉高2T,但是其本身有0.5T的时序差
- 那么在cnt=0时,clk_pos拉高2拍
- cnt=0时,clk_neg拉高2拍
- 最后clk_pos & clk_neg 即为分频结果
代码
module div_and #(parameter n=5)
(
input clk,
input rstn,
output clk_div
);
reg [15:0] cnt;
reg clk_pos,clk_neg;
assign clk_div = clk_pos & clk_neg;
always @(posedge clk or negedge rstn)begin
if(!rstn)
cnt <= 0;
else if(cnt==n-1)
cnt <= 0;
else
cnt <= cnt + 1;
end
always @(posedge clk or negedge rstn)begin
if(!rstn)
clk_pos <= 1'b0;
else if(cnt==n-1)
clk_pos <= 1'b1;
else if(cnt==(n>>1))
clk_pos <= 1'b0;
end
always @(negedge clk or negedge rstn)begin
if(!rstn)
clk_neg <= 0;
else if(cnt==n-1)
clk_neg <= 1'b1;
else if (cnt==(n>>1))
clk_neg <= 1'b0;
end
endmodule
或逻辑分频
- 用或逻辑来组合,需要clk_pos和clk_neg只拉高1T,但是其本身有0.5T的时序差
- 那么在cnt=2时,clk_pos拉高1拍
- cnt=2时,clk_neg拉高1拍
- 最后clk_pos | clk_neg 即为分频结果
代码
module div_and #(parameter n=5)
(
input clk,
input rstn,
output clk_div
);
reg [15:0] cnt;
reg clk_pos,clk_neg;
assign clk_div = clk_pos & clk_neg;
always @(posedge clk or negedge rstn)begin
if(!rstn)
cnt <= 0;
else if(cnt==n-1)
cnt <= 0;
else
cnt <= cnt + 1;
end
always @(posedge clk or negedge rstn)begin
if(!rstn)
clk_pos <= 1'b0;
else if(cnt==n-1)
clk_pos <= 1'b1;
else if(cnt==(n>>1))
clk_pos <= 1'b0;
end
always @(negedge clk or negedge rstn)begin
if(!rstn)
clk_neg <= 0;
else if(cnt==n-1)
clk_neg <= 1'b1;
else if (cnt==(n>>1))
clk_neg <= 1'b0;
end
endmodule
异或逻辑分频
- 用异或逻辑来将两个六分频组合成三分频原理很简单,因为异或是相同为0,不同为1 所以需要将上升沿与下降沿差一半再翻转
- 如果上升沿在cnt=2时翻转,那么下降沿应该在cnt=1翻转
- 这样cnt带来了1T的时序差
- 而上升沿和下降沿自带0.5T的时序差。
代码
module div #(parameter n=3)
(
input clk,
input rstn,
output clk_div
);
reg [15:0] cnt;
reg clk_pos,clk_neg;
assign clk_div = clk_pos ^ clk_neg;
always @(posedge clk or negedge rstn)begin
if(!rstn)
cnt <= 0;
else if(cnt==n-1)
cnt <= 0;
else
cnt <= cnt + 1;
end
always @(posedge clk or negedge rstn)begin
if(!rstn)
clk_pos <= 0;
else if(cnt==n-1)
clk_pos <= ~clk_pos;
end
always @(negedge clk or negedge rstn)begin
if(!rstn)
clk_neg <= 0;
else if(cnt==(n>>1))
clk_neg <= ~clk_neg;
end
endmodule
边沿检测
在使用FIFO进行数据读写的时候,我们会用到边沿检测,比如当FIFO内的数据被读空了,这样 empty 信号就会从0→1,产生一个上升沿,当这个上升沿到来的时候代表FIFO内孔,我们就可以对FIFO进行一个写操作(把写使能拉高,往FIFO写数据),所以边沿检测是十分常用的东西,下面我们来讲讲边沿检测的原理、代码以及对应仿真。
原理
比如我们要检测信号 signal 的上升沿,我们使用两个寄存器,级联记录 signal 的值,这样由于寄存器延迟一拍的特性,第二个寄存器永远会延迟第一个寄存器。
当 signal 上升沿到来时,会有:第一个寄存器检测为高,第二个寄存器检测为低。
这样,我们只要把第二个寄存器取反,然后和第一个寄存器做与运算,这样当计算的数值为 1 时,就检测到了上升边沿。
代码
module edge_det(
input clk ,
input rstn ,
input signal ,
output signal_edge
);
reg reg1,reg2;
always @(posedge clk or negedge rstn)begin
if(!rstn)begin
reg1 <= 1'b0;
reg2 <= 1'b0;
end
else begin
reg1 <= signal;
reg2 <= reg1;
end
end
assign signal_edge = (~reg2) & reg1;
endmodule
testbench
module edge_det_tb();
reg clk,rstn;
reg signal;
wire signal_edge;
always #10 clk = ~clk;
initial begin
clk <= 1'b0;
rstn <= 1'b0;
signal <= 1'b0;
#50
rstn <= 1'b1;
#100
signal <= 1'b1;
end
edge_det u_edge_det (
.clk (clk) ,
.rstn (rstn) ,
.signal (signal) ,
.signal_edge (signal_edge)
);
endmodule
串转并
串转并的使用环境非常多,在接口处用到最多,在某些模块的输入仅允许串行输入时,而总线上的数时并行的,那就需要通过串并转换,把并行的数据转换成串行的数据,包括在输出的时候,需要把串行的数据转换成并行的数据,才能送到总线中,使用方法都是用一个计数器来计数实现 。
常规转换
用一个计数器count,每输入8个数,就输出一次,每周期dout_temp左移一位,然后再将输入的1bit串行据存入dout_temp的最低位
代码
module serial_to_parallel(
input clk,
input rst_n,
input din_serial,
input din_valid,
output reg [7:0]dout_parallel,
output reg dout_valid
);
reg[7:0]din_tmp;
reg[3:0]cnt;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt <= 0;
else if(din_valid)
cnt <= (cnt == 4'd8)?0:cnt+1'b1;
else
cnt <= 0;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
din_tmp <= 8'b0;
else if(din_valid && cnt <= 4'd7)
din_tmp <= {din_tmp[6:0],din_serial};
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
dout_valid <= 1'b0;
dout_parallel <= 8'b0;
end
else if(cnt == 4'd8)begin
dout_valid <= 1'b1;
dout_parallel <= din_tmp;
end
else begin
dout_valid <= 1'b0;
end
end
endmodule
Testbench:
module serial_to_parallel_tb();
reg clk,rst_n;
reg din_serial,din_valid;
wire dout_valid;
wire[7:0]dout_parallel;
always #5 clk = ~clk;
initial begin
clk <= 1'b0;
rst_n <= 1'b0;
#15
rst_n <= 1'b1;
din_valid <= 1'b1;
din_serial <= 1'b1; #10
din_serial <= 1'b1; #10
din_serial <= 1'b1; #10
din_serial <= 1'b1; #10
din_serial <= 1'b0; #10
din_serial <= 1'b0; #10
din_serial <= 1'b0; #10
din_serial <= 1'b0; #10
din_valid <= 1'b0;
#30
din_valid <= 1'b1;
din_serial <= 1'b1; #10
din_serial <= 1'b1; #10
din_serial <= 1'b0; #10
din_serial <= 1'b0; #10
din_serial <= 1'b0; #10
din_serial <= 1'b0; #10
din_serial <= 1'b1; #10
din_serial <= 1'b1; #20
din_valid <= 1'b0;
#50
$stop;
end
serial_to_parallel u0(
.clk (clk) ,
.rst_n (rst_n) ,
.din_serial (din_serial) ,
.dout_parallel (dout_parallel) ,
.din_valid (din_valid) ,
.dout_valid (dout_valid)
);
endmodule
结合握手机制的串转并
实现串并转换电路,输入端输入单bit数据,每当本模块接收到6个输入数据后,输出端输出拼接后的6bit数据。本模块输入端与上游的采用valid-ready双向握手机制,输出端与下游采用valid-only握手机制。数据拼接时先接收到的数据放到data_b的低位。
电路的接口如下图所示。valid_a用来指示数据输入data_a的有效性,valid_b用来指示数据输出data_b的有效性;ready_a用来指示本模块是否准备好接收上游数据,本模块中一直拉高;clk是时钟信号;rst_n是异步复位信号。
代码:
module s_to_p(
input clk,
input rst_n,
input valid_a,
input data_a,
output reg ready_a,
output reg valid_b,
output reg [5:0]data_b
);
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
ready_a <= 1'b0;
else
ready_a <= 1'b1;
end
re[2:0]cnt;
always@(posedge clk or negeedge rst_n)begin
if(!rst_n)
cnt <= 3'd0;
else if(valid_a && ready_a)
cnt <= cnt == 3'd5 ? 3'd0 : cnt + 1'b1;
else
cnt <= cnt;
end
reg [5:0]data_r;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
data_r <= 6'd0;
else if(valid_a && ready_a)
data_r <= {data_a,data_r[5:1]};
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
valid_b <= 1'b0;
data_b <= 6'd0;
end
else if(cnt == 3'd5)begin
data_b <= {data_a,data_r[5:1]};
valid_b <= 1'b1;
end
else
valid_b <= 1'b0;
end
endmodule
并转串
并转串的原理和串转并差不多,都是用一个计数器来技术,根据串行和并行之间的关系来决定计数器计数的上下限。
在输入数据有效的时候din_valid为高时,把输入的数据寄存一下,寄存到din_parallel_temp中,然后再用一个计数器去检测输出,当计数器处在1~8的范围内时,让输出的串行数据等于输入的并行数据的最高位,然后每周期让并行数据左移一bit。这样,八周期后,就实现了并转串。
代码:
module parallel_to_serial(
input clk,
input rst_n,
input [7:0] din_parallel,
input din_valid,
output reg dout_serial,
output reg dout_valid
);
reg[7:0]din_parallel_tmp;
reg [3:0]cnt;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt <= 0;
else if(din_valid)
cnt <= cnt + 1'b1;
else
cnt <= 0;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
din_parallel_tmp <= 8'b0;
dout_serial <= 1'b0;
dout_valid <= 1'b0;
end
else if(din_valid && cnt == 0)begin
din_parallel_tmp <= din_parallel;
end
else if((cnt >= 4'd1) && (cnt <= 4'd8))begin
dout_serial <= din_parallel_tmp[7];
din_parallel_tmp <= din_parallel_tmp << 1;
dout_valid <= 1'b1;
end
else begin
dout_serial <= 1'b0;
dout_valid <= 1'b0;
end
end
endmodule
Testbench:
module parallel_to_serial_tb();
reg clk, rst_n;
reg [7:0] din_parallel;
reg din_valid;
wire dout_valid,dout_serial;
always #5 clk = ~clk;
initial begin
clk <= 1'b0;
rst_n <= 1'b0;
#15
rst_n <= 1'b1;
din_valid <= 1'b1;
din_parallel <= 8'b11110000;
#80
din_valid <= 1'b0;
#40
din_valid <= 1'b1;
din_parallel <= 8'b10100011;
#80
din_valid <= 1'b0;
#50
$stop();
end
parallel_to_serial u_parallel_to_serial(
.clk (clk) ,
.rst_n (rst_n) ,
.din_parallel (din_parallel) ,
.din_valid (din_valid) ,
.dout_serial (dout_serial) ,
.dout_valid (dout_valid)
);
endmodule
数据位宽转换器
数据位宽转换器,一般常用于模块接口处,比如一个电路模块的输出数据位宽大于另一个模块的输入数据位宽,此时就需要进行数据位宽转换。比如SATA控制器中,内部数据位宽为32bit,但外部物理收发器PHY的接口通常为16bit,或者8bit,在不使用FIFO进行缓存的情况下,可以使用数据位宽转换器,通过时钟倍频在实现数据位宽的转换。
宽到窄
假设数据从模块A传入到模块B,模块A的输出数据为32位,模块B的输入数据位宽为16位,并把数据从A传入B而不损失数据。
假设一个原时钟clk2x,通过二分频,把原时钟变为时钟clk1x,那么clk2x的时钟频率就是clk1x的两倍。在clk2x的上升沿,我们读入32bit数据,在clk2x的上升沿我们输出16bit数据,由于clk2x的频率是clk1x的两倍,每读入一次32bit数据,就会输出两次16bit数据,从而实现了数据位宽的转换,如图:
代码
module width_32to16(
input clk,
input rst_n,
input [31:0] data_in,
output [15:0] data_out
);
reg clk1;
reg [31:0] data_r;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
clk1 <= 1'b1;
else
clk1 <= ~clk1;
end
always@(posedge clk1 or negedge rst_n)begin
if(!rst_n)
data_r <= 31'd0;
else
data_r <= data_in;
end
assign data_out = clk1?data_r[31:16]:data_r[15:0];
endmodule
Testbench:
`timescale 1ns/1ns
module width_32to16_tb();
reg clk;
reg rst_n;
reg [31:0]data_in;
wire [15:0]data_out;
width_32to16 inst(
.clk(clk),
.rst_n(rst_n),
.data_in(data_in),
.data_out(data_out)
);
always #10 clk = ~clk;
initial begin
clk =0;
rst_n = 0;
data_in = 32'h0000_0000;
#10
rst_n = 1;
#5
data_in = 32'h0000_1111;
#50
data_in = 32'h2222_3333;
#50
data_in = 32'h4444_5555;
#50
data_in = 32'h6666_7777;
#200
$stop;
end
endmodule
窄到宽
8bit to 16bit
实现数据位宽转换电路,实现8bit数据输入转换为16bit数据输出。其中,先到的8bit数据应置于输出16bit的高8位。
电路的接口如下图所示。valid_in用来指示数据输入data_in的有效性,valid_out用来指示数据输出data_out的有效性;clk是时钟信号;rst_n是异步复位信号
代码:
module width_8to16(
input clk,
input rst_n,
input valid_in,
input [7:0]data_in,
output reg valid_out,
output reg [15:0]data_out
);
reg [1:0]cnt;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt <= 2'b0;
else if(valid_in)
cnt <= (cnt == 1'd1)?1'd0:cnt + 1'b1;
else
cnt <= cnt;
end
reg [7:0]data_r;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
data_r <= 8'd0;
else
data_r <= valid_in?data_in:data_r;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data_out <= 16'd0;
valid_out <= 1'b0;
end
else if(cnt == 1)begin
data_out <= valid_in?{data_r,data_in}:data_out;
valid_out <= valid_in;
end
else begin
data_out <= data_out;
valid_out <= 1'b0;
end
end
endmodule
Testbench:
module width_8to16_tb();
reg clk;
reg rst_n;
reg valid_in;
reg [7:0]data_in;
wire valid_out;
wire [15:0]data_out;
width_8to16 inst(
.clk(clk),
.rst_n(rst_n),
.valid_in(valid_in),
.data_in(data_in),
.valid_out(valid_out),
.data_out(data_out)
);
always #10 clk = ~clk;
initial begin
clk = 0;
rst_n = 0;
valid_in = 0;
data_in = 8'd0;
#15
rst_n = 1;
#5
valid_in = 1;
#20
data_in = 8'h11;
#20
data_in = 8'h22;
#20
data_in = 8'h33;
#20
valid_in = 0;
#20
valid_in = 1;
#20
data_in = 8'h44;
#20
data_in = 8'h55;
#200
$stop;
end
endmodule
非整数倍转换
8bit to 12bit
代码:
module width_8to12(
input clk ,
input rst_n ,
input valid_in ,
input [7:0] data_in ,
output reg valid_out,
output reg [11:0] data_out
);
reg [1:0]cnt;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt <= 0;
else if(valid_in)
cnt <= cnt == 2?0:cnt + 1;
else
cnt <= cnt;
end
reg [7:0]data_r;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
data_r <= 0;
else
data_r <= valid_in?data_in:data_r;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
data_out <= 0;
else if(cnt == 1)
data_out <= valid_in?{data_r,data_in[7:4]}:data_out;
else if(cnt == 2)
data_out <= valid_in?{data_r[3:0],data_in}:data_out;
else
data_out <= data_out;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
valid_out <= 0;
else
valid_out <= (cnt == 1 || cnt == 2) && valid_in;
end
endmodule
testbench:
module width_8to12_tb();
reg clk;
reg rst_n;
reg valid_in;
reg [7:0] data_in;
wire valid_out;
wire [11:0] data_out;
width_8to12 inst(
.clk(clk),
.rst_n(rst_n),
.valid_in(valid_in),
.data_in(data_in),
.valid_out(valid_out),
.data_out(data_out)
);
always #10 clk = ~clk;
initial begin
clk = 0;
rst_n = 0;
data_in = 0;
valid_in = 0;
#15
rst_n = 1;
#15
valid_in = 1;
data_in = 8'ha0;
#40
valid_in = 0;
data_in = 8'ha1;
#60
valid_in = 1;
#20
valid_in = 0;
data_in = 8'hb0;
#200
$stop;
end
endmodule
24bit to 128bit
代码:
module width_24to128(
input clk,
input rst_n,
input valid_in,
input [23:0] data_in,
output reg valid_out,
output reg [127:0] data_out
);
reg [3:0]cnt;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt <= 4'd0;
else if(valid_in)
cnt <= (cnt == 4'd15)?0:cnt + 1'b1;
end
reg [127:0] data_r;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
data_r <= 128'd0;
else
data_r <= valid_in?{data_r[103:0],data_in}:data_r;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
data_out <= 128'd0;
else if(cnt == 4'd5)
data_out <= valid_in?{data_r[119:0],data_in[23:16]}:data_out;
else if(cnt == 4'd10)
data_out <= valid_in?{data_r[111:0],data_in[23:8]}:data_out;
else if(cnt == 4'd15)
data_out <= valid_in?{data_r[103:0],data_in[23:0]}:data_out;
else
data_out <= data_out;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
valid_out <= 1'd0;
else
valid_out <= (cnt == 4'd5 || cnt == 4'd10 || cnt == 4'd15) && valid_in;
end
endmodule
Testbench:
module width_24to128_tb();
reg clk;
reg rst_n;
reg valid_in;
reg [23:0] data_in;
wire valid_out;
wire [127:0] data_out;
width_24to128 inst(
.clk(clk),
.rst_n(rst_n),
.valid_in(valid_in),
.data_in(data_in),
.valid_out(valid_out),
.data_out(data_out)
);
always #10 clk = ~clk;
initial begin
clk = 0;
rst_n = 0;
valid_in = 0;
data_in = 0;
#15
rst_n = 1;
#15
valid_in = 1;
data_in = 24'ha0a1a2;
#20;
data_in = 24'hb2b1b0;
#20;
data_in = 24'hc2c1c0;
#20;
data_in = 24'hd2d1d0;
#20;
data_in = 24'he2e1e0;
#20;
data_in = 24'hf2f1f0;
#20
valid_in = 0;
#200
$stop;
end
endmodule