您的位置:首页 > 其它

Cache控制器设计

2016-06-07 23:22 246 查看


Cache控制器设计


一、 实验目的

(1)
认识和掌握Cache控制器的原理及其设计方法;
(2)
掌握Cache控制器的实现方法,代码实现方法。
 

二、实验内容

本实验要求采用直接相联地址变换,实现Cache(数据Cache)及其地址变换逻辑(也叫Cache控制器)。CPU从Cache读数据,读到就送CPU,若读不到,还必须考虑先从主存中读取数据,然后再将数据写到Cache中,之后,将数据送往CPU;其次,还要考虑CPU修改Cache和存储器数据的情况。
说明:CLK为系统时钟(用于计数器计数控制等操作),CLR为系统总清零信号(清区表存储器、计数器),WCT为写Cache区表存储器信号,AB31..AB0为CPU访问内存的地址(地址总线),RD(为0,读)为Cache的读信号,CMWr(为1,写)为Cache和主存的写信号,MRd(为0,读)为主存的读信号,CA17..CA0为Cache地址,MD31..MD0为主存与Cache的数据传送线,D31..D0为Cache和CPU的数据传送线,LA3...LA0为块内地址。
 

三、实验原理

本实验采用的地址变换是直接相联映象方式,这种变换方式简单而直接,硬件实现很简单,访问速度也比较快,但是块的冲突率比较高。其主要原则是:主存中一块只能映象到Cache的一个特定的块中。
假设主存的块号为B,Cache的块号为b,则它们之间的映象关系可以表示为:
b = B mod Cb
其中,Cb是Cache的块容量。设主存的块容量为Mb,区容量为Me,则直接映象方法的关系如图1所示。把主存按Cache的大小分成区,一般主存容量为Cache容量的整数倍,主存每一个分区内的块数与Cache的总块数相等。直接映象方式只能把主存各个区中相对块号相同的那些块映象到Cache中同一块号的那个特定块中。例如,主存的块0只能映象到Cache的块0中,主存的块1只能映象到Cache的块1中,同样,主存区1中的块Cb(在区1中的相对块号是0),也只能映象到Cache的块0中,看图1。根据上面给出的地址映象规则,整个Cache地址与主存地址的低位部分是完全相同的。 


图1
直接相联映象方式
    直接 映象方式的地址变换过程如图2
所示,主存地址分为三个部分:区号E、块号B和块内地址W;Cache地址分为两部分:块号b和块内地址w。主存地址中的块号B与Cache地址中的块号b是完全相同的。同样,主存地址中的块内地址W与Cache地址中的块内地址w也是完全相同的,主存地址比Cache地址长出来的部分称为区号E。



图2
直接相联地址变换示意图
在程序执行过程中,当要访问Cache时,为了实现主存块号到Cache块号的变换,需要有一个存放主存区号的小容量存储器(称为区表存储器),这个存储器的容量与Cache的块数相等,字长为主存地址中区号E的长度,另外再加一个有效位。
从主存地址到Cache地址的变换过程中,首先用主存地址中的块号B去访问区表存储器(用块号B作为区表存储器的地址,访问它),然后,将读出来的区号与主存地址中的区号E进行比较,比较结果相等,有效位为1,则Cache命中,表示要访问的那一块已经装入到Cache中了,可以直接用块号及块内地址组成的缓冲地址到缓存Cache中取数,把读出来的数据送往CPU;如果比较结果不相等,有效位为1,可以进行替换,如果有效位为0,可以直接调入所需块。至于比较不相等情况,不论有效位是1或0均为Cache没有命中,或称为Cache失效,表示要访问的那个块还没有装入到Cache中,这时,要用主存地址去访问主存储器,先把该地址所在的块读到Cache中,然后CPU再从Cache中读取该地址中的数据。Cache和CPU以及存储器的关系如下图所示。



图3  Cache的基本框架图
如图3所示,32位主存地址为AB31..AB0(地址总线),RD(为0,读)为Cache的读信号,CMWr(为1,写)为Cache和主存的写信号,MRd(为0,读)为主存的读信号,D31..D0为CPU与Cache的数据传送线,MD31..MD0为存储器RAM与Cache的数据传送线。
如图4所示,区号E取14位,块号B为14位,块内地址为4位,这样Cache地址就是18位了,其中Cache块号b为14位,块内地址w为4位,所以Cache容量为256KB(2^18)个单元,块号b取14位,那么Cache分为16KB(2^14)块,块内地址w取4位,则每块为16个单元(每个单元一个字节)。



