Close

RetroChallenge 2023/10 Log Entry #6 - Final Wrap-Up Demo Video

A project log for PicoRAM 2090

A Raspberry Pi Pico (RP2040)-based 2114 SRAM Emulator & Multi-Expansion for the Microtronic Computer System * RetroChallenge 2023/10 *

michael-wesselMichael Wessel 10/29/2023 at 07:480 Comments

I achieved everything I wanted for this year's RetroChallenge 2023/10 - here is a final wrap up demo video:

So the last week I mostly spent time writing the demo programs you see in the video. Some of these graphics demos (involving trig) were created with the help of a bunch of little Lisp functions (see below) - the Microtronic cannot do trig functions (sin, cos) reasonably fast, so these were pre-computed with Lisp and materialized in the code. The Lisp-program generated Microtronic instructions were just put in a .MIC file and then put on the PicoRAM's SD card. In the end, it turned out to be a good design decision to utilize an SD card interface for memory dumps / .MIC file storage, as opposed to, say, an EEPROM - given that I can easily create and exchange .MIC programs / content with and from the PC using the SD card.

It might be a good idea to actually add extended op-codes just for trig, i.e., if I wanted to add simple Logo-turtle like graphics capabilities as op-codes, I will need fast trig from register memory as well. For now, I've run out of available vacuous op-codes, but it is of course possible to implement different sets of vacuous op-codes. There could be one set which includes op-codes for sound and speech, like the current set, and another set that utilizes the same vacuous op-codes for trig functions instead. I could use one of the PicoRAM push buttons to cycle through different op-code sets, just like I am doing now to swap memory banks.

Interesting side note: running some of these graphics demos, I noticed that the 2nd core, which is running the op-code extensions, display, user interface, SD card interface, speech, sound, .etc., wasn't fast enough during certain display line drawing operations to keep up with the Microtronic. As the PicoRAM has no way of halting the Microtronic (it only snoops the RAM bus!), if the extended op-codes such as drawing a line to the display take too long, the 2nd core might miss the next extended op-code if it's still busy executing the current operation (e.g., drawing the line). Hence resulting in (graphics) glitches.

Solution: I overclocked the Pico to 250 Mhz - all glitches are gone! This problem never occurred for the SRAM emulation running on the 1st core, only the 2nd core was problematic as it was implementing the extended op-codes, driving the display, etc. In general, the Pico is a great platform for this kind of work - fast and powerful. I don't think this would have been possible with the old AVR MCUs - the 2nd core is a blessing. And overclocking and using the 2nd core is insanely easy, even from C! It's definitely a platform / MCU I am going to use for future projects. Despite the slight annoying required level shifters, it's close to perfect.

Speaking of the op-codes - writing all these demo programs I tweaked the firmware and its op-codes here and there, just to make the programming more convenient. You don't really know how well a programming language or a set of op-codes works until you eat your own dog food! For example, I removed the original

   50D SOUND PLAY FREQ <X><Y> 

op-codes because I found it more useful to have an op-code that enables auto-echo to the speech synthesizer of text. With speech echo enabled, I can use the

   506 DISP SHOW CHAR <LOW><HIGH>

op-code to drive the text display as well as the speech synthesizer.

