user70005: Background
In 2019, Microchip acquired Atmel and released several new AVR microcontrollers, including the Tiny-0 and Tiny-1 Series. On these mircocontrollers, traditional Atmel ICSP programming was not supported. Instead, they feature a new one-wire programming interface called Unified Programming and Debug Interface (UPDI). The new hardware also made existing TTL-serial bootloader programming obsolete. Thus, one must use a native UPDI programmer to work with these microcontrollers, such as pyupdi.
However, Optiboot's developers have subsquently developed a new version of the bootloader called Optiboot_X and implemented the STK500 programming protocol on these new microcontrollers, allowing one to program these microcontrollers using the traditional TTL-serial bootloader approach, and it appears that the Arduino community has already supported them for a while, with MegaCoreX and megaTinyCore, which was good news to me. Nevertheless, it seems that there's little resource beyond these prepackaged Arduino cores, in particular, there's almost no documentation on how to program them manually from the commandline.
What I've already done
I designed a custom AVR development board based on the Atmel ATTiny1604 microcontroller, which is a member of the new Tiny-0 family. Due to technical considerations, it must be programmed via the serial port using the bootloader similar to a traditional Arduino, not UPDI (UPDI is only used to burn the bootloader).
To achieve this goal, I installed the latest version of binutils, GCC and an experimental patch to avr-libc, so the compiler is able to generate code targeting my microcontroller. Then, I compiled my personal Optiboot_X bootloader.
$ git clone https://github.com/Optiboot/optiboot.git
$ cd optiboot/optiboot/bootloaders/optiboot
$ make -f Makefile.mega0 optiboot_attiny1604.hex UARTTX=A1 TIMEOUT=8 LED=A7 BAUD_RATE=57600 LED_START_FLASHES=10

Using Compiler at: /home/user/code/optiboot/optiboot/bootloaders/optiboot/avr-gcc
avr-gcc -g -Wall -Os -fno-split-wide-types -mrelax  -DWDTTIME=8  -DLED_START_FLASHES=10  -DLED=A7   -DUARTTX=A1 -DBAUD_RATE=57600       -Wl,-section-start=.text=0 -Wl,--section-start=.application=0x200 -Wl,--section-start=.version=0x1fe -Wl,--relax -nostartfiles -nostdlib  -mmcu=attiny1604 -o optiboot_attiny1604.elf optiboot_x.c
avr-size optiboot_attiny1604.elf
   text    data     bss     dec     hex filename
    494       9       0     503     1f7 optiboot_attiny1604.elf
avr-objdump -S  optiboot_attiny1604.elf > optiboot_attiny1604.lst
avr-objcopy -j .text -j .data -j .version --set-section-flags .version=alloc,load -O ihex optiboot_attiny1604.elf optiboot_attiny1604.hex

Then I uploaded the Optiboot_X bootloader to the ATTiny1604 microcontroller via UPDI, using pyupdi.
# set fuse BOOTEND
$ pyupdi -d tiny1604 -c /dev/ttyUSB0 -fs 8:0x02 

# burn Optiboot_X
$ pyupdi -d tiny1604 -c /dev/ttyUSB0 -f optiboot_attiny1604.hex

# reset
$ pyupdi -d tiny1604 -c /dev/ttyUSB0 -r

After burning the Optiboot_X via UPDI, I was able to see 10 pulses during power-on at A7 on my oscilloscope, indicating that Optiboot_X was alive.
Note: Technically the installation is not complete yet. Since UPDI and /RESET share the same pin, UPDI must be permanently disabled via fuse to allow programming via bootloader, but it means a nonfunctional bootloader can brick the chip. I decided to manually reset the chip by power cycling the board for now.
Next, I wrote a test program.
$ cat blink.c

#define F_CPU 20000000UL
#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
    PORTB.DIRSET = 0b00000001;

    while(1) {
        PORTB.OUTSET = 0b00000001;
        _delay_ms(500);
        PORTB.OUTCLR = 0b00000001;
        _delay_ms(500);
    }
}

