PiDP-8/I SoftwareCheck-in [de6d1f5a43]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Untested application of Ian Schofield's LED dithering patch, which replaces the NLS mode with a different PWM-based incandescent lamp simulator scheme than either his original ILS or my "new ILS," intended to give much the same effect while using less host CPU power, so that it can run on a Pi Zero. This checkin is very different from the patch as posted on the mailing list, but I *think* it implements the same core algorithm. It's untested because while I have a PiDP-8/I front panel and a Pi Zero W here, I don't want to tear my PiDP-8/I apart to put the two together. That's and many other reasons are why this is on a branch.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | pi-zero-ils
Files: files | file ages | folders
SHA3-256: de6d1f5a435f437c37daede778f8b4d05eb369b91a81c24b81da777e5e492f11
User & Date: tangent 2019-05-22 11:58:56
Context
2019-05-22
19:19
Removed the handler for "freeze display on STOP" to match Ian Schofield's original "LED dithering" ILS-lite patch for the Pi Zero. Leaf check-in: 7d1cd49fe2 user: tangent tags: pi-zero-ils
11:58
Untested application of Ian Schofield's LED dithering patch, which replaces the NLS mode with a different PWM-based incandescent lamp simulator scheme than either his original ILS or my "new ILS," intended to give much the same effect while using less host CPU power, so that it can run on a Pi Zero. This checkin is very different from the patch as posted on the mailing list, but I *think* it implements the same core algorithm. It's untested because while I have a PiDP-8/I front panel and a Pi Zero W here, I don't want to tear my PiDP-8/I apart to put the two together. That's and many other reasons are why this is on a branch. check-in: de6d1f5a43 user: tangent tags: pi-zero-ils
11:03
Removed an obsolete FIXME comment check-in: 0d1ada3484 user: tangent tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/SIMH/PDP8/pdp8_cpu.c.

391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
...
437
438
439
440
441
442
443



444
445

446
447
448
449
450
451
452
....
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
static const size_t pidp8i_updates_per_sec = 3200;
max_skips = get_pidp8i_initial_max_skips (pidp8i_updates_per_sec);
srand48 (time (&last_update));

// Reset display info in case we're re-entering the simulator from Ctrl-E
extern display display_bufs[2];
memset (display_bufs, 0, sizeof(display_bufs));
static size_t skip_count, dither, inst_count;
skip_count = dither = inst_count = 0;

// Copy a global flag set by main() to control whether we run the GPIO
// stuff based on what name this program was called by.  We reference it
// this way to clue the compiler into the fact that it doesn't change
// once set: tests based on a stack constant are easier to optimize than
// those involving a non-const imported from another module.
//
................................................................................
            // this, the front panel won't show the correct state, a
            // serious problem since it'll be stuck in that state for
            // the user to study until the user gives a "cont" command.
            //
            // We're passing IR for the MB line on purpose.  MB doesn't
            // have the correct value at this point.
            if (pidp8i_gpio) {



                set_pidp8i_leds (PC, SteadyMA, IR, IR, LAC, MQ, IF, DF,
                    SC, int_req, Pause);


                // Also copy SR hardware value to software register in
                // case the user tries poking at it from the sim> prompt.
                SR = get_switch_register();
                }
/* ---PiDP end---------------------------------------------------------------------------------------------- */
            break;
