西工大计组实验——流水线CPU并处理数据冒险

流水线CPU实验报告

最近看到不少学弟学妹浏览了我的两篇博客
流水线
单周期
此处加个声明,只提供参考,严禁报告直接抄袭!
老师会查重的,不要有侥幸心理,这个实验难度可能有点大,但是很锻炼个人能力,还是自己好好做下吧!
如果有帮到大家求个赞,谢谢!

1.实验要求

在单周期的基础上,实现流水线CPU

并且实现处理数据冒险,控制冒险等

该实验是分层次递进的,分为以下几个阶段

  • 流水线CPU(不考虑冒险)
  • 流水线CPU(EXE级数据冒险1)
  • 流水线CPU(EXE级数据冒险2)
  • 流水线CPU(EXE级数据冒险3)
  • 流水线CPU(J,JAL,JR控制冒险)
  • 流水线CPU(BEQ指令控制冒险)
  • 流水线CPU(BEQ指令引入的ID级数据冒险)

2.实验过程

2.1 基础模块

单周期的cpu部分已经介绍的一些模块,在流水线CPU中已经介绍的很详细了,故此处不多赘述

下面是在流水线CPU中需要增加的模块

2.1.1 IF_ID级流水线寄存器

基本功能

保存当前指令字段和产生的信号

if_id

模块信号

这部分信号比较多,而且输入输出基本相同,和单周期相同的就省略了,底下几个与它差不多,时间比较紧,就不详细写了

信号名描述
s_data_write写寄存器数据选择信号
s_ext扩展信号
s_balu多选器选择信号
aluopalu控制信号
s_num_write写寄存器号
mem_write储存器写使能信号
reg_write寄存器写使能信号
s_npc下地址选择信号
mem_read存储器读信号
rdrd字段
rtrt字段
rsrs字段
Imm2626位立即数
Imm1616位立即数
pc_outpc的输出信号
模块代码
module if_id (
            input if_id_flush,
            input [31:0] pc_4,
            input [1:0] s_npc_in,
            output reg [1:0] s_npc_out,
            input reg_write_in,
            input s_ext_in,
            input [1:0] s_num_write_in,
            input [1:0] s_data_write_in,
            input mem_write_in,
            input mem_read_in,
            output reg mem_read_out,
            output reg reg_write_out,
            output reg s_ext_out,
            output reg [1:0] s_num_write_out,
            output reg [1:0] s_data_write_out,
            output reg mem_write_out,
            input [3:0] aluop_in,
            output reg [3:0] aluop_out,
            input s_b_in,
            output reg s_b_out,
            
              input [31:0] instruction,
              output reg [31:0] pc_4_out,
              output reg [15:0] Imm_16,
              output reg [25:0] Imm_26,
              output reg [4:0] rs,
              output reg [4:0] rd,
              output reg [4:0] rt,
              input clock,
              input reset,
              input if_id_write);