The final (hah - or let's say latest, current) set of op-codes is hence the following: 

   0xx ENTER LITERAL DATA x

   3Fx ENTER DATA FROM REG x 

   500 HEX DATA ENTRY MODE
   501 DEC DATA ENTRY MODE
   502 DISP CLEAR SCREEN 
   503 DISP TOGGLE UPDATE  
   504 DISP REFRESH
   505 DISP CLEAR LINE  
   506 DISP SHOW CHAR 
   507 DISP CURSOR SET CURSOR LINE 
   508 DISP SET CURSOR  
   509 DISP PLOT   
   50A DISP LINE    
   50B DISP LINE FROM   
   50C DISP LINE TO     
   50D SOUND PLAY NOTE  (SOUND OFF FIRST) 
   50E SPEAK DISP ECHO
   50F SPEAK BYTE 

   70x SWITCH MEMORY BANK x

And here is the Lisp program that I used to generate some of these demo programs:

(in-package :cl-user) 

(defun sin-d (x) (sin (* (/ x 180) pi)))

(defun cos-d (x) (cos (* (/ x 180) pi)))

(defun invert (x) 
  (let ((string (format nil "~2,'0X" x)))
    (format nil "~A~A"
            (elt string 1) (elt string 0))))

(defvar *adr* 0) 

(defun low-nibble (x) 
  (let ((string (format nil "~2,'0X" x)))
    (format t "~2,'0X 0~A~A~%"
            (incf *adr*)
            (elt string 1)
            (elt string 1))))

(defun high-nibble (x) 
  (let ((string (format nil "~2,'0X" x)))
    (format t "~2,'0X 0~A~A~%"
            (incf *adr*)
            (elt string 0)
            (elt string 0))))

(defun low-nibble0 (x) 
  (let ((string (format nil "~2,'0X" x)))
    (format t "0~A~A~%"
            (elt string 1)
            (elt string 1))))

(defun high-nibble0 (x) 
  (let ((string (format nil "~2,'0X" x)))
    (format t "0~A~A~%"
            (elt string 0)
            (elt string 0))))


(defun test (n)
  ;; polygon k-m 
  (setf *adr* 0) 
    (loop as a from 0 by (/ 360 n) do
        (when (>= a 360)
          (return))
        (format t "~2,'0X F10~%" *adr*)
        (format t "~2,'0X 509~%" (incf *adr*))
        (low-nibble (round (+ #x40 (* #x3f (cos-d a)))))
        (high-nibble (round (+ #x40 (* #x3f (cos-d a)))))
        (low-nibble (round (+ #x10 (* #x0f (sin-d a)))))
        (high-nibble (round (+ #x10 (* #x0f (sin-d a)))))
        (format t "~2,'0X 50B~%" (incf *adr*))
        (loop as b from 0 by (/ 360 n) do
              (when (>= b 360)
                (return))
              (low-nibble (round (+ #x40 (* #x3f (cos-d b)))))
              (high-nibble (round (+ #x40 (* #x3f (cos-d b)))))
              (low-nibble (round (+ #x10 (* #x0f (sin-d b)))))
              (high-nibble (round (+ #x10 (* #x0f (sin-d b))))))))

(defun test2 (n)
  ;; circle granularity n 
  (let ((rx #x3f)
        (ry #x0f))
    (format t "F10~%")
    (format t "509~%")
    (loop as a from 0 by (/ 360 n) do
          (when (> a (+ 360 (/ 360 n)))
            (return))
          (low-nibble0 (round (+ #x40 (* rx (cos-d a)))))
          (high-nibble0 (round (+ #x40 (* rx (cos-d a)))))
          (low-nibble0 (round (+ #x10 (* ry (sin-d a)))))
          (high-nibble0 (round (+ #x10 (* ry (sin-d a)))))
          (when (> a 0) 
            (format t "50C~%")))
    (format t "F00~%")))

(defun test3 (n m) 
  ;; with sound! and nested circles 
  ;; not good... 
  (let* ((rx #x3f)
         (ry #x0f)
         (ix (round (/ rx m)))
         (iy (round (/ ry m))))
    (format t "F10~%")
    (dotimes (i m) 
      (loop as a from 0 by (/ 360 n) 
            as step from 0 by 1 do
            (format t "50D~%")
            (low-nibble0 i)
            (low-nibble0 step)      
            (format t "509~%")
            (when (> a (+ 360 (/ 360 n)))
              (return))
            (low-nibble0 (round (+ #x40 (* rx (cos-d a)))))
            (high-nibble0 (round (+ #x40 (* rx (cos-d a)))))
            (low-nibble0 (round (+ #x10 (* ry (sin-d a)))))
            (high-nibble0 (round (+ #x10 (* ry (sin-d a)))))
            (when (> a 0) 
              (format t "50C~%")))
      (decf rx ix)
      (decf ry iy))
    (format t "502~%")
    (format t "C00~%")))

(defun test3a (n m) 
  ;; with sound! and nested circles 
  (let* ((rx #x3f)
         (ry #x0f)
         (ix (round (/ rx m)))
         (iy (round (/ ry m))))
    (format t "F10~%")
    (dotimes (i m) 
      (format t "50D~%")
      (low-nibble0 1)
      (low-nibble0 i)      
      (format t "509~%")            
      (loop as a from 0 by (/ 360 n) 
            as step from 0 by 1 do
            (when (> a (+ 360 (/ 360 n)))
              (return))
            (low-nibble0 (round (+ #x40 (* rx (cos-d a)))))
            (high-nibble0 (round (+ #x40 (* rx (cos-d a)))))
            (low-nibble0 (round (+ #x10 (* ry (sin-d a)))))
            (high-nibble0 (round (+ #x10 (* ry (sin-d a)))))
            (when (> a 0) 
              (format t "50C~%")))
      (decf rx ix)
      (decf ry iy))
    (format t "502~%")
    (format t "C00~%")))

(defun test4 (n m)
  ;; nested circles animation 
  (let* ((rx #x3f)
         (ry #x0f)
         (ix (round (/ rx m)))
         (iy (round (/ ry m))))
    (format t "F10~%")
    (format t "503~%")
    (dotimes (i m) 
      (format t "509~%")
      (loop as a from 0 by (/ 360 n) do
            (when (> a (+ 360 (/ 360 n)))
              (return))
            (low-nibble0 (round (+ #x40 (* rx (cos-d a)))))
            (high-nibble0 (round (+ #x40 (* rx (cos-d a)))))
            (low-nibble0 (round (+ #x10 (* ry (sin-d a)))))
            (high-nibble0 (round (+ #x10 (* ry (sin-d a)))))
            (when (> a 0) 
              (format t "50C~%")))
      (format t "504~%")
      (decf rx ix)
      (decf ry iy))
    (format t "502~%")
    (format t "503~%")
    (format t "C00~%")))

(defun test5 (n m step)
  ;; nested circle animation with step rotation for each  
  (let* ((rx #x3f)
         (ry #x0f)
         (ix (round (/ rx m)))
         (iy (round (/ ry m)))
         (aoff 0))
    (format t "F10~%")
    (format t "503~%")
    (dotimes (i m) 
      (format t "509~%")
      (loop as a from 0 by (/ 360 n) do
            (when (> a (+ 360 (/ 360 n)))
              (return))
            (let ((a (+ a aoff)))
              (low-nibble0 (round (+ #x40 (* rx (cos-d a)))))
              (high-nibble0 (round (+ #x40 (* rx (cos-d a)))))
              (low-nibble0 (round (+ #x10 (* ry (sin-d a)))))
              (high-nibble0 (round (+ #x10 (* ry (sin-d a))))))
            (when (> a 0) 
              (format t "50C~%")))
      (format t "504~%")
      (decf rx ix)
      (decf ry iy)
      (incf aoff step))
    (format t "502~%")
    (format t "503~%")
    (format t "C00~%")))

(defun string-to-ascii (string file)
  (format file  "F10")
  (format file  "~%502")
  (format file  "~%50E")
  (format file  "~%506")

  (dolist (x ;;(substitute #\Space #\Newline (coerce string 'list)))
             (remove #\Newline (coerce string 'list)))
    (let ((string (string-upcase (format nil "~2,'0X" (char-code x)))))
      (format file  "~%0~A~A" (elt string 1)  (elt string 1))
      (format file  "~%0~A~A" (elt string 0)  (elt string 0)))))


(defun page-bank-reader (pages) 
  (let ((bank 0))
    (dolist (page pages) 
      (with-open-file (file
                       (format nil "c:/temp/PAGE~A.MIC" bank)
                       :direction :output
                       :if-exists :supersede)
        (string-to-ascii page file)
        (incf bank)
        (format file "~%50F")
        (format file "~%0AA")
        (format file "~%000")
        (format file "~%FF0")
        (format file "~%70~X" bank)
        (format file "~%C00~%~%")))))

(page-bank-reader (list "Hi. Nice to meet
you.  I  am  the
Microtronic 2090
Computer System."

"I was created in
1981 by the  ELO
magazine and the
company   Busch."

"My CPU is a 4bit
T M S 1600 micro
controller.  And
I have  a RAM of"

"256 12bit words.
I speak a custom
machine language
and I am big  in"

"education.  With
my PicoRAM I can
now  do   things
that ought to be"

"impossible   for
me.  My   master
LambaMikel loves
me. Bye bye now."))


(defun spiral (n m ) 
  ;; draw a spiral   
  (let* ((rx #x3f)
         (ry #x0f)
         (irx (/ rx (* n m)))
         (iry (/ ry (* n m))))
    (format t "F10~%")
    (format t "509~%")
    (loop as a from 0 by (/ 360 n) 
          as step from 1 to (* n m) do 
          (low-nibble0 (round (+ #x40 (* (round rx) (cos-d a)))))
          (high-nibble0 (round (+ #x40 (* (round rx) (cos-d a)))))
          (low-nibble0 (round (+ #x10 (* (round ry) (sin-d a)))))
          (high-nibble0 (round (+ #x10 (* (round ry) (sin-d a)))))
          (when (> a 0) 
            (format t "50C~%"))
          (decf rx irx)
          (decf ry iry))

    (format t "502~%")
    (format t "C00~%")))

Discussions