1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
//! Texas Instruments TMS9918A VDP emulator library

use rand::Rng;

// TMS9918A video modes
#[derive(PartialEq, Debug)]
pub enum VideoMode {
    /// Graphics I: 256x192 pixels, 32x24 tiles of 8x8 pixels each, 1 character set.
    /// 
    /// Each group of 8 tiles has the same 2-color limit.
    Gfx1,
    /// Graphics II: 256x192 pixels, 32x24 tiles of 8x8 pixels each, 3 character sets.
    /// 
    /// Each 8-pixel line of a tile has a 2-color limit.
    /// 
    /// This mode is not currently implemented.
    Gfx2,
    /// Text: 240x192 pixels, 40x24 tiles of 6x8 pixels each, 1 character set.
    /// 
    /// 2 colors for the whole screen, set by the contents of register 7.
    Text,
    /// Multicolor: 256x192 pixels, 64x48 virtual pixels
    /// 
    /// Each virtual pixel has their own color.
    /// 
    /// This mode is not currently implemented.
    Multicolor
}

pub struct TMS9918A {
    /// VDP framebuffer
    pub frame: Vec<u32>,
    /// VDP framebuffer width
    pub frame_width: usize,
    /// VDP framebuffer height
    pub frame_height: usize,
    // if true, clear framebuffer on next update
    frame_clear: bool,

    /// TMS9918A video memory, 16KB: contains name table, color table, and pattern table
    /// 
    /// Initialized with random values to simulate real memory behavior.
    pub vdp_ram: Vec<u8>,
    // offsets into VDP_RAM for the various tables
    vdp_name_table_offset: u16,
    vdp_color_table_offset: u16,
    vdp_pattern_table_offset: u16,
    // TMS9918A registers
    vdp_register: Vec<u8>,
    // TMS9918A video mode
    vdp_mode: VideoMode,
    // temporary data register
    vdp_temp_data: u8,
    // current VDP memory address pointer
    vdp_addr_pointer: u16,
    // true after the first command byte was sent
    vdp_first_byte_saved_flag: bool,
    // byte at current memory address pointer
    vdp_read_ahead: u8
}

impl TMS9918A {
    /// Create a new TMS9918A state
    /// 
    /// # Examples
    /// 
    /// ```no_run
    /// # use tms9918a_emu::TMS9918A;
    /// # fn main() {
    /// let mut vdp = TMS9918A::new();
    /// # }
    /// ```
    pub fn new() -> Self {
        let frame: Vec<u32> = vec![0; 256 * 196];

        TMS9918A {
            frame: frame,
            frame_width: 256,
            frame_height: 196,
            frame_clear: false,
            vdp_ram: (0..16*1024).map(|_| rand::thread_rng().gen()).collect(),
            vdp_name_table_offset: 0,
            vdp_color_table_offset: 0,
            vdp_pattern_table_offset: 0,
            vdp_register: vec![0; 8],
            vdp_mode: VideoMode::Gfx1,
            vdp_temp_data: 0,
            vdp_addr_pointer: 0,
            vdp_first_byte_saved_flag: false,
            vdp_read_ahead: 0
        }
    }

