OpenRisc-47-or1200的WB模块分析
引言
“善妖善老,善始善终”,说的是无论什么事情要从有头有尾,别三分钟热度。
对于or1200的流水线来说,MA阶段是最后一个阶段,也是整条流水线的收尾阶段,负责战场的清扫工作。比如,把运算指令的运算结果要写到寄存器里,把从内存读来的数据写到寄存器里,如果在前面的流水阶段出现了异常,WB阶段还要负责把异常指令的地址存到寄存器里。总之呢,就是写回寄存器。
本小节,我们就分析一下or1200五级流水线最后一级,也就是WB(write back)。
1,整体结构
or1200的WB模块,主要包括的rtl文件是or1200_wbmux.v和or1200_rf.v。上面,我们介绍了WB阶段的功能和任务,那么这个模块的整体结构是什么样子的呢?如下所示。
WB模块负责将其他流水阶段的结果写回寄存器堆,而寄存器堆只有一个,所以就需要一个多路选择器(wb_mux)。
wb_mux是一个5选1的多路选择器,指令解码的结果和ex_freeze信号作为多路选择器的选择信号。
需要选择的5路信号分别是:
a,来自运算单元的alu的运算结果和fpu的运算结果(fpu模块,并没有实现)。
b,load指令的处理结果(从内存读来的数据)。
c,异常指令的地址。这个需要说明的是,在采用流水线的cpu结构时,各个流水阶段都可能产生异常,但并不是一旦产生异常后就马上处理,而是把所有流水阶段的异常统一处理,这样才能保证精确异常。
d,来自特殊功能寄存器的数据。
多路选择器的输出:
wb_mux模块的输出,
a,oprandmuxes模块。这个是用作forword的。流水线的forword技术,之前我们曾经介绍过,这里不再赘述。
b,rf模块。这个是显然的,写回阶段,肯定要将数据写回到reg_file里面。
c,调试模块。将输出数据给上述模块的同时,也会送给debug模块,这个是调试单元的需要。
2,wb_mux模块
上面介绍了wb_mux的整体功能,下面我们就是其对应的rtl代码。
module or1200_wbmux(
// Clock and reset
clk, rst,
// Internal i/f
wb_freeze, rfwb_op,
muxin_a, muxin_b, muxin_c, muxin_d, muxin_e,
muxout, muxreg, muxreg_valid
); parameter width = 32; //
// Internal wires and regs
//
reg [width-1:0] muxout;
reg [width-1:0] muxreg;
reg muxreg_valid; //
// Registered output from the write-back multiplexer
//
always @(posedge clk or `OR1200_RST_EVENT rst) begin
if (rst == `OR1200_RST_VALUE) begin
muxreg <= 32'd0;
muxreg_valid <= 1'b0;
end
else if (!wb_freeze) begin
muxreg <= muxout;
muxreg_valid <= rfwb_op[0];
end
end //
// Write-back multiplexer
//
always @(muxin_a or muxin_b or muxin_c or muxin_d or muxin_e or rfwb_op) begin
casez(rfwb_op[4:1]) `OR1200_RFWBOP_ALU: muxout = muxin_a;
`OR1200_RFWBOP_LSU: begin
muxout = muxin_b;
end
`OR1200_RFWBOP_SPRS: begin
muxout = muxin_c;
end
`OR1200_RFWBOP_LR: begin
muxout = muxin_d + 32'h8;
end
`ifdef OR1200_FPU_IMPLEMENTED
`OR1200_RFWBOP_FPU : begin
muxout = muxin_e; end
`endif
default : begin
muxout = 0;
end endcase
end endmodule
3,rf模块
1>整体分析
写回的意思,就是写回到寄存器堆(register file),所以,rf模块是WB阶段的核心模块。
寄存器堆,其物理结构就是RAM。其整体结构如下所示:
or1200的寄存器堆是由两个双端口的RAM组成的。关于这两个RAM(rf_a,rf_b),有以下几点需要注意:
a,每个RAM由32个4字节的寄存器(32x32)组成,分别是r0~r31。
b,双端的RAM,有两个端口,portA和portB,其中portA用来读,portB用来写。
c,这两个双端口的RAM是同步的,也就是说读写端口的时钟信号是相同的。
d,这两个双端口RAM的连接也很有意思,rf_a和rf_b的写端口是连在一起的。也就是说,rf_a和rf_b两个寄存器堆的值是完全相同的。rf_a和rf_b的读端口是分开的。
关于为什么采用两个寄存器堆,这两个寄存器堆为什么要这样连接?原因如下:
在解释原因之前,我们先看一条汇编指令:add r2,r2,r1。其含义是将r1和r2的值相加,再将计算结果写回r2。下面我们看一下分别采用1个寄存器堆和两个寄存器堆的处理过程。
采用一个寄存器堆:共需要4步(不包括指令译码)。
a,读取r2的值
b,读取r1的值
c,运算
d,将结果写回r2
采用两个寄存器堆:只需3步(不包含指令译码)。
a,同时读取r2的值,r1的值
b,运算
c,将结果写回r2(同时写入rf_a和rf_b)
可见,如果采用两套寄存器堆,并且想办法使两套寄存器堆的值完全一样的话,就会减少操作时间。
其实CPU采用多套寄存器堆,是很常见的一种做法。采用不止一套寄存器堆,除了可以提高指令执行速度之外,还有另外一种好处,就是MIPS结构中的影子寄存器(shadow register),专门用来进行异常处理中的现场保护,减少异常上下文的切换时间。
关于指令中的两个源寄存器的地址解码是在ID阶段完成的,这个,我们前面在分析ID模块时已说过,如有疑问,请参考相关内容。下面是解码的核心代码(or1200_ctrl.v)。
assign rf_addra = if_insn[20:16];
assign rf_addrb = if_insn[15:11];
assign rf_rda = if_insn[31] || if_maci_op;
assign rf_rdb = if_insn[30];
2>代码分析
了解了rf的整体结构之后,再来分析代码就简单多了。
rf模块对应的rtl文件是or1200_rf.v。
1》寄存器堆的例化
下面是例化两个寄存器堆的部分代码。
//
// Instantiation of register file two-port RAM A
//
or1200_dpram #
(
.aw(5),
.dw(32)
)
rf_a
(
// Port A
.clk_a(clk),
.ce_a(rf_ena),
.addr_a(rf_addra),
.do_a(from_rfa), // Port B
.clk_b(clk),
.ce_b(rf_we),
.we_b(rf_we),
.addr_b(rf_addrw),
.di_b(rf_dataw)
); //
// Instantiation of register file two-port RAM B
//
or1200_dpram #
(
.aw(5),
.dw(32)
)
rf_b
(
// Port A
.clk_a(clk),
.ce_a(rf_enb),
.addr_a(addrb),
.do_a(from_rfb), // Port B
.clk_b(clk),
.ce_b(rf_we),
.we_b(rf_we),
.addr_b(rf_addrw),
.di_b(rf_dataw)
);
可见是例化了两个完全一样的双端口ram,关于双端口ram本身的逻辑,我想大家就都很熟悉了,这里不做过多解释,or1200_dpram_32x32.v文件的核心代码如下。
//
// Generic RAM's registers and wires
//
reg [dw-1:0] mem [(1<<aw)-1:0]; // RAM content
reg [aw-1:0] addr_a_reg; // RAM address registered //
// Data output drivers
//
assign do_a = (oe_a) ? mem[addr_a_reg] : {dw{1'b0}}; //
// RAM read
//
always @(posedge clk_a or `OR1200_RST_EVENT rst_a)
if (rst_a == `OR1200_RST_VALUE)
addr_a_reg <= {aw{1'b0}};
else if (ce_a)
addr_a_reg <= addr_a; //
// RAM write
//
always @(posedge clk_b)
if (ce_b && we_b)
mem[addr_b] <= di_b;
2》特殊处理
rf模块的读来源有两个,一个是sprs,一个是debug。
rf模块的写来源也有两个,一个是sprs,一个是其它流水阶段的正常结果。
由于两个寄存器堆的内容完全一样,所以debug模块只需要读一个就可以了,对于or1200的具体实现,debug读的是rf_a。
由于rf_a有两个读来源,那么如果不进行特殊处理,当sprs和debug同时发来读请求的话,那么就可能会出现竞争,造成错误的输出结果。为了避免这种情况出现,or1200增加了一个仲裁机制。首先本地设置一个寄存器来保存上次读操作的地址,此外还增加了一根竞争信号线。这样一旦产生竞争,根据优先级设置的仲裁策略,选择是由sprs读还是debug来读。代码如下所示:
其中有两个关键信号:spr_valid和spr_cs_fe。
spr_valid信号是对两个写来源的仲裁结果。
spr_cs_fe是对两个读来源的仲裁结果。
只要弄明白了这两个信号,这个模块就很好理解了。
// Logic to restore output on RFA after debug unit has read out via SPR if.
// Problem was that the incorrect output would be on RFA after debug unit
// had read out - this is bad if that output is relied upon by execute
// stage for next instruction. We simply save the last address for rf A and
// and re-read it whenever the SPR select goes low, so we must remember
// the last address and generate a signal for falling edge of SPR cs.
// -- Julius // Detect falling edge of SPR select
reg spr_du_cs;
wire spr_cs_fe;
// Track RF A's address each time it's enabled
reg [aw-1:0] addra_last; always @(posedge clk)
if (rf_ena & !(spr_cs_fe | (du_read & spr_cs)))
addra_last <= addra; always @(posedge clk)
spr_du_cs <= spr_cs & du_read; assign spr_cs_fe = spr_du_cs & !(spr_cs & du_read); //
// SPR access is valid when spr_cs is asserted and
// SPR address matches GPR addresses
//
assign spr_valid = spr_cs & (spr_addr[10:5] == `OR1200_SPR_RF); //
// SPR data output is always from RF A
//
assign spr_dat_o = from_rfa; //
// Operand A comes from RF or from saved A register
//
assign dataa = from_rfa; //
// Operand B comes from RF or from saved B register
//
assign datab = from_rfb; //
// RF A read address is either from SPRS or normal from CPU control
//
assign rf_addra = (spr_valid & !spr_write) ? spr_addr[4:0] :
spr_cs_fe ? addra_last : addra; //
// RF write address is either from SPRS or normal from CPU control
//
assign rf_addrw = (spr_valid & spr_write) ? spr_addr[4:0] : addrw; //
// RF write data is either from SPRS or normal from CPU datapath
//
assign rf_dataw = (spr_valid & spr_write) ? spr_dat_i : dataw; //
// RF write enable is either from SPRS or normal from CPU control
//
always @(`OR1200_RST_EVENT rst or posedge clk)
if (rst == `OR1200_RST_VALUE)
rf_we_allow <= 1'b1;
else if (~wb_freeze)
rf_we_allow <= ~flushpipe; assign rf_we = ((spr_valid & spr_write) | (we & ~wb_freeze)) & rf_we_allow; assign cy_we_o = cy_we_i && ~wb_freeze && rf_we_allow; //
// CS RF A asserted when instruction reads operand A and ID stage
// is not stalled
//
assign rf_ena = (rda & ~id_freeze) | (spr_valid & !spr_write) | spr_cs_fe; //
// CS RF B asserted when instruction reads operand B and ID stage
// is not stalled
//
assign rf_enb = rdb & ~id_freeze;
4,小结
自此,我们已经分析了or1200的流水线的整个条数据通路。
OpenRisc-47-or1200的WB模块分析的更多相关文章
- OpenRisc-48-or1200的SPRS模块分析
引言 之前,我们在分析or1200的WB模块时(http://blog.csdn.net/rill_zhen/article/details/10220619),介绍了OpenRISC的GPRS(ge ...
- OpenRisc-43-or1200的IF模块分析
引言 “喂饱饥饿的CPU”,是计算机体系结构设计者时刻要考虑的问题.要解决这个问题,方法大体可分为两部分,第一就是利用principle of locality而引进的cache技术,缩短取指时间,第 ...
- OpenRisc-41-or1200的cache模块分析
引言 为CPU提供足够的,稳定的指令流和数据流是计算机体系结构设计中两个永恒的话题.为了给CPU提供指令流,需要设计分支预测机构,为了给CPU提供数据流,就需要设计cache了.其实,无论是insn还 ...
- OpenRisc-45-or1200的ID模块分析
引言 之前,我们分析了or1200流水线的整体结构,也分析了流水线中IF级,EX级,本小节我们来分析ID(insn decode)级的一些细节. 1,基础 or1200的pipeline的ID阶段包含 ...
- OpenRisc-40-or1200的MMU模块分析
引言 MMU(memory management unit),无论对于computer architecture designer还是OS designer,都是至关重要的部分,设计和使用的好坏,对性 ...
- OpenRisc-42-or1200的ALU模块分析
引言 computer(计算机),顾名思义,就是用来compute(计算)的.计算机体系结构在上世纪五六十年代的时候,主要就是研究如何设计运算部件,就是想办法用最少的元器件(那时元器件很贵),最快的速 ...
- nginx事件模块分析(一)
nginx ngx_events_module模块分析 ngx_events_module模块是核心模块之一,它是其它所有事件模块的代理模块.nginx在启动时只与events模块打交道,而由even ...
- 游戏模块分析总结(2)之UI、操作篇
转自:http://www.gameres.com/309812.html 游戏模块分析总结(2)之UI.操作篇 发布者: wuye | 发布时间: 2014-12-12 15:03| 评论数: 0 ...
- css扁平化博客学习总结(一)模块分析
一.模块分析 1.每开发一个项目之前,首先要对项目进行一个大致规划,它到底要做什么功能,它有什么具体需求. 2.所以需要进行模块化分析,把这些东西具象化,把一个问题模块化,对需求有一个宏观的了解. 3 ...
随机推荐
- SQL 约束解说
SQL 约束解说 2009-04-27 09:29 约束主要包含: NOT NULL UNIQUE PRIMARY KEY FOREIGN KEY CHECK DEFAULT 1.not null : ...
- spring mvc使用ClassPathXmlApplicationContext或FileSystemXmlApplicationContext和XmlWebApplicationContext类的操作其中 XmlWebApplicationContext是专为Web工程定制的。
一.简单的用ApplicationContext做测试的话,获得Spring中定义的Bean实例(对象).可以用: ApplicationContext ac = new ClassPathXmlAp ...
- Day5 - Python基础5 常用模块学习
Python 之路 Day5 - 常用模块学习 本节大纲: 模块介绍 time &datetime模块 random os sys shutil json & picle shel ...
- 加载MSCOMCTL.OCX错误处理的几个关键
一.工程文件说明,两个版本Object={831FDD16-0C5C-11D2-A9FC-0000F8754DA1}#2.0#0; MSCOMCTL.OCXObject={831FDD16-0C5C- ...
- OD: Windows Driver Fuzz
内核 FUZZ 思路 内核 API 函数:是提供给 Ring3 调用,在 Ring0 完成最终功能的函数.这些函数接收 Ring3 传入的参数,如果处理参数的过程存在问题的话,很有可能成为一个内核漏 ...
- Linux常用操作练习
Linux常用操作练习 练习一:安装CentOS 1.设置为1G内存(才有图形界面).10G硬盘 2.分给交换分区2G(4G一下2G,8G-32G分4G-8G) 练习二:安装CentOS迷你版 1.安 ...
- DNX概述
1. 什么是.NET执行环境 ? .NET Execution Environment(DNX) 是一个SDK 和运行时环境,它包含所有的你需要创建和运行.net应用程序的组件.它提供一个主机进程,C ...
- tableView创建方法调用的研究
当两个section的cell数量都为5的时候,方法的调用顺序: -[ViewController numberOfSectionsInTableView:] -[ViewController tab ...
- (转载)小课堂UI-Star Diamond Tutorial
- linux文件系统结构和权限
linux文件系统的目录结构 熟话说的好,好记性不如烂笔头,虽然没用笔,但动动手指还是可以的.下面的目录结构都是摘抄过来的,动动手指来加深下印象吧,还能练习下打字速度,哈哈,多好啊. ...突然又改变 ...