译码 ID级 设计

提到译码级,那就得是译码器啊。纯组合逻辑,包你写到爽。

译码级还有什么?立即数移位也需要放在ID级,以减轻EX级的压力。

指令译码器 Decoder.sv

指令译码器为组合逻辑模块。我们需要哪些信号?

首先要把31位的指令分类:是RIBJSU的哪一种?之后,要根据分类,将寄存器地址拆分出来,准备送进同一级的寄存器堆来读取数据;要提取指令的funct3funct7,以便在EX级告诉ALU“长的像的指令如何区分”;要根据指令类型,区分跳转类型和读取/写入类型,给后面的流水级进行处理;还要生成对应位的寄存器读信号,和分支指令判断信号,为后面处理数据冒险准备……要干的事情真不少。

一步一步全写上即可。推荐使用宏定义,大幅度提升可读性。

在模块内使用``ifdef DEBUG`配合上ASCII字符进行Debug是另一个小技巧。在综合时取消宏定义即可。

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
`include "include/defines.svh"

module Decoder (
input logic [31:0] instr,
output logic [ 4:0] alu_op,
// ALU 第一操作数来源
output logic is_auipc,
// ALU 第二操作数来源
output logic alu_src,
// 写使能
output logic dram_we, // 高电平为写使能 低电平为读
output logic rf_we,
// 写回数据来源
output logic [ 1:0] wd_sel,
// 分支相关
output logic is_branch_instr,
output logic [ 2:0] branch_type,
// 跳转相关
output logic [ 1:0] jump_type,
// 数据读取类型
output logic [ 3:0] sl_type,
// 源寄存器是否在使用
output logic rs1_used,
output logic rs2_used
);

// 提取指令字段
logic [6:0] opcode;
logic [2:0] funct3;
logic [6:0] funct7;

always_comb begin
opcode = instr[6:0];
funct3 = instr[14:12];
funct7 = instr[31:25];

end

// 跳转类型判断
// assign jump_type = (opcode == `OPCODE_JAL) ?
// `JUMP_JAL : (opcode == `OPCODE_JALR) ? `JUMP_JALR : `JUMP_NOP;
always_comb begin
case (opcode)
`OPCODE_JAL: jump_type = `JUMP_JAL;
`OPCODE_JALR: jump_type = `JUMP_JALR;
default: jump_type = `JUMP_NOP;
endcase
end

// 源寄存器读取判断
// 反向判断简化逻辑 记得去除ERROR
// rs1:除了 LUI/AUIPC/JAL 之外都用到
assign rs1_used = ~((opcode == `OPCODE_LUI) || (opcode == `OPCODE_AUIPC) ||
(opcode == `OPCODE_JAL) || (opcode == `OPCODE_ZERO));
// // rs2:只有 R-type / B-type / S-type 用到
assign rs2_used = (opcode == `OPCODE_RTYPE) || (opcode == `OPCODE_BTYPE) ||
(opcode == `OPCODE_STYPE);


// [HACK] Icarus Verilog 不支持 inside 语法糖 :(
// assign rs1_used = !(opcode inside {
// `OPCODE_LUI,
// `OPCODE_AUIPC,
// `OPCODE_JAL
// });
// assign rs2_used = (opcode inside {
// `OPCODE_RTYPE,
// `OPCODE_BTYPE,
// `OPCODE_STYPE
// });


// 分支类型判断
// 看funct3决定具体的分支类型
// assign is_branch_instr = (opcode == `OPCODE_BTYPE);
// assign branch_type = funct3;

