跳转至

lab0:Vivado_Starter

主要记录一下lab0的手搓过程,免得以后又忘记了Verilog里面各种东西的写法.

Warning

我使用的Vivado版本是2022.2,slides上面是2017年的版本,所以会有很多细节上的出入. 事实上,本文档也记录了很多版本出入上的困惑.

lab0包含的任务有:预热(Water_LED的仿真试验、自定义模块IP核的生成)、正式任务(基本逻辑模块的原理学习、Xilinx IP的生成).

林芃班的验收规则:

  • 流水灯仿真波形 PPT 39

  • 流水灯运行结果 PPT 54

  • MUX2T1_5仿真波形 PPT 78

  • 多选器控制LED运行结果 PPT 167

    1. 注意推荐使用.v形式完成:通过Add Sources->Add or create design source->Create File 创建所需模块,然后实现各模块功能

    2. ppt79-94以及ppt142-149中自定义IP模块的封装和调用可以先做了解

预热

流水灯Water_LED的仿真代码书写

如图为NEXYS-A7上的16个并行LED流水灯原理图, 所有的阴极(负极)接地(共阴极),当阳极接高电平时点亮.

将学在浙大上给出的Water_LED.vWater_LED_tb.v代码分别导入Vivado的Design SourcesSimulation Sources中.

首先看Water_LED.v的代码逻辑:

