计算机技术学习札记

Verilog、FPGA 和开发流程

什么是 Verilog

Verilog 是一门「硬件描述语言」(Handware Description Language, HDL)。我们分别关注一下这里面的三个单词:

  • 硬件 Hardware:说明这个东西是与硬件相关的,而与软件关系不大。事实上,这里的「硬件」特别指数字电路。

  • 描述 Description:说明这个东西存在的意义,是用来刻画某个事物的「样子」或者「形态」——就像数学公式是用来描述数学关系那样。

  • 语言 Language:说明这个东西的存在形式是语言,也就是要一个一个字符地写的东西。

连起来看,HDL 就是「用来刻画数字电路的语言」。如果不说「语言」两个字,你可能想到的是电路图,因为它满足「刻画数字电路」:

事实上,HDL 的作用(在一定程度上)就和电路图一样。Verilog 是一门 HDL,用 Verilog 描述上面的电路,则是这样的一段代码:

module some_circuit(
    input a,
    input b,
    input c,
    output x
);

    assign x = ((a | ~b) & (b | c)) & b;

endmodule

你大概能看懂一点儿上面的代码在说什么,也可能看不懂——那都没有关系。我们现在只用知道,这份代码和刚刚的电路图有着一样的作用:它们都在描述同一个电路。而 HDL 比电路图好弄、好分发、好修改、好携带,因此它成为了数字电路设计领域的标准;而我们学习的 Verilog,则是众多 HDL 之中应用最广泛的那个。

还有其他的 HDL 吗?当然有。Verilog 是 HDL 中最出名的那一个,有很大一部分原因是它出生早——可能你还没有出生的时候,它已经在产业中应用了。事实上,Verilog 并不是那么完美,因此人们后来又发明了诸如 SystemVerilog、Chisel、SpinalHDL 等其他 HDL。它们对 Verilog 的痛点进行了各种各样的改进,易用性故而比 Verilog 高,现在也在产业中有所应用。

那为什么在这个年代我们还学传统 Verilog?因为 HDL 的思路是相通的,语言只是工具。

什么是 FPGA

FPGA 全称「现场可编程逻辑门阵列」(Field Programmable Gate Array)。在了解 FPGA 是什么之前,先请想一个问题:你直觉中的「集成电路」或者说「芯片」内部是什么样子的?

上面是一枚电脑 CPU。中间那个亮晶晶的部分是芯片的本体——它是在纯度极高的硅片上利用光「刻」上电路实现的东西。刻在上面的电路是什么呢?就是由我们在理论课中学习过的逻辑门、触发器等这些数字电路元件所搭建成的数字逻辑电路。我们手机、电脑里的芯片,都是这样的超大规模的数字电路。

像电脑、手机处理器 CPU 这样的芯片,它们一旦生产完成,想要修改内部的结构是基本没有可能的——电路是死的,已经接好的东西,我们没法再去改变它。尽管它们可以运行不同的软件来实现不同的功能,但它们在硬件层面是固化的、「不可编程」的——你的 CPU 芯片它过十年还是个 CPU,不会变成什么其他的芯片。

但是,FPGA 与这样的芯片不同,它是「可编程」的。FPGA 内部也有着大量的逻辑门、触发器这样的元件,但是它们之间的连线不固定,是可以改变的。我可以让这些元件搭成一个大号逻辑门,也可以让这些元件搭成一个计数器,甚至可以搭出一个 CPU。所谓「现场可编程逻辑门阵列」,描绘的就是这样的一个结构。

再想想前面的 Verilog……我们用 Verilog 设计的数字电路,不正好可以在 FPGA 上「搭」出来吗?没错!事实上,我们使用的 Minisys 开发板,核心就是一枚 FPGA 芯片。它由美国赛灵思公司(现在是 AMD 的一部分)推出,内有上万个可以互连的逻辑单元,能部署从「一个与门」到「一个 CPU」的各种数字电路进去。我们在《数字逻辑设计》实验中要做的,就是设计各种各样的电路,将它们部署到这枚 FPGA 上,实现各种有趣的东西。

看到这里,你能意识到 FPGA 这东西的重要性吗?它可以在使用中改变内部的结构,因此在芯片设计中,用 FPGA 验证设计的正确性是非常重要的一环——就像建筑师会用木质模型来验证自己的设计一样。

我们使用的赛灵思 FPGA 是市面上比较常见,也比较优质的 FPGA 芯片。赛灵思在全球占据了 FPGA 这个市场的「半壁江山」,它的竞争者有 Intel、Lattice 等——都是美国企业。国产 FPGA 目前还在发展,在低端领域有许多不错的产品,但在高端领域,以及产品配套的服务方面还相当欠缺。

FPGA 芯片很贵——我们使用开发板上的那一颗就要上千元,请一定要爱惜实验硬件!

在 FPGA 上的 Verilog 开发流程

