Skip to content

Commit

Permalink
Merge pull request #46 from Kautenja/ram_buffer
Browse files Browse the repository at this point in the history
Ram buffer
  • Loading branch information
Kautenja authored Jan 5, 2019
2 parents 85365c3 + c909ffd commit 73e10bb
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 95 deletions.
15 changes: 3 additions & 12 deletions nes_py/cpp/include/emulator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,20 +65,11 @@ class Emulator {
///
uint32_t* get_screen_buffer() { return ppu.get_screen_buffer(); };

/// Read a byte from a 16-bit memory address
/// Return a 8-bit pointer to the RAM buffer's first address.
///
/// @param address the address to read from memory
/// @return a 8-bit pointer to the RAM buffer's first address
///
/// @return the byte located at the given memory address
///
uint8_t read_memory(uint16_t address) { return bus.read(address); };

/// Write a byte to a 16-bit memory address
///
/// @param address the address to write to in memory
/// @param value the byte to write to the memory address
///
void write_memory(uint16_t address, uint8_t value) { bus.write(address, value); };
uint8_t* get_memory_buffer() { return bus.get_memory_buffer(); };

/// Load the ROM into the NES.
void reset();
Expand Down
6 changes: 6 additions & 0 deletions nes_py/cpp/include/main_bus.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ class MainBus {
/// Initialize a new main bus.
MainBus() : m_RAM(0x800, 0), m_mapper(nullptr) { };

/// Return a 8-bit pointer to the RAM buffer's first address.
///
/// @return a 8-bit pointer to the RAM buffer's first address
///
uint8_t* get_memory_buffer() { return &m_RAM.front(); };

/// Read a byte from an address on the RAM.
///
/// @param addr the 16-bit address of the byte to read in the RAM
Expand Down
4 changes: 2 additions & 2 deletions nes_py/cpp/include/ppu.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class PPU {
void setOAMData(uint8_t value) { writeOAM(m_spriteDataAddress++, value); };

/// Return a pointer to the screen buffer.
std::uint32_t* get_screen_buffer() { return *screen_buffer; };
uint32_t* get_screen_buffer() { return *screen_buffer; };

private:
uint8_t readOAM(uint8_t addr) { return m_spriteMemory[addr]; };
Expand Down Expand Up @@ -105,7 +105,7 @@ class PPU {
/// The internal screen data structure as a vector representation of a
/// matrix of height matching the visible scans lines and width matching
/// the number of visible scan line dots
uint32_t screen_buffer[VisibleScanlines][ScanlineVisibleDots] = {{0}};
uint32_t screen_buffer[VisibleScanlines][ScanlineVisibleDots];

};

Expand Down
58 changes: 20 additions & 38 deletions nes_py/cpp/lib_nes_env.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,67 +7,49 @@
// setup the module initializer. required to link visual studio C++ ctypes
void PyInit_lib_nes_env() { }
// setup the function modifier to export in the DLL
#define external __declspec(dllexport)
#define EXP __declspec(dllexport)
// Unix-like systems
#else
// setup the modifier as a dummy
#define external
#define EXP
#endif

// definitions of functions for the Python interface to access
extern "C" {
/// Return the width of the NES.
EXP unsigned GetNESWidth() { return NESVideoWidth; }

/// Return the height of the NES.
EXP unsigned GetNESHeight() { return NESVideoHeight; }

/// Initialize a new emulator and return a pointer to it
external Emulator* Initialize(wchar_t* path) {
EXP Emulator* Initialize(wchar_t* path) {
// convert the c string to a c++ std string data structure
std::wstring ws_rom_path(path);
std::string rom_path(ws_rom_path.begin(), ws_rom_path.end());
// create a new emulator with the given ROM path
return new Emulator(rom_path);
}

/// Return the width of the NES.
external unsigned GetNESWidth() {
return NESVideoWidth;
}

/// Return the height of the NES.
external unsigned GetNESHeight() {
return NESVideoHeight;
}

/// Read a byte from memory
external uint8_t ReadMemory(Emulator* emulator, uint16_t address) {
return emulator->read_memory(address);
}

/// Set a byte in memory
external void WriteMemory(Emulator* emulator, uint16_t address, uint8_t value) {
emulator->write_memory(address, value);
}

/// Return the pointer to the screen buffer
external uint32_t* GetScreenBuffer(Emulator* emulator) {
return emulator->get_screen_buffer();
}
EXP uint32_t* Screen(Emulator* emu) { return emu->get_screen_buffer(); }

/// Return the pointer to the memory buffer
EXP uint8_t* Memory(Emulator* emu) { return emu->get_memory_buffer(); }

/// Reset the emulator
external void Reset(Emulator* emulator) {
emulator->reset();
}
EXP void Reset(Emulator* emu) { emu->reset(); }

/// Perform a discrete step in the emulator (i.e., 1 frame)
external void Step(Emulator* emulator, unsigned char action) {
emulator->step(action);
}
EXP void Step(Emulator* emu, unsigned char action) { emu->step(action); }

/// Close the emulator, i.e., purge it from memory
external void Close(Emulator* emulator) {
delete emulator;
}
EXP void Close(Emulator* emu) { delete emu; }

/// Create a deep copy (i.e., a clone) of the given emulator
external Emulator* Clone(Emulator* emulator) {
return new Emulator(emulator);
}
EXP Emulator* Clone(Emulator* emu) { return new Emulator(emu); }

}

// un-define the macro
#undef EXP
64 changes: 25 additions & 39 deletions nes_py/nes_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,21 @@
raise OSError('missing static lib_nes_env*.so library!')


# setup the argument and return types for Initialize
_LIB.Initialize.argtypes = [ctypes.c_wchar_p]
_LIB.Initialize.restype = ctypes.c_void_p
# setup the argument and return types for GetNESWidth
_LIB.GetNESWidth.argtypes = None
_LIB.GetNESWidth.restype = ctypes.c_uint
# setup the argument and return types for GetNESHeight
_LIB.GetNESHeight.argtypes = None
_LIB.GetNESHeight.restype = ctypes.c_uint
# setup the argument and return types for ReadMemory
_LIB.ReadMemory.argtypes = [ctypes.c_void_p, ctypes.c_ushort]
_LIB.ReadMemory.restype = ctypes.c_ubyte
# setup the argument and return types for WriteMemory
_LIB.WriteMemory.argtypes = [ctypes.c_void_p, ctypes.c_ushort, ctypes.c_ubyte]
_LIB.WriteMemory.restype = None
# setup the argument and return types for GetScreenBuffer
_LIB.GetScreenBuffer.argtypes = [ctypes.c_void_p]
_LIB.GetScreenBuffer.restype = ctypes.c_void_p
# setup the argument and return types for Initialize
_LIB.Initialize.argtypes = [ctypes.c_wchar_p]
_LIB.Initialize.restype = ctypes.c_void_p
# setup the argument and return types for Screen
_LIB.Screen.argtypes = [ctypes.c_void_p]
_LIB.Screen.restype = ctypes.c_void_p
# setup the argument and return types for GetMemoryBuffer
_LIB.Memory.argtypes = [ctypes.c_void_p]
_LIB.Memory.restype = ctypes.c_void_p
# setup the argument and return types for Reset
_LIB.Reset.argtypes = [ctypes.c_void_p]
_LIB.Reset.restype = None
Expand All @@ -67,6 +64,10 @@
SCREEN_TENSOR = ctypes.c_byte * np.prod(SCREEN_SHAPE_32_BIT)


# create a type for the RAM vector from C++
RAM_VECTOR = ctypes.c_byte * 0x800


# the magic bytes expected at the first four bytes of the iNES ROM header.
# It spells "NES<END>"
MAGIC = bytearray([0x4E, 0x45, 0x53, 0x1A])
Expand Down Expand Up @@ -149,13 +150,15 @@ def __init__(self, rom_path,
self._backup_env = None
# setup the NumPy screen from the C++ vector
self.screen = None
self.ram = None
self.done = True
self._setup_screen()
self._setup_ram()

def _setup_screen(self):
"""Setup the screen buffer from the C++ code."""
# get the address of the screen
address = _LIB.GetScreenBuffer(self._env)
address = _LIB.Screen(self._env)
# create a buffer from the contents of the address location
buffer_ = ctypes.cast(address, ctypes.POINTER(SCREEN_TENSOR)).contents
# create a NumPy array from the buffer
Expand All @@ -171,32 +174,15 @@ def _setup_screen(self):
# store the instance to screen
self.screen = screen

def _read_mem(self, address):
"""
Read a byte from the given memory address.
Args:
address (int): the 16-bit address to read from
Returns:
(int) the 8-bit value at the given memory address
"""
return _LIB.ReadMemory(self._env, address)

def _write_mem(self, address, value):
"""
Write a byte to the given memory address.
Args:
address (int): the 16-bit address to write to
value (int): the 8-bit value to write to memory
Returns:
None
"""
_LIB.WriteMemory(self._env, address, value)
def _setup_ram(self):
"""Setup the RAM buffer from the C++ code."""
# get the address of the RAM
address = _LIB.Memory(self._env)
# create a buffer from the contents of the address location
buffer_ = ctypes.cast(address, ctypes.POINTER(RAM_VECTOR)).contents
# create a NumPy array from the buffer
self.ram = np.frombuffer(buffer_, dtype='uint8')
# TODO: handle endian-ness of machine?

def _frame_advance(self, action):
"""
Expand Down
6 changes: 3 additions & 3 deletions nes_py/tests/test_nes_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ def test(self):
for _ in range(90):
env.step(8)
env.step(0)
self.assertEqual(129, env._read_mem(0x0776))
env._write_mem(0x0776, 0)
self.assertEqual(0, env._read_mem(0x0776))
self.assertEqual(129, env.ram[0x0776])
env.ram[0x0776] = 0
self.assertEqual(0, env.ram[0x0776])
env.close()


Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def build_extensions(self):

setup(
name='nes_py',
version='3.0.3',
version='4.0.0',
description='An NES Emulator and OpenAI Gym interface',
long_description=README,
long_description_content_type='text/markdown',
Expand Down

0 comments on commit 73e10bb

Please sign in to comment.