    /// Update the framebuffer from the TMS9918A video memory contents
    ///
    /// # Examples
    /// 
    /// ```no_run
    /// # use tms9918a_emu::TMS9918A;
    /// # fn main() {
    /// let mut vdp = TMS9918A::new();
    /// 
    /// while vdp.window.is_open() {
    ///     vdp.update();
    /// }
    /// # }
    /// ```
    pub fn update(&mut self) {
        let colors: [u32; 16] = [0x000000, 0x000000, 0x21C942, 0x5EDC78,
                                0x5455ED, 0x7D75FC, 0xD3524D, 0x43EBF6,
                                0xFD5554, 0xFF7978, 0xD3C153, 0xE5CE80,
                                0x21B03C, 0xC95BBA, 0xCCCCCC, 0xFFFFFF];

        if self.frame_clear {
            for i in self.frame.iter_mut() {
                *i = 0;
            }
            self.frame_clear = false;
        }

        // check blanking bit
        if self.vdp_register[1] & (1 << 6) != 0 {
            // blanking bit is set, screen is enabled
            // Graphics I
            if self.vdp_mode == VideoMode::Gfx1 {
                self.frame_width = 256;
                self.frame_height = 196;
                for tile_y in 0..24 {
                    for tile_x in 0..32 {
                        let name_entry = self.vdp_ram[self.vdp_name_table_offset as usize + (tile_y * 32) + tile_x];
                        let color_entry = name_entry / 8;
                        let color_byte = self.vdp_ram[self.vdp_color_table_offset as usize + color_entry as usize];
                        let foreground_color = colors[color_byte as usize >> 4 & 0x0F];
                        let background_color = colors[color_byte as usize & 0x0F];
                        for pattern_byte in 0..8 {
                            let offset = self.vdp_pattern_table_offset as usize + (name_entry as usize * 8) + (pattern_byte);
                            let pattern = self.vdp_ram[offset];
                            let pattern_bit_indexes = 0..8;
                            let frame_bit_indexes = (0..8).rev();
                            for (pattern_bit, frame_bit) in pattern_bit_indexes.zip(frame_bit_indexes) {
                                let pixel = if pattern & (1 << pattern_bit) != 0 { foreground_color } else { background_color };
                                let frame_offset = (tile_x * 8) + (tile_y * 8 * self.frame_width) + (pattern_byte * self.frame_width) + frame_bit;
                                self.frame[frame_offset] = pixel;
                            }
                        }
                    }
                }
            }

            // Text
            if self.vdp_mode == VideoMode::Text {
                self.frame_width = 240;
                self.frame_height = 196;
                for tile_y in 0..24 {
                    for tile_x in 0..40 {
                        let name_entry = self.vdp_ram[self.vdp_name_table_offset as usize + (tile_y * 40) + tile_x];
                        let color_byte = self.vdp_register[7];
                        let foreground_color = colors[color_byte as usize >> 4 & 0x0F];
                        let background_color = colors[color_byte as usize & 0x0F];
                        for pattern_byte in 0..8 {
                            let offset = self.vdp_pattern_table_offset as usize + (name_entry as usize * 8) + (pattern_byte);
                            let pattern = self.vdp_ram[offset];
                            let pattern_bit_indexes = 2..8;
                            let frame_bit_indexes = (0..6).rev();
                            for (pattern_bit, frame_bit) in pattern_bit_indexes.zip(frame_bit_indexes) {
                                let pixel = if pattern & (1 << pattern_bit) != 0 { foreground_color } else { background_color };
                                let frame_offset = (tile_x * 6) + (tile_y * 8 * self.frame_width) + (pattern_byte * self.frame_width) + frame_bit;
                                self.frame[frame_offset] = pixel;
                            }
                        }
                    }
                }
            }
        } else {
            // blanking bit is clear, screen is disabled
            for i in self.frame.iter_mut() {
                *i = 0;
            }
        }
    }

    /// Enable or disable the video display by setting or clearing the blanking bit in register 1
    /// 
    /// The video display is disabled by default due to registers 0 and 1 being cleared on reset,
    /// resulting in a black screen similar to the behavior of a real TMS9918A.
    #[inline]
    pub fn enable_video(&mut self, enable: bool) {
        if enable {
            self.vdp_register[1] |= 1 << 6;
        } else {
            self.vdp_register[1] &= !(1 << 6);
        }
    }

    /// Reset VDP to initial state without modifying video memory
    pub fn warm_reset(&mut self) {
        self.write_register(0, 0);
        self.write_register(1, 0);
        self.vdp_temp_data = 0;
        self.vdp_addr_pointer = 0;
        self.vdp_read_ahead = 0;
        self.vdp_first_byte_saved_flag = false;
    }

    /// Reset VDP to initial state and randomize video memory contents
    pub fn cold_reset(&mut self) {
        self.warm_reset();
        for i in self.vdp_ram.iter_mut() {
            *i = rand::thread_rng().gen();
        }
    }

