近跟着老师学习Verilog,做了中科大的练习题,将答案记录一下
Q53 查找表
题目描述
在本题中,请设计一个 8x1 存储器的电路,其中写入内存是通过移位来完成的,而读取是“随机访问”,就像在典型的 RAM 中一样,以三个输入(A、B、C)作为地址,实现对存储器单元的随机访问。
首先,使用 8 个 D 型触发器实现一个 8 位移位寄存器。将触发器输出标记为 Q[0]…Q[7]。移位寄存器输入应被调用 S,它将输入 Q[0](高位通过移位操作输入)。通过使能信号控制是否移动。电路的行为应如下:当 ABC 为 000 时,Z=Q[0],ABC 为 001 时,Z=Q[1],等等。您的电路应仅包含 8 位移位寄存器和多路复用器(该电路称为 3 输入查找表(LUT))。
输入格式
1bit信号S、使能信号以及多路选择器控制信号ABC
输出格式
在使能信号为true的情况下,每个时钟上升沿移位寄存器进行移位操作,其中Q[0]的值从S处获得。根据ABC选择信号的值,输出信号Z从Q[0]...Q[7]中取值。
代码
module top_module(
input clk,
input enable,
input S,
input A, B, C,
output reg Z
);
// Write your code here
reg [7:0] q;
always@(posedge clk) begin
if (enable) begin
q <= {q[6:0], S}; //S在D0触发器?实现左移功能
end
end
always@(*) begin
Z = q[{A,B,C}]; //代替case等同q[0]-q[7]
end
endmodule
Q54 ROM
题目描述
在数字电路中,不可避免的需要对数据进行存储和访问,虽然寄存器能够实现数据的存储功能,但使用寄存器进行大量数据的存储并不现实,这是因为:第一,寄存器实现成本过高;第二,寄存器结构复杂,难以提高集成度。因此,一般使用存储器进行大段数据的存储,常见的存储器有ROM(只读存储器)、RAM(随机访问存储器)和FIFO(先进先出队列)。ROM顾名思义,该存储器里面的数据是预选设置好的,在使用时只能读取,不能写入,在使用FPGA作为硬件平台时,一般是通过EDA工具中的IP生成工具来生成的。此外,我们也可以通过使用Verilog定义一个数组来实现,并由EDA工具自动例化成相应的IP核,这样做对于开发者来说简单了很多,但性能多少会受些影响。
对于存储器,其内部数据的初始化有两种方式:(1)在Verilog代码中指定初始内容(2)在外部文件中指定初始内容。在实际中,一般采用第二种方式。
对于存储器来说,有两个非常重要的参数:地址位宽和数据位宽,地址位宽与存储器的存储深度(即存储单元数量)相关,如地址宽度为N,则存储器深度为2N,数据位宽则表示每个存储单元所包含的bit数。
下例是一个4*8bit的ROM实例,该ROM通过在Verilog代码中指定内容进行初始化
module rom_4x8bit(
input [1:0] addr
output [7:0] q);
reg [7:0] mem [3:0];
initial
begin
mem[0] = 8’h00;
mem[1] = 8’h00;
mem[2] = 8’h00;
mem[3] = 8’h00;
end
assign q = mem[addr];
endmodule
试根据上述示例,设计一个8*4bit的ROM,并对其进行初始化,使其初始化数据为“0,1,2,3,…”
输入格式
3'b000
输出格式
4'd0
代码
module top_module(
input [2:0] addr,
output [3:0] q
);
reg [3:0]mem[7:0];
initial begin
mem[0] = 4'd0;
mem[1] = 4'd1;
mem[2] = 4'd2;
mem[3] = 4'd3;
mem[4] = 4'd4;
mem[5] = 4'd5;
mem[6] = 4'd6;
mem[7] = 4'd7;
end
assign q = mem[addr];
endmodule
Q55 RAM
题目描述
RAM全称为随机访问存储器,可以对其内部的单元进行读写访问,根据读写端口数量和类型,可以分为单端口(读写端口共用地址信号)、简单双端口(读写端口独立,可同时进行读写操作)、全双端口(有两套读写端口)等不同类型。
下图是三种不同RAM的接口信号对比,图中列出的都是基本接口信号,根据具体要求,还可以增加复位、读使能、字节使能等端口信号。
下例是一个4*8bit的单端口RAM的实例,采用外部文件进行初始化
module ram_one_port(
input clk,
input [1:0] addr,
input wr_en,
input [7:0] wr_data,
output [7:0] rd_data);
reg [7:0] mem[3:0];
initial
begin
$readmemh("memfile.dat",mem);
end
assign rd_data = mem[addr];
always@(posedge clk)
begin
if(wr_en)
mem[addr] <= wr_data;
end
endmodule
上例中使用系统函数$readmemh,该函数可以将文件中的数据初始化到存储器数组中,memfile.dat为RAM的初始化文件,内容如下(16进制格式,可以使用文本工具编辑):
00
01
02
03
试根据上述示例,设计一个8*16bit的简单双端口RAM,并使用memfile.dat文件对其初始化,在本例中,该文件已在后台提供,用户可直接使用。
代码模板:
module ram_one_port(
input clk,
input wr_en,
input [2:0] wr_addr,
input [15:0] wr_data,
input [2:0] rd_addr,
output [15:0] rd_data);
endmodule
输入格式
时钟信号clk 使能信号wr_en 3位宽写地址wr_addr 8位宽写数据wr_data 3位宽读地址rd_addr
输出格式
8位宽读数据rd_data
示例波形
代码
module ram_one_port(
input clk,
input wr_en,
input [2:0] wr_addr,
input [15:0] wr_data,
input [2:0] rd_addr,
output [15:0] rd_data);
reg [15:0] mem[7:0];
initial
begin
$readmemh("memfile.dat",mem);
end
assign rd_data = mem[rd_addr];
always@(posedge clk)
begin
if(wr_en)
mem[wr_addr] <= wr_data;
end
endmodule
Q56 有限状态机
题目描述
下图是摩尔型有限状态机的结构图,我们可以发现其包含三个部分,第一部分为纯组合逻辑,通过现态和输入信号生成次态信号,第二部分为时序逻辑,该时序逻辑非常简单,只包含一个带有复位功能的寄存器单元,复位时现态信号变为初始值,否则在每个时钟的上升沿将次态信号赋值给现态信号。第三部分为组合逻辑,该部分通过现态信号生成各输出信号。
对于上述电路,其Verilog代码实现为:
module traffic_ctrl(
input clk,
input rst,
input timer_pulse,
output green_light
);
parameter C_PASS = 2'b00;
parameter C_TRANS = 2'b01;
parameter C_STOP = 2'b10;
reg [1:0] curr_state;
reg [1:0] next_state;
//有限状态机第一部分
always@(*)
begin
if(timer_pulse)
begin
case(curr_state)
C_PASS: next_state = C_TRANS;
C_TRANS:next_state = C_STOP;
C_STOP: next_state = C_PASS;
default:next_state = C_PASS;
endcase
end
else
next_state = curr_state;
end
//有限状态机第二部分
always@(posedge clk or posedge rst)
begin
if(rst)
curr_state <= C_PASS;
else
curr_state <= next_state;
end
//有限状态机第三部分,各输出信号的赋值都应放在此部分
assign green_light = (curr_state==C_PASS)? 1'b1 : 1'b0;
//...
endmodule
试用Verilog代码实现下图中的摩尔型状态机,有两个状态,一个输入(in),一个输出(out)。实现这个状态机。请注意,电路采用异步复位,复位状态是B。
输入格式
clk areset: Asynchronous reset to state B 1bit in
输出格式
1bit out
代码
module top_module(
input clk,
input areset, // Asynchronous reset to state B
input in,
output out);//
parameter A=0, B=1;
reg state, next_state;
always @(*) begin //有限状态机第一段
// State transition logic
if(~in)
begin
case(state)
A: next_state = B;
B:next_state = A;
default:next_state = B;
endcase
end
else
next_state = state;
end
always @(posedge clk, posedge areset) begin //有限状态机第二段
// State flip-flops with asynchronous reset
if(areset)
state <= B;
else
state <= next_state;
end
//有限状态机第三段,信号输出逻辑
assign out = (state == B);
endmodule
Q57 读代码找错误
题目描述
如果设计的Verilog模块不能正常工作,说明存在语法或者功能上的错误,这时需要对错误进行定位,对于语法错误,可通过EDA工具中的语法检查功能进行定位,对于已经掌握了Verilog语法的用户来说,很容易便能解决。对于电路功能上的错误,可以通过以下两种途径定位:阅读代码、电路仿真。
以下是一个8bit位宽的二选一选择器的Verilog代码,但存在错误,请仔细阅读代码,修改代码使其能正常工作。
输入格式
对下面的代码进行改错即可
输出格式
对下面的代码进行改错即可 注:有两行代码有问题 以及,sel取何值时选择哪一个,代码里已经有暗示。
代码
module top_module (
input sel,
input [7:0] a,
input [7:0] b,
output [7:0]out );//这就是错误2,差点儿忘了这个也需要检查
assign out = ({8{~sel}} & a) | ({8{sel}} & b);//操作位数需要保持一致
endmodule
Q58 编写仿真文件
题目描述
在Verilog设计中,仿真是非常重要的一环,绝大部分的电路功能错误都可以通过仿真进行定位并解决。
仿真的过程为:用户使用Verilog编写一个没有任何输入信号的测试模块(一般命名为tb.v,即testbench的缩写),在该模块中调用被仿真的模块(及我们使用Verilog编写的功能模块),并使用initial等关键字构造被测模块的输入信号,通过专门的仿真工具进行仿真,并查看各个信号的波形变化
以四选一选择器的仿真为例,被测模块和测试文件的Verilog代码如下所示:
仿真结果如下图所示:
通过观察波形我们可以发现,该电路的仿真波形符合四选一选择器的行为特性,Verilog代码设计正确。
在initial进程块内,语句都是顺序执行的,通过延时符号“#”实现时序控制。例如:
`timescale 1ns/1ps
module tb();
reg a;
initial begin
a = 1’b0;
#10 a = 1’b1;
#20 a = 1’b0;
#20 $finish;
end
对应的波形为:
其中`timescale 1ns/1ps表示时间精度,分辨率为1ps,时间单位为1ns,#10表示延时10个时间单位(即10ns),$finish为仿真专用的系统函数,表示仿真结束
试编写测试文件,实现如下波形:
输入格式
无
输出格式
无
代码
module tb();
reg a,b;
initial begin
a = 1'b1;
b = 1'b0;
#10 b = 1'b1;
#10 a = 1'b0;
#10 b = 1'b0;
#10 a = 1'b1;
#10 $finish;
end
endmodule
Q59 组合逻辑模块仿真
题目描述
以下是给定模块mymodule的Verilog代码:
module mymodule(
input a,b,
output q);
assign q = a & b;
endmodule
试编写仿真文件,使其符合如下示例波形(板载时钟为100MHz,虚线分隔时间每格为10ns)
输入格式
无
输出格式
无
代码 提示:应该是这题的上传者在他的root/testbench/tb/写死了10ns
module tb();
reg a,b;
wire q;
//对ab信号进行初始化
initial begin
a=0;
b=0;
#3 b=1;
#2 b=0; a=1;
#2 b=1;
#2 b=0; a=0;
#2 b=1;
#2 b=0; a=1;
#2 b=1;
#2 b=0; a=0;
#2 $finish;
end
//例化mymodule模块
mymodule test(
.a(a),
.b(b),
.q(q)
);
endmodule
module mymodule(
input a,b,
output q
);
assign q = a & b;
endmodule
Q60 生成时钟信号
题目描述
给定被测试模块dut,其verilog代码如下:
module dut(input clk, output reg [2:0]out);
//测试模块
always @(posedge clk)
out <= out + 1'b1;
endmodule
请编写仿真文件,对该模块进行仿真(dut模块可直接调用,不需要用户编写),clk信号应符合以下波形:
Hint:
应注意clk的周期长度(如上图)
输入格式
无
输出格式
无
代码
module tb();
wire [2:0]out;//必要输出信号
//信号定义
reg clk;
//信号生成
initial clk = 0;
always #5 clk = ~clk;
//模块例化
dut dut_inst(
.clk(clk),
.out(out)
);
endmodule
module dut(input clk, output reg [2:0]out);
//测试模块
always @(posedge clk)
out <= out + 1'b1;
endmodule
Q61 单端口 RAM 仿真
题目描述
以下是一个单端口RAM的verilog描述:
module ram_one_port(
input clk,
input [1:0] addr,
input wr_en,
input [7:0] wr_data,
output [7:0] rd_data);
reg [7:0] mem[3:0];
initial
begin
$readmemh("memfile.dat",mem);
end
assign rd_data = mem[addr];
always@(posedge clk)
begin
if(wr_en)
mem[addr] <= wr_data;
end
endmodule
如对该模块进行仿真,需要在tb文件中生成clk、addr、wr_en、wr_data等RAM所需的输入信号(在tb中都需定义为reg类型),并查看rd_data端口的输出数据。
clk信号可以使用以下语句生成:
initial begin
clk = 0;
forever #5 clk = ~clk; //生成周期为10的一个时钟信号,forever为verilog的关键字
end
addr信号应该在时钟的上升沿变化,我们可以借助前面生成的clk信号来生成addr,如:
initial begin
addr = 2'b0;
repeat(4) begin //repeat为verilog关键字,表示重复操作
@(posedge clk); //等待clk信号的上升沿到来
#1 addr = addr + 1; //clk上升沿1个时间单位后,addr加一
end
end
对于wr_en信号,我们可以使其持续4个周期的高电平,如下所示:
initial begin
wr_en = 0;
#501; //延时一段时间,
@(posedge clk);
#1 wr_en = 1;
@(posedge clk);
@(posedge clk);
@(posedge clk);
@(posedge clk); //等待4个clk上升沿
#1 wr_en = 0;
end
此外,我们还可以使用系统函数生成随机数,来作为测试数据,以wr_data为例:
initial begin
wr_data = 8'h0;
repeat(4) begin
wait(wr_en==1'b1);
#1 wr_data = $random%256;
@(posedge clk);
end
end
请根据上述提示,按照以下波形的要求,编写单端口ram的仿真文件:
注意:由于模块内包含随机数的特殊性,OJ系统不会自动判断本题对错,只会根据提交代码生成仿真波形,具体检查方式待定。示例波形为运行一次参考代码所得结果,仅供参考。
输入格式
无输入,所有所需信号已在模块内预定义
输出格式
无输出,评测系统会抓取模块内 clk, addr, wr_en, wr_data, rd_data 显示。
代码
module ram_one_port(
input clk,
input [1:0] addr,
input wr_en,
input [7:0] wr_data,
output [7:0] rd_data
);
reg [7:0] mem[3:0];
initial
begin
mem[0] = 8'b0;
mem[1] = 8'b0;
mem[2] = 8'b0;
mem[3] = 8'b0;
end
assign rd_data = mem[addr];
always@(posedge clk)
begin
if(wr_en)
mem[addr] <= wr_data;
end
endmodule
module tb(
);
//信号定义
reg clk,wr_en;
reg [1:0] addr;
reg [7:0] wr_data;
wire [7:0] rd_data;
//信号生成
initial begin
clk = 0;
forever #5 clk = ~clk; //生成周期为10的一个时钟信号,forever为verilog的关键字
end
initial begin
addr = 2'b0;
repeat(4) begin //repeat为verilog关键字,表示重复操作
@(posedge clk); //等待clk信号的上升沿到来
#1 addr = addr + 1; //clk上升沿1个时间单位后,addr加一
end
end
initial begin
wr_en = 0;
#501; //延时一段时间,
@(posedge clk);
#1 wr_en = 1;
@(posedge clk);
@(posedge clk);
@(posedge clk);
@(posedge clk); //等待4个clk上升沿
#1 wr_en = 0;
end
initial begin
wr_data = 8'h0;
repeat(4) begin
wait(wr_en==1'b1);
#1 wr_data = $random%256;
@(posedge clk);
end
end
//例化被测模块
ram_one_port ram_one_port(
.clk(clk),
.addr(addr),
.wr_en(wr_en),
.wr_data(wr_data),
.rd_data(rd_data)
);
endmodule