计算机组成与设计实训-用 Verilog HDL 玩转计算机硬件系统设计(头歌实践教育平台) 学习过程记录

Verilog(知识&实验)

Author: Peter Han

计算机组成与设计实训-用 Verilog HDL 玩转计算机硬件系统设计 (educoder.net)

// Verilog HDL 模块的模板(仅考虑用于逻辑综合的程序)
module<顶层模块名>(<输入输出端口列表>);
	output 输出端口列表;
	input 输入端口列表;
	//(1)使用assign语句定义逻辑功能
		wire<结果信号名>;
		assign<结果信号名>=表达式;
	//(2)使用always块定义逻辑功能
	always@(<敏感信号表达式>)
		begin
			//过程赋值语句
			//if语句
			//case语句
			//while,repeat,for循环语句
			//task,function调用
		end
endmodule
	 //(3)元件例化

assign

赋值语法以关键字assign开头,后面是信号名,可以是单个信号,也可以是不同信号网的连接。驱动强度和延迟是可选的,主要用于数据流建模,而不是综合到实际硬件中。右侧的表达式或信号被分配给左侧的网或网的表达式。

语法结构如下:

assign  = [drive_strength] [delay] 

延迟值对于指定门的延迟很有用,并用于模拟实际硬件中的时序行为,因为该值决定了何时应该用评估值分配网。

使用 assign 语句时, 需要遵循一些规则:

  • LHS(左值) 应该始终是wire类型的标量或向量网络, 或者标量或矢量网络的串联, 而绝对不能是reg类型的标量或矢量寄存器。
  • RHS 可以包含标量或向量寄存器以及函数调用。
  • 只要 RHS 上的任何操作数的值发生变化, LHS 就会使用新值进行更新。
  • assign 语句也称为连续赋值, 并且始终处于活动状态

reg类型的变量不能使用assign进行连续赋值,这是因为reg类型的变量可以存储数据,并且不需要连续驱动。我们只能在initial以及always块内对reg类型变量进行赋值。

always

在满足always后面括号内的条件时,执行always内的代码。

always时间控制:沿触发或电平触发

单个信号或多个信号(中间用关键词or连接)
1、沿触发

功能:常用于描述时序行为。e.g.有限状态机

2、电平触发

功能:常用于描述组合逻辑行为

关于always的一些说明
1、一个模块中可有多个always块,并行运作
2、若always块可综合,则表示的是某种结构;若always块不可综合,则表示的电路结构的行为

需要注意的地方
1、assign不能在always中使用
2、assign和实例应用可独立于过程块存在

在Verilog中,设计组合逻辑和时序逻辑时,都要用到always:

always @(*) 
    //组合逻辑 if(a > b) out = 1; else out = 0; always @(posedge clk) 
    //时序逻辑 flip-flop触发器 if(en) out <= in;

注:若敏感信号表达式为“*”,则表示所有输入信号有变化时都触发。

逻辑移位和算术移位

在verilog中,逻辑移位与算术移位的右移符号分别为“>>”和“>>>”,左移同理。

两者的区别在于:逻辑移位不考虑符号位,左移和右移都只补零;算术移位考虑符号位,左移补零,右移补符号位。

reg

①在物理结构上相对比较麻烦,左端有一个输入端口D,右端有一个输出端口Q, 并且 reg 型存储数据需要在CLK(时钟)沿的控制下完成,在 Verilog HDL 描述时也相对麻烦。在对reg型变量进行赋值时,必须在 always 块内完成。

②reg型变量是最常用的variable型变量,variable型变量必须放在过程语句(如initial、 always) 中,通过过程赋值语句赋值;在always、initial等过程块内被赋值的信号也必须定义成variable型。variable型变量(reg)并不意味着一定对应着硬件上的一个触发器或寄存器等存储元件,在综合器进行综合时,综合器会根据具体情况确定是映射成连线还是映射为存储元件(触发器或寄存器)。

reg[31:0] q;

“!“和”~”

“!”表示逻辑取反,“~”表示按位取反

“{}”

{}的基本使用是两个,一个是拼接,一个是复制。

  • { }表示拼接,{第一位,第二位…};
  • {{ }}表示复制,{4{a}}等同于{a,a,a,a};
    所以{13{1‘b1}}就表示将13个1拼接起来,即13’b1111111111111。

