入门俩月产物,敬请勘误。
前言
指北内容
ISE 的安装
写实验要用到的基本语法&设计代码的思想
实验的基本流程解读
面向人群
hdu 计科在读 verilog 速成人,仅供入门参考。因为这也是一个入门才两个月的菜写的(。
欢迎勘误:)
基础实验 1-15 代码参考
正文
ISE 的安装
https://pan.baidu.com/s/1LjkCsYhLFDcYiqq2KQPNPQ 提取码 987Q
安装好后会看到如下界面
点击 manage 之后,点 load 打开破解文件
win64 用户可能会碰到闪退问题,具体解决方法查一下就能找到,主要是忘了怎么解决的了
实验的基本操作流程
新手建议跟着老师下发的操作手册 pdf 走一遍流程。该 pdf 含有图形化界面的指引,相对友好
大概了解操作按钮方位后,可以跟着如下精简提示进行实验 ↓
创建工程
左上管理区任意位置右键,选择 New Source -> Verilog Module
project settings 里配置 Device 为 XC7A100T, Package 为 FGG484,Speed 为-2L(一定要配对 setting,不然仿真/生成 bit 文件会生成不出来)(闪退相关具体问题见下)检查语法
右下管理区 Synthesize-XST -> Check Syntax编写测试代码
左上角最上一栏勾选 Simulation,在左上管理区任意位置右键,选择 New Source -> Verilog Test Fixture -> 选择要测试的模块 -> 写测试代码(测试编写方法见下)仿真
确认左上角工程管理区 view 选项为 simualtion -> 选中仿真激励文件(左上角工作区中你要运行的 test.v) -> 左下角工作区 Simulation Behavior Model 启动仿真配置管脚
勾选左上角工程管理区 view 选项为 implement ->左上管理区任意位置右键,选择 New Source -> Implementation-Constraints File -> 输入约束文件名,点击 Next
(管脚编写方法见下)生成二进制代码
左下区域右键选中 Generate Programming File -> Process Properties -> 在 General Options 页面勾选”-g compress” -> ok -> 双击左下区域的 Generate Programming File
可能出现的问题
创建工程闪退
file->new Project 之后,你可能会想更改文件位置(location)。然而如果你是 win64 用户,在点击 location 旁边的省略号后可能喜提闪退。此时可以将想存放的文件位置地址进行复制,直接粘贴到 location 中。
另外,在左上工作区右键add resource
时,win64 也可能闪退。但是new resource
是没问题的。
希望删除某一文件,实际上并没有删除
对文件右键点击remove
,实际上并不是直接删除到回收站,而仅仅是将文件从工作区移除。所以再重新生成同名文件时会提示是否进行覆盖。
如何编写测试
如果某文件已生成测试文件后,又更改了其input
output
的变量,需要手动删除旧测试模块,再生成新的测试,因为测试模块不会自动随被测试文件更新。不过如果只是涉及测试模块的逻辑修改,一般不需要重新生成。
生成的测试文件将具有初始内容,测试时只需要在initial begin
和end
之间填写测试代码即可。以下提供两类测试代码的书写方法。
直接指定特定数值。俗称打表
initial begin // Initialize Inputs mr = 0; load = 1; en = 0; dn = 0; clk = 0; d = 4'b0011; #10 clk=1; #20 load=0; en=0; clk=0; #30 load=0; en=1; dn=0; clk=1; #40 clk=0; #50 clk=1; #60 dn=1;clk=0; #70 clk=1; #80 mr=1; end
使用 forever begin,根据赋值时间间隔不断地自动生成不同的组合
initial begin
// Initialize Inputs
A = 0;
B = 0;
C = 0;
D = 0;
E = 0;
// 这边的时间间隔建议互为质数,这样每个组合都能出现
forever begin
#2 A=~A;
#3 B=~B;
#5 C=~C;
#7 D=~D;
#11 E=~E;
end
end
管脚配置
端口的选择
开关:
T3 U3 T4 V3 V4 W4 Y4 Y6 W7 Y8 Y7 T1 U1 U2 W1 W2 Y1 AA1 V2 Y2
灯泡:
K1 J1 H2 G2 F1 E2 D1 B1 B2 N3 M3 K3 H3 N4 L4 J4
按钮:
R4 AA4 AB6
配置管脚的时候就根据这个来接端口
管脚文件解读
以五表决为例
// 这边是端口配置。A/B/C/D/E是开关,用于切换1/0,所以接到属于开关的端口
NET "A" LOC = T3;
NET "B" LOC = U3;
NET "C" LOC = T4;
NET "D" LOC = V3;
NET "E" LOC = V4;
// Y是输出,所以接到灯泡的端口上
NET "Y" LOC = K1;
// 如果是向量,比如input [1:0] Y。那配管脚是这样的:
// NET "Y[1]" LOC = T3;
// NET "Y[0]" LOC = U3;
// 这边是所有的input和output都要写在这
NET "A" IOSTANDARD = LVCMOS18;
NET "B" IOSTANDARD = LVCMOS18;
NET "C" IOSTANDARD = LVCMOS18;
NET "D" IOSTANDARD = LVCMOS18;
NET "E" IOSTANDARD = LVCMOS18;
NET "Y" IOSTANDARD = LVCMOS18;
// 这边是所有的input(要开关的)写在这
NET "A" PULLDOWN;
NET "B" PULLDOWN;
NET "C" PULLDOWN;
NET "D" PULLDOWN;
NET "E" PULLDOWN;
实验中要用到的语法
变量声明
wire:没特别声明的默认类型
reg:如果要到 always 里对某个变量进行赋值,就要给变量指定为 reg 型
input: 没特别声明都是 wire 类型,不能为 reg 类型,用于定义输入的变量
output: 没特别声明都是 wire 类型,用于定义输出的变量
input a; // 默认是wire类型
output reg b; // 显式声明b为reg,可以在always代码块里对其进行赋值
代码块
使用示例如下:
if(a==b)begin
a=0;
end
else begin
a=1;
end
// 代码块里只有一个语句时,begin和end可以省略
赋值
非阻塞&阻塞赋值
// 阻塞赋值。执行c=a时a已经=b了
begin
a=b;
c=a;
end
// 非阻塞赋值。执行c=a时a还是原来的数值,等代码块结束之后a的值才会发生变化
begin
a<=b;
c<=a;
end
如何对变量赋值
初始化变量时进行赋值
单独赋值时,需要用到
assign
或always
ps. assign
和always
不可混用,否则报错
assign
一般用于较简单的赋值&门级描述
assign b=(a==0)?c:d;
// a=0时,使b=c,否则使b=d。只要b右边的式子里有变量变化,b就会被随时更新
always
一般用于行为描述
input a;
output b;
always@(a)
// 括号里是指“当a发生改变时,执行该语句”。如果是想监听所有变量,则将`a`替换成`*`
begin
b=a;
end
判断
两类判断语句都只能配合 always 使用
if-else
input a;
output reg b;
always@(*)
begin
if(a==1)
begin
b=1;
end
else if(a==0)
begin
b=1;
end
end
case
input a;
output reg b;
always@(*)
begin
case(a)
1: begin b=1; end
2: begin b=0; end
endcase
end
函数
定义函数
// add为函数名,并且在函数的书写中当成一个output变量使用。前面的位宽[4:0]是指定output的位宽
function [4:0] add;
// 定义输入变量
input[3:0] a;
input b;
// 必须用begin/end将函数体包起来
begin
// 可以直接写if/case之类必须在always里写的语句,并且将函数名作为output变量名使用
add={a,b};
end
endfunction
使用函数
module demo(a,b,c);
input[3:0] a;
input b;
output c;
always@(*)begin
c=add(a,b);
end
endmodule
时序逻辑电路的 posedge/negedge
写完 RS 之后,就要开始处理 clk 上跳触发的情况了,这种涉及时钟的情况必须使用 always 完成
input a,en,clk;
output b;
// clk上跳触发,且没有其他异步置位/清零的操作,则always的括号中仅写入clk,并指明是posedge上跳沿触发
// 如果有en进行异步清零,则在括号中还要写入en;没显式指明是异步清零,就不需要把en写在括号里
// 值得注意的是,和posedge clk一起写的时候,必须指定posedge还是negedge,即使en并非指明是上跳触发
// 指定posedege还是negedge的原则:假设en为1时异步清零,则将en指定为上跳触发(0->1确实是上跳沿,此时posedge生效的时候en必定为1)
always@(posedge clk, posedge en)
begin
if(en==1) b=0;
...
end
一点写法技巧
- 判断 a=0 且 b=0
if({a,b}==2'b00)
- 同时赋 a、b 为 0
// always代码块中某行代码
{a,b}=2'b00;
- 切片赋值
// always代码块中某行代码
a[2:1]<=a[1:0];
a[0]<=a[2];
一个 verilog 代码的基本结构
以第一个实验五表决为例
module fiveSentense_1(A,B,C,D,E,Y); // 括号里是所有的input和output变量
input A,B,C,D,E; // 输入变量
output Y; // 输出变量
// 门级描述
wire A,B,C,D,E,Y;
assign Y=((A&B&C)||(A&B&D)||(A&B&E)||(A&C&D)||(A&C&E)||(A&D&E)||(B&C&D)||(B&C&E)||(B&D&E)||(C&D&E));
endmodule
设计代码的基本思想
需要注意老师要求中有无限定实现方式。如 RS 触发器只能结构建模描述,否则 RS=11 时将会与预期行为有差别
行为描述
根据真值表来计算 output 的值,这种方法较为符合直觉。通常要用到 always
比如第三个实验译码器便可以利用行为描述
module decoder_3(g1,g2a,g2b,c,b,a,Y);
input g1,g2a,g2b,c,b,a;
output reg[7:0] Y;
always@(*)
begin
// 行为描述
if(g1==0||g2a==0||g2b==0) Y=8'b11111111;
else begin
case({c,b,a})
3'b000: Y=8'b11111110;
3'b001: Y=8'b11111101;
3'b010: Y=8'b11111011;
3'b011: Y=8'b11110111;
3'b100: Y=8'b11101111;
3'b101: Y=8'b11011111;
3'b110: Y=8'b10111111;
3'b111: Y=8'b01111111;
endcase
end
end
endmodule
结构描述
实际上 verilog 会根据你设定的 output 和 input 之间的关系连好电路,你只需要为 input 和 output 建立正确的表达式
结构描述就像连电路,语句主要涉及 assign
以第一个实验五表决为例
module fiveSentense_1(A,B,C,D,E,Y);
input A,B,C,D,E;
output Y;
// 门级描述
assign Y=((A&B&C)||(A&B&D)||(A&B&E)||(A&C&D)||(A&C&E)||(A&D&E)||(B&C&D)||(B&C&E)||(B&D&E)||(C&D&E));
endmodule