图4  地址分段划分示意图
实现Cache的存储体的方法是先实现一个8位的存储单元,然后用这个8位的存储单元来构成一个256Kb X
8位的Cache(地址18位)。
再实现一个15位(14+1)的存储单元,然后,用这个15位的存储单元来构成一个16k X
15位的区表存储器(地址14位与块号B相同),用来存放区号(14位)和有效位(1位)。在这个部分中,还要实现一个区号E比较器,也就是如果主存地址的区号E和区表存储器中按块号B取出的相应单元中的区号E相等,有效位标志为M,且有效位为1,M=1时,则Cache命中,否则Cache失效,M=0时表示Cache失效。
当Cache命中时,就将Cache存储体中相应单元的数据送往CPU,这个过程比较简单。当Cache失效时,就将主存中相应块中的数据读出写入Cache中,这样Cache控制器就要产生主存储器的读信号MRd(为0,读),由于每个Cache块占十六个单元,按32
位(4个字节)为访问存储器单位,那么需要连续访问4次主存,读取存储器中该块的数据,即16个字节,然后写入Cache相应块中,最后再修改区表存储器(标志位M=1)。至于访问主存的方法,要用到计数器。写数据时,如果Cache中有该地址数据,则修改,同时,修改存储器该地址内容(CMWr为1,写,为主存的写信号);如果Cache中无该地址数据,就直接修改存储器该地址单元内容,这时无需修改Cache。读/写存储器时,要注意互锁情况。
 

四、实验设备

PC机一台,BASYS 2 实验板一块?,Xilinx ISE 开发软件一套。

五、实验过程
(1)首先实现cache模块,cache模块中有一个区表寄存器,存储的是区号以及一个有效位,利用cpu地址中块号地址作为索引搜索区号,与cpu地址中的区号作比较,同时记录其中有效位的值;模块中另外有一个缓存器,存的是相应块的数据,每一个块为16个字节,即4个32位的数据。情况一:cpu发出读指令,cache中查找区表寄存器并比较区号以及获取有效位,区号相等并且有效位为1,直接从缓存器中读取相应块以及块内地址所存储的值,传回到cpu中。情况二:cpu发出读指令,cache中查找区表寄存器并比较区号,区号不相等或者有效位为0,意味着缓存器中并没有存储相应的数据,因此需要读存储器RAM,此处做法为,先从RAM中读取相应的块的数据(4个32位数据)即要读取4次,我在这里利用了一个times作为计数器,传到RAM中读取数据,即RAM和Cache中各自多了一个接口,读取到的相应块的数据存储到缓存器中,改变缓存器和区表寄存器相应值,然后再将数据从缓存器中读取数据到cpu。情况三:cpu发出写指令,cache中查找区表寄存器并比较区号以及获取有效位,区号不相等或者有效位不相等,只需要直接将数据传到RAM中,进行写存储器工作。情况四:cpu发出写指令,cache中查找区表寄存器并比较区号,区号相等并且有效位为1,先改变相对应的地址的缓存器的值,然后将数据传到RAM中,进行写存储器工作。
(2)接下来实现的是RAM模块,RAM模块需要一个存储器,在addr来临以及在上述times来时分别进行写操作和读操作。
(3)用一个顶层模块将Cache和RAM连接在一起。
(4)用一个测试文件,调用顶层模块进行数据测试。
 
注:存储方式均为低位地址存高位数据。

代码参考:cache

module Cache(clk, clr, addr, rd, mwr, ramdata, mrd, data, times);
input[31:0] addr;
input rd, mwr, clk, clr;
output reg mrd;
output reg[1:0] times;
inout[31:0] data, ramdata;
//input[31:0] datain;
reg[31:0] dataout, datatoram;
reg[14:0] regiontable[63:0];
reg[7:0] cachetable[63:0];
reg[13:0] regionnumber, blocknumber; //区号,块号
reg[14:0] tablevalue; //区表存储器的值
reg isequal, valid;
reg[13:0] tablenumber; //区表寄存器的区号
reg[3:0] insideblock; //块内地址
reg[13:0] result;
reg[17:0] cacheaddr, blockaddr;
integer i;
reg[14:0] mid;

assign data = (mwr == 1) ? 32'bz : dataout;
assign ramdata = (mwr == 1) ? datatoram : 32'bz;

always@(negedge clr)
begin
if(!clr)
begin
for(i = 3; i < 64; i=i+1)
begin
regiontable[i] = 15'h0000;
end
for(i = 1; i < 64; i=i+1)
begin
cachetable[i] = 8'h00;
end
regiontable[3] = 15'h0001;
regiontable[2] = 15'h0010;
regiontable[1] = 15'h0000;
regiontable[0] = 15'h0001;
cachetable[0] = 8'h03;
end
end

always@(posedge clk)
begin
#1;
//块号,区号,块内地址,缓存地址,块的初始地址
blocknumber = addr[31:18];
regionnumber = addr[17:4];
insideblock = addr[3:0];
cacheaddr = addr[17:0];
blockaddr[17:4] = addr[17:4];
blockaddr[3:0] = 0;
//从regiontable中找相应的区号
tablevalue = regiontable[regionnumber];
valid = tablevalue[0];
tablenumber = tablevalue[14:1];

//比较是否相等
result = tablenumber - blocknumber;
if(result == 0) isequal = 1;
else isequal = 0;

if(isequal && !mwr && !rd && valid) //读指令且区号相同
begin
mrd = 1;
dataout[31:24] = cachetable[cacheaddr];
dataout[23:16] = cachetable[cacheaddr+1];
dataout[15:8] = cachetable[cacheaddr+2];
dataout[7:0] = cachetable[cacheaddr+3];
end

