这些代码可以借鉴在RISCV 里面实现,注意大小MIPS核是大字端,RISCV核实小字端。
由于是32位处理器,并且指令长度也是32位,所以为了性能要求我们要求存储器宽度要达到32位(如果达不到吗,那用32位处理器就有大材小用了,可以换成8位核或者16核)。而处理器要达到精确到字节的寻址,所以我们用四个8位的款的存储器拼接在一起,但是写信号可以分别控制。我们下面的代码就要实现这部分:
module mem_addr_ctl(
input [3:0]ctl,
input [31:0]addr_i,
output reg[3:0]wr_en
);
always@(*)
case (ctl)
`DMEM_SB://store byte [8bits]
begin
case(addr_i[1:0])
0:wr_en = 4'b0001;
1:wr_en = 4'b0010;
2:wr_en = 4'b0100;
3:wr_en = 4'b1000;
default :wr_en = 4'b000;
endcase
end
`DMEM_SH ://store half word [16bits]
begin
case(addr_i[1:0])
'd2:wr_en=4'b1100;
'd0:wr_en=4'b0011;
default :wr_en = 4'b0000;
endcase
end
`DMEM_SW ://store word [32bits]
begin
wr_en=4'b1111;
end
default wr_en=4'b0000;
endcase
endmodule
module mem_din_ctl(
input [3:0]ctl,
input [31:0]din,
output reg [31:0]dout
);
always @(*)
case (ctl)
`DMEM_SB : // store byte[8bits]
dout={din[7:0],din[7:0],din[7:0],din[7:0]};
`DMEM_SH : // store half word [16bits]
dout = {din[15:0],din[15:0]};
`DMEM_SW :
dout =din; // store word [32bits]
default dout=32'bX;
endcase
endmodule
mem_addr_ctl模块根据保存的要STORE的数据类型以及地址的低两位,确定那个需要写哪个存储器。这里的wr_en[3:0]信号可以对应axi 总线的strb[3:0]信号。
假设一种情况,我们将通用寄存器里的32'H0000_00AA这个数字保存到字节数字的某一位置,根据上面mem_addr_ctlr的处理,如果地址最后两位是2'B11,那么就是wr_en[3:0]=4'b1000.这时候要求寄存器内容的低8位出现在存储器的数据写入端。
`DMEM_SB : // store byte[8bits]
dout={din[7:0],din[7:0],din[7:0],din[7:0]};
所以我们统一化处理,只要是store byte指令,就将低8位输出给每个字节存储器的数据写入端,具体写入到哪个存储器,由wr_en来确定。同样写入16位的half word我们将低16位呈现在总线的输入端。这里要写16位的halfword的地址必须是偶数的。而当写32位寄存器时可以直接对应这32位写入8个字节。
以上是我们看的是store指令,之后我们再看一下从存储器里面加载load 的处理。
module mem_dout_ctl(
input [1:0]byte_addr,
input [3:0]ctl,
input [31:0] din,
output reg [31:0] dout
);
always @(*)
case (ctl)
`DMEM_LBS : // load byte signed [8bits]
case (byte_addr)
'd3:dout={{24{din[31]}},din[31:24]};
'd2:dout={{24{din[23]}},din[23:16]};
'd1:dout={{24{din[15]}},din[15:8]};
'd0:dout={{24{din[7]}},din[7:0] };
// default : dout=32'bX;
endcase
`DMEM_LBU : // load byte unsigned [8bits]
case (byte_addr)
'd0:dout={24'b0,din[7:0]};
'd1:dout={24'b0,din[15:8]};
'd2:dout={24'b0,din[23:16]};
'd3:dout={24'b0,din[31:24]};
// default : dout=32'bX;
endcase
`DMEM_LHU : // load half word [16bits] unsigned
case (byte_addr)
'd2:dout={16'b0,din[31:24],din[23:16]};
'd0:dout={16'b0,din[15:8],din[7 :0]};
default:dout=32'bX;
endcase
`DMEM_LHS :
case (byte_addr) // load half_word[16its] signed
'd2 :dout={{16{din[31]}},din[31:24],din[23:16]};
'd0 :dout={{16{din[15]}},din[15:8],din[7 :0]};
default:dout=32'bX;
endcase
`DMEM_LW : //load word [32bits]
dout=din;
default :
dout=0;
endcase
endmodule
从存储器里load要解决两个问题:
1,输出的数据进行位处理。比如LOAD BYTE指令从某字节存储器里面读出数据对应放在32位输出总线的低8位。
2,进行符号的扩展。8位数据的BIT7就是符号位,16位数据的BIT15是符号位。32位数据的BIT31是符号位。
`DMEM_LBS : // load byte signed [8bits]
case (byte_addr)
'd0:dout={{24{din[7]}},din[7:0] };
以上一段典型的有符号扩展,将BIT7复制24次防止在32位数据总线的高24位。比如说以前din[7:0]=8'h80, 是8'B1000_0000,那么位扩展后就是32'hFFFF_FF80,如果din[7:0]=8'h7f,是8'B0111_1111,那么位扩展后就是32'h0000_007F。
无符号扩展就是将高24位全部填为0.
Half Word的符号扩展和无符号扩展也是类似这样。
当然从存储器读一个32位的字,直接将读出来的32位保存到寄存器就好,不牵扯是否扩展。