1.数字电路系统设计的层次化
串行加法器:
一个四位串行加法器由4个全加器构成。全加器是串行加法器的子模块,而全加器是由基本的逻辑门构成,这些基本的逻辑门就是所说的叶子模块。这个设计中运用叶子模块(基本逻辑门)搭建成子模块(全加器),再用子模块搭建成所需要的电路(串行加法器)。
显然,Bottom-Up的设计方法没有明显的规律可循,主要依靠设计者的实践经验和熟练的设计技巧,用逐步试探的方法最后设计出一个完整的数字系统。系统的各项性能指标只有在系统构成后才能分析测试 此种设计方法常用于原理图的设计中,相比于其它方法此种方法对于实现各个子模块电路所需的时间较短。
使用Top-Down设计方法对一个典型cpu进行设计:
向量点积乘法器:
采用模块层次化设计方法,设计4维向量点积乘法器,其中向量a =(a1,a2,a3,a4);b=(b1,b2,b3,b4)。点积乘法规则:
Verilog代码:
module vector(a1,a2,a3,a4,b1,b2,b3,b4,out);
input [3:0] a1,a2,a3,a4,b1,b2,b3,b4;
output [9:0] out;
wire [7:0] out1,out2,out3,out4;
wire [8:0] out5, out6;
wire [9:0] out;
mul_addtree U1(.x(a1), .y(b1), .out(out1));
mul_addtree U2(.x(a2), .y(b2), .out(out2));
mul_addtree U3(.x(a3), .y(b3), .out(out3));
mul_addtree U4(.x(a4), .y(b4), .out(out4));
add #(8) U5(a(out1), .b(out2), .out(out5));
add #(8) U6(.a(out3), .b(out4), .out(out6));
add #(9) U7(.a(out5), .b(out6), .out(out));
endmodule
//adder
module add(a,b,out);
parameter size=8;
input [size-1:0] a,b;
output [size:0j out;
assign out=a+b;
endmodule
//Multiplier
module mul_addtree(mul_a,mul_b,mul_out);
input [3:0] mul_a.mul_b;
output [7:0] mul_out;
wire [3:0] muI_out;
wire [3:0] stored0,stored1,stored2,stored3;
wire [3:0] add0l, add23;
assign stored3=mul_b[3]?{1'b0,mul_a,3'b0):8’b0;
assign stored2=mul_b[2]?{2’b0,mul_a ,2’b0}:8’b0;
assign stored1=mul_b[1]?{3'b0,mul_a,1'b0}:8’b0;
assign storedo=mul_b[0]?{4’b0,mul_a}:8’b0;
assign add01=storedl +stored0;
assign add23=stored3+stored2;
assign mul_out=add0l +add23;
endmodule
2.典型电路设计
2.1加法器树乘法器
加法器树乘法器的设计思想是”移位后加",并且加法运算采用加法器树的形式。乘法运算的过程是:被乘数与乘数的每一位相乘并且乘以相应的权值,最后将所得的结果相加,便得到了最终的乘法结果。
例:下图是一个4位的乘法器结构,用verilog设计一个加法器树4位乘法器
module mul_addtree(mul_a,mul_b,mul_out);
input [3:0] mul_a,mul_b;
output [7:0] mul_out;
wire [7:0] mul_out;
wire [7:0] stored0,stored1,stored2,stored3;
wire [7:0] add01,add23;
assign stored3 = mul_b[3]?{1'b0,mul_a,3'b0}:8'b0;
assign stored2 = mul_b[2]?{2'b0,mul_a,2'b0}:8'b0;
assign stored1 = mul_b[1]?{3'b0,mul_a,1'b0}:8'b0;
assign stored0 = mul_b[0]?{4'b0,mul_a,0'b0}:8'b0;
assign add01=stored1+stored0;
assign add23=stored3+stored2;
assign mul_out=add01 + add23;
endmodule
module mult_addtree_tb;
reg [3:0] mult_a;
reg [3:0] mult_b;
wire [7:0] mult_out;
mul_addtree U1(.mul_a(mult_a), .mul_b(mult_b), .mul_out(mult_out));
initial
begin
mult_a = a;
mult_b = 0;
repeat(9)
begin
#20 mult_a = mult_a + 1;
mult_b = mult_b + 1;
end
end
endmodule
流水线结构:
例:下图是一个4位的乘法器结构, 用Verilog设计一个两级流水线加法器树4位乘法器。
两级流水线加法器树4位乘法器结构如图所示,通过在第一级与第二级、第二级与第三级加法器之间插入D触发器组,可以实现两级流水线设计。
module mul_addtree_2_stage(clk,clr,mul_a,mul_b,mul_out);
input clk,clr;
input [3:0] mul_a,mul_b;
output [7:0] mul_out;
reg [7:0] add_tmp_1,add_tmp_2,mul_out;
wire [7:0] stored0,stored1,stored2,stored3;
assign stored3 = mul_b[3]?{1'b0,mul_a,3'b0}:8'b0;
assign stored2 = mul_b[2]?{2'b0,mul_a,2'b0}:8'b0;
assign stored1 = mul_b[1]?{3'b0,mul_a,1'b0}:8'b0;
assign stored0 = mul_b[0]?{4'b0,mul_a}:8'b0;
always@(posedge clk or negedge clr)
begin
if(!clr)
begin
add_tmp_1 <= 8'b0000_0000;
add_tmp_2 <= 8'b0000_0000;
mul_out <= 8'b0000_0000;
end
else
begin
add_tmp_1 <= stored3 + stored2;
add_tmp_2 <= stored1 +stored0;
mul_out <= add_tmp_1 + add_tmp_2;
end
end
endmodule
module mult_addtree_2_stag_tb;
reg clk,clr;
reg [3:0] mult_a,mult_b;
wire [7:0] mult_out;
mul_addtree_2_tage U1(.mul_a(mult_a), .mul_b(mult_b), .mul_out(mult_out), .clk(clk), .clr(clr));
initial
begin
clk = 0;clr = 0;mult_a = 1;mult_b = 1;
#5 clr = 1;
end
always #10 clk = ~clk;
initial
begin
repeat(5)
begin
#20 mult_a = mult_a + 1;
mult_b = mult_b + 1;
end
end
endmodule
2.2Wallace树乘法器
Wallace树乘法器运算原理如下图,其中FA为全加器HA为半加器。其基本原理是,加法从数据最密集的地方开始,不断地反复使用全加器半加器来覆盖"树"。这一级全加器是一个3输入2输出的器件,因此全加器又称为3-2压缩器。通过全加器将树的深度不断缩减,最终缩减为一个深度为2的树。最后一级则采用一个简单的两输入加法器组成。
module wallace(x,y,out);
parameter size=4;
input [size-1:0] x,y;
output [2*size-1:0] out;
wire [size*size-1:0] a;
wire [1:0] b0,b1,c0,c1,c2,c3;
wire [5:0] add_a,add_b;
wire [6:0] add_out;
wire [2*size-1 :0] out;
assign a={x[3],x[3],x[2],x[2],x[1],x[3],x[1],x[0],x[3],x[2],x[1],x[0],x[2],x[1],x[0],x[0]}
&
{y[3],y[2],y[3],y[2],y[3],y[1],y[2],y[3],y[0],y[1],y[1],y[2],y[0],y[0],y[1],y[0]};
hadd U1(.x(a[8]), .y(a[9]), .out(b0));
hadd U2(.x(a[11]), .y(a(a[12]), .out(b1));
hadd U3(.x(a[4]), .y(a[5]), .out(c0));
fadd U4(.x(a[6]), .y(a[7]), .z(b0[0]),.out(c1));
fadd U5(.x(a[13]), .y(a[14]), . z(b0[1]), .out(c2));
fadd U6(.x(b1[0]), .y(a[10]), .z(b1[1], .out(c3));
assign add_a = {c3[1],c2[1],c1[1],c0[1],a[3],a[1]);
assign add_b ={ a[15],c3[0],c2[0],c1[0],c0[0],a[2]};
assign add_out = add_a + add_b;
assign out={add_out,a[0]};
endmodule
module fadd(x, y, z, out);
output [1:0] out;
input x,y,z;
assign out=x+y+z;
endmodule
module hadd(x, y, out);
output [1:0] out;
input x.y;
assign out=x+y;
endmodule
module wallace_tb;
reg [3:0] x, y;
wire [7:0] out;
wallace m(.x(x), .y(y), .out(out));
initial
begin
x=3; y=4;
#20 x=2; y=3;
#20 x=6; y=8;
end
endmodule
2.3复数乘法器
复数乘法器的电路结构如下图所示。将复数x的实部与复数y的实部相乘,减去x的虚部与y的虚部相乘,得到输出结果的实部。将x的实部与y的虚部相乘,加上x的虚部与y的实部相乘,得到输出结果的虚部。
module complex(a,b,c,d,out_real,out_im);
input [3:0]a,b,c,d;
output [8:0] out_real,out_im;
wire [7:0] sub1,sub2,add1,add2;
wallace U1(.x(a), .y(c), .out(sub1));
wallace U2(.x(b), .y(d), .out(sub2));
wallace U3(.x(a), .y(d), .out(add1));
wallace U4(.x(b), .y(c), .out(add2));
assign out_real=subl - sub2;
assign out_im = add1 + add2;
endmodule
module complex_tb;
reg [3:0] a,b,c,d;
wire [8:0] out_real;
wire [8:0] out_im;
complex U1(.a(a), .b(b), .c(c), .d(d), .out_real(out_real), .out_im(out_im));
initial
begin
a=2;b=2;c=5;d=4;
#10
a=4;b=3;c=2;d=1;
#10
a=3;b=2;c=3;d=4
end
endmodule
2.4 FIR滤波器设计
有限冲激响应(FIR)滤波器就是一种常用的数字滤波器,采用对己输入样值的加权和来形成它的输出。其系统函数为:
其中Z-1表示延时一个时钟周期,Z-2表示延时两个时钟周期。
对于输入序列X[n]的FIR滤波器可用下图所示的结构示意图来表示,其中X[n]是输入数据流。各级的输入连接和输出连接被称为抽头,并且系数(b0,b1,…,bn)被称为抽头系数。一个M阶的FIR滤波器将会有M+1个抽头。
通过移位寄存器用每个时钟边沿n(时间下标)处的数据流采样值乘以抽头系数,并将它们加起来形成输出Y[n]。
代码如下:
module FIR(Data_out, Data_in ,dock,reset); //模块FIR
output [9:0] Data_out;
input [3:0] Data_in;
input clock,reset;
wire [9:0] Data_out;
wire [3:0] samples_0,sampies_1 ,samples_2,samples_3,samples_4,
samples_5, samples_6, samples_7, samples_8;
shift_register U1(.Data_in(Data_in), .clock(ciock), .reset(reset),
.samples_0(samples_0), .samples_1(samples_1),
.samples_2(samples_2), .samples_3(samples_3),
.samples_4(samples_4), .samples_5(samples_5),
.samples_6(sam ples_6), .samples_7(samples_7),
.samples_8(samples_8));
caculator U2(.samples_0(samples_0), .samples_1 (samples_1),
.samples_2(samples_2), .sam ples_3(samples_3),
.samples_4(samples_4), .samples_5(sam ples_5),
.sampIes_6(samples_6), .samples_7(sampies_7),
.samples_8(sam pies_B), .Data_out( Data_out));
endmodule
shift_register
module shift_register(Data_in,clock,reset,samples_0,samples_1 ,samples_2,samples_3,
samples_4,samples_5,samples_6,samples_7, samples_8);
input [3:0] Data_in;
input clock,reset;
output [3:0] samples_0,samples_1 ,samples_2,samples_3,samples_4,
samples_5,samples_6, samples_7,samples_8;
reg [3:0] samples_0,samples_l ,samples_2,samples_3,samples_4,
samples_5, sam pies 6, sampies_7,samptes_8;
always(posedge clock or negedge reset)
begin
if(reset)
begin
samples_0 <= 4’b0;
samples_1 <= 4’b0;
samples_2 <= 4’b0;
samples_3 <= 4’b0;
samples_4 <= 4’b0;
samples_5 <= 4’b0;
samples_6 <= 4’b0;
samples_7 <= 4’b0;
samples_8 <= 4’b0;
end
else
begin
samples_0 <= Data_in;
samples_1 <= samples_0;
samples_2 <= samples_1;
samples_3 <= samples_2;
samples_4 <= samples_3;
samples_5 <= samples_4;
samples_6 <= samples_5;
samples_7 <= samples_6;
samples_8 <= samples_7;
end
end
endmodule
//模块caculator
module caculator(sampies_O,samples_i ,samples_2,samples_3,samples_4,
samples_5,samples_6, samples_7,samples_8,Data_out);
input [3:0] samples_0,samples_1 ,samples_2,samples_3,samples_4,samples_5,samples_6, samples_7,samples_8;
output [9:0] Data_out;
wire [9:0] Data_out;
wire [3:0] out_tmp_1 ,out_tmp_2,out_tmp_3,out_tmp_4,out_tmp_5;
wire [7:0] outl,out2,out3,out4,out5;
parameter b0=4’b0010;
parameter b1=4’b0011;
parameter b2=4’b0110;
parameter b3=4’b1010;
parameter b4=4’b1100;
mul_addtree U1(.mul_a(b0),.mul_b(out_tmp_1),.mul_out(out1));
mul_addtree U2(.mul_a(b1),.mul_b(out_tmp_2),.mul_out(out2));
mul_addtree U3(.mul_a(b2),.mul_b(out_tmp_3),.mul_out(out3));
mul_addtree U4(.mul_a(b3),.mul_b(out_tmp_4),.mul_out(out4));
mul_addtree U5(.mul_a(b4),.mul_b(samples_4),.mul_out(out5));
assign out_tmp_1 = samples_0 + samples_8;
assign out_tmp_2 =samples_1 + samples7;
assign out_tmp_3 = samples_2 + samples_6;
assign out_tmp_4 = samples3 + samples_5;
assign Data_out = out1 +out2 + out3 + out4 + qout5;
endmodule
//模块FIR_tb
module FIR_tb;
reg clock,reset;
reg [3:0] Data_in;
wire [9:0] Data_out;
FIR U1(.Data_out(Data_out), .Data_in(Data_in), .clock(clock), reset(reset));
initial
begin
Data_in = 0; clock = 0; reset = 1;
#10 reset = 0;
end
always
begin
#5 clock <= ~clock;
#5 Data_in <= Data_in+1;
end
endmodule
2.5 片内存储器的设计
(1)RAM 的 Verilog描述
RAM是随机存储器,存储单元的内容可按需随意取出或存入。这种存储器在断电后将丢失掉所有数据,一般用来存储一些短时间内使用的程序和数据。其内部结构如下所示:
例:用Verilog设计深度为8,位宽为8的单端口RAM。单口RAM,只有一套地址总线,读操作和写操作是分开的。
module ram_single(clk, addm, cs_n, we_n, din, dout);
input clk; //clock signal
input [2:0] addm; //address signal
input cs_n; //chip select signal
input we_n; //write enable signal
input [7:0] din; //input data
output[7:O] dout; //output data
reg [7:0] dout;
reg [7:0] raml [7:0]; //8*8 bites register
aIways(posedge clk)
begin
if(cs_n)
dout <= 8’bzzzz_zzzz;
else
if(we_n) //read data
dout <= raml[addm];
else //write data
raml[addm] <= din;
end
end module
module ram single tb;
reg clk, we_n, cs_n;
reg [2:0] addm;
reg [7:0] din;
wire [7:0] dout;
ram_single U1(.clk(clk),.addm(addm),.cs_n(cs_n),.we_n(we_n),.din(din),.dout(dout));
initial
begin
clk=0; addm=0; cs_n=1; we_n=0; din=0;
#5 cs_n=0;
#315 we_n=1;
end
always #10 clk=~clk;
initial
begin
repeat(7)
begin
#40 addm=addm+1;
din=din+1;
end
#40 repeat(7)
#40 addm=addm-1;
end
endmodule
例:用Verilog设计深度为8,位宽为8的双端口RAM。双口RAM具有两套地址总线,一套用于读数据,另一套用于写数据。 二者可以分别独立操作。
module ram_dual(q, addr _n, addr_out, d, we, rd, clk1, clk2);
output [7:0] q; //output data
input [7:0] d; //input data
input [2:0] addr_in; //write data address signal
input [2:0] addr_out; //output data address signal
input we; //write data control signal
input rd; //read data control signal
input clk1; //write data clock
input clk2; //read data clock
reg[7:0] q;
reg[7:0] mem[7:0]; //8*8 bites register
always@(posedge clk1)
begin
if(we)
mem[addr_n] <= d;
end
always@(posedge clk2)
begin
if(rd)
q <= mem[addr_out];
end
endmodule
module ram_dual_tb;
reg clk1, clk2, we, rd;
reg [2:0] addr_in;
reg [2:0 ]addr_out;
reg [7:0] d;
wire [7:0] q;
ram_dual U1(.q(q),.addr_in(addr_in),.addr_out(addr_out),.d(d),.we(we),.rd(rd),.clk1(clk1),.clk2(clk2));
initial
begin
clk1=0; clk2=0; we=1; rd=0; addr_in=0; addr_out=0; d=0;
#320 we=0;
rd=1;
end
always
begin
#10 clk1 = ~clk1;
clk2 = ~clk2;
end
initial
begin
repeat(7)
begin
#40 addr_in=addr_in+1;
d=d+1;
end
#40
repeat(7) #40 addr_out=addr_out+1;
end
endmodule
(2)ROM的Verilog描述
ROM即只读存储器,是一种只能读出事先存储的数据的存储器,其特性是存入数据无法改变,也就是说这种存储器只能读不能写。由于ROM在断电之后数据不会丢失,所以通常用在不需经常变更资料的电子或电脑系统中,资料并不会因为电源关闭而消失。
module rom(dout, clk, addm, cs_n);
input clk, cs_n;
input [2:0] addm;
output [7:0] dout;
reg [7:0] dout;
reg [7:0] rom[7:0];
initial
begin
rom[0]=8b0000_0000;
rom[1]=8b0000_0001;
rom[2]=8b0000_0010;
rom[3]=8b0000_0011;
rom[4]=8b0000_0100
rom[5]=8b0000_0101;
rom[6]=8b0000_0110;
rom[7]=8'b0000_0111;
end
always@(posedge clk)
begin
if(cs_n)
dout<=8'bzzzz_zzzz;
else
dout<=rom[addm];
end
endmodule
module rom_tb;
reg clk, cs_n;
reg [2:0] addm;
wire [7:0] dout;
rom U1(.dout(dout),.clk(clk),.addm(addm),.cs_n(cs_n));
initial
begin
clk=0; addm=0; cs_n=0;
end
always #10 clk=~clk;
initial
begin
repeat(7)
#20 addm=addm+1;
end
endmodule
2.6 FIFO设计
FIFO(First In First Out) 是一种先进先出的数据缓存器,通常用于接口电路的数据缓存。与普通存储器的区别是没有外部读写地址线,可以使用两个时钟分别进行写和读操作。FIFO只能顺序写入数据和顺序读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
FIFO由存储器块和对数据进出FIFO的通道进行管理的控制器构成,每次只对一个寄存器提供存取操作,而不是对整个寄存器阵列进行。FIFO有两个地址指针,一个用于将数据写入下一个可用的存储单元,一个用于读取下一个未读存储单元的操作。读写数据必须一次进行。
其读写过程如图所示:
当一个堆栈为空时 (图A),读数据指针和写数据指针都指向第一个存储单元如所示;当写入一个数据时(图B)写数据指针将指向下个存储单元;经过七次写数据操作后(图C)写指针将指向最后一个数据单元;当经过连续八次写操作之后写指针将回到首单元并且显示堆栈状态为满(图D)。数据的读操作和写操作相似,当读出一个数据时,读数据指针将移向下一个存储单元,直到读出全部的数据,此时读指针回到首单元,堆栈状态显示为空。
一个FIFO的组成一般包括两个部分: 地址控制部分和存储数据的RAM部分。如下图所示。地址控制部分可以根据读写指令生成RAM地址。RAM用于存储堆栈数据,并根据控制部分生成的地址信号进行数据的存储和读取操作。这里的RAM采用的是前面提到的双口RAM。
例: 用Verilog HDL设计深度为8,位宽为8的FIFO
//顶层模块:
module FIFO_buffer(clk,rst,write_to_stack,read_from_stack,Data_in,Data_out);
input clk,rst;
input write_to_stack,read_from_stack;
input [7:0] Data_in;
output [7:0] Data_out;
wire [7:0] Data_out;
wire stack_full, stack_empty;
wire [2:0] addr_in, addr_out;
FIFO_control U1(.stack_full(stack_full),.stack_empty(stack_empty),.write_to_stack(write_to_stack),
.write_ptr(addr_in),read_ptr(addr_out),.read from stack(read from stack),
.clk(clk),.rst(rst));
ram_dual U2(.q(Data out),.addr_in(addr_in),.addr_out(addr_out),.d(Data_in),
.we(write_to_stack),.rd(read_from_stack),.clk1(clk),.clk2(clk));
endmodule
//控制模块:
module FIFO_control(write_ptr, read_ptr, stack_full, stack_empty, write_to_stack,read_from_stack, clk, rst);
parameter stack_width=8;
parameter stack_height=8
parameter stack_ptr_width=3,
output stack_full; //stack full flag
output stack_empty; //stack empty flag
output [stack_ptr_width-1:0] read_ptr; //read data address
output[stack_ptr_width-1:0] write ptr; //write data address
input write_to_stack; //write data to stack
input read_from_stack; //read data from stack
input clk;
input rst;
reg [stack_ptr_width-1:0] read_ptr;
reg [stack_ptr_width-1:0] write_ptr;
reg [stack_ptr_width:0] ptr_gap;
reg [stack_width-1:0] Data_out;
reg [stack_width-1:0] stack[stack_height-1:0];
//stack status signal
assign stack_full=(ptr_gap==stack height);
assign stack_empty=(ptr_gap==0);
always@(posedge clk or posedge rst)
begin
if(rst)
begin
Data_out<=0;
read_ptr<=0;
write_ptr<=0;
ptr_gap<=0;
end
else if(write_to_stack && (!stack_full) && (!read_from_stack))
begin
write_ptr<=write_ptr+1;
ptr_gap<=ptr_gap+1;
end
else if(!write_to_stack && (!stack_empty) && (read_from_stack))
begin
read_ptr<=read_ptr+1;
ptr_gap<=ptr_gap-1;
end
else if(write_to_stack && stack_empty && read_from_stack)
begin
write_ptr<=write_ptr+1;
ptr_gap<=ptr_gap+1;
end
else if(write_to_stack && stack_full && read_from_stack)
begin
read_ptr<=read_ptr+1;
ptr_gap<=ptr_gap-1;
end
else if(write_to_stack && read_from_stack&& (!stack_full)&&(!stack_empty))
begin
read_ptr<=read_ptr+1;
write_ptr<=write_ptr+1;
end
end
endmodule
module FIFO_tb;
reg clk, rst;
reg [7:0] Data_in;
reg write_to_stack, read_from_stack;
wire [7:0] Data_out;
FIFO_buffer U1(.clk(clk),.rst(rst),.write_to_stack(write_to_stack),
.read_from_stack(read_from_stack),.Data_in(Data_in),.Data_out(Data_out));
initial
begin
clk=0; rst=1; Data_in=0, write_to-stack=1; read_from_stack=0;
#5 rst=0;
#155 write_to_stack=0;
read _rom_stack=1:
end
always #10 clk = ~clk;
initial
begin
repeat(7)
#20 Data_in =Data_in+1;
end
endmodule
2.7 键盘扫描和编码器
键盘扫描和编码器用于在拥有键盘的数字系统中手工输入数据,通过检测按键是否按下,产生一个唯一对应此按键的扫描码。
例:用Verilog设计十六进制键盘电路的键盘扫描和编码器:
控制信号状态机转移图如下图所示:
详情见:16进制键盘扫描器的Verilog实现
此时行列线的交叉处就是按键的位置。根据已确定的按键的位置输出其对应的编码信息。其键盘编码表如下表所示。
Key | Row[3:0] | Col[3:0] | Code |
---|---|---|---|
0 | 0001 | 0001 | 0000 |
1 | 0001 | 0010 | 0001 |
2 | 0001 | 0100 | 0010 |
3 | 0001 | 1000 | 0011 |
4 | 0010 | 0001 | 0100 |
5 | 0010 | 0010 | 0101 |
6 | 0010 | 0100 | 0110 |
7 | 0010 | 1000 | 0111 |
8 | 0100 | 0001 | 1000 |
9 | 0100 | 0010 | 1001 |
A | 0100 | 0100 | 1010 |
B | 0100 | 1000 | 1011 |
C | 1000 | 0001 | 1100 |
D | 1000 | 0010 | 1101 |
E | 1000 | 0100 | 1110 |
F | 1000 | 1000 | 1111 |
为了使测试更接近于真实的物理环境,测试平台中必须包括模拟按键状态的信号发生器,能确认按键对应行线的模块Row_signal和被测试模块Hex Keypad Grayhill 072。模拟按键状态的信号发生器可以嵌入在测试平台中,通过不断地给key信号赋值,模拟产生不同的按键信号。Row_Signal模块用于检测按键的有效性并确定按键所处的行。而Synchronizer模块通过检测各个行线值的或来确定是否有按键按下,当此模块的输出发生变化时,被测模块Hex Keypad Grayhil 072将会确定按键的位置并输出相应的代码
其Verilog HDL程序代码是:
// 顶层模块:
module keypad(clock,reset,row,code,vaild,col);
input clock,reset;
input [3:0] row;
output [3:0] code;
output vaild;
output [3:0] col;
wire s_row;
hex_keypad_grayhill U1(.code(code),.col(col),.valid(valid),
.row(row),.s_row(s_row),.clock(clock),.reset(reset));
synchronizer U2(.s_row(srow),.row(row),.clock(clock),.reset(reset));
endmodule
//编码模块:
module hex_keypad_grayhill(code,col,valid,row,s_row,clock,reset);
output [3:0] code;
output valid;
output [3:0] col;
input [3:0] row;
inputs row;
input clock,reset;
reg [3:0] col;
reg[3:0] code;
reg [5:0] state,next_state,
parameter s_0=6'b000001,s_1=6'b000010,s_2=6'b000100;
parameter s_3=6'b001000,s_4=6'b010000,s_5=6'b100000;
assign valid=((state==s_1)|(state==s_2)|(state==s_3)|(state==s_4))&&row;
always@(row or col)
case(frow,col})
8'b0001_0001: code=0;
8'b0001_0010: code=1;
8'b0001_0100: code=2;
8'b0001_1000: code=3;
8'b0010_0001: code=4;
8'b0010_0010: code=5;
8'b0010_0100: code=6;
8'b0010_1000: code=7;
8'b0100_0001: code=8,
8'b0100_0010: code=9;
8'b0100_0100: code=10;
8'b0100_1000: code=11;
8'b1000_0001: code=12;
8'b1000_0010: code=13;
8'b1000_0100: code=14;
8'b1000_1000: code=15;
default code=0;
endcase
always@(state or s_row or row) //next-state logic
begin
col=0:next_state=state;
case(state)
s_0:
begin
col=15;
if(s_row) next_state=s_1;
end
s_1:
begin
col=1;
if(row) next_state=s_5;
else next_state=s_2
end
s_2:
begin
col=2;
if(row) next_state=s_5;
else next_state=s_3,
end
s_3:
begin
col=4;
if(row) next_state=s_5;
else next_state=s _4;
end
s_4:
begin
col=8;
if(row) next_state=s_5;
else next_state=s_0;
end
s_5:
begin
col=15;
if(!row) next_state=s_0;
end
endcase
end
always@(posedge clock or posedge reset)
if(reset)
state<=s_0;
else
state<=next_state;
endmodule
module synchronizer(s_row,row,clock,reset);
output s_row;
input [3:0] row;
input clock,reset;
reg a_row,s_row;
always@(negedge clock or posedge reset)
begin
if(reset)
begin
a_row<=0;
s_row<=0;
end
else
begin
a_row<=(row[0]llrow[1]llrow[2]llrow[3]);
s row<=a row;
end
endendmodule
//模拟键盘产生信号
module row_signal(row,key,col);
output [3:0] row;
input [15:0] key;
input[3:0] col;
reg[3:0] row;
always@(key or col)
begin
row[0]=key[0]&&col[0]||key[1]&&col[1]||key[2]&&col[2]||key[3]&&col[3];
row[1]=key[4]&&col[0]||key[5]&&col[1]||key[6]&&col[2]||key[7]&&col[3];
row[2]=key[8]&&col[0]||key[9]&&col[1]||key[10]&&col[2]||key[11]&&col[3];
row[3]=key[12]&&col[0]||key[13]&&col[1]key[14]&&col[2]||key[15]&&col[3];
end
endmodule
//Testbench
module hex_keypad_grayhill_tb;
wire [3:0] code;
wirevalid;
wire [3:0] col;
wire [3:0] row;
reg clock;
reg reset;
reg [15:0] key;
integer j,k;
reg [39:0] pressed;
parameter [39:0] key_0="key_0";
parameter [39:0] key_1="key_1";
parameter [39:0] key_2="key_2";
parameter [39:0] key_3="key_3";
parameter [39:0] key_4="key_4";
parameter [39:0] key_5="key_5";
parameter [39:0] key_6="key_6";
parameter [39:0] key_7="key_7";
parameter [39:0] key 8="key 8";
parameter [39:0] key_9="key_9";
parameter [39:0] key_A="key_A";
parameter [39:0] key_B="key_B";
parameter [39:0] key_C="key_C";
parameter [39:0] key_D="key_D";
parameter [39:0] key_E="key_E";
parameter [39:0] key_F="key_F";
parameter [39:0] None="None";
keypad U1(.clock(clock),.reset(reset),.row(row),
.code(code),.vaild(vaild),.col(col)); //top module
row_signal U2(.row(row),.key(key),.col(col)); // Simulatesignal generation
always@(key)
begin
case(key)
16'h0000: pressed=None;
16'h0001: pressed=key_0;
16'h0002: pressed=key_1;
16'h0004: pressed=key_2;
16'h0008: pressed=key_3;
16'h0010: pressed=key_4;
16'h0020: pressed=key_5;
16'h0040: pressed=key_6;
16'h0080: pressed=key_7;
16'h0100: pressed=key_8;
16'h0200: pressed=key_9;
16'h0400: pressed=key_A;
16'h0800: pressed=key_B;
16'h1000: pressed=key_C;
16'h2000: pressed=key_D;
16'h4000: pressed=key_E;
16'h8000: pressed=key_F;
default: pressed=None;
endcase
end
initial #2000 $stop;
initial
begin
clock=0;
forever #5 clock=~clock;
end
initial
begin
reset=1;
#10 reset=0;
end
initial
begin
for(k=0;k<=1;k=k+1)
begin
key=0;
#20 for(j=0;j<=16;j=j+1)
begin
#20 keyli]=1;
#60 key=0;
end
end
end
endmodule
2.8 log函数的Verilog设计
log函数是一种典型的单目计算函数,与其相应的还有指数函数、三角函数等。对于单目计算函数的硬件加速器设计一般两种简单方法:一种是查找表的方式;一种是使用泰勒级数展开成多项式进行近似计算。这两种方式在设计方法和精确度方面有很大的不同。查找表方式是通过存储器进行设计,设计方法简单,其精度需要通过提高存储器深度实现,在集成电路中占用面积大,因此着这种方式通常在精度要求不高的近似计算中使用。泰勒级数展开方式采用乘法器和加法器实现,可以通过增加展开级数提高计算精确度。
例:用Verilog HDL设计采用查找表方式的log函数,输入信号位宽4bits,输出信号位宽8bits
其中输入数据为一位整数位三位小数位精确到2-3,输出结果两位整数位六位小数位精确到2-6。其Verilog程序代码是:
module log_lookup(x,clk,out);
input [3:0] x;
input clk;
output [7:0] out;
reg [7:0] out;
always@(posedge clk)
begin
case(x)
4b1000:out<=8b00000000;
4b1001:out<=8b00000111;
4b1010:out<=8b00001110;
4b1011:out<=8b00010101;
4b1100:out<=8b00011001;
4b1101:out<=8b00100000;
4b1110:out<=8b00100100;
4b1111:out<=8b00101000
default:out<=8'bz;
endcase
end
endmodule
module log_lookup_tb;
reg clk;
reg [3:0]x;
wire [7:0] out;
initial
begin
x=4'b1000;
clk=1'b0;
repeat(7)
#10 x=x+1;
end
always #5 clk=~clk;
log_lookup U1(.x(x),.ck(clk),.out(out));
endmodule
例:用Verilog设计采用泰勒级数展开方式的log函数,输入信号位宽4bits,输出信号位宽8bits
泰勒级数的定义:若函数f (x) 在点的某一邻域内具有直到 (n+1)阶导数,则在该邻域内f (x) 的阶泰勒公式为:
泰勒级数可以将一些复杂的函数用多项式相加的形式进行近似,从而简化其硬件实现。logax在x0=b处的泰勒展开为:
误差范围为:
在x0=1处展开为:
误差范围:
电路结构图如下:
上述的log函数在X=1处展开,并且要求X的取值范围为1<X<2,输入4位二进制数据X精确到2-3,其中一位整数位四位小数位,输出8位二进制数据精确到2-6,其中两位整数位六位小数位。设计当中所用到的乘法器和减法器均采用前文所给出的减法器和乘法器。
module log(x,out);
input[3:0] x;
output[7:0] out;
wire [3:0] out1;
wire [7:0] out2,out3, out5, out;
wire [3:0] out4;
assign out4={out3[7:4]};
assign out1=x-4'b1000;
wallace U1(.x(out1),.y(4'b0111),.out(out2));
wallace U2(.x(out1),.y(out1),.out(out3));
wallace U3(.x(out4),.y(4'b011),.out(out5));
assign out=out2-out5;
endmodule
module log_tb;
reg [3:0] x=4'b1000
wire [7:0] out;
log U1(.x(x),.out(out));
always
#10 x=x+1;
always@(x)
begin
if(x==4'b0000)
$stop;
end
endmodule
2.9 CORDIC算法的Verilog实现
坐标旋转数字计算机CORDIC(Coordinate Rotation Digital Computer)算法,通过移位和加减运算,能递归计算常用函数值,如sin, cos,sinh,cosh等函数,最早用于导航系统,使得矢量的旋转和定向运算不需要做查三角函数表、乘法、开方及反三角函数等复杂运算。J.Walther在1971年用它研究了一种能计算出多种超越函数的统一算法引入参数m将CORDIC实现的三种迭代模式:三角运算、双曲运算和线性运算统一于同一个表达式下。形成目前所用的CORDIC算法的最基本的数学基础。该算法的基本思想是通过一系列固定的、与运算基数相关的角度不断偏摆以逼近所需的旋转角度。可用下列等式进行描述。
提出,从而得到
这里取,所有迭代的角度的这里,这里矩阵中的所以矩阵就变为:
上式中的。随着迭代次数的增加,改式就会收敛为一个常数:
k作为一个常数增益,可以暂不考虑,这时上式就会变为:
如果用Z来表示相位累加的部分和,则
若想使Z旋转到0,则Sn的符号由Zn,来确定,如下:
旋转后的最终结果为:
对于一组特殊的初始值:
得到的结果为:
将这种工作模式称为旋转工作模式通过旋转模式就可以求出一个角度的sin和cos值。
迭代结构
简单地将CORDIC算法的公式复制到硬件描述上,就可以实现迭代的CORDIC算法,其结构如下图所示。
流水线结构
流水线结构虽然比迭代结构占用的资源多,但是它大大的提高了数据的吞吐率。流水线结构是将迭代结构展开,因此n个处理单元中的每个都可以同时并行处理一个相同的迭代运算。其结构如下图所示。
例:用Verilog HDL设计基于7级流水结构求正余弦的CORDIC算法在CORDIC算法中有一个初始的X、Y值。输入变量Z是角度变量,首先将X、Y输入到固定移位次数的移位寄存器进行移位,然后将结果输入到加/减法器,并且根据角度累加器的输出结果来确定加减法器的加减操作,这样就完成了一次迭代,将此次迭代运算的结果作为输入传送到下一级的迭代运算,将迭代运算依次进行下去,当达到所需要的迭代次数(本例为7次) 的时候将结果输出,此时就是想要的结果所以整个CORDIC处理器就是一个内部互联的加/减法器阵列。
module sincos(clk,rst_n,ena,phase_in,sin_out,cos_out,eps);
parameter DATA_WIDTH=8;
parameter PIPELINE=8;
input clk;
inpu trst_n;
input ena;
input [DATA _WIDTH-1:0] phase_in;
output [DATA_WIDTH-1:0] sin_out;
output [DATA_WIDTH-1:0] cos_out;
output [DATA_WIDTH-1:0] eps;
reg [DATA_WIDTH-1:0] sin_out;
reg [DATA_WIDTH-1:0] cos_out;
reg [DATA_WIDTH-1:0] eps;
reg[DATA_WIDTH-1:0] phase_in_reg;
reg [DATA_WIDTH-1:0] x0,y0,z0;
wire [DATA_WIDTH-1:0] x1,y1,z1;
wire [DATA_WIDTH-1:0] x2,y2,z2;
wire [DATA_WIDTH-1:0] x3,y3,z3;
wire [DATA_WIDTH-1:0] x4,y4,z4;
wire [DATA_WIDTH-1:0] x5,y5,z5;
wire [DATA_WIDTH-1:0] x6,y6,z6;
wire [[DATA_WIDTH-1:0] x7,y7,z7;
reg [1:0] quadrant[PIPELINE:0];
integer i;
always@(posedge clk or negedge rst n)
begin
if(!rst_n)
phase_in_reg<=8b0000_0000;
else
if(ena)
begin
case(phase_in[7:6])
2b00:phase_in_reg<=phase_in;
2b01:phase_in_reg<=phase_in-8'h40;
2b10:phase in reg<=phase_in-8'h80;
2b11:phase_in_reg<=phase_in-8hc0;
endcase
end
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
x0<=8b00000000;
y0<=8b00000000;
z0<=8b00000000;
end
else
if(ena)
begin
x0<=8'h4D;
y0<=8'h00;
z0<=phase_in_reg;
end
end
lteration #(8,0,8'h20)u1(.clk(clk),.rst_n(rst_n),.ena(ena),.x0(x0),.y0(y0),.z0(z0),.x1(x1),.y1(y1),.z1(z1));
lteration #(8,1,8'h12)u2(.clk(clk),.rst_n(rst_n),.ena(ena),.x0(x1),.y0(y1),.z0(z1),.x1(x2),.y1(y2),.z1(z2));
lteration #(8,2,8'h09)u3(.clk(clk),.rst_n(rst_n),.ena(ena),.x0(x2),.y0(y2),.z0(z2),.x1(x3),.y1(y3),.z1(z3));
lteration #(8,3,8'h04)u4(.clk(clk),.rst_n(rst_n),.ena(ena),.x0(x3),.y0(y3),.z0(z3),.x1(x4),.y1(y4),.z1(z4));
lteration #(8,4,8'h02)u5(.clk(clk),.rst_n(rst_n),.ena(ena),.x0(x4),.y0(y4),.z0(z4),.x1(x5),.y1(y5),.z1(z5));
Iteration #(8,5,8'h01)u6(.clk(clk),.rst_n(rst_n),.ena(ena),.x0(x5),.y0(y5),.z0(z5),.x1(x6),.y1(y6),.z1(z6));
Iteration #(8,6,8'h00)u7(.clk(clk),.rst_n(rst_n),.ena(ena),.x0(x6),.y0(y6),.z0(z6),.x1(x7),.y1(y7),.z1(z7));
always@(posedge clk or negedge rst _n)
begin
if(!rst_n)
for(i=0;i<=PIPELINE;i=i+1)
quadrant[i]<=2'b00;
else
if(ena)
begin
for(i=0;i<=PIPELINE;i=i+1)
quadrant[i+1]<=quadrant[i];
quadrant[0]<=phasein[7:6];
end
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
sin_out<=8'b00000000;
cos_out<=8'b00000000;
eps<=8'b00000000;
end
else
if(ena)
case(quadrant[7])
2'b00:
begin
sin_out<=y6;
cos_out<=x6;
eps<=z6;
end
2b01:
begin
sin_out<=x6;
cos_out<=~(y6)+1'b1;
eps<=z6;
end
2b10:
begin
sin_out<=~(y6)+1b1;
cos_out<=~(x6)+1b1;
eps<=z6
end
2'b11:
begin
sin_out<=~(x6)+1'b1;
cos_out<=y6;
eps<=z6;
end
endcase
end
endmodule
//迭代模块:
module lteration(clk,rst_n,ena,x0,y0,z0,x1,y1,z1):
parameter DATA_WIDTH=8;
parameter shift=0;
parameter constant=8"h20;
input clk,rst_n,ena;
input [DATA_WIDTH-1:0] x0,y0,z0;
output[DATA_WIDTH-1:0] x1,y1,z1;
reg [DATA_WIDTH-1:0] x1,y1,z1;
always@(posedge ck or negedge rst_n)
begin
if(!rst_n)
begin
x1<=8'b00000000;
y1<=8'b00000000
z1<=8'b00000000
end
else
if(ena)
if(z0[7]==1'b0)
begin
x1<=x0-{{shift{y0[DATA_WIDTH-1]}},y0[DATA_WIDTH-1:shift]};
y1<=y0+{{shift{x0[DATA_WIDTH-1]},x0[DATA_WIDTH-1:shift]};
z1<=z0-constant;
end
else
begin
x1<=x0+{{shift{y0[DATA_WIDTH-1]}},y0[DATA_WIDTH-1:shift]};
y1<=y0-{{shift{x0[DATA_WIDTH-1]},x0[DATA_WIDTH-1:shift]};
z1<=z0+constant;
end
end
endmodule
module sincos tb;
reg clk,rst_n,ena;
reg [7:0] phase_in;
wire [7:0] sin_out,cos_out,eps;
sincos U1(.clk(clk),.rst_n(rst_n),.ena(ena),.phase_in(phase_in),
.sin_out(sin_out),.cos_out(cos_out),.eps(eps));
initial
begin
clk=0;rst_n=0;ena=1;
phasei_n=8b00000000;
#3 rst_n=1;
end
always #5 clk=~clk;
always #10
phase_in=phase_in+1;
endmodule
3.总线控制器设计
3.1UART接口控制器
串口也称作UART(Universal Asynchronous Receiver/Transmitters),在实际应用中,通常只用TXD和RXD两个脚,而其它的管脚都不使用,UART接口时序如图所示:
一个简单的UART结构图如下图所示:
发送模块:发送模块的功能是将数据以串行的形式发送出去,并且将每一组的串行数据加上开始位和停止位。当byte_ready信号有效时数据被载入移位寄存器并添加开始位(低电平)和停止位(高电平)。当byte_ready信号无效时移位寄存器开始移位操作将数据以串行的形式发送出去。
module UART_transmitter(clk,reset,byte_ready,data,TXD);
inputclk,reset;
input byte_ready;
input[7:0] data;
output TXD;
reg [9:0] shift_reg;
assign TXD=shift_reg[0];
always@(posedge clk or negedge reset)
begin
if(!reset)
shift reg<=10'b1111111111;
else
if(byte_ready)
shift reg<=(1'b1,data,1'b0);
else
shift reg<=(1'b1,shift reg[9:1]);
endmodule
接收模块:接收模块的功能是接收发送模块输出的串行数据,并以并行的方式将数据送入存储器。当接收模块检测到开始位(低电平) 时开始接收数据,并且输入串行数据存入移位寄存器,当接收完成时将数据并行输出。
module UART_receiver(clk,reset,RXD,data_out);
parameter idle=2'b00;
parameter receiving=2'b01;
inputclk,reset;
input RXD;
output [7:0] data out;
reg shift;
reg inc_count;
reg [7:0] data_out;
reg[7:0] shift_reg;
reg(3:0] count;
reg[2:0] state,next state;
always@(state or RXD or count)
begin
shift=0;
inc_count=0;
next_state=state,
case(state)
idle:
if(!RXD)
next state=receiving;
receiving:
begin
if(count==8)
begin
data_out=shift_reg;
next_state=idle;
count=0;
inc_count=0;
end
else
begin
inc_count = 1;
shift=1;
end
end
default:next_state<=idle;
endcase
end
always@(posedge clk or negedge reset)
begin
if(!reset)
begin
data_out<=8'b0;
count<=0;
state<=idle;
end
else
begin
state<=next_state;
if(shift)
shift_reg<={shift_reg[6:0],RXD):
if(inc_count)
count<=count+1;
end
end
endmodule
module UART_tb;
reg clk,reset;
reg [7:0] data;
reg byte_ready;
wire [7:0] data_out;
wire serial_data
initial
begin
clk=0;
reset=0;
byte_ready=0;
data=8'b10101010;
#40 byte_ready=1;
#50 reset=1;
#170 byte_ready=0:
end
always #80 clk=~clk
UART transmitterU1(.clk(clk),.reset(reset),.byte ready(byte_ready),
.data(data),.TXD(serial data));UART receiverU2(.clk(clk),
.reset(reset),.RXD(serial data),.data out(data out));
endmodule
3.2SPI接口控制器
串行外设接口(Serial PeripheralInterface SPI) 是一种同步串行外设接口,能够实现在微控制器之间或微控制器与各种外设之间以串行方式进行通信数据交换。SPI可以共享,便于组成带多个SPI接口器件的系统,且传送速率高,可编程,连接线少,具有良好的扩展性,是一种优秀的同步时序电路SPI总线通常有4条线:串行时钟线 (SCLK)、主机输入/从机输出数据线MISO)、主机输出/从机输入数据线(MOSI)。低电平有效从机选择线 (SS N)。SPI系统可分为主机设备和从机设备两大类,主机提供SPI时钟信号和片选信号,从机是接收SPI信号的任何集成电路。当SPI工作时,在移位寄存器中的数据逐位从输出引脚(MOSI)输出,同时从输入引脚(MISO)逐位接收数据。发送和接收数据操作都受控于SPI主设备时钟信号(SCLK),从而保证了同步。因此只能有一个主设备,但可以有多个从设备,可以通过片选信号 (SSN)可同时选中一个或多个从设备。
其典型结构如下图:
SPI总线典型时序图如下图:
例:采用Verilog设计一个简化的SPI接收机,用来完成8bits数据的传输SPI接收机框图如图所示:
文章来源:https://www.toymoban.com/news/detail-503562.html
module SPI(sdout,MISO,sclk,srst,sen,ss_n);
output [7:0] sdout;
output ss_n;
input MISO,sclk,srst,sen;
reg [2:0] counter;
reg [7:0] shift regist;
reg ss_n;
always @(posedge sclk)
if (!srst)
counter<=3'b000;
else if(sen)
if (counter==3'b111)
begin
counter<=3'b000:
ss_n<=1'b1;
end
else
begin
counter<=counter+1;
ss_n<=1'b0;
end
else
counter<=counter;
always@(posedge sclk)
if(sen)
shift_regist<={shift_regist[6:0],MISO};
else
shift_regist<=shift_regist;
assign sdout=ss_n?shift_regist:8'b00000000;
endmodule
代码中,sclk为接口时钟。srst为清零信号,低电平有效。sen为接口使能信号,高电平有效。ss_n为片选信号,选择从设备,高电平有效当电路上电时,首先将清零信号置为有效,初始化电路。当sen使能信号有效后,开始传输数据,由于传输数据为8bits,因此sen使能信号应至少保持8个时钟周期。当8bits数据全部输入后,片选信号ss n有效,选择从设备,将数据整体输出。片选信号ss n由3bits计数器产生,当计数器计数到111状态时,ss_n=1,其它状态下ss_n=0文章来源地址https://www.toymoban.com/news/detail-503562.html
module SPI_tb;
reg MISO,sclk,sen,srst;
wire [7:0] sdout;
wire ss_n;
SPI U1(.sdout(sdout),.MISO(MISO),.sclk(sclk),.srst(srst),.sen(sen),ss_n(ss_n));
initial
begin
MISO=0;sclk=0;srst=0:sen=0;
#10 srst=1;
#10 sen=1;
#80 sen=0;
#10 sen=1;
#80 sen=0;
end
initial
begin
#30 MISO=1;
#10 MISO=0;
#10 MISO=1;
#10 MISO=0:
#10 MISO=1;
#10 MISO=0;
#10 MISO=1;
#20 MISO=1;
#10 MISO=0;
#10 MISO=1;
#10 MISO=0;
#10 MISO=1;
#10 MISO=0;
#10 MISO=1;
#10 MISO=0;
end
always #5 sclk<=~sclk;
endmodule
来源:蔡觉平老师的Verilog课程
到了这里,关于Verilog学习笔记(5):Verilog高级程序设计的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!