`timescale 1ns / 1ps
module Water_LED(
    input CLK_i,
    input RSTn_i,
    output reg [3:0]LED_o
    );
    reg [31:0]C0;

always @(posedge CLK_i)
    if(!RSTn_i) begin
        LED_o <= 4'b1;
        C0 <= 32'h0;
    end
    else begin
        if (C0 == 32'd100_000_000) begin
            C0 <= 32'h0;
            if (LED_o == 4'b1000)
                LED_o <= 4'b1;
            else LED_o <= LED_o << 1;
        end
        else begin
            C0 <= C0 + 1'b1;
            LED_o <= LED_o;
        end
    end   

endmodule

这段代码里面,C0是中间计数器,每计数\(10^8\)翻转一次,产生1Hz时钟来更新LED亮暗状态;

通过左移一位高电平的操作来逐一点亮LED,从而在视觉上形成流水的效果.

补全Water_LED_tb.v的代码:

module Water_LED_tb;
    reg CLK_i;
    reg RSTn_i;
    wire [3:0]LED_o;

    Water_LED Water_LED_U(
        .CLK_i(CLK_i),
        .RSTn_i(RSTn_i),
        .LED_o(LED_o)
    );

    always #5 CLK_i = ~CLK_i;

    initial begin
        CLK_i = 0;
        RSTn_i = 0;
        #100 RSTn_i = 1;
        //Your code here.
        #4000000000 $finish;
    end    

endmodule

事实上只需要添加一行就行,不过记得延长一下仿真时间,否则会很快出结果且LED全为1,因为默认的\(10^6\)ps还不足以让LED产生进位.

结果图大概长这样:

添加约束文件

添加约束文件主要是两种方法,工具约束和脚本约束,比较推荐脚本约束。

工具约束

上学期并没有讲过这个部分,但其实不麻烦,是在RTL Analysis的Schematic一节来完成,具体参见Slides.

脚本约束

你也可以直接将给出的约束文件Water_LED.xdc加入到Constraint Sources中.

set_property PACKAGE_PIN C12 [get_ports RSTn_i]
set_property IOSTANDARD LVCMOS33 [get_ports RSTn_i]
set_property PACKAGE_PIN H17 [get_ports {LED_o[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED_o[0]}]
set_property PACKAGE_PIN K15 [get_ports {LED_o[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED_o[1]}]
set_property PACKAGE_PIN J13 [get_ports {LED_o[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED_o[2]}]
set_property PACKAGE_PIN N14 [get_ports {LED_o[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED_o[3]}]
set_property PACKAGE_PIN E3 [get_ports CLK_i]
set_property IOSTANDARD LVCMOS33 [get_ports CLK_i]

实验文档里面怎么有一个不同约束文件的图???但是看起来并不是这次的板子,令人疑惑,是祖传的slides不修改吗?

接下来生成bitstream并program device之后就能看到流水灯效果了,是索引为3,2,1,0的四个端口出现了流水的效果.

点击下载效果视频

自定义模块设计学习

Tips

这个模块操作过于老旧,可以不做.

IP核的生成

这一模块首先需要完成的任务是:利用vivado完成对MUX2T1_5的设计和封装.

首先新建一个项目,命名为MUX2T1_5,并新建MUX2T1_5.v文件,输入以下1bit MUX的代码:

module MUX2T1_5(
    input[4:0]I0,
    input[4:0]I1,
    input s,
    output [4:0] o
    );
    assign o = s ? I1 : I0;
endmodule

接着新建仿真文件MUX2T1_5_tb.v,输入:

module MUX2T1_5_tb(
    );
    reg [4:0] I0;
    reg [4:0] I1;
    reg s;

    wire [4:0] o;

    MUX2T1_5 MUX2T1_5_test(
        .I0(I0),
        .I1(I1),
        .s(s),
        .o(o)
    );
    initial begin
        s = 0;
        I0 = 0;
        I1 = 1;
        #50;
        s = 0;
        #50;
        s = 1;
    end
endmodule

跑出来结果:

slides上面的仿真代码,从波形倒推来讲比较复杂. 看意思是只要能验证MUX的设计代码功能成立,就可以进入封装部分了.

找到上面的Tools->Create and Package New IP->Next,一路点击底下的Next>至IP Location的选择,这里我的Vivado初始路径跟slides不是很一样,slides上面要求是../mux2t1_5/mux2t1_5.srcs/sources_1/new,而我这里是../mux2t1_5/mux2t1_5.srcs,所以还得往里面切2层找到目标路径.

finish之后,在弹出的窗口中选择Review and Package->Package IP即可,这样封装就完成了.

创建IP的流程(不含源文件)

分为2步,一是网表文件.edf的生成,二是根据.edf文件进行IP封装.

Settings-Synthesis-More Option中输入-mode out_of_context,这表明在该级不插入任何I/O buffers.

在综合完成之后弹出的窗口中选择Open Synthesized Design,并在底部的Tcl Console中输入:write_verilog -mode synth_stub E:/Vivadodemo/ip/MUX2T1_5.v,其中存档路径是自己选择的.

接着在Tcl Console中输入:write_edif E:/Vivadodemo/ip/MUX2T1_5.edf,将生成的两份文件放到新建的一个项目中打包成IP就可以了.

正式任务1:基本逻辑模块的原理学习、Xilinx IP的生成

目标:熟悉多选器、逻辑运算、数据扩展等模块的设计原理,掌握存储器的生成方法.

任务1:熟悉基本模块功能

熟悉基本模块功能,在Vivado环境中采用Verilog进行基本模块的设计和仿真验证.

堪称迷惑的一个部分,列举了一堆数逻涉及的模块然后不知所云地抛出了一个“参考模块功能,自行设计逻辑模块”的指示,能不能写明白一点呢?以及,在最后说让我保持端口信息一致,那你为什么一会大写一会小写一会驼峰命名一会下划线?写个实验文档这么随意吗???

记录一下提到的逻辑模块和他们实现用到的代码:

Tips

MUX: m位n选1的多路器

module MUXnT1_m(input [m-1:0] I0,
                input [m-1:0] I1,
                // etc.
                input sel,
                output reg [m-1:0] o
                );
    // Some code here.
endmodule     

MUX2T1_5:

module MUX2T1_5(input [4:0] I0,
                input [4:0] I1,
                input sel,
                output reg [4:0] o
                );
    always @(*) begin
        if (sel == 1)
            o = I1;
        else
            o = I0;
    end

    // 以下语句是错误的,因为o是reg类型,不支持assign连续赋值.
    // assign o = sel ? I1 : I0;

endmodule

MUX2T1_32:

module MUX2T1_32(input [31:0] I0,
                 input [31:0] I1,
                 input sel,
                 output reg [31:0] o
                );
    always @(*) begin
        if (sel == 1)
            o = I1;
        else
            o = I0;
    end
endmodule

MUX4T1_5:

module MUX4T1_5(input [1:0] s,
                input [4:0] I0,
                input [4:0] I1,
                input [4:0] I2,
                input [4:0] I3,
                output reg [4:0] o
                );
    always @(*) begin
        if (s == 2'b00)
            o = I0;
        else if (s == 2'b01)
            o = I1;
        else if (s == 2'b10)
            o = I2;
        else 
            o = I3;
    end
endmodule

MUX4T1_32:

module MUX4T1_32(input [1:0] s,
                input [31:0] I0,
                input [31:0] I1,
                input [31:0] I2,
                input [31:0] I3,
                output reg [31:0] o
                );
    always @(*) begin
        if (s == 2'b00)
            o = I0;
        else if (s == 2'b01)
            o = I1;
        else if (s == 2'b10)
            o = I2;
        else 
            o = I3;
    end
endmodule

MUX8T1_32:

module MUX8T1_32(input [31:0] I0,
                 input [31:0] I1,
                 input [31:0] I2,
                 input [31:0] I3,
                 input [31:0] I4,
                 input [31:0] I5,
                 input [31:0] I6,
                 input [31:0] I7,
                 input [2:0] s,
                 output reg [31:0] o
                );
    always @(*) begin
    case (s)
        3'b000: o = I0;
        3'b001: o = I1;
        3'b010: o = I2;
        3'b011: o = I3;
        3'b100: o = I4;
        3'b101: o = I5;
        3'b110: o = I6;
        3'b111: o = I7;
        default: o = I0;  // 可选的默认情况
    endcase
end
endmodule

Tips

算术函数模块

32位无进位加法器:

module add_32(input [31:0] a,
              input [31:0] b,
              output [31:0] c
            );
    assign c = a ^ b;
endmodule

32位带进位加减器:

这里c == 1时选择减法,c == 0时选择加法.

module ADC32(input [31:0] A,
              input [31:0] B,
              input C0,
              output [32:0] S
            );
    wire b0 = C0 ^ 1'b0;
    assign S = {1'b0,A} + {b0,B} + C0; 
endmodule

32位与运算:

module and32(input [31:0] A,
              input [31:0] B,
              output [31:0] res
            );
    assign res = (A & B);
endmodule

32位或运算:

module or32(input [31:0] A,
              input [31:0] B,
              output [31:0] res
            );
    assign res = (A | B);
endmodule

32位或非运算:

module nor32(input [31:0] A,
              input [31:0] B,
              output [31:0] res
            );
    assign res = ~(A | B); // | 是按位或, || 是逻辑或
endmodule

32位异或运算:

module xor32(input [31:0] A,
              input [31:0] B,
              output [31:0] res
            );
    assign res = A ^ B;
endmodule

32位逻辑右移运算(srl):移位量由b[4:0]来控制

module srl32(input [31:0] A,
              input [31:0] B,
              output [31:0] res
            );
    assign res = A >> B[4:0];
endmodule

32位自或运算:(判断是否为全0)

module or_bit_32(input [31:0] A,
              output o
            );
    assign o = |A;
endmodule

16位符号数-32位符号数算术拓展:

module Ext_32(input [15:0] imm_16,
              output [31:0] imm_32
            );
    assign imm_32 = {16{imm_16[15]}imm_16};
endmodule

1位信号-32位算术拓展:

module SignalExt_32(input S,
              output [31:0] So
            );
    assign So = {32{S}};
endmodule

任务2:生成IP并初始化存储内容

Tips

这个模块中的IP Core生成操作过于老旧,暂时可以不做,但是lab2还是要进行这个操作的.

学习并利用Vivado生成Xilinx库中的IP,以存储器ROM,RAM为例,并完成其存储内容的初始化.

ROM_D IP Core的生成

点击左侧的IP Catalog,在打开的窗口搜索栏中输入memory generator,选择Block Memory Generator或者Distributed Memory Generator. 其中前者是同步访问,后者是异步访问.

先双击打开Distributed Memory GeneratorDepth输入1024,Data Width输入32,存储类型调成ROM -> OK,接着在操作窗口中选择跟memory config同级的RST & Initialization,你需要点击Browse按钮来添加Coefficients File并查验.

Tips

这里slides又开始顺序颠倒不说人话了,coe文件实则需要自己创建完再在这个窗口中选中,需要自行编辑一个.coe文件.

个人是VSCode打开这个文件夹然后按照slides-124的指示添加ROM.coe文件:

memory_initialization_radix=16;
memory_initialization_vector=
00000000, 11111111, 22222222, 33333333, 44444444, 55555555, 
66666666, 77777777, 88888888, 99999999, aaaaaaaa, bbbbbbbb, 
cccccccc, dddddddd, eeeeeeee, ffffffff, 557EF7E0, D7BDFBD9, 
D7DBFDB9, DFCFFCFB, DFCFBFFF, F7F3DFFF, FFFFDF3D, FFFF9DB9, 
FFFFBCFB, DFCFFCFB, DFCFBFFF, D7DB9FFF, D7DBFDB9, D7BDFBD9, 
FFFF07E0, 007E0FFF, 03bdf020, 03def820, 08002300;

点击生成之后,你就能看到类似slides-123上面的效果,查看ROM_D.veo模块,出现这样的片段即为成功:

//----------- Begin Cut here for INSTANTIATION Template ---// INST_TAG
ROM_D your_instance_name (
  .a(a),      // input wire [9 : 0] a
  .spo(spo)  // output wire [31 : 0] spo
);
// INST_TAG_END ------ End INSTANTIATION Template ---------

RAM_B IP Core的生成

按slides的操作进行,看到.veo文件中包含这个片段就说明完成了.

//----------- Begin Cut here for INSTANTIATION Template ---// INST_TAG
RAM your_instance_name (
  .clka(clka),    // input wire clka
  .wea(wea),      // input wire [0 : 0] wea
  .addra(addra),  // input wire [9 : 0] addra
  .dina(dina),    // input wire [31 : 0] dina
  .douta(douta)  // output wire [31 : 0] douta
);
// INST_TAG_END ------ End INSTANTIATION Template ---------
Questions

RAM IP Core的生成过程中有一个问题,为什么不能勾选Primitives Output Register

理由:默认的输出端口会勾选输出寄存器,导致数据会往后延迟一个时钟周期,所以应该不勾选.

正式任务2:利用自定义模块构建实验平台

Tips

这个模块IP Core相关部分操作过于老旧,可以不做. 最好使用.v文件模块化打包的操作来实现.

这个部分就更加混沌了,IP核的封装不太明白为什么不使用.v直接模块化打包进去,所幸助教哥哥的验收要求中规避了IP Core的生成.

这个是主文件:

module OExp01_muxctrl(
    input wire [4:0]I0,
    input wire [4:0]I1,
    input wire [1:0]s,
    input wire s1,
    input wire [2:0]s2,
    output wire [4:0]o_0 );
    wire [4:0] MUX2T1_5_0_o;
    wire [4:0] MUX2T1_5_1_o;
    wire [7:0] MUX8T1_8_0_o;

    MUX2T1_5_0 MUX2T1_5_0
    (.I0(I0),
    .I1(I1),
    .o(MUX2T1_5_0_o),
    .s(s1));

    MUX2T1_5_0 MUX2T1_5_1
    (.I0(I0),
    .I1(I1),
    .o(MUX2T1_5_1_o),
    .s(1'b1));

    MUX4T1_5_0 MUX4T1_5_1(.I0(MUX8T1_8_0_o[3:0]),
    .I1({MUX2T1_5_1_o[0],MUX2T1_5_0_o[3:0]}),
    .I2(5'b1),
    .I3(5'b0),
    .o(o_0),   //这个是最终输出
    .s(s));

    MUX8T1_8_0 MUX8T1_8_0
    (.I0({MUX2T1_5_0_o[3:0],MUX2T1_5_1_o[3:0]}),
    .I1({MUX2T1_5_1_o[3:0],MUX2T1_5_0_o[3:0]}),
    .I2(8'b1),
    .I3(8'b1),
    .I4(8'b1),
    .I5(8'b1),
    .I6(8'b1),
    .I7(8'b1),
    .o(MUX8T1_8_0_o),
    .s(s2));
endmodule

接下来就是把三个模块分别补上去就完成了.

添加约束文件:

#switch
set_property IOSTANDARD LVCMOS33 [get_ports {s[0]}]
set_property PACKAGE_PIN J15    [get_ports {s[0]}]                 
set_property IOSTANDARD LVCMOS33 [get_ports {s[1]}]
set_property PACKAGE_PIN L16    [get_ports {s[1]}]                 
set_property IOSTANDARD LVCMOS33 [get_ports  s1]
set_property PACKAGE_PIN M13    [get_ports  s1]                 
set_property IOSTANDARD LVCMOS33 [get_ports {s2[0]}]
set_property PACKAGE_PIN R15    [get_ports {s2[0]}]                 
set_property IOSTANDARD LVCMOS33 [get_ports {s2[1]}]
set_property PACKAGE_PIN R17     [get_ports {s2[1]}]                 
set_property IOSTANDARD LVCMOS33 [get_ports {s2[2]}]
set_property PACKAGE_PIN T18     [get_ports {s2[2]}]                 
set_property IOSTANDARD LVCMOS33 [get_ports {I0[0]}]
set_property PACKAGE_PIN U18    [get_ports {I0[0]}]                 
set_property IOSTANDARD LVCMOS33 [get_ports {I0[1]}]
set_property PACKAGE_PIN R13    [get_ports {I0[1]}]                 
set_property IOSTANDARD LVCMOS18 [get_ports {I0[2]}]
set_property PACKAGE_PIN T8    [get_ports {I0[2]}]                  
set_property IOSTANDARD LVCMOS18 [get_ports {I0[3]}]
set_property PACKAGE_PIN U8    [get_ports {I0[3]}]                 
set_property IOSTANDARD LVCMOS33 [get_ports {I0[4]}]
set_property PACKAGE_PIN R16    [get_ports {I0[4]}]                 
set_property IOSTANDARD LVCMOS33 [get_ports {I1[0]}]
set_property PACKAGE_PIN T13    [get_ports {I1[0]}]                 
set_property IOSTANDARD LVCMOS33 [get_ports {I1[1]}]
set_property PACKAGE_PIN H6     [get_ports {I1[1]}]                 
set_property IOSTANDARD LVCMOS33 [get_ports {I1[2]}]
set_property PACKAGE_PIN U12    [get_ports {I1[2]}]                 
set_property IOSTANDARD LVCMOS33 [get_ports {I1[3]}]
set_property PACKAGE_PIN U11    [get_ports {I1[3]}]                 
set_property IOSTANDARD LVCMOS33 [get_ports {I1[4]}]
set_property PACKAGE_PIN V10    [get_ports {I1[4]}]  

set_property IOSTANDARD LVCMOS33 [get_ports {o_0[0]}]
set_property PACKAGE_PIN H17     [get_ports {o_0[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {o_0[1]}]
set_property PACKAGE_PIN K15    [get_ports {o_0[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {o_0[2]}]
set_property PACKAGE_PIN J13     [get_ports {o_0[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {o_0[3]}]
set_property PACKAGE_PIN N14    [get_ports {o_0[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {o_0[4]}]
set_property PACKAGE_PIN R18     [get_ports {o_0[4]}]  

约束文件中,最右侧的5个小灯从左至右对应了o[4:0]信号;

底下的按钮,从左至右分别对应是 I1[4:0],I0[4:0],s2[2:0],s1,s[1:0].