// ALU 第一操作数来源
// 判断是否为 AUIPC 指令即可
assign is_auipc = (opcode == `OPCODE_AUIPC);

// ALU 第二操作数来源
// verilog_format:off
always_comb begin : alu_src_selection
unique case (opcode)
`OPCODE_RTYPE,
`OPCODE_BTYPE: alu_src = `ALUSRC_RS2; // 来自寄存器 rs2

`OPCODE_ITYPE,
`OPCODE_LTYPE,
`OPCODE_STYPE,
`OPCODE_AUIPC,
`OPCODE_JALR:
alu_src = `ALUSRC_IMM; // 来自立即数

default: alu_src = `ALUSRC_RS2;
endcase
end
// verilog_format:on

// 回写数据来源选择
// verilog_format:off
always_comb begin : wb_source_selection
unique case (opcode)
`OPCODE_RTYPE,
`OPCODE_ITYPE: wd_sel = `WD_SEL_FROM_ALU;

`OPCODE_LTYPE: wd_sel = `WD_SEL_FROM_DRAM;

`OPCODE_JAL,
`OPCODE_JALR: wd_sel = `WD_SEL_FROM_PC4;

`OPCODE_LUI,
`OPCODE_AUIPC: wd_sel = `WD_SEL_FROM_IEXT;

default: wd_sel = 2'b0;
endcase
end
// verilog_format:on

// 寄存器堆写使能
// verilog_format:off
always_comb begin : rf_write_enable
unique case (opcode)
`OPCODE_RTYPE,
`OPCODE_ITYPE,
`OPCODE_LTYPE,
`OPCODE_LUI,
`OPCODE_AUIPC,
`OPCODE_JAL,
`OPCODE_JALR: rf_we = 1'b1;

`OPCODE_BTYPE,
`OPCODE_STYPE: rf_we = 1'b0;

default: rf_we = 1'b0; // 考虑异常情况 默认不写
endcase
end
// verilog_format:on

// 数据存储器写使能
assign dram_we = (opcode == `OPCODE_STYPE) ? 1'b1 : 1'b0;

// ALU 操作码生成
// verilog_format:off
always_comb begin : ALUOp_selection
// 默认值
alu_op = `ALU_NOP;
branch_type = `BRANCH_NOP;
is_branch_instr = 1'b0;

unique case (opcode)

