Data Memory

chapter 5

BrainIO has two main memory: The program memory and the data memory.
5.1 Why do we need a Data memory?
Two main storage unit of all cpu models are the registers and memory. While registers are definite in size, depending on the design it can get upto thirty registers. The memory is a combination of registers which can get upto 1 gigabytes of registers. High level languages on the other hand uses lots of variables including those used for counting in loops and for rewritable programs where their numbers are not fixed , it can vary upto any number. The brainIO C codes takes over fifty different varaibles used at different points. Therefore, when designing cpu models that involves storage of indefinite number of varaibles, its a good design to use memory rather than registers.
In chapter three we discussed the program memory. We know that this memory does not store data or modify its data while the program is running. We simply load instructions and it executes. In high level programming we use variables, Which plainly interprets as items that change or vary. During program execution these variables change and modify its value depending on the instruction executing them. While the program memory stores constants and instructions which do not change during program execution, another memory which stores variables or values that are modified during program execution called data memory needs to be implemented.

5.2 The pinout
The data memory pinout is given above. The ram address (ram_addr) is 8-bit wide while the ram data (ram_data_in) is 16-bit. The ram chip_select (ram_cs), switches between ram read/write. It reads from the ram when low and writes to the ram when high. The ram_sel is used for register indirect addressing mode. It acts like a push/pop button that either increments the current address or decrements it, rather than having an actual ram address. This addressing can be useful when initially loading all the data variables serially into the ram. The ram_sel signal has two bits , Its two bits represents four states: 00→PEEK, 01→PUSH, 10→POP, 11→MAN_SELECT. The PUSH and POP increments and decrements the last recorded address to form a new ram address. This address is stored is a separate register within the ram unit. The PEEK which is 00, bypass the ram_sel input to use an actual ram address contained in the instruction without recording it as the last address. The MAN_SELECT does same function as PEEK but records the address as the last address which would be used for the next PUSH/POP instruction. The ram has only two outputs, the ram_data_out and the ram_full (turns on when the ram is full).

5.3 The Schematic
The data memory schematic is given above. Four comparators at the bottom left are used to compare the ram_addr_sel signal. The output is either:pop, push, man_sel, or peek. The pop operation is best used when the memory needs to be cleared for insertion of new program variables. The value zero can be set as the data, as the address regisetr reduces from top to bottom initializing the ram to zero. The push will be used at the start of a new program to push all program variables into the ram serially, as the address register increases from zero towards the size of the ram. PEEK is the default format for all register direct addressing. The main difference between peek and man_sel is that the former does not modify the address register while the later modifies the address register after storing storing its value as the ram_address. A single multiplexer found at the center of the schematic, is used during a peek operation. It sends the ram address directly to the ram without passig through the address register.
The address register is a 5-bit register that can found at the top-right side of the schematic. Two 'AND' gates, One is used to ensure that the push operation does not signal the address register when it is full i.e when the address register is equal to x'1f' While the other 'AND' gate ensures that the pop operation does not signal the address register when its empty i.e when the address register is eual to x'00'. From the schematic, a one bit adder and another one bit subtractor is used for a push and pop operation respectively. They increment and decrement the value in the address register and serves as input to three multiplexers directly connected to the address register. Depending on the signal, the multiplexers selects either the pop, push or man_select operation. Peek operation does not affect the regsiter.
The ram_data_out and the ram_data_in are both 16-bit which is the data size of brainIO while the ram address is 8-bit size. In VHDL, a wire splitter is used to select 8-bit out of every 16-bit data that is used as the ram address. In C language a wire splitter is not available, so a seperate function block called the split_bit() will be used to select 8-bit out of every 16-bit data.

5.4 datamemory.vhd
First let us declare the title.

Next is the IEEE; standard library

On line 33 we declare the entity called data_memory. The generic contains the ram address size and data width size. Both the data width and address size are 16-bit wide. The port consists of data pins discussed in the diagram above.

next we declare the architecture which consists of the signals and ram which we will discuss as we go through the code.

We define the RAM_SEL input single. Depending on what value it is, its output can either be: peek-(use the address on the RAM_ADDR input, and do not store it in the address register), man_sel-(means manual select. It uses the address on the RAM_ADDR input, and stores it in the address register), pop-(use the address on the address register and decrement it.), push-( use the address on the address register and increment it.).