And I compiled it.
$ make
avr-gcc -mmcu=attiny1604 -Wall -Os -o blink.elf blink.c
avr-objcopy -j .text -j .data -j .rodata -O ihex blink.elf blink.hex

Question
Now what? How do I upload blink.hex to the Optiboot_X on the microcontroller?
On a traditional Arduino, the command is something like...
$ avrdude -C/etc/avrdude/avrdude.conf -v -patmega328p \
  -carduino -P/dev/ttyUSB0 -b38400 -D -Uflash:w:blink.hex:i

But this is not an ATMega328P and there's nearly zero documentation on the web, I have a lot of questions and I'm not sure how to proceed.
To begin with, in man avrdude, there's no mention of any Tiny0 or Tiny1 devices. What should the -p argument be? Also, do I need to use a patched version of avrdude? Do I need a customized avrdude.conf? If so, where can I find them?

user70005: After poking around in the git repository for a while, I've solved the problem. To work with these new TinyAVR-0/1 microcontrollers, a new version of avrdude is needed. Since avrdude hasn't made a stable release yet, it's necessary to build a new avrdude from source from SVN.
$ svn co https://svn.savannah.nongnu.org/svn/avrdude/trunk avrdude
$ cd avrdude/avrdude
$ ./bootstrap
$ mkdir build && cd build
$ ../configure
$ make
$ make install

After installation, /usr/local/bin/avrdude and /usr/local/etc/avrdude.conf should be available. One can see that a bunch of new TinyAVR parts have been added...
$ avrdude -p?

Valid parts are:
  [...]
  t10      = ATtiny10
  t11      = ATtiny11
  t12      = ATtiny12
  t13      = ATtiny13
  t15      = ATtiny15
  t1604    = ATtiny1604
  t1606    = ATtiny1606
  t1607    = ATtiny1607
  t1614    = ATtiny1614
  t1616    = ATtiny1616
  t1617    = ATtiny1617
  t1634    = ATtiny1634
  t20      = ATtiny20
  t202     = ATtiny202
  t204     = ATtiny204
  t212     = ATtiny212
  t214     = ATtiny214
  t2313    = ATtiny2313
  t24      = ATtiny24
  t25      = ATtiny25
  t26      = ATtiny26
  t261     = ATtiny261
  t28      = ATtiny28
  t3214    = ATtiny3214
  t3216    = ATtiny3216
  t3217    = ATtiny3217
  t4       = ATtiny4
  t40      = ATtiny40
  t402     = ATtiny402
  t404     = ATtiny404
  t406     = ATtiny406
  t412     = ATtiny412
  t414     = ATtiny414
  t416     = ATtiny416
  t417     = ATtiny417
  t4313    = ATtiny4313
  t43u     = ATtiny43u
  t44      = ATtiny44
  t441     = ATtiny441
  t45      = ATtiny45
  t461     = ATtiny461
  t5       = ATtiny5
  t804     = ATtiny804
  t806     = ATtiny806
  t807     = ATtiny807
  t814     = ATtiny814
  t816     = ATtiny816
  t817     = ATtiny817
  t84      = ATtiny84
  t841     = ATtiny841
  t85      = ATtiny85
  t861     = ATtiny861
  t88      = ATtiny88
  t9       = ATtiny9
  [...]

To program the ATTiny1604, simply run...
$ avrdude -C/usr/local/etc/avrdude.conf -v -pt1604 \
  -carduino -P/dev/ttyUSB0 -b57600 -D -Uflash:w:blink.hex:i

Don't forget to modify the -b57600 argument to match the baud of your Optiboot_X build.
Lesson learned: When RTFM is impossible, use the source, Luke!
Potential Problems
avrdude: verification error; content mismatch
It's possible that avrdude successfully establishes communication with the board via the serial port, reads the correct device signature, then attempts to upload a program, but ultimately fails verification.
$ avrdude -v -pattiny1604 -carduino -P/dev/ttyUSB0 -b57600 -D -Uflash:w:blink.hex:i

Reading | ################################################## | 100% 0.00s

avrdude: Device signature = 0x1e9425 (probably t1604)
avrdude: reading input file "blink.hex"
avrdude: writing flash (204 bytes):