posedge/negedge

  • posedge:上升沿
  • negedge:下降沿
  • posedge or negedge:时钟信号有变化

用法:

// 1.
always @ (negedge clk) begin
// 2.
always @ (posedge clk) begin
// 3.
always @ (posedge clk or negedge clrn) begin

clk/clrn

clk为时钟信号输入端,上升沿触发;clrn为异步清零信号输入端,clrn=0时清零。

e.g.

module dffe 32 (d, clk, clrn, e, q);

case

case(控制表达式/值)

分支表达式:执行语句

default:执行语句

endcase

自上而下,按照顺序逐个对分支表达式进行判断,如果这一分支表达式等于控制表达式的值,就执行其对应操作;均不相等时,执行default操作。

e.g.

case(s)
	2'b00: select = a0;
	2'b01: select = a1;
	2'b10: select = a2;
	2'b11: select = a3;
endcase
  1. case语句中,敏感表达式中与各项值之间的比较是一种全等比较,每一位都相同才认为匹配。

  2. casez语句中,如果分支表达式某些位的值为高阻z,那么对这些位的比较就会忽略,不予考虑,而只关注其他位的比较结果。

  3. casex语句中,则把这种处理方式进一步扩展到对x的处理,即如果比较双方有一方的某些位的值是z或x,那么这些位的比较就不予考虑。

数据表示

计算位宽时,都要转成二进制来计算,因为时序元件只能存储二值电平。

对于4’h0,由于是这里的0是十六进制的,它表示二进制的4’b0000,所以是四位的。

对于16’h4012,每个数字表示4位,一共4个数字,所以一共是4*4=16位。

总结:$ (num1)'(letter)(num2)$:

  • num1:表示位宽

  • letter:一个字母,代表不同进制

    • b:二进制
    • h:十六进制
  • num2:表示具体的数据

= 与 <=

非阻塞(Non_Blocking)赋值方式( 如 b <= a; ),块结束后才完成赋值操作,值并不是立刻就改变的,这是一种比较常用的赋值方法。(特别在编写可综合模块时)。

阻塞(Blocking)赋值方式( 如 b = a; ),赋值语句执行完后块才结束,值在赋值语句执行完后立刻就改变的,可能会产生意想不到的结果。

一般情况下组合逻辑使用=赋值,时序逻辑使用<=赋值。

function

函数通过关键词function和endfunction定义,语法如下:

function [range] function_id;
	input_declaration
	other_declarations
	procedural_statement
endfunction

其中,function 语句标志着函数定义结构的开始;[range]参数指定函数返回值的类型或位宽,是一个可选项,若没有指定,默认缺省值为 1 比特的寄存器数据;function_id 为所定义函数的名称,对函数的调用也是通过函数名完成的,并在函数结构体内部代表一个内部变量,函数调用的返回值就是通过函数名变量传递给调用语句;input_declaration 用于对寒暑各个输入端口的位宽和类型进行说明,在函数定义中至少要有一个输入端口;endfunction为函数结构体结束标志。

inout

在Verilog中,inout是一个双向接口(区别于input与output),也是一个可综合语句,广泛的应用于比如I2C等协议的设计中,声明inout端口,意味着数据既可以从主设备流向从设备,也可以从从设备流向主设备。

  • inout端口默认为wire型,这意味着我们不能在always中对其进行赋值,而需要使用assign进行赋值。

  • 每一个inout端口都需要一个reg型的buffer来做缓冲器。

全加器设计

一位全加器的真值表:

输入 输入 输入 输出 输出
A i A_i Ai B i B_i Bi C i C_i Ci S i S_i Si C i + 1 C_{i+1} Ci+1
0 0 0 0 0
0 0 1 1 0
0 1 0 1 0
0 1 1 0 1
1 0 0 1 0
1 0 1 0 1
1 1 0 0 1
1 1 1 1 1

故可以根据真值表和逻辑表达式写出Verilog代码:

module fa_behavioral(a,b,ci,s,co);//考虑进位的加法器模块 
       input a,b;
       input ci;
       output  s;
       output co;
