在计算机体系结构的学习和实践中,理解原子操作的概念及其硬件实现是每个硬件工程师和计算机科学学生的必修课。MIPS架构中的LL(Load Linked)和SC(Store Conditional)指令对是实现原子操作的关键,它们共同构成了一个"读-修改-写"的原子操作序列。本文将带你从零开始,在FPGA上实现这两条关键指令。
原子操作是指在执行过程中不会被中断的操作,这对于多任务环境和并发编程至关重要。MIPS架构采用LL/SC指令对来实现原子操作,相比其他架构的原子指令实现,这种分离设计更符合RISC理念。
LL指令(Load Linked)执行以下操作:
verilog复制// LL指令示例
ll r7, 0x20(r1) // 从地址(r1+0x20)加载数据到r7,并设置LLbit
SC指令(Store Conditional)执行条件存储:
verilog复制// SC指令示例
sc r7, 0x20(r1) // 条件存储:仅在LLbit有效时将r7存入(r1+0x20)
在单处理器系统中,主要需要处理的是中断异常对原子操作的干扰。当发生中断或异常时,硬件应自动清除LLbit,确保原子操作序列的完整性。
我们的FPGA模型机采用典型的五级流水线结构(取指IF、译码ID、执行EX、访存MEM、写回WB),重点修改MEM阶段以支持LL/SC指令。关键新增组件包括:
系统架构如下图所示(文字描述):
code复制取指(IF) → 译码(ID) → 执行(EX) → 访存(MEM) → 写回(WB)
↑ ↑
└── LLbit寄存器 ─┘
LLbit寄存器需要特殊处理以下情况:
verilog复制`include "define.v"
module LLbit(
input wire clk,
input wire rst,
input wire excpt, // 异常信号
input wire wbit, // 写使能
input wire wLLbit, // 写入值
output reg rLLbit // 读出值
);
reg LLbit_reg; // 内部存储
always@(*) begin
if(rst == `RstEnable)
rLLbit = `Zero;
else
rLLbit = LLbit_reg;
end
always@(posedge clk) begin
if(rst == `RstEnable || excpt)
LLbit_reg <= `ClearFlag;
else if(wbit == `Valid)
LLbit_reg <= wLLbit;
end
endmodule
ID模块需要识别LL/SC指令并生成相应的控制信号:
verilog复制// 在ID模块中添加LL/SC指令译码
`Inst_ll: begin
op = `Ll;
regaRead = `Valid; // 读取基址寄存器
regbRead = `Invalid;
regcWrite = `Valid; // 写入目标寄存器
regaAddr = inst[25:21];
regcAddr = inst[20:16];
imm = {{16{inst[15]}}, inst[15:0]}; // 符号扩展偏移量
end
`Inst_sc: begin
op = `Sc;
regaRead = `Valid; // 读取基址寄存器
regbRead = `Valid; // 读取要存储的值
regcWrite = `Valid; // 需要写入rt寄存器(存储结果)
regaAddr = inst[25:21];
regbAddr = inst[20:16];
regcAddr = inst[20:16];
imm = {{16{inst[15]}}, inst[15:0]}; // 符号扩展偏移量
end
MEM模块是LL/SC实现的核心,需要处理以下逻辑:
LL指令处理:
SC指令处理:
verilog复制module MEM(
input wire rst,
input wire [5:0] op,
input wire [31:0] regcData,
input wire [4:0] regcAddr,
input wire regcWr,
input wire [31:0] memAddr_i,
input wire [31:0] memData,
input wire [31:0] rdData,
input wire rLLbit, // LLbit状态输入
output wire [4:0] regAddr,
output wire regWr,
output wire [31:0] regData,
output wire [31:0] memAddr,
output reg [31:0] wtData,
output reg memWr,
output reg memCe,
output reg wbit, // LLbit写使能
output reg wLLbit // LLbit写入值
);
// 寄存器文件写入地址和控制
assign regAddr = regcAddr;
assign regWr = regcWr;
// SC指令结果生成逻辑
wire [31:0] sc_result = (rLLbit == `SetFlag) ? 32'b1 : 32'b0;
wire [31:0] regcData_modified = (op == `Sc) ? sc_result : regcData;
// 最终写入寄存器文件的数据选择
assign regData = (op == `Lw || op == `Ll) ? rdData : regcData_modified;
assign memAddr = memAddr_i;
always @ (*) begin
if(rst == `RstEnable) begin
wtData = `Zero;
memWr = `RamUnWrite;
memCe = `RamDisable;
wbit = `Invalid;
wLLbit = `ClearFlag;
end else begin
// 默认值
wtData = `Zero;
memWr = `RamUnWrite;
memCe = `RamDisable;
wbit = `Invalid;
wLLbit = `ClearFlag;
case(op)
`Ll: begin
// 加载操作,不写入内存
memCe = `RamEnable; // 使能存储器
wbit = `Valid; // 需要更新LLbit
wLLbit = `SetFlag; // 设置LLbit为1
end
`Sc: begin
if(rLLbit == `SetFlag) begin
// LLbit仍有效,执行存储
wtData = memData; // 要存储的数据
memWr = `RamWrite;
memCe = `RamEnable;
end
// 无论成功与否,都要清除LLbit
wbit = `Valid;
wLLbit = `ClearFlag;
end
// 其他指令处理...
endcase
end
end
endmodule
在顶层模块中,我们需要实例化LLbit寄存器并正确连接所有信号:
verilog复制module MIPS(
input wire clk,
input wire rst,
// 其他端口...
);
// LLbit相关信号
wire excpt; // 异常信号(连接中断/异常处理模块)
wire wbit; // 来自MEM模块
wire wLLbit; // 来自MEM模块
wire rLLbit; // 输出到MEM模块
// 实例化LLbit寄存器
LLbit llbit0(
.clk(clk),
.rst(rst),
.excpt(excpt),
.wbit(wbit),
.wLLbit(wLLbit),
.rLLbit(rLLbit)
);
// 实例化MEM模块并连接LLbit信号
MEM mem0(
.rst(rst),
// 其他连接...
.rLLbit(rLLbit),
.wbit(wbit),
.wLLbit(wLLbit)
// 其他连接...
);
// 其他模块实例化...
endmodule
我们设计一个简单的测试程序来验证LL/SC指令的正确性。该程序尝试原子地获取和释放一个"锁"(信号量):
verilog复制initial begin
// 初始化寄存器
instmem[0] = 32'h34011100; // ori R1, R0, 0x1100
instmem[1] = 32'h34020020; // ori R2, R0, 0x0020
// 原子操作测试
instmem[6] = 32'b110000_00001_00111_0000_0000_0010_0000; // LL R7, 0x20(R1)
instmem[7] = 32'b000101_00111_00000_0000_0000_0000_0100; // BNE R7, R0, +4
instmem[8] = 32'h3407ffff; // ORI R7, R0, 0xFFFF
instmem[9] = 32'b111000_00001_00111_0000_0000_0010_0000; // SC R7, 0x20(R1)
instmem[10] = 32'b000101_00111_00000_0000_0000_0000_0010; // BNE R7, R0, +2 (成功)
instmem[11] = 32'h08000006; // J 6 (失败时重试)
instmem[12] = 32'h08000006; // J 6 (初始条件不满足时重试)
// 成功后的操作
instmem[13] = 32'h30070000; // ANDI R7, R7, 0 (清除标志)
instmem[14] = 32'b100011_00001_00111_0000_0000_0010_0000; // LW R7, 0x20(R1)
end
在仿真时,需要特别关注以下信号:
关键检查点:
SC指令总是失败:
流水线冲突:
仿真波形分析技巧:
bash复制# Modelsim常用命令
vsim work.mips_tb
add wave -position insertpoint sim:/mips_tb/uut/*
run -all
在真正的多核系统中,LL/SC实现还需要:
verilog复制// 简化的多核LLbit设计
module LLbit(
input wire clk,
input wire rst,
input wire [1:0] core_id, // 处理器核心ID
input wire [31:0] ll_addr, // 监控地址
input wire ll_addr_valid, // 地址有效
input wire sc_attempt, // SC尝试信号
output reg sc_success // SC成功信号
);
// 实现更复杂的地址监控逻辑
// ...
endmodule
基于LL/SC可以构建更高级的原子操作:
c复制// 原子加法实现示例
int atomic_add(int* ptr, int value) {
int old;
do {
old = LL(ptr);
} while (!SC(ptr, old + value));
return old;
}
项目目录结构建议如下:
code复制mips_fpga/
├── rtl/
│ ├── define.v // 宏定义
│ ├── mips.v // 顶层模块
│ ├── if.v // 取指
│ ├── id.v // 译码
│ ├── ex.v // 执行
│ ├── mem.v // 访存(含LL/SC实现)
│ ├── llbit.v // LLbit寄存器
│ └── regfile.v // 寄存器文件
├── bench/
│ ├── mips_tb.v // 测试平台
│ └── instmem.hex // 测试程序
└── scripts/
└── modelsim.do // 仿真脚本
关键模块的实现我们已经在前文中详细讨论,完整代码可以根据上述框架和代码片段进行整合。在实际项目中,还需要考虑添加完善的注释、文档和测试用例。