近跟着老师学习Verilog,做了中科大的练习题,将答案记录一下
Q62-99
题在哪儿
Q100 寄存器堆模块
题目描述
在RV32I中,寄存器堆指32个通用寄存器的集合,具有专门的读写端口,可并发访问不同寄存器。
我们用5位数代表寄存器的端口号,需要注意的是:当待写入寄存器端口号为0时,往x0写入的数据总是被丢弃,因为x0寄存器恒为0,不能对x0寄存器的值进行修改。设置x0寄存器,既可以提供常量0(比如RISC-V用sub rd, x0, rs来实现neg取负数指令),也可以提供一个可以丢弃结果的场所(比如RISC-V使用addi x0, x0, 0实现nop空指令)。
当A1有意义时,其对应指令中的rs1,即第15到19位;同理,当有意义时,A2对应指令中的rs2,即第20到24位;A3对应指令中的rd,即第7到11位。
输入格式
1.时钟信号clk 2.位宽为5的待读取寄存器端口号A1 3.位宽为5的待读取寄存器端口号A2 4.位宽为5的待写入寄存器端口号A3 5.位宽为32的待写入数据WD 6.位宽为1的寄存器写使能信号WE
输出格式文章来源:https://www.toymoban.com/news/detail-780395.html
1.位宽为32的从A1对应的寄存器中读出的数据RD1 2.位宽为32的从A2对应的寄存器中读出的数据RD2
代码
module top_module(
input clk,
input [4:0] A1,A2,A3,
input [31:0] WD,
input WE,
output [31:0] RD1,RD2
);
reg [31:0] reg_file[0:31];
//初始化寄存器堆
integer i;
initial
begin
for(i=0;i<32;i=i+1) reg_file[i] = 0;
end
//写入寄存器
always@(posedge clk)
begin
/*待填*/
if(WE&A3!=0) //A3==0的情况排除?X0一直为0
reg_file[A3]= WD;
else ;
end
//读取寄存器
assign RD1 = reg_file[A1]/*待填*/;
assign RD2 = reg_file[A2]/*待填*/;
endmodule
Q101 程序计数器模块
题目描述
当执行一条指令时,首先需要根据PC中存放的指令地址,将指令由内存取到指令寄存器中,此过程称为取指。
PC全称ProgramCounter(程序计数器),它是计算机处理器中的寄存器,包含当前正在执行的指令的地址。
一般情况下,当指令被获取,程序计数器存储的地址加四(一条指令有32位,为4字节)。当遇到跳转指令(jal,jalr)或者满足分支跳转指令(所有B类指令)的跳转条件时,则将程序计数器的内容设置为转移指令规定的地址。
输入格式
1.时钟信号clk 2.复位键rst,当rst为1时将PC置零 3.位宽为1的跳转信号JUMP,当JUMP为1时将PC设为JUMP_PC 4.位宽为32的待跳转的地址JUMP_PC
输出格式
位宽为32的当前周期需要执行指令的地址pc
代码
module top_module(
input clk,
input rst,
input JUMP,
input [31:0] JUMP_PC,
output reg [31:0] pc);
wire [31:0] pc_plus4;
assign pc_plus4 = pc + 32'h4;
//计算PC
always@(posedge clk or posedge rst)
begin
/*待填*/
if(rst)
pc<=0;
else if(JUMP)
pc<=JUMP_PC;
else
pc<=pc_plus4;
end
endmodule
Q102 立即数扩展模块
题目描述
通过上图可知,不同类型的指令,立即数的位置不同,我们需要从原始的指令中提取出顺序正确的立即数,并依照指令的类型对立即数做相应扩展。
通过了解各指令的具体作用和指令格式,我们能得到下表:
注:符号位扩展就是对不足32位的数,在高位扩展符号位至32位的操作(符号位是1则填1,符号位为0则填0)
输入格式
位宽为32的指令inst
输出格式
经过扩展的32位立即数out
代码
module top_module(
input [31:0] inst,
output reg [31:0] out
);
wire [6:0] opcode;
assign opcode= inst[6:0];
//立即数扩展
always@(*)
begin
case(opcode)
/*待填*/
7'b0010111:out<={inst[31:12],12'b0};
7'b0110111:out<={inst[31:12],12'b0};
7'b1100011:out<={{20{inst[31]}},inst[7],inst[30:25],inst[11:8],1'b0};
7'b1101111:out<={{12{inst[31]}},inst[19:12],inst[20],inst[30:21],1'b0};
7'b1100111:out<={{12{inst[31]}},inst[19:12],inst[20],inst[30:21],1'b0};
7'b0000011:out<={{20{inst[31]}},inst[31:20]};
7'b0100011:out<={{20{inst[31]}},inst[31:25],inst[11:7]};
7'b0010011:out<={{20{inst[31]}},inst[31:20]};
default:out<=32'h0;
endcase
end
endmodule
Q103 分支判断模块
题目描述
RV32I基础指令集中提供了6条B型指令,分支判断模块负责判断跳转条件是否满足,需要分支跳转时产生分支信号。
如上图,Type是代表不同比较类型的3位编码,其与输入输出的关系如下表:
注:Type并不等价于B类指令中的funct3(功能码),只是控制器处理指令后输出的一个信号。因为beq的funct3为000,而我们在编写代码时,把0作为默认值;当信号为0时,我们不做操作,这样可以有利于增强代码的容错率。
输入格式
1.位宽为32的(从寄存器中读取的)待比较数REG1 2.位宽为32的(从寄存器中读取的)待比较数REG2 3.位宽为3的Type,用于区分两个待比较数比较的类型
输出格式
位宽为1的分支跳转信号BrE
代码
module top_module(
input [31:0] REG1,
input [31:0] REG2,
input [2:0] Type,
output reg BrE
);
wire signed [31:0] signed_REG1;
wire signed [31:0] signed_REG2;
wire unsigned [31:0] unsigned_REG1;
wire unsigned [31:0] unsigned_REG2;
assign signed_REG1 = REG1;
assign signed_REG2 = REG2;
assign unsigned_REG1 = REG1;
assign unsigned_REG2 = REG2;
always@(*)
begin
case(Type)
/*待填*/
3'b010:BrE<=(signed_REG1==signed_REG2?1:0);
3'b011:BrE<=(signed_REG1!=signed_REG2?1:0);
3'b100:BrE<=(signed_REG1<signed_REG2?1:0);
3'b101:BrE<=(signed_REG1>=signed_REG2?1:0);
3'b110:BrE<=(unsigned_REG1<unsigned_REG2?1:0);
3'b111:BrE<=(unsigned_REG1>=unsigned_REG2?1:0);
default:BrE<=1'b0;
endcase
end
endmodule
Q104 ALU模块
题目描述
ALU全称Arithmetic and Logic Unit,即算数逻辑单元,是数字计算机中执行加、减等算术运算,执行与、或等逻辑运算,以及执行比较、移位、传送等操作的功能部件。
ALU模块如上图,其中func是区分操作类型的4位编码,其与输入输出的对应如下表:
注:上表只是一种设计,设计不唯一
输入格式
1.位宽为32的源操作数SrcA 2.位宽为32的源操作数SrcB 3.位宽为4的func,用于区分操作类型
输出格式
位宽为32的运算结果ALUout
代码
module top_module(
input [31:0] SrcA,SrcB,
input [3:0] func,
output reg [31:0] ALUout
);
wire signed [31:0] signed_a;
wire signed [31:0] signed_b;
wire unsigned [31:0] unsigned_a;
wire unsigned [31:0] unsigned_b;
assign unsigned_a = SrcA;
assign unsigned_b = SrcB;
assign signed_a = SrcA;
assign signed_b = SrcB;
always@(*)
begin
case(func)
/*待填*/
4'b0000:ALUout<=signed_a+signed_b;
4'b1000:ALUout<=signed_a-signed_b;
4'b0001:ALUout<=signed_a<<signed_b[4:0];
4'b0010:ALUout<=signed_a<signed_b?1:0;
4'b0011:ALUout<=unsigned_a<unsigned_b?1:0;
4'b0100:ALUout<=signed_a^signed_b;
4'b0101:ALUout<=signed_a>>signed_b[4:0];
4'b1101:ALUout<=signed_a>>>signed_b[4:0];
4'b0110:ALUout<=signed_a|signed_b;
4'b0111:ALUout<=1'b0;
4'b1001:ALUout<=1'b0;
4'b1010:ALUout<=1'b0;
4'b1011:ALUout<=1'b0;
4'b1100:ALUout<=1'b0;
4'b1110:ALUout<=signed_b;
4'b1111:ALUout<=1'b0;
endcase
end
endmodule
Q105 存储器
题目描述
存储器模块包括指令存储器和数据存储器,其中指令存储器用于存放指令,只读;数据存储器用于存放数据,可读可写;指令存储器按字节编址,按字读取;数据存储器按字节编址,可以完成对字、半字、字节的对齐存取。
因为指令存储器的容量有限(本实验中为16KB,可存储4K条指令),指令长度为32bit(即4B),因此为了判断地址是否有效,我们需要判断im_addr的31至14位以及1至0位是否为0。当im_addr有效时,im_dout输出地址对应的指令,否则输出全0。
区分读取类型的3位信号dm_rd_ctrl与读取类型的关系如下:
区分写入类型的2位信号dm_wr_ctrl与写入类型的关系如下:
数据写入时,由于需要对齐,数据不能跨字(即不能跨越两个32bit的单元)。在代码中,我们采用了四位的辅助信号byte_en,对于待覆写的32bit的单元mem[dm_addr[13:2]],我们可以把其看作四个一字节(8bit)宽度的单位,对应byte_en的四位,对应位为1表示该字节需要覆写,反之不需要,具体如下:
本系列实验使用哈佛体系结构设计,指令和数据分开存储,但地址空间是重叠的,所以设计verilog代码时可以合并成一个模块。
输入格式
1.位宽为32bit的地址im_addr 2.位宽为32bit的地址dm_addr 3.位宽为32bit的写入数据dm_din 4.区分读取类型的3位信号dm_rd_ctrl 5.区分写入类型的2位信号dm_wr_ctrl;
输出格式
1.位宽为32bit的指令im_dout 2.位宽为32bit的读出数据dm_dout文章来源地址https://www.toymoban.com/news/detail-780395.html
代码
module top_module(
input clk,
input [31:0] im_addr,
output [31:0] im_dout,
input [2:0] dm_rd_ctrl,
input [1:0] dm_wr_ctrl,
input [31:0] dm_addr,
input [31:0] dm_din,
output reg [31:0] dm_dout
);
reg [3:0] byte_en;
reg [31:0] mem[0:4095];
reg [31:0] mem_out;
integer i;
initial
begin
for(i=0;i<4095;i=i+1) mem[i] = 0;
end
initial
begin
$readmemh("./problem/inst.dat",mem);
end
assign im_dout = ((|im_addr[31:14]==0) &(|im_addr[1:0]==0))?mem[im_addr[13:2]]:32'b0;/*待填*/
//由于不能跨单位读取数据,地址最低两位的数值决定了当前单位能读取到的数据,即mem_out
always@(*)
begin
case(dm_addr[1:0])
2'b00: mem_out = mem[dm_addr[13:2]][31:0];
2'b01: mem_out = {8'h0,mem[dm_addr[13:2]][31:8]};
2'b10: mem_out = {16'h0,mem[dm_addr[13:2]][31:16]};
2'b11: mem_out = {24'h0,mem[dm_addr[13:2]][31:24]};
endcase
end
always@(*)
begin
case(dm_rd_ctrl)
/*待填*/
3'b001: dm_dout = {{24{mem_out[7]}},mem_out[7:0]};
3'b010: dm_dout = {24'h0,mem_out[7:0]};
3'b011: dm_dout = {{16{mem_out[15]}},mem_out[15:0]};
3'b100: dm_dout = {16'h0,mem_out[15:0]};
3'b101: dm_dout = mem_out;
default: dm_dout=32'b0;
endcase
end
always@(*)
begin
if(dm_wr_ctrl == 2'b11)
byte_en = 4'b1111;
else if(dm_wr_ctrl == 2'b10)
begin
if(dm_addr[1] == 1'b1)
byte_en = 4'b1100;
else
byte_en = 4'b0011;
end
else if(dm_wr_ctrl == 2'b01)
begin
case(dm_addr[1:0])
2'b00: byte_en = 4'b0001;
2'b01: byte_en = 4'b0010;
2'b10: byte_en = 4'b0100;
2'b11: byte_en = 4'b1000;
endcase
end
else
byte_en = 4'b0000;
end
always@(posedge clk)
begin
if((byte_en != 1'b0)&&(dm_addr[30:12]==19'b0))
begin
case(byte_en)
/*待填*/
4'b0001: mem[dm_addr[13:2]][7:0] =dm_din[7:0];
4'b0010: mem[dm_addr[13:2]][15:8] =dm_din[15:8];
4'b0100: mem[dm_addr[13:2]][24:16] =dm_din[24:16];
4'b1000: mem[dm_addr[13:2]][31:17] =dm_din[31:17];
4'b0011: mem[dm_addr[13:2]][15:0] =dm_din[15:0];
4'b1100: mem[dm_addr[13:2]][31:16] =dm_din[31:16];
4'b1111: mem[dm_addr[13:2]] =dm_din;
default: ;
endcase
end
end
endmodule
到了这里,关于中科大OJ Verilog 在线评测题解 100-105的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!