// 请在下面添加代码,完成一位全加器功能
/* Begin */
assign s = a ^ b ^ ci;
assign co = (a & b) + ((a ^ b) & ci);
/* End */

endmodule

无符号二进制数加法器的实现

设计一个n=8位的无符号二进制数加法器。

输入:a(8),b(8),cin(1)(低位进位)。

输出:sum(8),cout(1)(向高位进位)。 功能:sum=a+b+cin。

module adder(a,b,cin,cout,sum);
  parameter bit_width=8;
  output[bit_width-1:0] sum;
  output cout;
  input [bit_width-1:0] a,b;
  input cin;
// 请在下面添加代码,完成n=8位的无符号二进制数加法器功能
/* Begin */
assign {cout, sum} = a + b + cin;//加法模块
/* End */
endmodule

代码含义: 这里的{cout, sum}表示将a+b+cin的结果拆为两部分,高位作为cout的结果,低位作为sum的结果。

减法运算器

设计一个n位的无符号二进制数减法器。

module substractor(a,b,cin,cout,sum);
  parameter bit_width=8;
  output[bit_width-1:0] sum;
  output cout;
  input [bit_width-1:0] a,b;
  input cin;//carry
// 请在下面添加代码,完成n位的无符号二进制数减法器功能
/* Begin */
assign {cout, sum} = a - b - cin;
/* End */
endmodule

原理同上

定点二进制数的补码加减法运算器

module add_sub(a,b,control,cout,overflow,sum);
  parameter bit_width=4;
  output[bit_width-1:0] sum;     output cout,overflow;
  input [bit_width-1:0] a,b;         input control;//carry
  reg overflow,cout;                      reg [bit_width-1:0] sum;
  reg [bit_width:0] a2,b2,sum2;  
   // 请在下面添加代码
        /********** Begin *********/
        always@(a)
        /********** End *********/
    begin
       a2[bit_width]=a[bit_width-1];    //将a符号位扩展成2位并赋值给a2
       a2[bit_width-1:0]=a[bit_width-1:0];
      // 请在下面添加代码,将b符号位扩展成2位并赋值给b2
        /********** Begin *********/
        b2[bit_width]=b[bit_width-1];
        b2[bit_width-1:0]=b[bit_width-1:0];
        /********** End *********/ 
    if (control==0) {cout,sum2}=a2+b2;
    else  {cout,sum2}=a2+(~b2)+control;	 
    if((sum2[bit_width]^sum2[bit_width-1])==1)   overflow=1;    
    else overflow=0;   //用双符号位判溢出
     sum[bit_width-1:0]=sum2[bit_width-1:0];   
  end           
endmodule

设计一个3位二进制优先编码器

功能:优先编码器允许同时输入两个以上编码信号,并按照事先规定的优先级别,对优先权最高的一个输入信号进行编码。

//设计一个输入输出均为高电平有效的3位二进制优先编码器
//I[7]的优先权最高,I[0]的优先权最低
module encoder8_3_test(I,Y);
input [7:0] I;
output reg [2:0] Y;

// 请在下面添加代码,完成设计任务
/* Begin */

wire[7:0] I;
// reg [2:0] Y;
always@(*)
begin
if(I[7]==1) Y = 3'b111;
    else if(I[6]==1) Y =3'b110;
    else if(I[5]==1) Y =3'b101;
    else if(I[4]==1) Y =3'b100;
    else if(I[3]==1) Y =3'b011;
    else if(I[2]==1) Y =3'b010;
    else if(I[1]==1) Y =3'b001;
    else if(I[0]==1) Y =3'b000;
    else Y = 3'b000;
end

/* End */
endmodule

代码含义: 使用if…else if…else语句进行选择判断。谁的优先级高就先输出谁给Y。

设计一个3线-8线译码器

译码是编码的逆过程,3线-8线译码器可以将n位二进制代码可译成2n种电路状态。

真值表:

E A2 A1 A0 Y0 Y1 Y2 Y3 Y4 Y5 Y6 Y7
0 X X X 0 0 0 0 0 0 0 0
1 0 0 0 1 0 0 0 0 0 0 0
1 0 0 1 0 1 0 0 0 0 0 0
1 0 1 0 0 0 1 0 0 0 0 0
1 0 1 1 0 0 0 1 0 0 0 0
1 1 0 0 0 0 0 0 1 0 0 0
1 1 0 1 0 0 0 0 0 1 0 0
1 1 1 0 0 0 0 0 0 0 1 0
1 1 1 1 0 0 0 0 0 0 0 1
//设计具有一位使能端的3线-8线译码器。当使能端为0时,8位输出信号全为0;
//如果一位使能信号为1,则输出高电平有效的译码信号。
module decoder3e_test(a,ena,y);
  input [2:0] a;
  input ena;
  output [7:0] y; 
  reg [7:0] y;
  // 请在下面添加代码,完成设计任务
/* Begin */
  always @ (a or ena)
    begin
        if(ena == 1)
            case(a)
               3'b000: y = 8'b00000001; 
               3'b001: y = 8'b00000010; 
               3'b010: y = 8'b00000100; 
               3'b011: y = 8'b00001000; 
               3'b100: y = 8'b00010000; 
               3'b101: y = 8'b00100000; 
               3'b110: y = 8'b01000000; 
               3'b111: y = 8'b10000000; 
               default: y = 8'b00000000;
            endcase
        else
            y = 8'b00000000;
    end
/* End */
endmodule

代码含义: 根据输入的a和ena的值和真值表中的对应关系,决定输出结果y的取值。

编写一个异步清零带写使能端的32位寄存器

 module dffe32(d,clk,clrn,e,q);
    input [31:0] d;
    input clk,clrn,e;
    output [31:0] q;
   //begin
   reg[31:0] q;
    always @(posedge clk or negedge clrn)
        begin
            if(clrn == 0)
                q = 0;
            else if (e == 1)
                q = d;
            
        end
    //end
endmodule

异步清零带写使能端的32位寄存器能实现异步(不受时钟的控制)清零功能。

当写使能输入端e=1,在时钟信号的上升沿到来时,将32位的输入数据d写入寄存器。
32位的数据输入端d,用于写入数据;
e为写使能输入端,e=1执行写操作;
clk为时钟信号输入端,上升沿触发;
clrn为异步清零信号输入端,clrn=0时清零。

代码含义: 在上升沿和下降沿的时候,执行always内的操作。如果符合变化的条件,就根据clrn和e的取值进行判断。clrn为异步清零信号输入端,clrn=0时清零,所以此时应将输出的结果置0。如果e为1,即上升沿信号到来时,将d,也就是输入的内容写入寄存器。

设计一个3-8译码器电路

module decoder3e (n,ena,e);
	input [2:0] n;
	input       ena;
	output [7:0] e;
    // 请利用always结构说明语句填写代码,完成3-8译码器功能
        /********** Begin *********/
        reg[7:0] e;
            always @(ena or n) begin
            e = 8'b0;
            e[n] = ena;
            end
        /********** End *********/
endmodule

代码含义: input的是3位的n,output的是8位的e。因其对应的真值表为:

Input Input Output
ena n[2:0] e[7:0]
1 000 00000001
1 001 00000010
1 010 00000100
1 011 00001000
1 100 00010000
1 101 00100000
1 110 01000000
1 111 10000000
0 xxx 00000000

所以在ena或n的值发生的变化时,应当执行always内的代码。首先将e定义为00000000,在根据input的两个值来决定结果。如ena为1,则需要看n的取值,根据n的取值确定output中添加数字1的位置。

32位移位器设计

要求:设计一个如图所示的32位移位器shift,能对32位二进制数进行左移(sll)、逻辑右移(srl)、算数右移(sra),移位位数可在0~31之间自由选择。输入输出端口设计要求如下:

d:32位二进制数输入端

sa:5位移位位数输入端

right:右移控制端,高电平有效。right=1时,进行右移;right=0时,进行左移

arith:算术移位控制端,高电平有效。arith=1时,进行算术移位;arith=0时,进行逻辑移位 sh:32位数据输出端

