存取控制模块:再进化

从零开始学RISC:第十篇 中,设计了LoadStoreUnit来处理各类存取指令,但将同一个模块复用了两遍。这样会带来额外的逻辑开销。

最好的办法是将其拆分为两个模块:一个放在MEM级,负责存储;另一个放在WB级,负责读取。

存储控制模块 StoreUnit.sv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// StoreUnit模块 - 专门用于MEM级的Store操作
// 负责处理SB/SH/SW的字节对齐和写使能生成
module StoreUnit (
input logic [ 3:0] sl_type, // 存取类型
input logic [31:0] addr, // 地址(用于计算字节偏移)
input logic [31:0] store_data_i, // 要存储的数据(来自rs2)
output logic [31:0] store_data_o, // 对齐后的数据
input logic dram_we, // 写使能输入
output logic [ 3:0] wstrb // 按位写使能
);

always_comb begin
// 默认值
wstrb = 4'b0000;
store_data_o = 32'b0;

if (dram_we) begin
unique case (sl_type[1:0])
2'b01: begin // SB
logic [4:0] shift;
shift = {addr[1:0], 3'b000}; // addr[1:0] * 8

// 写使能
unique case (addr[1:0])
2'b00: wstrb = 4'b0001;
2'b01: wstrb = 4'b0010;
2'b10: wstrb = 4'b0100;
2'b11: wstrb = 4'b1000;
default: wstrb = 4'b0000;
endcase

// 把 byte 放到对应 byte lane
store_data_o = ({24'b0, store_data_i[7:0]} << shift);
end

2'b10: begin // SH
logic [4:0] shift;
shift = {addr[1], 4'b0000}; // addr[1] ? 16 : 0

// 写使能
unique case (addr[1])
1'b0: wstrb = 4'b0011;
1'b1: wstrb = 4'b1100;
default: wstrb = 4'b0000;
endcase

// 把 halfword 放到对应位置
store_data_o = ({16'b0, store_data_i[15:0]} << shift);
end

2'b11: begin // SW
wstrb = 4'b1111;
store_data_o = store_data_i;
end

default: begin
wstrb = 4'b0000;
store_data_o = 32'b0;
end
endcase
end
end

`ifdef DEBUG
logic [31:0] sl_type_ascii;
always_comb begin
case (sl_type)
`MEM_SB: sl_type_ascii = "SB ";
`MEM_SH: sl_type_ascii = "SH ";
`MEM_SW: sl_type_ascii = "SW ";
default: sl_type_ascii = "----";
endcase
end
`endif

endmodule

读取控制模块 LoadUnit.sv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// LoadUnit模块 - 专门用于WB级的Load操作
// 负责处理LB/LH/LW/LBU/LHU的字节提取和符号扩展
module LoadUnit (
input logic [ 3:0] sl_type, // 存取类型
input logic [31:0] addr, // 地址(用于计算字节偏移)
input logic [31:0] load_data_i, // 从DRAM读取的原始数据
output logic [31:0] load_data_o // 处理后的数据
);

logic is_load_unsigned;
assign is_load_unsigned = (sl_type[2] == 1'b1);

always_comb begin
logic [31:0] raw;
raw = 32'b0;
load_data_o = 32'b0;

// 根据 sl_type 和 addr 偏移提取数据
case (sl_type[1:0])
2'b01: begin // byte
raw = (load_data_i >> (addr[1:0] * 8)) & 32'h000000FF;
end
2'b10: begin // half
raw = (load_data_i >> (addr[1] * 16)) & 32'h0000FFFF;
end
2'b11: begin // word
raw = load_data_i;
end
default: raw = 32'b0;
endcase

// 符号扩展或零扩展
if (is_load_unsigned) begin
load_data_o = raw; // 零扩展
end else begin
case (sl_type[1:0])
2'b01: load_data_o = {{24{raw[7]}}, raw[7:0]}; // LB
2'b10: load_data_o = {{16{raw[15]}}, raw[15:0]}; // LH
2'b11: load_data_o = raw; // LW
default: load_data_o = 32'b0;
endcase
end
end

`ifdef DEBUG
logic [31:0] sl_type_ascii;
logic [ 1:0] select_bits;
assign select_bits = addr[1:0];
always_comb begin
case (sl_type)
`MEM_LB: sl_type_ascii = "LB ";
`MEM_LH: sl_type_ascii = "LH ";
`MEM_LW: sl_type_ascii = "LW ";
`MEM_LBU: sl_type_ascii = "LBU ";
`MEM_LHU: sl_type_ascii = "LHU ";
default: sl_type_ascii = "----";
endcase
end
`endif

endmodule

拆分后的逻辑就清楚多了。