现在,我们是时候梳理使用 Verilog + FPGA 的开发流程了。

  • 编写 Verilog 代码,描述电路的行为。

    这一步,我们将使用 Verilog 语言编写代码,设计各个模块。例如,我们编写一个 3 线—8 线译码器模块:

    module decoder_38 (
    	input  wire [2:0] en,
    	input  wire [2:0] data_in,
    	output reg  [7:0] data_out
    );
    
    always @ (*) begin
    	if (en == 3'b100) begin
    	    case (data_in)
    		    3'h0 : data_out = 8'hfe;
    			3'h1 : data_out = 8'hfd;
    			3'h2 : data_out = 8'hfb;
    			3'h3 : data_out = 8'hf7;
    			3'h4 : data_out = 8'hef;
    			3'h5 : data_out = 8'hdf;
    			3'h6 : data_out = 8'hbf;
    			3'h7 : data_out = 8'h7f;
    			default : data_out = 8'hff;
    		endcase
    	end
    	else data_out = 8'hff;
    end
    
    endmodule
    

    如上面所说,这份 Verilog 代码从行为的角度,描述了一个名叫「decoder_38」的模块;这个模块的功能,是将输入的 3 位二进制数译码。

  • 仿真(Simulation):软件读入我们所写的 Verilog 代码,模拟电路的行为,供我们判断设计的正确性。

    现在,我们有了设计好的模块 decoder_38,但我们怎么验证它能正确地发挥功能呢?我们当然可以将它「变」成实际的电路,然后去实际地观察它——但是,在那之前,我们应该先让我们的软件来对模块进行仿真:软件模拟电路的动作,然后将电路中各个信号的值展示给我们,供我们判断。

    以上面的 3 线—8 线译码器为例。我们希望验证它能正常工作,因此可以尝试将 000001010……111 各种信号「送」给这个模块,然后观察它的表现(是否有正确的信号输出)。这个「送」的过程,叫做「测试激励」;而负责完成「送」这个动作的,叫做「激励文件」(testbench)。

    Testbench 是需要我们去编写的——也就是说,我们不仅负责写一个模块,我们还需要自己写用来测试这个模块的东西。软件所作的,就是按 testbench 去模拟电路行为并暴露给我们。

    用于测试 3 线—8 线译码器的 testbench 代码如下:

    `timescale 1ns/1ps
    
    module decoder_38_sim();
    
       // 定义需要用到的信号,若信号是在块语句里面用到,则定义为reg型,
       // 信号名可以任意,最好与测试模块的代码有相同的含义
       reg [2:0] data_in;         // 3位二进制数据输入
       reg [2:0] en;              // 3位使能信号输入
       wire [7:0] data_out;       // 8位输出
    
       // 实例化被测试的模块
       decoder_38 u_decoder_38(    //被测试模块名decoder_38写在前面
      	     .data_in(data_in),    // 指定testbenablech中定义的信号与被测试模块信号的连接关系
    	     .en(en),              // ()括号里面的信号是testbenablech中定义的
    	     .data_out(data_out)
       );
    
       initial    // initial块中的语句只执行一次,initial只用于testbench,不应该用在功能代码中
       begin
           en = 3'b000; data_in = 3'b000;               // 默认从0ns开始,若0ns不做初始化,系统会对输入赋值不定态度x
           #5 begin en = 3'b100;data_in = 3'b000; end   // 延时5个时间单位,即延时5ns
           #5 begin en = 3'b100;data_in = 3'b001; end   // 构造测试输入:使能端有效,输入遍历8种情况 
           #5 begin en = 3'b100;data_in = 3'b010; end
           #5 begin en = 3'b100;data_in = 3'b011; end  
           #5 begin en = 3'b100;data_in = 3'b100; end
           #5 begin en = 3'b100;data_in = 3'b101; end  
           #5 begin en = 3'b100;data_in = 3'b110; end
           #5 begin en = 3'b100;data_in = 3'b111; end  
           #5 begin en = 3'b101;data_in = 3'b000; end   //构造测试输入:使能端无效
           #10 $finish ;                               // 结束仿真,不然系统会一直运行,直到系统设置的默认的仿真时间到(一般是1000ns)   
       end
       
    endmodule
    

    软件运行上面的 testbench 后,将给我们展示波形图。波形图反映了电路中每一个信号随时间的变化,我们利用它来判断正确性。

  • 综合(Synthesis):由综合器(Synthesiser)分析我们写的 Verilog 代码,绘制出相应功能的数字电路图(称为「网表」)。

    例如,上面的 3 线—8 线译码器,经综合后得到的网表如下。网表中的元件就是 FPGA 芯片中的微元件,而连线就像是真正的电线一样将它们相连接。

  • 布局布线(Implementation):由布局布线器读入上面的网表,根据 FPGA 内部的资源分布,将网表转换成 FPGA 内部的连线关系。

    例如,上面的网表经过布局布线,将在我们的 FPGA 芯片中分配下面的元件和线路:

  • 比特流(Bitstream)生成与上板:将布局布线后的设计生成为能写入 FPGA 的「比特流」,将比特流输入 FPGA 来部署我们的设计。

在这整个过程中,我们所使用的工具都是 Vivado。Vivado 是赛灵思官方的 FPGA 开发工具,包含综合器、布局布线器、比特流下载器等所有开发需要的工具。