Lets define the peek process block. The peek signal when high sets the ram address as the RAM_ADDR input, else the address register is set.

Aside from being used as an address, the address register can also tell the state of the ram. If the address register is equal the zero, the sig_empty is on else if the address register is full or equal to x"1f" in hexadecimal, then the sig_full is turned on. This is true if only the pop and push signal is used to address the ram.

The push and pop signal needs to pass through an ‘AND’ gate before they can finally signal the address register. The ‘AND’ gate makes sure the ram is not empty during a pop operation and the ram is not full during a push operation.

Let us set the address register.

Let us now write to the ram, by setting the ram chip select (RAM_CS) high. The address signal was set earlier and the data is the RAM_DATA_IN input.

Finally, the sig_full signal is assigned to the FULL output. We need to know when the ram is full but not when it is empty. The RAM_DATAOUT is output enable always and the address is also the addr signal.

5.5 datamemory.h
The databus unit provides the ram address and ram data. Remember it has two outputs, and depending on the type of instruction one of the outputs will serve as the ram address while the other will be the ram data. Therefore, we would include the ‘mult.h’ header file which is the databus header file. The databus is also referred to as multiplexer unit. We also include the ‘common.h’ . The brainIO has a 16-bit data size, and during a register indirect addressing, the 8-bit ram address is stored in the register. So we would need a new function that would extract bits 7 down to 0, from the 16-bit register. We call this function ‘splitbit()’. Therefore, I have included ‘splitbit.h’ to use this function.

The ram address can either be part of the instruction(direct addressing) or stored in a register (register indirect addressing). So we create an enum called addr_type to store this operation. ADDR_INSTR- for address in instruction and ADDR_INDR- for address stored in register.

Next, lets create another enum to store the : peek, pop, push and manual select operations. This enum is called ramOp.

Next, we create the data memory struct called data_memory dm. This will store the data memory output: RAM_DATA(output of the ram) and full(ram full).

Lastly, we would declare two functions for the ram read and write. These two functions would have three arguments.
1. addr_type: whether it is direct or indirect addressing.
2. address: stores the actual address for a direct addressing. For an indirect addressing it is either left as null or zero.
3. ramOp: For direct addressing, it is always peek.

5.6 datamemory.c
We include the ‘datamemory.h’ header file.

Next we would declare some variables we would use. The ram_in array is our ram. The rest would be discussed later.

The data to be written to the ram is stored in the data bus output called MULTOUT which belongs to the struct called databus_data . As for the address, different ram addressing needs different method of read or write. So, for the write operation we would first switch the type. If It is a direct addressing, we would store the data to the address contained in the instruction, that is the address in the argument. If it is a ‘register indirect addressing’ then the address is in the databus second output called regA which is also part of the databus_data struct. Since this databus data is 16-bit and the ram address is 8-bit, we would use the splitbit() function to extract bits 7 down to 0 of the databus output. The splitbit() function takes three arguments: the main data to extract from, the starting bit and the stop bit. After extracting the address, we need another function to know which address format to use , whether it is : pop, push, peek or man_sel. This function is called ‘switch_ram()’. It takes two argument, the address and the ram_addr_select. The ram_addr_select contains the actual select option.

The switch_ram() function switches between the different ram operations. For a man_sel -The address register stores the new address. For a push or pop - The address register increments and decrements respectively. For a PEEK- The address is returned without being stored. For each operation the ‘set_ram_flag()’ function is called to determine the state of the ram. This function determines if the ram is full after a push operation or empty after a pop operation.

After each operation, the ‘set_ram_flag()’ function checks if the size of the ram array is 0 or 1024. If it is 0, then it sets empty high and this prevents a further pop operation in the ‘switch_ram()’ function. If it is full it sets full high and prevents a further push operation. We need to know when the ram is full not when it is empty, so we store the full in the data memory struct called dm. This will make it accessible outside the data memory unit.

Lets define the read operation. The read operation is almost same with the write operation except that here there is data to be written rather data is read. We would also switch the address type and for a direct addressing we would use the address in the instruction and store the output in the data memory output called RAM_DATA which is stored in the data memory struct called dm. For indirect addressing we would use the bins variable to extract bits down to 0 from the data bus output called regA, contained in the databus struct. The generated address is passed to the switch ram function and the returned address is used to access the ram. The output is stored in the RAM_DATA.

split_bit() Cache