中科大OJ Verilog 在线评测题解 100-105

近跟着老师学习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

输出格式

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

代码

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

  • 32
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
这道题目的大致思路如下: 1. 首先读入原有文件中的数据,并将其保存到一个数组中; 2. 读入要插入的数据,并将其插入到数组中相应的位置; 3. 将数组中的数据写回到原有文件中。 下面是一个可能的实现: ```c++ #include <iostream> #include <fstream> #include <vector> using namespace std; struct Record { int id; string name; int age; }; int main() { // 读入原有文件中的数据 vector<Record> records; ifstream fin("data.txt"); if (fin.is_open()) { int id, age; string name; while (fin >> id >> name >> age) { records.push_back({id, name, age}); } fin.close(); } // 读入要插入的数据 Record new_record; cin >> new_record.id >> new_record.name >> new_record.age; // 将新数据插入到数组中相应的位置 int pos = -1; for (int i = 0; i < records.size(); ++i) { if (records[i].id > new_record.id) { pos = i; break; } } if (pos == -1) { records.push_back(new_record); } else { records.insert(records.begin() + pos, new_record); } // 将数组中的数据写回到原有文件中 ofstream fout("data.txt"); if (fout.is_open()) { for (const auto& record : records) { fout << record.id << " " << record.name << " " << record.age << "\n"; } fout.close(); } return 0; } ``` 其中,我们定义了一个 `Record` 结构体来表示每一条记录,然后使用一个 `vector` 来保存所有的记录。在读入原有文件中的数据时,我们使用了文件读取流 `ifstream`,在写回到文件中时,我们使用了文件写入流 `ofstream`。读入要插入的数据时,我们直接使用标准输入流 `cin`。 在将新数据插入到数组中时,我们首先需要找到相应的位置。这里我们使用了一种简单的方法,即遍历数组,找到第一个 ID 大于新数据 ID 的位置,然后将新数据插入到该位置。如果没有找到这样的位置,说明新数据 ID 是最大的,我们将其追加到数组末尾即可。在将新数据插入到数组中时,我们使用了 `vector` 的 `insert` 方法。 最后,我们将数组中的数据写回到原有文件中。在写回到文件中时,我们使用了 `ofstream` 的输出流运算符 `<<`。由于每条记录都需要以一行的形式写入文件,因此我们在输出时需要加上换行符 `\n`。 希望这个解答能够帮助到你!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值