Blame | Last modification | View Log | Download | RSS feed
/*
* Troy's TMS9918 Emulator - Core interface
*
* Copyright (c) 2021 Troy Schrapel
*
* This code is licensed under the MIT license
*
* https://github.com/visrealm/VrEmuTms9918
*
*/
#include "tms9918.h"
#include <stdlib.h>
#include <memory.h>
#include <math.h>
#include <string.h>
#if PICO_BUILD
#include "pico/stdlib.h"
#define inline __force_inline
#else
#define __time_critical_func(fn) fn
#endif
#ifndef __MINGW32__
#undef VR_EMU_TMS9918_DLLEXPORT
#define VR_EMU_TMS9918_DLLEXPORT
#endif
#define VRAM_SIZE (1 << 14) /* 16KB */
#define VRAM_MASK (VRAM_SIZE - 1) /* 0x3fff */
#define GRAPHICS_NUM_COLS 32
#define GRAPHICS_NUM_ROWS 24
#define GRAPHICS_CHAR_WIDTH 8
#define TEXT_NUM_COLS 40
#define TEXT_NUM_ROWS 24
#define TEXT_CHAR_WIDTH 6
#define TEXT_PADDING_PX 8
#define PATTERN_BYTES 8
#define GFXI_COLOR_GROUP_SIZE 8
#define MAX_SPRITES 32
#define SPRITE_ATTR_Y 0
#define SPRITE_ATTR_X 1
#define SPRITE_ATTR_NAME 2
#define SPRITE_ATTR_COLOR 3
#define SPRITE_ATTR_BYTES 4
#define LAST_SPRITE_YPOS 0xD0
#define MAX_SCANLINE_SPRITES 4
#define STATUS_INT 0x80
#define STATUS_5S 0x40
#define STATUS_COL 0x20
#define TMS_R0_MODE_GRAPHICS_II 0x02
#define TMS_R0_EXT_VDP_ENABLE 0x01
#define TMS_R1_DISP_ACTIVE 0x40
#define TMS_R1_INT_ENABLE 0x20
#define TMS_R1_MODE_MULTICOLOR 0x08
#define TMS_R1_MODE_TEXT 0x10
#define TMS_R1_SPRITE_16 0x02
#define TMS_R1_SPRITE_MAG2 0x01
/* PRIVATE DATA STRUCTURE
* ---------------------- */
struct vrEmuTMS9918_s {
/* the eight write-only registers */
uint8_t registers[TMS_NUM_REGISTERS];
/* status register (read-only) */
uint8_t status;
/* current address for cpu access (auto-increments) */
uint16_t currentAddress;
/* address or register write stage (0 or 1) */
uint8_t regWriteStage;
/* holds first stage of write to address/register port */
uint8_t regWriteStage0Value;
/* buffered value */
uint8_t readAheadBuffer;
/* current display mode */
vrEmuTms9918Mode mode;
/* video ram */
uint8_t vram[VRAM_SIZE];
uint8_t rowSpriteBits[TMS9918_PIXELS_X]; /* collision mask */
};
/* Function: tmsMode
* ----------------------------------------
* return the current display mode
*/
static vrEmuTms9918Mode __time_critical_func(tmsMode)(VrEmuTms9918* tms9918) {
if(tms9918->registers[TMS_REG_0] & TMS_R0_MODE_GRAPHICS_II) {
return TMS_MODE_GRAPHICS_II;
}
/* MC and TEX bits 3 and 4. Shift to bits 0 and 1 to determine a value (0, 1 or 2) */
switch((tms9918->registers[TMS_REG_1] & (TMS_R1_MODE_MULTICOLOR | TMS_R1_MODE_TEXT)) >> 3) {
case 0:
return TMS_MODE_GRAPHICS_I;
case 1:
return TMS_MODE_MULTICOLOR;
case 2:
return TMS_MODE_TEXT;
}
return TMS_MODE_GRAPHICS_I;
}
/* Function: tmsSpriteSize
* ----------------------------------------
* sprite size (8 or 16)
*/
static inline uint8_t tmsSpriteSize(VrEmuTms9918* tms9918) { return tms9918->registers[TMS_REG_1] & TMS_R1_SPRITE_16 ? 16 : 8; }
/* Function: tmsSpriteMagnification
* ----------------------------------------
* sprite size (0 = 1x, 1 = 2x)
*/
static inline bool tmsSpriteMag(VrEmuTms9918* tms9918) { return tms9918->registers[TMS_REG_1] & TMS_R1_SPRITE_MAG2; }
/* Function: tmsNameTableAddr
* ----------------------------------------
* name table base address
*/
static inline uint16_t tmsNameTableAddr(VrEmuTms9918* tms9918) { return (tms9918->registers[TMS_REG_NAME_TABLE] & 0x0f) << 10; }
/* Function: tmsColorTableAddr
* ----------------------------------------
* color table base address
*/
static inline uint16_t tmsColorTableAddr(VrEmuTms9918* tms9918) {
const uint8_t mask = (tms9918->mode == TMS_MODE_GRAPHICS_II) ? 0x80 : 0xff;
return (tms9918->registers[TMS_REG_COLOR_TABLE] & mask) << 6;
}
/* Function: tmsPatternTableAddr
* ----------------------------------------
* pattern table base address
*/
static inline uint16_t tmsPatternTableAddr(VrEmuTms9918* tms9918) {
const uint8_t mask = (tms9918->mode == TMS_MODE_GRAPHICS_II) ? 0x04 : 0x07;
return (tms9918->registers[TMS_REG_PATTERN_TABLE] & mask) << 11;
}
/* Function: tmsSpriteAttrTableAddr
* ----------------------------------------
* sprite attribute table base address
*/
static inline uint16_t tmsSpriteAttrTableAddr(VrEmuTms9918* tms9918) { return (tms9918->registers[TMS_REG_SPRITE_ATTR_TABLE] & 0x7f) << 7; }
/* Function: tmsSpritePatternTableAddr
* ----------------------------------------
* sprite pattern table base address
*/
static inline uint16_t tmsSpritePatternTableAddr(VrEmuTms9918* tms9918) { return (tms9918->registers[TMS_REG_SPRITE_PATT_TABLE] & 0x07) << 11; }
/* Function: tmsBgColor
* ----------------------------------------
* background color
*/
static inline vrEmuTms9918Color tmsMainBgColor(VrEmuTms9918* tms9918) { return tms9918->registers[TMS_REG_FG_BG_COLOR] & 0x0f; }
/* Function: tmsFgColor
* ----------------------------------------
* foreground color
*/
static inline vrEmuTms9918Color tmsMainFgColor(VrEmuTms9918* tms9918) {
const vrEmuTms9918Color c = (vrEmuTms9918Color)(tms9918->registers[TMS_REG_FG_BG_COLOR] >> 4);
return c == TMS_TRANSPARENT ? tmsMainBgColor(tms9918) : c;
}
/* Function: tmsFgColor
* ----------------------------------------
* foreground color
*/
static inline vrEmuTms9918Color tmsFgColor(VrEmuTms9918* tms9918, uint8_t colorByte) {
const vrEmuTms9918Color c = (vrEmuTms9918Color)(colorByte >> 4);
return c == TMS_TRANSPARENT ? tmsMainBgColor(tms9918) : c;
}
/* Function: tmsBgColor
* ----------------------------------------
* background color
*/
static inline vrEmuTms9918Color tmsBgColor(VrEmuTms9918* tms9918, uint8_t colorByte) {
const vrEmuTms9918Color c = (vrEmuTms9918Color)(colorByte & 0x0f);
return c == TMS_TRANSPARENT ? tmsMainBgColor(tms9918) : c;
}
/* Function: vrEmuTms9918New
* ----------------------------------------
* create a new TMS9918
*/
VR_EMU_TMS9918_DLLEXPORT VrEmuTms9918* vrEmuTms9918New() {
VrEmuTms9918* tms9918 = (VrEmuTms9918*)malloc(sizeof(VrEmuTms9918));
if(tms9918 != NULL) {
vrEmuTms9918Reset(tms9918);
}
return tms9918;
}
/* Function: vrEmuTms9918Reset
* ----------------------------------------
* reset the new TMS9918
*/
VR_EMU_TMS9918_DLLEXPORT void vrEmuTms9918Reset(VrEmuTms9918* tms9918) {
if(tms9918) {
tms9918->regWriteStage0Value = 0;
tms9918->currentAddress = 0;
tms9918->regWriteStage = 0;
tms9918->status = 0;
tms9918->readAheadBuffer = 0;
memset(tms9918->registers, 0, sizeof(tms9918->registers));
/* ram intentionally left in unknown state */
tms9918->mode = tmsMode(tms9918);
}
}
/* Function: vrEmuTms9918Destroy
* ----------------------------------------
* destroy a TMS9918
*
* tms9918: tms9918 object to destroy / clean up
*/
VR_EMU_TMS9918_DLLEXPORT void vrEmuTms9918Destroy(VrEmuTms9918* tms9918) {
if(tms9918) {
free(tms9918);
}
}
/* Function: vrEmuTms9918WriteAddr
* ----------------------------------------
* write an address (mode = 1) to the tms9918
*
* data: the data (DB0 -> DB7) to send
*/
VR_EMU_TMS9918_DLLEXPORT void vrEmuTms9918WriteAddr(VrEmuTms9918* tms9918, uint8_t data) {
if(tms9918 == NULL) return;
if(tms9918->regWriteStage == 0) {
/* first stage byte - either an address LSB or a register value */
tms9918->regWriteStage0Value = data;
tms9918->regWriteStage = 1;
} else {
/* second byte - either a register number or an address MSB */
if(data & 0x80) /* register */
{
tms9918->registers[data & 0x07] = tms9918->regWriteStage0Value;
tms9918->mode = tmsMode(tms9918);
} else /* address */
{
tms9918->currentAddress = tms9918->regWriteStage0Value | ((data & 0x3f) << 8);
if((data & 0x40) == 0) {
tms9918->readAheadBuffer = tms9918->vram[(tms9918->currentAddress++) & VRAM_MASK];
}
}
tms9918->regWriteStage = 0;
}
}
/* Function: vrEmuTms9918ReadStatus
* ----------------------------------------
* read from the status register
*/
VR_EMU_TMS9918_DLLEXPORT uint8_t vrEmuTms9918ReadStatus(VrEmuTms9918* tms9918) {
if(tms9918 == NULL) return 0;
const uint8_t tmpStatus = tms9918->status;
tms9918->status = 0;
tms9918->regWriteStage = 0;
return tmpStatus;
}
/* Function: vrEmuTms9918WriteData
* ----------------------------------------
* write data (mode = 0) to the tms9918
*
* data: the data (DB0 -> DB7) to send
*/
VR_EMU_TMS9918_DLLEXPORT void vrEmuTms9918WriteData(VrEmuTms9918* tms9918, uint8_t data) {
if(tms9918 == NULL) return;
tms9918->regWriteStage = 0;
tms9918->readAheadBuffer = data;
tms9918->vram[(tms9918->currentAddress++) & VRAM_MASK] = data;
}
/* Function: vrEmuTms9918ReadData
* ----------------------------------------
* read data (mode = 0) from the tms9918
*/
VR_EMU_TMS9918_DLLEXPORT uint8_t vrEmuTms9918ReadData(VrEmuTms9918* tms9918) {
if(tms9918 == NULL) return 0;
tms9918->regWriteStage = 0;
uint8_t currentValue = tms9918->readAheadBuffer;
tms9918->readAheadBuffer = tms9918->vram[(tms9918->currentAddress++) & VRAM_MASK];
return currentValue;
}
/* Function: vrEmuTms9918ReadDataNoInc
* ----------------------------------------
* read data (mode = 0) from the tms9918
*/
VR_EMU_TMS9918_DLLEXPORT uint8_t vrEmuTms9918ReadDataNoInc(VrEmuTms9918* tms9918) {
if(tms9918 == NULL) return 0;
return tms9918->readAheadBuffer;
}
/* Function: vrEmuTms9918OutputSprites
* ----------------------------------------
* Output Sprites to a scanline
*/
static void __time_critical_func(vrEmuTms9918OutputSprites)(VrEmuTms9918* tms9918, uint8_t y, uint8_t pixels[TMS9918_PIXELS_X]) {
const bool spriteMag = tmsSpriteMag(tms9918);
const bool sprite16 = tmsSpriteSize(tms9918) == 16;
const uint8_t spriteSize = tmsSpriteSize(tms9918);
const uint8_t spriteSizePx = spriteSize * (spriteMag + 1);
const uint16_t spriteAttrTableAddr = tmsSpriteAttrTableAddr(tms9918);
const uint16_t spritePatternAddr = tmsSpritePatternTableAddr(tms9918);
uint8_t spritesShown = 0;
if(y == 0) {
tms9918->status = 0;
}
uint8_t* spriteAttr = tms9918->vram + spriteAttrTableAddr;
for(uint8_t spriteIdx = 0; spriteIdx < MAX_SPRITES; ++spriteIdx) {
int16_t yPos = spriteAttr[SPRITE_ATTR_Y];
/* stop processing when yPos == LAST_SPRITE_YPOS */
if(yPos == LAST_SPRITE_YPOS) {
if((tms9918->status & STATUS_5S) == 0) {
tms9918->status |= spriteIdx;
}
break;
}
/* check if sprite position is in the -31 to 0 range and move back to top */
if(yPos > 0xe0) {
yPos -= 256;
}
/* first row is YPOS -1 (0xff). 2nd row is YPOS 0 */
yPos += 1;
int16_t pattRow = y - yPos;
if(spriteMag) {
pattRow >>= 1; // this needs to be a shift because -1 / 2 becomes 0. Bad.
}
/* check if sprite is visible on this line */
if(pattRow < 0 || pattRow >= spriteSize) {
spriteAttr += SPRITE_ATTR_BYTES;
continue;
}
if(spritesShown == 0) {
int* rsbInt = (int*)tms9918->rowSpriteBits;
int* end = rsbInt + sizeof(tms9918->rowSpriteBits) / sizeof(int);
while(rsbInt < end) {
*rsbInt++ = 0;
}
}
const uint8_t spriteColor = spriteAttr[SPRITE_ATTR_COLOR] & 0x0f;
/* have we exceeded the scanline sprite limit? */
if(++spritesShown > MAX_SCANLINE_SPRITES) {
if((tms9918->status & STATUS_5S) == 0) {
tms9918->status |= STATUS_5S | spriteIdx;
}
break;
}
/* sprite is visible on this line */
const uint8_t pattIdx = spriteAttr[SPRITE_ATTR_NAME];
const uint16_t pattOffset = spritePatternAddr + pattIdx * PATTERN_BYTES + (uint16_t)pattRow;
const int16_t earlyClockOffset = (spriteAttr[SPRITE_ATTR_COLOR] & 0x80) ? -32 : 0;
const int16_t xPos = (int16_t)(spriteAttr[SPRITE_ATTR_X]) + earlyClockOffset;
int8_t pattByte = tms9918->vram[pattOffset];
uint8_t screenBit = 0, pattBit = 0;
int16_t endXPos = xPos + spriteSizePx;
if(endXPos >= TMS9918_PIXELS_X) {
endXPos = TMS9918_PIXELS_X;
}
for(int16_t screenX = xPos; screenX < endXPos; ++screenX, ++screenBit) {
if(screenX >= 0) {
if(pattByte < 0) {
if(spriteColor != TMS_TRANSPARENT && tms9918->rowSpriteBits[screenX] < 2) {
pixels[screenX] = spriteColor;
}
/* we still process transparent sprites, since
they're used in 5S and collision checks */
if(tms9918->rowSpriteBits[screenX]) {
tms9918->status |= STATUS_COL;
} else {
tms9918->rowSpriteBits[screenX] = spriteColor + 1;
}
}
}
/* next pattern bit if non-magnified or if odd screen bit */
if(!spriteMag || (screenBit & 0x01)) {
pattByte <<= 1;
if(++pattBit == GRAPHICS_CHAR_WIDTH && sprite16) /* from A -> C or B -> D of large sprite */
{
pattBit = 0;
pattByte = tms9918->vram[pattOffset + PATTERN_BYTES * 2];
}
}
}
spriteAttr += SPRITE_ATTR_BYTES;
}
}
/* Function: vrEmuTms9918GraphicsIScanLine
* ----------------------------------------
* generate a Graphics I mode scanline
*/
static void __time_critical_func(vrEmuTms9918GraphicsIScanLine)(VrEmuTms9918* tms9918, uint8_t y, uint8_t pixels[TMS9918_PIXELS_X]) {
const uint8_t tileY = y >> 3; /* which name table row (0 - 23) */
const uint8_t pattRow = y & 0x07; /* which pattern row (0 - 7) */
/* address in name table at the start of this row */
const uint16_t rowNamesAddr = tmsNameTableAddr(tms9918) + tileY * GRAPHICS_NUM_COLS;
const uint8_t* patternTable = tms9918->vram + tmsPatternTableAddr(tms9918);
const uint8_t* colorTable = tms9918->vram + tmsColorTableAddr(tms9918);
/* iterate over each tile in this row */
for(uint8_t tileX = 0; tileX < GRAPHICS_NUM_COLS; ++tileX) {
const uint8_t pattIdx = tms9918->vram[rowNamesAddr + tileX];
uint8_t pattByte = patternTable[pattIdx * PATTERN_BYTES + pattRow];
const uint8_t colorByte = colorTable[pattIdx / GFXI_COLOR_GROUP_SIZE];
const uint8_t fgColor = tmsFgColor(tms9918, colorByte);
const uint8_t bgColor = tmsBgColor(tms9918, colorByte);
/* iterate over each bit of this pattern byte */
for(uint8_t pattBit = 0; pattBit < GRAPHICS_CHAR_WIDTH; ++pattBit) {
const bool pixelBit = pattByte & 0x80;
*(pixels++) = pixelBit ? fgColor : bgColor;
pattByte <<= 1;
}
}
vrEmuTms9918OutputSprites(tms9918, y, pixels - TMS9918_PIXELS_X);
}
/* Function: vrEmuTms9918GraphicsIIScanLine
* ----------------------------------------
* generate a Graphics II mode scanline
*/
static void __time_critical_func(vrEmuTms9918GraphicsIIScanLine)(VrEmuTms9918* tms9918, uint8_t y, uint8_t pixels[TMS9918_PIXELS_X]) {
const uint8_t tileY = y >> 3; /* which name table row (0 - 23) */
const uint8_t pattRow = y & 0x07; /* which pattern row (0 - 7) */
/* address in name table at the start of this row */
const uint16_t rowNamesAddr = tmsNameTableAddr(tms9918) + tileY * GRAPHICS_NUM_COLS;
/* the datasheet says the lower bits of the color and pattern tables must
be all 1's for graphics II mode. however, the lowest 2 bits of the
pattern address are used to determine if pages 2 & 3 come from page 0
or not. Similarly, the lowest 6 bits of the color table register are
used as an and mask with the nametable index */
const uint8_t nameMask = ((tms9918->registers[TMS_REG_COLOR_TABLE] & 0x7f) << 3) | 0x07;
const uint16_t pageThird = ((tileY & 0x18) >> 3) & (tms9918->registers[TMS_REG_PATTERN_TABLE] & 0x03); /* which page? 0-2 */
const uint16_t pageOffset = pageThird << 11; /* offset (0, 0x800 or 0x1000) */
const uint8_t* patternTable = tms9918->vram + tmsPatternTableAddr(tms9918) + pageOffset;
const uint8_t* colorTable = tms9918->vram + tmsColorTableAddr(tms9918) + (pageOffset & ((tms9918->registers[TMS_REG_COLOR_TABLE] & 0x60) << 6));
/* iterate over each tile in this row */
for(uint8_t tileX = 0; tileX < GRAPHICS_NUM_COLS; ++tileX) {
uint8_t pattIdx = tms9918->vram[rowNamesAddr + tileX] & nameMask;
const size_t pattRowOffset = pattIdx * PATTERN_BYTES + pattRow;
const uint8_t pattByte = patternTable[pattRowOffset];
const uint8_t colorByte = colorTable[pattRowOffset];
const vrEmuTms9918Color fgColor = tmsFgColor(tms9918, colorByte);
const vrEmuTms9918Color bgColor = tmsBgColor(tms9918, colorByte);
/* iterate over each bit of this pattern byte */
for(uint8_t pattBit = 0; pattBit < GRAPHICS_CHAR_WIDTH; ++pattBit) {
const bool pixelBit = (pattByte << pattBit) & 0x80;
pixels[tileX * GRAPHICS_CHAR_WIDTH + pattBit] = (uint8_t)(pixelBit ? fgColor : bgColor);
}
}
vrEmuTms9918OutputSprites(tms9918, y, pixels);
}
/* Function: vrEmuTms9918TextScanLine
* ----------------------------------------
* generate a Text mode scanline
*/
static void __time_critical_func(vrEmuTms9918TextScanLine)(VrEmuTms9918* tms9918, uint8_t y, uint8_t pixels[TMS9918_PIXELS_X]) {
const uint8_t tileY = y >> 3; /* which name table row (0 - 23) */
const uint8_t pattRow = y & 0x07; /* which pattern row (0 - 7) */
/* address in name table at the start of this row */
const uint16_t rowNamesAddr = tmsNameTableAddr(tms9918) + tileY * TEXT_NUM_COLS;
const uint8_t* patternTable = tms9918->vram + tmsPatternTableAddr(tms9918);
const vrEmuTms9918Color bgColor = tmsMainBgColor(tms9918);
const vrEmuTms9918Color fgColor = tmsMainFgColor(tms9918);
/* fill the first and last 8 pixels with bg color */
memset(pixels, bgColor, TEXT_PADDING_PX);
memset(pixels + TMS9918_PIXELS_X - TEXT_PADDING_PX, bgColor, TEXT_PADDING_PX);
for(uint8_t tileX = 0; tileX < TEXT_NUM_COLS; ++tileX) {
const uint8_t pattIdx = tms9918->vram[rowNamesAddr + tileX];
const uint8_t pattByte = patternTable[pattIdx * PATTERN_BYTES + pattRow];
for(uint8_t pattBit = 0; pattBit < TEXT_CHAR_WIDTH; ++pattBit) {
bool pixelBit = (pattByte << pattBit) & 0x80;
pixels[TEXT_PADDING_PX + tileX * TEXT_CHAR_WIDTH + pattBit] = (uint8_t)(pixelBit ? fgColor : bgColor);
}
}
}
/* Function: vrEmuTms9918MulticolorScanLine
* ----------------------------------------
* generate a Multicolor mode scanline
*/
static void __time_critical_func(vrEmuTms9918MulticolorScanLine)(VrEmuTms9918* tms9918, uint8_t y, uint8_t pixels[TMS9918_PIXELS_X]) {
const uint8_t tileY = y >> 3;
const uint8_t pattRow = ((y / 4) & 0x01) + (tileY & 0x03) * 2;
const uint16_t namesAddr = tmsNameTableAddr(tms9918) + tileY * GRAPHICS_NUM_COLS;
const uint8_t* patternTable = tms9918->vram + tmsPatternTableAddr(tms9918);
for(uint8_t tileX = 0; tileX < GRAPHICS_NUM_COLS; ++tileX) {
const uint8_t pattIdx = tms9918->vram[namesAddr + tileX];
const uint8_t colorByte = patternTable[pattIdx * PATTERN_BYTES + pattRow];
memset(pixels + tileX * 8, tmsFgColor(tms9918, colorByte), 4);
memset(pixels + tileX * 8 + 4, tmsBgColor(tms9918, colorByte), 4);
}
vrEmuTms9918OutputSprites(tms9918, y, pixels);
}
/* Function: vrEmuTms9918ScanLine
* ----------------------------------------
* generate a scanline
*/
VR_EMU_TMS9918_DLLEXPORT void __time_critical_func(vrEmuTms9918ScanLine)(VrEmuTms9918* tms9918, uint8_t y, uint8_t pixels[TMS9918_PIXELS_X]) {
if(tms9918 == NULL) return;
if(!vrEmuTms9918DisplayEnabled(tms9918) || y >= TMS9918_PIXELS_Y) {
memset(pixels, tmsMainBgColor(tms9918), TMS9918_PIXELS_X);
return;
}
switch(tms9918->mode) {
case TMS_MODE_GRAPHICS_I:
vrEmuTms9918GraphicsIScanLine(tms9918, y, pixels);
break;
case TMS_MODE_GRAPHICS_II:
vrEmuTms9918GraphicsIIScanLine(tms9918, y, pixels);
break;
case TMS_MODE_TEXT:
vrEmuTms9918TextScanLine(tms9918, y, pixels);
break;
case TMS_MODE_MULTICOLOR:
vrEmuTms9918MulticolorScanLine(tms9918, y, pixels);
break;
}
if(y == TMS9918_PIXELS_Y - 1 && (tms9918->registers[1] & TMS_R1_INT_ENABLE)) {
tms9918->status |= STATUS_INT;
}
}
/* Function: vrEmuTms9918RegValue
* ----------------------------------------
* return a reigister value
*/
VR_EMU_TMS9918_DLLEXPORT
uint8_t vrEmuTms9918RegValue(VrEmuTms9918* tms9918, vrEmuTms9918Register reg) {
if(tms9918 == NULL) return 0;
return tms9918->registers[reg & 0x07];
}
/* Function: vrEmuTms9918WriteRegValue
* ----------------------------------------
* write a reigister value
*/
VR_EMU_TMS9918_DLLEXPORT
void vrEmuTms9918WriteRegValue(VrEmuTms9918* tms9918, vrEmuTms9918Register reg, uint8_t value) {
if(tms9918 != NULL) {
tms9918->registers[reg & 0x07] = value;
tms9918->mode = tmsMode(tms9918);
}
}
/* Function: vrEmuTms9918VramValue
* ----------------------------------------
* return a value from vram
*/
VR_EMU_TMS9918_DLLEXPORT
uint8_t vrEmuTms9918VramValue(VrEmuTms9918* tms9918, uint16_t addr) {
if(tms9918 == NULL) return 0;
return tms9918->vram[addr & VRAM_MASK];
}
/* Function: vrEmuTms9918DisplayEnabled
* ----------------------------------------
* check BLANK flag
*/
VR_EMU_TMS9918_DLLEXPORT
bool vrEmuTms9918DisplayEnabled(VrEmuTms9918* tms9918) {
if(tms9918 == NULL) return false;
return tms9918->registers[TMS_REG_1] & TMS_R1_DISP_ACTIVE;
}
/* Function: vrEmuTms9918DisplayMode
* --------------------
* current display mode
*/
VR_EMU_TMS9918_DLLEXPORT
vrEmuTms9918Mode vrEmuTms9918DisplayMode(VrEmuTms9918* tms9918) { return tms9918->mode; }