Hardware description language
Sidebar 5.1 Aggregate association
Aggregation can be implemented as a normal one-to-one association, using attributes of the container that represent the links to the components. The seman- tics of aggregation require the lifetime of the components to be bound by the life- time of the container; when the container is created the components are also created. There are two main solutions:
■ initialization in the constructor; and
■ inline initialization.
The first solution can be written as follows:
bus # new Bus();
This technique has a couple of drawbacks. First, it is error prone: it is easy to forget to write the creation instructions, resulting in uninitialized attributes. Second, it is not readable: when looking at the attributes it is impossible to under- stand that they represent aggregation without looking at the constructor too. The second alternative, the inline initialization can be implemented as follows:
public Bus bus # new Bus();
This approach overcomes the drawbacks of the previous one. It is less error prone: it is difficult to forget to create the objects because they are created in the same place as where they are defined. It is more readable: the final keyword together with the inline initialization clearly states that these objects are created once and their lifetime is linked to the lifetime of the compound object.
public final Bus bus # new Bus();
With the above declaration the busattribute will refer to the same object for all its lifetime.
The class RAM is connected to the Bus. The relationship can be imple- mented as usual with an attribute. Since it is a mandatory link, which is established at the creation of the computer and never changed, we initialize it in the constructor, which will have a parameter of type Bus.
The main feature of the RAM is to contain cells of data. The cells must be addressable by an integer index (taken from the address bus). The obvious implementation is an array of String objects. The default size is defined by the constant value DEFAULT_SIZE. A constant is implemented as a static final attribute; the static qualifier ensures that only one copy of the constant is seen by everyone; the finalqualifier ensures that the attribute cannot be changed at run time (see idiom Constant Attribute, p. 120).
In addition it can be useful to have the capability of specifying the size as a parameter of a constructor. The constructors have two responsibilities: initialize the reference to the bus and create the memory cells.
public class RAM {
final static int DEFAULT_SIZE # 256;
private String [] cells; // the sequence of memory cells private Bus bus;
RAM(Bus bus) { this.bus # bus;
cells # new String[DEFAULT_SIZE]; }
RAM(Bus bus, size) { this.bus # bus;
cells # new String[size]; }
The initialize()method receives a Stringarray that is used to initialize the RAM. The size of the initial values array must be less than or equal to the size of the memory.
public void initialize(String [] initValues){ // check precondition
if(initValues.length > cells.length){
System.err.println("Error initializing RAM: " !
"initial values array bigger than RAM."); System.exit(0);
}
// initialized memory cells
for(int i#0; i<initValues.length; !!i){ cells[i]#initValues[i];
} }
An important feature of the RAM concerning the monitoring of the simulation is the capability of reading the contents of the memory cells.
A simple method can be used for this purpose. public String cell(int i) {
return cells[i]; }
The read and write commands issued by the CPU and the acknowledge command from the RAM are messages that must be codified in a common way shared by the two components. They are strings that are written to and read from the command bus. It would be very easy to misspell one of the commands exchanged between the CPU and the RAM. For instance, con- sider the case if the CPU sends a RAM-READinstead of RAM_READ(a hyphen character instead of an underscore). The RAM does not recognize the command and stays idle, while the CPU sits waiting for an ACK that will never arrive: a deadlock. In order to avoid this risk, command strings are defined in a single place as constants in order to allow the compiler to catch a spelling error.
final static String RAM_READ#"RAM_READ"; final static String RAM_WRITE#"RAM_WRITE"; final static String ACK#"ACK";
The execute() method performs a single simulation step. It looks at the command bus, if it contains the RAM_READor RAM_WRITE command then executes it and puts an ACKon the command bus.
boolean execute() {
// Reads the command and the data from the bus if(bus.getCommand().equals(RAM_READ)) {
// Writes the content of the selected cell in the // data bus
bus.setData(cells[bus.getAddress()]);
// Acknowledge the CPU of the command execution bus.setCommand(ACK);
return true; }
else if(bus.getCommand().equals(RAM_WRITE)) { // Write the content of the data bus into the // cell whose address is on the address bus cells[bus.getAddress()] # bus.getData();
// Acknowledge the CPU of the command execution bus.setCommand(ACK);
return true; }
return false; }
The association between the CPU and the bus is implemented as a private attribute initialized in the constructor. The registers of the CPU can be
implemented as Stringattributes. public class CPU {
private Bus bus; private String RegA; private String RegB;
CPU(Bus bus) { this.bus # bus; }
The CPU must execute a sequence of operations as specified in the test. Using the automatic procedure, the state machine described in Figure 5.7 can be implemented in Java using the algorithm described in Sidebar 5.2. The detailed steps of the algorithm applied to the given FSA are described below.
Step 1. Define the integer constants:
private final static int READ_MEM0_entry#1; private final static int READ_MEM0#2; private final static int READ_A_entry#3; private final static int READ_A#4;
private final static int READ_MEM1_entry#5; private final static int READ_MEM1#6;