................................................................................
    // full-panel updates per second.  We need a bare minimum of 32
    // discernible brightness values per update for ILS, so if we don't
    // update the LED status data at least 3,200 times per second, we
    // don't have enough data for smooth panel updates.  Fortunately,
    // computers are pretty quick, and our slowest script runs at 30
    // kIPS.  (5.script.)
    //
    // We deliberately add some timing jitter here to get stochastic
    // sampling of the incoming instructions to avoid beat frequencies
    // between our update rate and the instruction pattern being
    // executed by the front panel.  It's a form of dithering.
    //
    // You might think to move this code to the top of set_pidp8i_leds,
    // but the function call itself is a nontrivial hit.  In fact, you
    // don't even want to move all of this to a function here in this
    // module and try to get GCC to inline it: that's good for a 1 MIPS
    // speed hit in my testing!  (GCC 4.9.2, Raspbian Jessie on Pi 3B.)

    if (pidp8i_gpio && (++skip_count >= (max_skips - dither))) {
        // Save skips to inst counter and reset
        inst_count += skip_count;
        skip_count = 0;

        // We need to update the LED data again.  Using IR for the MB
        // line here for same reason as above.
        set_pidp8i_leds (PC, SteadyMA, IR, IR, LAC, MQ, IF, DF, SC,
                int_req, Pause);

        // Has it been ~1s since we updated our max_skips value?
        time_t now;
        if (time(&now) > last_update) {
            // Yep; simulator IPS may have changed, so freshen it.
            last_update = now;
            max_skips = inst_count / pidp8i_updates_per_sec;
            //printf("Inst./repaint: %zu - %zu; %.2f MIPS\r\n",
            //        max_skips, dither, inst_count / 1e6);
            inst_count = 0;
            }
        dither = max_skips > 32 ? lrand48() % (max_skips >> 3) : 0; // 12.5%
        }
    Pause = 0;      // it's set outside the "if", so it must be *reset* outside
/* ---PiDP end---------------------------------------------------------------------------------------------- */
    }                                                   /* end while */

/* ---PiDP add--------------------------------------------------------------------------------------------- */
// If we're leaving the simulator's CPU instruction execution loop for







|
|







 







>
>
>
|
|
>







 







|
|
|
<
<
<
<
<
<
<

|








<
<
<
<
<
<
<
<
<
<
<
<







391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
...
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
....
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566







1567
1568
1569
1570
1571
1572
1573
1574
1575
1576












1577
1578
1579
1580
1581
1582
1583
static const size_t pidp8i_updates_per_sec = 3200;
max_skips = get_pidp8i_initial_max_skips (pidp8i_updates_per_sec);
srand48 (time (&last_update));

// Reset display info in case we're re-entering the simulator from Ctrl-E
extern display display_bufs[2];
memset (display_bufs, 0, sizeof(display_bufs));
static size_t skip_count, inst_count;
skip_count = inst_count = 0;

// Copy a global flag set by main() to control whether we run the GPIO
// stuff based on what name this program was called by.  We reference it
// this way to clue the compiler into the fact that it doesn't change
// once set: tests based on a stack constant are easier to optimize than
// those involving a non-const imported from another module.
//
................................................................................
            // this, the front panel won't show the correct state, a
            // serious problem since it'll be stuck in that state for
            // the user to study until the user gives a "cont" command.
            //
            // We're passing IR for the MB line on purpose.  MB doesn't
            // have the correct value at this point.
            if (pidp8i_gpio) {
                // Pi Zero ILS: run the PWM algorithm through two
                // display updates.  (10 PWM levels per update.)
                for (int i = 0; i < 20; ++i) {
                    set_pidp8i_leds (PC, SteadyMA, IR, IR, LAC, MQ, IF, DF,
                        SC, int_req, Pause);
                    }

                // Also copy SR hardware value to software register in
                // case the user tries poking at it from the sim> prompt.
                SR = get_switch_register();
                }
/* ---PiDP end---------------------------------------------------------------------------------------------- */
            break;
................................................................................
    // full-panel updates per second.  We need a bare minimum of 32
    // discernible brightness values per update for ILS, so if we don't
    // update the LED status data at least 3,200 times per second, we
    // don't have enough data for smooth panel updates.  Fortunately,
    // computers are pretty quick, and our slowest script runs at 30
    // kIPS.  (5.script.)
    //
    // Unlike in the trunk version, we do no dither here, we just use an
    // odd CPU IPS rate divisor that has a low likelihood of dividing
    // into the IPS rate in a way that creates a visible beat frequency.








    if (pidp8i_gpio && (++skip_count >= 233)) {
        // Save skips to inst counter and reset
        inst_count += skip_count;
        skip_count = 0;

        // We need to update the LED data again.  Using IR for the MB
        // line here for same reason as above.
        set_pidp8i_leds (PC, SteadyMA, IR, IR, LAC, MQ, IF, DF, SC,
                int_req, Pause);












        }
    Pause = 0;      // it's set outside the "if", so it must be *reset* outside
