// license:BSD-3-Clause
// copyright-holders:Curt Coder
/**********************************************************************
RCA CDP1869/1870/1876 Video Interface System (VIS) emulation
**********************************************************************/
/*
TODO:
- white noise
- scanline based update
- CMSEL output
*/
#include "emu.h"
#include "cdp1869.h"
//**************************************************************************
// MACROS / CONSTANTS
//**************************************************************************
#define LOG 0
#define CDP1869_WEIGHT_RED 30 // % of max luminance
#define CDP1869_WEIGHT_GREEN 59
#define CDP1869_WEIGHT_BLUE 11
#define CDP1869_COLUMNS_HALF 20
#define CDP1869_COLUMNS_FULL 40
#define CDP1869_ROWS_HALF 12
#define CDP1869_ROWS_FULL_PAL 25
#define CDP1869_ROWS_FULL_NTSC 24
enum
{
CDB0 = 0,
CDB1,
CDB2,
CDB3,
CDB4,
CDB5,
CCB0,
CCB1
};
//**************************************************************************
// DEVICE DEFINITIONS
//**************************************************************************
// device type definition
const device_type CDP1869 = &device_creator<cdp1869_device>;
// I/O map
DEVICE_ADDRESS_MAP_START( io_map, 8, cdp1869_device )
AM_RANGE(0x03, 0x03) AM_WRITE(out3_w)
AM_RANGE(0x04, 0x04) AM_WRITE(out4_w)
AM_RANGE(0x05, 0x05) AM_WRITE(out5_w)
AM_RANGE(0x06, 0x06) AM_WRITE(out6_w)
AM_RANGE(0x07, 0x07) AM_WRITE(out7_w)
ADDRESS_MAP_END
// character RAM map
DEVICE_ADDRESS_MAP_START( char_map, 8, cdp1869_device )
AM_RANGE(0x000, 0x3ff) AM_READWRITE(char_ram_r, char_ram_w)
ADDRESS_MAP_END
// page RAM map
DEVICE_ADDRESS_MAP_START( page_map, 8, cdp1869_device )
AM_RANGE(0x000, 0x7ff) AM_READWRITE(page_ram_r, page_ram_w)
ADDRESS_MAP_END
// default address map
static ADDRESS_MAP_START( cdp1869, AS_0, 8, cdp1869_device )
AM_RANGE(0x000, 0x7ff) AM_RAM
ADDRESS_MAP_END
//**************************************************************************
// INLINE HELPERS
//**************************************************************************
//-------------------------------------------------
// is_ntsc - is device in NTSC mode
//-------------------------------------------------
inline bool cdp1869_device::is_ntsc()
{
return m_read_pal_ntsc() ? false : true;
}
//-------------------------------------------------
// read_page_ram_byte - read a page RAM byte at
// the given address
//-------------------------------------------------
inline UINT8 cdp1869_device::read_page_ram_byte(offs_t pma)
{
return space().read_byte(pma);
}
//-------------------------------------------------
// write_page_ram_byte - write a page RAM byte at
// the given address
//-------------------------------------------------
inline void cdp1869_device::write_page_ram_byte(offs_t pma, UINT8 data)
{
space().write_byte(pma, data);
}
//-------------------------------------------------
// read_char_ram_byte - read a char RAM byte at
// the given address
//-------------------------------------------------
inline UINT8 cdp1869_device::read_char_ram_byte(offs_t pma, offs_t cma, UINT8 pmd)
{
UINT8 data = 0;
if (!m_in_char_ram_func.isnull())
{
data = m_in_char_ram_func(pma, cma, pmd);
}
return data;
}
//-------------------------------------------------
// write_char_ram_byte - write a char RAM byte at
// the given address
//-------------------------------------------------
inline void cdp1869_device::write_char_ram_byte(offs_t pma, offs_t cma, UINT8 pmd, UINT8 data)
{
if (!m_out_char_ram_func.isnull())
{
m_out_char_ram_func(pma, cma, pmd, data);
}
}
//-------------------------------------------------
// read_pcb - read page control bit
//-------------------------------------------------
inline int cdp1869_device::read_pcb(offs_t pma, offs_t cma, UINT8 pmd)
{
int pcb = 0;
if (!m_in_pcb_func.isnull())
{
pcb = m_in_pcb_func(pma, cma, pmd);
}
return pcb;
}
//-------------------------------------------------
// update_prd_changed_timer -
//-------------------------------------------------
inline void cdp1869_device::update_prd_changed_timer()
{
int start = CDP1869_SCANLINE_PREDISPLAY_START_PAL;
int end = CDP1869_SCANLINE_PREDISPLAY_END_PAL;
int next_state;
int scanline = m_screen->vpos();
int next_scanline;
if (is_ntsc())
{
start = CDP1869_SCANLINE_PREDISPLAY_START_NTSC;
end = CDP1869_SCANLINE_PREDISPLAY_END_NTSC;
}
if (scanline < start)
{
next_scanline = start;
next_state = ASSERT_LINE;
}
else if (scanline < end)
{
next_scanline = end;
next_state = CLEAR_LINE;
}
else
{
next_scanline = start;
next_state = ASSERT_LINE;
}
if (m_dispoff)
{
next_state = CLEAR_LINE;
}
attotime duration = m_screen->time_until_pos(next_scanline);
m_prd_timer->adjust(duration, next_state);
}
//-------------------------------------------------
// get_rgb - get RGB value
//-------------------------------------------------
inline rgb_t cdp1869_device::get_rgb(int i, int c, int l)
{
int luma = 0, r, g, b;
luma += (l & 4) ? CDP1869_WEIGHT_RED : 0;
luma += (l & 1) ? CDP1869_WEIGHT_GREEN : 0;
luma += (l & 2) ? CDP1869_WEIGHT_BLUE : 0;
luma = (luma * 0xff) / 100;
r = (c & 4) ? luma : 0;
g = (c & 1) ? luma : 0;
b = (c & 2) ? luma : 0;
return rgb_t(r, g, b);
}
//-------------------------------------------------
// get_lines - get number of character lines
//-------------------------------------------------
inline int cdp1869_device::get_lines()
{
if (m_line16 && !m_dblpage)
{
return 16;
}
else if (!m_line9)
{
return 9;
}
else
{
return 8;
}
}
//-------------------------------------------------
// get_pmemsize - get page memory size
//-------------------------------------------------
inline UINT16 cdp1869_device::get_pmemsize(int cols, int rows)
{
int pmemsize = cols * rows;
if (m_dblpage) pmemsize *= 2;
if (m_line16) pmemsize *= 2;
return pmemsize;
}
//-------------------------------------------------
// get_pma - get page memory address
//-------------------------------------------------
inline UINT16 cdp1869_device::get_pma()
{
if (m_dblpage)
{
return m_pma;
}
else
{
return m_pma & 0x3ff;
}
}
//-------------------------------------------------
// get_pen - get pen for color bits
//-------------------------------------------------
inline int cdp1869_device::get_pen(int ccb0, int ccb1, int pcb)
{
int r = 0, g = 0, b = 0;
switch (m_col)
{
case 0:
r = ccb0;
b = ccb1;
g = pcb;
break;
case 1:
r = ccb0;
b = pcb;
g = ccb1;
break;
case 2:
case 3:
r = pcb;
b = ccb0;
g = ccb1;
break;
}
int color = (r << 2) + (b << 1) + g;
if (m_cfc)
{
return color + ((m_bkg + 1) * 8);
}
else
{
return color;
}
}
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
//-------------------------------------------------
// cdp1869_device - constructor
//-------------------------------------------------
cdp1869_device::cdp1869_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) :
device_t(mconfig, CDP1869, "RCA CDP1869", tag, owner, clock, "cdp1869", __FILE__),
device_sound_interface(mconfig, *this),
device_video_interface(mconfig, *this),
device_memory_interface(mconfig, *this),
m_read_pal_ntsc(*this),
m_write_prd(*this),
m_color_clock(0),
m_stream(nullptr),
m_palette(*this, "palette"),
m_space_config("pageram", ENDIANNESS_LITTLE, 8, 11, 0, nullptr, *ADDRESS_MAP_NAME(cdp1869))
{
}
static MACHINE_CONFIG_FRAGMENT( cdp1869 )
MCFG_PALETTE_ADD("palette", 8+64)
MCFG_PALETTE_INIT_OWNER(cdp1869_device, cdp1869)
MACHINE_CONFIG_END
//-------------------------------------------------
// machine_config_additions - return a pointer to
// the device's machine fragment
//-------------------------------------------------
machine_config_constructor cdp1869_device::device_mconfig_additions() const
{
return MACHINE_CONFIG_NAME( cdp1869 );
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void cdp1869_device::device_start()
{
// resolve callbacks
m_read_pal_ntsc.resolve_safe(0);
m_write_prd.resolve_safe();
m_in_pcb_func.bind_relative_to(*owner());
m_in_char_ram_func.bind_relative_to(*owner());
m_out_char_ram_func.bind_relative_to(*owner());
// allocate timers
m_prd_timer = timer_alloc();
m_dispoff = 0;
update_prd_changed_timer();
// initialize palette
m_bkg = 0;
// create sound stream
m_stream = machine().sound().stream_alloc(*this, 0, 1, machine().sample_rate());
// initialize other
m_tonediv = 0;
m_tonefreq = 0;
m_toneamp = 0;
m_dblpage = 0;
m_line16 = 0;
m_line9 = 0;
m_fresvert = 0;
m_freshorz = 0;
m_hma = 0;
m_col = 0;
m_incr = 0;
m_signal = 0;
m_cfc = 0;
m_toneoff = 0;
m_cmem = 0;
// register for state saving
save_item(NAME(m_prd));
save_item(NAME(m_dispoff));
save_item(NAME(m_fresvert));
save_item(NAME(m_freshorz));
save_item(NAME(m_cmem));
save_item(NAME(m_dblpage));
save_item(NAME(m_line16));
save_item(NAME(m_line9));
save_item(NAME(m_cfc));
save_item(NAME(m_col));
save_item(NAME(m_bkg));
save_item(NAME(m_pma));
save_item(NAME(m_hma));
save_item(NAME(m_signal));
save_item(NAME(m_incr));
save_item(NAME(m_toneoff));
save_item(NAME(m_wnoff));
save_item(NAME(m_tonediv));
save_item(NAME(m_tonefreq));
save_item(NAME(m_toneamp));
save_item(NAME(m_wnfreq));
save_item(NAME(m_wnamp));
}
//-------------------------------------------------
// device_post_load - device-specific post-load
//-------------------------------------------------
void cdp1869_device::device_post_load()
{
update_prd_changed_timer();
}
//-------------------------------------------------
// device_timer - handler timer events
//-------------------------------------------------
void cdp1869_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
m_write_prd(param);
m_prd = param;
update_prd_changed_timer();
}
//-------------------------------------------------
// memory_space_config - return a description of
// any address spaces owned by this device
//-------------------------------------------------
const address_space_config *cdp1869_device::memory_space_config(address_spacenum spacenum) const
{
return (spacenum == 0) ? &m_space_config : nullptr;
}
//-------------------------------------------------
// initialize_palette - initialize palette
//-------------------------------------------------
PALETTE_INIT_MEMBER(cdp1869_device, cdp1869)
{
// color-on-color display (CFC=0)
int i;
for (i = 0; i < 8; i++)
{
palette.set_pen_color(i, get_rgb(i, i, 15));
}
// tone-on-tone display (CFC=1)
for (int c = 0; c < 8; c++)
{
for (int l = 0; l < 8; l++)
{
palette.set_pen_color(i, get_rgb(i, c, l));
i++;
}
}
}
//-------------------------------------------------
// sound_stream_update - handle update requests for
// our sound stream
//-------------------------------------------------
void cdp1869_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
// reset the output stream
memset(outputs[0], 0, samples * sizeof(*outputs[0]));
INT16 signal = m_signal;
stream_sample_t *buffer = outputs[0];
if (!m_toneoff && m_toneamp)
{
double frequency = (clock() / 2) / (512 >> m_tonefreq) / (m_tonediv + 1);
// double amplitude = m_toneamp * ((0.78*5) / 15);
int rate = machine().sample_rate() / 2;
/* get progress through wave */
int incr = m_incr;
if (signal < 0)
{
signal = -(m_toneamp * (0x07fff / 15));
}
else
{
signal = m_toneamp * (0x07fff / 15);
}
while( samples-- > 0 )
{
*buffer++ = signal;
incr -= frequency;
while( incr < 0 )
{
incr += rate;
signal = -signal;
}
}
/* store progress through wave */
m_incr = incr;
m_signal = signal;
}
/*
if (!m_wnoff)
{
double amplitude = m_wnamp * ((0.78*5) / 15);
for (int wndiv = 0; wndiv < 128; wndiv++)
{
double frequency = (clock() / 2) / (4096 >> m_wnfreq) / (wndiv + 1):
sum_square_wave(buffer, frequency, amplitude);
}
}
*/
}
//-------------------------------------------------
// draw_line - draw character line
//-------------------------------------------------
void cdp1869_device::draw_line(bitmap_rgb32 &bitmap, const rectangle &rect, int x, int y, UINT8 data, int color)
{
int i;
pen_t fg = m_palette->pen(color);
data <<= 2;
for (i = 0; i < CDP1869_CHAR_WIDTH; i++)
{
if (data & 0x80)
{
bitmap.pix32(y, x) = fg;
if (!m_fresvert)
{
bitmap.pix32(y + 1, x) = fg;
}
if (!m_freshorz)
{
bitmap.pix32(y, x + 1) = fg;
if (!m_fresvert)
{
bitmap.pix32(y + 1, x + 1) = fg;
}
}
}
if (!m_freshorz)
{
x++;
}
x++;
data <<= 1;
}
}
//-------------------------------------------------
// draw_char - draw character
//-------------------------------------------------
void cdp1869_device::draw_char(bitmap_rgb32 &bitmap, const rectangle &rect, int x, int y, UINT16 pma)
{
UINT8 pmd = read_page_ram_byte(pma);
for (UINT8 cma = 0; cma < get_lines(); cma++)
{
UINT8 data = read_char_ram_byte(pma, cma, pmd);
int ccb0 = BIT(data, CCB0);
int ccb1 = BIT(data, CCB1);
int pcb = read_pcb(pma, cma, pmd);
int color = get_pen(ccb0, ccb1, pcb);
draw_line(bitmap, rect, rect.min_x + x, rect.min_y + y, data, color);
y++;
if (!m_fresvert)
{
y++;
}
}
}
//-------------------------------------------------
// out3_w - register 3 write
//-------------------------------------------------
WRITE8_MEMBER( cdp1869_device::out3_w )
{
/*
bit description
0 bkg green
1 bkg blue
2 bkg red
3 cfc
4 disp off
5 colb0
6 colb1
7 fres horz
*/
m_bkg = data & 0x07;
m_cfc = BIT(data, 3);
m_dispoff = BIT(data, 4);
m_col = (data & 0x60) >> 5;
m_freshorz = BIT(data, 7);
}
//-------------------------------------------------
// out4_w - register 4 write
//-------------------------------------------------
WRITE8_MEMBER( cdp1869_device::out4_w )
{
/*
bit description
0 tone amp 2^0
1 tone amp 2^1
2 tone amp 2^2
3 tone amp 2^3
4 tone freq sel0
5 tone freq sel1
6 tone freq sel2
7 tone off
8 tone / 2^0
9 tone / 2^1
10 tone / 2^2
11 tone / 2^3
12 tone / 2^4
13 tone / 2^5
14 tone / 2^6
15 always 0
*/
m_toneamp = offset & 0x0f;
m_tonefreq = (offset & 0x70) >> 4;
m_toneoff = BIT(offset, 7);
m_tonediv = (offset & 0x7f00) >> 8;
m_stream->update();
}
//-------------------------------------------------
// out5_w - register 5 write
//-------------------------------------------------
WRITE8_MEMBER( cdp1869_device::out5_w )
{
/*
bit description
0 cmem access mode
1 x
2 x
3 9-line
4 x
5 16 line hi-res
6 double page
7 fres vert
8 wn amp 2^0
9 wn amp 2^1
10 wn amp 2^2
11 wn amp 2^3
12 wn freq sel0
13 wn freq sel1
14 wn freq sel2
15 wn off
*/
m_cmem = BIT(offset, 0);
m_line9 = BIT(offset, 3);
m_line16 = BIT(offset, 5);
m_dblpage = BIT(offset, 6);
m_fresvert = BIT(offset, 7);
m_wnamp = (offset & 0x0f00) >> 8;
m_wnfreq = (offset & 0x7000) >> 12;
m_wnoff = BIT(offset, 15);
m_stream->update();
if (m_cmem)
{
m_pma = offset;
}
else
{
m_pma = 0;
}
}
//-------------------------------------------------
// out6_w - register 6 write
//-------------------------------------------------
WRITE8_MEMBER( cdp1869_device::out6_w )
{
/*
bit description
0 pma0 reg
1 pma1 reg
2 pma2 reg
3 pma3 reg
4 pma4 reg
5 pma5 reg
6 pma6 reg
7 pma7 reg
8 pma8 reg
9 pma9 reg
10 pma10 reg
11 x
12 x
13 x
14 x
15 x
*/
m_pma = offset & 0x7ff;
}
//-------------------------------------------------
// out7_w - register 7 write
//-------------------------------------------------
WRITE8_MEMBER( cdp1869_device::out7_w )
{
/*
bit description
0 x
1 x
2 hma2 reg
3 hma3 reg
4 hma4 reg
5 hma5 reg
6 hma6 reg
7 hma7 reg
8 hma8 reg
9 hma9 reg
10 hma10 reg
11 x
12 x
13 x
14 x
15 x
*/
m_hma = offset & 0x7fc;
}
//-------------------------------------------------
// char_ram_r - character RAM read
//-------------------------------------------------
READ8_MEMBER( cdp1869_device::char_ram_r )
{
UINT8 cma = offset & 0x0f;
UINT16 pma;
if (m_cmem)
{
pma = get_pma();
}
else
{
pma = offset;
}
if (m_dblpage)
{
cma &= 0x07;
}
UINT8 pmd = read_page_ram_byte(pma);
return read_char_ram_byte(pma, cma, pmd);
}
//-------------------------------------------------
// char_ram_w - character RAM write
//-------------------------------------------------
WRITE8_MEMBER( cdp1869_device::char_ram_w )
{
UINT8 cma = offset & 0x0f;
UINT16 pma;
if (m_cmem)
{
pma = get_pma();
}
else
{
pma = offset;
}
if (m_dblpage)
{
cma &= 0x07;
}
UINT8 pmd = read_page_ram_byte(pma);
write_char_ram_byte(pma, cma, pmd, data);
}
//-------------------------------------------------
// page_ram_r - page RAM read
//-------------------------------------------------
READ8_MEMBER( cdp1869_device::page_ram_r )
{
UINT16 pma;
if (m_cmem)
{
pma = get_pma();
}
else
{
pma = offset;
}
return read_page_ram_byte(pma);
}
//-------------------------------------------------
// page_ram_w - page RAM write
//-------------------------------------------------
WRITE8_MEMBER( cdp1869_device::page_ram_w )
{
UINT16 pma;
if (m_cmem)
{
pma = get_pma();
}
else
{
pma = offset;
}
write_page_ram_byte(pma, data);
}
//-------------------------------------------------
// page_ram_w - predisplay
//-------------------------------------------------
READ_LINE_MEMBER( cdp1869_device::predisplay_r )
{
return m_prd;
}
//-------------------------------------------------
// pal_ntsc_r - PAL/NTSC
//-------------------------------------------------
READ_LINE_MEMBER( cdp1869_device::pal_ntsc_r )
{
return m_read_pal_ntsc();
}
//-------------------------------------------------
// update_screen - update screen
//-------------------------------------------------
UINT32 cdp1869_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
rectangle screen_rect, outer;
if (is_ntsc())
{
outer.min_x = CDP1869_HBLANK_END;
outer.max_x = CDP1869_HBLANK_START - 1;
outer.min_y = CDP1869_SCANLINE_VBLANK_END_NTSC;
outer.max_y = CDP1869_SCANLINE_VBLANK_START_NTSC - 1;
screen_rect.min_x = CDP1869_SCREEN_START_NTSC;
screen_rect.max_x = CDP1869_SCREEN_END - 1;
screen_rect.min_y = CDP1869_SCANLINE_DISPLAY_START_NTSC;
screen_rect.max_y = CDP1869_SCANLINE_DISPLAY_END_NTSC - 1;
}
else
{
outer.min_x = CDP1869_HBLANK_END;
outer.max_x = CDP1869_HBLANK_START - 1;
outer.min_y = CDP1869_SCANLINE_VBLANK_END_PAL;
outer.max_y = CDP1869_SCANLINE_VBLANK_START_PAL - 1;
screen_rect.min_x = CDP1869_SCREEN_START_PAL;
screen_rect.max_x = CDP1869_SCREEN_END - 1;
screen_rect.min_y = CDP1869_SCANLINE_DISPLAY_START_PAL;
screen_rect.max_y = CDP1869_SCANLINE_DISPLAY_END_PAL - 1;
}
outer &= cliprect;
bitmap.fill(m_palette->pen(m_bkg), outer);
if (!m_dispoff)
{
int width = CDP1869_CHAR_WIDTH;
int height = get_lines();
if (!m_freshorz)
{
width *= 2;
}
if (!m_fresvert)
{
height *= 2;
}
int cols = m_freshorz ? CDP1869_COLUMNS_FULL : CDP1869_COLUMNS_HALF;
int rows = screen_rect.height() / height;
UINT16 pmemsize = get_pmemsize(cols, rows);
UINT16 addr = m_hma;
for (int sy = 0; sy < rows; sy++)
{
for (int sx = 0; sx < cols; sx++)
{
int x = sx * width;
int y = sy * height;
draw_char(bitmap, screen_rect, x, y, addr);
addr++;
if (addr == pmemsize) addr = 0;
}
}
}
return 0;
}