    /// Set TMS9918A video mode
    /// 
    /// Valid video modes are Text, Graphics I, Graphics II, and Multicolor.
    /// 
    /// Graphics II and Multicolor modes are not currently implemented, and sprites are not currently implemented in any mode.
    /// 
    /// Undocumented modes (combining video modes by setting the bitmap enable bit in register 0) are not supported.
    /// 
    /// # Examples
    /// 
    /// ```no_run
    /// # use tms9918a_emu::{TMS9918A, VideoMode};
    /// # fn main() {
    /// # let mut vdp = TMS9918A::new();
    /// // use text mode
    /// vdp.set_video_mode(VideoMode::Text);
    /// # }
    /// ```
    pub fn set_video_mode(&mut self, mode: VideoMode) {
        match mode {
            VideoMode::Gfx1 => {
                let r0 = self.vdp_register[0] & !(0b01000000);
                let r1 = self.vdp_register[1] & !(0b00011000);
                self.write_register(0, r0);
                self.write_register(1, r1);
            }
            VideoMode::Gfx2 => {
                let r0 = self.vdp_register[0] | (0b01000000);
                let r1 = self.vdp_register[1] & !(0b00011000);
                self.write_register(0, r0);
                self.write_register(1, r1);
            }
            VideoMode::Multicolor => {
                let r0 = self.vdp_register[0] & !(0b01000000);
                let r1 = (self.vdp_register[1] & !(0b00010000)) | 0b00001000;
                self.write_register(0, r0);
                self.write_register(1, r1);
            }
            VideoMode::Text => {
                let r0 = self.vdp_register[0] & !(0b01000000);
                let r1 = (self.vdp_register[1] & !(0b00001000)) | 0b00010000;
                self.write_register(0, r0);
                self.write_register(1, r1);
            }
        }
    }

    /// Write register value
    pub fn write_register(&mut self, register: u8, data: u8) {
        // write register value
        self.vdp_register[register as usize] = data;

        // write offset values
        self.vdp_name_table_offset = self.vdp_register[2] as u16 * 0x0400;
        self.vdp_color_table_offset = self.vdp_register[3] as u16 * 0x0040;
        self.vdp_pattern_table_offset = self.vdp_register[4] as u16 * 0x0800;

        // write video mode
        if register == 0 || register == 1 {
            // register 0 bit 6: enable a bitmap graphics mode
            let m3 = if self.vdp_register[0] & (1 << 6) != 0 { true } else { false };
            // register 1 bit 3: enable text mode
            let m1 = if self.vdp_register[1] & (1 << 4) != 0 { true } else { false };
            // register 0 bit 6: enable multicolor mode
            let m2 = if self.vdp_register[1] & (1 << 3) != 0 { true } else { false };

            match (m1, m2, m3) {
                (false, false, false) => {
                    self.vdp_mode = VideoMode::Gfx1;
                    // clear framebuffer on next update
                    self.frame_clear = true;
                }
                (false, false, true) => {
                    self.vdp_mode = VideoMode::Gfx2;
                    // clear framebuffer on next update
                    self.frame_clear = true;
                }
                (false, true, false) => {
                    self.vdp_mode = VideoMode::Multicolor;
                    // clear framebuffer on next update
                    self.frame_clear = true;
                }
                (true, false, false) => {
                    self.vdp_mode = VideoMode::Text;
                    // clear framebuffer on next update
                    self.frame_clear = true;
                }
                _ => panic!("unimplemented video mode combination: M1: {}, M2: {}, M3: {}", m1, m2, m3)
            }

            //println!("set graphics mode: {:?}", self.vdp_mode);
        }
    }

    /// Write memory contents
    #[inline]
    pub fn write_ram(&mut self, address: usize, data: u8) {
        self.vdp_ram[address] = data;
    }

    /// Read memory contents
    #[inline]
    pub fn read_ram(&mut self, address: usize) -> u8 {
        let data = self.vdp_ram[address];
        data
    }

    /// Set the name table address multiplier in register 2
    /// 
    /// Name table base address is equal to multiplier * 0x0400.
    /// 
    /// This function is equivalent to setting register 2 directly.
    /// 
    /// # Examples
    /// 
    /// ```no_run
    /// # use tms9918a_emu::{TMS9918A, VideoMode};
    /// # fn main() {
    /// # let mut vdp = TMS9918A::new();
    /// // set name table base address to 0x0400
    /// vdp.set_name_table_multiplier(1);
    /// # }
    /// ```
    #[inline]
    pub fn set_name_table_multiplier(&mut self, mut multiplier: u8) {
        if multiplier > 15 {
            multiplier = 15;
        }
        self.write_register(2, multiplier);
    }

    /// Fill name table contents from an array
    /// 
    /// Name table offset register must be set first.
    /// 
    /// # Examples
    /// 
    /// ```no_run
    /// # use tms9918a_emu::{TMS9918A, VideoMode};
    /// # fn main() {
    /// # let mut vdp = TMS9918A::new();
    /// // fill the first 5 name table entries
    /// let name_table: [u8; 5] = [1, 2, 3, 4, 5];
    /// vdp.fill_name_table(&name_table, 0, name_table.len());
    /// # }
    /// ```
    #[inline]
    pub fn fill_name_table(&mut self, array: &[u8], offset: usize, length: usize) {
        for i in offset..offset+length {
            self.write_name_table(i, array[i]);
        }
    }

