Specification:
ioOut is asynchronous; if (ioSelect), ioOut = DP1, else ioOut = DP0.
ioIn is asynchronously inputted, it takes from ioinput.trace while in simulation, otherwise it takes from the boards switches. While in simulation, every time it reads, it tries to pull in a new value, when ioinput.trace empties, it inputs 0.
dout's value is determined by the address.
if (0xfffffff0) dout = DP0;
if (0xfffffff4) dout = DP1;
if (0xfffffff8) dout = ioIn;
if (0xfffffffc) dout = counter;
counter increments every clock unless a reset is high.
every posedge if (we), check address.
if (0xfffffff0) DP0 = din
if (0xfffffff4) DP1 = din
otherwise nothing should change.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
input [31:0] addr; // the address to read or write, should be as early as possible for processing
input clk;
input rst;
input readEnb; //enable the cache to read, should be high 1 clock cycle before data comes out if hit
input writeEnb; //enable the cache to write, should be high the same time as data comes in
input writeDone; //should be high when writing to the last clock cycle of the block, as the last word is coming in
output miss; //indicate there is a miss, should stall the pipeline
output hit; //when high, indicate there is a hit
output [31:0] dataOUt; //output the data syncronously
On a read, address should be inputted as early as possible, and read should be enable, it will feed the address into both the tag file and data file. The data will come out the next cycle, syncronously. At this point, compare the tag with the address, if they match and valid, indicate a hit, indicate a miss.
When writing a block of 8 words, writeEnable should be on as each word comes in, the address should be incremented by 4, since cache will chop off the 2 least significant bits.
We are only writing one cache module, it's used for both instMem and dataMem.
We will modify to do 2 way associative once we get it to work, but the interface is the same. So we don't need to change anything on the top level.
Controller will generate a readEnb signal for the cache. For data cache, we will only read if the data is not in the write buffer, dataMem might take the signal from writebuffer. Or we can check if there is a hit from the write buffer, we can ignore the miss from the cache. This way we dont need to depend on the write buffer, it will execute in parallel.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
//write buffer read and write syncrously,
//I can make it do asyncronous read if need be,
//but syncronous is much better
input clk, rst;
input readNext; cause the buffer the empty out the first element, and decrement the count by 1, syncronously
input [31:0] dataIn; //writeData in should come in the same clock cycle as the addr and writeEnb should be high
input [31:0] addr; //read or write address
input writeEnb; //should be write when writing to the buffer, write syncronously
input readEnb; //should be high when reading from the buffer, data go out the next cycle
output empty; //when there is no data, empty is high, empty is low after the next cycle after a writeEnb is high
output full; //when there are 3 data in the buffer, and a writeEnb is high, then full is high and stay high until readNext is high
output dataMiss; should be high, if the data is not in the buffer, dataMiss comes out the same time as the data, and we can use it to control the mux between write buffer and cache.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
input [31:0] instAddr, //Address into instAddr
dataAddr; //Address into dataAddr
input instMiss, //Miss signal from inst
dataMiss; //Miss signal from data
input wbFull, //Write buffer is full
wbEmpty; //Write buffer is empty
input clockBufferBusy; //wire from memController saying if fifos are !empty
input [22:0] readAddr; //address from write buffer
output [22:0] address; //address we're gonna chop up
output [1:0] command;
Address will always be a 23 bit value, where it's value is determined by the command.
If write, the address <= readAddr,
if READINST, the address <= {instAddr [24:5], 3'b0}
if READDATA, the address <= {dataAddr[24:5],3'b0}
else don't care.
Command will be outputted based on the following priorities.
Highest priority.
RST or clockBufferBusy, should force a nop
If writeBuffer is full, it has next highest priority, so write.
If dataMiss, it has higher priority than instMiss. readData, else readInst
Finally,
if there's anything in the writebuffer, Write,
if nothing to be done, send a nop
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
another thing we can do for arbitractor is have the datamem hold the address until miss goes low, so arbitrator don't need to latch in the value and since the cache is latching in the value anyway, we can save some registers and simplified the logic a little bit
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
The readNext signal should be send as soon as memoryController receive the data, because there might be a sw that is writing to the very same address. It readNext is sent 2 cycles after memory received the data, another sw comes in, and will write over the data, since the data is already dirty, writeBuffer will not send it again. When readNext comes in, it will be flushed out. Make sure you test this case.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Attach any specifications you design. Along with brief description.
module io (addr, clk, din, dout, ioIn, ioOut, ioSelect, we, rst);
input [31:0] addr, // address
din, // input from ram, sync
ioIn; // input to IO, async
input clk, // clk
we, // write enable
ioSelect, // selects DP0 or DP1
rst; //resets io
output [31:0] dout, //output to ram, sync
ioOut; //output of IO, async
Specification:
ioOut is asynchronous; if (ioSelect), ioOut = DP1, else ioOut = DP0.
ioIn is asynchronously inputted, it takes from ioinput.trace while in simulation, otherwise it takes from the boards switches. While in simulation, every time it reads, it tries to pull in a new value, when ioinput.trace empties, it inputs 0.
dout's value is determined by the address.
if (0xfffffff0) dout = DP0;
if (0xfffffff4) dout = DP1;
if (0xfffffff8) dout = ioIn;
if (0xfffffffc) dout = counter;
counter increments every clock unless a reset is high.
every posedge if (we), check address.
if (0xfffffff0) DP0 = din
if (0xfffffff4) DP1 = din
otherwise nothing should change.
module cache (addr, clk, rst, readEnb, writeEnb, writeDone, dataOut, miss, hit);
input [31:0] addr; // the address to read or write, should be as early as possible for processing
input clk;
input rst;
input readEnb; //enable the cache to read, should be high 1 clock cycle before data comes out if hit
input writeEnb; //enable the cache to write, should be high the same time as data comes in
input writeDone; //should be high when writing to the last clock cycle of the block, as the last word is coming in
output miss; //indicate there is a miss, should stall the pipeline
output hit; //when high, indicate there is a hit
output [31:0] dataOUt; //output the data syncronously
On a read, address should be inputted as early as possible, and read should be enable, it will feed the address into both the tag file and data file. The data will come out the next cycle, syncronously. At this point, compare the tag with the address, if they match and valid, indicate a hit, indicate a miss.
When writing a block of 8 words, writeEnable should be on as each word comes in, the address should be incremented by 4, since cache will chop off the 2 least significant bits.
We are only writing one cache module, it's used for both instMem and dataMem.
We will modify to do 2 way associative once we get it to work, but the interface is the same. So we don't need to change anything on the top level.
Controller will generate a readEnb signal for the cache. For data cache, we will only read if the data is not in the write buffer, dataMem might take the signal from writebuffer. Or we can check if there is a hit from the write buffer, we can ignore the miss from the cache. This way we dont need to depend on the write buffer, it will execute in parallel.
module writeBuffer(clk, rst, dataIn, addr, readEnb, writeEnb, readNext, empty, full, dataOut, dataMiss);
//write buffer read and write syncrously,
//I can make it do asyncronous read if need be,
//but syncronous is much better
input clk, rst;
input readNext; cause the buffer the empty out the first element, and decrement the count by 1, syncronously
input [31:0] dataIn; //writeData in should come in the same clock cycle as the addr and writeEnb should be high
input [31:0] addr; //read or write address
input writeEnb; //should be write when writing to the buffer, write syncronously
input readEnb; //should be high when reading from the buffer, data go out the next cycle
output empty; //when there is no data, empty is high, empty is low after the next cycle after a writeEnb is high
output full; //when there are 3 data in the buffer, and a writeEnb is high, then full is high and stay high until readNext is high
output dataMiss; should be high, if the data is not in the buffer, dataMiss comes out the same time as the data, and we can use it to control the mux between write buffer and cache.
arbitrator(clk, //dram clock
rst,
instAddr,
dataAddr,
readAddr,
instMiss,
dataMiss,
wbFull,
wbEmpty,
clockBufferBusy,
//output
command,
address,
);
input clk, //dram clock
rst; //global reset
input [31:0] instAddr, //Address into instAddr
dataAddr; //Address into dataAddr
input instMiss, //Miss signal from inst
dataMiss; //Miss signal from data
input wbFull, //Write buffer is full
wbEmpty; //Write buffer is empty
input clockBufferBusy; //wire from memController saying if fifos are !empty
input [22:0] readAddr; //address from write buffer
output [22:0] address; //address we're gonna chop up
output [1:0] command;
command output = NOP = 0, WRITE = 1, READINST = 2, READDATA = 3;
Address will always be a 23 bit value, where it's value is determined by the command.
If write, the address <= readAddr,
if READINST, the address <= {instAddr [24:5], 3'b0}
if READDATA, the address <= {dataAddr[24:5],3'b0}
else don't care.
Command will be outputted based on the following priorities.
Highest priority.
RST or clockBufferBusy, should force a nop
If writeBuffer is full, it has next highest priority, so write.
If dataMiss, it has higher priority than instMiss. readData, else readInst
Finally,
if there's anything in the writebuffer, Write,
if nothing to be done, send a nop
command=3, address=0f9160.
# rst=0, instAddr=4df3819b, dataAddr=493e4592, readAddr=44df28, instMiss=1, dataMiss=1, wbFull=0, wbEmpty=0, clockBufferBusy=0.
# ERROR at time 13200000: DataAddr incorect.
.
.
.
# command=3, address=1dab00.
# rst=0, instAddr=7c41aff8, dataAddr=8376ac06, readAddr=0576ef, instMiss=1, dataMiss=1, wbFull=0, wbEmpty=0, clockBufferBusy=0.
# ERROR at time 25200000: DataAddr incorect.
.
.
.
# command=2, address=1aed80.
# rst=0, instAddr=86ebb60d, dataAddr=b87c1070, readAddr=4bf92d, instMiss=1, dataMiss=0, wbFull=0, wbEmpty=0, clockBufferBusy=0.
# ERROR at time 30400000: InstAddr incorect.
.
.
.
# command=2, address=08dbf0.
# rst=0, instAddr=6da36fdb, dataAddr=84651408, readAddr=426f75, instMiss=1, dataMiss=0, wbFull=0, wbEmpty=0, clockBufferBusy=0.
# ERROR at time 40800000: InstAddr incorect.
.
.
.
Fixed.
another thing we can do for arbitractor is have the datamem hold the address until miss goes low, so arbitrator don't need to latch in the value and since the cache is latching in the value anyway, we can save some registers and simplified the logic a little bit
The readNext signal should be send as soon as memoryController receive the data, because there might be a sw that is writing to the very same address. It readNext is sent 2 cycles after memory received the data, another sw comes in, and will write over the data, since the data is already dirty, writeBuffer will not send it again. When readNext comes in, it will be flushed out. Make sure you test this case.