Writing | ################################################## | 100% 0.06s

avrdude: 204 bytes of flash written
avrdude: verifying flash memory against blink.hex:
avrdude: load data flash data from input file blink.hex:
avrdude: input file blink.hex contains 204 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 0.06s

avrdude: verifying ...
avrdude: verification error, first mismatch at byte 0x0000
         0x01 != 0x0c
avrdude: verification error; content mismatch

Solution: In TinyAVR 0/1, the bootloader is located at the beginning of the flash memory, occupies the first 512 bytes. When compiling the program to be uploaded via the bootloader, the correct 512-byte text section offset must be specified. Otherwise, avrdude will attempt to program from 0x00, which is impossible, because it's the location of the bootloader. Upon readback, instead of your program, avrdude reads the bootloader, which triggers the verification failure in question (it's worth noticing that 0x01, the first mismatched byte that triggers verification failure, is actually the first byte in the Optiboot_X binary code).
Instead of using...
avr-gcc -mmcu=attiny1604 -Wall -Os -o blink.elf blink.c

Recompile your program with `
avr-gcc -mmcu=attiny1604 -Wall -Os -o blink.elf blink.c \
-Wl,--section-start=.text=0x200

avr-objcopy -j .text -j .data -j .rodata -O ihex blink.elf blink.hex

And reupload it.
$ avrdude -v -pattiny1604 -carduino -P/dev/ttyUSB0 -b57600 -D -Uflash:w:blink.hex:i

avrdude: Device signature = 0x1e9425 (probably t1604)
avrdude: reading input file "blink.hex"
avrdude: writing flash (716 bytes):

Writing | ################################################## | 100% 0.00s

avrdude: 716 bytes of flash written
avrdude: verifying flash memory against blink.hex:
avrdude: load data flash data from input file blink.hex:
avrdude: input file blink.hex contains 716 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 0.00s

avrdude: verifying ...
avrdude: 716 bytes of flash verified

avrdude: safemode: Fuses OK (E:FF, H:FF, L:FF)

avrdude done.  Thank you.

avrdude: programmer type jtagice3_updi not found
It's possible that avrdude reports the following error...
/usr/bin/avrdude -C/usr/local/etc/avrdude.conf -v -pattiny1604 -carduino -P/dev/ttyUSB0 -b38400 -D -Uflash:w:blink.hex:i

avrdude: Version 6.3, compiled on Jul 24 2019 at 00:00:00
         Copyright (c) 2000-2005 Brian Dean, http://www.bdmicro.com/
         Copyright (c) 2007-2014 Joerg Wunsch

         System wide configuration file is "/usr/local/etc/avrdude.conf"
avrdude: error at "/usr/local/etc/avrdude.conf:1091: programmer type jtagice3_updi not found
avrdude: error reading system wide configuration file "/usr/local/etc/avrdude.conf"

Solution: You are trying to run an old version of avrdude with a new configuration file. Make sure the avrdude executable you are running is actually the up-to-date version compiled from the upstream SVN repository.

70005: <h3>Background</h3>
<p>In 2019, Microchip acquired Atmel and released several new AVR microcontrollers, including the Tiny-0 and Tiny-1 Series. On these mircocontrollers, traditional Atmel ICSP programming was not supported. Instead, they feature a new one-wire programming interface called <em>Unified Programming and Debug Interface (UPDI)</em>. The new hardware also made existing TTL-serial bootloader programming obsolete. Thus, one must use a native UPDI programmer to work with these microcontrollers, such as <a href="https://github.com/mraardvark/pyupdi" rel="nofollow noreferrer">pyupdi</a>.</p>
<p>However, Optiboot's developers have subsquently developed a new version of the bootloader called <a href="https://github.com/Optiboot/optiboot#Optiboot_X" rel="nofollow noreferrer">Optiboot_X</a> and implemented the STK500 programming protocol on these new microcontrollers, allowing one to program these microcontrollers using the traditional TTL-serial bootloader approach, and it appears that the Arduino community has already supported them for a while, with <a href="https://github.com/MCUdude/MegaCoreX" rel="nofollow noreferrer">MegaCoreX</a> and <a href="https://github.com/SpenceKonde/megaTinyCore" rel="nofollow noreferrer">megaTinyCore</a>, which was good news to me. Nevertheless, it seems that there's little resource beyond these prepackaged Arduino cores, in particular, there's almost no documentation on how to program them manually from the commandline.</p>
<h3>What I've already done</h3>
<p>I designed a custom AVR development board based on the Atmel ATTiny1604 microcontroller, which is a member of the new Tiny-0 family. Due to technical considerations, it must be programmed via the serial port using the bootloader similar to a traditional Arduino, not UPDI (UPDI is only used to burn the bootloader).</p>
<p>To achieve this goal, I installed the latest version of binutils, GCC and an experimental patch to avr-libc, so the compiler is able to generate code targeting my microcontroller. Then, I compiled my personal Optiboot_X bootloader.</p>
<pre><code>$ git clone https://github.com/Optiboot/optiboot.git
$ cd optiboot/optiboot/bootloaders/optiboot
$ make -f Makefile.mega0 optiboot_attiny1604.hex UARTTX=A1 TIMEOUT=8 LED=A7 BAUD_RATE=57600 LED_START_FLASHES=10

Using Compiler at: /home/user/code/optiboot/optiboot/bootloaders/optiboot/avr-gcc
avr-gcc -g -Wall -Os -fno-split-wide-types -mrelax  -DWDTTIME=8  -DLED_START_FLASHES=10  -DLED=A7   -DUARTTX=A1 -DBAUD_RATE=57600       -Wl,-section-start=.text=0 -Wl,--section-start=.application=0x200 -Wl,--section-start=.version=0x1fe -Wl,--relax -nostartfiles -nostdlib  -mmcu=attiny1604 -o optiboot_attiny1604.elf optiboot_x.c
avr-size optiboot_attiny1604.elf
   text    data     bss     dec     hex filename
    494       9       0     503     1f7 optiboot_attiny1604.elf
avr-objdump -S  optiboot_attiny1604.elf &gt; optiboot_attiny1604.lst
avr-objcopy -j .text -j .data -j .version --set-section-flags .version=alloc,load -O ihex optiboot_attiny1604.elf optiboot_attiny1604.hex
</code></pre>
<p>Then I uploaded the Optiboot_X bootloader to the ATTiny1604 microcontroller via UPDI, using <code>pyupdi</code>.</p>
<pre><code># set fuse BOOTEND
$ pyupdi -d tiny1604 -c /dev/ttyUSB0 -fs 8:0x02 

# burn Optiboot_X
$ pyupdi -d tiny1604 -c /dev/ttyUSB0 -f optiboot_attiny1604.hex

# reset
$ pyupdi -d tiny1604 -c /dev/ttyUSB0 -r
</code></pre>
<p>After burning the Optiboot_X via UPDI, I was able to see 10 pulses during power-on at A7 on my oscilloscope, indicating that Optiboot_X was alive.</p>
<p>Note: Technically the installation is not complete yet. Since UPDI and <code>/RESET</code> share the same pin, UPDI must be permanently disabled via fuse to allow programming via bootloader, but it means a nonfunctional bootloader can brick the chip. I decided to manually reset the chip by power cycling the board for now.</p>
<p>Next, I wrote a test program.</p>
<pre><code>$ cat blink.c

#define F_CPU 20000000UL
#include &lt;avr/io.h&gt;
#include &lt;util/delay.h&gt;

int main(void)
{
    PORTB.DIRSET = 0b00000001;

    while(1) {
        PORTB.OUTSET = 0b00000001;
        _delay_ms(500);
        PORTB.OUTCLR = 0b00000001;
        _delay_ms(500);
    }
}
</code></pre>
<p>And I compiled it.</p>
<pre><code>$ make
avr-gcc -mmcu=attiny1604 -Wall -Os -o blink.elf blink.c
avr-objcopy -j .text -j .data -j .rodata -O ihex blink.elf blink.hex
</code></pre>
<h3>Question</h3>
<p>Now what? How do I upload <code>blink.hex</code> to the Optiboot_X on the microcontroller?</p>
<p>On a traditional Arduino, the command is something like...</p>
<pre><code>$ avrdude -C/etc/avrdude/avrdude.conf -v -patmega328p \
  -carduino -P/dev/ttyUSB0 -b38400 -D -Uflash:w:blink.hex:i
</code></pre>
<p>But this is not an ATMega328P and there's nearly zero documentation on the web, I have a lot of questions and I'm not sure how to proceed.</p>
<p>To begin with, in <code>man avrdude</code>, there's no mention of any Tiny0 or Tiny1 devices. What should the <code>-p</code> argument be? Also, do I need to use a patched version of <code>avrdude</code>? Do I need a customized <code>avrdude.conf</code>? If so, where can I find them?</p>
 70005: <p>After poking around in the git repository for a while, I've solved the problem. To work with these new TinyAVR-0/1 microcontrollers, a new version of <code>avrdude</code> is needed. Since <code>avrdude</code> hasn't made a stable release yet, it's necessary to build a new <code>avrdude</code> from source from SVN.</p>
<pre><code>$ svn co https://svn.savannah.nongnu.org/svn/avrdude/trunk avrdude
$ cd avrdude/avrdude
$ ./bootstrap
$ mkdir build &amp;&amp; cd build
$ ../configure
$ make
$ make install
</code></pre>
<p>After installation, <code>/usr/local/bin/avrdude</code> and <code>/usr/local/etc/avrdude.conf</code> should be available. One can see that a bunch of new TinyAVR parts have been added...</p>
<pre><code>$ avrdude -p?

Valid parts are:
  [...]
  t10      = ATtiny10
  t11      = ATtiny11
  t12      = ATtiny12
  t13      = ATtiny13
  t15      = ATtiny15
  t1604    = ATtiny1604
  t1606    = ATtiny1606
  t1607    = ATtiny1607
  t1614    = ATtiny1614
  t1616    = ATtiny1616
  t1617    = ATtiny1617
  t1634    = ATtiny1634
  t20      = ATtiny20
  t202     = ATtiny202
  t204     = ATtiny204
  t212     = ATtiny212
  t214     = ATtiny214
  t2313    = ATtiny2313
  t24      = ATtiny24
  t25      = ATtiny25
  t26      = ATtiny26
  t261     = ATtiny261
  t28      = ATtiny28
  t3214    = ATtiny3214
  t3216    = ATtiny3216
  t3217    = ATtiny3217
  t4       = ATtiny4
  t40      = ATtiny40
  t402     = ATtiny402
  t404     = ATtiny404
  t406     = ATtiny406
  t412     = ATtiny412
  t414     = ATtiny414
  t416     = ATtiny416
  t417     = ATtiny417
  t4313    = ATtiny4313
  t43u     = ATtiny43u
  t44      = ATtiny44
  t441     = ATtiny441
  t45      = ATtiny45
  t461     = ATtiny461
  t5       = ATtiny5
  t804     = ATtiny804
  t806     = ATtiny806
  t807     = ATtiny807
  t814     = ATtiny814
  t816     = ATtiny816
  t817     = ATtiny817
  t84      = ATtiny84
  t841     = ATtiny841
  t85      = ATtiny85
  t861     = ATtiny861
  t88      = ATtiny88
  t9       = ATtiny9
  [...]
</code></pre>
<p>To program the ATTiny1604, simply run...</p>
<pre><code>$ avrdude -C/usr/local/etc/avrdude.conf -v -pt1604 \
  -carduino -P/dev/ttyUSB0 -b57600 -D -Uflash:w:blink.hex:i
</code></pre>
<p>Don't forget to modify the <code>-b57600</code> argument to match the baud of your Optiboot_X build.</p>
<p>Lesson learned: When <a href="https://en.wikipedia.org/wiki/RTFM" rel="nofollow noreferrer">RTFM</a> is impossible, <a href="https://wiki.c2.com/?UseTheSourceLuke" rel="nofollow noreferrer">use the source, Luke</a>!</p>
<h2>Potential Problems</h2>
<h3>avrdude: verification error; content mismatch</h3>
<p>It's possible that <code>avrdude</code> successfully establishes communication with the board via the serial port, reads the correct device signature, then attempts to upload a program, but ultimately fails verification.</p>
<pre><code>$ avrdude -v -pattiny1604 -carduino -P/dev/ttyUSB0 -b57600 -D -Uflash:w:blink.hex:i

Reading | ################################################## | 100% 0.00s

avrdude: Device signature = 0x1e9425 (probably t1604)
avrdude: reading input file &quot;blink.hex&quot;
avrdude: writing flash (204 bytes):

Writing | ################################################## | 100% 0.06s

avrdude: 204 bytes of flash written
avrdude: verifying flash memory against blink.hex:
avrdude: load data flash data from input file blink.hex:
avrdude: input file blink.hex contains 204 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 0.06s

avrdude: verifying ...
avrdude: verification error, first mismatch at byte 0x0000
         0x01 != 0x0c
avrdude: verification error; content mismatch
</code></pre>
<p>Solution: In TinyAVR 0/1, the bootloader is located at the beginning of the flash memory, occupies the first 512 bytes. When compiling the program to be uploaded via the bootloader, the correct 512-byte text section offset must be specified. Otherwise, <code>avrdude</code> will attempt to program from 0x00, which is impossible, because it's the location of the bootloader. Upon readback, instead of your program, <code>avrdude</code> reads the bootloader, which triggers the verification failure in question (it's worth noticing that <code>0x01</code>, the first mismatched byte that triggers verification failure, is actually the first byte in the Optiboot_X binary code).</p>
<p>Instead of using...</p>
<pre><code>avr-gcc -mmcu=attiny1604 -Wall -Os -o blink.elf blink.c
</code></pre>
<p>Recompile your program with `</p>
<pre><code>avr-gcc -mmcu=attiny1604 -Wall -Os -o blink.elf blink.c \
-Wl,--section-start=.text=0x200