    /// Clear the screen by zeroing the name table
    /// 
    /// Name table offset register must be set first.
    #[inline]
    pub fn clear_name_table(&mut self) {
        if self.vdp_mode == VideoMode::Text {
            // text mode's name table is 960 bytes
            for i in 0..960 {
                self.write_name_table(i, 0);
            }
        } else {
            // all other modes' name tables are 768 bytes
            for i in 0..768 {
                self.write_name_table(i, 0);
            }
        }
    }

    /// Write name table contents
    /// 
    /// Name table offset register must be set first.
    #[inline]
    pub fn write_name_table(&mut self, offset: usize, data: u8) {
        self.vdp_ram[self.vdp_name_table_offset as usize + offset] = data;
    }

    /// Read name table contents
    /// 
    /// Name table offset register must be set first.
    #[inline]
    pub fn read_name_table(&self, offset: usize) -> u8 {
        self.vdp_ram[self.vdp_name_table_offset as usize + offset]
    }

    /// Set the color table address multiplier in register 3
    /// 
    /// Color table base address is equal to multiplier * 0x0040.
    /// 
    /// This function is equivalent to setting register 3 directly.
    /// 
    /// # Examples
    /// 
    /// ```no_run
    /// # use tms9918a_emu::{TMS9918A, VideoMode};
    /// # fn main() {
    /// # let mut vdp = TMS9918A::new();
    /// // set color table base address to 0x0040
    /// vdp.set_color_table_multiplier(1);
    /// # }
    /// ```
    #[inline]
    pub fn set_color_table_multiplier(&mut self, multiplier: u8) {
        self.write_register(3, multiplier);
    }

    /// Fill color table contents from an array
    /// 
    /// Color table offset register must be set first.
    /// 
    /// # Examples
    /// 
    /// ```no_run
    /// # use tms9918a_emu::{TMS9918A, VideoMode};
    /// # fn main() {
    /// # let mut vdp = TMS9918A::new();
    /// // fill the first 5 color table entries
    /// // black on white, white on black, light blue on dark blue, light red on cyan, black on gray
    /// let color_table: [u8; 5] = [0x1F, 0xF1, 0x54, 0x97, 0x1E];
    /// vdp.fill_color_table(&color_table, 0, color_table.len());
    /// # }
    /// ```
    #[inline]
    pub fn fill_color_table(&mut self, array: &[u8], offset: usize, length: usize) {
        for i in offset..offset+length {
            self.write_color_table(i, array[i]);
        }
    }

    /// Write color table contents
    /// 
    /// Color table offset register must be set first.
    #[inline]
    pub fn write_color_table(&mut self, offset: usize, data: u8) {
        self.vdp_ram[self.vdp_color_table_offset as usize + offset] = data;
    }

    /// Read color table contents
    /// 
    /// Color table offset register must be set first.
    #[inline]
    pub fn read_color_table(&self, offset: usize) -> u8 {
        self.vdp_ram[self.vdp_color_table_offset as usize + offset]
    }

    /// Set the pattern table address multiplier in register 4
    /// 
    /// Pattern table base address is equal to multiplier * 0x0800.
    /// 
    /// This function is equivalent to setting register 4 directly.
    /// 
    /// # Examples
    /// 
    /// ```no_run
    /// # use tms9918a_emu::{TMS9918A, VideoMode};
    /// # fn main() {
    /// # let mut vdp = TMS9918A::new();
    /// // set pattern table base address to 0x0800
    /// vdp.set_pattern_table_multiplier(1);
    /// # }
    /// ```
    #[inline]
    pub fn set_pattern_table_multiplier(&mut self, mut multiplier: u8) {
        if multiplier > 7 {
            multiplier = 7;
        }
        self.write_register(4, multiplier);
    }

