文章目录
Verilog梳理
诸位看官请确保阅读本文之前已经学过了数字逻辑基本的知识。本文梳理的内容基于《计算机组成与设计:硬件与软件接口》这本书,侧重点在于最终设计出CPU。
1. Verilog的数据类型和操作类型
两种基本数据类型datatypes
wire表示一个组合信号(specifies a combinational signal)
reg-用于存储一个数据 (a register)
//下面展示一些基本定义方式
//32位宽
reg [31:0]
wire[31:0]
//访问指定连续位
reg[starting bit: ending bit]
//定义寄存器案例
reg [31:0] registerfile[0:31];
1个reg或wire可能取值
0 或 1
X:未知
Z:高阻态(不讨论)
不同进制的定义
4'b0100 //4位二进制数,也可以表示为4'd4; b, d, h对应二进制、十进制和16进制
//binary2, octal8, hexadecimal 16, decimal 10
操作符
从C语言继承单目运算符和双目运算符和条件运算符?:。
有自己的操作符:所有位进行对应操作
& | ^ ,如 ^A返回A中所有位异或的结果
拼接运算符:{}
{16{2'b01}} //创建了一个32位的数,形式为010101...01
{A[31:16], B[15:0]} //创建了一个值,upper16位来自A,lower16位来自B
2. Verilog程序的结构
- initial结构(对reg型变量进行初始化)
- 连续赋值语句(只用于组合逻辑)
- always结构(sequential时序和combinational组合逻辑都可以用)
- 模块实例化(个人补充:有点基于类定义对象)
3.Verilog构造复杂的组合逻辑
连续赋值continuous assignment在assign关键字中反映出来,就像一个组合逻辑的函数一样使用(output被连续赋值,input值的一个改变会立即在output值处体现)。wires只能通过连续赋值来赋值。(博主注:这句话还有待讨论,请自行判断)。
下面给出一个用连续赋值来实现半加法器的例子
module half_adder(A, B, Sum, Carry):
input A, B; //两个1位输入
output Sum, Carry; //两个1位的输出
assign Sum = A^B;
assign Carry = A & B;
endmodule
但是,对于更复杂的结构,用assign显得很蠢(原文tedious & awkward)。于是,这时候可以使用always块(block)来描述一个组合逻辑的module。always block中很重要的就是由@引导的敏感列表(sensitivity list)。
如果敏感列表中任一个信号干煸,always块会被不断地reevaluate。如果always块中有很多的statements,那么用begin,end把它框起来,就像C语言中的括号一样。
Reg或许只能在(博主注:may only be?原文是?这里怎么大家也要自己分辨一下)always block里面赋值,赋值方式是过程赋值procedural assignment(不同于之前assign这种连续赋值continuous assignment方式)。有两种不同的过程赋值,一种是=,一种是<=, 前者是阻塞赋值,后者是非阻塞赋值(nonblocking), always模块中的非阻塞赋值右侧是同时执行的(simultaneously)。
在always模块中只有reg能被赋值,当我们想用always块的时候,一定要确保reg没有synthesize into a register(博主注:这里怎么翻译没太想好)。原书这里提出了一些编程的好习惯,感兴趣可以去进一步阅读。
下面给出always模块的两个例子。其中的case就像C语言中的switch。
//32位输入的4选1多路选择器,用case语句实现
module Mult4to1(In1, In2, In3, In4, Sel, Out):
input [31:0] In1, In2, In3, In4;
input [1:0] Sel; //select signal
output reg [31:0] Out;
always @(In1, In2, In3, In4, Sel)
case(Sel)
0: Out <=In1;
1: Out <=In2;
2: Out <=In3;
default: Out <=In4;
endcase
endmodule
//一个mips ALU(用Verilog的行为级语言表述)
module MIPSALU(ALUctl, A, B, ALUOut, Zero):
input [3:0] ALUctl;
input [31:0] A, B;
output reg[31:0] ALUOut;
output Zero;
assign Zero = (ALUOut==0);
always @(AlUctl, A, B) begin
case(ALUct1)
0: ALUOut <= A&B;
1: ALUOut <= A|B;
2: ALUOut <= A+B;
6: ALUOut <= A-B;
7: ALUOut <= A<B?1:0;
12: ALUOut <= ~(A|B);
default: ALUOut<=0; //default to 0, should not happen
endcase
end
endmodule
下面给出MIPS ALU control的一个小片段
module ALUControl(ALUOp, FuncCode, ALUCtl);
input [1:0] ALUOp;
input [5:0] FuncCode;
output [3:0] reg ALUCtl;
always case(FuncCode)
32: ALUOp <=2; //add
34: ALUOp <=6; //substract
36: ALUOp <=0; //and
37: ALUOp <=1; //or
39: ALUOP <=12; //nor
42: ALUOp <=7; //slt
default: ALUOp <=15; //should not happen
endcase
endmodule
4-Clocks
下面来简单介绍一下时钟verilog怎么写。如果概念不清楚的同学,请自己补一下数字逻辑中的有关知识。
自我考核时间,你是否能说出以下概念的意义:
边沿触发,时钟周期,上升沿,下降沿,同步,异步,建立时间setup time,保持时间hold time
下面对后面两个概念进行一个说明:
setup time:the minimum time that the input to a memory device must be valid before the clock edge(输入在时钟沿到来前必须保持的时间)
hold time: 输入在时钟沿之后必须保持的时间。
时钟周期必须大于等于:Tprop(信号在触发器传递的时间,也叫clock to Q) + Tcombinational(关键路径最长时延)+Tsetup(时钟建立时间)+Tskew(时钟扭曲时间,指的是时钟信号到不同地方不同时间带来的影响时间)。
在CPU设计中,有关技术常用于寄存器堆即register file。
存储元件Memory Elements
Flip-flops触发器
下面给出一个上升沿触发的D触发器的例子
module DFF(clock, D, Q, Qbar):
input clock, D;
output reg Q;
output Qbar;
assign Qbar=~Q;
always @(posedge clock)
Q=D;
endmodule
Latches锁存器
Registers寄存器
具体例子
//A specification of a clock
reg clock;
always
#1 clock=1; #1 clock=0;
//一个小的sample,上升沿时寄存器A写入b的值
reg [31:0] A;
wire [31:0] b;
always @(posedge clock) A<=b;
//行为级语言的MIPS registers file,上升沿触发
module registerfile(Read1, Read2, WriteReg, WriteData, RegWrite, Data1, Data2, clock);
input [5:0] Read1, Read2, WriteReg;//用于读或写的寄存器编号
input [31:0] WriteData;//data to write
input RegWrite, clock;//写控制信号
output [31:0] Data1, Data2; //the register values read
reg [31:0] RF [31:0]; //32个32位寄存器
assign Data1 = RF[Read1];
assign Data2 = RF[Read2];
always begin
@(posedge clock) if (RegWrite) RF[WriteReg] <= WriteData;
end
endmodule
5-Finite-State Machines有限状态机FSM
这是又一个很重要的概念。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dFtmT7YN-1652006634991)(C:\Users\Eliza\AppData\Roaming\Typora\typora-user-images\image-20220508183302195.png)]
下面给出上面的两状态交通灯的verilog代码。NSlite=1当切仅当NS方向的灯是绿的,EWlite同理。NScar=1当且仅当有车经过NS方向的路,EWcar同理。NSgreen=1当且仅当NS方向交通灯是绿的,EWgreen同理。
下面给出对应的verilog代码
module TrafficLite(EWCar, NSCar, EWLite, NSLite, clock);
input EWCar, NSCar, clock;
output EWLite, NSLite;
reg state;
initial state=0;
assign NSLite = ~state;
assign EWLite = state;
always @(posedge clock)
case (state)
0: state = EWCar;
1: state = NSCar;
endcase
endmodule
6- 补充在设计CPU时候适用的trick
-
assign语句左边的信号必须为wire类型,always中被复制的信号必须是reg类型
-
用#来说明参数
module mux2 #(parameter width = 8) (out, s, d0, d1); -
mux2_tb是上面对应的测试文件,其中
`timescale 10ns/1ns //预编译命令,时间单位/时延精度 module mux2_tb; ... mux2 #{32} dut(out, s, d0, d1); //实例化 initial $monitor("$s=%d", s); //monitor用于检测指定变量,只要这些变量发生了变化,就会立即显示对应的输出语句 initial begin #5 $display("in=%d;out=%8b", in, out) //显示输出对应信号,自动换行 #5 $stop; end endmodule -
always@(*) 表示所有输入信号都是敏感信号
-
端口说明也可以写在端口列表中
-
也可以用名称关联写法来实例化
mux2 #(.width(width)) muxh(.out(t2), .s(s[0]), .d0(d2), .d1(d3));

449

被折叠的 条评论
为什么被折叠?



