Welcome to Memory Street

What does RAM look like? I’ll stop all the smartasses first: I know its just a bunch of little capacitors that store a bit. No, I’m not talking about the big sheep either. I’m interested in an abstraction of RAM so that it’s useful to a programmer. I almost never say “RAM”. I say “memory”. Hard drives are “storage”.

First, what is memory used for? It stores a number. A little piece of computer memory saves a number. That’s it. There’s no magic to determine if it’s a letter, word, symbol, or your ex’s phone number. It’s just a number. Programs do math on these numbers and move them around in memory. When numbers are put in the correct memory spot, the computer will do something like show a letter on the screen. Intel and IBM got to decide where to put numbers so the computer does something. I know, it’s not fair.

Hopefully, the next obvious question is: Where do I put numbers?
Every piece of memory (that stores just 1 number) has an address. Let’s go with an analogy. Image a street that stretches for miles; we’ll call it Memory St. On one side of it there are houses. And every house has a number. The first house is #1, the second house is #2, and the house go on for miles. Nobody lives on the other side of the street, so the addresses are in order. (Unlike so many neighborhoods where two adjacent houses are 100 numbers apart)

A number lives in each house. The same number could even live in lots of houses. (Like rich people have multiple houses) Each house has its own address and that’s how numbers know where they live. When a program needs to use a number from Memory St, it tells the community gate manager “I need to speak with whoever lives at house 423.” The community manager drags whoever lives in house 423 out into the street for and he’s sent to the CPU for processing. The CPU uses these addresses to move numbers around in memory. It gets to decide who lives where on Memory St.

Where do Big Number Live?

Each house can only hold a number between 0 and 255. If the computer needs a bigger number, it must live in multiple adjacent houses. The biggest number that can live in two houses is 65535. 2^32 must live across 4 houses.

What’s My Address?

Memory addresses aren’t exactly like that: It’s not #753664 Memory Street, David’s Computer, WA. Its just the number, in hexadecimal form: 0xB8000. That and the next 32,767 houses are where you can put things to show up on the screen.*

It’s Not Called a House

They aren’t called houses. They are called bytes. And the biggest number that can fit in 1 byte is 255.

 

 

 

 

*Well, you can’t put stuff there unless you map your address space to the identity. And you can’t do that unless you write your own operating system.
If you’re interested in how I did it, here is some of the code. There is a class called VGADriver that keeps some bookkeeping information. I didn't include the class's declaration.

typedef uint16_t VGAChar;

VGAChar* VGA_BUF_ADDR = (VGAChar*)0xB8000;
static constexpr uint16_t VGA_WIDTH_DEFAULT = 80;
static constexpr uint16_t VGA_HEIGHT_DEFAULT = 25;
static constexpr uint16_t VGA_TAB_STOPS = 3;

enum class VGA_COLORS : uint8_t
{
   Black = 0x0,
   Blue = 0x1,
   Green = 0x2,
   Cyan = 0x3,
   Red = 0x4,
   Magenta = 0x5,
   Brown = 0x6,
   LightGray = 0x7,
   DarkGray = 0x8,
   LightBlue = 0x9,
   LightGreen = 0xa,
   LightCyan = 0xb,
   LightRed = 0xc,
   Pink = 0xd,
   Yellow = 0xe,
   White = 0xf,
};

constexpr uint8_t operator&(const VGA_COLORS c, const int mask)
{
   return uint8_t(uint8_t(c) & uint8_t(mask));
}

//Combines the forground, background, and blinking parameters to make an attribute.
constexpr uint8_t vgaMakeAttribute(VGA_COLORS foreground,
                                   VGA_COLORS background,
                                   bool blink)
{
   return uint8_t(blink ? 0x80 : 0) | uint8_t((background & 0xf) << 4) | uint8_t(foreground & 0x0F);
}

//Returns a formatted character that can be written to the screen buffer.
constexpr VGAChar vgaMakeChar(uint8_t c,
                              uint8_t attribute)
{
   return (uint16_t(attribute) << 8) | uint16_t(c);
}

//Writes a character to the screen buffer at the next available slot.
void VGADriver::WriteCharacter(uint8_t vgaAttr, char character)
{
   switch (character)
   {
      case '\n':
      {
         this->_cursorRow = uint16_t((this->_cursorRow + uint16_t(1)) % this->_height);
         this->_cursorCol = 0;
         break;
      }
      case '\t':
      {
         this->_cursorCol = uint16_t((this->_cursorCol + VGA_TAB_STOPS) % this->_width);
         break;
      }
      default:
      {
         //Write the character
         PutCharacter(this->_cursorRow, this->_cursorCol, vgaMakeChar(character, vgaAttr));

         if ((this->_cursorCol + uint16_t(1)) == this->_width)
         {
            this->_cursorRow = uint16_t((this->_cursorRow + uint16_t(1)) % this->_height);
            this->_cursorCol = 0;
         }
         else
         {
            this->_cursorCol = uint16_t((this->_cursorCol + uint16_t(1)) % this->_width);
         }

         break;
      }
   }
}

//Writes a string to the screen buffer and returns the length of the string.
size_t VGADriver::WriteString(uint8_t vgaAttr, const char* str)
{
   const size_t strlength = MemoryLength(str);
   for (int i = 0; i < strlength; i++) 
   { 
      WriteCharacter(vgaAttr, str[i]); 
   } 
   return strlength; 
} 

//Writes a character to the screen buffer at the specified location. 
void VGADriver::PutCharacter(uint16_t row, uint16_t col, VGAChar character) 
{ 
   VGA_BUF_ADDR[row * this->_width + col] = character;
}