diff --git a/PDP11/pdp11_io_lib.c b/PDP11/pdp11_io_lib.c index b1fd0c94..b3eeea6c 100644 --- a/PDP11/pdp11_io_lib.c +++ b/PDP11/pdp11_io_lib.c @@ -867,6 +867,8 @@ AUTO_CON auto_tab[] = {/*c #v am vm fxa fxv */ {04040}, {0270} }, /* NG - vector display */ { { "DAZ" }, 1, 1, 0, 0, {00104} }, /* DAZ */ + { { "TV" }, 1, 0, 0, 0, + {04100} }, /* TV - raster display */ { { NULL }, -1 } /* end table */ }; diff --git a/PDP11/pdp11_sys.c b/PDP11/pdp11_sys.c index 80579fb0..56cbc549 100644 --- a/PDP11/pdp11_sys.c +++ b/PDP11/pdp11_sys.c @@ -122,6 +122,7 @@ extern DEVICE ch_dev; #ifdef USE_DISPLAY extern DEVICE ng_dev; extern DEVICE daz_dev; +extern DEVICE tv_dev; #endif extern REG cpu_reg[]; extern int32 saved_PC; @@ -206,6 +207,7 @@ DEVICE *sim_devices[] = { #ifdef USE_DISPLAY &ng_dev, &daz_dev, + &tv_dev, #endif #else &clk_dev, diff --git a/PDP11/pdp11_tv.c b/PDP11/pdp11_tv.c new file mode 100644 index 00000000..4cadb7a5 --- /dev/null +++ b/PDP11/pdp11_tv.c @@ -0,0 +1,624 @@ +#ifdef USE_DISPLAY +/* pdp11_tv.c: TV, Logo TV raster display + + Copyright (c) 2022, Lars Brinkhoff + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the authors shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from the authors. +*/ + +#include "pdp11_defs.h" +#include "sim_video.h" + +t_stat tv_rd(int32 *data, int32 PA, int32 access); +t_stat tv_wr(int32 data, int32 PA, int32 access); +t_stat tv_svc(UNIT *uptr); +t_stat tv_reset(DEVICE *dptr); +const char *tv_description (DEVICE *dptr); + +#define TV_WINDOWS 2 +#define TV_WIDTH 576 /* Display width. */ +#define TV_HEIGHT 454 /* Display height. */ +#define TV_PIXELS (TV_WIDTH * TV_HEIGHT) /* Total number of pixels. */ +#define TV_KEYS 16 /* Max keys in buffer. */ + +static uint32 tv_surface[TV_WINDOWS][TV_PIXELS]; +static uint32 tv_palette[TV_WINDOWS][2]; +static VID_DISPLAY *tv_vptr[TV_WINDOWS]; +static char tv_updated[TV_WINDOWS]; + +static int tv_keys = 0; +static uint16 tv_key[TV_KEYS]; /* Buffer for LKBB. */ +static uint16 COLORD; +static uint16 VIDSW; /* Video switch register. */ +static uint16 COLORA; +static uint8 tv_source[256]; +static uint8 tv_display[256]; +static uint16 TVINCR; +static uint8 TVSEL; /* Frame buffer select. */ +static uint16 TVRADR; +static uint16 tvdata; +static uint16 TVWC; +static uint16 TVDADR; +static uint16 TVSHR; +static uint16 TVMSK; +static uint16 TVDWIN; +static uint16 TVRWIN; +static uint16 TVCNSL[64]; + +#define FB (16*1024) +static uint16 RAM[64*FB]; + +#define BASE ((TVSEL & 077) * FB) + +/* VIDSW bits. */ +// Low byte is the console number. +// High byte is the video source. + +/* TVINCR bits. */ +#define TVINC 0077 /* Mask for the increment. */ + +/* TVSEL bits. */ +#define TVRCNS 0077 /* The console number mask. */ +#define TVNSH 0000 /* No shift write mode. */ +#define TVIOR 0100 /* The inclusive or mode. */ +#define TVXOR 0200 /* The xor mode. */ +#define TVMOV 0300 /* The move function. */ + +/* TVSHR bits. */ +#define TVSHCN 0017 /* The shift count. */ + +/* TVCNSL bits. */ +#define SCROLL 007777 /* Scroll pointer. */ +#define REVSCR 010000 /* Reverse video. */ + +#define IOLN_TV 064 +DIB tv_dib = { + IOBA_AUTO, IOLN_TV, &tv_rd, &tv_wr, + 0, 0, 0, {NULL}, IOLN_TV +}; + +UNIT tv_unit = { + UDATA (&tv_svc, UNIT_IDLE, 0), 0 +}; + +REG tv_reg[] = { + { ORDATAD(LKBB, tv_key[0], 16, "Lebel keyboard interface") }, + { ORDATAD(COLORA, COLORA, 16, "Color map address") }, + { ORDATAD(VIDSW, VIDSW, 16, "Video switch") }, + { ORDATAD(COLORD, COLORD, 16, "Color map data") }, + { ORDATAD(TVINCR, TVINCR, 16, "Increment") }, + { ORDATAD(TVSEL, TVSEL, 16, "Console select") }, + { ORDATAD(TVRADR, TVRADR, 16, "Regular transfer address") }, + { ORDATAD(TVWC, TVWC, 16, "Word count") }, + { ORDATAD(TVDADR, TVDADR, 16, "Disk transfer address") }, + { ORDATAD(TVSHR, TVSHR, 16, "Shift register") }, + { ORDATAD(TVMSK, TVMSK, 16, "Mask") }, + { ORDATAD(TVDWIN, TVDWIN, 16, "Disk data window") }, + { ORDATAD(TVRWIN, TVRWIN, 16, "Regular data window") }, + { ORDATAD(TVCNSL, TVCNSL[0], 16, "Console status") }, + { NULL } +}; + +MTAB tv_mod[] = { + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 020, "ADDRESS", "ADDRESS", + &set_addr, &show_addr, NULL, "Bus address" }, + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "VECTOR", "VECTOR", + &set_vec, &show_vec, NULL, "Interrupt vector" }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "AUTOCONFIGURE", + &set_addr_flt, NULL, NULL, "Enable autoconfiguration of address & vector" }, + { 0 } }; + +#define DBG_IO 0001 +#define DBG_VID 0002 +#define DBG_KEY 0004 + +DEBTAB tv_deb[] = { + { "IO", DBG_IO, "IO page" }, + { "VID", DBG_VID, "video" }, + { "KEY", DBG_KEY, "keyboard" }, + { NULL, 0 } +}; + +DEVICE tv_dev = { + "TV", &tv_unit, tv_reg, tv_mod, + 1, 8, 16, 1, 8, 16, + NULL, NULL, &tv_reset, + NULL, NULL, NULL, + &tv_dib, DEV_DIS | DEV_DISABLE | DEV_UBUS | DEV_DEBUG, + 0, tv_deb, NULL, NULL, NULL, NULL, NULL, + &tv_description +}; + +static void +render_word (uint8 buffer, uint16 address) +{ + uint8 display = tv_display[buffer]; + uint16 data; + int i; + + buffer &= 077; + address /= 2; + address += (TVCNSL[buffer] & SCROLL) << 2; + address &= FB-1; + data = RAM[FB * buffer + address]; + + for (i = 0; i < 16; i++) { + int pixel = (data & (0100000 >> i)) ? 0 : 1; + if (TVCNSL[buffer] & REVSCR) + pixel ^= 1; + tv_surface[display][address * 16 + i] = + tv_palette[display][pixel]; + } +} + +static void +render_display (uint16 display) +{ + uint8 buffer = tv_source[display]; + int i; + sim_debug (DBG_VID, &tv_dev, "Render display %d buffer %d\n", + display, buffer); + for (i = 0; i < (TV_PIXELS / 8); i += 2) + render_word (buffer, i); + tv_updated[display] = 1; +} + +static void +tv_alu (void) +{ + uint16 data = RAM[BASE + (TVRADR/2)]; + uint16 data2 = tvdata; + int sh = (TVSHR & TVSHCN); + + if (TVSEL & TVMOV) + data2 = (data2 << sh) | (data2 >> (16 - sh)); + data2 &= ~TVMSK; + switch (TVSEL & TVMOV) { + case TVIOR: data |= data2; break; + case TVXOR: data ^= data2; break; + case TVNSH: + case TVMOV: data = (data & TVMSK) | data2; break; + } + RAM[BASE + (TVRADR/2)] = data; + render_word (TVSEL & 077, TVRADR); + tv_updated[tv_display[TVSEL & 077]] = 1; + TVRADR += 2 * (TVINCR & TVINC); +} + +static void +tv_loop (void) +{ + while (TVWC != 0) { + tv_alu (); + TVWC++; + } +} + +t_stat +tv_rd(int32 *data, int32 PA, int32 access) +{ + t_stat stat = SCPE_OK; + *data = 0; + switch (PA & 077) { + case 000: + if (tv_keys > 0) + *data = tv_key[--tv_keys]; + else + *data = 0; + sim_debug (DBG_IO, &tv_dev, "READ LKBB %06o\n", *data); + break; + case 002: + *data = COLORD; + sim_debug (DBG_IO, &tv_dev, "READ COLORD %06o\n", *data); + break; + case 004: + *data = VIDSW; + sim_debug (DBG_IO, &tv_dev, "READ VIDSW %06o\n", *data); + break; + case 006: + *data = COLORA; + sim_debug (DBG_IO, &tv_dev, "READ COLORA %06o\n", *data); + break; + case 040: + *data = TVINCR; + sim_debug (DBG_IO, &tv_dev, "READ TVINCR %06o\n", *data); + break; + case 042: + *data = TVSEL; + sim_debug (DBG_IO, &tv_dev, "READ TVSEL %06o\n", *data); + break; + case 044: + *data = TVRADR; + sim_debug (DBG_IO, &tv_dev, "READ TVRADR %06o\n", *data); + break; + case 046: + *data = TVWC; + sim_debug (DBG_IO, &tv_dev, "READ TVWC %06o\n", *data); + break; + case 050: + *data = TVDADR; + sim_debug (DBG_IO, &tv_dev, "READ TVDADR %06o\n", *data); + break; + case 052: + *data = TVSHR; + sim_debug (DBG_IO, &tv_dev, "READ TVSHR %06o\n", *data); + break; + case 053: + *data = TVSHR >> 8; + sim_debug (DBG_IO, &tv_dev, "READ TVSHR+1 %06o\n", *data); + break; + case 054: + *data = TVMSK; + sim_debug (DBG_IO, &tv_dev, "READ TVMSK %06o\n", *data); + break; + case 056: + *data = TVDWIN; + sim_debug (DBG_IO, &tv_dev, "READ TVDWIN %06o\n", *data); + break; + case 060: + *data = RAM[BASE + (TVRADR/2)]; + sim_debug (DBG_IO, &tv_dev, "READ TVRWIN[%06o] %06o\n", + TVRADR, *data); + break; + case 062: + *data = TVCNSL[TVSEL & 077]; + sim_debug (DBG_IO, &tv_dev, "READ TVCNSL %06o\n", *data); + break; + default: + sim_debug (DBG_IO, &tv_dev, "READ %06o\n", PA); + break; + } + return stat; +} + +t_stat +tv_wr(int32 data, int32 PA, int32 access) +{ + t_stat stat = SCPE_OK; + uint16 x; + const char *a = "WRITE"; + if (access == WRITEB) + a = "WRITEB"; + switch (PA & 077) { + case 000: + sim_debug (DBG_IO, &tv_dev, "%s LKBB %06o\n", a, data); + break; + case 002: + sim_debug (DBG_IO, &tv_dev, "%s COLORD %06o\n", a, data); + COLORD = data; + break; + case 004: + sim_debug (DBG_IO, &tv_dev, "%s VIDSW %06o\n", a, data); + tv_source[data >> 8] = data & 0377; + tv_display[data & 0377] = data >> 8; + render_display (data >> 8); + break; + case 006: + sim_debug (DBG_IO, &tv_dev, "%s COLORA %06o\n", a, data); + COLORA = data; + break; + case 040: + sim_debug (DBG_IO, &tv_dev, "%s TVINCR %06o\n", a, data); + TVINCR = data; + break; + case 042: + sim_debug (DBG_IO, &tv_dev, "%s TVSEL %06o\n", a, data); + if (access == WRITEB) { + data &= 0377; + data |= TVSEL & 0177400; + } + TVSEL = data; + break; + case 044: + sim_debug (DBG_IO, &tv_dev, "%s TVRADR %06o\n", a, data); + TVRADR = data; + break; + case 046: + sim_debug (DBG_IO, &tv_dev, "%s TVWC %06o\n", a, data); + TVWC = data; + tv_loop (); + break; + case 050: + sim_debug (DBG_IO, &tv_dev, "%s TVDADR %06o\n", a, data); + TVDADR = data; + break; + case 052: + sim_debug (DBG_IO, &tv_dev, "%s TVSHR %06o\n", a, data); + TVSHR = data; + break; + case 053: + sim_debug (DBG_IO, &tv_dev, "%s TVSHR+1 %06o\n", a, data); + TVSHR &= 0377; + TVSHR |= (data & 0377) << 8; + break; + case 054: + sim_debug (DBG_IO, &tv_dev, "%s TVMSK %06o\n", a, data); + TVMSK = data; + break; + case 056: + sim_debug (DBG_IO, &tv_dev, "%s TVDWIN %06o\n", a, data); + TVDWIN = data; + break; + case 060: + sim_debug (DBG_IO, &tv_dev, "%s TVRWIN[%06o] %06o\n", + a, TVRADR, data); + tvdata = data; + tv_alu (); + break; + case 062: + sim_debug (DBG_IO, &tv_dev, "%s TVCNSL %06o\n", a, data); + x = TVCNSL[TVSEL & 077]; + TVCNSL[TVSEL & 077] = data; + if (x ^ data) + render_display (tv_display[TVSEL & 077]); + break; + default: + sim_debug (DBG_IO, &tv_dev, "%s %06o %06o\n", a, PA, data); + break; + } + return stat; +} + +static void +toggle_fullscreen (VID_DISPLAY *vptr) +{ + vid_set_fullscreen_window (vptr, !vid_is_fullscreen_window (vptr)); +} + +#define KEY(NORMAL, SHIFTED) (shifted ? (SHIFTED) : (NORMAL)) +#define CTL(NORMAL, SHIFTED, CTLED) \ + (control ? (CTLED) : KEY(NORMAL, SHIFTED)) + +static uint16 +translate_key (SIM_KEY_EVENT *ev) +{ + static int shifted = 0; + static int control = 0; + + if (ev->state == SIM_KEYPRESS_UP) { + switch (ev->key) { + case SIM_KEY_F11: + toggle_fullscreen (ev->vptr); + break; + case SIM_KEY_SHIFT_L: + case SIM_KEY_SHIFT_R: + shifted = 0; + break; + case SIM_KEY_CTRL_L: + case SIM_KEY_CTRL_R: + case SIM_KEY_CAPS_LOCK: + control = 0; + break; + } + return 0; + } + + switch (ev->key) { + case SIM_KEY_SHIFT_L: + case SIM_KEY_SHIFT_R: + shifted = 1; + return 0; + case SIM_KEY_CTRL_L: + case SIM_KEY_CTRL_R: + case SIM_KEY_CAPS_LOCK: + control = 1; + return 0; + case SIM_KEY_0: + return KEY (0060, 0004); + case SIM_KEY_1: + return KEY (0061, 0041); + case SIM_KEY_2: + return KEY (0062, 0052); + case SIM_KEY_3: + return KEY (0063, 0043); + case SIM_KEY_4: + return KEY (0064, 0044); + case SIM_KEY_5: + return KEY (0065, 0045); + case SIM_KEY_6: + return CTL (0066, 0137, 0037); + case SIM_KEY_7: + return KEY (0067, 0136); + case SIM_KEY_8: + return KEY (0070, 0030); + case SIM_KEY_9: + return KEY (0071, 0003); + case SIM_KEY_A: + return CTL (0141, 0101, 0000); + case SIM_KEY_B: + return CTL (0142, 0102, 0000); + case SIM_KEY_C: + return CTL (0143, 0103, 0000); + case SIM_KEY_D: + return CTL (0144, 0104, 0002); + case SIM_KEY_E: + return CTL (0145, 0105, 0000); + case SIM_KEY_F: + return CTL (0146, 0106, 0014); + case SIM_KEY_G: + return CTL (0147, 0107, 0034); + case SIM_KEY_H: + return CTL (0150, 0110, 0000); + case SIM_KEY_I: + return CTL (0151, 0111, 0012); + case SIM_KEY_J: + return CTL (0152, 0112, 0007); + case SIM_KEY_K: + return CTL (0153, 0113, 0000); + case SIM_KEY_L: + return CTL (0154, 0114, 0013); + case SIM_KEY_M: + return CTL (0155, 0115, 0015); + case SIM_KEY_N: + return CTL (0156, 0116, 0000); + case SIM_KEY_O: + return CTL (0157, 0117, 0000); + case SIM_KEY_P: + return CTL (0160, 0120, 0000); + case SIM_KEY_Q: + return CTL (0161, 0121, 0000); + case SIM_KEY_R: + return CTL (0162, 0122, 0000); + case SIM_KEY_S: + return CTL (0163, 0123, 0000); + case SIM_KEY_T: + return CTL (0164, 0124, 0000); + case SIM_KEY_U: + return CTL (0165, 0125, 0177); + case SIM_KEY_V: + return CTL (0166, 0126, 0000); + case SIM_KEY_W: + return CTL (0167, 0127, 0000); + case SIM_KEY_X: + return CTL (0170, 0130, 0000); + case SIM_KEY_Y: + return CTL (0171, 0131, 0000); + case SIM_KEY_Z: + return CTL (0172, 0132, 0000); + case SIM_KEY_BACKQUOTE: + return KEY (0050, 0051); + case SIM_KEY_MINUS: + return CTL (0020, 0033, 005); + case SIM_KEY_EQUALS: + return KEY (0055, 0001); + case SIM_KEY_LEFT_BRACKET: + return CTL (0133, 0000, 0011); + case SIM_KEY_RIGHT_BRACKET: + return CTL (0134, 0000, 0036); + case SIM_KEY_SEMICOLON: + return KEY (0042, 0047); + case SIM_KEY_SINGLE_QUOTE: + return KEY (0046, 0073); + case SIM_KEY_BACKSLASH: + case SIM_KEY_LEFT_BACKSLASH: + return CTL (0032, 0176, 0035); + case SIM_KEY_COMMA: + return KEY (0054, 0075); + case SIM_KEY_PERIOD: + return KEY (0056, 0140); + case SIM_KEY_SLASH: + return KEY (0027, 0057); + case SIM_KEY_ESC: + case SIM_KEY_F1: + return 011; + case SIM_KEY_BACKSPACE: + case SIM_KEY_DELETE: + return 031; + case SIM_KEY_TAB: + return 012; + case SIM_KEY_ENTER: + return 015; + case SIM_KEY_SPACE: + return 040; + default: + return 0; + } +} + +static uint16 +keyboard_number (VID_DISPLAY *vptr, uint16 code) +{ + int i; + for (i = 0; i < TV_WINDOWS; i++) { + if (vptr == tv_vptr[i]) + return code | (i << 8); + } + return 0; +} + +t_stat tv_svc(UNIT *uptr) +{ + SIM_KEY_EVENT ev; + uint16 key; + int i; + + for (i = 0; i < TV_WINDOWS; i++) { + if (!tv_updated[i]) + continue; + vid_draw_window (tv_vptr[i], 0, 0, TV_WIDTH, TV_HEIGHT, tv_surface[i]); + vid_refresh_window (tv_vptr[i]); + tv_updated[i] = 0; + sim_debug (DBG_VID, &tv_dev, "Display %d refreshed.\n", i); + } + + while (vid_poll_kb (&ev) == SCPE_OK) { + key = translate_key (&ev); + if (key != 0) + key = keyboard_number (ev.vptr, key); + sim_debug (DBG_KEY, &tv_dev, "Keyboard %06o.\n", key); + if (tv_keys < TV_KEYS) + tv_key[tv_keys++] = key; + } + + sim_activate_after (&tv_unit, 10000); + + return SCPE_OK; +} + +t_stat tv_reset(DEVICE *dptr) +{ + t_stat r; + int i; + + if (dptr->flags & DEV_DIS || sim_switches & SWMASK('P')) { + for (i = 0; i < TV_WINDOWS; i++) { + if (tv_vptr[i] != NULL) + vid_close_window (tv_vptr[i]); + } + memset (tv_vptr, 0, sizeof tv_vptr); + memset (tv_palette, 0, sizeof tv_palette); + memset (tv_updated, 0, sizeof tv_updated); + sim_cancel (&tv_unit); + return SCPE_OK; + } + + for (i = 0; i < TV_WINDOWS; i++) { + if (tv_vptr[i] == NULL) { + char title[40]; + snprintf (title, sizeof title, "Display %d", i); + r = vid_open_window (&tv_vptr[i], &tv_dev, title, TV_WIDTH, TV_HEIGHT, 0); + if (r != SCPE_OK) + return r; + fprintf(stderr, "Window %d is %p\r\n", i, tv_vptr[i]); + tv_palette[i][0] = vid_map_rgb_window (tv_vptr[i], 0x00, 0x00, 0x00); + tv_palette[i][1] = vid_map_rgb_window (tv_vptr[i], 0x00, 0xFF, 0x30); + memset (TVCNSL, 0, sizeof TVCNSL); + memset (tv_source, 0, sizeof tv_source); + memset (tv_display, 0, sizeof tv_display); + render_display (i); + } + } + + tv_keys = 0; + + sim_activate (&tv_unit, 1); + return SCPE_OK; +} + +const char *tv_description (DEVICE *dptr) +{ + return "Raster display controller for MIT Logo PDP-11/45"; +} + +#else /* USE_DISPLAY not defined */ +char pdp11_tv_unused; /* sometimes empty object modules cause problems */ +#endif /* USE_DISPLAY not defined */ diff --git a/Visual Studio Projects/PDP11.vcproj b/Visual Studio Projects/PDP11.vcproj index c705c469..c27304a1 100644 --- a/Visual Studio Projects/PDP11.vcproj +++ b/Visual Studio Projects/PDP11.vcproj @@ -373,6 +373,10 @@ RelativePath="..\PDP11\pdp11_tu.c" > + + diff --git a/makefile b/makefile index 28d2a45e..57d3b3ec 100644 --- a/makefile +++ b/makefile @@ -1440,7 +1440,7 @@ PDP11 = ${PDP11D}/pdp11_fp.c ${PDP11D}/pdp11_cpu.c ${PDP11D}/pdp11_dz.c \ ${PDP11D}/pdp11_kmc.c ${PDP11D}/pdp11_dup.c ${PDP11D}/pdp11_rs.c \ ${PDP11D}/pdp11_vt.c ${PDP11D}/pdp11_td.c ${PDP11D}/pdp11_io_lib.c \ ${PDP11D}/pdp11_rom.c ${PDP11D}/pdp11_ch.c ${PDP11D}/pdp11_dh.c \ - ${PDP11D}/pdp11_ng.c ${PDP11D}/pdp11_daz.c \ + ${PDP11D}/pdp11_ng.c ${PDP11D}/pdp11_daz.c ${PDP11D}/pdp11_tv.c \ ${DISPLAYL} ${DISPLAYNG} ${DISPLAYVT} PDP11_OPT = -DVM_PDP11 -I ${PDP11D} ${NETWORK_OPT} ${DISPLAY_OPT}