module shift (d,sa,right,arith,sh);
input [31:0] d;
input [4:0] sa;
input right,arith;
reg [31:0] ah;
output reg [31:0] sh;

        /********** Begin *********/
        always @(*)
        begin 
        if(right&&arith)
        begin 
        if(d&32'h8000_0000) // 比较符号
        begin 
        ah=32'hffff_ffff;
        ah=ah<<(32-sa); // sa:移了多少位数
        sh=ah|(d>>sa); // d>>sa:逻辑移位
        end
        else sh = d>>sa;
        end
        else if(!right&&arith) sh=d<>sa;
        end
        
        /********** End *********/
endmodule

代码含义: 通过if…else if…else语句实现分情况讨论。

如果是左移,则都是在低位补0;如果是右移,则要分为算术移位和逻辑移位。算术移位高位补符号,逻辑移位高位补0。

如果只是补0的情况,则可以直接移位,否则应单独存储符号(通过将原数的二进制形式与8000_0000做与运算),根据需要移动的位数确定前面多少位填充符号位的数值,再将1…10…0与逻辑移位的结果相与即可。

带符号数乘法器设计

要求:设有2个补码表示的8位带符号数a和b,试计算z=a*b,完成带符号数乘法器的设计。

module mul_signed(a,b,z);
	input [7:0] a,b;
	output [15:0] z;
	wire [7:0] ab0=b[0]?a:8'b0;
	wire [7:0] ab1=b[1]?a:8'b0;
	wire [7:0] ab2=b[2]?a:8'b0;
	wire [7:0] ab3=b[3]?a:8'b0;
	wire [7:0] ab4=b[4]?a:8'b0;
	wire [7:0] ab5=b[5]?a:8'b0;
	wire [7:0] ab6=b[6]?a:8'b0;
	wire [7:0] ab7=b[7]?a:8'b0;
    // 请补全下面的代码,完成带符号数乘法器的设计
        /********** Begin *********/
assign z = (
({8'b1,~ab0[7],ab0[6:0]}+{7'b0,~ab1[7],ab1[6:0],1'b0})+
({6'b0,~ab2[7],ab2[6:0],2'b0}+{5'b0,~ab3[7],ab3[6:0],3'b0})+
({4'b0,~ab4[7],ab4[6:0],4'b0}+{3'b0,~ab5[7],ab5[6:0],5'b0})+
({2'b0,~ab6[7],ab6[6:0],6'b0}+{1'b1,ab7[7],~ab7[6:0],7'b0})
);
        /********** End *********/
	
endmodule

代码含义: z = a × b z=a×b z=a×b

assign表示一个信号,这里的乘法运算是借助 ( a b ) n (ab)_n (ab)n来实现的,算出来每一位的乘积后再相加。

这里的大括号表示拼接。本质上说就是分别计算每一位上的乘积结果,把它们加在一起构成z。

4位算术逻辑运算器的实现

要求:设计实现一个4位算术逻辑运算alu

module alu(x, y,instruction,overflow,result);
	parameter bit_width=4;
	input [bit_width-1:0]x,y;
	input [2:0] instruction;
	output overflow;
	output [bit_width-1:0] result;
	reg [bit_width:0] temp;
	reg [bit_width-1:0] result;
	reg overflow;
	initial 	
     overflow=0;
  always@ (x or y or instruction)
	begin  case (instruction)
		  3'b001:begin temp = {x[bit_width-1], x}+{y[bit_width-1],y};
			     result <= temp[bit_width-1 :0]; 
			     overflow <= temp[bit_width] ^ temp[bit_width-1];
			   end  // 当输入为001的情况时的加功能为:result<=x+y;
  
        /********** Begin *********/
		 3'b010:begin temp = {x[bit_width-1], x}-{y[bit_width-1],y};
			     result <= temp[bit_width-1:0]; 
			     overflow <= temp[bit_width] ^ temp[bit_width-1];
			   end
        /********** End *********/  // 请补全上面为*的代码,实现当输入为010的情况时的减功能为:result<=x-y;
		  
		  3'b011:begin  result <= x&y; end
		  3'b100:begin  result <= x|y; end
		  3'b101:begin  result <= x^y; end
		  3'b110:begin  result <={1'b0,x[bit_width-1:1]}; end //实现逻辑右移1位

		  3'b111:begin  result <= {x[bit_width-2:0],1'b0}; end //补全该行代码,实现逻辑左移1位。
		  default:begin result <= 0; overflow <=0; end
		endcase   
       end   
endmodule

类似上一题的思路,此处思路略。

注:

  • 000:空操作:result = 0
  • 001:算术加法:result = x + y
  • 010:算术减法:result = x - y
  • 011:逻辑与:result = x AND y
  • 100:逻辑或:result = x OR y
  • 101:逻辑异或:result = x XOR y
  • 110:将x逻辑右移1位:result = srl x
  • 111:将x逻辑左移1位:result = sll x

编写能对32位二进制数进行算术和逻辑运算的ALU

注:参加运算的两个32位数据分别为a[31:0]和b[31:0],运算器的功能由控制信号(即操作码)aluc[3:0]决定,r[31:0]为输出结果,z为运算后的零标志位。

控制信号aluc ALU功能 控制信号aluc ALU功能
*000 算术加法 *001 逻辑与
*010 逻辑异或 *100 算术减法
*101 逻辑或 *110 将b逻辑左移16位
0011 将b逻辑左移a[4:0]位 0111 将b逻辑右移a[4:0]位
1111 将b算术右移a[4:0]位 - -
 module alu(a,b,aluc,r,z);
	input [31:0] a,b;
	input [3:0] aluc;
	output [31:0] r;
	output z;
	
    //begin
    assign r = cal (a,b,aluc);
    assign z =~|r;

    function [31:0] cal;
	   input [31:0] a,b;
	   input [3:0]  aluc;
	   casex (aluc)
	   //0 4 1 5 2 6 3 7 F
			4'bx000: cal =a + b;
			4'bx100: cal =a - b;
			4'bx001: cal =a & b;
			4'bx101: cal =a | b;
			4'bx010: cal =a ^ b;
            4'bx110: cal ={b[15:0],16'h0}; // 拼接
			4'bx011: cal =b << a[4:0];
			4'b0111: cal =b >> a[4:0];
            4'b1111: cal =$signed(b) >>> a[4:0]; // 算术移位
		endcase
	endfunction
    
    //end
