-
名词解释(10分)
- VCD:波形记录数据库、
- RTL:寄存器传输级
- SDF:标准延时格式
- Strong:Verilog默认的信号强度
- 设计规则约束
-
根据给定逻辑画电路图(8分)
- 具体是高性能编码风格那一节的if分支���句的电路图.程序应该是下面这个样子
module mod( input wire a,b,c, input wire s1,s2, output o); always @(*) begin if(s1) o = a; else if(s2) o = b; else o = c; end endmodule
-
根据电路图写逻辑和时序specify,具体电路时一位的加法器(12分)
- 题目给了每个输入到输出的延时,写specify延时语句
-
Wire Load Model计算电阻电容(8分)
- 很容易忽视!即下图的内容类似,给定fanout,求解电阻R和电容C
-
FIFO实现,基于一个给定的memory(20分)
- 作业题,考试要写的时16*4的FIFO,需要对给的32*8memory参数重载
-
检测序列111(20分)
- 状态机,课上练习那个,有一点稍微改动
-
cmp判断大小器件实现,先写1位,再用generate写8位cmp(10分)
- 具体就是比较A和B两个数大小,有三个端口G,E,L,分别表示A大于B,A等于B,A小于B,一位的模块需要输入一位Ai,Bi,和上一位传来的Gi,Ei,Li,输出这一位计算的Go,Eo,Lo。大概逻辑是:
Go = Gi || (Ei & (Ai > Bi)); Eo = Ei & (A == B); Lo = Li || (Ei & (Ai < Bi));
- 其次根据一位的写8为的CMP来判断八位A和B大小
-
给电路图和d触发器代码,求输入输出延时和时钟最高频率(12分)
- 很容易忽视!大概逻辑综合那一张,下图这块内容
总结:回归上课ppt很重要,时序考的很多,19年卷子仅是参考,光靠老师给的参考卷子肯定不行,不然就寄了,还是要看课本内容!
一些测试与练习的代码实现
根据图中电路,完成Verilog实现。
D 型主从触发器电路如下图所示,请用 verilog 语言对其进行门级描述。
-
门级描述:
-
确定输入输出端口及类型
-
确定中间变量及其类型
-
确定门的类型和门的实例名称
-
注意门的输出写在最前面的端口,输入在输出后
-
细心
-
module dff(
input wire clear,
clock,
data,
output wire q,
qb
); // 注意分号不要丢
wire ndata, nclock;
wire a, b, c, d, e, f;
not iv1(ndata, data),
iv2(nclock, clock);
nand nd1(a, clear, clock, data),
nd2(b, ndata, clock),
nd3(c, a, d),
nd4(d, c, clear, b),
nd5(e, c, nclock),
nd6(f, nclock, d),
nd7(q, e, qb),
nd8(qb, clear, f, q);
endmodule
一个 32×8 的异步 SRAM 的外部端口如图所示,其读写信号时序图如图(时间 单位 ns)所示。
- 试建立此 SRAM 的 Verilog 行为模型。
- 给出此 SRAM 模型的测试程序,要求测试程序使用 task:
- 向地址 12 写入 8’H55,地址 13 写 8’HAA。
- 再读取地址 12 和 13 的数据。
- 根据题目确定存在的状态有多少种,根据可能的种类来判断需要一个多少位的参数变量来存储状态(parameter),例如:6中状态需要3位来存储。其中,参数一般用从0开始的二进制数递增方式表示。
- 定义一个寄存器类型(reg)的状态变量来表示当前所处的状态(state),其位数和状态参数一致。
- 确定时钟与���位信号同步与异步的选择,并且复位信号有效时需要将状态机置于初始状态,一般是(state0 = 0)的状态。
- 在复位信号无效的情况下,需要根据所有状态作为case语句的各个分支,并且在各个分支下在根据不同的动作产生二级分支,一般动作(action)都是由输入的数据信号所决定,一位的输入一般只有两种动作,只需要if……else……语句写即可,多于一位的输入信号会产生很多种动作导致状态的变化,一般用case语句来产生二级分支更好,并且在每个动作下除了状态的变化,还有一些参数需要变动的也在这些分支下取撰写。注意每个动作最后需要加上default选项:用于判断x,z等情况。
- 最后,输出一般设置成wire类型,在过程快外用assign语句来赋值,赋值的右式一般是state所处的状态,或者一些中间变量的组合。
- 注:还有将数据和控制分离的写法,可以将state分支判断放在另一个always(*)里面单独判断,这种针对一位以上的输入,两位的状态机参考:作业3。
例如:练习4的状态机题解思路如下
`timescale 1ns/1ns
module Detect111 (
input wire X,
clk,
rst,
output wire out
);
// 定义一个寄存器类型(reg)的状态变量来表示当前所处的状态(state),其位数和状态参数一致,4种状态两位存储即可
reg [1 : 0] state;
// 根据题目确定存在的状态有多少种,根据可能的种类来判断需要一个多少位的参数变量来存储状态(parameter)
parameter IDLE = 2'b00;
parameter State1 = 2'b01;
parameter State2 = 2'b10;
parameter State3 = 2'b11;
// 确定时钟与复位信号同步与异步的选择,这里选择异步方式,注意rst高电平有效,需要posedge rst
always @(posedge clk, posedge rst) begin
// 复位信号有效时需要将状态机置于初始状态
if(rst)
state <= 0;
else
// 在复位信号无效的情况下,需要根据所有状态作为case语句的各个分支,这里即四个状态的IDLE,State1-3
case (state)
IDLE:
// 各个分支下在根据不同的动作产生二级分支,一般动作(action)都是由输入的数据信号所决定,一位的输入一般只有两种动作,只需要if……else……语句写即可
if(X)
state = State1;
else // 包括X = 0,x,z
state = IDLE;
State1:
if(X)
state = State2;
else
state = IDLE;
State2:
if(X)
state = State3;
else
state = IDLE;
State3:
state = State3;
endcase
end
// 输出一般设置成wire类型,在过程快外用assign语句来赋值,赋值的右式一般是state所处的状态,或者一些中间变量的组合
assign out = (state == State3);
endmodule
- 根据电路图判断输入输出端口,一般的D触发器(DFF,D flip-flop)的输出作为输出端口,如练习1,但是如果输出还需要做逻辑操作,那么一般最后会用一些组合后的变量作为最终的输出,那么D触发器的输出Q需要用中间变量存储,且是Reg类型。
- 输入端口一般包括时钟和复位信号,可采用同步或异步的方式设置复位信号,如果有其他输入信号一并加上,输入信号都需要是net类型(如wire),输出信号即D触发器Q端口或者Q端口继续组合产生的信号,输出信号一般设置为wire类型,也可以是reg类型,取决于最终再过程块中给输出赋值,还是assign给输出赋值。中间需要定义的变量一般只有D触发器的Q端口,如果Q端口是输出端口则不需要再单独定义,且中间变量都为reg类型。
- 复位信号有效时,一般需要将定义的中间变量置零,复位信号无效时,电路正常工作,这时候需要在过程块中写出所有Q端口的组合逻辑,即每个Q端口的输入是什么。
- 最后输出端口采用assign语句赋值(如果输出设置了wire类型),如果设置的reg类型,那么最终的输出也需要在过程块中赋值,且两种方式等式右边是中间变量Q端口的组合。
例如:练习2的题解
`timescale 1ns/1ns
module Counter (
input wire clk,
rst,
output wire[2 : 0] Y
);
// 中间需要定义的变量一般只有D触发器的Q端口,且中间变量都为reg类型
reg Q1,
Q0;
// 可采用同步或异步的方式设置复位信号,这里采用异步方式
always @(posedge clk, posedge rst) begin
// 位信号有效时,一般需要将定义的中间变量置零
if(rst) begin
Q0 <= 0;
Q1 <= 0;
end
// 复位信号无效时,需要在过程块中写出所有Q端口的组合逻辑,即每个Q端口(D触发器)的输入是什么
else begin
Q0 <= ~Q0;
Q1 <= Q0 ^ Q1;
end
end
// 输出端口采用assign语句赋值,等式右边是中间变量Q端口的组合
assign Y[0]= Q0 | Q1;
assign Y[1]= Q0;
assign Y[2]= Q1;
endmodule
不需要时钟信号的通用写法:
- 双向SRAM通用代码段:
assign data_o = rd_en? mem[addr] : 8'bz;
always @(posedge wr_en) begin
mem[addr] = data_in;
end
- 如果是FIFO,
addr
需要改为相应的指针:
assign data_o = rd_en? mem[tail] : 8'bz;
always @(posedge wr_en) begin
mem[head] = data_in;
end
- 双向驱动建模:
- 注意:inout类型端口只能只能声明wire类型,只能用assign赋值
- 确定信号基准,有时钟即以时钟为准 ,像RAM这类不需要时钟的存储器,则根据图像判断基准,例如,练习7中时间基准为addr的周期性赋值。
- 确定输入信号,之后输入信号可以在Testbench中控制它输入的时序;输出信号无法在Testbench中给予时序,需要在Design中给出延时。
- 最后根据图示在给定的延时下赋值或改变信号高低电平即可。
例如:练习7
`timescale 1ns/1ns
module SRAM_tb ();
reg read,
write;
reg [4 : 0] addr;
wire[7 : 0] data;
reg [7 : 0] data_buf;
SRAM u( .addr(addr),
.data(data),
.write(write),
.read(read));
assign data = write? data_buf : 8'bz;
task wr;
input [4 : 0] address;
input [7 : 0] dat;
begin
addr = address;
#1 data_buf = dat; // 写入阶段一个周期的时序控制,注意此时data_buf是充当输入信号,所以可以直接给予延时
#2 write = 1;
#4 write = 0;
#1 data_buf = 8'bz;
#1;
end
endtask
task rd;
input [4 : 0] address;
begin
addr = address; // 读出阶段,data_buf是充当输出信号,不能赋值,赋值只能在Design中实现,且延时基准一般为读入控制信号为基础的延时,这里为1s,需要在design中体现
#3 read = 1;
#4 read = 0;
#2;
end
endtask
initial begin
write = 0;
read = 0;
data_buf= 0;
addr = 0;
wr(5'h12, 8'h55);
wr(5'h13, 8'hAA);
rd(5'h12);
rd(5'h13);
#10 $finish;
end
endmodule
波形显示工具从数据库,如SHM数据库中读取数据。使用下面的系统任务可以对SHM数据库进行操作:
系统任务 | 描述 |
---|---|
$shm_open(“waves.shm”); | 打开一个仿真数据库。同时只能打开一个库写入。 |
$shm_probe(); | 选择信号,当它们的值变化时写入仿真库 |
$shm_close; $shm_save; | 关闭仿真库 将仿真数据库写到磁盘 |
initial
begin
$shm_open(“./data/lab.shm”);
$shm_probe( );
end
-
$shm_probe的语法:
$shm_probe(scope0, node0, scope1, node1, ...);
- 每个node都是基于前面scope的说明(层次化的)
- scope参数缺省值为当前范围(scope)。node参数缺省值为指定范围的所有输入、输出及输入输出。
在$shm_probe中使用scope/node对作为参数。参数可以使用缺省值或两个参数都设置。例如:
-
$shm_probe( ); 观测当前范围(scope)所有端口
-
$shm_probe(“A”); 观测当前范围所有节点
-
$shm_probe(alu, adder); 观测实例alu和adder的所有端口
-
$shm_probe(“S”, top.alu, “AC”); 观测:
(1): 当前范围及其以下所有端口,除库单元
(2):top.alu模块及其以下所有节点,包括库单元
Verilog提供一系列系统任务用于记录信号值变化保存到标准的VCD(Value Change Dump)格式数据库中。大多数波形显示工具支持VCD格式。
VCD数据库是仿真过程中数据信号变化的记录。它只记录用户指定的信号。
- 用户可以用$dump系统任务打开一个数据库,保存信号并控制信号的保存。除$dumpvars外,其它任务的作用都比较直观。 $dumpvars将在后面详细描述。
- 必须首先使用$dumpfile系统任务,并且在一次仿真中只能打开一个VCD数据库。
- 在仿真前(时间0前)必须先指定要观测的信号,这样才能看到信号完整的变化过程。
- 仿真时定期的将数据保存到磁盘是一个好的习惯,万一系统出现问题数据也不会全部丢失。
- VCD数据库不记录仿真结束时的数据。因此如果希望看到最后一次数据变化后的波形,必须在结束仿真前使用$dumpall。
要给$dumpvars提供层次(levels)及范围(scope)参数,例如
$dumpvars; // Dump所有层次的信号
$dumpvars (1, top); // Dump top模块中的所有信号
$dumpvars (2, top.u1); // Dump实例top. u1及其下一层的信号
$dumpvars (0, top.u2, top.u1.u13.q); // Dump top.u2及其以下所有信号,以及信号top.u1.u13.q。
$dumpvars (3, top.u2, top.u1); // Dump top.u1和top.u2及其下两层中的所有信号。
用下面的代码可以代替前面test fixture的$monitor命令:
initial
begin
$dumpfile (“verilog.dump”);
$dumpvars (0, testfixture);
end
. . .
integer MCD1; //注意integer需要在过程快外定义
MCD1 = $fopen("<name_of_file>");
$fdisplay( MCD1, P1, P2, .., Pn);
$fwrite( MCD1, P1, P2, .., Pn);
$fstrobe( MCD1, P1, P2, .., Pn);
$fmonitor( MCD1, P1, P2, .., Pn);
$fclose( MCD1); // 注意$flcose在仿真结束$finish上一行写最合适
. . .
-
$fopen打开一个文件并返回一个多通道描述符(MCD)。
-
MCD是与文件唯一对应的32位无符号整数。
-
如果文件不能打开并进行写操作,MCD等于0。
-
如果文件成功打开,MCD中的一位被置位。
-
-
以$f开始的显示系统任务将输出写入与MCD相对应的文件中。
-
$fopen打开参数中指定的文件并返回一个32位无符号 整数MCD,MCD是与文件一一对应的多通道描述符。如果文件不能打开并进行写操作,它返回0。
-
$fclose关闭MCD指定的通道。
-
输出信息到log文件和标准输出的四个格式化显示任务($display, $write, $monitor, $strobe)都有相对应的任务用于向指定文件输出。
-
这些对应的任务($fdisplay,$fwrite,$fmonitor,$fstrobe)的参数形式与对应的任务相同,只有一个例外:第一个参数必须是一个指定向何哪个文件输出的MCD。MCD可以是一个表达式,但其值必须是一个32位的无符号整数。这个值决定了该任务向哪个打开的文件写入。
-
MCD可以看作由32个标志构成的组,每个标志代表一个单一的输出通道。
-
Verilog中有两个系统任务可以将数据文件读入寄存器组。一个读取二进制数据,另一个读取十六进制数据:
-
$readmemb
$readmemb ("file_name", <memory_name>);
$readmemb ("file_name", <memory_name>, <start_addr>);
$readmemb ("file_name", <memory_name>, <start_addr>, <finish_addr>);
-
$readmemh
$readmemh (" file_name", <memory_name>);
$readmemh (" file_name", <memory_name>, <start_addr>);
$readmemh (" file_name", <memory_name>, <start_addr>, <finish_addr>);
系统任务$readmemb和$readmemh从一个文本文件读取数据并写入存储器。
- 如果数据为二进制,使用$readmemb;如果数据为十六进制,使用$readmemh。
- filename指定要读入的文件。
- mem_name指定存储器信号名称。
- start和finish给出存储器加载的地址。Start为开始地址,finish为结束地址。如果不指定开始和结束地址,$readmem按从低端开始读入数据,与说明顺序无关。
$readmemb和$readmemh的文件格式 :
$readmemb("mem_file. txt", mema);
- 可以指定二进制(b)或十六进制(h)数
- 用下划线“_”提高可读性。
- 可以包含单行或多行注释。
- 可以用空格和换行区分各个数据。
- 可以给后面的值设定一个特定的地址,格式为:
- @(hex_address)
- 十六进制地址的大小写不敏感。
- 在@和数字之间不允许有空格。
- @(hex_address)
注意:没有readmem,只有加b或h的。
- 撰写design代码(正常写法与状态机写法,注意变量类型)
- 撰写testbench(注意变量类型,测试全面,校验代码),考虑时钟,激励方式等
- 考虑将一些冗余代码修改为Task,函数等高级形式撰写
- 参数化变量,task或function
- 合理增加注释,注解,检查变量命名合理性
- 增加关键变量的信息打印(monitor,display,write等),并考虑将其写入日志文件
- 使用shm或vcd库记录波形数据,或者通过文件输入输出写到磁盘文件里
- specify时序设置,时序检查setup,hold等
注意:
👆上述要点用法测试程序见:👉Mod_tb程序(测试了VCD,File Output,File Input)和mod_fsm程序👈(测试Specify),其中SHM软件不支持。
- g = a · b (generate)
- p = a ^ b (propagate)
- c = g + p · ci
- s = p ^ ci
module mult (
input wire clk ,
en_mult ,
input wire [3: 0] a ,
b ,
output reg [7: 0] out
);
always @( posedge clk)
multme (a, b, out);
task multme; // 任务定义
input [3: 0] xme,
tome;
output [7: 0] result;
// 注意task语句多余一个需要加begin……end
wait (en_mult)
result = xme * tome;
endtask
endmodule
module foo(
input [7: 0] loo,
output [3: 0] goo);
// 可以持续赋值中调用函数
wire [3: 0] goo = zero_count ( loo );
function [3: 0] zero_count;
input [7: 0] in_ bus;
integer I;
// 注意begin……end
begin
zero_count = 0;
for (I = 0; I < 8; I = I + 1)
if (! in_bus[ I ])
zero_count = zero_count + 1;
` end
endfunction
endmodule