入门俩月产物,敬请勘误。

前言

指北内容

  • ISE 的安装

  • 写实验要用到的基本语法&设计代码的思想

  • 实验的基本流程解读

面向人群

hdu 计科在读 verilog 速成人,仅供入门参考。因为这也是一个入门才两个月的菜写的(。

欢迎勘误:)

基础实验 1-15 代码参考

杭电计科数电基础实验 1-15

正文

ISE 的安装

https://pan.baidu.com/s/1LjkCsYhLFDcYiqq2KQPNPQ 提取码 987Q

安装好后会看到如下界面

a

点击 manage 之后,点 load 打开破解文件

win64 用户可能会碰到闪退问题,具体解决方法查一下就能找到,主要是忘了怎么解决的了

实验的基本操作流程

新手建议跟着老师下发的操作手册 pdf 走一遍流程。该 pdf 含有图形化界面的指引,相对友好

大概了解操作按钮方位后,可以跟着如下精简提示进行实验 ↓

  1. 创建工程
    左上管理区任意位置右键,选择 New Source -> Verilog Module
    project settings 里配置 Device 为 XC7A100T, Package 为 FGG484,Speed 为-2L(一定要配对 setting,不然仿真/生成 bit 文件会生成不出来)(闪退相关具体问题见下)

  2. 检查语法
    右下管理区 Synthesize-XST -> Check Syntax

  3. 编写测试代码
    左上角最上一栏勾选 Simulation,在左上管理区任意位置右键,选择 New Source -> Verilog Test Fixture -> 选择要测试的模块 -> 写测试代码(测试编写方法见下)

  4. 仿真
    确认左上角工程管理区 view 选项为 simualtion -> 选中仿真激励文件(左上角工作区中你要运行的 test.v) -> 左下角工作区 Simulation Behavior Model 启动仿真

  5. 配置管脚
    勾选左上角工程管理区 view 选项为 implement ->左上管理区任意位置右键,选择 New Source -> Implementation-Constraints File -> 输入约束文件名,点击 Next
    (管脚编写方法见下)

  6. 生成二进制代码
    左下区域右键选中 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 beginend之间填写测试代码即可。以下提供两类测试代码的书写方法。

  1. 直接指定特定数值。俗称打表

        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
  2. 使用 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
如何对变量赋值
  • 初始化变量时进行赋值

  • 单独赋值时,需要用到assignalways

ps. assignalways不可混用,否则报错

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