avr-objcopy -j .text -j .data -j .rodata -O ihex blink.elf blink.hex
</code></pre>
<p>And reupload it.</p>
<pre><code>$ avrdude -v -pattiny1604 -carduino -P/dev/ttyUSB0 -b57600 -D -Uflash:w:blink.hex:i

avrdude: Device signature = 0x1e9425 (probably t1604)
avrdude: reading input file &quot;blink.hex&quot;
avrdude: writing flash (716 bytes):

Writing | ################################################## | 100% 0.00s

avrdude: 716 bytes of flash written
avrdude: verifying flash memory against blink.hex:
avrdude: load data flash data from input file blink.hex:
avrdude: input file blink.hex contains 716 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 0.00s

avrdude: verifying ...
avrdude: 716 bytes of flash verified

avrdude: safemode: Fuses OK (E:FF, H:FF, L:FF)

avrdude done.  Thank you.
</code></pre>
<h3>avrdude: programmer type jtagice3_updi not found</h3>
<p>It's possible that <code>avrdude</code> reports the following error...</p>
<pre><code>/usr/bin/avrdude -C/usr/local/etc/avrdude.conf -v -pattiny1604 -carduino -P/dev/ttyUSB0 -b38400 -D -Uflash:w:blink.hex:i

avrdude: Version 6.3, compiled on Jul 24 2019 at 00:00:00
         Copyright (c) 2000-2005 Brian Dean, http://www.bdmicro.com/
         Copyright (c) 2007-2014 Joerg Wunsch

         System wide configuration file is &quot;/usr/local/etc/avrdude.conf&quot;
avrdude: error at &quot;/usr/local/etc/avrdude.conf:1091: programmer type jtagice3_updi not found
avrdude: error reading system wide configuration file &quot;/usr/local/etc/avrdude.conf&quot;
</code></pre>
<p>Solution: You are trying to run an old version of <code>avrdude</code> with a new configuration file. Make sure the <code>avrdude</code> executable you are running is actually the up-to-date version compiled from the upstream SVN repository.</p>