endmodule

代码含义: 此处的cal函数用来计算r的输出结果。cal函数输入32位的a,b以及4位的aluc。aluc决定当前函数计算的功能。通过使用casex语句来选择不同的情况。casex可以忽略比较双方中为z或x的位,因此在这里选用casex作为选择语句。在不同分支中,实现不同的计算功能。

$signed():声明有符号数;<<和>>:逻辑移位;<<<和>>>:算术移位。

编写有32个32位寄存器的寄存器堆

寄存器堆有32个32位的寄存器、两个读端口rna,rnb和一个写端口wn(每个端口都可以输入一个寄存器号,用于指定一个寄存器)、两个32位的数据输出端qa,qb; 32位的数据输入端d,用于写入数据; we为写使能输入端,we=1执行写操作; clk为时钟信号输入端,上升沿触发; clrn为异步清零信号输入端,clrn=0时清零。 其中,r0寄存器的内容恒为0。

 module regfile(rna,rnb,d,wn,we,clk,clrn,qa,qb);
  input [4:0] rna,rnb,wn;
  input [31:0] d;
  input we,clk,clrn;
  output [31:0] qa,qb;
     //begin
    
        reg [31:0] register [1:31];                                         
    assign qa = (rna == 0) ? 0 : register[rna];                       
    assign qb = (rnb == 0) ? 0 : register[rnb];                    

    //posedge:上沿,negedge:下沿
    always @ (posedge clk or negedge clrn) begin
        if (clrn == 0) begin   //初始化寄存器堆                                        
            integer i;  
            for (i=1; i<32; i=i+1)    
                register[i] <= 0;  //<= 非阻塞赋值
        end else begin 
            if ((wn != 0) && (we == 1))   //we:写使能,wn:write number                               
                register[wn] <= d; //<=非阻塞赋值
        end
    end
    //end
endmodule

代码含义: qa和qb输出的是寄存器读端口rna和rnb的值,如果读端口值为0,则输出0,否则输出寄存器相关的内容。如果clrn为0,即需要清零信号输入端时,借助循环语句将所有的寄存器值置0。否则如果we为1,即执行写操作时,将输入端d的内容写至编号为wn的寄存器中。

