/**
* ****************************************************************************
* Copyright (c) 2010-2016 by Min Cai (min.cai.china@gmail.com).
* <p>
* This file is part of the Archimulator multicore architectural simulator.
* <p>
* Archimulator is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* <p>
* Archimulator is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License
* along with Archimulator. If not, see <http://www.gnu.org/licenses/>.
* ****************************************************************************
*/
package archimulator.isa;
import archimulator.common.BasicSimulationObject;
import archimulator.common.CPUExperiment;
import archimulator.common.Simulation;
import archimulator.common.SimulationObject;
import archimulator.common.report.ReportNode;
import archimulator.common.report.Reportable;
import archimulator.os.Kernel;
import archimulator.uncore.cache.CacheGeometry;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*;
/**
* Memory.
*
* @author Min Cai
*/
public class Memory extends BasicSimulationObject<CPUExperiment, Simulation>
implements SimulationObject<CPUExperiment, Simulation>, Reportable {
private int id;
private boolean littleEndian;
private Map<Integer, Page> pages;
private Kernel kernel;
private int processId;
private int numPages;
private Map<Integer, ByteBuffer> byteBuffers;
private boolean speculative;
private Map<Integer, List<SpeculativeMemoryBlock>> speculativeMemoryBlocks;
/**
* Create a "memory".
*
* @param kernel the kernel
* @param littleEndian a value indicating whether the memory is little endian or not
* @param processId the ID of the process
*/
public Memory(Kernel kernel, boolean littleEndian, int processId) {
super(kernel);
this.kernel = kernel;
this.littleEndian = littleEndian;
this.processId = processId;
this.id = this.kernel.currentMemoryId++;
this.pages = new TreeMap<>();
this.byteBuffers = new HashMap<>();
this.speculative = false;
this.speculativeMemoryBlocks = new TreeMap<>();
}
/**
* Read a byte at the specified address.
*
* @param address the address
* @return a byte at the specified address
*/
public byte readByte(int address) {
byte[] buffer = new byte[1];
this.access(address, 1, buffer, false, true);
return buffer[0];
}
/**
* Read a half word at the specified address.
*
* @param address the address
* @return a half word at the specified address
*/
public short readHalfWord(int address) {
byte[] buffer = new byte[2];
this.access(address, 2, buffer, false, true);
return ByteBuffer.wrap(buffer).order(this.littleEndian ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN).getShort();
}
/**
* Read a word at the specified address.
*
* @param address the address
* @return a word at the specified address
*/
public int readWord(int address) {
byte[] buffer = new byte[4];
this.access(address, 4, buffer, false, true);
return ByteBuffer.wrap(buffer).order(this.littleEndian ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN).getInt();
}
/**
* Read a double word at the specified address.
*
* @param address the address
* @return a double word at the specified address
*/
public long readDoubleWord(int address) {
byte[] buffer = new byte[8];
this.access(address, 8, buffer, false, true);
return ByteBuffer.wrap(buffer).order(this.littleEndian ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN).getLong();
}
/**
* Read a block of bytes at the specified address.
*
* @param address the address
* @param size the size in bytes of the block
* @return a block of bytes at the specified address
*/
public byte[] readBlock(int address, int size) {
byte[] buffer = new byte[size];
this.access(address, size, buffer, false, true);
return buffer;
}
/**
* Read a string of the specified size at the specified address.
*
* @param address the address
* @param size the size of the string to be read
* @return a string of the specified size at the specified address
*/
public String readString(int address, int size) {
byte[] data = this.readBlock(address, size);
StringBuilder sb = new StringBuilder();
for (int i = 0; data[i] != '\0'; i++) {
sb.append((char) data[i]);
}
return sb.toString();
}
/**
* Write a byte at the specified address.
*
* @param address the address
* @param data one byte of data to be written
*/
public void writeByte(int address, byte data) {
byte[] buffer = new byte[]{data};
this.access(address, 1, buffer, true, true);
}
/**
* Write a half word at the specified address.
*
* @param address the address
* @param data one half word of data to be written
*/
public void writeHalfWord(int address, short data) {
byte[] buffer = new byte[2];
ByteBuffer.wrap(buffer).order(this.littleEndian ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN).putShort(data);
this.access(address, 2, buffer, true, true);
}
/**
* Write a word at the specified address.
*
* @param address the address
* @param data one word of data to be written
*/
public void writeWord(int address, int data) {
byte[] buffer = new byte[4];
ByteBuffer.wrap(buffer).order(this.littleEndian ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN).putInt(data);
this.access(address, 4, buffer, true, true);
}
/**
* Write a double word at the specified address.
*
* @param address the address
* @param data one double word of data to be written
*/
public void writeDoubleWord(int address, long data) {
byte[] buffer = new byte[8];
ByteBuffer.wrap(buffer).order(this.littleEndian ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN).putLong(data);
this.access(address, 8, buffer, true, true);
}
/**
* Write a string at the specified address.
*
* @param address the address
* @param data the string to be written
* @return the length of the string that is written
*/
public int writeString(int address, String data) {
byte[] buffer = (data + "\0").getBytes();
int bytesCount = buffer.length;
this.access(address, bytesCount, buffer, true, true);
return bytesCount;
}
/**
* Write a block of data of the specified size at the specified address.
*
* @param address the address
* @param size the size of the block to be written
* @param data the block of data to be written
*/
public void writeBlock(int address, int size, byte[] data) {
this.access(address, size, data, true, true);
}
/**
* Fill zeros of the specified size at the specified address.
*
* @param address the address
* @param size the size of zeros to be filled
*/
public void zero(int address, int size) {
this.writeBlock(address, size, new byte[size]);
}
/**
* Perform a read or write operation on the specified range of addresses.
*
* @param address the starting address
* @param size the size
* @param buffer the buffer
* @param write a value indicating whether the access is a read or write
* @param createNewPageIfNecessary a value indicating whether creating a new page if necessary
*/
public void access(int address, int size, byte[] buffer, boolean write, boolean createNewPageIfNecessary) {
if (this.speculative) {
this.doSpeculativeAccess(address, size, buffer, write);
} else {
this.doNonSpeculativeAccess(address, size, buffer, write, createNewPageIfNecessary);
}
}
/**
* Perform a speculative read or write operation on the specified range of addresses.
*
* @param address the starting address
* @param size the size
* @param buffer the buffer
* @param write a value indicating whether the access is a read or write
*/
private void doSpeculativeAccess(int address, int size, byte[] buffer, boolean write) {
int tag = address >> SpeculativeMemoryBlock.BLOCK_LOGSIZE;
int index = tag % SpeculativeMemoryBlock.COUNT;
SpeculativeMemoryBlock blockFound = null;
if (this.speculativeMemoryBlocks.containsKey(index)) {
for (SpeculativeMemoryBlock blk : this.speculativeMemoryBlocks.get(index)) {
if (blk.tag == tag) {
blockFound = blk;
break;
}
}
}
if (blockFound == null) {
blockFound = new SpeculativeMemoryBlock(tag);
if (!this.speculativeMemoryBlocks.containsKey(index)) {
this.speculativeMemoryBlocks.put(index, new ArrayList<>());
}
this.speculativeMemoryBlocks.get(index).add(0, blockFound);
this.doNonSpeculativeAccess(address & ~(SpeculativeMemoryBlock.BLOCK_SIZE - 1), SpeculativeMemoryBlock.BLOCK_SIZE, blockFound.data, write, false);
}
if ((size & (size - 1)) != 0 || size > SpeculativeMemoryBlock.BLOCK_SIZE || (address & (size - 1)) != 0) {
return;
}
int displacement = address & (SpeculativeMemoryBlock.BLOCK_SIZE - 1);
if (!write) {
System.arraycopy(blockFound.data, displacement, buffer, 0, size);
} else {
System.arraycopy(buffer, 0, blockFound.data, displacement, size);
}
}
/**
* Enter the speculative state.
*/
public void enterSpeculativeState() {
this.speculative = true;
}
/**
* Exit the speculative state.
*/
public void exitSpeculativeState() {
this.speculativeMemoryBlocks.clear();
this.speculative = false;
}
/**
* Perform a non-speculative access operation on the specified range of addresses.
*
* @param address the starting address
* @param size the size
* @param buffer the buffer
* @param write a value indicating whether the access is a read or write
* @param createNewPageIfNecessary a value indicating whether creating a new page if necessary
*/
private void doNonSpeculativeAccess(int address, int size, byte[] buffer, boolean write, boolean createNewPageIfNecessary) {
int offset = 0;
int pageSize = getPageSize();
while (size > 0) {
int chunkSize = Math.min(size, pageSize - getDisplacement(address));
this.accessPageBoundary(address, chunkSize, buffer, offset, write, createNewPageIfNecessary);
size -= chunkSize;
offset += chunkSize;
address += chunkSize;
}
}
/**
* Create pages for the specified range of addresses if necessary.
*
* @param address the starting address
* @param size the size
* @return the starting tag
*/
public int map(int address, int size) {
int tagStart, tagEnd;
tagStart = tagEnd = getTag(address);
int pageSize = getPageSize();
for (int pageCount = (getTag(address + size - 1) - tagStart) / pageSize + 1; ; ) {
if (tagEnd == 0) {
return -1;
}
if (this.getPage(tagEnd) != null) {
tagEnd += pageSize;
tagStart = tagEnd;
continue;
}
if ((tagEnd - tagStart) / pageSize + 1 == pageCount) {
break;
}
tagEnd += pageSize;
}
for (int tag = tagStart; tag <= tagEnd; tag += pageSize) {
if (this.getPage(tag) != null) {
throw new IllegalArgumentException();
}
this.addPage(tag);
}
return tagStart;
}
//TODO: fixme, and fix mmap and brk system call implementations!!!, see: http://www.makelinux.net/ldd3/chp-15-sect-1
// public void map2(int address, int size) {
// int tagStart = getTag(address);
// int tagEnd = getTag(address + size - 1);
//
// int pageSize = getPageSize();
//
// for (int tag = tagStart; tag <= tagEnd; tag += pageSize) {
// if(this.getPage(tag) == null) {
// this.addPage(tag);
// }
// }
// }
/**
* Remove pages for the specified range of addresses.
*
* @param address the starting address
* @param size the size
*/
public void unmap(int address, int size) {
int tagStart = getTag(address);
int tagEnd = getTag(address + size - 1);
int pageSize = getPageSize();
for (int tag = tagStart; tag <= tagEnd; tag += pageSize) {
this.removePage(tag);
}
}
/**
* Remap the specified range of addresses.
*
* @param oldAddr the old starting address
* @param oldSize the old size
* @param newSize the new size
* @return the new starting address
*/
public int remap(int oldAddr, int oldSize, int newSize) {
int start = this.map(0, newSize);
if (start != -1) {
this.copyPages(start, oldAddr, Math.min(oldSize, newSize));
this.unmap(oldAddr, oldSize);
}
return start;
}
private void copyPages(int tagDestination, int tagSource, int numPages) {
throw new UnsupportedOperationException(); //TODO: support it using BigMemory
// for (int i = 0; i < numPages; i++) {
// this.getPage(tagDestination + i * getPageSize()).bb = this.getPage(tagSource + i * getPageSize()).bb;
// }
}
/**
* Get the physical address of the specified virtual address.
*
* @param virtualAddress the virtual address
* @return the translated physical address of the specified virtual address
*/
public int getPhysicalAddress(int virtualAddress) {
return this.getPage(virtualAddress).physicalAddress + getDisplacement(virtualAddress);
}
/**
* Get the page containing the specified address.
*
* @param address the address
* @return the page containing the specified address if any exists; otherwise null
*/
private Page getPage(int address) {
int index = getIndex(address);
return this.pages.containsKey(index) ? this.pages.get(index) : null;
}
/**
* Add a page containing the specified address.
*
* @param address the address
* @return a newly created page containing the specified address
*/
private Page addPage(int address) {
int index = getIndex(address);
this.numPages++;
Page page = new Page(getExperiment().currentMemoryPageId++);
this.pages.put(index, page);
return page;
}
/**
* Remove a page containing the specified address.
*
* @param address the address
*/
private void removePage(int address) {
int index = getIndex(address);
this.pages.remove(index);
}
/**
* Perform a non-speculative access operation on the specified range of addresses at the page boundary.
*
* @param address the starting address
* @param size the size
* @param buffer the buffer
* @param write a value indicating whether the access is a read or write
* @param createNewPageIfNecessary a value indicating whether creating a new page if necessary
*/
private void accessPageBoundary(int address, int size, byte[] buffer, int offset, boolean write, boolean createNewPageIfNecessary) {
Page page = this.getPage(address);
if (page == null && createNewPageIfNecessary) {
page = this.addPage(getTag(address));
}
if (page != null) {
page.doAccess(address, buffer, offset, size, write);
}
}
/**
* Act on when a page is created.
*
* @param id the ID of the newly created page
*/
protected void onPageCreated(int id) {
this.byteBuffers.put(id, ByteBuffer.allocate(Memory.getPageSize()).order(isLittleEndian() ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN));
}
/**
* Act on when a page is accessed.
*
* @param pageId the ID of the page
* @param displacement the displacement within the page
* @param buffer the buffer
* @param offset the offset within the buffer
* @param size the size of data to be accessed within the buffer
* @param write whether the access is a read or write
*/
protected void doPageAccess(int pageId, int displacement, byte[] buffer, int offset, int size, boolean write) {
ByteBuffer bb = getByteBuffer(pageId);
bb.position(displacement);
if (write) {
bb.put(buffer, offset, size);
} else {
bb.get(buffer, offset, size);
}
}
@Override
public void dumpStats(ReportNode reportNode) {
reportNode.getChildren().add(new ReportNode(reportNode, "mem-" + getId()) {{
getChildren().add(new ReportNode(this, "numPages", getNumPages() + ""));
}});
}
/**
* Get the ID of the memory.
*
* @return the ID of the memory
*/
public int getId() {
return id;
}
/**
* Get a value indicating whether the memory is little endian or not.
*
* @return a value indicating whether the memory is little endian or not
*/
public boolean isLittleEndian() {
return littleEndian;
}
/**
* Get the kernel.
*
* @return the kernel
*/
public Kernel getKernel() {
return kernel;
}
/**
* Get the ID of the process.
*
* @return the ID of the process
*/
public int getProcessId() {
return processId;
}
/**
* Get a value indicating whether the memory is currently in the speculative mode or not.
*
* @return a value indicating whether the memory is currently in the speculative mode or not
*/
public boolean isSpeculative() {
return speculative;
}
/**
* Get the geometry of the memory.
*
* @return the geometry of the memory
*/
public static CacheGeometry getGeometry() {
return geometry;
}
/**
* Get the number of pages contained in the memory.
*
* @return the number of pages contained in the memory
*/
public int getNumPages() {
return numPages;
}
/**
* Get the byte buffer for the specified page ID.
*
* @param pageId the ID of the page
* @return the byte buffer for the specified page ID.
*/
private ByteBuffer getByteBuffer(int pageId) {
return this.byteBuffers.containsKey(pageId) ? this.byteBuffers.get(pageId) : null;
}
/**
* Get the name of the memory.
*
* @return the name of the memory
*/
@Override
public String getName() {
return getKernel().getProcessFromId(getProcessId()).getName() + "/mem";
}
/**
* Page.
*/
private class Page {
private int id;
private int physicalAddress;
/**
* Create a page.
*
* @param id the ID of the page that is to be created
*/
private Page(int id) {
this.id = id;
this.physicalAddress = this.id << Memory.getPageSizeInLog2();
onPageCreated(id);
}
/**
* Perform an access for the specified range of addresses.
*
* @param address the starting address
* @param buffer the buffer
* @param offset the offset within the buffer
* @param size the size of data to be accessed within the buffer
* @param write a value indicating whether the access is a read or write
*/
private void doAccess(int address, byte[] buffer, int offset, int size, boolean write) {
doPageAccess(id, getDisplacement(address), buffer, offset, size, write);
}
}
/**
* Speculative memory block. Used for representing a range of addresses involved in a speculative access.
*/
private class SpeculativeMemoryBlock {
private int tag;
private byte[] data;
/**
* Create a speculative memory block.
*
* @param tag the tag
*/
private SpeculativeMemoryBlock(int tag) {
this.tag = tag;
this.data = new byte[BLOCK_SIZE];
}
private static final int BLOCK_LOGSIZE = 8;
private static final int BLOCK_SIZE = 1 << BLOCK_LOGSIZE;
private static final int COUNT = 1024;
}
private static final CacheGeometry geometry = new CacheGeometry(-1, 1, 1 << 12);
/**
* Get the displacement for the specified address.
*
* @param address the address
* @return the displacement for the specified address
*/
public static int getDisplacement(int address) {
return CacheGeometry.getDisplacement(address, geometry);
}
/**
* Get the tag for the specified address.
*
* @param address the address
* @return the tag for the specified address
*/
private static int getTag(int address) {
return CacheGeometry.getTag(address, geometry);
}
/**
* Get the index for the specified address.
*
* @param address the address
* @return the index for the specified address
*/
private static int getIndex(int address) {
return CacheGeometry.getLineId(address, geometry);
}
/**
* Get the memory's page size in bytes in Log2.
*
* @return the memory's page size in in bytes in Log2
*/
public static int getPageSizeInLog2() {
return geometry.getLineSizeInLog2();
}
/**
* Get the memory's page size in bytes.
*
* @return the memory's page size in bytes
*/
public static int getPageSize() {
return geometry.getLineSize();
}
}