`OPCODE_RTYPE, `OPCODE_ITYPE: begin
unique case (funct3)
`FUNCT3_ADD_SUB_MUL:
// 使用 case-true 结构
case (1'b1)
(opcode == `OPCODE_RTYPE) && // R-Type SUB
(funct7 == `FUNCT7_SUB):
alu_op = `ALU_SUB;

(opcode == `OPCODE_RTYPE): // R-Type 其它(默认为 ADD)
alu_op = `ALU_ADD;

default: // 非 R-type(同样默认 ADD)
alu_op = `ALU_ADD;
endcase

`FUNCT3_SLL_MULH: alu_op = `ALU_SLL;
`FUNCT3_SLT_MULHSU: alu_op = `ALU_SLT;
`FUNCT3_SLTU_MULHU: alu_op = `ALU_SLTU;
`FUNCT3_XOR_DIV: alu_op = `ALU_XOR;
`FUNCT3_SRL_SRA_DIVU: alu_op = (funct7 == `FUNCT7_SRA) ?
`ALU_SRA : `ALU_SRL;
`FUNCT3_OR_REM: alu_op = `ALU_OR;
`FUNCT3_AND_REMU: alu_op = `ALU_AND;
default: alu_op = `ALU_NOP; // 默认为空
endcase
end

`OPCODE_BTYPE: begin
alu_op = (funct3 == `FUNCT3_BLTU || funct3 == `FUNCT3_BGEU) ? `ALU_SLTU :
`ALU_SUB; // 用于比较是否为有符号/无符号
// 判断分支类型
unique case (funct3)
`FUNCT3_BEQ: branch_type = `BRANCH_BEQ;
`FUNCT3_BNE: branch_type = `BRANCH_BNE;
`FUNCT3_BLT: branch_type = `BRANCH_BLT;
`FUNCT3_BGE: branch_type = `BRANCH_BGE;
`FUNCT3_BLTU: branch_type = `BRANCH_BLTU;
`FUNCT3_BGEU: branch_type = `BRANCH_BGEU;
default: branch_type = `BRANCH_NOP;
endcase

is_branch_instr = 1'b1;

end

`OPCODE_JAL,
`OPCODE_LUI: alu_op = `ALU_RIGHT; // 特殊指令

`OPCODE_JALR,
`OPCODE_AUIPC: alu_op = `ALU_ADD;

`OPCODE_LTYPE: begin
unique case (funct3)
`FUNCT3_LB: alu_op = `ALU_LB;
`FUNCT3_LBU: alu_op = `ALU_LBU;
`FUNCT3_LH: alu_op = `ALU_LH;
`FUNCT3_LHU: alu_op = `ALU_LHU;
`FUNCT3_LW: alu_op = `ALU_LW;
default: alu_op = `ALU_NOP;
endcase
end

`OPCODE_STYPE: begin
unique case (funct3)
`FUNCT3_SB: alu_op = `ALU_SB;
`FUNCT3_SH: alu_op = `ALU_SH;
`FUNCT3_SW: alu_op = `ALU_SW;
default: alu_op = `ALU_NOP;
endcase
end

default: alu_op = `ALU_NOP; // 默认加法
endcase
end
// verilog_format:on

// 数据存取类型判断
// verilog_format:off
always_comb begin : sl_selection
unique case (opcode)
`OPCODE_LTYPE: begin
case (funct3)
`FUNCT3_LB: sl_type = `MEM_LB;
`FUNCT3_LBU: sl_type = `MEM_LBU;
`FUNCT3_LH: sl_type = `MEM_LH;
`FUNCT3_LHU: sl_type = `MEM_LHU;
`FUNCT3_LW: sl_type = `MEM_LW;
default: sl_type = `MEM_NOP;
endcase
end

`OPCODE_STYPE:begin
case (funct3)
`FUNCT3_SB: sl_type = `MEM_SB;
`FUNCT3_SH: sl_type = `MEM_SH;
`FUNCT3_SW: sl_type = `MEM_SW;
default: sl_type = `MEM_NOP;
endcase
end
default: sl_type = `MEM_NOP;
endcase
end

// 可视化输出判断
//verilog_format:off
`ifdef DEBUG
// 方便调试的 ASCII 指令输出
logic [256-1:0] instr_ascii;
logic [256-1:0] branch_type_ascii;

always_comb begin : ascii_output
unique case (branch_type)
`BRANCH_NOP: branch_type_ascii = "BRANCH_NOP";
`BRANCH_BEQ: branch_type_ascii = "BRANCH_BEQ";
`BRANCH_BNE: branch_type_ascii = "BRANCH_BNE";
`BRANCH_BLT: branch_type_ascii = "BRANCH_BLT";
`BRANCH_BGE: branch_type_ascii = "BRANCH_BGE";
`BRANCH_BLTU: branch_type_ascii = "BRANCH_BLTU";
`BRANCH_BGEU: branch_type_ascii = "BRANCH_BGEU";
`BRANCH_JALR: branch_type_ascii = "BRANCH_JALR";
default: branch_type_ascii = "BRANCH_UNKNOWN";
endcase
// 判断当前具体为32条基本指令的哪一条
unique case (opcode)
`OPCODE_LUI: instr_ascii = "LUI";
`OPCODE_AUIPC: instr_ascii = "AUIPC";
`OPCODE_JAL: instr_ascii = "JAL";
`OPCODE_JALR: instr_ascii = "JALR";

`OPCODE_BTYPE: begin
unique case (funct3)
`FUNCT3_BEQ: instr_ascii = "BEQ";
`FUNCT3_BNE: instr_ascii = "BNE";
`FUNCT3_BLT: instr_ascii = "BLT";
`FUNCT3_BGE: instr_ascii = "BGE";
`FUNCT3_BLTU: instr_ascii = "BLTU";
`FUNCT3_BGEU: instr_ascii = "BGEU";
default: instr_ascii = "B_UNKNOWN";
endcase
end

`OPCODE_LTYPE: begin
unique case (funct3)
`FUNCT3_LB: instr_ascii = "LB";
`FUNCT3_LH: instr_ascii = "LH";
`FUNCT3_LW: instr_ascii = "LW";
`FUNCT3_LBU: instr_ascii = "LBU";
`FUNCT3_LHU: instr_ascii = "LHU";
default: instr_ascii = "L_UNKNOWN";
endcase
end

`OPCODE_STYPE: begin
unique case (funct3)
`FUNCT3_SB: instr_ascii = "SB";
`FUNCT3_SH: instr_ascii = "SH";
`FUNCT3_SW: instr_ascii = "SW";
default: instr_ascii = "S_UNKNOWN";
endcase
end

`OPCODE_ITYPE: begin
unique case (funct3)
`FUNCT3_ADD_SUB_MUL: instr_ascii = "ADDI";
`FUNCT3_SLL_MULH: instr_ascii = "SLLI";
`FUNCT3_SLT_MULHSU: instr_ascii = "SLTI";
`FUNCT3_SLTU_MULHU: instr_ascii = "SLTIU";
`FUNCT3_XOR_DIV: instr_ascii = "XORI";
`FUNCT3_SRL_SRA_DIVU: instr_ascii = (funct7 == `FUNCT7_SRAI) ? "SRAI" : "SRLI";
`FUNCT3_OR_REM: instr_ascii = "ORI";
`FUNCT3_AND_REMU: instr_ascii = "ANDI";
default: instr_ascii = "I_UNKNOWN";
endcase
end

`OPCODE_RTYPE: begin
unique case (funct3)
`FUNCT3_ADD_SUB_MUL: begin
if (funct7 == `FUNCT7_SUB)
instr_ascii = "SUB";
else if (funct7 == `FUNCT7_ADD)
instr_ascii = "ADD";
else instr_ascii = "R_UNKNOWN";
end
`FUNCT3_SLL_MULH: instr_ascii = "SLL";
`FUNCT3_SLT_MULHSU: instr_ascii = "SLT";
`FUNCT3_SLTU_MULHU: instr_ascii = "SLTU";
`FUNCT3_XOR_DIV: instr_ascii = "XOR";
`FUNCT3_SRL_SRA_DIVU: begin
if (funct7 == `FUNCT7_SRA)
instr_ascii = "SRA";
else if (funct7 == `FUNCT7_SRL)
instr_ascii = "SRL";
else instr_ascii = "R_UNKNOWN";
end
`FUNCT3_OR_REM: instr_ascii = "OR";
`FUNCT3_AND_REMU: instr_ascii = "AND";
default: instr_ascii = "R_UNKNOWN";
endcase
end

default: instr_ascii = "ERROR";
endcase
end
`endif
// verilog_format:on


endmodule

立即数扩展模块 imm_extener.sv

立即数扩展模块也是组合逻辑模块。不需要接受译码器传出的信号,而是和译码器并行工作,同样接受32位指令instr后进行分类处理。当然可以写在Decoder.v,但是那样子有点乱。

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
`include "include/defines.svh"

module imm_extender (
input logic [31:0] instr,
output logic [31:0] imm_out
);
logic [6:0] opcode;
assign opcode = instr[6:0];

always_comb begin : imm_classifier
unique case (opcode)
`OPCODE_ITYPE, `OPCODE_LTYPE, `OPCODE_JALR: begin // I-type
// 先输出立即数
// 之后再根据 instr[30] 区分 SLLI/SRLI和SRAI
imm_out = {{20{instr[31]}}, instr[31:20]};
end
`OPCODE_BTYPE: begin // B-type
imm_out = {{20{instr[31]}}, instr[7], instr[30:25], instr[11:8], 1'b0};
end
`OPCODE_JAL: begin // J-type
imm_out = {{12{instr[31]}}, instr[19:12], instr[20], instr[30:21], 1'b0};
end
`OPCODE_STYPE: begin // S-type
imm_out = {{20{instr[31]}}, instr[31:25], instr[11:7]};
end
`OPCODE_AUIPC, `OPCODE_LUI: begin // U-type
imm_out = {instr[31:12], 12'b0};
end

default: begin
imm_out = 32'b0;
end
endcase
end

endmodule

ID/EX级流水线寄存器 PR_ID_EX.sv

终于,要进入下一阶段了。ID/EX级的流水线寄存器可谓是相当重要,重要性仅次于EX/MEM级,因为这一级中转了各类数据以及地址——还有未来的数据前递入口。

我们要接收译码器传出的各种控制信号,包括ALU的操作数来源is_auipcalu_src。根据译码器判断,对于S-Type指令,在MEM级还需要写入DRAM,因此还有写使能dram_we,或者是寄存器写使能rf_we

还有什么?别忘了判断指令存取类型的sl_type,后面需要在MEM级用到。

即使RISC-V不强制要求支持非对齐访存,lb/lh等指令也必须支持!

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
`include "include/defines.svh"

module PR_ID_EX (
input logic clk,
input logic rst_n,
// 流水线控制信号
input logic flush,
// ID级输入
input logic [31:0] pc_id_i,
input logic [31:0] pc4_id_i,
// input logic [31:0] instr_id_i,
// ID级输出 给EX级输入
output logic [31:0] pc_ex_o,
output logic [31:0] pc4_ex_o,
// output logic [31:0] instr_ex_o,
// 判断指令是否有效
// 流水线冲刷时需要将指令置为无效
input logic instr_valid_id_i,
output logic instr_valid_ex_o,

// 寄存器堆第一寄存器数据
input logic [31:0] rD1_i,
output logic [31:0] rD1_o,
// 寄存器堆第二寄存器数据
input logic [31:0] rD2_i,
output logic [31:0] rD2_o,

// ALUOp
input logic [ 4:0] alu_op_id_i,
output logic [ 4:0] alu_op_ex_o,
// ALU第一操作数来源 判断AUIPC
input logic is_auipc_id_i,
output logic is_auipc_ex_o,
// ALU第二操作数来源
input logic alu_src2_sel_id_i,
output logic alu_src2_sel_ex_o,
// 写使能
input logic dram_we_id_i,
output logic dram_we_ex_o,
input logic rf_we_id_i,
output logic rf_we_ex_o,
// 写回数据来源
input logic [ 1:0] wd_sel_id_i,
output logic [ 1:0] wd_sel_ex_o,
// 写回寄存器地址
input logic [ 4:0] wr_id_i,
output logic [ 4:0] wr_ex_o,
// 分支跳转
input logic is_branch_instr_id_i,
output logic is_branch_instr_ex_o,
input logic [ 2:0] branch_type_id_i,
output logic [ 2:0] branch_type_ex_o,
// 跳转相关
input logic [ 1:0] jump_type_id_i,
output logic [ 1:0] jump_type_ex_o,
// 读取类型
input logic [ 3:0] sl_type_id_i,
output logic [ 3:0] sl_type_ex_o,
// 立即数
input logic [31:0] imm_id_i,
output logic [31:0] imm_ex_o,
// PC跳转时地址
input logic [31:0] pc_jump_id_i,
output logic [31:0] pc_jump_ex_o,
// 加上前递相关
input fwd_rD1e_EX,
input fwd_rD2e_EX,
input logic [31:0] fwd_rD1_EX,
input logic [31:0] fwd_rD2_EX
);

// 前递信号与数据
logic [31:0] rD1_forwarded, rD2_forwarded;
// 考虑了前递就不需要冲刷了
always_comb begin
rD1_forwarded = fwd_rD1e_EX ? fwd_rD1_EX : rD1_i;
rD2_forwarded = fwd_rD2e_EX ? fwd_rD2_EX : rD2_i;
end

// 寄存器堆数据
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rD1_o <= 32'b0;
rD2_o <= 32'b0;
end else begin
rD1_o <= rD1_forwarded;
rD2_o <= rD2_forwarded;
end
end

// 分支跳转相关
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
is_branch_instr_ex_o <= 1'b0;
branch_type_ex_o <= 3'b0;
jump_type_ex_o <= 2'b0;
end else if (flush) begin
is_branch_instr_ex_o <= 1'b0;
branch_type_ex_o <= 3'b0;
jump_type_ex_o <= 2'b0;
end else begin
is_branch_instr_ex_o <= is_branch_instr_id_i;
branch_type_ex_o <= branch_type_id_i;
jump_type_ex_o <= jump_type_id_i;
end
end

// ALUOp相关
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
alu_op_ex_o <= 4'b0;
is_auipc_ex_o <= 1'b0;
alu_src2_sel_ex_o <= 1'b0;
dram_we_ex_o <= 1'b0;
sl_type_ex_o <= 3'b0;
imm_ex_o <= 32'b0;
end else if (flush) begin
alu_op_ex_o <= 4'b0;
is_auipc_ex_o <= 1'b0;
alu_src2_sel_ex_o <= 1'b0;
dram_we_ex_o <= 1'b0;
sl_type_ex_o <= sl_type_id_i;
imm_ex_o <= 32'b0;
end else begin
alu_op_ex_o <= alu_op_id_i;
is_auipc_ex_o <= is_auipc_id_i;
alu_src2_sel_ex_o <= alu_src2_sel_id_i;
dram_we_ex_o <= dram_we_id_i;
sl_type_ex_o <= sl_type_id_i;
imm_ex_o <= imm_id_i;
end
end

// 写回来源
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
pc_ex_o <= 32'b0;
pc4_ex_o <= 32'b0;
instr_valid_ex_o <= 1'b0;

rf_we_ex_o <= 1'b0;
wd_sel_ex_o <= 2'b0;
pc_jump_ex_o <= 32'b0;

end else if (flush && pc_id_i) begin // 确保不是因为流水线暂停引起的冲刷
pc_ex_o <= 32'b0;
pc4_ex_o <= 32'b0;
instr_valid_ex_o <= 1'b0;

rf_we_ex_o <= 1'b0;
wd_sel_ex_o <= 2'b0;
pc_jump_ex_o <= 32'b0;

end else begin
pc_ex_o <= pc_id_i;
pc4_ex_o <= pc4_id_i;
instr_valid_ex_o <= instr_valid_id_i;

rf_we_ex_o <= rf_we_id_i;
wd_sel_ex_o <= wd_sel_id_i;
pc_jump_ex_o <= pc_jump_id_i;
end
end

// 写回寄存器地址
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr_ex_o <= 5'b0;
end else if (flush) begin
wr_ex_o <= 5'b0;
end else begin
wr_ex_o <= wr_id_i;
end
end

endmodule