设计用于存放32位MIPS指令的指令存储器

功能:只读存储器ROM有一个32位的地址输入端a,用于输入存储单元地址; 一个32位的数据输出端inst,用于输出指令。

要求:设计一个按字节编址的有32位地址输入端和32位数据输出端的指令存储器,其中从地址0开始存放的32条MIPS指令如下所示。

MIPS指令(16进制)

3c010000

34240050

20050004

0c000018

ac820000

8c890000

01244022

20a5ffff

34a8ffff

39085555

2009ffff

312affff

01493025

01494026

01463824

10a00001

08000008

2005ffff

000543c0

00084400

00084403

000843c2

08000017

00004020

8c890000

20840004

01094020

20a5ffff

14a0fffb

00081000

03e00008

module scinstmem (a,inst);
input [31:0] a, t;
output [31:0] inst;
reg [31:0] ROM [31:0];
//begin
    assign ROM[0] = 32'h3c010000;
    assign ROM[1] = 32'h34240050;
    assign ROM[2] = 32'h20050004;
    assign ROM[3] = 32'h0c000018;
    assign ROM[4] = 32'hac820000;
    assign ROM[5] = 32'h8c890000;
    assign ROM[6] = 32'h01244022;
    assign ROM[7] = 32'h20050003;
    assign ROM[8] = 32'h20a5ffff;
    assign ROM[9] = 32'h34a8ffff;
    assign ROM[10] = 32'h39085555;
    assign ROM[11] = 32'h2009ffff;
    assign ROM[12] = 32'h312affff;
    assign ROM[13] = 32'h01493025;
    assign ROM[14] = 32'h01494026;
    assign ROM[15] = 32'h01463824;
    assign ROM[16] = 32'h10a00001;
    assign ROM[17] = 32'h08000008;
    assign ROM[18] = 32'h2005ffff;
    assign ROM[19] = 32'h000543c0;
    assign ROM[20] = 32'h00084400;
    assign ROM[21] = 32'h00084403;
    assign ROM[22] = 32'h000843c2;
    assign ROM[23] = 32'h08000017;
    assign ROM[24] = 32'h00004020;
    assign ROM[25] = 32'h8c890000;
    assign ROM[26] = 32'h20840004;
    assign ROM[27] = 32'h01094020;
    assign ROM[28] = 32'h20a5ffff;
    assign ROM[29] = 32'h14a0fffb;
    assign ROM[30] = 32'h00081000;
    assign ROM[31] = 32'h03e00008;
    
    //只有32条指令,需要先除以4再对32取模
    assign inst = ROM[(a / 4) % 32];
//end

endmodule

代码含义: 使用assign语句将所有的指令填入不同下标的ROM中。通过输入a的值来决定输出的指令。因为a表示的是指令地址,且地址从0开始,故可以通过除以4再对32取模的方式求出对应的下标,达到找出相应指令的目的。

设计一个字长32位,容量256个字的ROM

只读存储器ROM有一个8位的地址输入端address,用于输入存储单元地址;一个1位的片选输入端cs,cs=0时读出数据,cs=1时输出高阻态。一个32位的数据输出端data,用于数据输出。

要求:设计一个按字节编址的有32位地址输入端、1位的片选输入端cs和32位数据输出端的只读存储器ROM,其中从地址0开始存放17条MIPS指令。

module rom(address,cs,data);
       parameter data_width=32;
       parameter addr_width=8;
       input [addr_width-1:0] address;
       input cs;
       output reg [data_width-1:0] data;
//begin
reg [31:0] rom [255:0];

    assign rom[0] = 32'h3c010000;
    assign rom[1] = 32'h34240050;
    assign rom[2] = 32'h20050004;
    assign rom[3] = 32'h0c000018;
    assign rom[4] = 32'hac820000;
    assign rom[5] = 32'h8c890000;
    assign rom[6] = 32'h01244022;
    assign rom[7] = 32'h20050003;
    assign rom[8] = 32'h20a5ffff;
    assign rom[9] = 32'h34a8ffff;
    assign rom[10] = 32'h39085555;
    assign rom[11] = 32'h2009ffff;
    assign rom[12] = 32'h312affff;
    assign rom[13] = 32'h01493025;
    assign rom[14] = 32'h01494026;
    assign rom[15] = 32'h01463824;
    assign rom[16] = 32'h10a00001;
    
    always@(address)
    begin
        data = 32'hzzzzzzzz;
        if (cs == 0)
            data <= rom[address];
    end