    /// Fill pattern table contents from an array
    /// 
    /// Pattern table offset register must be set first.
    /// 
    /// # Examples
    /// 
    /// ```no_run
    /// # use tms9918a_emu::{TMS9918A, VideoMode};
    /// # fn main() {
    /// # let mut vdp = TMS9918A::new();
    /// // fill 8 pattern table entries starting at offset 8
    /// // 8 pattern table entries make one tile
    /// // this makes tile 1 a nice smiley face :)
    /// let pattern_table: [u8; 8] = [0b00000000,
    ///                               0b00100100,
    ///                               0b00100100,
    ///                               0b00100100,
    ///                               0b00000000,
    ///                               0b01000010,
    ///                               0b01111110,
    ///                               0b00000000];
    /// vdp.fill_pattern_table(&pattern_table, 8, pattern_table.len());
    /// # }
    /// ```
    #[inline]
    pub fn fill_pattern_table(&mut self, array: &[u8], offset: usize, length: usize) {
        for i in offset..offset+length {
            self.write_pattern_table(i, array[i]);
        }
    }

    /// Write pattern table contents
    /// 
    /// Pattern table offset register must be set first.
    #[inline]
    pub fn write_pattern_table(&mut self, offset: usize, data: u8) {
        self.vdp_ram[self.vdp_pattern_table_offset as usize + offset] = data;
    }

    /// Read pattern table contents
    /// 
    /// Pattern table offset register must be set first.
    #[inline]
    pub fn read_pattern_table(&self, offset: usize) -> u8 {
        self.vdp_ram[self.vdp_pattern_table_offset as usize + offset]
    }

    /// Write to the TMS9918A control port
    /// 
    /// This expects standard TMS9918A commands,
    /// see the [TMS9918A Data Manual](http://www.bitsavers.org/components/ti/TMS9900/TMS9918A_TMS9928A_TMS9929A_Video_Display_Processors_Data_Manual_Nov82.pdf) for details.
    pub fn write_control_port(&mut self, data: u8) {
        if self.vdp_first_byte_saved_flag == false {
            // this is the first byte of the command, save it
            self.vdp_temp_data = data;
            self.vdp_first_byte_saved_flag = true;
        } else {
            // this is the second byte of the command, execute the command
            if (data & (1 << 7) != 0) && (data & (1 << 6) == 0) {
                // bit 7 is set and bit 6 is clear, this is a write a register
                let register = data & 0b00000111;
                let register_value = self.vdp_temp_data;
                self.write_register(register, register_value);
                self.vdp_first_byte_saved_flag = false;
                return;
            }
            if (data & (1 << 7) == 0) && (data & (1 << 6) != 0) {
                // bit 7 is clear and bit 6 is set, this is a write to memory
                let address = ((data as u16 & 0b00111111) << 8) | (self.vdp_temp_data as u16 & 0x00FF);
                self.vdp_addr_pointer = address;
                self.vdp_first_byte_saved_flag = false;
                return;
            }
            if (data & (1 << 7) == 0) && (data & (1 << 6) == 0) {
                // bit 7 is clear and bit 6 is clear, this is a read from memory
                let address = ((data as u16 & 0b00111111) << 8) | (self.vdp_temp_data as u16 & 0x00FF);
                self.vdp_addr_pointer = address;
                self.vdp_read_ahead = self.read_ram(address as usize);
                self.vdp_first_byte_saved_flag = false;
                return;
            }
        }
    }

    /// Write to the TMS9918A data port
    /// 
    /// This follows the standard TMS9918A behavior of incrementing the addr. pointer after each write,
    /// see the [TMS9918A Data Manual](http://www.bitsavers.org/components/ti/TMS9900/TMS9918A_TMS9928A_TMS9929A_Video_Display_Processors_Data_Manual_Nov82.pdf) for details.
    pub fn write_data_port(&mut self, data: u8) {
        self.vdp_first_byte_saved_flag = false;
        let address = self.vdp_addr_pointer;
        self.write_ram(address as usize, data);
        self.vdp_addr_pointer += 1;
    }

    /// Read from the TMS9918A data port
    /// 
    /// This follows the standard TMS9918A behavior of incrementing the addr. pointer after each read,
    /// see the [TMS9918A Data Manual](http://www.bitsavers.org/components/ti/TMS9900/TMS9918A_TMS9928A_TMS9929A_Video_Display_Processors_Data_Manual_Nov82.pdf) for details.
    pub fn read_data_port(&mut self) -> u8 {
        self.vdp_first_byte_saved_flag = false;
        let data = self.vdp_read_ahead;
        self.vdp_addr_pointer += 1;
        self.vdp_read_ahead = self.read_ram(self.vdp_addr_pointer as usize);
        data
    }
}