/* ---PiDP end---------------------------------------------------------------------------------------------- */
    }                                                   /* end while */

/* ---PiDP add--------------------------------------------------------------------------------------------- */
// If we're leaving the simulator's CPU instruction execution loop for

Changes to src/pidp8i/gpio-common.c.in.

109
110
111
112
113
114
115


116
117
118
119
120
121
122
...
508
509
510
511
512
513
514
515
516
517









518
519
520
521
522
523
524
525
526
527
528
...
531
532
533
534
535
536
537
538


539
540
541

542
543
544
545
546
547
548
...
554
555
556
557
558
559
560
561

562
563
564
565
566
567
568
// zeroes it and swaps it for the current "update-to" copy, giving the
// CPU thread a blank slate, and giving the GPIO thread a stable set of
// LED "on" time values to work with.
display display_bufs[2];
display* pdis_update = display_bufs + 0;    // exported to SIMH CPU thread
display* pdis_paint  = display_bufs + 1;    // exported to gpio-*.c




// GPIO thread control variables manipulated by start/stop_*() below
static pthread_t gpio_thread_info;
static int terminate_gpio_thread = 0;
static pthread_mutex_t gpio_start_mutex;


................................................................................
    }

    return ac;
}


//// update_led_states /////////////////////////////////////////////////
// Generic front panel LED updater used by NLS full time and by ILS
// while the CPU is in STOP mode.  Just uses the paint-from display's
// bitfields to turn the LEDs on full-brightness.










void update_led_states (const us_time_t delay)
{
    uint16_t *pcurr = pdis_paint->curr;

#if 0   // debugging
    static time_t last = 0, now;
    if (time(&now) != last) {
        printf("\r\nLED: [PC:%04o] [MA:%04o] [MB:%04o] [AC:%04o] [MQ:%04o]",
                pcurr[0], pcurr[1], pcurr[2], pcurr[3], pcurr[4]);
        last = now;
................................................................................
    
    // Override Execute and Run LEDs if the CPU is currently stopped,
    // since we only get set_pidp8i_leds calls while the CPU's running.
    if (swStop || swSingInst) {
        pdis_paint->curr[5] &= ~(1 << 2);
        pdis_paint->curr[6] &= ~(1 << 7);
    }



    for (size_t row = 0; row < NLEDROWS; ++row) {
        for (size_t col = 0; col < NCOLS; ++col) {
            if ((pcurr[row] & (1 << col)) == 0) {

                GPIO_SET = 1 << cols[col];
            }
            else {
                GPIO_CLR = 1 << cols[col];
            }
        }

................................................................................
        sleep_us (delay);

        // Toggle this LED row off
        GPIO_CLR = 1 << ledrows[row]; // superstition
        INP_GPIO (ledrows[row]);

        // Small delay to reduce UDN2981 ghosting
        sleep_us (10);

    }
}


//// turn_on/off_pidp8i_leds ///////////////////////////////////////////
// Set GPIO pins into a state that [dis]connects power to/from the LEDs.
// Doesn't pay any attention to the panel values.







>
>







 







|
|
|
>
>
>
>
>
>
>
>
>



|







 








>
>

|
<
>







 







|
>







109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
...
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
...
542
543
544
545
546
547
548
549
550
551
552
553

554
555
556
557
558
559
560
561
...
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
// zeroes it and swaps it for the current "update-to" copy, giving the
// CPU thread a blank slate, and giving the GPIO thread a stable set of
// LED "on" time values to work with.
display display_bufs[2];
display* pdis_update = display_bufs + 0;    // exported to SIMH CPU thread
display* pdis_paint  = display_bufs + 1;    // exported to gpio-*.c

// Pi Zero ILS counter
static int gpio_pwm_cnt = 0;

// GPIO thread control variables manipulated by start/stop_*() below
static pthread_t gpio_thread_info;
static int terminate_gpio_thread = 0;
static pthread_mutex_t gpio_start_mutex;


................................................................................
    }

    return ac;
}


