// license:BSD-3-Clause // copyright-holders:Curt Coder /********************************************************************** Intelligent Designs NICK emulation **********************************************************************/ #include "emu.h" #include "nick.h" //************************************************************************** // MACROS / CONSTANTS //************************************************************************** #define LOG 0 /* colour mode types */ #define NICK_2_COLOUR_MODE 0 #define NICK_4_COLOUR_MODE 1 #define NICK_16_COLOUR_MODE 2 #define NICK_256_COLOUR_MODE 3 /* Display mode types */ #define NICK_VSYNC_MODE 0 #define NICK_PIXEL_MODE 1 #define NICK_ATTR_MODE 2 #define NICK_CH256_MODE 3 #define NICK_CH128_MODE 4 #define NICK_CH64_MODE 5 #define NICK_UNUSED_MODE 6 #define NICK_LPIXEL_MODE 7 /* MODEBYTE defines */ #define NICK_MB_VIRQ (1<<7) #define NICK_MB_VRES (1<<4) #define NICK_MB_LPT_RELOAD (1<<0) /* Left margin defines */ #define NICK_LM_MSBALT (1<<7) #define NICK_LM_LSBALT (1<<6) /* Right margin defines */ #define NICK_RM_ALTIND1 (1<<7) #define NICK_RM_ALTIND0 (1<<6) /* useful macros */ #define NICK_GET_LEFT_MARGIN(x) (x & 0x03f) #define NICK_GET_RIGHT_MARGIN(x) (x & 0x03f) #define NICK_GET_DISPLAY_MODE(x) ((x>>1) & 0x07) #define NICK_GET_COLOUR_MODE(x) ((x>>5) & 0x03) #define NICK_RELOAD_LPT(x) (x & 0x080) #define NICK_CLOCK_LPT(x) (x & 0x040) /* Macros to generate memory address is CHx modes */ /* x = LD2, y = buf1 */ #define ADDR_CH256(x,y) (((x & 0x0ff)<<8) | (y & 0x0ff)) #define ADDR_CH128(x,y) (((x & 0x01ff)<<7) | (y & 0x07f)) #define ADDR_CH64(x,y) (((x & 0x03ff)<<6) | (y & 0x03f)) /* No of highest resolution pixels per "clock" */ #define NICK_PIXELS_PER_CLOCK 16 /* "clocks" per line */ #define NICK_TOTAL_CLOCKS_PER_LINE 64 //************************************************************************** // DEVICE DEFINITIONS //************************************************************************** DEFINE_DEVICE_TYPE(NICK, nick_device, "nick", "NICK") void nick_device::vram_map(address_map &map) { map(0x0000, 0xffff).rw(FUNC(nick_device::vram_r), FUNC(nick_device::vram_w)); } void nick_device::vio_map(address_map &map) { map(0x00, 0x00).w(FUNC(nick_device::fixbias_w)); map(0x01, 0x01).w(FUNC(nick_device::border_w)); map(0x02, 0x02).w(FUNC(nick_device::lpl_w)); map(0x03, 0x03).w(FUNC(nick_device::lph_w)); } void nick_device::nick_map(address_map &map) { map(0x0000, 0xffff).ram(); } //************************************************************************** // LIVE DEVICE //************************************************************************** //------------------------------------------------- // nick_device - constructor //------------------------------------------------- nick_device::nick_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, NICK, tag, owner, clock), device_memory_interface(mconfig, *this), device_video_interface(mconfig, *this), m_space_config("vram", ENDIANNESS_LITTLE, 8, 16, 0, address_map_constructor(FUNC(nick_device::nick_map), this)), m_write_virq(*this), m_scanline_count(0), m_FIXBIAS(0), m_BORDER(0), m_LPL(0), m_LPH(0), m_LD1(0), m_LD2(0), m_virq(CLEAR_LINE) { memset(&m_LPT, 0x00, sizeof(m_LPT)); } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void nick_device::device_start() { screen().register_screen_bitmap(m_bitmap); calc_visible_clocks(ENTERPRISE_SCREEN_WIDTH); // initialize palette initialize_palette(); // resolve callbacks m_write_virq.resolve_safe(); // allocate timers m_timer_scanline = timer_alloc(); m_timer_scanline->adjust(screen().time_until_pos(0, 0), 0, screen().scan_period()); // state saving save_item(NAME(m_scanline_count)); save_item(NAME(m_FIXBIAS)); save_item(NAME(m_BORDER)); save_item(NAME(m_LPL)); save_item(NAME(m_LPH)); save_item(NAME(m_LD1)); save_item(NAME(m_LD2)); save_item(NAME(m_LPT.SC)); save_item(NAME(m_LPT.MB)); save_item(NAME(m_LPT.LM)); save_item(NAME(m_LPT.RM)); save_item(NAME(m_LPT.LD1L)); save_item(NAME(m_LPT.LD1H)); save_item(NAME(m_LPT.LD2L)); save_item(NAME(m_LPT.LD2H)); save_item(NAME(m_LPT.COL)); save_item(NAME(m_dest_pos)); save_item(NAME(m_dest_max_pos)); save_item(NAME(m_reg)); save_item(NAME(m_first_visible_clock)); save_item(NAME(m_last_visible_clock)); } //------------------------------------------------- // device_reset - device-specific reset //------------------------------------------------- void nick_device::device_reset() { m_write_virq(CLEAR_LINE); m_virq = 0; m_scanline_count = 0; } //------------------------------------------------- // device_timer - handler timer events //------------------------------------------------- void nick_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { int scanline = screen().vpos(); if (scanline < ENTERPRISE_SCREEN_HEIGHT) { /* set write address for line */ m_dest = &m_bitmap.pix32(scanline); m_dest_pos = 0; m_dest_max_pos = m_bitmap.width(); /* write line */ do_line(); } } //------------------------------------------------- // memory_space_config - return a description of // any address spaces owned by this device //------------------------------------------------- device_memory_interface::space_config_vector nick_device::memory_space_config() const { return space_config_vector { std::make_pair(0, &m_space_config) }; } //------------------------------------------------- // update_screen - update screen //------------------------------------------------- uint32_t nick_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect) { copybitmap(bitmap, m_bitmap, 0, 0, 0, 0, cliprect); return 0; } //------------------------------------------------- // vram_r - video RAM read //------------------------------------------------- READ8_MEMBER( nick_device::vram_r ) { return this->space().read_byte(offset); } //------------------------------------------------- // vram_w - video RAM write //------------------------------------------------- WRITE8_MEMBER( nick_device::vram_w ) { this->space().write_byte(offset, data); } //------------------------------------------------- // fixbias_w - //------------------------------------------------- WRITE8_MEMBER( nick_device::fixbias_w ) { m_FIXBIAS = data; } //------------------------------------------------- // border_w - //------------------------------------------------- WRITE8_MEMBER( nick_device::border_w ) { m_BORDER = data; } //------------------------------------------------- // lpl_w - //------------------------------------------------- WRITE8_MEMBER( nick_device::lpl_w ) { m_LPL = m_reg[2] = data; update_lpt(); } //------------------------------------------------- // lph_w - //------------------------------------------------- WRITE8_MEMBER( nick_device::lph_w ) { m_LPH = m_reg[3] = data; update_lpt(); } //------------------------------------------------- // initialize_palette - //------------------------------------------------- void nick_device::initialize_palette() { const int resistances_rg[] = { (int) RES_R(470), (int) RES_R(220), (int) RES_R(100) }; const int resistances_b[] = { (int) RES_R(220), (int) RES_R(82) }; double color_weights_rg[3], color_weights_b[2]; compute_resistor_weights(0, 0xff, -1.0, 3, resistances_rg, color_weights_rg, 0, 0, 2, resistances_b, color_weights_b, 0, 0, 0, nullptr, nullptr, 0, 0); for (int i = 0; i < 256; i++) { /* bit description PC0 100R -- RED PC1 100R -- GREEN PC2 82R -- BLUE PC3 220R -- RED PC4 220R -- GREEN PC5 220R -- BLUE PC6 470R -- RED PC7 470R -- GREEN */ int ra = BIT(i, 0); int rb = BIT(i, 3); int rc = BIT(i, 6); int ga = BIT(i, 1); int gb = BIT(i, 4); int gc = BIT(i, 7); int ba = BIT(i, 2); int bb = BIT(i, 5); uint8_t r = combine_weights(color_weights_rg, rc, rb, ra); uint8_t g = combine_weights(color_weights_rg, gc, gb, ga); uint8_t b = combine_weights(color_weights_b, bb, ba); m_palette[i] = rgb_t(r, g, b); } for (int i = 0; i < 256; i++) { int pen_index; pen_index = (BIT(i, 7) << 0) | (BIT(i, 3) << 1); m_pen_idx_4col[i] = pen_index; pen_index = (BIT(i, 7) << 0) | (BIT(i, 3) << 1) | (BIT(i, 5) << 2) | (BIT(i, 1) << 3); m_pen_idx_16col[i] = pen_index; } } // MESS specific /* 8-bit pixel write! */ void nick_device::write_pixel(int ci) { if (m_dest_pos < m_dest_max_pos) { m_dest[m_dest_pos++] = m_palette[ci]; } } /* we align based on the clocks */ void nick_device::calc_visible_clocks(int width) { /* number of clocks we can see */ int no_visible_clocks = width / NICK_PIXELS_PER_CLOCK; m_first_visible_clock = (NICK_TOTAL_CLOCKS_PER_LINE - no_visible_clocks) >> 1; m_last_visible_clock = m_first_visible_clock + no_visible_clocks; } /* write border colour */ void nick_device::write_border(int clocks) { int col_index = m_BORDER; for (int i = 0; i < (clocks << 4); i++) write_pixel(col_index); } void nick_device::do_left_margin() { uint8_t left = NICK_GET_LEFT_MARGIN(m_LPT.LM); if (left > m_first_visible_clock) { /* some of the left margin is visible */ uint8_t left_visible = left - m_first_visible_clock; /* render the border */ write_border(left_visible); } } void nick_device::do_right_margin() { uint8_t right = NICK_GET_RIGHT_MARGIN(m_LPT.RM); if (right < m_last_visible_clock) { /* some of the right margin is visible */ uint8_t right_visible = m_last_visible_clock - right; /* render the border */ write_border(right_visible); } } int nick_device::get_color_index(int pen_index) { if (pen_index & 0x08) return ((m_FIXBIAS & 0x01f) << 3) | (pen_index & 0x07); else return m_LPT.COL[pen_index]; } void nick_device::write_pixels2color(uint8_t pen0, uint8_t pen1, uint8_t data_byte) { int col_index[2]; int pen_index; uint8_t data = data_byte; col_index[0] = get_color_index(pen0); col_index[1] = get_color_index(pen1); for (int i = 0; i < 8; i++) { pen_index = col_index[BIT(data, 7)]; write_pixel(pen_index); data <<= 1; } } void nick_device::write_pixels2color_lpixel(uint8_t pen0, uint8_t pen1, uint8_t data_byte) { int col_index[2]; int pen_index; uint8_t data = data_byte; col_index[0] = get_color_index(pen0); col_index[1] = get_color_index(pen1); for (int i = 0; i < 8; i++) { pen_index = col_index[BIT(data, 7)]; write_pixel(pen_index); write_pixel(pen_index); data <<= 1; } } void nick_device::write_pixels(uint8_t data_byte, uint8_t char_idx) { /* pen index colour 2-C (0,1), 4-C (0..3) 16-C (0..16) */ int pen_idx; /* Col index = EP colour value */ int pal_idx; uint8_t color_mode = NICK_GET_COLOUR_MODE(m_LPT.MB); uint8_t data = data_byte; switch (color_mode) { case NICK_2_COLOUR_MODE: { int pen_offs = 0; /* do before displaying byte */ /* left margin attributes */ if (m_LPT.LM & NICK_LM_MSBALT) { if (data & 0x080) { pen_offs |= 2; } data &=~0x080; } if (m_LPT.LM & NICK_LM_LSBALT) { if (data & 0x001) { pen_offs |= 4; } data &=~0x01; } if (m_LPT.RM & NICK_RM_ALTIND1) { if (char_idx & 0x080) { pen_offs |= 0x02; } } #if 0 if (m_LPT.RM & NICK_RM_ALTIND0) { if (data & 0x040) { pen_offs |= 0x04; } } #endif write_pixels2color(pen_offs, (pen_offs | 0x01), data); } break; case NICK_4_COLOUR_MODE: { //osd_printf_info("4 colour\r\n"); /* left margin attributes */ if (m_LPT.LM & NICK_LM_MSBALT) { data &= ~0x080; } if (m_LPT.LM & NICK_LM_LSBALT) { data &= ~0x01; } for (int i = 0; i < 4; i++) { pen_idx = m_pen_idx_4col[data]; pal_idx = m_LPT.COL[pen_idx & 0x03]; write_pixel(pal_idx); write_pixel(pal_idx); data <<= 1; } } break; case NICK_16_COLOUR_MODE: { //osd_printf_info("16 colour\r\n"); /* left margin attributes */ if (m_LPT.LM & NICK_LM_MSBALT) { data &= ~0x080; } if (m_LPT.LM & NICK_LM_LSBALT) { data &= ~0x01; } for (int i = 0; i < 2; i++) { pen_idx = m_pen_idx_16col[data]; pal_idx = get_color_index(pen_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); data <<= 1; } } break; case NICK_256_COLOUR_MODE: { /* left margin attributes */ if (m_LPT.LM & NICK_LM_MSBALT) { data &= ~0x080; } if (m_LPT.LM & NICK_LM_LSBALT) { data &= ~0x01; } pal_idx = data; write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); } break; } } void nick_device::write_pixels_lpixel(uint8_t data_byte, uint8_t char_idx) { /* pen index colour 2-C (0,1), 4-C (0..3) 16-C (0..16) */ int pen_idx; /* Col index = EP colour value */ int pal_idx; uint8_t color_mode = NICK_GET_COLOUR_MODE(m_LPT.MB); uint8_t data = data_byte; switch (color_mode) { case NICK_2_COLOUR_MODE: { int pen_offs = 0; /* do before displaying byte */ /* left margin attributes */ if (m_LPT.LM & NICK_LM_MSBALT) { if (data & 0x080) { pen_offs |= 2; } data &=~0x080; } if (m_LPT.LM & NICK_LM_LSBALT) { if (data & 0x001) { pen_offs |= 4; } data &=~0x01; } if (m_LPT.RM & NICK_RM_ALTIND1) { if (char_idx & 0x080) { pen_offs |= 0x02; } } #if 0 if (m_LPT.RM & NICK_RM_ALTIND0) { if (data & 0x040) { pen_offs |= 0x04; } } #endif write_pixels2color_lpixel(pen_offs, (pen_offs | 0x01), data); } break; case NICK_4_COLOUR_MODE: { //osd_printf_info("4 colour\r\n"); /* left margin attributes */ if (m_LPT.LM & NICK_LM_MSBALT) { data &= ~0x080; } if (m_LPT.LM & NICK_LM_LSBALT) { data &= ~0x01; } for (int i = 0; i < 4; i++) { pen_idx = m_pen_idx_4col[data]; pal_idx = m_LPT.COL[pen_idx & 0x03]; write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); data <<= 1; } } break; case NICK_16_COLOUR_MODE: { //osd_printf_info("16 colour\r\n"); /* left margin attributes */ if (m_LPT.LM & NICK_LM_MSBALT) { data &= ~0x080; } if (m_LPT.LM & NICK_LM_LSBALT) { data &= ~0x01; } for (int i = 0; i < 2; i++) { pen_idx = m_pen_idx_16col[data]; pal_idx = get_color_index(pen_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); data <<= 1; } } break; case NICK_256_COLOUR_MODE: { /* left margin attributes */ if (m_LPT.LM & NICK_LM_MSBALT) { data &= ~0x080; } if (m_LPT.LM & NICK_LM_LSBALT) { data &= ~0x01; } pal_idx = data; write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); write_pixel(pal_idx); } break; } } void nick_device::do_pixel(int clocks_visible) { uint8_t buf1, buf2; for (int i = 0; i < clocks_visible; i++) { buf1 = space().read_byte(m_LD1); m_LD1++; buf2 = space().read_byte(m_LD1); m_LD1++; write_pixels(buf1, buf1); write_pixels(buf2, buf1); } } void nick_device::do_lpixel(int clocks_visible) { uint8_t buf1; for (int i = 0; i < clocks_visible; i++) { buf1 = space().read_byte(m_LD1); m_LD1++; write_pixels_lpixel(buf1, buf1); } } void nick_device::do_attr(int clocks_visible) { uint8_t buf1, buf2; for (int i = 0; i < clocks_visible; i++) { buf1 = space().read_byte(m_LD1); m_LD1++; buf2 = space().read_byte(m_LD2); m_LD2++; { uint8_t bg_color = ((buf1 >> 4) & 0x0f); uint8_t fg_color = (buf1 & 0x0f); write_pixels2color_lpixel(bg_color, fg_color, buf2); } } } void nick_device::do_ch256(int clocks_visible) { uint8_t buf1, buf2; for (int i = 0; i < clocks_visible; i++) { buf1 = space().read_byte(m_LD1); m_LD1++; buf2 = space().read_byte(ADDR_CH256(m_LD2, buf1)); write_pixels_lpixel(buf2, buf1); } } void nick_device::do_ch128(int clocks_visible) { uint8_t buf1, buf2; for (int i = 0; i < clocks_visible; i++) { buf1 = space().read_byte(m_LD1); m_LD1++; buf2 = space().read_byte(ADDR_CH128(m_LD2, buf1)); write_pixels_lpixel(buf2, buf1); } } void nick_device::do_ch64(int clocks_visible) { uint8_t buf1, buf2; for (int i = 0; i < clocks_visible; i++) { buf1 = space().read_byte(m_LD1); m_LD1++; buf2 = space().read_byte(ADDR_CH64(m_LD2, buf1)); write_pixels_lpixel(buf2, buf1); } } void nick_device::do_display() { LPT_ENTRY *pLPT = &m_LPT; uint8_t clocks_visible; uint8_t right_margin = NICK_GET_RIGHT_MARGIN(pLPT->RM); uint8_t left_margin = NICK_GET_LEFT_MARGIN(pLPT->LM); clocks_visible = right_margin - left_margin; if (clocks_visible) { /* get display mode */ uint8_t display_mode = NICK_GET_DISPLAY_MODE(pLPT->MB); if (m_scanline_count == 0) // || //((pLPT->MB & NICK_MB_VRES)==0)) { /* doing first line */ /* reload LD1, and LD2 (if necessary) regardless of display mode */ m_LD1 = (pLPT->LD1L & 0xff) | ((pLPT->LD1H & 0xff) << 8); if ((display_mode != NICK_LPIXEL_MODE) && (display_mode != NICK_PIXEL_MODE)) { /* lpixel and pixel modes don't use LD2 */ m_LD2 = (pLPT->LD2L & 0xff) | ((pLPT->LD2H & 0xff) << 8); } } else { /* not first line */ switch (display_mode) { case NICK_ATTR_MODE: { /* reload LD1 */ m_LD1 = (pLPT->LD1L & 0xff) | ((pLPT->LD1H & 0xff) << 8); } break; case NICK_CH256_MODE: case NICK_CH128_MODE: case NICK_CH64_MODE: { /* reload LD1 */ m_LD1 = (pLPT->LD1L & 0xff) | ((pLPT->LD1H & 0xff) << 8); m_LD2++; } break; default: break; } } switch (display_mode) { case NICK_PIXEL_MODE: { do_pixel(clocks_visible); } break; case NICK_ATTR_MODE: { //osd_printf_info("attr mode\r\n"); do_attr(clocks_visible); } break; case NICK_CH256_MODE: { //osd_printf_info("ch256 mode\r\n"); do_ch256(clocks_visible); } break; case NICK_CH128_MODE: { do_ch128(clocks_visible); } break; case NICK_CH64_MODE: { //osd_printf_info("ch64 mode\r\n"); do_ch64(clocks_visible); } break; case NICK_LPIXEL_MODE: { do_lpixel(clocks_visible); } break; default: break; } } } void nick_device::update_lpt() { uint16_t CurLPT = (m_LPL & 0x0ff) | ((m_LPH & 0x0f) << 8); CurLPT++; m_LPL = CurLPT & 0x0ff; m_LPH = (m_LPH & 0x0f0) | ((CurLPT >> 8) & 0x0f); } void nick_device::reload_lpt() { /* get addr of LPT */ uint32_t LPT_Addr = ((m_LPL & 0x0ff) << 4) | ((m_LPH & 0x0f) << (8+4)); /* update internal LPT state */ m_LPT.SC = space().read_byte(LPT_Addr); m_LPT.MB = space().read_byte(LPT_Addr + 1); m_LPT.LM = space().read_byte(LPT_Addr + 2); m_LPT.RM = space().read_byte(LPT_Addr + 3); m_LPT.LD1L = space().read_byte(LPT_Addr + 4); m_LPT.LD1H = space().read_byte(LPT_Addr + 5); m_LPT.LD2L = space().read_byte(LPT_Addr + 6); m_LPT.LD2H = space().read_byte(LPT_Addr + 7); m_LPT.COL[0] = space().read_byte(LPT_Addr + 8); m_LPT.COL[1] = space().read_byte(LPT_Addr + 9); m_LPT.COL[2] = space().read_byte(LPT_Addr + 10); m_LPT.COL[3] = space().read_byte(LPT_Addr + 11); m_LPT.COL[4] = space().read_byte(LPT_Addr + 12); m_LPT.COL[5] = space().read_byte(LPT_Addr + 13); m_LPT.COL[6] = space().read_byte(LPT_Addr + 14); m_LPT.COL[7] = space().read_byte(LPT_Addr + 15); } /* call here to render a line of graphics */ void nick_device::do_line() { uint8_t scanline; m_write_virq((m_LPT.MB & NICK_MB_VIRQ) ? ASSERT_LINE : CLEAR_LINE); if (m_virq && !(m_LPT.MB & NICK_MB_VIRQ)) { m_timer_scanline->adjust(screen().time_until_pos(0, 0), 0, screen().scan_period()); } m_virq = (m_LPT.MB & NICK_MB_VIRQ) ? 1 : 0; if ((m_LPT.MB & NICK_MB_LPT_RELOAD)!=0) { /* reload LPT */ m_LPL = m_reg[2]; m_LPH = m_reg[3]; reload_lpt(); } /* left border */ do_left_margin(); /* do visible part */ do_display(); /* right border */ do_right_margin(); // 0x0f7 is first! /* scan line count for this LPT */ scanline = ((~m_LPT.SC) + 1) & 0x0ff; //printf("scanline %02x\r\n", scanline); /* update count of scanlines done so far */ m_scanline_count++; if (m_scanline_count == scanline) { /* done all scanlines of this Line Parameter Table, get next */ m_scanline_count = 0; update_lpt(); reload_lpt(); } }