//end

endmodule

代码含义: 在address的值发生改变的时候,cs=0时读出数据,cs=1时输出高阻态。

设计用于存放32位数据的数据存储器

随机访问存储器RAM,数据既可以读出,也可以写入。其特点是:存储器的任一存储单元的内容都可以随机存取;访问各存储单元所需的读写时间完全相同,与被访问单元的地址是无关的,可作为数据存储器。

  • we:读写控制端,低电平时进行读操作,高电平时进行写操作。
  • clk:读写时钟脉冲,上升沿触发。
  • datain[31:0]:RAM的32位数据输入端。
  • addr[31:0]:RAM的32位读出和写入地址。
  • dataout[31:0]:RAM的32位数据输出端。

we=1时,进行数据的写入:输入数据和地址准备好以后,当读写时钟脉冲clk的上升沿到来时,数据写入存储单元。 we=0时,完成数据的读出:从addr[31:0]输入存储单元地址,该单元数据从dataout[31:0]输出。

module scdatamem (clk, dataout, datain, addr, we);
input [31:0] datain;
input [31:0] addr;
input clk, we;
output reg [31:0] dataout;
reg [31:0] RAM [0:31];

//--------------begin---------------- 

integer i;
initial begin
    for (i = 0; i < 32; i = i + 1)
        RAM[i] = 0;
    // 从地址50H开始存放数据
    RAM[20] = 32'h000000A3;
    RAM[21] = 32'h00000027;
    RAM[22] = 32'h00000079;
    RAM[23] = 32'h00000115;
end
//地址这里只取2-5位,也就是直接除以了4,故上面的地址编号从20开始存储的
assign dataout = RAM[addr[6:2]];

always @ (posedge clk) begin
    if (we == 1)
        RAM[addr[6:2]] <= datain;
end

//--------------end------------------  


endmodule

代码含义: 从50H开始存放数据,转为十进制为80,80÷4=20,所以从下标为20的位置存储数据,并将之前的置0。在时钟脉冲clk发生变化时,如果we为1,就进行数据的写入操作,将datain的数据存入RAM中。输出数据根据RAM中下标为addr的来。

设计字长8位,容量256个字的随机访问存储器RAM

随机访问存储器RAM随机存取方式工作,根据存储单元的地址,可对其进行读写操作。

随机访问存储器RAM其输入、输出引脚定义和读写操作如下:

we:读写控制端,低电平时进行读操作,高电平时进行写操作。

clk:读写时钟脉冲,上升沿触发。

data: 8位数据输入/输出端。

address: 8位地址。

cs:片选信号。

oe:输出控制(三态)。

  1. cs = 1,we = 1, oe = 0(三态)时,进行数据的写入:输入数据和地址准备好以后,当读写时钟脉冲clk的上升沿到来时,数据写入存储单元。
  2. cs = 1, we = 0, oe = 1时,完成数据的读出:从address输入存储单元地址,当读写时钟脉冲clk的上升沿到来时,该单元数据从data输出。
module ram (  clk         , // Clock Input
             address     , // Address Input
             data        , // Data bi-directional
             cs          , // Chip Select
             we          , // Write Enable/Read Enable
             oe            // Output Enable
  ); 
  parameter DATA_WIDTH = 8 ;   
  parameter ADDR_WIDTH=8;
  parameter RAM_DEPTH=1<

代码含义: (cs, we, oe) = (1, 0, 1)时,完成数据的读入,从address处读出RAM中的值。当读写时钟脉冲clk的上升沿到来时,如果(cs, we, oe) = (1, 1, 0),则将data的值送给RAM。

此处借助inout类型的data,既可以实现数据的读入,也可以实现数据的读出。

你可能感兴趣的:(学习)