always@(posedge clock , negedge reset)
begin
    if (!reset )
    begin
        pc_4_out <= 32'h0000_0000;
        Imm_16   <= 16'h0000;
        Imm_26   <= {26{1'b0}};
        rs = 5'b00000;
        rt = 5'b00000;
        rd = 5'b00000;
        mem_read_out=0;
        s_npc_out=2'b01;
        reg_write_out=1;
        mem_write_out=0;
        s_num_write_out=2'b00;


    end
    else
    begin
    if(if_id_flush)
    begin
    pc_4_out <= 32'h0000_0000;
        Imm_16   <= 16'h0000;
        Imm_26   <= {26{1'b0}};
        rs = 5'b00000;
        rt = 5'b00000;
        s_num_write_out=2'b00;
        mem_read_out=0;
        s_npc_out=2'b01;
        reg_write_out=1;
        mem_write_out=0;

    end
    else if(if_id_write)
    begin
    s_npc_out=s_npc_in;
        mem_read_out=mem_read_in;
        aluop_out=aluop_in;
        s_b_out=s_b_in;
        reg_write_out=reg_write_in;
        s_ext_out=s_ext_in;
        s_num_write_out=s_num_write_in;
        s_data_write_out=s_data_write_in;
        mem_write_out=mem_write_in;
        pc_4_out = pc_4;
        Imm_16   = instruction[15:0];
        Imm_26   = instruction[25:0];
        rs       = instruction[25:21];
        rt       = instruction[20:16];
        rd       = instruction[15:11];
        end
    end
end

endmodule //if_i

2.1.2 ID_EXE级流水线寄存器

基本功能

保存ID级输出信号和数据

输出EXE级信号和数据

可以看到,在ID级就使用的信号是不需要传入该寄存器的,但是后续还有使用的信号需要传入

id_exe

模块信号
信号名描述
data_1_out读取数值1
data_2_out读取数值2
模块代码
`include "ctrl_encode_def.v"
module id_exe (input [31:0] pc_4,
                input [3:0] aluop_in,
                input s_b_in,
                output reg s_b_out,
                input reg_write_in,
                output reg reg_write_out,
                output reg [3:0] aluop_out,
                input [1:0] s_data_write_in,
                output reg [1:0] s_data_write_out,
                input mem_write_in,
                output reg mem_write_out,
                input mem_read_in,
                output reg mem_read_out,
               input [31:0] data_1,
               input [31:0] data_2,
               input [31:0] Imm_32,
               input [4:0] num_write_in,
               output reg [31:0] pc_4_out,
               output reg [31:0] data_1_out,
               output reg [31:0] data_2_out,
               output reg [31:0] Imm_32_out,
               output reg [4:0] num_write_out,
               input [4:0] rs_in,
               input [4:0] rt_in,
               output reg [4:0] rs_out,
               output reg [4:0] rt_out,
               input clock,
               input reset,
               input id_exe_flush);
    
    always@(posedge clock,negedge reset)
        if (!reset )
        begin
            pc_4_out      = {32{1'b0}};
            data_1_out     = {32{1'b0}};
            data_2_out    = {32{1'b0}};
            Imm_32_out    = {32{1'b0}};
            num_write_out={5{1'b0}}; 
            reg_write_out=0;
            mem_write_out=0;
            s_data_write_out=2'b00;
        end
        else if (id_exe_flush)
        begin
            mem_read_out=0;
            reg_write_out=0;
            // s_b_out=1;
            // s_data_write_out=2'b00;
            aluop_out<=`ALUOp_NOP;
            rs_out={5{1'b0}};
            rt_out={5{1'b0}};
            
            pc_4_out      = {32{1'b0}};
            data_1_out     = {32{1'b0}};
            data_2_out    = {32{1'b0}};
            Imm_32_out    = {32{1'b0}};
            num_write_out={5{1'b0}};
            mem_write_out=0;
            num_write_out=0;
        end
        else
        begin
        mem_read_out=mem_read_in;
            rs_out=rs_in;
            rt_out=rt_in;
            reg_write_out=reg_write_in;
            s_b_out=s_b_in;
            s_data_write_out=s_data_write_in;
            mem_write_out=mem_write_in;
            pc_4_out      = pc_4;
            data_1_out    = data_1;
            data_2_out    = data_2;
            Imm_32_out    = Imm_32;
            num_write_out = num_write_in;
            aluop_out=aluop_in;
            
        end
    
endmodule //id_exe

2.1.3 EXE_MEM级流水线寄存器

基本功能

和之前差不多

在这里插入图片描述

模块信号

和前面基本一样

模块代码
module exe_mem (input [31:0] pc_4,
                input [31:0] alu_in,
                input [31:0] data_in,
                input [1:0] s_data_write_in,
                input mem_write_in,
                input reg_write_in,
                output reg reg_write_out,
                output reg [1:0] s_data_write_out,
                output reg mem_write_out,
                input  mem_read_in,
                output reg mem_read_out,
             
                input [4:0] num_write_in,
                output reg [31:0] pc_4_out,
                output reg [31:0] alu_out,
                output reg [31:0] data_out,
                output reg [4:0] num_write_out,
                input clock,
                input reset);
    
    always@(posedge clock,negedge reset)
        if (!reset)
        begin
            pc_4_out      = {32{1'b0}};
            alu_out       = {32{1'b0}};
            data_out      = {32{1'b0}};
            num_write_out = {5{1'b0}};
            mem_read_out=0;
            mem_write_out=0;
            reg_write_out=0;
            s_data_write_out=2'b00;
        end
        else
        begin
        mem_read_out=mem_read_in;
            reg_write_out=reg_write_in;
            s_data_write_out=s_data_write_in;
            mem_write_out=mem_write_in;
            pc_4_out      = pc_4;
            alu_out       = alu_in;
            data_out      = data_in;
            num_write_out = num_write_in;
            
        end
    
endmodule //exe_mem

2.1.4 MEM_WB级流水线寄存器

基本功能

和之前一样

在这里插入图片描述

模块信号

基本差不多

模块代码
module mem_wb (input [31:0] pc_4,
               input [31:0] alu_in,
               input [31:0] mem_in,
               input [4:0] num_write_in,
               input [1:0] s_data_write_in,
               input reg_write_in,
               output reg reg_write_out,
               output reg [1:0] s_data_write_out,
               output reg [31:0] pc_4_out,
               output reg [31:0] alu_out,
               output reg [31:0] mem_out,
               output reg [4:0] num_write_out,
               input clock,
               input reset);
    always@(posedge clock,negedge reset)
    begin
        if (!reset)
        begin
            pc_4_out      = {32{1'b0}};
            alu_out       = {32{1'b0}};
            mem_out       = {32{1'b0}};
            num_write_out = {5{1'b0}};
            reg_write_out=0;
            s_data_write_out=2'b00;
        end
        else
        begin
            reg_write_out=reg_write_in;
            s_data_write_out=s_data_write_in;
            pc_4_out      = pc_4;
            alu_out       = alu_in;
            mem_out       = mem_in;
            num_write_out = num_write_in;
        end
    end
    
endmodule //mem_w

2.1.5 SIDE_ROAD旁路信号单元

基本功能

实现输出旁路信号,正确选择源数据

在这里插入图片描述

模块信号
信号名描述
s_forwardA2读数据选择源A
s_forwardB2读数据选择源B
s_forwardA3ALU旁路选择数据源A
s_forwardB3ALU旁路选择数据源B
模块代码
`include "ctrl_encode_def.v"
module side_road (input [4:0] rs,
                  input [4:0] rt,
                  input [4:0] rs_in,
                  input [4:0] rt_in,
                  input [4:0] rd_exe_mem,
                  input [4:0] rd_mem_wb,
                  input [4:0] num_write_out,
                  input reg_write_exe_mem,
                  input reg_write_mem_wb,
                  input reg_write_id_exe,
                  output reg [1:0] s_forwardA3,
                  output reg [1:0] s_forwardB3,
                  output reg [1:0] s_forwardA2,
                  output reg [1:0] s_forwardB2);
    
    wire flag_rs_exe_mem;
    wire flag_rs_mem_wb;
    wire flag_rt_exe_mem;
    wire flag_rt_mem_wb;
    wire flag_rt_write;
    wire flag_rs_write;
    
    
    
    assign flag_rs_exe_mem = (reg_write_exe_mem
    && (rd_exe_mem !== 5'b00000)
    && (rd_exe_mem === rs));
    assign flag_rs_mem_wb = (reg_write_mem_wb
    && (rd_mem_wb!== 5'b00000)
    && rd_mem_wb === rs);
    assign flag_rt_exe_mem = (reg_write_exe_mem
    && (rd_exe_mem !== 5'b00000)
    && (rd_exe_mem === rt));
    assign flag_rt_mem_wb = (reg_write_mem_wb
    && (rd_mem_wb!== 5'b00000)
    && rd_mem_wb === rt);
    assign flag_rt_write=(reg_write_id_exe
    &&(num_write_out!==5'b00000)
    && num_write_out===rt_in);
    assign flag_rs_write=(reg_write_id_exe
    &&(num_write_out!==5'b00000)
    && num_write_out===rs_in);

        
        
   
    always@(*)
    begin
    s_forwardA3=(flag_rs_mem_wb)?
    (flag_rs_exe_mem?`SIDE_ROAD_EXE_MEM:`SIDE_ROAD_MEM_WB):
    (flag_rs_exe_mem?`SIDE_ROAD_EXE_MEM:`SIDE_ROAD_GPR);
    s_forwardB3=(flag_rt_mem_wb)?
    (flag_rt_exe_mem?`SIDE_ROAD_EXE_MEM:`SIDE_ROAD_MEM_WB):
    (flag_rt_exe_mem?`SIDE_ROAD_EXE_MEM:`SIDE_ROAD_GPR);

    s_forwardA2=(flag_rs_write)?`SIDE_ROAD_MEM_WB:`SIDE_ROAD_GPR;
    s_forwardB2=(flag_rt_write)?`SIDE_ROAD_MEM_WB:`SIDE_ROAD_GPR;
 
        
        
        
    end
    
    
    
endmodule //side_roa

2.1.6 HAZARD 阻塞信号产生单元

基本功能

产生阻塞信号,阻塞流水线,使各数据选择正确,避免数据冒险

在这里插入图片描述

模块信号
信号名描述
pc_writepc写使能信号
if_id_writeif_id级写使能信号
id_exe_flushid_exe级阻塞信号
if_id_flushif_id级阻塞信号
模块代码
module hazard(input mem_read,
              input [4:0] rs,
              input [4:0] rt,
              input [4:0] rt_id_exe,
              output reg pc_write,
              output reg if_id_write,
              output reg id_exe_flush,
	      input reset,
              input clock,
              input [1:0] s_npc,
              output reg if_id_flush);
    
    
    wire flag;
    assign flag = (mem_read)&& (rt_id_exe == rs || rt_id_exe == rt)&&(rt_id_exe!={5{1'b0}});
    assign flag_j=(s_npc==2'b00)||(s_npc==2'b10);
    
    always@(*)
        if (!reset)
        begin
            pc_write     = 1;
            if_id_write  = 1;
            id_exe_flush = 0;
            if_id_flush=0;
        end
        else
        begin
            begin
            if (flag)
            begin
                pc_write     = 0;
                if_id_write  = 0;
                id_exe_flush = 1;
            end
            else
            begin
                pc_write     = 1;
                if_id_write  = 1;
                id_exe_flush = 0;
                
            end

            if(flag_j)
            begin
                if_id_flush=1;
            end
            else
            begin
                if_id_flush=0;
            end
        end
    end
    
    
    
endmodule

2.2 冒险控制过程

2.2.1 EXE数据冒险1

发生原因

EXE级需要从寄存器堆读数据,如果读取时数据不是最新数据,就会产生数据冒险

在这里插入图片描述

解决方法

设置旁路,选择合适的alu输入

在这里插入图片描述

以旁路判断条件为

assign flag_rs_exe_mem = (reg_write_exe_mem
    && (rd_exe_mem !== 5'b00000)
    && (rd_exe_mem === rs));
    assign flag_rs_mem_wb = (reg_write_mem_wb
    && (rd_mem_wb!== 5'b00000)
    && rd_mem_wb === rs);
    assign flag_rt_exe_mem = (reg_write_exe_mem
    && (rd_exe_mem !== 5'b00000)
    && (rd_exe_mem === rt));
    assign flag_rt_mem_wb = (reg_write_mem_wb
    && (rd_mem_wb!== 5'b00000)
    && rd_mem_wb === rt);

s_forwardA3=(flag_rs_mem_wb)?
    (flag_rs_exe_mem?`SIDE_ROAD_EXE_MEM:`SIDE_ROAD_MEM_WB):
    (flag_rs_exe_mem?`SIDE_ROAD_EXE_MEM:`SIDE_ROAD_GPR);
    s_forwardB3=(flag_rt_mem_wb)?
    (flag_rt_exe_mem?`SIDE_ROAD_EXE_MEM:`SIDE_ROAD_MEM_WB):
    (flag_rt_exe_mem?`SIDE_ROAD_EXE_MEM:`SIDE_ROAD_GPR);
测试结果及仿真波形

测试代码为

addu $5,$6,$7
sub $8,$9,$5
or $2,$5,$3

or指令发生数据冒险

在时钟周期3,4时发生旁路,数据选择exe级输出和mem级输出

在这里插入图片描述

测试结果运算正确

在这里插入图片描述

2.2.2 EXE数据冒险2

发生原因

寄存器新值是从DM中读出的数据

在这里插入图片描述

解决方法

产生一个阻塞信号,先将流水线阻塞一个周期,再运行

这个阻塞信号将暂停pc改变。将if_id,id_exe控制信号清零

在这里插入图片描述

 else if (id_exe_flush)
        begin
            mem_read_out=0;
            reg_write_out=0;
            // s_b_out=1;
            // s_data_write_out=2'b00;
            aluop_out<=`ALUOp_NOP;
            rs_out={5{1'b0}};
            rt_out={5{1'b0}};
            
            pc_4_out      = {32{1'b0}};
            data_1_out     = {32{1'b0}};
            data_2_out    = {32{1'b0}};
            Imm_32_out    = {32{1'b0}};
            num_write_out={5{1'b0}};
            mem_write_out=0;
            num_write_out=0;
        end
测试结果及仿真波形

测试例子为

lw $5,8($6)

sub $8,$9,$5

sub指令发生数据冒险

在第三个时钟周期,发生阻塞

在这里插入图片描述

2.2.3 EXE数据冒险3

发生原因

寄存器在WB周期结束时才能写入引起的数据冒险

1.课本中的理想化的寄存器堆采用前半个周期写后半个周期读,所以不存在这种数据冒险。

2.实际实现中,寄存器堆在WB级的结束时才写入值,所以add指令在clock5读取的**$5**号寄存器的值是旧值,存在数据冒险。

在这里插入图片描述

解决方法

在id级产生两个旁路信号,选择正确的数据源

在这里插入图片描述

产生条件是

assign flag_rt_write=(reg_write_id_exe
    &&(num_write_out!==5'b00000)
    && num_write_out===rt_in);
    assign flag_rs_write=(reg_write_id_exe
    &&(num_write_out!==5'b00000)
    && num_write_out===rs_in);

s_forwardA2=(flag_rs_write)?`SIDE_ROAD_MEM_WB:`SIDE_ROAD_GPR;
s_forwardB2=(flag_rt_write)?`SIDE_ROAD_MEM_WB:`SIDE_ROAD_GPR;
测试结果及仿真波形

测试程序

addu $5,$6,$7
sub $8,$9,$10
or $2,$1,$3
add $11,$12,$5

在第五个时钟周期时,发生旁路,第二个读出数据选则写入gpr的值

在这里插入图片描述

此时数据正确选择

得到正确结果

在这里插入图片描述

2.2.4 J,JAL,JR控制冒险

发生原因

当j型指令确定了跳转地址之后,后面一条指令已经被取值,所以必须增加一个flush信号将其清理

在这里插入图片描述

解决方法

增加一个清零信号

在这里插入图片描述

if(if_id_flush)
    begin
    pc_4_out <= 32'h0000_0000;
        Imm_16   <= 16'h0000;
        Imm_26   <= {26{1'b0}};
        rs = 5'b00000;
        rt = 5'b00000;
        s_num_write_out=2'b00;
        mem_read_out=0;
        s_npc_out=2'b01;
        reg_write_out=1;
        mem_write_out=0;

    end
测试结果及仿真波形

测试例子为

add $4,$8,$9
j abc
or $6,$1,$3
abc:
add $11,$12,$5

在第三个时钟周期,产生了一个flush信号

在这里插入图片描述

这个信号将已经取得的指令清零

在这里插入图片描述

最终,可以观察到pc值正确,正确跳转到相应地址

在这里插入图片描述

与mars中结果一致

在这里插入图片描述

2.2.5 BEQ指令控制冒险

发生原因

beq指令确定下地址在id级,必须把已经取得的指令清零以进行跳转

在这里插入图片描述

解决方法

只需要增加判断即可

当两个寄存器值一样时,zero为1,选择下地址为beq指令计算出的地址

assign flag_j=(s_npc==2'b00)||(s_npc==2'b10)||(s_npc==2'b11&&zero==1);

在这里插入图片描述

同时,也需要将流水线阻塞

if(flag_j&&!flag)
            
            begin
                if_id_flush=1;
            end
            else
            begin
                if_id_flush=0;
            end
测试结果及仿真波形

测试例子

Loop:
add $4,$4,$1
beq $4,$7,Loop_end
sub $8,$8,$8
j Loop


Loop_end:
add $5,$4,$1

改程序是自己编写的一个简单汇编程序,是将寄存器$4的值加一,判断与寄存器$7的大小,如果相等就退出循环,并且把$4的值加一放在寄存器$5

在这里插入图片描述

可以看到,在beq指令发生时,流水线阻塞,产生一个flush信号

如果寄存器的值不一样,则pc值选择pc+4

如果寄存器值一样,则在这个时钟周期计算得到npc

在这里插入图片描述

最终,各寄存器值为

在这里插入图片描述

2.2.6 BEQ指令引入的ID级数据冒险

发生原因

beq指令提前到id级,引入了新的数据冒险

在这里插入图片描述

解决方法

id级增加旁路选择信号,并且实现对应的阻塞

在这里插入图片描述

 assign flag = (mem_read)&& (rt_id_exe == rs || rt_id_exe == rt)&&(rt_id_exe!={5{1'b0}})
    ||(s_npc==2'b11&&(rs_id_exe==rs||rs_id_exe==rt));
    
    
 assign flag_rs_id_exe=(!id_exe_flush&&s_npc==2'b11
    &&(num_write_out_exe_mem!=5'b00000)
    && num_write_out_exe_mem==rs_in);

    assign flag_rt_id_exe=(!id_exe_flush&&s_npc==2'b11
    &&(num_write_out_exe_mem!=5'b00000)
    && num_write_out_exe_mem==rt_in);
 s_forwardA2=(flag_rs_id_exe)?
    (flag_rs_write?`SIDE_ROAD_EXE_MEM:`SIDE_ROAD_EXE_MEM):
    (flag_rs_write?`SIDE_ROAD_MEM_WB:`SIDE_ROAD_GPR);

     s_forwardB2=(flag_rt_id_exe)?
    (flag_rt_write?`SIDE_ROAD_EXE_MEM:`SIDE_ROAD_EXE_MEM):
    (flag_rt_write?`SIDE_ROAD_MEM_WB:`SIDE_ROAD_GPR);
测试波形及仿真波形

仿真程序为三阶斐波那契数列

 .text

    addi     $t5,$t5,40            #  $t5 = 20
    
    li      $t2, 1                  # $t2 = 1 
    sw      $t2, 0($t0)             # store F[0] with 1
    sw      $t2, 4($t0)             # store F[1] with 1
    sw      $t2, 8($t0)             # store F[2] with 1
    ori     $t6, $zero, 3           # $t6 = 3
    subu    $t1, $t5, $t6           # the number of loop is (size-3)
    ori     $t7, $zero, 1           # the lastest loop $t7 = 1

    addi    $t0, $t0, 12             # point to F[3]
    
Loop:
    slt     $t4, $t1, $t7           # $t4 = ($t1 < 1) ? 1 : 0
    beq	    $t4, $t7, Loop_End      # repeat if not finished yet
    lw      $a0, -12($t0)           # $a0 = F[n-3]
    lw      $a1, -8($t0)            # $a0 = F[n-2]
    lw      $a2, -4($t0)            # $a1 = F[n-1]
    jal     fibonacci               # F[n] = fibonacci( F[n-3], F[n-2], F[n-1] )
    sw      $v0, 0($t0)             # store F[n]
    addi    $t0, $t0, 4             # $t0 point to next element
    addi    $t1, $t1, -1            # loop counter decreased by 1
	j       Loop
	
Loop_End:    
    lui     $t6, 0xABCD             # $t6 = 0xABCD0000
    sw      $t6, 0($t0)             # *$t0 = $t6
Loop_Forever:
    j       Loop_Forever            # loop forever

fibonacci :
    addu    $v0, $a0, $a1	# $v0 = x + y
    addu    $v0, $v0, $a2	# $v0 = x + y
    jr      $ra             # return

仿真结果为正确实现跳转,并且各寄存器的值正确

在这里插入图片描述

3.实验总结

本次实验收获

偶然发现,似乎modelsim有直接可以看数据流的方法,可以使用这个方法进行观察数据流并且观察各模块之间的联系

add dataflow sim:/tb_pipeline_cpu/PIPELINE_CPU/IF_ID
add dataflow sim:/tb_pipeline_cpu/PIPELINE_CPU
add dataflow sim:/tb_pipeline_cpu/PIPELINE_CPU/ID_EXE
add dataflow sim:/tb_pipeline_cpu/PIPELINE_CPU/EXE_MEM
add dataflow sim:/tb_pipeline_cpu/PIPELINE_CPU/MEM_WB
add dataflow sim:/tb_pipeline_cpu/PIPELINE_CPU/SIDE_ROAD
add dataflow sim:/tb_pipeline_cpu/PIPELINE_CPU/HAZARD

这样就可以在view->dataflow立面观察了,十分方便

当然也可以直接添加顶层模块以观察整体设计,不用再打开quartus进行分析了

add dataflow sim:/tb_pipeline_cpu/PIPELINE_CPU/*

在这里插入图片描述

实验中遇到的问题及解决

一开始总是看不懂实验平台上提供的测试用例,一直想的是,怎么把平台上的机器码读懂,去推导对应的指令,寻求老师帮助,老师说可以自己编写汇编测试用例进行测试,我一开始还不太理解,后来才知道,原来之所以没有出现对应的译码器,是因为没有这种需求,对于机器码,是为了给计算机进行运算的,没必要让人读懂

所以我学习了使用mars进行测试,学会了怎么自己进行测试以满足合适的结果

如何让测试一步步复杂,调试程序

在这里插入图片描述

临近考试,事情非常多,复习压力繁重,我们需要坚持下去,认真负重前行!

  • 36
    点赞
  • 99
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
哈夫曼编码是一种常用的数据压缩算法,可以将原始数据转换为更短的编码,从而减少存储空间。它的基本思想是:根据字符出现的频率,构建一颗二叉树,使得出现频率高的字符离根节点近,出现频率低的字符离根节点远。然后,对于每个字符,从根节点出发,沿着对应的路径到达该字符所在的叶子节点,记录下路径,作为该字符的编码。 哈夫曼编码的具体实现步骤如下: 1. 统计每个字符在原始数据中出现的频率。 2. 根据字符的频率构建哈夫曼树。构建方法可以采用贪心策略,每次选择出现频率最低的两个字符,将它们作为左右子节点,父节点的权值为两个子节点的权值之和。重复这个过程,直到只剩下一个根节点。 3. 对哈夫曼树进行遍历,记录下每个字符的编码,为了避免编码产生歧义,通常规定左子节点为0,右子节点为1。 4. 将原始数据中的每个字符,用它对应的编码来代替。这一步可以通过哈夫曼树来实现。 5. 将编码后的数据存储起来。此时,由于每个字符的编码长度不同,所以压缩后的数据长度也不同,但总体上来说,压缩效果通常是比较好的。 实现哈夫曼编码的关键在于构建哈夫曼树和计算每个字符的编码。构建哈夫曼树可以采用优先队列来实现,每次从队列中取出两个权值最小的节点,合并成一个节点,再将合并后的节点插入队列中。计算每个字符的编码可以采用递归遍历哈夫曼树的方式,从根节点出发,如果走到了左子节点,则将0添加到编码中,如果走到了右子节点,则将1添加到编码中,直到走到叶子节点为止。 以下是基于C++的代码实现,供参考: ```c++ #include <iostream> #include <queue> #include <string> #include <unordered_map> using namespace std; // 定义哈夫曼树节点的结构体 struct Node { char ch; // 字符 int freq; // 出现频率 Node* left; // 左子节点 Node* right; // 右子节点 Node(char c, int f) : ch(c), freq(f), left(nullptr), right(nullptr) {} }; // 定义哈夫曼树节点的比较函数,用于优先队列的排序 struct cmp { bool operator() (Node* a, Node* b) { return a->freq > b->freq; } }; // 构建哈夫曼树的函数 Node* buildHuffmanTree(unordered_map<char, int> freq) { priority_queue<Node*, vector<Node*>, cmp> pq; for (auto p : freq) { pq.push(new Node(p.first, p.second)); } while (pq.size() > 1) { Node* left = pq.top(); pq.pop(); Node* right = pq.top(); pq.pop(); Node* parent = new Node('$', left->freq + right->freq); parent->left = left; parent->right = right; pq.push(parent); } return pq.top(); } // 遍历哈夫曼树,计算每个字符的编码 void calcHuffmanCode(Node* root, unordered_map<char, string>& code, string cur) { if (!root) return; if (root->ch != '$') { code[root->ch] = cur; } calcHuffmanCode(root->left, code, cur + "0"); calcHuffmanCode(root->right, code, cur + "1"); } // 将原始数据编码成哈夫曼编码 string encode(string s, unordered_map<char, string> code) { string res; for (char c : s) { res += code[c]; } return res; } // 将哈夫曼编码解码成原始数据 string decode(string s, Node* root) { string res; Node* cur = root; for (char c : s) { if (c == '0') { cur = cur->left; } else { cur = cur->right; } if (!cur->left && !cur->right) { res += cur->ch; cur = root; } } return res; } int main() { string s = "abacabad"; unordered_map<char, int> freq; for (char c : s) { freq[c]++; } Node* root = buildHuffmanTree(freq); unordered_map<char, string> code; calcHuffmanCode(root, code, ""); string encoded = encode(s, code); string decoded = decode(encoded, root); cout << "Original string: " << s << endl; cout << "Encoded string: " << encoded << endl; cout << "Decoded string: " << decoded << endl; return 0; } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

codeqb

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值