else if(isequal && mwr && valid) //写指令且区号相同
begin
mrd = 1;
datatoram = data;
cachetable[cacheaddr] = data[31:24];
cachetable[cacheaddr + 1] = data[23:16];
cachetable[cacheaddr + 2] = data[15:8];
cachetable[cacheaddr + 3] = data[7:0];
end

else if((!isequal || !valid) && mwr) //写指令但区号不相等
begin
mrd = 1;
datatoram = data;
end

else if(!rd && !mwr && (!isequal || !valid)) //读指令但区号不相等
begin
mrd = 0;
times = 0;
for(i = 0; i < 4; i=i+1)
begin
#10;
cachetable[blockaddr+4*i] = ramdata[31:24];
cachetable[blockaddr+4*i+1] = ramdata[23:16];
cachetable[blockaddr+4*i+2] = ramdata[15:8];
cachetable[blockaddr+4*i+3] = ramdata[7:0];
times=i+1;
end
dataout[31:24] = cachetable[cacheaddr];
dataout[23:16] = cachetable[cacheaddr+1];
dataout[15:8] = cachetable[cacheaddr+2];
dataout[7:0] = cachetable[cacheaddr+3];
mid[14:1] = blocknumber;
mid[0] = 1;
regiontable[regionnumber] = mid;
end
end

endmodule
RAM:
module Ram(addr, times, mwr, mrd, ramdata);
input[31:0] addr;
input mwr, mrd;
input[1:0] times;
inout[31:0] ramdata;
reg[31:0] dataout;
reg[7:0] ram[127:0];
reg[31:0] blockaddr;
integer i;

initial
begin
for(i = 0; i < 128; i=i+1)
begin
ram[i] = 0;
end
ram[0] = 8'h03;
ram[16] = 8'h12;
ram[17] = 8'h34;
ram[20] = 8'h77;
ram[25] = 8'ha8;
ram[31] = 8'h60;
end

assign ramdata = (mwr == 1) ? 32'bz : dataout;

always@(addr)
begin
if(mwr)
begin
#2;
ram[addr] = ramdata[31:24];
ram[addr + 1] = ramdata[23:16];
ram[addr + 2] = ramdata[15:8];
ram[addr + 3] = ramdata[7:0];
end
end

always@(times)
begin
if(!mrd)
begin
blockaddr[31:4] = addr[31:4];
blockaddr[3:0] = 0;
dataout[31:24] = ram[blockaddr + times*4];
dataout[23:16] = ram[blockaddr + 1 + times*4];
dataout[15:8] = ram[blockaddr + 2 + times*4];
dataout[7:0] = ram[blockaddr + 3 + times*4];
end
end

endmodule

测试部分:
#50;
clr = ~clr; //清零并初始化
#50; //读指令且区号相等
clr = ~clr;
clk = ~clk;
rd = 0;
mwr = 0;
addr = 32'h00000000;

#50;
clk = ~clk;
#50; //读指令且区号不相等
clk = ~clk;
rd = 0;
mwr = 0;
addr = 32'h00000010;

#50;
clk = ~clk;
#50; //写指令且区号不相等
clk = ~clk;
mwr = 1;
rd = 1;
addr = 32'h00000020;
datain = 32'h88888888;
#50;
clk = ~clk;

#50; //写指令且区号相等
clk = ~clk;
mwr = 1;
rd = 1;
addr = 32'h00000030;
datain = 32'h12345678;

#50;
clk = ~clk;
最后进行仿真:



最后各个存储单元的值为:
                      cachetable(缓存器)



                      regiontable(区表寄存器)



                      RAM(存储器)



六、遇到的问题与解决办法
(1)遇到的问题:计数器不知道该怎么实现,使得cache模块能读取4次存储器。
 解决办法:为cache和ram各自多了一个接口times,times改变时,ram会接收到信息并传出数据到cache中,令times改变4次,使存储器能执行4次读操作。
 
(2)遇到的问题:cpu和cache以及cache和ram的数据交换只用了一条线,即相应接口有输入也有输出功能。
         解决办法:利用inout声明,做一个三态缓存器,利用mwr或者rd控制变量是输入或者是输出。
 
(3)遇到的问题:在时钟端上升沿来临时,因为有一点延时导致cache不能及时得到数据或者ram不能及时得到要写入的数据,使得数据读写出错。
         解决办法:在always里加入了延迟,比如#1;的1ns延迟和#10;的10ns延迟。
 
 
七、实验总结和心得
本次实验认识和掌握Cache控制器的原理及其设计方法以及掌握Cache控制器的实现方法,代码实现方法。
对于cache的理解更加深入,工作的机制和原理比较清晰。
其中遇到了比较多的问题,逐一上网查询解决办法,并逐个进行尝试,并最终得到了解决,对于个人的信心有极大的提高。
在计数器的处理上可能存在着一些漏洞,使用的方法有点取巧,但是总的来说比较完整的完成实验。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  缓存 设计