//// update_led_states /////////////////////////////////////////////////
// Unlike the trunk version, this function generates a PWM sequence for
// the LEDs according to the count value in pdis_paint->on. This array
// contains 'on' counts betqeen 0 (off) and 9 (fully on).  This data is
// compared with a cycle 10 counter (gpio_pwm_cnt) and each led in a
// row/col is turned between 0 and 9 out of 80 calls to this function.
// This 10 level PWM is sufficient to give an impression of varying
// brightness although not quite as good as the trunk's ILS mode.  The
// advantage is that it uses far less host CPU power, so that it will
// run on a Pi Zero.  Another virtue of this scheme is that if this
// function is called synchronously from the CPU instruction decoding
// loop (pdp8_cpu.c), the LEDs will not flicker even if the primary CPU
// loop varies in speed.

void update_led_states (const us_time_t delay)
{
    size_t *prow = pdis_paint->on[0];

#if 0   // debugging
    static time_t last = 0, now;
    if (time(&now) != last) {
        printf("\r\nLED: [PC:%04o] [MA:%04o] [MB:%04o] [AC:%04o] [MQ:%04o]",
                pcurr[0], pcurr[1], pcurr[2], pcurr[3], pcurr[4]);
        last = now;
................................................................................
    
    // Override Execute and Run LEDs if the CPU is currently stopped,
    // since we only get set_pidp8i_leds calls while the CPU's running.
    if (swStop || swSingInst) {
        pdis_paint->curr[5] &= ~(1 << 2);
        pdis_paint->curr[6] &= ~(1 << 7);
    }

    if (++gpio_pwm_cnt >= 10) gpio_pwm_cnt = 0;

    for (size_t row = 0; row < NLEDROWS; ++row) {
        for (size_t col = 0; col < NCOLS; ++col, ++prow) {

            if (gpio_pwm_cnt >= *prow) {
                GPIO_SET = 1 << cols[col];
            }
            else {
                GPIO_CLR = 1 << cols[col];
            }
        }

................................................................................
        sleep_us (delay);

        // Toggle this LED row off
        GPIO_CLR = 1 << ledrows[row]; // superstition
        INP_GPIO (ledrows[row]);

        // Small delay to reduce UDN2981 ghosting
        for (int i = 0; i < 2000; ++i)
            __asm__ ("nop");
    }
}


//// turn_on/off_pidp8i_leds ///////////////////////////////////////////
// Set GPIO pins into a state that [dis]connects power to/from the LEDs.
// Doesn't pay any attention to the panel values.

Changes to src/pidp8i/gpio-nls.c.

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

//// gpio_core  ////////////////////////////////////////////////////////
// The GPIO module's main loop core, called from thread entry point in
// gpio-common.c.

void gpio_core (struct bcm2835_peripheral* pgpio, int* terminate)
{
    // Light each row of LEDs 1.2 ms.  With 8 rows, that's an update
    // rate of ~100x per second.  Not coincidentally, this is the human
    // persistence of vision limit: changes faster than this are
    // difficult for humans to perceive visually.
    const us_time_t intervl = 1200;  

    // This is a simplified version of what's in the gpio-ils.c version
    // of this function, so if you want more comments, read them there.
    while (*terminate == 0) {
        for (size_t i = 0; i < NCOLS; ++i) OUT_GPIO(cols[i]);
        swap_displays ();
        update_led_states (intervl);
        read_switches (intervl * 1000 / 100);
#if defined(HAVE_SCHED_YIELD)
        sched_yield ();
#endif
    }
}







|
|
|
<
|





<
|






38
39
40
41
42
43
44
45
46
47

48
49
50
51
52
53

54
55
56
57
58
59
60

//// gpio_core  ////////////////////////////////////////////////////////
// The GPIO module's main loop core, called from thread entry point in
// gpio-common.c.

void gpio_core (struct bcm2835_peripheral* pgpio, int* terminate)
{
    // This loop runs at a high priority with a total time of
    // 8*120 uS + 1.2 mS.  See upadte_led_states as this
    // implements a PWM system with 10 steps.

    const us_time_t intervl = 1200;

    // This is a simplified version of what's in the gpio-ils.c version
    // of this function, so if you want more comments, read them there.
    while (*terminate == 0) {
        for (size_t i = 0; i < NCOLS; ++i) OUT_GPIO(cols[i]);

        update_led_states (intervl / 10);
        read_switches (intervl * 1000 / 100);
#if defined(HAVE_SCHED_YIELD)
        sched_yield ();
#endif
    }
}

Changes to src/pidp8i/main.c.in.

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


//// set_pidp8i_leds ///////////////////////////////////////////////////
// Given all of the PDP-8's internal registers that affect the front
// panel display, modify the GPIO thread's LED state values accordingly.
//
// Also update the LED brightness values based on those new states.










void set_pidp8i_leds (uint32_t sPC, uint32_t sMA, uint32_t sMB,
    uint16_t sIR, int32_t sLAC, int32_t sMQ, int32_t sIF, int32_t sDF,
    int32_t sSC, int32_t int_req, int Pause)
{
    // Bump the instruction count.  This should always be equal to the
    // Fetch LED's value, but integers are too cheap to get cute here.
    //
    // Note that we only update pdis_update directly once in this whole
    // process.  This is in case the display swap happens while we're
    // working: we want to finish work on the same display even though
    // it's now called the paint-from display, so it's consistent.
    display* pd = pdis_update;
    ++pd->inst_count;

    // Rows 0-4, easy cases: single-register LED strings.
    // 
    // The values passed for rows 1 and 2 are non-obvious.  See the code
    // calling us from ../SIMH/PDP8/pdp8_cpu.c for details.
    set_pidp8i_row_leds (pd, 0, sPC);
    set_pidp8i_row_leds (pd, 1, sMA);







>
>
>
>
>
>
>
>
>













|







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


//// set_pidp8i_leds ///////////////////////////////////////////////////
// Given all of the PDP-8's internal registers that affect the front
// panel display, modify the GPIO thread's LED state values accordingly.
//
// Also update the LED brightness values based on those new states.
//
// Pi Zero ILS: Unlike the trunk version, this function implements a
// 10 level PWM system by generating a value between 0 and 9 for each
// LED by counting the LED on cycles each time the function is called.
// As a result, it does not matter if this function is synchronously
// called as it only calculates the on/off ratios.  These are stored in
// the display->on region of the display structure.  This data is then
// copied to pdis_paint and used by update_led_states to generate the
// PWM sequence.

void set_pidp8i_leds (uint32_t sPC, uint32_t sMA, uint32_t sMB,
    uint16_t sIR, int32_t sLAC, int32_t sMQ, int32_t sIF, int32_t sDF,
    int32_t sSC, int32_t int_req, int Pause)
{
    // Bump the instruction count.  This should always be equal to the
    // Fetch LED's value, but integers are too cheap to get cute here.
    //
    // Note that we only update pdis_update directly once in this whole
    // process.  This is in case the display swap happens while we're
    // working: we want to finish work on the same display even though
    // it's now called the paint-from display, so it's consistent.
    display* pd = pdis_update;
    if (++pd->inst_count >= 10) swap_displays ();       // Pi Zero ILS

    // Rows 0-4, easy cases: single-register LED strings.
    // 
    // The values passed for rows 1 and 2 are non-obvious.  See the code
    // calling us from ../SIMH/PDP8/pdp8_cpu.c for details.
    set_pidp8i_row_leds (pd, 0, sPC);
    set_pidp8i_row_leds (pd, 1, sMA);