<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>http://www.ninerpedia.org/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Mizapf</id>
	<title>Ninerpedia - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="http://www.ninerpedia.org/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Mizapf"/>
	<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/wiki/Special:Contributions/Mizapf"/>
	<updated>2026-04-24T15:46:29Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.37.1</generator>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Geneve_physical_memory_map&amp;diff=50998</id>
		<title>Geneve physical memory map</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Geneve_physical_memory_map&amp;diff=50998"/>
		<updated>2024-06-02T12:15:12Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Memory map */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The physical memory map declares which physical addresses are used to access memory regions or devices.&lt;br /&gt;
&lt;br /&gt;
== Native mode ==&lt;br /&gt;
&lt;br /&gt;
=== Memory map ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;memory&amp;quot;&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Address&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Pages&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Region&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
! 000000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 00-3F&lt;br /&gt;
| class=&amp;quot;dramb&amp;quot; | 512 KiB On-board DRAM &lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Slow RAM (1 ws)&lt;br /&gt;
|-&lt;br /&gt;
! 080000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 40-7F&lt;br /&gt;
| class=&amp;quot;b512&amp;quot; | 512 KiB On-board DRAM expansion&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Allocated, but technically infeasible (no sockets or signal lines)&lt;br /&gt;
|-&lt;br /&gt;
! 100000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 80-B7&lt;br /&gt;
| class=&amp;quot;b64&amp;quot; | 448 KiB P-Box space&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | May be used by MEMEX card&lt;br /&gt;
|-&lt;br /&gt;
! 170000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | B8-BF&lt;br /&gt;
| class=&amp;quot;b64&amp;quot; | 64 KiB P-Box space&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Used by current expansion cards&lt;br /&gt;
|-&lt;br /&gt;
! 180000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | C0-E7&lt;br /&gt;
| class=&amp;quot;b64&amp;quot; | Future SRAM expansion&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | On-board, but with 1 WS&lt;br /&gt;
|-&lt;br /&gt;
! 1D0000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | E8-EB&lt;br /&gt;
| class=&amp;quot;sram&amp;quot; | 32 KiB On-board SRAM expansion&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Soldered on top of the existing 32 KiB chip&lt;br /&gt;
|-&lt;br /&gt;
! 1D8000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | EC-EF&lt;br /&gt;
| class=&amp;quot;sram&amp;quot; | 32 KiB On-board SRAM&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Original SRAM memory (0 ws)&lt;br /&gt;
|-&lt;br /&gt;
! 1E0000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | F0-FF&lt;br /&gt;
| class=&amp;quot;b128&amp;quot; | 128 KiB Boot EPROM&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Originally, only 16 KiB installed at F0,F1; mirrored&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Memory expansions ===&lt;br /&gt;
&lt;br /&gt;
The page area 40-7F is assigned to a &amp;quot;Future on-board memory expansion&amp;quot;. In fact, the external bus (to the PEB) is not activated during accesses in that area, which means that external cards cannot populate this space. However, the Geneve main board does not offer sockets for such additional on-board memory. It is not possible to add these chips on top of the existing DRAM chips either. The only way to make use of this area is by using the MEMEX in a Genmod configuration.&lt;br /&gt;
&lt;br /&gt;
The page area C0-E7 is assigned to SRAM (0 ws) fast memory. For accesses in that area, the external bus is activated, which means that SRAM chips for that area must be put on a card that plugs into the PEB.&lt;br /&gt;
&lt;br /&gt;
=== Boot EPROM space ===&lt;br /&gt;
&lt;br /&gt;
The Boot EPROM region may have up to 128 KiB space as 16 pages of 8 KiB. This is used with the [[Programmable Flash Memory Expansion | PFM]].&lt;br /&gt;
&lt;br /&gt;
On reset, page F8 is mapped into the 0000-1FFF logical address region. The EPROM on the stock Geneve defines all even pages as mirrors of F0, and all odd pages as mirrors of F1.&lt;br /&gt;
&lt;br /&gt;
== GPL mode ==&lt;br /&gt;
&lt;br /&gt;
=== Memory map ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;memory&amp;quot;&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Address&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Pages&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Region&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
! 000000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 00-35&lt;br /&gt;
| class=&amp;quot;dramb&amp;quot; | 432 KiB On-board DRAM &lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Slow RAM (1 ws)&lt;br /&gt;
|-&lt;br /&gt;
! 06C000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 36&lt;br /&gt;
| class=&amp;quot;dram&amp;quot; | 8 KiB Cartridge ROM space (bank 1)&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | First bank of cartridge (Extended Basic style)&lt;br /&gt;
|-&lt;br /&gt;
! 06E000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 37&lt;br /&gt;
| class=&amp;quot;dram&amp;quot; | 8 KiB Cartridge ROM space (bank 2)&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Second bank of cartridge (Extended Basic style)&lt;br /&gt;
|-&lt;br /&gt;
! 070000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 38-3F&lt;br /&gt;
| class=&amp;quot;dram&amp;quot; | 64 KiB GRAM Cartridge space&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | [[Geneve paged memory organization | Mapper byte 06]] must be set to 0x03 to enable GROM/GRAM&lt;br /&gt;
|-&lt;br /&gt;
! 080000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 40-7F&lt;br /&gt;
| class=&amp;quot;b512&amp;quot; | 512 KiB On-board DRAM expansion&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Allocated, but technically infeasible (no sockets or signal lines)&lt;br /&gt;
|-&lt;br /&gt;
! 100000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 80-B7&lt;br /&gt;
| class=&amp;quot;b128&amp;quot; | 448 KiB P-Box space&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | May be used by MEMEX card&lt;br /&gt;
|-&lt;br /&gt;
! 170000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | B8-BF&lt;br /&gt;
| class=&amp;quot;b64&amp;quot; | 64 KiB P-Box space&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Used by current expansion cards&lt;br /&gt;
|-&lt;br /&gt;
! 180000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | C0-E7&lt;br /&gt;
| class=&amp;quot;b64&amp;quot; | Future SRAM expansion&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | On-board, but with 1 WS&lt;br /&gt;
|-&lt;br /&gt;
! 1D0000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | E8-EB&lt;br /&gt;
| class=&amp;quot;sram&amp;quot; | 32 KiB On-board SRAM expansion&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Soldered on top of the existing 32 KiB chip&lt;br /&gt;
|-&lt;br /&gt;
! 1D8000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | EC-EF&lt;br /&gt;
| class=&amp;quot;sram&amp;quot; | 32 KiB On-board SRAM&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Original SRAM memory (0 ws)&lt;br /&gt;
|-&lt;br /&gt;
! 1E0000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | F0-FF&lt;br /&gt;
| class=&amp;quot;b128&amp;quot; | 128 KiB Boot EPROM&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Originally, only 16 KiB installed at F0,F1; mirrored&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Cartridge emulation ===&lt;br /&gt;
&lt;br /&gt;
The cartridge emulation in the Gate array shows some peculiarities. &lt;br /&gt;
&lt;br /&gt;
* The mapper byte 06 (F116 / 8006) must be set to 03 to enable the GROM/GRAM cartridge space. Otherwise, no GROM/GRAM handling is emulated.&lt;br /&gt;
* In GPL mode, logical addresses 6000-7FFF are always mapped to page 36/37, regardless of the mapper byte 03 (F113 / 8003). &lt;br /&gt;
* CRU address 1EF8 is used to switch between single or dual bank cartridge ROM. When set to 0, only page 36 is used. When set to 1, writing to cartridge space addresses 6000 + n*4 selects page 36, while writing to 6002 + n*4 selects page 37.&lt;br /&gt;
* Cartridge GRAM regions are 8 KiB large, unlike real GROMs with 6 KiB.&lt;br /&gt;
* Address wrapping occurs at 8K boundaries (3FFF+1 = 2000).&lt;br /&gt;
* Reading via the GROM read address delivers the byte at the current GROM address, then advances the address by 1. Writing puts the byte at the current address, then advances the address by 1.&lt;br /&gt;
* Reading the GROM address pointer delivers the MSB, then on every subsequent read access the LSB. The counter contains the former LSB in both bytes after reading and must be restored.&lt;br /&gt;
&lt;br /&gt;
== Genmod ==&lt;br /&gt;
&lt;br /&gt;
The Genmod (Geneve modification) can be run in two modes, selectable by a switch. The TI mode activates the on-board slow DRAM, which is required to run the GPL mode. The reason is that the Gate array maps the GROM accesses to the DRAM on the board, which cannot be changed to the external RAM.&lt;br /&gt;
&lt;br /&gt;
=== TI mode ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;memory&amp;quot;&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Address&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Pages&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Region&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
! 000000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 00-35&lt;br /&gt;
| class=&amp;quot;dramb&amp;quot; | 432 KiB On-board DRAM &lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Slow RAM (1 ws)&lt;br /&gt;
|-&lt;br /&gt;
! 06C000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 36&lt;br /&gt;
| class=&amp;quot;dram&amp;quot; | 8 KiB Cartridge ROM space (bank 1)&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | First bank of cartridge (Extended Basic style)&lt;br /&gt;
|-&lt;br /&gt;
! 06E000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 37&lt;br /&gt;
| class=&amp;quot;dram&amp;quot; | 8 KiB Cartridge ROM space (bank 2)&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Second bank of cartridge (Extended Basic style)&lt;br /&gt;
|-&lt;br /&gt;
! 070000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 38-3F&lt;br /&gt;
| class=&amp;quot;dram&amp;quot; | 64 KiB GRAM Cartridge space&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | [[Geneve paged memory organization | Map byte 06]] must be set to 0x03 to enable GRAM&lt;br /&gt;
|--&lt;br /&gt;
! 080000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 40-EF&lt;br /&gt;
| class=&amp;quot;sraml&amp;quot; | 1408 KiB P-Box memory expansion (MEMEX)&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | SRAM (0 ws)&lt;br /&gt;
|-&lt;br /&gt;
! 1E0000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | F0-FF&lt;br /&gt;
| class=&amp;quot;b128&amp;quot; | 128 KiB Boot EPROM&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Originally, only 16 KiB installed at F0,F1; mirrored&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Non-TI mode ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;memory&amp;quot;&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Address&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Pages&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Region&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
! 000000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 00-EF&lt;br /&gt;
| class=&amp;quot;sraml&amp;quot; | 1920 KiB P-Box memory expansion (MEMEX)&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | SRAM (0 ws)&lt;br /&gt;
|-&lt;br /&gt;
! 1E0000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | F0-FF&lt;br /&gt;
| class=&amp;quot;b128&amp;quot; | 128 KiB Boot EPROM&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Originally, only 16 KiB installed at F0,F1; mirrored&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[Category:Geneve]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Geneve_physical_memory_map&amp;diff=50997</id>
		<title>Geneve physical memory map</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Geneve_physical_memory_map&amp;diff=50997"/>
		<updated>2024-06-02T12:14:40Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Memory map */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The physical memory map declares which physical addresses are used to access memory regions or devices.&lt;br /&gt;
&lt;br /&gt;
== Native mode ==&lt;br /&gt;
&lt;br /&gt;
=== Memory map ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;memory&amp;quot;&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Address&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Pages&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Region&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
! 000000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 00-3F&lt;br /&gt;
| class=&amp;quot;dramb&amp;quot; | 512 KiB On-board DRAM &lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Slow RAM (1 ws)&lt;br /&gt;
|-&lt;br /&gt;
! 080000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 40-7F&lt;br /&gt;
| class=&amp;quot;b512&amp;quot; | 512 KiB On-board DRAM expansion&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Allocated, but technically infeasible (no sockets or signal lines)&lt;br /&gt;
|-&lt;br /&gt;
! 100000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 80-B7&lt;br /&gt;
| class=&amp;quot;b64&amp;quot; | 448 KiB P-Box space&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | May be used by MEMEX card&lt;br /&gt;
|-&lt;br /&gt;
! 170000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | B8-BF&lt;br /&gt;
| class=&amp;quot;b64&amp;quot; | 64 KiB P-Box space&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Used by current expansion cards&lt;br /&gt;
|-&lt;br /&gt;
! 180000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | C0-E7&lt;br /&gt;
| class=&amp;quot;b64&amp;quot; | Future SRAM expansion&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | On-board, but with 1 WS&lt;br /&gt;
|-&lt;br /&gt;
! 1D0000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | E8-EB&lt;br /&gt;
| class=&amp;quot;sram&amp;quot; | 32 KiB On-board SRAM expansion&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Soldered on top of the existing 32 KiB chip&lt;br /&gt;
|-&lt;br /&gt;
! 1D8000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | EC-EF&lt;br /&gt;
| class=&amp;quot;sram&amp;quot; | 32 KiB On-board SRAM&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Original SRAM memory (0 ws)&lt;br /&gt;
|-&lt;br /&gt;
! 1E0000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | F0-FF&lt;br /&gt;
| class=&amp;quot;b128&amp;quot; | 128 KiB Boot EPROM&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Originally, only 16 KiB installed at F0,F1; mirrored&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Memory expansions ===&lt;br /&gt;
&lt;br /&gt;
The page area 40-7F is assigned to a &amp;quot;Future on-board memory expansion&amp;quot;. In fact, the external bus (to the PEB) is not activated during accesses in that area, which means that external cards cannot populate this space. However, the Geneve main board does not offer sockets for such additional on-board memory. It is not possible to add these chips on top of the existing DRAM chips either. The only way to make use of this area is by using the MEMEX in a Genmod configuration.&lt;br /&gt;
&lt;br /&gt;
The page area C0-E7 is assigned to SRAM (0 ws) fast memory. For accesses in that area, the external bus is activated, which means that SRAM chips for that area must be put on a card that plugs into the PEB.&lt;br /&gt;
&lt;br /&gt;
=== Boot EPROM space ===&lt;br /&gt;
&lt;br /&gt;
The Boot EPROM region may have up to 128 KiB space as 16 pages of 8 KiB. This is used with the [[Programmable Flash Memory Expansion | PFM]].&lt;br /&gt;
&lt;br /&gt;
On reset, page F8 is mapped into the 0000-1FFF logical address region. The EPROM on the stock Geneve defines all even pages as mirrors of F0, and all odd pages as mirrors of F1.&lt;br /&gt;
&lt;br /&gt;
== GPL mode ==&lt;br /&gt;
&lt;br /&gt;
=== Memory map ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;memory&amp;quot;&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Address&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Pages&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Region&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
! 000000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 00-35&lt;br /&gt;
| class=&amp;quot;dramb&amp;quot; | 432 KiB On-board DRAM &lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Slow RAM (1 ws)&lt;br /&gt;
|-&lt;br /&gt;
! 06C000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 36&lt;br /&gt;
| class=&amp;quot;dram&amp;quot; | 8 KiB Cartridge ROM space (bank 1)&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | First bank of cartridge (Extended Basic style)&lt;br /&gt;
|-&lt;br /&gt;
! 06E000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 37&lt;br /&gt;
| class=&amp;quot;dram&amp;quot; | 8 KiB Cartridge ROM space (bank 2)&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Second bank of cartridge (Extended Basic style)&lt;br /&gt;
|-&lt;br /&gt;
! 070000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 38-3F&lt;br /&gt;
| class=&amp;quot;dram&amp;quot; | 64 KiB GRAM Cartridge space&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | [[Geneve paged memory organization | Mapper byte 06]] must be set to 0x03 to enable GROM/GRAM&lt;br /&gt;
|-&lt;br /&gt;
! 080000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 40-7F&lt;br /&gt;
| class=&amp;quot;b512&amp;quot; | 512 KiB On-board DRAM expansion&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Allocated, but technically infeasible (no sockets or signal lines)&lt;br /&gt;
|-&lt;br /&gt;
! 100000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 80-B7&lt;br /&gt;
| class=&amp;quot;b128&amp;quot; | 448 KiB P-Box space&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | May be used by MEMEX card&lt;br /&gt;
|-&lt;br /&gt;
! 170000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | B8-BF&lt;br /&gt;
| class=&amp;quot;b64&amp;quot; | 64 KiB P-Box space&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Used by current expansion cards&lt;br /&gt;
|-&lt;br /&gt;
! 180000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | C0-E7&lt;br /&gt;
| class=&amp;quot;b64&amp;quot; | Future SRAM expansion&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Only in P-Box&lt;br /&gt;
|-&lt;br /&gt;
! 1D0000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | E8-EB&lt;br /&gt;
| class=&amp;quot;sram&amp;quot; | 32 KiB On-board SRAM expansion&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Soldered on top of the existing 32 KiB chip&lt;br /&gt;
|-&lt;br /&gt;
! 1D8000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | EC-EF&lt;br /&gt;
| class=&amp;quot;sram&amp;quot; | 32 KiB On-board SRAM&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Original SRAM memory (0 ws)&lt;br /&gt;
|-&lt;br /&gt;
! 1E0000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | F0-FF&lt;br /&gt;
| class=&amp;quot;b128&amp;quot; | 128 KiB Boot EPROM&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Originally, only 16 KiB installed at F0,F1; mirrored&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Cartridge emulation ===&lt;br /&gt;
&lt;br /&gt;
The cartridge emulation in the Gate array shows some peculiarities. &lt;br /&gt;
&lt;br /&gt;
* The mapper byte 06 (F116 / 8006) must be set to 03 to enable the GROM/GRAM cartridge space. Otherwise, no GROM/GRAM handling is emulated.&lt;br /&gt;
* In GPL mode, logical addresses 6000-7FFF are always mapped to page 36/37, regardless of the mapper byte 03 (F113 / 8003). &lt;br /&gt;
* CRU address 1EF8 is used to switch between single or dual bank cartridge ROM. When set to 0, only page 36 is used. When set to 1, writing to cartridge space addresses 6000 + n*4 selects page 36, while writing to 6002 + n*4 selects page 37.&lt;br /&gt;
* Cartridge GRAM regions are 8 KiB large, unlike real GROMs with 6 KiB.&lt;br /&gt;
* Address wrapping occurs at 8K boundaries (3FFF+1 = 2000).&lt;br /&gt;
* Reading via the GROM read address delivers the byte at the current GROM address, then advances the address by 1. Writing puts the byte at the current address, then advances the address by 1.&lt;br /&gt;
* Reading the GROM address pointer delivers the MSB, then on every subsequent read access the LSB. The counter contains the former LSB in both bytes after reading and must be restored.&lt;br /&gt;
&lt;br /&gt;
== Genmod ==&lt;br /&gt;
&lt;br /&gt;
The Genmod (Geneve modification) can be run in two modes, selectable by a switch. The TI mode activates the on-board slow DRAM, which is required to run the GPL mode. The reason is that the Gate array maps the GROM accesses to the DRAM on the board, which cannot be changed to the external RAM.&lt;br /&gt;
&lt;br /&gt;
=== TI mode ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;memory&amp;quot;&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Address&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Pages&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Region&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
! 000000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 00-35&lt;br /&gt;
| class=&amp;quot;dramb&amp;quot; | 432 KiB On-board DRAM &lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Slow RAM (1 ws)&lt;br /&gt;
|-&lt;br /&gt;
! 06C000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 36&lt;br /&gt;
| class=&amp;quot;dram&amp;quot; | 8 KiB Cartridge ROM space (bank 1)&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | First bank of cartridge (Extended Basic style)&lt;br /&gt;
|-&lt;br /&gt;
! 06E000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 37&lt;br /&gt;
| class=&amp;quot;dram&amp;quot; | 8 KiB Cartridge ROM space (bank 2)&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Second bank of cartridge (Extended Basic style)&lt;br /&gt;
|-&lt;br /&gt;
! 070000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 38-3F&lt;br /&gt;
| class=&amp;quot;dram&amp;quot; | 64 KiB GRAM Cartridge space&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | [[Geneve paged memory organization | Map byte 06]] must be set to 0x03 to enable GRAM&lt;br /&gt;
|--&lt;br /&gt;
! 080000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 40-EF&lt;br /&gt;
| class=&amp;quot;sraml&amp;quot; | 1408 KiB P-Box memory expansion (MEMEX)&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | SRAM (0 ws)&lt;br /&gt;
|-&lt;br /&gt;
! 1E0000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | F0-FF&lt;br /&gt;
| class=&amp;quot;b128&amp;quot; | 128 KiB Boot EPROM&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Originally, only 16 KiB installed at F0,F1; mirrored&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Non-TI mode ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;memory&amp;quot;&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Address&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Pages&lt;br /&gt;
! class=&amp;quot;top&amp;quot; | Region&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
! 000000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | 00-EF&lt;br /&gt;
| class=&amp;quot;sraml&amp;quot; | 1920 KiB P-Box memory expansion (MEMEX)&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | SRAM (0 ws)&lt;br /&gt;
|-&lt;br /&gt;
! 1E0000&lt;br /&gt;
| class=&amp;quot;bank&amp;quot; | F0-FF&lt;br /&gt;
| class=&amp;quot;b128&amp;quot; | 128 KiB Boot EPROM&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Originally, only 16 KiB installed at F0,F1; mirrored&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[Category:Geneve]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Geneve_video_wait_states&amp;diff=50978</id>
		<title>Geneve video wait states</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Geneve_video_wait_states&amp;diff=50978"/>
		<updated>2024-01-13T00:38:11Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The Geneve video wait state handling is controlled by the PAL chip on the board. Unfortunately, there are no public specifications on that custom chip, so all we can do is to try by experiment.&lt;br /&gt;
&lt;br /&gt;
(Update: We do have the PAL equations, found by reverse engineering, but the effects mentioned below still cannot fully be explained.)&lt;br /&gt;
&lt;br /&gt;
Video wait states are created after the access has occured. On that occasion, 14 wait states are created by the counter in the PAL, pulling down the READY line for that period of time. Before this, one or two wait states are inserted since the video access is an external one.&lt;br /&gt;
&lt;br /&gt;
The long wait state period starts after the access, which causes this very access to terminate, and then to start the period for the next command. This means that those wait states are completely ineffective in the following case:&lt;br /&gt;
&lt;br /&gt;
  // Assume workspace at F000&lt;br /&gt;
  F040    MOVB @VDPRD,R3&lt;br /&gt;
  F044    NOP&lt;br /&gt;
  F046    DEC  R1&lt;br /&gt;
  F048    JNE  F040&lt;br /&gt;
&lt;br /&gt;
The instruction at address F040 requires 5 cycles: get MOVB @,R3, get the source address, read from the source address, 1 WS for this external access, write to register 3. Then the next command is read (NOP) which is in the on-chip RAM (check the addresses). For that reason, the READY pin is ignored, which has been pulled low in the meantime. NOP takes three cycles, as does DEC R1, still ignoring the low READY. JNE also takes three cycles. If R1 is not 0, we jump back, and the command is executed again. &lt;br /&gt;
&lt;br /&gt;
Obviously, the counter is now reset, because we measure that the command again takes only 5 cycles, despite the fact that the 14 WS from last iteration are not yet over. &lt;br /&gt;
&lt;br /&gt;
However, if we add an access to SRAM, we will notice a delay:&lt;br /&gt;
&lt;br /&gt;
  // Assume workspace at F000&lt;br /&gt;
  F040    MOVB @VDPRD,R3     *  5 cycles&lt;br /&gt;
  F044    MOVB @SRAM,R4      * 15 cycles (4 cycles + 11 WS)&lt;br /&gt;
  F048    DEC  R1            *  3 cycles&lt;br /&gt;
  F04A    JNE  F040          *  3 cycles&lt;br /&gt;
&lt;br /&gt;
But we only get 11 WS, not 14. This is reasonable if we remember that the access to SRAM does not occur immediately after the VDP access but after writing to R3 and getting the next command. This can be shown more easily in the following way. ADDR is the constant, symbolizing the memory location; *ADDR means an access to the memory location at address ADDR, &amp;lt; is reading, &amp;gt; is writing. PC is the program counter. The opcode, which is read in the first cycle, contains information about the types of source and destination locations, as well as the register numbers.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;waitstate&amp;quot;&lt;br /&gt;
! 1 !! 2 !! 3 !! 4 !! 5 !! 6 !! 7 !! 8 !! 9 !! 10 !! 11 !! 12 !! 13 !! 14 !! 15 &lt;br /&gt;
|-&lt;br /&gt;
| MOVB @,R3&lt;br /&gt;
| VDPRD&lt;br /&gt;
| class=&amp;quot;wait&amp;quot; | wait&lt;br /&gt;
| &amp;lt; *VDPRD&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;gt; R3 &lt;br /&gt;
|-&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | MOVB @,R4&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | SRAM&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| &amp;lt; *SRAM&lt;br /&gt;
| &amp;gt; R3&lt;br /&gt;
|-&lt;br /&gt;
| DEC R1&lt;br /&gt;
| &amp;lt; R1&lt;br /&gt;
| &amp;gt; R1&lt;br /&gt;
|-&lt;br /&gt;
| JNE&lt;br /&gt;
| &amp;lt; PC&lt;br /&gt;
| &amp;gt; PC&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
There are several activities going on while the wait states are active &amp;quot;in the background&amp;quot;, as symbolized by the blue background. Only when the next access to external memory occurs the wait states become effective. As said above, the read operations only terminate when the READY line gets high again, so we put the termination of the read access in the last box. You should keep in mind that the read access starts right after the last activity. &lt;br /&gt;
&lt;br /&gt;
We can spend so much time inside the on-chip address space that all wait states pass without effect. The following code is executed at the same speed, whether the video wait state bit is set of not:&lt;br /&gt;
&lt;br /&gt;
  // Assume workspace at F000&lt;br /&gt;
  F040    MOVB @VDPRD,R3 &lt;br /&gt;
          NOP&lt;br /&gt;
          DEC  @&amp;gt;F006&lt;br /&gt;
          DEC  @&amp;gt;F006&lt;br /&gt;
          MOVB @SRAM,R4&lt;br /&gt;
          DEC  R1&lt;br /&gt;
          JNE  F040&lt;br /&gt;
&lt;br /&gt;
as you can see here:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;waitstate&amp;quot;&lt;br /&gt;
! 1 !! 2 !! 3 !! 4 !! 5 &lt;br /&gt;
|-&lt;br /&gt;
| MOVB @,R3&lt;br /&gt;
| VDPRD&lt;br /&gt;
| class=&amp;quot;wait&amp;quot; | wait&lt;br /&gt;
| &amp;lt; *VDPRD&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;gt; R3 &lt;br /&gt;
|-&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | NOP&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;lt; PC&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;gt; PC&lt;br /&gt;
|- &lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | DEC @&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | F006&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;lt; *F006&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;gt; *F006&lt;br /&gt;
|-&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | DEC @&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | F006&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;lt; *F006&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;gt; *F006&lt;br /&gt;
|-&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | MOVB @,R4&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | SRAM&lt;br /&gt;
| &amp;lt; *SRAM&lt;br /&gt;
| &amp;gt; R3&lt;br /&gt;
|-&lt;br /&gt;
| DEC R1&lt;br /&gt;
| &amp;lt; R1&lt;br /&gt;
| &amp;gt; R1&lt;br /&gt;
|-&lt;br /&gt;
| JNE&lt;br /&gt;
| &amp;lt; PC&lt;br /&gt;
| &amp;gt; PC&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Now an example on writing to VDP. &lt;br /&gt;
&lt;br /&gt;
 F040   MOVB R3,@&amp;gt;VDPWD&lt;br /&gt;
        NOP&lt;br /&gt;
        NOP&lt;br /&gt;
        NOP&lt;br /&gt;
        MOVB @SRAM,@&amp;gt;F000&lt;br /&gt;
        DEC  R1&lt;br /&gt;
        JNE  F040&lt;br /&gt;
&lt;br /&gt;
Again as a diagram. Note that box 5 in the first line is not backgrounded: The READY line must be high, or the command will not be completed. We assume that the data are written to VDPWD in box 4 already.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;waitstate&amp;quot;&lt;br /&gt;
! 1 !! 2 !! 3 !! 4 !! 5 !! 6 !! 7  !! 8 !! 9&lt;br /&gt;
|-&lt;br /&gt;
| MOVB R3,@&lt;br /&gt;
| &amp;lt; R3&lt;br /&gt;
| VDPWD&lt;br /&gt;
| class=&amp;quot;wait&amp;quot; | &amp;gt; *VDPWD&lt;br /&gt;
| wait&lt;br /&gt;
|-&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | NOP&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;lt; PC&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;gt; PC&lt;br /&gt;
|- &lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | NOP&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;lt; PC&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;gt; PC&lt;br /&gt;
|- &lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | NOP&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;lt; PC&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;gt; PC&lt;br /&gt;
|- &lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | MOVB @,@&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | SRAM&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| &amp;lt; *SRAM&lt;br /&gt;
| F000&lt;br /&gt;
| &amp;gt; *F000&lt;br /&gt;
|-&lt;br /&gt;
| DEC R1&lt;br /&gt;
| &amp;lt; R1&lt;br /&gt;
| &amp;gt; R1&lt;br /&gt;
|-&lt;br /&gt;
| JNE&lt;br /&gt;
| &amp;lt; PC&lt;br /&gt;
| &amp;gt; PC&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
There is one peculiarity: We now have 15 wait states, not 14. Indeed, as my measurements prove, the complete code requires 29 cycles, and by using commands in the on-chip RAM as shown in the previous example one can see that the second MOVB line requires 9 cycles, including 4 wait states. As it seems, video writing requires &amp;#039;&amp;#039;one wait state more&amp;#039;&amp;#039; than reading. This may be a design issue of the PAL, but it won&amp;#039;t cause any trouble.&lt;br /&gt;
&lt;br /&gt;
Finally we should look at the case when we get wait states by different reasons. Assume we set up the system to use video wait states &amp;#039;&amp;#039;&amp;#039;and&amp;#039;&amp;#039;&amp;#039; extra wait states. &lt;br /&gt;
&lt;br /&gt;
 F040   MOVB R3,@&amp;gt;VDPWD&lt;br /&gt;
        NOP&lt;br /&gt;
        NOP&lt;br /&gt;
        DEC  @&amp;gt;F000&lt;br /&gt;
        MOVB @SRAM,R4&lt;br /&gt;
        DEC  R1&lt;br /&gt;
        JNE  F040&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;waitstate&amp;quot;&lt;br /&gt;
! 1 !! 2 !! 3 !! 4 !! 5 !! 6 !! 7  !! 8 !! 9&lt;br /&gt;
|-&lt;br /&gt;
| MOVB R3,@&lt;br /&gt;
| &amp;lt; R3&lt;br /&gt;
| VDPWD&lt;br /&gt;
| class=&amp;quot;wait&amp;quot; | &amp;gt; *VDPWD&lt;br /&gt;
| class=&amp;quot;wait&amp;quot; | wait&lt;br /&gt;
| wait&lt;br /&gt;
|-&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | NOP&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;lt; PC&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;gt; PC&lt;br /&gt;
|- &lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | NOP&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;lt; PC&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;gt; PC&lt;br /&gt;
|- &lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | DEC @&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | F000&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;lt; *F000&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;gt; *F000&lt;br /&gt;
|- &lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | MOVB @,R4&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | SRAM&lt;br /&gt;
| class=&amp;quot;wait2&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait2&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| &amp;lt; *SRAM&lt;br /&gt;
| &amp;gt; R4&lt;br /&gt;
|-&lt;br /&gt;
| DEC R1&lt;br /&gt;
| &amp;lt; R1&lt;br /&gt;
| &amp;gt; R1&lt;br /&gt;
|-&lt;br /&gt;
| JNE&lt;br /&gt;
| &amp;lt; PC&lt;br /&gt;
| &amp;gt; PC&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
First thing to notice is that the first line gets another wait state, since we have requested additional wait states, and VDPWD is an external address. Also, we have 15 wait states from video (blue). &lt;br /&gt;
&lt;br /&gt;
What happens to the additional wait states for the SRAM access in line 5? They occur at the same time as the video wait states, and therefore are not effective. As it seems, the counters for the wait states do not add on each other. The SRAM access is still delayed by 3 wait states (not 2) because there is one wait state left from the video access. After this one is over, the access occurs.&lt;br /&gt;
&lt;br /&gt;
== Unsolved questions ==&lt;br /&gt;
&lt;br /&gt;
There are some &amp;#039;&amp;#039;&amp;#039;unsolved questions&amp;#039;&amp;#039;&amp;#039;, unfortunately. They are not really a problem, but they make it difficult to understand the actual implementation and the way how this could be used within an emulation.&lt;br /&gt;
&lt;br /&gt;
=== VDP write causes one more wait state ===&lt;br /&gt;
&lt;br /&gt;
Why do VDP write accesses cause 1 more wait state than read accesses? &lt;br /&gt;
&lt;br /&gt;
 F040   MOVB R3,@&amp;gt;VDPWD&lt;br /&gt;
        NOP&lt;br /&gt;
        NOP&lt;br /&gt;
        NOP&lt;br /&gt;
        NOP&lt;br /&gt;
        MOVB @SRAM,R4&lt;br /&gt;
        DEC  R1&lt;br /&gt;
        JNE  F040&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;waitstate&amp;quot;&lt;br /&gt;
! 1 !! 2 !! 3 !! 4 !! 5 !! 6 !! 7  !! 8 !! 9 !! 10 !! 11 !! 12 &lt;br /&gt;
|-&lt;br /&gt;
| MOVB R3,@&lt;br /&gt;
| &amp;lt; R3&lt;br /&gt;
| VDPWD&lt;br /&gt;
| class=&amp;quot;wait&amp;quot; | &amp;gt; *VDPWD&lt;br /&gt;
| wait&lt;br /&gt;
|- &lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | NOP&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;lt; PC&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;gt; PC&lt;br /&gt;
|- &lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | NOP&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;lt; PC&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;gt; PC&lt;br /&gt;
|- &lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | NOP&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;lt; PC&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;gt; PC&lt;br /&gt;
|- &lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | NOP&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;lt; PC&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;gt; PC&lt;br /&gt;
|-&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | MOVB @,R4&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | SRAM&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| &amp;lt; SRAM&lt;br /&gt;
| &amp;gt; R4&lt;br /&gt;
|- &lt;br /&gt;
| DEC R1&lt;br /&gt;
| &amp;lt; R1&lt;br /&gt;
| &amp;gt; R1&lt;br /&gt;
|-&lt;br /&gt;
| JNE&lt;br /&gt;
| &amp;lt; PC&lt;br /&gt;
| &amp;gt; PC&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When you turn off the video wait states, the above code runs at 27 cycles. With video wait states, it needs 28 cycles. This clearly indicates that all but one wait state have passed in the background, and that last wait state delays the reading of the SRAM memory location. If we assumed that only 14 wait states were produced, the code should not have slowed down by one cycle.&lt;br /&gt;
&lt;br /&gt;
=== Less wait states when next access is write? ===&lt;br /&gt;
&lt;br /&gt;
Compare this:&lt;br /&gt;
&lt;br /&gt;
 F040   MOVB @&amp;gt;VDPRD,R3&lt;br /&gt;
        MOVB @SRAM,R4&lt;br /&gt;
        DEC  R1&lt;br /&gt;
        JNE  F040&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;waitstate&amp;quot;&lt;br /&gt;
! 1 !! 2 !! 3 !! 4 !! 5 !! 6 !! 7  !! 8 !! 9 !! 10 !! 11 !! 12 !! 13 !! 14 !! 15&lt;br /&gt;
|-&lt;br /&gt;
| MOVB @&lt;br /&gt;
| VDPWD&lt;br /&gt;
| class=&amp;quot;wait&amp;quot; | wait&lt;br /&gt;
| &amp;lt; *VDPRD&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;gt; R3&lt;br /&gt;
|- &lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | MOVB @,R4&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | SRAM&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| &amp;lt; *SRAM&lt;br /&gt;
| &amp;gt; R4&lt;br /&gt;
|- &lt;br /&gt;
| DEC R1&lt;br /&gt;
| &amp;lt; R1&lt;br /&gt;
| &amp;gt; R1&lt;br /&gt;
|-&lt;br /&gt;
| JNE&lt;br /&gt;
| &amp;lt; PC&lt;br /&gt;
| &amp;gt; PC&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
with this one:&lt;br /&gt;
&lt;br /&gt;
 F040   MOVB @&amp;gt;VDPRD,R3&lt;br /&gt;
        MOVB R4,@SRAM&lt;br /&gt;
        DEC  R1&lt;br /&gt;
        JNE  F040&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;waitstate&amp;quot;&lt;br /&gt;
! 1 !! 2 !! 3 !! 4 !! 5 !! 6 !! 7  !! 8 !! 9 !! 10 !! 11 !! 12 !! 13&lt;br /&gt;
|-&lt;br /&gt;
| MOVB @&lt;br /&gt;
| VDPWD&lt;br /&gt;
| class=&amp;quot;wait&amp;quot; | wait&lt;br /&gt;
| &amp;lt; *VDPRD&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;gt; R3&lt;br /&gt;
|- &lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | MOVB R4,@&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;lt; R4&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | SRAM&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;gt; *SRAM&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| wait&lt;br /&gt;
|- &lt;br /&gt;
| DEC R1&lt;br /&gt;
| &amp;lt; R1&lt;br /&gt;
| &amp;gt; R1&lt;br /&gt;
|-&lt;br /&gt;
| JNE&lt;br /&gt;
| &amp;lt; PC&lt;br /&gt;
| &amp;gt; PC&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
It seems as if the number of wait states depends on the next memory access type, whether read or write. Remember that the last wait state in the second example must show a high READY line, or the operation will not be complete. Accordingly, in the first example, READY is low for 14 cycles, while in the second example we cannot have more than 13 cycles.&lt;br /&gt;
&lt;br /&gt;
=== One more wait state when acquiring instructions? ===&lt;br /&gt;
&lt;br /&gt;
If we put the code in SRAM and access video, the following command should be delayed by 14 WS. However, it turns out to require 15 WS. Have a look:&lt;br /&gt;
&lt;br /&gt;
First we assume that instructions are in on-chip memory, also the registers.&lt;br /&gt;
&lt;br /&gt;
 F040   MOVB @&amp;gt;VDPRD,R3&lt;br /&gt;
        NOP&lt;br /&gt;
        MOVB @SRAM,R4&lt;br /&gt;
        DEC  R1&lt;br /&gt;
        JNE  F040&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;waitstate&amp;quot;&lt;br /&gt;
! 1 !! 2 !! 3 !! 4 !! 5 !! 6 !! 7  !! 8 !! 9 !! 10 !! 11 !! 12 &lt;br /&gt;
|-&lt;br /&gt;
| MOVB @,R3&lt;br /&gt;
| VDPRD&lt;br /&gt;
| class=&amp;quot;wait&amp;quot; | wait&lt;br /&gt;
|  &amp;lt; *VDPRD&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;gt; R3&lt;br /&gt;
|- &lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | NOP&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;lt; PC&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;gt; PC&lt;br /&gt;
|-&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | MOVB @,R4&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | SRAM&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait+&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| &amp;lt; SRAM&lt;br /&gt;
| &amp;gt; R4&lt;br /&gt;
|- &lt;br /&gt;
| DEC R1&lt;br /&gt;
| &amp;lt; R1&lt;br /&gt;
| &amp;gt; R1&lt;br /&gt;
|-&lt;br /&gt;
| JNE&lt;br /&gt;
| &amp;lt; PC&lt;br /&gt;
| &amp;gt; PC&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
+ At this point the processor attempts to fetch the contents of the memory location at SRAM. The access is delayed until READY goes high.&lt;br /&gt;
&lt;br /&gt;
Tests show that the code as listed above requires 26 cycles. Without video wait states we would get 18 cycles.&lt;br /&gt;
&lt;br /&gt;
Now have a look at this sample. We assume that registers are in on-chip memory and the program code is now in SRAM. This also means that for each command and operand acquisition we need two memory accesses.&lt;br /&gt;
&lt;br /&gt;
 A040   MOVB @&amp;gt;VDPRD,R3&lt;br /&gt;
        NOP&lt;br /&gt;
        MOVB @SRAM,R4&lt;br /&gt;
        DEC  R1&lt;br /&gt;
        JNE  A040&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;waitstate&amp;quot;&lt;br /&gt;
! 1 !! 2 !! 3 !! 4 !! 5 !! 6 !! 7  !! 8 !! 9 !! 10&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | MOVB @,R3&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | VDPRD&lt;br /&gt;
| class=&amp;quot;wait&amp;quot; | wait&lt;br /&gt;
|  &amp;lt; *VDPRD&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | &amp;gt; R3&lt;br /&gt;
|- &lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait+&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
|-&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| class=&amp;quot;wait1&amp;quot; | wait&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | NOP&lt;br /&gt;
| &amp;lt; PC&lt;br /&gt;
| &amp;gt; PC&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | MOVB @,R4&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | SRAM&lt;br /&gt;
| &amp;lt; SRAM&lt;br /&gt;
| &amp;gt; R4&lt;br /&gt;
|- &lt;br /&gt;
|  colspan=&amp;quot;2&amp;quot; | DEC R1&lt;br /&gt;
| &amp;lt; R1&lt;br /&gt;
| &amp;gt; R1&lt;br /&gt;
|-&lt;br /&gt;
|  colspan=&amp;quot;2&amp;quot; | JNE&lt;br /&gt;
| &amp;lt; PC&lt;br /&gt;
| &amp;gt; PC&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
+ At this point the processor tries to acquire the NOP command.&lt;br /&gt;
&lt;br /&gt;
If this code is executed in the on-chip RAM it takes 25 cycles. When executed in SRAM it requires 39 cycles. This seems at first a good number, as we said reading causes 14 wait states. But we forgot that one wait state occurs while writing to R3 (on-chip), so we should only have 13 effective WS. &lt;br /&gt;
&lt;br /&gt;
The command acquision from SRAM begins in the second line but completes exactly when the READY line goes high again, reading the first and then the second byte of NOP. We must therefore assume 15 WS. &lt;br /&gt;
&lt;br /&gt;
Still, cause comes before effect: the PAL cannot &amp;quot;know&amp;quot; after line 1 that it must set the counter 1 higher. The PAL may possibly know that the current read cycle is an instruction acquisition, as the IAQ line is connected to one of its pins. When acquiring an opcode, the line gets asserted (high), unlike for operand acquisitions where it remains low. &lt;br /&gt;
&lt;br /&gt;
=== Instruction prefetch as a possible reason ===&lt;br /&gt;
&lt;br /&gt;
There may also be an effect from the processor&amp;#039;s [[instruction prefetch]] capability. As mentioned, the TMS9995 is highly efficient in its usage of clock cycles, which is only possible when it can get the next instruction during internal operations. The additional wait state is probably be caused by the fact that external accesses are still blocked by the READY line, so one more cycle may be required.&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;br /&gt;
&lt;br /&gt;
[[Category:Geneve]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=File_systems&amp;diff=50844</id>
		<title>File systems</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=File_systems&amp;diff=50844"/>
		<updated>2023-08-31T20:50:40Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Volume Information Block (HFDC) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Floppy Disk File System ==&lt;br /&gt;
&lt;br /&gt;
Floppy file systems had no notion of subdirectories for most of the time. Disk controllers like the Myarc FDC and HFDC introduced a directory scheme where the root directory may have up to three subdirectories. These subdirectories may have no subdirectories by themselves.&lt;br /&gt;
&lt;br /&gt;
=== Volume Information Block ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;filesys&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 00&lt;br /&gt;
| rowspan=&amp;quot;5&amp;quot; colspan=&amp;quot;2&amp;quot; | Volume name &lt;br /&gt;
| rowspan=&amp;quot;5&amp;quot; class=&amp;quot;descr&amp;quot; | May not contain a dot (&amp;quot;.&amp;quot;) because this is used as the path separator. Padded with spaces.&lt;br /&gt;
|-&lt;br /&gt;
! 02&lt;br /&gt;
|-&lt;br /&gt;
! 04&lt;br /&gt;
|-&lt;br /&gt;
! 06&lt;br /&gt;
|-&lt;br /&gt;
! 08&lt;br /&gt;
|-&lt;br /&gt;
! 0A&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot;| Total number of sectors&lt;br /&gt;
|-&lt;br /&gt;
! 0C&lt;br /&gt;
| Sectors per track&lt;br /&gt;
| &amp;quot;D&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 0E&lt;br /&gt;
| &amp;quot;S&amp;quot;&lt;br /&gt;
| &amp;quot;K&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 10&lt;br /&gt;
| Protection&lt;br /&gt;
| Tracks per side&lt;br /&gt;
|-&lt;br /&gt;
! 12&lt;br /&gt;
| Number of sides&lt;br /&gt;
| Density&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Density may have values 0 .. 4 (see below)&lt;br /&gt;
|-&lt;br /&gt;
! 14&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; rowspan=&amp;quot;5&amp;quot; | Directory 1 name&lt;br /&gt;
| rowspan=&amp;quot;5&amp;quot; class=&amp;quot;descr&amp;quot; | Same constraints as for file or volume name&lt;br /&gt;
|-&lt;br /&gt;
! 16&lt;br /&gt;
|-&lt;br /&gt;
! 18&lt;br /&gt;
|-&lt;br /&gt;
! 1A&lt;br /&gt;
|-&lt;br /&gt;
! 1C&lt;br /&gt;
|-&lt;br /&gt;
! 1E&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Sector number of FDIR of dir 1&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Null if no directory exists. Only on start of AU. &lt;br /&gt;
|-&lt;br /&gt;
! 20&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; rowspan=&amp;quot;5&amp;quot; | Directory 2 name&lt;br /&gt;
|-&lt;br /&gt;
! 22&lt;br /&gt;
|-&lt;br /&gt;
! 24&lt;br /&gt;
|-&lt;br /&gt;
! 26&lt;br /&gt;
|-&lt;br /&gt;
! 28&lt;br /&gt;
|-&lt;br /&gt;
! 2A&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Sector number of FDIR of dir 2&lt;br /&gt;
|-&lt;br /&gt;
! 2C&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; rowspan=&amp;quot;5&amp;quot; | Directory 3 name&lt;br /&gt;
|-&lt;br /&gt;
! 2E&lt;br /&gt;
|-&lt;br /&gt;
! 30&lt;br /&gt;
|-&lt;br /&gt;
! 32&lt;br /&gt;
|-&lt;br /&gt;
! 34&lt;br /&gt;
|-&lt;br /&gt;
! 36&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Sector number of FDIR of dir 3&lt;br /&gt;
|-&lt;br /&gt;
! 38&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; rowspan=&amp;quot;5&amp;quot; | Allocation bit map&lt;br /&gt;
| rowspan=&amp;quot;5&amp;quot; class=&amp;quot;descr&amp;quot; | Each bit represents a collection of sectors on the disk (see below)&lt;br /&gt;
|-&lt;br /&gt;
! 3A&lt;br /&gt;
|-&lt;br /&gt;
! 3C&lt;br /&gt;
|-&lt;br /&gt;
! ...&lt;br /&gt;
|-&lt;br /&gt;
! FE&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Density values:&lt;br /&gt;
* Single (0,1): 125 kBit/s FM&lt;br /&gt;
* Double (2): 250 kBit/s MFM&lt;br /&gt;
* High (3): 500 kBit/s MFM&lt;br /&gt;
* Ultra (4): 1MBit/s MFM&lt;br /&gt;
&lt;br /&gt;
=== File Descriptor Index Record ===&lt;br /&gt;
&lt;br /&gt;
The File Descriptor Index Record (FDIR) contains pointers to the File Descriptor Records. The pointers are arranged so that the list of files has a lexicographic order (alphabetic sorting). The FDIR of the root directory is always in sector 1. The FDIRs of the subdirectories are pointed to by the respective fields in sector 0.&lt;br /&gt;
&lt;br /&gt;
The end of the list is marked by the first null pointer. &lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;filesys&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 00&lt;br /&gt;
| Sector number of first file descriptor record&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | Always sector number, not AU&lt;br /&gt;
|-&lt;br /&gt;
! 02&lt;br /&gt;
| Sector number of second file descriptor record&lt;br /&gt;
|-&lt;br /&gt;
! 04&lt;br /&gt;
| Sector number of third file descriptor record&lt;br /&gt;
|-&lt;br /&gt;
! 06&lt;br /&gt;
| Sector number of fourth file descriptor record&lt;br /&gt;
|-&lt;br /&gt;
! ...&lt;br /&gt;
|-&lt;br /&gt;
! FC&lt;br /&gt;
| Sector number of 127th file descriptor record&lt;br /&gt;
|-&lt;br /&gt;
! FE&lt;br /&gt;
| 0&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Unlike the FDIRs in the hard disk file system there is no pointer back to the parent directory as the floppy file system does not allow nested subdirectories, and thus the parent is always the root directory.&lt;br /&gt;
&lt;br /&gt;
=== File Descriptor Records ===&lt;br /&gt;
&lt;br /&gt;
Each file entry in the diretory is defined by a file descriptor record.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;filesys&amp;quot; style=&amp;quot;width:90%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 00&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; rowspan=&amp;quot;5&amp;quot; | File name&lt;br /&gt;
|-&lt;br /&gt;
! 02&lt;br /&gt;
|-&lt;br /&gt;
! 04&lt;br /&gt;
|-&lt;br /&gt;
! 06&lt;br /&gt;
|-&lt;br /&gt;
! 08&lt;br /&gt;
|-&lt;br /&gt;
! 0A&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Extended record length&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | used for logical record lengths beyond 255&lt;br /&gt;
|-&lt;br /&gt;
! 0C&lt;br /&gt;
| File status flags &lt;br /&gt;
| Number of records/sec&lt;br /&gt;
|-&lt;br /&gt;
! 0E&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Number of sectors currently allocated&lt;br /&gt;
|-&lt;br /&gt;
! 10&lt;br /&gt;
| End-of-file offset&lt;br /&gt;
| Logical record length&lt;br /&gt;
|-&lt;br /&gt;
! 12&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Level 3 record count (little-endian)&lt;br /&gt;
|-&lt;br /&gt;
! 14&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; rowspan=&amp;quot;2&amp;quot; | Date and time of creation&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | hhhh.hmmm.mmms.ssss  (2 seconds resolution)&lt;br /&gt;
|-&lt;br /&gt;
! 16&lt;br /&gt;
| class=&amp;quot;descr&amp;quot; | yyyy.yyyM.MMMd.dddd&lt;br /&gt;
|-&lt;br /&gt;
! 18&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; rowspan=&amp;quot;2&amp;quot; | Date and time of last update&lt;br /&gt;
|-&lt;br /&gt;
! 1A&lt;br /&gt;
|-&lt;br /&gt;
! 1C&lt;br /&gt;
| rowspan=&amp;quot;4&amp;quot; colspan=&amp;quot;2&amp;quot; | Data chain pointer blocks&lt;br /&gt;
|-&lt;br /&gt;
! 1E&lt;br /&gt;
|-&lt;br /&gt;
! ...&lt;br /&gt;
|-&lt;br /&gt;
! FE&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Extended record length&amp;#039;&amp;#039;&amp;#039; field (offset 0x0A) is used for record lengths beyond 255; up to 255 bytes the length is given by the byte at offset 0x11. The HFDC introduced this field, but it is unknown whether it was ever used. According to [1], the Extended Record Length field defines the length of records of data files when the &amp;#039;&amp;#039;Logical Record Length&amp;#039;&amp;#039; field (offset 0x11) is 0.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;File status flags&amp;#039;&amp;#039;&amp;#039; are defined as follows (see also [[TIFILES format]]):&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;tfiflags&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
! MSB&lt;br /&gt;
! &lt;br /&gt;
! &lt;br /&gt;
! &lt;br /&gt;
! &lt;br /&gt;
! &lt;br /&gt;
! &lt;br /&gt;
! LSB&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| FIXED&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; style=&amp;quot;background-color:#eeeeee&amp;quot;| Reserved&lt;br /&gt;
| normal&lt;br /&gt;
| Unmodified&lt;br /&gt;
| Unprotected&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; style=&amp;quot;background-color:#eeeeee&amp;quot; | Reserved&lt;br /&gt;
| DISPLAY&lt;br /&gt;
| Data&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| VARIABLE&lt;br /&gt;
| [[Emulate File format|Emulate File]]&lt;br /&gt;
| Modified&lt;br /&gt;
| Protected&lt;br /&gt;
| INTERNAL&lt;br /&gt;
| Program&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;EOF offset&amp;#039;&amp;#039;&amp;#039; is the location in the last sector where the EOF marker is found. Only variable length data files have an EOF marker (0xff). For program files and fixed length files this field points to the first byte after the file contents. If the EOF offset contains 0, the last sector is completely filled with data.&lt;br /&gt;
&lt;br /&gt;
To recreate the actual file length, &lt;br /&gt;
&lt;br /&gt;
* the total number of sectors must be multiplied by 256&lt;br /&gt;
* the number of bytes past the EOF marker must be subtracted (if the offset is not 0).&lt;br /&gt;
&lt;br /&gt;
That is, if the number of sectors is 10 and the EOF offset is 36, the file length is (10*256) - (256-36) = 2340. If the EOF offset is zero, we get the full 2560 bytes.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Level 3 record count&amp;#039;&amp;#039;&amp;#039;  (offset 0x12 and 0x13) specifies the number of [[records]] in [[data files]]; this means the number of times you can execute an INPUT operation on that file. In the case of fixed length records, the field &amp;quot;Number of Level-3 records&amp;quot; contains the highest record actually written to. If the last sector is filled as far as possible, we have &lt;br /&gt;
&lt;br /&gt;
:&amp;#039;&amp;#039;L3 = Records/Sector * Total number of sectors&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
This number is required to determine the highest record number, especially if the last sector is not filled completely.&lt;br /&gt;
&lt;br /&gt;
In the case of variable length records, it contains the highest sector actually written to and should therefore be equal to the field &amp;quot;Total number of sectors&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
For program files, 0x0000 is usually found in this field.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;NOTE&amp;#039;&amp;#039;&amp;#039;: The bytes in this field are in &amp;#039;&amp;#039;&amp;#039;reverse order&amp;#039;&amp;#039;&amp;#039; (little-endian).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Creation&amp;#039;&amp;#039;&amp;#039; and &amp;#039;&amp;#039;&amp;#039;Update time specification&amp;#039;&amp;#039;&amp;#039; are two 16-bit words, the bits having the following meaning:&lt;br /&gt;
&lt;br /&gt;
* hhhh.hmmm.mmms.ssss &lt;br /&gt;
* yyyy.yyyM.MMMd.dddd &lt;br /&gt;
&lt;br /&gt;
With only 5 bits for seconds, the timestamp has a resolution of 2 seconds. The years reach from 1970 (values 70..99) to 2069 (values 0..69).&lt;br /&gt;
&lt;br /&gt;
These bytes were reserved for future expansion, and will always be 0 on a disk formatted with the TI Disk Manager Module.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Data chain pointers&amp;#039;&amp;#039;&amp;#039; are used to refer to sections on the disk where fragments of the file will be found. There is always at least one data chain pointer; if there are more, the file is &amp;#039;&amp;#039;fragmented&amp;#039;&amp;#039;. &lt;br /&gt;
&lt;br /&gt;
Every data chain pointer consists of three bytes with two hex digits each, so we have six hex digits&lt;br /&gt;
&lt;br /&gt;
{| style=&amp;quot;width:40%; text-align:center&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| 1 &lt;br /&gt;
| 2 &lt;br /&gt;
| 3 &lt;br /&gt;
| 4 &lt;br /&gt;
| 5 &lt;br /&gt;
| 6 &lt;br /&gt;
|-&lt;br /&gt;
| M2&lt;br /&gt;
| M1 &lt;br /&gt;
| N1 &lt;br /&gt;
| M3 &lt;br /&gt;
| N3 &lt;br /&gt;
| N2 &lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
from where we get the 16-bit numbers &lt;br /&gt;
&lt;br /&gt;
* M = 0 M3 M2 M1: Sector or AU number of start of chain element&lt;br /&gt;
* N = 0 N3 N2 N1: Highest file sector count (starting at 0) for this chain element &lt;br /&gt;
&lt;br /&gt;
This may sound a bit confusing, therefore I&amp;#039;m adding an example. Here are the pointers for the data chain:&lt;br /&gt;
&lt;br /&gt;
36 10 00, 44 30 00, ac 62 00, 03 b3 00, 17 04 01 &lt;br /&gt;
&lt;br /&gt;
Using the mapping from above, we get&lt;br /&gt;
&lt;br /&gt;
 M =   0036    0044     02ac     0303      0417&lt;br /&gt;
 N =   0001    0003     0006     000b      0010&lt;br /&gt;
 &lt;br /&gt;
 Sectors&lt;br /&gt;
     0:0036  2:0044   4:02ac   7:0303   12:0417&lt;br /&gt;
     1:0037  3:0045   5:02ad   8:0304   13:0418&lt;br /&gt;
                      6:02ae   9:0305   14:0419&lt;br /&gt;
                              10:0306   15:041a&lt;br /&gt;
                              11:0307   16:041b&lt;br /&gt;
&lt;br /&gt;
This means: The file occupies the sectors &amp;gt;0036 (sector 0), &amp;gt;0037 (sector 1), &amp;gt;0044 (sector 2), ... &amp;gt;041b (sector 16). Note that N does not provide the length of the current chain element but the total number of read sectors after reading this chain element. Counting from 0, and together with the directory entry, we have&lt;br /&gt;
&lt;br /&gt;
:&amp;#039;&amp;#039;The number of sectors which a file occupies on the disk is N&amp;lt;sub&amp;gt;last&amp;lt;/sub&amp;gt;+1&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
Or you may want calculate the intervals as follows:&lt;br /&gt;
&lt;br /&gt;
: &amp;#039;&amp;#039;intv(i).start = M&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;&amp;#039;&amp;#039;&lt;br /&gt;
: &amp;#039;&amp;#039;intv(i).end = M&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; + N&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; - (N&amp;lt;sub&amp;gt;i-1&amp;lt;/sub&amp;gt; + 1)&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
with &amp;#039;&amp;#039;N&amp;lt;sub&amp;gt;-1&amp;lt;/sub&amp;gt;=-1&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
Important comment to &amp;#039;&amp;#039;&amp;#039;sector/AU usage&amp;#039;&amp;#039;&amp;#039;: For disks with capacities up to 720 KiB (2880 sectors), the M-value refers to a sector. For disks with higher capacities (1.44 or 2.88 MiB, i.e. 5760 or 11520 sectors), M-values are AUs. The N-values are always sector counts, even when the M-value points to an AU.&lt;br /&gt;
&lt;br /&gt;
=== Allocation Bit Map ===&lt;br /&gt;
&lt;br /&gt;
This map shows which sectors are occupied by files on this file system. The map is located starting at byte 0x38 and reaching to the end of this sector, so its size is 200 bytes. This means that for file systems with more than 1600 sectors, clusters must be formed which serve as &amp;#039;&amp;#039;allocation units&amp;#039;&amp;#039; (AU). &lt;br /&gt;
&lt;br /&gt;
Bits are organized in little-endian order. That is, a value of 0x01 declares the AU with the smallest number of a block of 8 to be allocated. On floppy disks with less than 1600 sectors, the allocation map is usually preset with 0x03, reserving sector 0 and sector 1.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;filesys&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| AU 7&lt;br /&gt;
| AU 6&lt;br /&gt;
| AU 5&lt;br /&gt;
| AU 4&lt;br /&gt;
| AU 3&lt;br /&gt;
| AU 2&lt;br /&gt;
| AU 1&lt;br /&gt;
| AU 0&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Disks with a capacity of at most 400 KiB have an AU size of 1 sector (all SSSD, SSDD, DSSD, DSDD, with 40 tracks, at most 1440 sectors). Disks with at most 800 KiB space have 2 sectors per AU (720 KiB, 2880 sectors), with 1.4 MiB we have 4 sectors per AU, and the disks with ultra capacity (2.8 MiB) have 8 sectors per AU.&lt;br /&gt;
&lt;br /&gt;
== Hard Disk File System ==&lt;br /&gt;
&lt;br /&gt;
The file system is defined by &lt;br /&gt;
&lt;br /&gt;
* the Volume Information Block&lt;br /&gt;
* the Directory Descriptor Records&lt;br /&gt;
* the File Descriptor Index Records&lt;br /&gt;
* the File Descriptor Records, and&lt;br /&gt;
* the Allocation Bit Map&lt;br /&gt;
&lt;br /&gt;
=== Volume Information Block (HFDC) ===&lt;br /&gt;
&lt;br /&gt;
The Volume information block (VIB) contains information about the complete file system on this volume. It is located in the first sector of the volume. The VIB takes the role both of the volume specifier and of the root directory specifier. Therefore, we find directory information as well as volume information in this block.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;filesys&amp;quot; style=&amp;quot;width:50%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 00&lt;br /&gt;
| colspan=&amp;quot;4&amp;quot; rowspan=&amp;quot;5&amp;quot; | Volume name&lt;br /&gt;
|-&lt;br /&gt;
! 02&lt;br /&gt;
|-&lt;br /&gt;
! 04&lt;br /&gt;
|-&lt;br /&gt;
! 06&lt;br /&gt;
|-&lt;br /&gt;
! 08&lt;br /&gt;
|-&lt;br /&gt;
! 0A&lt;br /&gt;
| colspan=&amp;quot;4&amp;quot; | Total number of allocation units&lt;br /&gt;
|-&lt;br /&gt;
! 0C&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Sectors/Track &lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Reserved AUs / W&lt;br /&gt;
|-&lt;br /&gt;
! 0E&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Step speed / I&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Red. write current / N&lt;br /&gt;
|-&lt;br /&gt;
! 10&lt;br /&gt;
| Sect/AU -1&lt;br /&gt;
| Heads -1&lt;br /&gt;
| BS&lt;br /&gt;
| Write precomp&lt;br /&gt;
|-&lt;br /&gt;
! 12&lt;br /&gt;
| colspan=&amp;quot;4&amp;quot; rowspan=&amp;quot;2&amp;quot;| Date and time of creation&lt;br /&gt;
|-&lt;br /&gt;
! 14&lt;br /&gt;
|-&lt;br /&gt;
! 16&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | # of files&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | # of subdirectories&lt;br /&gt;
|-&lt;br /&gt;
! 18&lt;br /&gt;
| colspan=&amp;quot;4&amp;quot; | AU number of file descriptor index record&lt;br /&gt;
|-&lt;br /&gt;
! 1A&lt;br /&gt;
| colspan=&amp;quot;4&amp;quot; | AU number of DSK1 emulation file&lt;br /&gt;
|-&lt;br /&gt;
! 1C&lt;br /&gt;
| colspan=&amp;quot;4&amp;quot; rowspan=&amp;quot;5&amp;quot; | AU numbers of subdirectories (DDR)&lt;br /&gt;
|-&lt;br /&gt;
! 1E&lt;br /&gt;
|-&lt;br /&gt;
! 20&lt;br /&gt;
|-&lt;br /&gt;
! ...&lt;br /&gt;
|-&lt;br /&gt;
! FE&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The W,I,N values may be present after initial formatting. Usually they are replaced by the given settings.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Reserved AUs&amp;#039;&amp;#039;&amp;#039; (0-255): Value is divided by 64 (0x40) and declares the start AU for file contents (0-16320). For instance, with a value of 0x15, file contents are stored at AUs 0x0540 and higher.&lt;br /&gt;
&lt;br /&gt;
There are 114 slots for pointers to subdirectories (from 0x1c to 0xff, 2 bytes each), so this is the maximum number of subdirectories in a directory.&lt;br /&gt;
&lt;br /&gt;
==== Special MFM parameters ====&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Step speed&amp;#039;&amp;#039;&amp;#039; (0-255): Controllers like the HDC9234 on the HFDC card use the value for step timing. The number is controller-specific.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Reduced write current&amp;#039;&amp;#039;&amp;#039; (0-255): Value is divided by 8 and indicates the cylinder number (0-2040) starting from which the write current in the write head is reduced.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;BS&amp;#039;&amp;#039;&amp;#039; = Buffered head stepping (one bit, 0 or 1): Some drives allow for fast stepping by increasing a step counter and then performing a fast, single seek.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Write precompensation&amp;#039;&amp;#039;&amp;#039; (0-127): Value is divided by 16 and indicates the cylinder number (0-2032) starting from which the timing of the flux writing is changed.&lt;br /&gt;
&lt;br /&gt;
=== Volume Information Block (SCSI) ===&lt;br /&gt;
&lt;br /&gt;
The Volume information block (VIB) contains information about the complete file system on this volume. It is located in the first sector of the volume. The VIB takes the role both of the volume specifier and of the root directory specifier. Therefore, we find directory information as well as volume information in this block.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;filesys&amp;quot; style=&amp;quot;width:50%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 00&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; rowspan=&amp;quot;5&amp;quot; | Volume name&lt;br /&gt;
|-&lt;br /&gt;
! 02&lt;br /&gt;
|-&lt;br /&gt;
! 04&lt;br /&gt;
|-&lt;br /&gt;
! 06&lt;br /&gt;
|-&lt;br /&gt;
! 08&lt;br /&gt;
|-&lt;br /&gt;
! 0A&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Total number of AUs&lt;br /&gt;
|-&lt;br /&gt;
! 0C&lt;br /&gt;
| Reserved &lt;br /&gt;
| Reserved AUs / W&lt;br /&gt;
|-&lt;br /&gt;
! 0E&lt;br /&gt;
| Unused / I&lt;br /&gt;
| Unused / N&lt;br /&gt;
|-&lt;br /&gt;
! 10&lt;br /&gt;
| Sectors per AU&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
! 12&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; rowspan=&amp;quot;2&amp;quot; | Date and time of creation&lt;br /&gt;
|-&lt;br /&gt;
! 14&lt;br /&gt;
|-&lt;br /&gt;
! 16&lt;br /&gt;
| # of files&lt;br /&gt;
| # of subdirectories&lt;br /&gt;
|-&lt;br /&gt;
! 18&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | AU number of file descriptor index record&lt;br /&gt;
|-&lt;br /&gt;
! 1A&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | Reserved&lt;br /&gt;
|-&lt;br /&gt;
! 1C&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; rowspan=&amp;quot;5&amp;quot; | AU numbers of subdirectories (DDR)&lt;br /&gt;
|-&lt;br /&gt;
! 1E&lt;br /&gt;
|-&lt;br /&gt;
! 20&lt;br /&gt;
|-&lt;br /&gt;
! ...&lt;br /&gt;
|-&lt;br /&gt;
! FE&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Byte &amp;gt;10 is the same as in the HFDC VIB (first 4 bits, adding 1). Thus, a value of &amp;gt;F0 means 16 sectors per AU and is the maximum number. The other four bits are not used. See above for the meaning of &amp;#039;&amp;#039;&amp;#039;Reserved AUs&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
=== Directory Descriptor Records ===&lt;br /&gt;
Each directory is defined by a directory descriptor record (DDR). It is structured similarly to a VIB. &lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;filesys&amp;quot; style=&amp;quot;width:50%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 00&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; rowspan=&amp;quot;5&amp;quot; style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | Directory name&lt;br /&gt;
|-&lt;br /&gt;
! 02&lt;br /&gt;
|-&lt;br /&gt;
! 04&lt;br /&gt;
|-&lt;br /&gt;
! 06&lt;br /&gt;
|-&lt;br /&gt;
! 08&lt;br /&gt;
|-&lt;br /&gt;
! 0A&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | HFDC: Total number of AUs / SCSI: unused&lt;br /&gt;
|-&lt;br /&gt;
! 0C&lt;br /&gt;
| style=&amp;quot;border: 1px solid black; padding:0.5ex; width:50%&amp;quot; | unused &lt;br /&gt;
| style=&amp;quot;border: 1px solid black; padding:0.5ex; width:50%&amp;quot; | &amp;quot;D&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 0E&lt;br /&gt;
| style=&amp;quot;border: 1px solid black; padding:0.5ex; width:50%&amp;quot; | &amp;quot;I&amp;quot;&lt;br /&gt;
| style=&amp;quot;border: 1px solid black; padding:0.5ex; width:50%&amp;quot; | &amp;quot;R&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 10&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | unused&lt;br /&gt;
|-&lt;br /&gt;
! 12&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; rowspan=&amp;quot;2&amp;quot; style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | Date and time of creation&lt;br /&gt;
|-&lt;br /&gt;
! 14&lt;br /&gt;
|-&lt;br /&gt;
! 16&lt;br /&gt;
| style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | # of files&lt;br /&gt;
| style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | # of subdirectories&lt;br /&gt;
|-&lt;br /&gt;
! 18&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | Pointer to file descriptor index record (FDIR)&lt;br /&gt;
|-&lt;br /&gt;
! 1A&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | Pointer to the parent directory AU&lt;br /&gt;
|-&lt;br /&gt;
! 1C&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; rowspan=&amp;quot;5&amp;quot; style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | AU numbers of subdirectories (DDR)&lt;br /&gt;
|-&lt;br /&gt;
! 1E&lt;br /&gt;
|-&lt;br /&gt;
! 20&lt;br /&gt;
|-&lt;br /&gt;
! ...&lt;br /&gt;
|-&lt;br /&gt;
! FE&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== File Descriptor Index Records ===&lt;br /&gt;
&lt;br /&gt;
As space in a DDR is limited, the files are referenced outside the DDR, using a File Decriptor Index Record (FDIR). There is one FDIR per directory. The FDIR is also responsible for the ordering of file names in the directory. That is, new files are inserted into the list, preserving alphabetical order. &lt;br /&gt;
&lt;br /&gt;
The end of the list is marked by the first null pointer. Note that the FDIR for hard disks contain pointers to &amp;#039;&amp;#039;&amp;#039;allocation units&amp;#039;&amp;#039;&amp;#039;, not to sectors.&lt;br /&gt;
&lt;br /&gt;
The last entry of the list (position &amp;gt;FE) points back to the containing directory AU. With a sector size of 256 bytes we can have 127 files per directory at most.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;filesys&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 00&lt;br /&gt;
| AU number of first file descriptor record&lt;br /&gt;
|-&lt;br /&gt;
! 02&lt;br /&gt;
| AU number of second file descriptor record&lt;br /&gt;
|-&lt;br /&gt;
! 04&lt;br /&gt;
| AU number of third file descriptor record&lt;br /&gt;
|-&lt;br /&gt;
! 06&lt;br /&gt;
| AU number of fourth file descriptor record&lt;br /&gt;
|-&lt;br /&gt;
! ...&lt;br /&gt;
|-&lt;br /&gt;
! FC&lt;br /&gt;
| AU number of 127th file descriptor record&lt;br /&gt;
|-&lt;br /&gt;
! FE&lt;br /&gt;
| AU number of directory descriptor record of this FDIR&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== File Descriptor Records ===&lt;br /&gt;
Each file is defined by a file descriptor record.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;filesys&amp;quot; style=&amp;quot;width:50%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 00&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; rowspan=&amp;quot;5&amp;quot; style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | File name&lt;br /&gt;
|-&lt;br /&gt;
! 02&lt;br /&gt;
|-&lt;br /&gt;
! 04&lt;br /&gt;
|-&lt;br /&gt;
! 06&lt;br /&gt;
|-&lt;br /&gt;
! 08&lt;br /&gt;
|-&lt;br /&gt;
! 0A&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | Extended record length&lt;br /&gt;
|-&lt;br /&gt;
! 0C&lt;br /&gt;
| style=&amp;quot;border: 1px solid black; padding:0.5ex; width:50%&amp;quot; | File status flags &lt;br /&gt;
| style=&amp;quot;border: 1px solid black; padding:0.5ex; width:50%&amp;quot; | Number of records/sec&lt;br /&gt;
|-&lt;br /&gt;
! 0E&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | Number of sectors currently allocated (also see extended information)&lt;br /&gt;
|-&lt;br /&gt;
! 10&lt;br /&gt;
| style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | End-of-file offset&lt;br /&gt;
| style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | Logical record length&lt;br /&gt;
|-&lt;br /&gt;
! 12&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | Number of level 3 records allocated (also see extended information)&lt;br /&gt;
|-&lt;br /&gt;
! 14&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; rowspan=&amp;quot;2&amp;quot; style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | Date and time of creation&lt;br /&gt;
|-&lt;br /&gt;
! 16&lt;br /&gt;
|-&lt;br /&gt;
! 18&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; rowspan=&amp;quot;2&amp;quot; style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | Date and time of last update&lt;br /&gt;
|-&lt;br /&gt;
! 1A&lt;br /&gt;
|-&lt;br /&gt;
! 1C&lt;br /&gt;
| style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | &amp;quot;F&amp;quot;&lt;br /&gt;
| style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | &amp;quot;I&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 1E&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | Pointer to previous FDR (AU)&lt;br /&gt;
|- &lt;br /&gt;
! 20&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | Pointer to next FDR (AU)&lt;br /&gt;
|- &lt;br /&gt;
! 22&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | Number of AUs allocated by this FDR for the file content &lt;br /&gt;
|- &lt;br /&gt;
! 24&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | Pointer to file descriptor index record (AU)&lt;br /&gt;
|- &lt;br /&gt;
! 26&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | Extended information (see below)&lt;br /&gt;
|-&lt;br /&gt;
! 28&lt;br /&gt;
| rowspan=&amp;quot;4&amp;quot; colspan=&amp;quot;2&amp;quot; style=&amp;quot;border: 1px solid black; padding:0.5ex&amp;quot; | Data chain pointer blocks (AU)&lt;br /&gt;
|-&lt;br /&gt;
! 2A&lt;br /&gt;
|-&lt;br /&gt;
! ...&lt;br /&gt;
|-&lt;br /&gt;
! FE&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
For hard drives, the data chain pointer blocks are structured differently. We have four bytes per chain element, two for the start AU, and two for the end AU.&lt;br /&gt;
&lt;br /&gt;
The extended information is a 16-bit word ABCD, built up as follows:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plain&amp;quot; style=&amp;quot;width:60%&amp;quot;&lt;br /&gt;
! A &lt;br /&gt;
! B &lt;br /&gt;
! C&lt;br /&gt;
! D&lt;br /&gt;
|-&lt;br /&gt;
| Number of allocated sectors / 65536&lt;br /&gt;
| Number of sectors of a VARIABLE file / 65536&lt;br /&gt;
| Sector number in AU pointing to previous FDR&lt;br /&gt;
| Sector number of AU pointing to next FDR&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The file system allows for multiple chained FIBs in case the cluster list does not fit into one FIB. Unlike the original FIB, the next FIBs may obviously be located in other sectors within an AU, not in the first one: It is possible to define which sector in the given AU actually contains the next FIB. However, this only makes sense if the first sector in the AU is from the same file; otherwise, when deleting a file, it would be impossible to decide whether an AU can be freed or not without checking each other file on the medium. &lt;br /&gt;
&lt;br /&gt;
Anyway, it should be a pretty rare case; I&amp;#039;ve never seen a file that was so heavily fragmented to require more than one FIB.&lt;br /&gt;
&lt;br /&gt;
=== Allocation Bit Map ===&lt;br /&gt;
&lt;br /&gt;
The allocation bitmap is located in sectors 1 to 31 where each bit represents one allocation unit. That is, we have&lt;br /&gt;
 31 sector * 256 byte/sector * 8 bit/byte * 1 AU/bit = 63488 AUs = &amp;gt;F800 AUs.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Caution&amp;#039;&amp;#039;&amp;#039;: The bit order in the bytes of the allocation map is &amp;#039;&amp;#039;&amp;#039;big-endian&amp;#039;&amp;#039;&amp;#039;, different to the situation with floppy disks.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;filesys&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| AU 0&lt;br /&gt;
| AU 1&lt;br /&gt;
| AU 2&lt;br /&gt;
| AU 3&lt;br /&gt;
| AU 4&lt;br /&gt;
| AU 5&lt;br /&gt;
| AU 6&lt;br /&gt;
| AU 7&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The allocation bitmap limits the maximum size of a hard drive in terms of allocation units. The maximum number of sectors in an allocation unit is determined by byte &amp;gt;10 in the VIB which leaves only 4 bits for this number (adding 1), so the maximum is 16. &lt;br /&gt;
&lt;br /&gt;
That is, we ultimately have a maximum usable space of &lt;br /&gt;
 16 sector/AU * 63488 AU * 256 byte/sector = 260046848 byte&lt;br /&gt;
&lt;br /&gt;
that is 248 MiB. Any bigger drive will stay unused beyond this point.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
&lt;br /&gt;
[1] Myarc, Inc.: &amp;#039;&amp;#039;Hard and Floppy Disk Controller / Users manual&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
[2] Western Horizon Technologies: &amp;#039;&amp;#039;Small Computer Systems Interface Host Adapter Card / Software Interface Specification&amp;#039;&amp;#039;, 1993&lt;br /&gt;
&lt;br /&gt;
[[Category:Programming]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=User_talk:Mizapf&amp;diff=50843</id>
		<title>User talk:Mizapf</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=User_talk:Mizapf&amp;diff=50843"/>
		<updated>2023-08-31T20:30:20Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Notes for further editing */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Notes for further editing ===&lt;br /&gt;
&lt;br /&gt;
* Using the TI console as an external keyboard&lt;br /&gt;
* BASIC file formats&lt;br /&gt;
&lt;br /&gt;
== Spammer ==&lt;br /&gt;
&lt;br /&gt;
[[Special:Contributions/Epergrem]]   -- blocked by me, Aug 31, 2023&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=User_talk:Mizapf&amp;diff=50842</id>
		<title>User talk:Mizapf</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=User_talk:Mizapf&amp;diff=50842"/>
		<updated>2023-08-31T20:29:54Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Spammer */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Notes for further editing ===&lt;br /&gt;
&lt;br /&gt;
* Running MESS on Raspi&lt;br /&gt;
* Using the TI console as an external keyboard&lt;br /&gt;
* BASIC file formats&lt;br /&gt;
&lt;br /&gt;
== Spammer ==&lt;br /&gt;
&lt;br /&gt;
[[Special:Contributions/Epergrem]]   -- blocked by me, Aug 31, 2023&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=MESS_Troubleshooting&amp;diff=50709</id>
		<title>MESS Troubleshooting</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=MESS_Troubleshooting&amp;diff=50709"/>
		<updated>2023-02-24T20:24:31Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== On startup ==&lt;br /&gt;
&lt;br /&gt;
=== Error message concerning shared object file (Linux) ===&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Q: When trying to start mess I&amp;#039;m getting an error message &amp;quot;error while loading shared libraries: libSDL_ttf-2.0.so.0: cannot open shared object file: No such file or directory&amp;quot;.&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
A: You must install SDL support, in this case the TTF library is missing. Check your software installation and add the required libraries.&lt;br /&gt;
&lt;br /&gt;
=== Screen is black ===&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Q: The system does not start. The screen remains black.&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
A1 &amp;#039;&amp;#039;&amp;#039;(GRAMKracker)&amp;#039;&amp;#039;&amp;#039;: You have activated the GRAMKracker support, you have set the GK DIP switches to point to GRAM 0 (instead of OpSys), and you have not inserted the GRAMKracker cartridge, or the cartridge is empty. Please read the GK manual.&lt;br /&gt;
&lt;br /&gt;
A2 &amp;#039;&amp;#039;&amp;#039;(HSGPL)&amp;#039;&amp;#039;&amp;#039;: You switched on the [[HSGPL]] without previously uploading an operating system to the HSGPL. As the HSGPL is always active for the SGCPU, you will encounter the same problem there. &lt;br /&gt;
&lt;br /&gt;
The HSGPL is a buffered memory card which replaces the console&amp;#039;s ROM and GROM content. When you turn it on, MESS automatically unplugs all memory in the console until you turn off the card again. When you buy an HSGPL card you get a preloaded system. To achieve this in MESS, you have to set the HSGPL configuration switch to &amp;quot;Flash&amp;quot;. This allows access to the card but prevents it from taking control. Now you can use a tool like DSRLDR to flash the (emulated) ROMs on the card using a ROM package. When done, you must copy the desired operating system version from the backup positions in the card to the location expected by the system (ROM and GROM). I have written a simple tool to perform this step. Finally, put the switch to the &amp;quot;on&amp;quot; position and reset the emulator.&lt;br /&gt;
&lt;br /&gt;
The HSGPL was sold by the System 99&amp;#039;er User Group ([[SNUG]]), so you should ask them to provide you with a suitable ROM package.&lt;br /&gt;
&lt;br /&gt;
=== Error loading multicart: no pcb found ===&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Q: I&amp;#039;m getting this message on startup.&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
A: You seem to have attempted to mount a cartridge using the (long obsoleted) &amp;quot;bin&amp;quot; format. Please use only [[MESS multicart system | RPK packages]] as cartridges. You can find almost all known cartridges on the FTP server of whtech. &lt;br /&gt;
&lt;br /&gt;
== During runtime ==&lt;br /&gt;
&lt;br /&gt;
=== RS232/PIO output hangs ===&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Q: When I try to output a file through RS232 or PIO, the computer seems to hang.&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
A: You have probably forgotten to &amp;quot;insert&amp;quot; an image into the RS232 or PIO device. Check the file manager and define an output file (similar to inserting a disk image into a drive). When printing to PIO or RS232, the contents are appended to the image file.&lt;br /&gt;
&lt;br /&gt;
[[Category:MESS]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=MAME&amp;diff=50708</id>
		<title>MAME</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=MAME&amp;diff=50708"/>
		<updated>2023-02-24T20:22:51Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Using */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Image:mess_logo_n.png|150px|right]]&lt;br /&gt;
MESS (Multiple Emulator Super System) is a multi-system emulator which emulates more than 400 computer systems, many of them from the good old days of the Home Computers. Among them are also a number of TI platforms:&lt;br /&gt;
&lt;br /&gt;
* TI-99/2 (24K and 32K ROM version)&lt;br /&gt;
* TI-99/4, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/4A, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/4QI, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/8&lt;br /&gt;
* Geneve 9640&lt;br /&gt;
&lt;br /&gt;
A number of peripherals are also emulated, ranging from various disk controllers (up to HFDC), to serial interfaces, USB interface, IDE interface, HSGPL, and also SGCPU.&lt;br /&gt;
&lt;br /&gt;
The emulator is freely downloadable from [http://www.mamedev.org/release.html mamedev.org] as a pre-built binary for Windows systems, and as a source zip archive for all platforms. &lt;br /&gt;
&lt;br /&gt;
You can download and work on the MAME source codes, since all MAME components have been relicenced under an open-source licence (BSD or GPL). If you want to contribute, you should get in contact with the maintainers, though. A good location for that is the [http://forums.bannister.org/ubbthreads.php?ubb=postlist&amp;amp;Board=1&amp;amp;page=1 MESS message board].&lt;br /&gt;
&lt;br /&gt;
I would be happy to see more people from the TI community helping to improve the TI emulation in MESS. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; margin-top:2em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;float:left; padding:1em; height: 35em; width:45%; background-color:#ddddee&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Installing and setup==&lt;br /&gt;
&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame/buildmame Building MAME] from source&lt;br /&gt;
* [https://www.mizapf.de/ti99/mame/winstmame Installing on Windows systems]&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame/ Getting the ROMs]&lt;br /&gt;
* [[Setting up the HSGPL for MAME]] &lt;br /&gt;
* [[Setting up the Horizon Ramdisk]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-left:50%; padding:1em; height: 35em;width:45%; background-color:#ddeedd&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Using==&lt;br /&gt;
&lt;br /&gt;
* [https://www.ninermame.org/info/scope Features of the TI and Geneve emulation in MAME]&lt;br /&gt;
* [[MAME TI emulation usage|Working with the TI/Geneve emulation]]&lt;br /&gt;
* [[MESS and TI cartridges|Cartridges]]&lt;br /&gt;
* [[MESS media handling|Media handling]]&lt;br /&gt;
** [[MESS media handling#Using_your_TI_and_Geneve_hard_drive_in_MESS|Using your TI/Geneve hard drive in MESS]]&lt;br /&gt;
** [[MESS media handling#Formatting_disks|Formatting disks]]&lt;br /&gt;
** [[MESS media handling#Setting_up_a_blank_hard_disk|Setting up a blank hard disk]]&lt;br /&gt;
** [[MESS media handling#Upgrading_to_new_CHD_format|Upgrading CHD version]]&lt;br /&gt;
** [[MESS media handling#IDE disk handling|Using the IDE HD controller]]&lt;br /&gt;
* [[MESS debugger|Built-in debugger]]&lt;br /&gt;
* [https://www.mizapf.de/ti99/timt TIImageTool] - a GUI-based image manager&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; height:1em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;float:left; padding:1em; height: 30em;width:45%; background-color:#eedddd&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Questions and Troubleshooting==&lt;br /&gt;
&lt;br /&gt;
* Some help for [[MESS Troubleshooting]].&lt;br /&gt;
* [[MESSFAQ | Frequently Asked Questions]] related to the TI/Geneve emulation in MESS.&lt;br /&gt;
* [https://www.ninermame.org/using/keyboard/uimodekey Change MESS menu mode key] from Scroll Lock to another key&lt;br /&gt;
* Hassle with the [[MESS keyboard modes]] &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;padding:1em; margin-left:50%; height: 30em;width:45%; background-color:#ddeeee&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
* Basics: [[Emulation and Simulation]]&lt;br /&gt;
* [[Guided tour through MESS|Get a look under the hood of MESS]]&lt;br /&gt;
* The [[MESS cartridge handling|cartridge system]] of MESS&lt;br /&gt;
* Programmer&amp;#039;s Guide to MESS&lt;br /&gt;
* [https://www.mizapf.de/ti99/mame/changes MAME version history] with features, fixes, and regressions list&lt;br /&gt;
* [https://www.ninermame.org/details/serialconn RS232 emulation]&lt;br /&gt;
* [[MAME Internals]]&lt;br /&gt;
* [[MAME Floppy sound emulation | Floppy sound emulation]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; margin-bottom:2em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;br /&gt;
&lt;br /&gt;
[[Category:Emulation]]&lt;br /&gt;
[[Category:MAME]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=MAME&amp;diff=50707</id>
		<title>MAME</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=MAME&amp;diff=50707"/>
		<updated>2023-02-24T20:21:52Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Questions and Troubleshooting */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Image:mess_logo_n.png|150px|right]]&lt;br /&gt;
MESS (Multiple Emulator Super System) is a multi-system emulator which emulates more than 400 computer systems, many of them from the good old days of the Home Computers. Among them are also a number of TI platforms:&lt;br /&gt;
&lt;br /&gt;
* TI-99/2 (24K and 32K ROM version)&lt;br /&gt;
* TI-99/4, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/4A, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/4QI, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/8&lt;br /&gt;
* Geneve 9640&lt;br /&gt;
&lt;br /&gt;
A number of peripherals are also emulated, ranging from various disk controllers (up to HFDC), to serial interfaces, USB interface, IDE interface, HSGPL, and also SGCPU.&lt;br /&gt;
&lt;br /&gt;
The emulator is freely downloadable from [http://www.mamedev.org/release.html mamedev.org] as a pre-built binary for Windows systems, and as a source zip archive for all platforms. &lt;br /&gt;
&lt;br /&gt;
You can download and work on the MAME source codes, since all MAME components have been relicenced under an open-source licence (BSD or GPL). If you want to contribute, you should get in contact with the maintainers, though. A good location for that is the [http://forums.bannister.org/ubbthreads.php?ubb=postlist&amp;amp;Board=1&amp;amp;page=1 MESS message board].&lt;br /&gt;
&lt;br /&gt;
I would be happy to see more people from the TI community helping to improve the TI emulation in MESS. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; margin-top:2em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;float:left; padding:1em; height: 35em; width:45%; background-color:#ddddee&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Installing and setup==&lt;br /&gt;
&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame/buildmame Building MAME] from source&lt;br /&gt;
* [https://www.mizapf.de/ti99/mame/winstmame Installing on Windows systems]&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame/ Getting the ROMs]&lt;br /&gt;
* [[Setting up the HSGPL for MAME]] &lt;br /&gt;
* [[Setting up the Horizon Ramdisk]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-left:50%; padding:1em; height: 35em;width:45%; background-color:#ddeedd&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Using==&lt;br /&gt;
&lt;br /&gt;
* [https://www.ninermame.org/info/scope Features of the TI and Geneve emulation in MAME]&lt;br /&gt;
* [[MAME TI emulation usage|Working with the TI/Geneve emulation]]&lt;br /&gt;
* [[Tools for MESS|Tools]]&lt;br /&gt;
* [[MESS and TI cartridges|Cartridges]]&lt;br /&gt;
* [[MESS media handling|Media handling]]&lt;br /&gt;
** [[MESS media handling#Using_your_TI_and_Geneve_hard_drive_in_MESS|Using your TI/Geneve hard drive in MESS]]&lt;br /&gt;
** [[MESS media handling#Formatting_disks|Formatting disks]]&lt;br /&gt;
** [[MESS media handling#Setting_up_a_blank_hard_disk|Setting up a blank hard disk]]&lt;br /&gt;
** [[MESS media handling#Upgrading_to_new_CHD_format|Upgrading CHD version]]&lt;br /&gt;
** [[MESS media handling#IDE disk handling|Using the IDE HD controller]]&lt;br /&gt;
* [[MESS debugger|Built-in debugger]]&lt;br /&gt;
* [https://www.mizapf.de/ti99/timt TIImageTool] - a GUI-based image manager&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; height:1em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;float:left; padding:1em; height: 30em;width:45%; background-color:#eedddd&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Questions and Troubleshooting==&lt;br /&gt;
&lt;br /&gt;
* Some help for [[MESS Troubleshooting]].&lt;br /&gt;
* [[MESSFAQ | Frequently Asked Questions]] related to the TI/Geneve emulation in MESS.&lt;br /&gt;
* [https://www.ninermame.org/using/keyboard/uimodekey Change MESS menu mode key] from Scroll Lock to another key&lt;br /&gt;
* Hassle with the [[MESS keyboard modes]] &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;padding:1em; margin-left:50%; height: 30em;width:45%; background-color:#ddeeee&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
* Basics: [[Emulation and Simulation]]&lt;br /&gt;
* [[Guided tour through MESS|Get a look under the hood of MESS]]&lt;br /&gt;
* The [[MESS cartridge handling|cartridge system]] of MESS&lt;br /&gt;
* Programmer&amp;#039;s Guide to MESS&lt;br /&gt;
* [https://www.mizapf.de/ti99/mame/changes MAME version history] with features, fixes, and regressions list&lt;br /&gt;
* [https://www.ninermame.org/details/serialconn RS232 emulation]&lt;br /&gt;
* [[MAME Internals]]&lt;br /&gt;
* [[MAME Floppy sound emulation | Floppy sound emulation]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; margin-bottom:2em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;br /&gt;
&lt;br /&gt;
[[Category:Emulation]]&lt;br /&gt;
[[Category:MAME]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=MAME&amp;diff=50706</id>
		<title>MAME</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=MAME&amp;diff=50706"/>
		<updated>2023-02-24T20:20:22Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Details */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Image:mess_logo_n.png|150px|right]]&lt;br /&gt;
MESS (Multiple Emulator Super System) is a multi-system emulator which emulates more than 400 computer systems, many of them from the good old days of the Home Computers. Among them are also a number of TI platforms:&lt;br /&gt;
&lt;br /&gt;
* TI-99/2 (24K and 32K ROM version)&lt;br /&gt;
* TI-99/4, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/4A, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/4QI, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/8&lt;br /&gt;
* Geneve 9640&lt;br /&gt;
&lt;br /&gt;
A number of peripherals are also emulated, ranging from various disk controllers (up to HFDC), to serial interfaces, USB interface, IDE interface, HSGPL, and also SGCPU.&lt;br /&gt;
&lt;br /&gt;
The emulator is freely downloadable from [http://www.mamedev.org/release.html mamedev.org] as a pre-built binary for Windows systems, and as a source zip archive for all platforms. &lt;br /&gt;
&lt;br /&gt;
You can download and work on the MAME source codes, since all MAME components have been relicenced under an open-source licence (BSD or GPL). If you want to contribute, you should get in contact with the maintainers, though. A good location for that is the [http://forums.bannister.org/ubbthreads.php?ubb=postlist&amp;amp;Board=1&amp;amp;page=1 MESS message board].&lt;br /&gt;
&lt;br /&gt;
I would be happy to see more people from the TI community helping to improve the TI emulation in MESS. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; margin-top:2em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;float:left; padding:1em; height: 35em; width:45%; background-color:#ddddee&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Installing and setup==&lt;br /&gt;
&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame/buildmame Building MAME] from source&lt;br /&gt;
* [https://www.mizapf.de/ti99/mame/winstmame Installing on Windows systems]&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame/ Getting the ROMs]&lt;br /&gt;
* [[Setting up the HSGPL for MAME]] &lt;br /&gt;
* [[Setting up the Horizon Ramdisk]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-left:50%; padding:1em; height: 35em;width:45%; background-color:#ddeedd&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Using==&lt;br /&gt;
&lt;br /&gt;
* [https://www.ninermame.org/info/scope Features of the TI and Geneve emulation in MAME]&lt;br /&gt;
* [[MAME TI emulation usage|Working with the TI/Geneve emulation]]&lt;br /&gt;
* [[Tools for MESS|Tools]]&lt;br /&gt;
* [[MESS and TI cartridges|Cartridges]]&lt;br /&gt;
* [[MESS media handling|Media handling]]&lt;br /&gt;
** [[MESS media handling#Using_your_TI_and_Geneve_hard_drive_in_MESS|Using your TI/Geneve hard drive in MESS]]&lt;br /&gt;
** [[MESS media handling#Formatting_disks|Formatting disks]]&lt;br /&gt;
** [[MESS media handling#Setting_up_a_blank_hard_disk|Setting up a blank hard disk]]&lt;br /&gt;
** [[MESS media handling#Upgrading_to_new_CHD_format|Upgrading CHD version]]&lt;br /&gt;
** [[MESS media handling#IDE disk handling|Using the IDE HD controller]]&lt;br /&gt;
* [[MESS debugger|Built-in debugger]]&lt;br /&gt;
* [https://www.mizapf.de/ti99/timt TIImageTool] - a GUI-based image manager&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; height:1em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;float:left; padding:1em; height: 30em;width:45%; background-color:#eedddd&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Questions and Troubleshooting==&lt;br /&gt;
&lt;br /&gt;
* Some help for [[MESS Troubleshooting]].&lt;br /&gt;
* [[MESSFAQ | Frequently Asked Questions]] related to the TI/Geneve emulation in MESS.&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame Change MESS menu mode key] from Scroll Lock to another key&lt;br /&gt;
* Hassle with the [[MESS keyboard modes]] &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;padding:1em; margin-left:50%; height: 30em;width:45%; background-color:#ddeeee&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
* Basics: [[Emulation and Simulation]]&lt;br /&gt;
* [[Guided tour through MESS|Get a look under the hood of MESS]]&lt;br /&gt;
* The [[MESS cartridge handling|cartridge system]] of MESS&lt;br /&gt;
* Programmer&amp;#039;s Guide to MESS&lt;br /&gt;
* [https://www.mizapf.de/ti99/mame/changes MAME version history] with features, fixes, and regressions list&lt;br /&gt;
* [https://www.ninermame.org/details/serialconn RS232 emulation]&lt;br /&gt;
* [[MAME Internals]]&lt;br /&gt;
* [[MAME Floppy sound emulation | Floppy sound emulation]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; margin-bottom:2em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;br /&gt;
&lt;br /&gt;
[[Category:Emulation]]&lt;br /&gt;
[[Category:MAME]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=MAME&amp;diff=50705</id>
		<title>MAME</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=MAME&amp;diff=50705"/>
		<updated>2023-02-24T20:19:50Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Details */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Image:mess_logo_n.png|150px|right]]&lt;br /&gt;
MESS (Multiple Emulator Super System) is a multi-system emulator which emulates more than 400 computer systems, many of them from the good old days of the Home Computers. Among them are also a number of TI platforms:&lt;br /&gt;
&lt;br /&gt;
* TI-99/2 (24K and 32K ROM version)&lt;br /&gt;
* TI-99/4, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/4A, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/4QI, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/8&lt;br /&gt;
* Geneve 9640&lt;br /&gt;
&lt;br /&gt;
A number of peripherals are also emulated, ranging from various disk controllers (up to HFDC), to serial interfaces, USB interface, IDE interface, HSGPL, and also SGCPU.&lt;br /&gt;
&lt;br /&gt;
The emulator is freely downloadable from [http://www.mamedev.org/release.html mamedev.org] as a pre-built binary for Windows systems, and as a source zip archive for all platforms. &lt;br /&gt;
&lt;br /&gt;
You can download and work on the MAME source codes, since all MAME components have been relicenced under an open-source licence (BSD or GPL). If you want to contribute, you should get in contact with the maintainers, though. A good location for that is the [http://forums.bannister.org/ubbthreads.php?ubb=postlist&amp;amp;Board=1&amp;amp;page=1 MESS message board].&lt;br /&gt;
&lt;br /&gt;
I would be happy to see more people from the TI community helping to improve the TI emulation in MESS. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; margin-top:2em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;float:left; padding:1em; height: 35em; width:45%; background-color:#ddddee&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Installing and setup==&lt;br /&gt;
&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame/buildmame Building MAME] from source&lt;br /&gt;
* [https://www.mizapf.de/ti99/mame/winstmame Installing on Windows systems]&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame/ Getting the ROMs]&lt;br /&gt;
* [[Setting up the HSGPL for MAME]] &lt;br /&gt;
* [[Setting up the Horizon Ramdisk]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-left:50%; padding:1em; height: 35em;width:45%; background-color:#ddeedd&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Using==&lt;br /&gt;
&lt;br /&gt;
* [https://www.ninermame.org/info/scope Features of the TI and Geneve emulation in MAME]&lt;br /&gt;
* [[MAME TI emulation usage|Working with the TI/Geneve emulation]]&lt;br /&gt;
* [[Tools for MESS|Tools]]&lt;br /&gt;
* [[MESS and TI cartridges|Cartridges]]&lt;br /&gt;
* [[MESS media handling|Media handling]]&lt;br /&gt;
** [[MESS media handling#Using_your_TI_and_Geneve_hard_drive_in_MESS|Using your TI/Geneve hard drive in MESS]]&lt;br /&gt;
** [[MESS media handling#Formatting_disks|Formatting disks]]&lt;br /&gt;
** [[MESS media handling#Setting_up_a_blank_hard_disk|Setting up a blank hard disk]]&lt;br /&gt;
** [[MESS media handling#Upgrading_to_new_CHD_format|Upgrading CHD version]]&lt;br /&gt;
** [[MESS media handling#IDE disk handling|Using the IDE HD controller]]&lt;br /&gt;
* [[MESS debugger|Built-in debugger]]&lt;br /&gt;
* [https://www.mizapf.de/ti99/timt TIImageTool] - a GUI-based image manager&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; height:1em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;float:left; padding:1em; height: 30em;width:45%; background-color:#eedddd&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Questions and Troubleshooting==&lt;br /&gt;
&lt;br /&gt;
* Some help for [[MESS Troubleshooting]].&lt;br /&gt;
* [[MESSFAQ | Frequently Asked Questions]] related to the TI/Geneve emulation in MESS.&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame Change MESS menu mode key] from Scroll Lock to another key&lt;br /&gt;
* Hassle with the [[MESS keyboard modes]] &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;padding:1em; margin-left:50%; height: 30em;width:45%; background-color:#ddeeee&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
* Basics: [[Emulation and Simulation]]&lt;br /&gt;
* [[Guided tour through MESS|Get a look under the hood of MESS]]&lt;br /&gt;
* The [[MESS cartridge handling|cartridge system]] of MESS&lt;br /&gt;
* Programmer&amp;#039;s Guide to MESS&lt;br /&gt;
* [https://www.mizapf.de/ti99/mame/changes MAME version history] with features, fixes, and regressions list&lt;br /&gt;
* [[MESS issues and non-issues | MESS issues]]&lt;br /&gt;
* [https://www.ninermame.org/details/serialconn RS232 emulation]&lt;br /&gt;
* [[MAME Internals]]&lt;br /&gt;
* [[MAME Floppy sound emulation | Floppy sound emulation]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; margin-bottom:2em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;br /&gt;
&lt;br /&gt;
[[Category:Emulation]]&lt;br /&gt;
[[Category:MAME]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=MAME&amp;diff=50704</id>
		<title>MAME</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=MAME&amp;diff=50704"/>
		<updated>2023-02-24T20:19:01Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Using */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Image:mess_logo_n.png|150px|right]]&lt;br /&gt;
MESS (Multiple Emulator Super System) is a multi-system emulator which emulates more than 400 computer systems, many of them from the good old days of the Home Computers. Among them are also a number of TI platforms:&lt;br /&gt;
&lt;br /&gt;
* TI-99/2 (24K and 32K ROM version)&lt;br /&gt;
* TI-99/4, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/4A, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/4QI, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/8&lt;br /&gt;
* Geneve 9640&lt;br /&gt;
&lt;br /&gt;
A number of peripherals are also emulated, ranging from various disk controllers (up to HFDC), to serial interfaces, USB interface, IDE interface, HSGPL, and also SGCPU.&lt;br /&gt;
&lt;br /&gt;
The emulator is freely downloadable from [http://www.mamedev.org/release.html mamedev.org] as a pre-built binary for Windows systems, and as a source zip archive for all platforms. &lt;br /&gt;
&lt;br /&gt;
You can download and work on the MAME source codes, since all MAME components have been relicenced under an open-source licence (BSD or GPL). If you want to contribute, you should get in contact with the maintainers, though. A good location for that is the [http://forums.bannister.org/ubbthreads.php?ubb=postlist&amp;amp;Board=1&amp;amp;page=1 MESS message board].&lt;br /&gt;
&lt;br /&gt;
I would be happy to see more people from the TI community helping to improve the TI emulation in MESS. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; margin-top:2em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;float:left; padding:1em; height: 35em; width:45%; background-color:#ddddee&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Installing and setup==&lt;br /&gt;
&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame/buildmame Building MAME] from source&lt;br /&gt;
* [https://www.mizapf.de/ti99/mame/winstmame Installing on Windows systems]&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame/ Getting the ROMs]&lt;br /&gt;
* [[Setting up the HSGPL for MAME]] &lt;br /&gt;
* [[Setting up the Horizon Ramdisk]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-left:50%; padding:1em; height: 35em;width:45%; background-color:#ddeedd&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Using==&lt;br /&gt;
&lt;br /&gt;
* [https://www.ninermame.org/info/scope Features of the TI and Geneve emulation in MAME]&lt;br /&gt;
* [[MAME TI emulation usage|Working with the TI/Geneve emulation]]&lt;br /&gt;
* [[Tools for MESS|Tools]]&lt;br /&gt;
* [[MESS and TI cartridges|Cartridges]]&lt;br /&gt;
* [[MESS media handling|Media handling]]&lt;br /&gt;
** [[MESS media handling#Using_your_TI_and_Geneve_hard_drive_in_MESS|Using your TI/Geneve hard drive in MESS]]&lt;br /&gt;
** [[MESS media handling#Formatting_disks|Formatting disks]]&lt;br /&gt;
** [[MESS media handling#Setting_up_a_blank_hard_disk|Setting up a blank hard disk]]&lt;br /&gt;
** [[MESS media handling#Upgrading_to_new_CHD_format|Upgrading CHD version]]&lt;br /&gt;
** [[MESS media handling#IDE disk handling|Using the IDE HD controller]]&lt;br /&gt;
* [[MESS debugger|Built-in debugger]]&lt;br /&gt;
* [https://www.mizapf.de/ti99/timt TIImageTool] - a GUI-based image manager&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; height:1em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;float:left; padding:1em; height: 30em;width:45%; background-color:#eedddd&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Questions and Troubleshooting==&lt;br /&gt;
&lt;br /&gt;
* Some help for [[MESS Troubleshooting]].&lt;br /&gt;
* [[MESSFAQ | Frequently Asked Questions]] related to the TI/Geneve emulation in MESS.&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame Change MESS menu mode key] from Scroll Lock to another key&lt;br /&gt;
* Hassle with the [[MESS keyboard modes]] &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;padding:1em; margin-left:50%; height: 30em;width:45%; background-color:#ddeeee&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
* Basics: [[Emulation and Simulation]]&lt;br /&gt;
* [[Guided tour through MESS|Get a look under the hood of MESS]] &amp;lt;span style=&amp;quot;font-weight:bold; color:red&amp;quot;&amp;gt;NEW&amp;lt;/span&amp;gt;&lt;br /&gt;
* The [[MESS cartridge handling|cartridge system]] of MESS&lt;br /&gt;
* Programmer&amp;#039;s Guide to MESS&lt;br /&gt;
* [https://www.mizapf.de/ti99/mame/changes MAME version history] with features, fixes, and regressions list&lt;br /&gt;
* [[MESS issues and non-issues | MESS issues]]&lt;br /&gt;
* [https://www.ninermame.org/details/serialconn RS232 emulation]&lt;br /&gt;
* [[MAME Internals]]&lt;br /&gt;
* [[MAME Floppy sound emulation | Floppy sound emulation]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; margin-bottom:2em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;br /&gt;
&lt;br /&gt;
[[Category:Emulation]]&lt;br /&gt;
[[Category:MAME]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=MAME&amp;diff=50703</id>
		<title>MAME</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=MAME&amp;diff=50703"/>
		<updated>2023-02-24T20:17:35Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Installing and setup */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Image:mess_logo_n.png|150px|right]]&lt;br /&gt;
MESS (Multiple Emulator Super System) is a multi-system emulator which emulates more than 400 computer systems, many of them from the good old days of the Home Computers. Among them are also a number of TI platforms:&lt;br /&gt;
&lt;br /&gt;
* TI-99/2 (24K and 32K ROM version)&lt;br /&gt;
* TI-99/4, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/4A, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/4QI, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/8&lt;br /&gt;
* Geneve 9640&lt;br /&gt;
&lt;br /&gt;
A number of peripherals are also emulated, ranging from various disk controllers (up to HFDC), to serial interfaces, USB interface, IDE interface, HSGPL, and also SGCPU.&lt;br /&gt;
&lt;br /&gt;
The emulator is freely downloadable from [http://www.mamedev.org/release.html mamedev.org] as a pre-built binary for Windows systems, and as a source zip archive for all platforms. &lt;br /&gt;
&lt;br /&gt;
You can download and work on the MAME source codes, since all MAME components have been relicenced under an open-source licence (BSD or GPL). If you want to contribute, you should get in contact with the maintainers, though. A good location for that is the [http://forums.bannister.org/ubbthreads.php?ubb=postlist&amp;amp;Board=1&amp;amp;page=1 MESS message board].&lt;br /&gt;
&lt;br /&gt;
I would be happy to see more people from the TI community helping to improve the TI emulation in MESS. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; margin-top:2em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;float:left; padding:1em; height: 35em; width:45%; background-color:#ddddee&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Installing and setup==&lt;br /&gt;
&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame/buildmame Building MAME] from source&lt;br /&gt;
* [https://www.mizapf.de/ti99/mame/winstmame Installing on Windows systems]&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame/ Getting the ROMs]&lt;br /&gt;
* [[Setting up the HSGPL for MAME]] &lt;br /&gt;
* [[Setting up the Horizon Ramdisk]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-left:50%; padding:1em; height: 35em;width:45%; background-color:#ddeedd&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Using==&lt;br /&gt;
&lt;br /&gt;
* [http://mess.redump.net/mess:howto The Official MESS User&amp;#039;s Manual]&lt;br /&gt;
* [[General MESS features]]&lt;br /&gt;
* [[Features of the TI and Geneve emulation in MESS]]&lt;br /&gt;
* [[MAME TI emulation usage|Working with the TI/Geneve emulation]]&lt;br /&gt;
* [[Tools for MESS|Tools]]&lt;br /&gt;
* [[MESS and TI cartridges|Cartridges]]&lt;br /&gt;
* [[MESS media handling|Media handling]]&lt;br /&gt;
** [[MESS media handling#Using_your_TI_and_Geneve_hard_drive_in_MESS|Using your TI/Geneve hard drive in MESS]]&lt;br /&gt;
** [[MESS media handling#Formatting_disks|Formatting disks]]&lt;br /&gt;
** [[MESS media handling#Setting_up_a_blank_hard_disk|Setting up a blank hard disk]]&lt;br /&gt;
** [[MESS media handling#Upgrading_to_new_CHD_format|Upgrading CHD version]]&lt;br /&gt;
** [[MESS media handling#IDE disk handling|Using the IDE HD controller]]&lt;br /&gt;
* [[MESS debugger|Built-in debugger]]&lt;br /&gt;
* [https://www.mizapf.de/ti99/timt TIImageTool] - a GUI-based image manager&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; height:1em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;float:left; padding:1em; height: 30em;width:45%; background-color:#eedddd&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Questions and Troubleshooting==&lt;br /&gt;
&lt;br /&gt;
* Some help for [[MESS Troubleshooting]].&lt;br /&gt;
* [[MESSFAQ | Frequently Asked Questions]] related to the TI/Geneve emulation in MESS.&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame Change MESS menu mode key] from Scroll Lock to another key&lt;br /&gt;
* Hassle with the [[MESS keyboard modes]] &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;padding:1em; margin-left:50%; height: 30em;width:45%; background-color:#ddeeee&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
* Basics: [[Emulation and Simulation]]&lt;br /&gt;
* [[Guided tour through MESS|Get a look under the hood of MESS]] &amp;lt;span style=&amp;quot;font-weight:bold; color:red&amp;quot;&amp;gt;NEW&amp;lt;/span&amp;gt;&lt;br /&gt;
* The [[MESS cartridge handling|cartridge system]] of MESS&lt;br /&gt;
* Programmer&amp;#039;s Guide to MESS&lt;br /&gt;
* [https://www.mizapf.de/ti99/mame/changes MAME version history] with features, fixes, and regressions list&lt;br /&gt;
* [[MESS issues and non-issues | MESS issues]]&lt;br /&gt;
* [https://www.ninermame.org/details/serialconn RS232 emulation]&lt;br /&gt;
* [[MAME Internals]]&lt;br /&gt;
* [[MAME Floppy sound emulation | Floppy sound emulation]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; margin-bottom:2em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;br /&gt;
&lt;br /&gt;
[[Category:Emulation]]&lt;br /&gt;
[[Category:MAME]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Technical_details&amp;diff=50702</id>
		<title>Technical details</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Technical_details&amp;diff=50702"/>
		<updated>2023-02-24T20:10:36Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;On this page I collect some findings that I have not yet put into specific categories (but which should be available nevertheless).&lt;br /&gt;
&lt;br /&gt;
== Communication with devices ==&lt;br /&gt;
&lt;br /&gt;
* Here is a commented log when [[SSSD Format Log|formatting a SSSD floppy disk]] with the TI disk controller. Actually, this dump was achieved using MESS and putting in some printf lines.&lt;br /&gt;
&lt;br /&gt;
* [[Hard disk track layout]] when created with the HFDC&lt;br /&gt;
&lt;br /&gt;
== Details about the chipset ==&lt;br /&gt;
&lt;br /&gt;
* [[Programmable Systems Interface TMS 9901]]&lt;br /&gt;
&lt;br /&gt;
== Determine rate register values for TMS9902 ==&lt;br /&gt;
&lt;br /&gt;
The TMS9902 UART (serial interface) is used on various RS232 controller cards. The baud rate is not set by providing some integer number but by configuring a clock divider and multiplier. The baud rate cannot therefore be set precisely but only approximately.&lt;br /&gt;
&lt;br /&gt;
The following illustration shows these approximations, the setting of the clock divider of and the multiplier, and the rate register values below the lines. The favorite values are printed in bold, and the corresponding hexadecimal rate register value is shown in blue below.&lt;br /&gt;
&lt;br /&gt;
[[File:baudrates.png|600px]]&lt;br /&gt;
&lt;br /&gt;
== TI-99/4A chipset and components ==&lt;br /&gt;
&lt;br /&gt;
=== TMS9900 processor ===&lt;br /&gt;
&lt;br /&gt;
Input/output operations are performed by the [[Communications Register Unit]].&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;TODO: Continue&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
=== Scratch Pad RAM ===&lt;br /&gt;
&amp;quot;Where is scratch pad RAM? - Scratch pad RAM is the ONLY area of RAM addressed with a 16 bit data line. It is FAST. &lt;br /&gt;
&lt;br /&gt;
It can be found from &amp;gt;8300 to &amp;gt;83FE, and parts of it are used by the operating system. (Note: The symbol &amp;gt; means the number following is in hexadecimal format). &lt;br /&gt;
&lt;br /&gt;
Different bits are used depending on how you are using the console! In case you thought EASY BUG was part of the Mini Memory module- yes, it is in the module, but it does not RUN from there- for greater speed it is transferred to the scratch pad area, where it resides from &amp;gt;8370 to &amp;gt;83FF &lt;br /&gt;
&lt;br /&gt;
&amp;gt;8300 to &amp;gt;8348 IS USED BY BASIC (XB) and so seems to be a reasonable place to put a pure machine code utility. &lt;br /&gt;
&lt;br /&gt;
&amp;gt;834A to &amp;gt;83FE seems to be fully utilised for most environments.&lt;br /&gt;
&lt;br /&gt;
== Setting up RXTX for Java ==&lt;br /&gt;
&lt;br /&gt;
RXTX is a serial and parallel port API implementation which is available for Windows, MacOS, and Unix systems, as binary and source code files. The complete documentation and the download location is at http://rxtx.qbang.org/wiki/index.php/Main_Page. You should build the binaries for 64 bit systems from the sources.&lt;br /&gt;
&lt;br /&gt;
=== Instructions for Windows ===&lt;br /&gt;
&lt;br /&gt;
Download the RXTX binaries package from http://rxtx.qbang.org/wiki/index.php/Download. Version 2.1-7 is OK. You now have to copy two files manually into the correct folders.&lt;br /&gt;
&lt;br /&gt;
* Locate RXTXComm.jar in the ZIP file.&lt;br /&gt;
* Copy this file into your Java installation folder. This should be at &amp;#039;&amp;#039;&amp;#039;C:\Program Files(x86)\Java\jre6&amp;#039;&amp;#039;&amp;#039; (or a similar name; can&amp;#039;t tell what version you have installed). Copy the file into the subfolder &amp;#039;&amp;#039;&amp;#039;lib\ext&amp;#039;&amp;#039;&amp;#039;. You may find some other JAR files there; in that case you are at the right location.&lt;br /&gt;
* Locate rxtxSerial.dll in the ZIP file.&lt;br /&gt;
* Copy this file into the subfolder &amp;#039;&amp;#039;&amp;#039;bin&amp;#039;&amp;#039;&amp;#039; of the Java installation folder as located above. You should find a lot of dll files there.&lt;br /&gt;
&lt;br /&gt;
You should now be able to use RXTX with your Java applications.&lt;br /&gt;
&lt;br /&gt;
=== Instructions for Linux ===&lt;br /&gt;
&lt;br /&gt;
* Unzip the file&lt;br /&gt;
* Enter the source directory&lt;br /&gt;
* Do a &lt;br /&gt;
 ./configure&lt;br /&gt;
* Install missing libraries if errors are reported.&lt;br /&gt;
* Create and install&lt;br /&gt;
 make&lt;br /&gt;
 make install&lt;br /&gt;
&lt;br /&gt;
The file &amp;#039;&amp;#039;RXTXcomm.jar&amp;#039;&amp;#039; should now be found in the &amp;#039;&amp;#039;lib/ext&amp;#039;&amp;#039; folder of your Java installation. The native libraries &amp;#039;&amp;#039;librxtxSerial.so&amp;#039;&amp;#039;, &amp;#039;&amp;#039;librxtxParallel.so&amp;#039;&amp;#039; and more have been copied into the &amp;#039;&amp;#039;lib/i386&amp;#039;&amp;#039; or &amp;#039;&amp;#039;lib/amd64&amp;#039;&amp;#039; folders (32 bit or 64 bit build; Intel 64 bit also uses the amd64 folder).&lt;br /&gt;
&lt;br /&gt;
At first you will likely get an error message during Java execution:&lt;br /&gt;
 check_group_uucp(): error testing lock file creation Error details:Permission deniedcheck_lock_status: &lt;br /&gt;
 No permission to create lock file.&lt;br /&gt;
 please see: How can I use Lock Files with rxtx? in INSTALL&lt;br /&gt;
&lt;br /&gt;
This message appears because you still have no permission to access the ports and to create a lock file. To fix this you must be member of the following groups:&lt;br /&gt;
&lt;br /&gt;
* uucp&lt;br /&gt;
* lock (to be allowed to create lock files in /var/lock; you may have to change permissions for this directory as well!)&lt;br /&gt;
* dialout (to be allowed to use the serial interface)&lt;br /&gt;
&lt;br /&gt;
Check /etc/group and add your user name behind all lines, or use your favorite system management tool. You must log out and log in again to make these changes effective. After this change, no such error message should be reported on the command line anymore.&lt;br /&gt;
&lt;br /&gt;
=== Known Issues ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039; &amp;#039;UTS_RELEASE&amp;#039; undeclared (first use in this function)&amp;#039;&amp;#039;&amp;#039;: Using the version rxtx-2.1-7r2 with a recent openSUSE issues this message on configure, and building also fails. Obviously this symbol has been thrown out of the kernel sources, which is still present in the 2.1 rxtx release. I could solve this by [http://rxtx.qbang.org/wiki/index.php/Retrieving_Source_Code using the CVS version].&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;make install&amp;#039;&amp;#039;&amp;#039;: There is a bug in the configure script, resulting in trouble when using a Java 1.6 JDK or higher. You may get an error message &lt;br /&gt;
 libtool: install: `x86_64-unknown-linux-gnu/librxtxRS485.la&amp;#039; is not a directory&lt;br /&gt;
In that case you need to manually add a version string into the configure script. Look for a sequence&lt;br /&gt;
 1.1*|1.2*|1.3*|1.4*|1.5*&lt;br /&gt;
and add a |1.6* behind. This appears multiple times in the configure script; make sure you change it at least for your operating system.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Bug in rxtx-2.1-7r2 on 64 bit systems&amp;#039;&amp;#039;&amp;#039;: If your application immediately exits with a SIGSEGV, and you can see in the dump file that it happened while calling read(), and if your system is 64 bit (x86_64), you may have run into a (known) bug in RXTX (see [http://bugzilla.qbang.org/show_bug.cgi?id=171 Bugzilla, bug 171]). Please apply the patch mentioned in the comment on the linked page.&lt;br /&gt;
&lt;br /&gt;
== Error codes from the disk controller card ==&lt;br /&gt;
&lt;br /&gt;
These are the error codes from the PHP1240 Disk Controller Card (the original TI disk controller).&lt;br /&gt;
&lt;br /&gt;
(moved, see [[Troubleshooting#Disk-related_issues]])&lt;br /&gt;
&lt;br /&gt;
== Hardware discussions ==&lt;br /&gt;
&lt;br /&gt;
=== Using ILA and SENILA ===&lt;br /&gt;
Todo&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Super_Sketch&amp;diff=50701</id>
		<title>Super Sketch</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Super_Sketch&amp;diff=50701"/>
		<updated>2023-02-24T20:06:36Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: Mizapf moved page Super sketch to Super Sketch without leaving a redirect: No lowercase in title&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SUPER SKETCH drawing tablet.&lt;br /&gt;
&lt;br /&gt;
Super Sketch Sketch-Master Model G2400 from Personal Peripherals Inc 1984.&lt;br /&gt;
&lt;br /&gt;
Medium: Cartridge plus tablet. Required: Console only.  The tablet plugs into the module port and has all necessary software in the module.&lt;br /&gt;
&lt;br /&gt;
When graphics tablets can easily cost over GBP 300, you don&amp;#039;t expect too much for GBP 65... in fact SUPERSKETCH is a remarkable product at the price.&lt;br /&gt;
&lt;br /&gt;
[[Image:SKETCH.JPG|250px|right]]&lt;br /&gt;
&lt;br /&gt;
The most significant drawback is a feature of the VDP in our console-  when in bit map mode (hi res graphics), each character on screen is divided into 8 rows of 8 pixels. For each row of 8 pixels, only two colours are permitted. When using SuperSketch, this can cause &amp;quot;bleeding&amp;quot; problems when you try to place three colours in a row. With great care you can reduce the visual effects of this but it is a significant drawback.&lt;br /&gt;
&lt;br /&gt;
SUPERSKETCH uses a movable arm which can be used to trace a drawing, or merely used &amp;#039;freeform&amp;#039;. A full palette of colours is available, and a good choice of &amp;#039;brushes&amp;#039;. As drawing straight lines can be difficult, special facilities are provided to assist you. There is a &amp;quot;fill&amp;quot; command also, but if there is even a one pixel gap in the shape, the ink will run out and fill the screen!&lt;br /&gt;
&lt;br /&gt;
Once created, pictures can be saved to cassette ONLY, not to disk, not to printer, although owners with 32k ram may be able to patch in a screen dump program of their own via an interrupt switch (console mod required).&lt;br /&gt;
&lt;br /&gt;
Essentially a toy, Supersketch is a very interesting peripheral for the 99/4A, and seems strongly enough built to be used by the younger members of the family, who could find it to be a very expressive medium. A colour tv should be considered essential for this item! although it can be used with black and white.&lt;br /&gt;
&lt;br /&gt;
[[Category:Expansion]]&lt;br /&gt;
[[Category:Module]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=MAME_TI_emulation_usage&amp;diff=50700</id>
		<title>MAME TI emulation usage</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=MAME_TI_emulation_usage&amp;diff=50700"/>
		<updated>2023-02-24T20:02:20Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Printing in MESS */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The following description addresses the usage of MAME starting from version 0.186. &lt;br /&gt;
&lt;br /&gt;
== Slot devices ==&lt;br /&gt;
&lt;br /&gt;
The TI emulation makes use of &amp;quot;slot devices&amp;quot;. These are devices that offer a &amp;#039;&amp;#039;slot&amp;#039;&amp;#039; into which another component can be plugged.&lt;br /&gt;
&lt;br /&gt;
=== Peripheral Box ===&lt;br /&gt;
&lt;br /&gt;
The [[Peripheral Expansion Box]] is modelled as a collection of expansion card slots. It plugs into the &amp;quot;ioport&amp;quot; of the console, so unless you explicitly plug it in, you do not see any of its slots.&lt;br /&gt;
&lt;br /&gt;
Check &lt;br /&gt;
&lt;br /&gt;
 mame64 ti99_4a -listslots&lt;br /&gt;
&lt;br /&gt;
and&lt;br /&gt;
&lt;br /&gt;
 mame64 ti99_4a -ioport peb -listslots&lt;br /&gt;
&lt;br /&gt;
Slot names must be specified on the command like with a leading &amp;quot;-&amp;quot;. As you can see, the slots of the PEB are named &amp;quot;ioport:peb:slot2&amp;quot; to &amp;quot;ioport:peb:slot8&amp;quot;. &lt;br /&gt;
&lt;br /&gt;
Any device plugged into the PEB must be specified using the slot name. An example could look like this:&lt;br /&gt;
&lt;br /&gt;
 mame64 ti99_4a -cart editass -ioport peb -ioport:peb:slot2 32kmem -ioport:peb:slot3 speech -ioport:peb:slot6 tirs232 -ioport:peb:slot8 hfdc -flop1 mydisk.dsk&lt;br /&gt;
&lt;br /&gt;
This may look pretty long, but you can hide this in a script (batch) file. Also, the frontends like QMC2 allow for a set of options to be defined for each start.&lt;br /&gt;
&lt;br /&gt;
The slots are all equivalent, concerning the cards that may be plugged into them, but if you want to keep with the real box, you should plug the disk controller into slot 8, because there is a gap in the chassis to connect the drives in the compartment to the controller. &lt;br /&gt;
&lt;br /&gt;
The [[Peripheral_Expansion_Box#Peripheral_Box_Slot_Concept|Flex Cable Interface]] traditionally goes into slot 1, even though each slot is connected in parallel with all lines. Accordingly, we leave that option away as well.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot;&lt;br /&gt;
! Slot parameter&lt;br /&gt;
! Value&lt;br /&gt;
! Device&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;14&amp;quot; | -ioport:peb:slot2 ... -ioport:peb:slot8&lt;br /&gt;
| 32kmem&lt;br /&gt;
| TI-99 32KiB memory expansion card&lt;br /&gt;
|-&lt;br /&gt;
| myarcmem&lt;br /&gt;
| Myarc Memory expansion card MEXP-1&lt;br /&gt;
|-&lt;br /&gt;
| samsmem&lt;br /&gt;
| SuperAMS memory expansion card&lt;br /&gt;
|-&lt;br /&gt;
| horizon&lt;br /&gt;
| Horizon 4000 Ramdisk&lt;br /&gt;
|-&lt;br /&gt;
| pcode&lt;br /&gt;
| TI-99 P-Code Card&lt;br /&gt;
|-&lt;br /&gt;
| hsgpl&lt;br /&gt;
| SNUG High-speed GPL card&lt;br /&gt;
|-&lt;br /&gt;
| speech&lt;br /&gt;
| TI-99 Speech synthesizer (on adapter card)&lt;br /&gt;
|-&lt;br /&gt;
| tirs232&lt;br /&gt;
| TI-99 RS232/PIO interface&lt;br /&gt;
|-&lt;br /&gt;
| ide&lt;br /&gt;
| Nouspikel IDE interface card&lt;br /&gt;
|-&lt;br /&gt;
| usbsm&lt;br /&gt;
| Nouspikel USB/Smartmedia card&lt;br /&gt;
|-&lt;br /&gt;
| tirs232&lt;br /&gt;
| TI-99 RS232/PIO interface&lt;br /&gt;
|-&lt;br /&gt;
| bwg&lt;br /&gt;
| SNUG BwG Floppy Controller&lt;br /&gt;
|-&lt;br /&gt;
| tifdc&lt;br /&gt;
| TI-99 Standard DSSD Floppy Controller&lt;br /&gt;
|-&lt;br /&gt;
| hfdc&lt;br /&gt;
| Myarc Hard and Floppy Disk Controller&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Joystick port ===&lt;br /&gt;
&lt;br /&gt;
The joystick port is also realized as a slot device. This has the benefit that special logic can be put into the devices instead of being somewhat incorporated in the console. In particular, the handling of the Mechatronics Mouse, the joysticks, and the TI-99/4 handsets required some case processing. &lt;br /&gt;
&lt;br /&gt;
Now the device can be specified as plugged into the &amp;quot;joyport&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
 mame64 ti99_4a -joyport twinjoy &lt;br /&gt;
 mame64 ti99_4a -joyport mecmouse&lt;br /&gt;
 mame64 ti99_4 -joyport handset&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;twinjoy&amp;quot; is the default setting, so you need not specify it explicitly.&lt;br /&gt;
&lt;br /&gt;
=== Cartridge port ===&lt;br /&gt;
&lt;br /&gt;
The cartridge port (also known as &amp;#039;&amp;#039;GROM port&amp;#039;&amp;#039;) allows for plugging in three different devices:&lt;br /&gt;
&lt;br /&gt;
* The single cartridge slot. This is not a real device; it just configures the cartridge slot to host a single cartridge. It is the default setting. The only cartridge option is &amp;quot;-cart&amp;quot;.&lt;br /&gt;
* The multi-cartridge expander. It allows for up to four cartridges to be plugged in by using &amp;quot;-cart1&amp;quot; to &amp;quot;-cart4&amp;quot;. When several cartridges are plugged in, the TI console offers a software selection using &amp;quot;REVIEW MODULE LIBRARY&amp;quot;.&lt;br /&gt;
* The GRAM Kracker. It is a device from [[Miller&amp;#039;s Graphics]] that is plugged into the cartridge port and offers another slot for a guest cartridge. You can then copy the cartridge contents into the GRAM Kracker with offers NVRAM to store them, and also to allow you to modify these contents.&lt;br /&gt;
&lt;br /&gt;
The cartridge slots work with both RPK and ZIP cartridges.&lt;br /&gt;
&lt;br /&gt;
So when using the TI-99/4, /4a, and /8 you can specify how the cartridge port looks like:&lt;br /&gt;
&lt;br /&gt;
 mame64 ti99_4a -gromport single -cart invaders&lt;br /&gt;
 mame64 ti99_4a -gromport multi -cart1 exbasic -cart2 editor_assembler.rpk&lt;br /&gt;
 mame64 ti99_4a -gromport gkracker -cart hunt_the_wumpus.rpk&lt;br /&gt;
&lt;br /&gt;
In order to use the GRAM Kracker, you must have the ROM contents of the GRAM Kracker in a file called gkracker.bin, and this must be found in a ZIP file ti99_gkracker.zip located in the rompath.&lt;br /&gt;
&lt;br /&gt;
You can create this zip file quite easily:&lt;br /&gt;
&lt;br /&gt;
== Using serial connections ==&lt;br /&gt;
&lt;br /&gt;
MAME does not offer a direct access to the PC serial interface (the UART). Instead, we can use a socket pipe which connects MESS with another application that provides the access. Details are explained on the [https://www.ninermame.org/details/serialconn serial emulation description].&lt;br /&gt;
&lt;br /&gt;
One application that provides the necessary connection is [https://www.mizapf.de/ti99/timt TIImageTool], in particular its &amp;#039;&amp;#039;serial bridge&amp;#039;&amp;#039; feature.&lt;br /&gt;
&lt;br /&gt;
Supposed that you have started TIImageTool and started the serial bridge on socket 10000 on the same computer where MAME is running, the connection is set up like this:&lt;br /&gt;
&lt;br /&gt;
 mame64 ti99_4a -ioport peb -ioport:peb:slot6 tirs232 -serl1 socket.localhost:10000&lt;br /&gt;
&lt;br /&gt;
Using the parameter &amp;#039;&amp;#039;serl2&amp;#039;&amp;#039; you can connect to the emulated RS2332/2. Also, you can set the CRU address to 1500 which turns the ports into RS232/3 and 4. The CRU address can be set in the [[MAME DIP switch settings|DIP switch settings]].&lt;br /&gt;
&lt;br /&gt;
== Printing in MESS ==&lt;br /&gt;
&lt;br /&gt;
You can output plain text content to a &amp;quot;virtual&amp;quot; printer. In our case it is a text file that is written to, and you can then use your PC printing facilities to print that file on a real printer.&lt;br /&gt;
&lt;br /&gt;
 mame64 ti99_4a -ioport peb -ioport:peb:slot6 tirs232 -parl outfile.txt&lt;br /&gt;
&lt;br /&gt;
Within TI BASIC, you can simply do a &lt;br /&gt;
&lt;br /&gt;
 LIST &amp;quot;PIO&amp;quot;&lt;br /&gt;
&lt;br /&gt;
which sends the program lines in memory to the file named &amp;quot;outfile.txt&amp;quot; on your PC file system. Of course, any other TI application may also write to PIO in the same way. You can also try to output graphic content, but this will end up in a lot of uninterpreted binary values, specific for the printer you chose in the application. You will need to write a tool to convert these binary values to an appropriate image.&lt;br /&gt;
&lt;br /&gt;
For the parallel output you &amp;#039;&amp;#039;&amp;#039;do not need&amp;#039;&amp;#039;&amp;#039; the [https://www.ninermame.org/details/serialconn serial bridge].&lt;br /&gt;
&lt;br /&gt;
[[Category:MAME]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=MAME_TI_emulation_usage&amp;diff=50699</id>
		<title>MAME TI emulation usage</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=MAME_TI_emulation_usage&amp;diff=50699"/>
		<updated>2023-02-24T20:01:27Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Using serial connections */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The following description addresses the usage of MAME starting from version 0.186. &lt;br /&gt;
&lt;br /&gt;
== Slot devices ==&lt;br /&gt;
&lt;br /&gt;
The TI emulation makes use of &amp;quot;slot devices&amp;quot;. These are devices that offer a &amp;#039;&amp;#039;slot&amp;#039;&amp;#039; into which another component can be plugged.&lt;br /&gt;
&lt;br /&gt;
=== Peripheral Box ===&lt;br /&gt;
&lt;br /&gt;
The [[Peripheral Expansion Box]] is modelled as a collection of expansion card slots. It plugs into the &amp;quot;ioport&amp;quot; of the console, so unless you explicitly plug it in, you do not see any of its slots.&lt;br /&gt;
&lt;br /&gt;
Check &lt;br /&gt;
&lt;br /&gt;
 mame64 ti99_4a -listslots&lt;br /&gt;
&lt;br /&gt;
and&lt;br /&gt;
&lt;br /&gt;
 mame64 ti99_4a -ioport peb -listslots&lt;br /&gt;
&lt;br /&gt;
Slot names must be specified on the command like with a leading &amp;quot;-&amp;quot;. As you can see, the slots of the PEB are named &amp;quot;ioport:peb:slot2&amp;quot; to &amp;quot;ioport:peb:slot8&amp;quot;. &lt;br /&gt;
&lt;br /&gt;
Any device plugged into the PEB must be specified using the slot name. An example could look like this:&lt;br /&gt;
&lt;br /&gt;
 mame64 ti99_4a -cart editass -ioport peb -ioport:peb:slot2 32kmem -ioport:peb:slot3 speech -ioport:peb:slot6 tirs232 -ioport:peb:slot8 hfdc -flop1 mydisk.dsk&lt;br /&gt;
&lt;br /&gt;
This may look pretty long, but you can hide this in a script (batch) file. Also, the frontends like QMC2 allow for a set of options to be defined for each start.&lt;br /&gt;
&lt;br /&gt;
The slots are all equivalent, concerning the cards that may be plugged into them, but if you want to keep with the real box, you should plug the disk controller into slot 8, because there is a gap in the chassis to connect the drives in the compartment to the controller. &lt;br /&gt;
&lt;br /&gt;
The [[Peripheral_Expansion_Box#Peripheral_Box_Slot_Concept|Flex Cable Interface]] traditionally goes into slot 1, even though each slot is connected in parallel with all lines. Accordingly, we leave that option away as well.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot;&lt;br /&gt;
! Slot parameter&lt;br /&gt;
! Value&lt;br /&gt;
! Device&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;14&amp;quot; | -ioport:peb:slot2 ... -ioport:peb:slot8&lt;br /&gt;
| 32kmem&lt;br /&gt;
| TI-99 32KiB memory expansion card&lt;br /&gt;
|-&lt;br /&gt;
| myarcmem&lt;br /&gt;
| Myarc Memory expansion card MEXP-1&lt;br /&gt;
|-&lt;br /&gt;
| samsmem&lt;br /&gt;
| SuperAMS memory expansion card&lt;br /&gt;
|-&lt;br /&gt;
| horizon&lt;br /&gt;
| Horizon 4000 Ramdisk&lt;br /&gt;
|-&lt;br /&gt;
| pcode&lt;br /&gt;
| TI-99 P-Code Card&lt;br /&gt;
|-&lt;br /&gt;
| hsgpl&lt;br /&gt;
| SNUG High-speed GPL card&lt;br /&gt;
|-&lt;br /&gt;
| speech&lt;br /&gt;
| TI-99 Speech synthesizer (on adapter card)&lt;br /&gt;
|-&lt;br /&gt;
| tirs232&lt;br /&gt;
| TI-99 RS232/PIO interface&lt;br /&gt;
|-&lt;br /&gt;
| ide&lt;br /&gt;
| Nouspikel IDE interface card&lt;br /&gt;
|-&lt;br /&gt;
| usbsm&lt;br /&gt;
| Nouspikel USB/Smartmedia card&lt;br /&gt;
|-&lt;br /&gt;
| tirs232&lt;br /&gt;
| TI-99 RS232/PIO interface&lt;br /&gt;
|-&lt;br /&gt;
| bwg&lt;br /&gt;
| SNUG BwG Floppy Controller&lt;br /&gt;
|-&lt;br /&gt;
| tifdc&lt;br /&gt;
| TI-99 Standard DSSD Floppy Controller&lt;br /&gt;
|-&lt;br /&gt;
| hfdc&lt;br /&gt;
| Myarc Hard and Floppy Disk Controller&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Joystick port ===&lt;br /&gt;
&lt;br /&gt;
The joystick port is also realized as a slot device. This has the benefit that special logic can be put into the devices instead of being somewhat incorporated in the console. In particular, the handling of the Mechatronics Mouse, the joysticks, and the TI-99/4 handsets required some case processing. &lt;br /&gt;
&lt;br /&gt;
Now the device can be specified as plugged into the &amp;quot;joyport&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
 mame64 ti99_4a -joyport twinjoy &lt;br /&gt;
 mame64 ti99_4a -joyport mecmouse&lt;br /&gt;
 mame64 ti99_4 -joyport handset&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;twinjoy&amp;quot; is the default setting, so you need not specify it explicitly.&lt;br /&gt;
&lt;br /&gt;
=== Cartridge port ===&lt;br /&gt;
&lt;br /&gt;
The cartridge port (also known as &amp;#039;&amp;#039;GROM port&amp;#039;&amp;#039;) allows for plugging in three different devices:&lt;br /&gt;
&lt;br /&gt;
* The single cartridge slot. This is not a real device; it just configures the cartridge slot to host a single cartridge. It is the default setting. The only cartridge option is &amp;quot;-cart&amp;quot;.&lt;br /&gt;
* The multi-cartridge expander. It allows for up to four cartridges to be plugged in by using &amp;quot;-cart1&amp;quot; to &amp;quot;-cart4&amp;quot;. When several cartridges are plugged in, the TI console offers a software selection using &amp;quot;REVIEW MODULE LIBRARY&amp;quot;.&lt;br /&gt;
* The GRAM Kracker. It is a device from [[Miller&amp;#039;s Graphics]] that is plugged into the cartridge port and offers another slot for a guest cartridge. You can then copy the cartridge contents into the GRAM Kracker with offers NVRAM to store them, and also to allow you to modify these contents.&lt;br /&gt;
&lt;br /&gt;
The cartridge slots work with both RPK and ZIP cartridges.&lt;br /&gt;
&lt;br /&gt;
So when using the TI-99/4, /4a, and /8 you can specify how the cartridge port looks like:&lt;br /&gt;
&lt;br /&gt;
 mame64 ti99_4a -gromport single -cart invaders&lt;br /&gt;
 mame64 ti99_4a -gromport multi -cart1 exbasic -cart2 editor_assembler.rpk&lt;br /&gt;
 mame64 ti99_4a -gromport gkracker -cart hunt_the_wumpus.rpk&lt;br /&gt;
&lt;br /&gt;
In order to use the GRAM Kracker, you must have the ROM contents of the GRAM Kracker in a file called gkracker.bin, and this must be found in a ZIP file ti99_gkracker.zip located in the rompath.&lt;br /&gt;
&lt;br /&gt;
You can create this zip file quite easily:&lt;br /&gt;
&lt;br /&gt;
== Using serial connections ==&lt;br /&gt;
&lt;br /&gt;
MAME does not offer a direct access to the PC serial interface (the UART). Instead, we can use a socket pipe which connects MESS with another application that provides the access. Details are explained on the [https://www.ninermame.org/details/serialconn serial emulation description].&lt;br /&gt;
&lt;br /&gt;
One application that provides the necessary connection is [https://www.mizapf.de/ti99/timt TIImageTool], in particular its &amp;#039;&amp;#039;serial bridge&amp;#039;&amp;#039; feature.&lt;br /&gt;
&lt;br /&gt;
Supposed that you have started TIImageTool and started the serial bridge on socket 10000 on the same computer where MAME is running, the connection is set up like this:&lt;br /&gt;
&lt;br /&gt;
 mame64 ti99_4a -ioport peb -ioport:peb:slot6 tirs232 -serl1 socket.localhost:10000&lt;br /&gt;
&lt;br /&gt;
Using the parameter &amp;#039;&amp;#039;serl2&amp;#039;&amp;#039; you can connect to the emulated RS2332/2. Also, you can set the CRU address to 1500 which turns the ports into RS232/3 and 4. The CRU address can be set in the [[MAME DIP switch settings|DIP switch settings]].&lt;br /&gt;
&lt;br /&gt;
== Printing in MESS ==&lt;br /&gt;
&lt;br /&gt;
You can output plain text content to a &amp;quot;virtual&amp;quot; printer. In our case it is a text file that is written to, and you can then use your PC printing facilities to print that file on a real printer.&lt;br /&gt;
&lt;br /&gt;
 mame64 ti99_4a -ioport peb -ioport:peb:slot6 tirs232 -parl outfile.txt&lt;br /&gt;
&lt;br /&gt;
Within TI BASIC, you can simply do a &lt;br /&gt;
&lt;br /&gt;
 LIST &amp;quot;PIO&amp;quot;&lt;br /&gt;
&lt;br /&gt;
which sends the program lines in memory to the file named &amp;quot;outfile.txt&amp;quot; on your PC file system. Of course, any other TI application may also write to PIO in the same way. You can also try to output graphic content, but this will end up in a lot of uninterpreted binary values, specific for the printer you chose in the application. You will need to write a tool to convert these binary values to an appropriate image.&lt;br /&gt;
&lt;br /&gt;
For the parallel output you &amp;#039;&amp;#039;&amp;#039;do not need&amp;#039;&amp;#039;&amp;#039; the [[MESS Serial connection|serial bridge]].&lt;br /&gt;
&lt;br /&gt;
[[Category:MAME]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=MAME&amp;diff=50698</id>
		<title>MAME</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=MAME&amp;diff=50698"/>
		<updated>2023-02-24T19:59:39Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Details */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Image:mess_logo_n.png|150px|right]]&lt;br /&gt;
MESS (Multiple Emulator Super System) is a multi-system emulator which emulates more than 400 computer systems, many of them from the good old days of the Home Computers. Among them are also a number of TI platforms:&lt;br /&gt;
&lt;br /&gt;
* TI-99/2 (24K and 32K ROM version)&lt;br /&gt;
* TI-99/4, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/4A, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/4QI, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/8&lt;br /&gt;
* Geneve 9640&lt;br /&gt;
&lt;br /&gt;
A number of peripherals are also emulated, ranging from various disk controllers (up to HFDC), to serial interfaces, USB interface, IDE interface, HSGPL, and also SGCPU.&lt;br /&gt;
&lt;br /&gt;
The emulator is freely downloadable from [http://www.mamedev.org/release.html mamedev.org] as a pre-built binary for Windows systems, and as a source zip archive for all platforms. &lt;br /&gt;
&lt;br /&gt;
You can download and work on the MAME source codes, since all MAME components have been relicenced under an open-source licence (BSD or GPL). If you want to contribute, you should get in contact with the maintainers, though. A good location for that is the [http://forums.bannister.org/ubbthreads.php?ubb=postlist&amp;amp;Board=1&amp;amp;page=1 MESS message board].&lt;br /&gt;
&lt;br /&gt;
I would be happy to see more people from the TI community helping to improve the TI emulation in MESS. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; margin-top:2em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;float:left; padding:1em; height: 35em; width:45%; background-color:#ddddee&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Installing and setup==&lt;br /&gt;
&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame/buildmame Building MAME] from source&lt;br /&gt;
* [https://www.mizapf.de/ti99/mame/winstmame Installing on Windows systems]&lt;br /&gt;
* [[Installing MAME on Linux|Installing on Linux systems]]&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame/ Getting the ROMs]&lt;br /&gt;
* [[Setting up the HSGPL for MAME]] &lt;br /&gt;
* [[Setting up the Horizon Ramdisk]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-left:50%; padding:1em; height: 35em;width:45%; background-color:#ddeedd&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Using==&lt;br /&gt;
&lt;br /&gt;
* [http://mess.redump.net/mess:howto The Official MESS User&amp;#039;s Manual]&lt;br /&gt;
* [[General MESS features]]&lt;br /&gt;
* [[Features of the TI and Geneve emulation in MESS]]&lt;br /&gt;
* [[MAME TI emulation usage|Working with the TI/Geneve emulation]]&lt;br /&gt;
* [[Tools for MESS|Tools]]&lt;br /&gt;
* [[MESS and TI cartridges|Cartridges]]&lt;br /&gt;
* [[MESS media handling|Media handling]]&lt;br /&gt;
** [[MESS media handling#Using_your_TI_and_Geneve_hard_drive_in_MESS|Using your TI/Geneve hard drive in MESS]]&lt;br /&gt;
** [[MESS media handling#Formatting_disks|Formatting disks]]&lt;br /&gt;
** [[MESS media handling#Setting_up_a_blank_hard_disk|Setting up a blank hard disk]]&lt;br /&gt;
** [[MESS media handling#Upgrading_to_new_CHD_format|Upgrading CHD version]]&lt;br /&gt;
** [[MESS media handling#IDE disk handling|Using the IDE HD controller]]&lt;br /&gt;
* [[MESS debugger|Built-in debugger]]&lt;br /&gt;
* [https://www.mizapf.de/ti99/timt TIImageTool] - a GUI-based image manager&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; height:1em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;float:left; padding:1em; height: 30em;width:45%; background-color:#eedddd&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Questions and Troubleshooting==&lt;br /&gt;
&lt;br /&gt;
* Some help for [[MESS Troubleshooting]].&lt;br /&gt;
* [[MESSFAQ | Frequently Asked Questions]] related to the TI/Geneve emulation in MESS.&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame Change MESS menu mode key] from Scroll Lock to another key&lt;br /&gt;
* Hassle with the [[MESS keyboard modes]] &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;padding:1em; margin-left:50%; height: 30em;width:45%; background-color:#ddeeee&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
* Basics: [[Emulation and Simulation]]&lt;br /&gt;
* [[Guided tour through MESS|Get a look under the hood of MESS]] &amp;lt;span style=&amp;quot;font-weight:bold; color:red&amp;quot;&amp;gt;NEW&amp;lt;/span&amp;gt;&lt;br /&gt;
* The [[MESS cartridge handling|cartridge system]] of MESS&lt;br /&gt;
* Programmer&amp;#039;s Guide to MESS&lt;br /&gt;
* [https://www.mizapf.de/ti99/mame/changes MAME version history] with features, fixes, and regressions list&lt;br /&gt;
* [[MESS issues and non-issues | MESS issues]]&lt;br /&gt;
* [https://www.ninermame.org/details/serialconn RS232 emulation]&lt;br /&gt;
* [[MAME Internals]]&lt;br /&gt;
* [[MAME Floppy sound emulation | Floppy sound emulation]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; margin-bottom:2em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;br /&gt;
&lt;br /&gt;
[[Category:Emulation]]&lt;br /&gt;
[[Category:MAME]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=MAME&amp;diff=50697</id>
		<title>MAME</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=MAME&amp;diff=50697"/>
		<updated>2023-02-24T19:58:34Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Image:mess_logo_n.png|150px|right]]&lt;br /&gt;
MESS (Multiple Emulator Super System) is a multi-system emulator which emulates more than 400 computer systems, many of them from the good old days of the Home Computers. Among them are also a number of TI platforms:&lt;br /&gt;
&lt;br /&gt;
* TI-99/2 (24K and 32K ROM version)&lt;br /&gt;
* TI-99/4, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/4A, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/4QI, 50 Hz and 60 Hz&lt;br /&gt;
* TI-99/8&lt;br /&gt;
* Geneve 9640&lt;br /&gt;
&lt;br /&gt;
A number of peripherals are also emulated, ranging from various disk controllers (up to HFDC), to serial interfaces, USB interface, IDE interface, HSGPL, and also SGCPU.&lt;br /&gt;
&lt;br /&gt;
The emulator is freely downloadable from [http://www.mamedev.org/release.html mamedev.org] as a pre-built binary for Windows systems, and as a source zip archive for all platforms. &lt;br /&gt;
&lt;br /&gt;
You can download and work on the MAME source codes, since all MAME components have been relicenced under an open-source licence (BSD or GPL). If you want to contribute, you should get in contact with the maintainers, though. A good location for that is the [http://forums.bannister.org/ubbthreads.php?ubb=postlist&amp;amp;Board=1&amp;amp;page=1 MESS message board].&lt;br /&gt;
&lt;br /&gt;
I would be happy to see more people from the TI community helping to improve the TI emulation in MESS. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; margin-top:2em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;float:left; padding:1em; height: 35em; width:45%; background-color:#ddddee&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Installing and setup==&lt;br /&gt;
&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame/buildmame Building MAME] from source&lt;br /&gt;
* [https://www.mizapf.de/ti99/mame/winstmame Installing on Windows systems]&lt;br /&gt;
* [[Installing MAME on Linux|Installing on Linux systems]]&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame/ Getting the ROMs]&lt;br /&gt;
* [[Setting up the HSGPL for MAME]] &lt;br /&gt;
* [[Setting up the Horizon Ramdisk]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-left:50%; padding:1em; height: 35em;width:45%; background-color:#ddeedd&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Using==&lt;br /&gt;
&lt;br /&gt;
* [http://mess.redump.net/mess:howto The Official MESS User&amp;#039;s Manual]&lt;br /&gt;
* [[General MESS features]]&lt;br /&gt;
* [[Features of the TI and Geneve emulation in MESS]]&lt;br /&gt;
* [[MAME TI emulation usage|Working with the TI/Geneve emulation]]&lt;br /&gt;
* [[Tools for MESS|Tools]]&lt;br /&gt;
* [[MESS and TI cartridges|Cartridges]]&lt;br /&gt;
* [[MESS media handling|Media handling]]&lt;br /&gt;
** [[MESS media handling#Using_your_TI_and_Geneve_hard_drive_in_MESS|Using your TI/Geneve hard drive in MESS]]&lt;br /&gt;
** [[MESS media handling#Formatting_disks|Formatting disks]]&lt;br /&gt;
** [[MESS media handling#Setting_up_a_blank_hard_disk|Setting up a blank hard disk]]&lt;br /&gt;
** [[MESS media handling#Upgrading_to_new_CHD_format|Upgrading CHD version]]&lt;br /&gt;
** [[MESS media handling#IDE disk handling|Using the IDE HD controller]]&lt;br /&gt;
* [[MESS debugger|Built-in debugger]]&lt;br /&gt;
* [https://www.mizapf.de/ti99/timt TIImageTool] - a GUI-based image manager&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; height:1em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;float:left; padding:1em; height: 30em;width:45%; background-color:#eedddd&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Questions and Troubleshooting==&lt;br /&gt;
&lt;br /&gt;
* Some help for [[MESS Troubleshooting]].&lt;br /&gt;
* [[MESSFAQ | Frequently Asked Questions]] related to the TI/Geneve emulation in MESS.&lt;br /&gt;
* [https://www.mizapf.de/en/ti99/mame Change MESS menu mode key] from Scroll Lock to another key&lt;br /&gt;
* Hassle with the [[MESS keyboard modes]] &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;padding:1em; margin-left:50%; height: 30em;width:45%; background-color:#ddeeee&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
* Basics: [[Emulation and Simulation]]&lt;br /&gt;
* [[Guided tour through MESS|Get a look under the hood of MESS]] &amp;lt;span style=&amp;quot;font-weight:bold; color:red&amp;quot;&amp;gt;NEW&amp;lt;/span&amp;gt;&lt;br /&gt;
* The [[MESS cartridge handling|cartridge system]] of MESS&lt;br /&gt;
* Programmer&amp;#039;s Guide to MESS&lt;br /&gt;
* [https://www.mizapf.de/ti99/mame/changes MAME version history] with features, fixes, and regressions list&lt;br /&gt;
* [[MESS issues and non-issues | MESS issues]]&lt;br /&gt;
* [https://www.ninermame.org/details/serialconn RS232 emulation]&lt;br /&gt;
* [[MESS Work in progress]]&lt;br /&gt;
* [[MAME Internals]]&lt;br /&gt;
* [[MAME Floppy sound emulation | Floppy sound emulation]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; margin-bottom:2em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;br /&gt;
&lt;br /&gt;
[[Category:Emulation]]&lt;br /&gt;
[[Category:MAME]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Main_Page&amp;diff=50680</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Main_Page&amp;diff=50680"/>
		<updated>2022-09-18T19:01:32Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div style=&amp;quot;font-size:1.5em;&amp;quot;&amp;gt;Welcome to Ninerpedia!&amp;lt;/div&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The Wiki for [[TI-99/4A]], [[TI-99/8]], [[Geneve 9640]], and all related hardware and software&lt;br /&gt;
&lt;br /&gt;
[[File:Ti994a_console.jpg|250px|link=TI-99/4A]]&amp;lt;span style=&amp;quot;margin-left:2em&amp;quot;&amp;gt; &amp;lt;/span&amp;gt;&lt;br /&gt;
[[File:99 8 Console.JPG|200px|link=TI-99/8]]&amp;lt;span style=&amp;quot;margin-left:3em&amp;quot;&amp;gt; &amp;lt;/span&amp;gt;&lt;br /&gt;
[[File:Geneve_expansion_card.jpg|200px|link=Geneve 9640]]&lt;br /&gt;
&lt;br /&gt;
On these pages, we try to collect valuable information for one of the greatest home computers and its extensions. You will find information on&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:inline-block; margin-right: 10em&amp;quot;&amp;gt;&lt;br /&gt;
* [[Community]] Links&lt;br /&gt;
* [[Protocols]] and [[Formats]] Specifications&lt;br /&gt;
* [[Interfacing with the outside world]]&lt;br /&gt;
* [[File systems]]&lt;br /&gt;
* [[Specifications]]&lt;br /&gt;
* [[Hardware]]&lt;br /&gt;
* [[Troubleshooting]]&lt;br /&gt;
* [[Media]]&lt;br /&gt;
* [[Software]]&lt;br /&gt;
* [[Technical details|Technical bits and pieces]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;display:inline-block&amp;quot;&amp;gt;&lt;br /&gt;
* [[Terminology]] (Glossary)&lt;br /&gt;
* [[Frequently Asked Questions]] (FAQ)&lt;br /&gt;
* [[Development resources]]&lt;br /&gt;
* [[Programming]]&lt;br /&gt;
* [[Using the system]]&lt;br /&gt;
* [[Recent developments]]&lt;br /&gt;
* [[Hardware projects]]&lt;br /&gt;
* [[Emulators]]&lt;br /&gt;
* [[News]]&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;New:&amp;#039;&amp;#039;&amp;#039; [[Challenges]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both; padding:1em; margin:3em 1em 1em 1em; background-color:#ccecff&amp;quot;&amp;gt;The &amp;#039;&amp;#039;&amp;#039;[[MAME | MAME TI-99 emulation]] pages&amp;#039;&amp;#039;&amp;#039; give you a lot of information, tips, and hints for running the current version of the [http://mamedev.org/ MAME emulator]. The MAME pages are currently moved to a new site; you will find an overview on [https://www.mizapf.de/ti99/mame/changes TI-related changes] there. There is also some more information on the utility program [https://www.mizapf.de/ti99/timt TIImageTool].&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Find more TI-related information on [http://www.99er.net/ 99er.net].&lt;br /&gt;
&lt;br /&gt;
[[Category:Main]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=GeneveOS_XOP_Definitions&amp;diff=50650</id>
		<title>GeneveOS XOP Definitions</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=GeneveOS_XOP_Definitions&amp;diff=50650"/>
		<updated>2022-04-30T16:27:50Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;GeneveOS makes frequent use of XOPs and offers them for user programs. An XOP (extended operation) is a special command of the TMS processor family which causes a [[Terminology#C|context switch]], transferring control to a location that is specified in a table.&lt;br /&gt;
&lt;br /&gt;
Compared to common architecture concepts, the XOP is TI&amp;#039;s way of implementing a &amp;#039;&amp;#039;&amp;#039;system call&amp;#039;&amp;#039;&amp;#039;. &lt;br /&gt;
&lt;br /&gt;
The XOP instruction takes two arguments; the first delivers data for the call, the second is a number from 0 to 15 and indicates the &amp;#039;&amp;#039;&amp;#039;XOP number&amp;#039;&amp;#039;&amp;#039;. In GeneveOS, all system calls are XOP 0 with specific arguments:&lt;br /&gt;
&lt;br /&gt;
 ARGUM  DATA &amp;lt;number&amp;gt;&lt;br /&gt;
        ...&lt;br /&gt;
        XOP  @ARGUM,0&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
=== Overview ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot;&lt;br /&gt;
! XOP argument&lt;br /&gt;
! Category&lt;br /&gt;
|-&lt;br /&gt;
| 5 || [[Geneve keyboard control | Keyboard ]]&lt;br /&gt;
|-&lt;br /&gt;
| 6 || [[GeneveOS Video Interface | Video display ]]&lt;br /&gt;
|-&lt;br /&gt;
| 7 || [[GeneveOS Memory Management Functions | Memory Management ]]&lt;br /&gt;
|-&lt;br /&gt;
| 8 || [[GeneveOS Device Operation | Devices (Files) ]]&lt;br /&gt;
|-&lt;br /&gt;
| 9 || [[GeneveOS Utility Functions | Utility functions]]&lt;br /&gt;
|-&lt;br /&gt;
| 10 || Mathematical functions&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Used workspaces ===&lt;br /&gt;
&lt;br /&gt;
The XOPs use workspaces F0C0 and F0A0.&lt;br /&gt;
&lt;br /&gt;
[[Category:MDOS]]&lt;br /&gt;
[[Category:Geneve]]&lt;br /&gt;
[[Category:Programming]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=GeneveOS_Device_Operation&amp;diff=50638</id>
		<title>GeneveOS Device Operation</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=GeneveOS_Device_Operation&amp;diff=50638"/>
		<updated>2022-04-24T19:43:05Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* BWRITE */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Accessing devices (floppy disk, hard disk, serial connector, printer) is possible in GeneveOS via XOP calls.&lt;br /&gt;
&lt;br /&gt;
== User-task XOPs ==&lt;br /&gt;
&lt;br /&gt;
User-task XOPs are available for use in application programs. Here is a typical example:&lt;br /&gt;
&lt;br /&gt;
 PABADD EQU  &amp;gt;F180   &lt;br /&gt;
 FILE   DATA 8&lt;br /&gt;
 ...&lt;br /&gt;
        LI   R0,PABADD&lt;br /&gt;
        XOP  @FILE,0&lt;br /&gt;
        MOVB @PABADD+2,R0&lt;br /&gt;
        JNE  ERROR&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
Similar as with the TI-99/4A device service routine concept (DSR), a Peripheral Access Block (PAB) must be set up prior to invoking the XOP.&lt;br /&gt;
&lt;br /&gt;
=== Device Service Routine Call ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Pointer to PAB || -&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The actual operation is contained in the Peripheral Access Block (PAB).&lt;br /&gt;
&lt;br /&gt;
=== Peripheral Access Block ===&lt;br /&gt;
&lt;br /&gt;
While the TI-99/4A DSRs expect the PAB to be stored in VDP RAM, the Geneve OS DSRs use CPU RAM for the PAB, which means the PAB need not be copied to the video RAM before use. Also, you can choose to have the I/O buffers in VDP or CPU RAM.&lt;br /&gt;
&lt;br /&gt;
The general layout of the PAB is as follows:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| Opcode &lt;br /&gt;
| Mode&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| Memory type&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Character count&lt;br /&gt;
| Status byte&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Note that the buffer addresses are 21-bit wide, represented by the [[MDOS Memory Management Functions#Local_.28virtual.29_pages | virtual address]] within the calling task. The address for this buffer should be derived from [[MDOS Memory Management Functions | memory allocation]] operations that were invoked earlier. &lt;br /&gt;
&lt;br /&gt;
After the operation, the PAB contains information about the results of the operation.&lt;br /&gt;
&lt;br /&gt;
=== Mode ===&lt;br /&gt;
&lt;br /&gt;
The mode bits are used to determine the operation direction (in/out/update/append), the encoding (display/internal), the structure (fixed/variable) and the access (sequential/relative).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainbits&amp;quot; style=&amp;quot;width:64%&amp;quot;&lt;br /&gt;
! 0&lt;br /&gt;
! 1&lt;br /&gt;
! 2&lt;br /&gt;
! 3&lt;br /&gt;
! 4&lt;br /&gt;
! 5&lt;br /&gt;
! 6&lt;br /&gt;
! 7&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width:24%&amp;quot; rowspan=&amp;quot;4&amp;quot; colspan=&amp;quot;3&amp;quot; | Unused (000)&lt;br /&gt;
| style=&amp;quot;width:8%&amp;quot; rowspan=&amp;quot;2&amp;quot; | Fixed=0&lt;br /&gt;
| style=&amp;quot;width:8%&amp;quot; rowspan=&amp;quot;2&amp;quot; | Display=0&lt;br /&gt;
| style=&amp;quot;width:16%&amp;quot; colspan=&amp;quot;2&amp;quot; | Update=00&lt;br /&gt;
| style=&amp;quot;width:8%&amp;quot; rowspan=&amp;quot;2&amp;quot; | Sequential=0&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Output=01&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Variable=1 &lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Internal=1&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Input=10&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Relative=1&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Append=11&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Error code ===&lt;br /&gt;
&lt;br /&gt;
The error code is an eight bit word with the following structure:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainbits&amp;quot; style=&amp;quot;float:left&amp;quot;&lt;br /&gt;
! 0&lt;br /&gt;
! 1&lt;br /&gt;
! 2&lt;br /&gt;
! 3&lt;br /&gt;
! 4&lt;br /&gt;
! 5&lt;br /&gt;
! 6&lt;br /&gt;
! 7&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot; | Detail code for error 7&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;float:left&amp;quot;&lt;br /&gt;
! Number&lt;br /&gt;
! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| No such device&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Write-protected&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Illegal open attribute&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Illegal operation&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Out of buffer space&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Read beyond EOF&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Device error&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| File error&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Operations ==&lt;br /&gt;
&lt;br /&gt;
=== OPEN ===&lt;br /&gt;
&lt;br /&gt;
Open a record-oriented file. A file must be opened before it can be read from or written to. This operation is not required for flat memory image files.&lt;br /&gt;
&lt;br /&gt;
The PAB must be set up prior to invoking the operation:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;00&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Records to reserve&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Open new file&amp;#039;&amp;#039;&amp;#039;: The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must be set to a value greater than 0. This will become the length of the records of the new file.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Open existing file&amp;#039;&amp;#039;&amp;#039;: The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must match the length of the records. If the length is unknown, 0 may be passed.&lt;br /&gt;
&lt;br /&gt;
After the OPEN operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Actual Record Length&lt;br /&gt;
| -&lt;br /&gt;
| 0&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | True Record Length&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Actual Record Length&amp;#039;&amp;#039;&amp;#039; is the length of the records of the existing file. When 0 was passed on the call, this delivers the record length of the file; otherwise this is the value that was passed for the call. The &amp;#039;&amp;#039;&amp;#039;True Record Length&amp;#039;&amp;#039;&amp;#039; is the record length of the file when it already exists.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== CLOSE ===&lt;br /&gt;
&lt;br /&gt;
Closes a record-oriented file. A file must be closed after being used. After closing, no further read or write operations may be invoked on the file. This operation is not required for flat memory image files.&lt;br /&gt;
&lt;br /&gt;
The PAB must be set up prior to invoking the operation:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;01&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
After the CLOSE operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== READ ===&lt;br /&gt;
&lt;br /&gt;
Reads a record from a record-oriented file. For flat memory image files, [[#LOAD | LOAD]] must be used. &lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;02&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| Memory&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Use the &amp;#039;&amp;#039;&amp;#039;Record number&amp;#039;&amp;#039;&amp;#039; field to specify the desired record in &amp;#039;&amp;#039;&amp;#039;fixed record length&amp;#039;&amp;#039;&amp;#039; files. This is not available for variable length record files.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must match the length of the records as specified by [[#OPEN | OPEN]].&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Memory&amp;#039;&amp;#039;&amp;#039; flag determines whether the data are read into CPU (0) or VDP (not 0) memory.&lt;br /&gt;
&lt;br /&gt;
After the READ operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of read bytes&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Next record number&amp;#039;&amp;#039;&amp;#039; applies for fixed record length files only.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== WRITE ===&lt;br /&gt;
&lt;br /&gt;
Writes a record to a record-oriented file. For flat memory image files, [[#SAVE | SAVE]] must be used. &lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;03&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| Memory&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of bytes to write&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Use the &amp;#039;&amp;#039;&amp;#039;Record number&amp;#039;&amp;#039;&amp;#039; field to specify the desired record in &amp;#039;&amp;#039;&amp;#039;fixed record length&amp;#039;&amp;#039;&amp;#039; files. This is not available for variable length record files.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must match the length of the records as specified by [[#OPEN | OPEN]].&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Memory&amp;#039;&amp;#039;&amp;#039; flag determines whether the data written from CPU (0) or VDP (not 0) memory.&lt;br /&gt;
&lt;br /&gt;
After the WRITE operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of read bytes&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Next record number&amp;#039;&amp;#039;&amp;#039; applies for fixed record length files only.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== RESTORE ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== LOAD ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== SAVE ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== DELETE ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== SCRATCH ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== STATUS ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== BREAD ===&lt;br /&gt;
&lt;br /&gt;
Reads a sector from a file or from a device. This is a level-2 operation, that is, it accesses the device based on sector addressing (or sectors of a file). BREAD allows for two operations:&lt;br /&gt;
&lt;br /&gt;
* Read sectors from a file.&lt;br /&gt;
* Read sectors from a device.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;0A&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Sector offset LSW&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| CPU / VDP&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Number of sectors&lt;br /&gt;
| Sector offset MSB&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The sector offset is defined by byte 0E as the most significant byte (value*65536) plus the word in bytes 06 and 07.&lt;br /&gt;
&lt;br /&gt;
Byte 0A specifies whether the buffer address belongs to the CPU (0) or the VDP (non-zero) address space. &lt;br /&gt;
&lt;br /&gt;
* CPU buffer address: ...p pppp.pppx xxxx.xxxx xxxx. The first three bits are ignored; the next 8 bits specify the page number in the user task, and the remaining 13 bits provide the offset in the page.&lt;br /&gt;
* VDP buffer address: .... ...x.xxxx xxxx.xxxx xxxx. The last 17 bits indicate the 128K video address.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Read from file&amp;#039;&amp;#039;&amp;#039;: When a file is addressed, the operation reads a sequence of sectors from the file. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the number of the first sector of the file, counting from 0. The physical locations of the sectors are subject to the sector allocation on the device.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Read from device&amp;#039;&amp;#039;&amp;#039;: When a device is addressed, the operation reads a sequence of sectors from the device. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the physical sector number.&lt;br /&gt;
&lt;br /&gt;
After the BREAD operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Updated buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Updated sector offset LSW&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Remaining sectors&lt;br /&gt;
| Updated sector offset MSB&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Error codes&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;60&amp;#039;&amp;#039;&amp;#039;: Bad operation; happens when the device is not a block device.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;A0&amp;#039;&amp;#039;&amp;#039;: Read beyond end of file or device. &lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;C0&amp;#039;&amp;#039;&amp;#039;: Media error. The system was unable to read the sector although it should be readable.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;E0&amp;#039;&amp;#039;&amp;#039;: General error; happens when the device does not contain the specified file.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Updated buffer address&amp;#039;&amp;#039;&amp;#039; points to the memory address after the sectors were read (e.g. when 4 sectors are read, the pointer is increased by 4*256 = 1024).&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Updated sector offset&amp;#039;&amp;#039;&amp;#039; is the first sector that has not been read due to an error condition.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Remaining sectors&amp;#039;&amp;#039;&amp;#039; is the number of unread sectors due to an error condition.&lt;br /&gt;
&lt;br /&gt;
=== BWRITE ===&lt;br /&gt;
&lt;br /&gt;
Writes a sector to a file or to a device. This is a level-2 operation, that is, it accesses the device based on sector addressing (or sectors of a file). BWRITE allows for three operations:&lt;br /&gt;
&lt;br /&gt;
* Create a new, empty file.&lt;br /&gt;
* Write sectors to a file.&lt;br /&gt;
* Write sectors to a device.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;0B&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Sector offset LSW&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| CPU / VDP&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Number of sectors&lt;br /&gt;
| Sector offset MSB&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Create a file&amp;#039;&amp;#039;&amp;#039;: When a file is addressed, and the number of sectors is 0, a new file is created. The contents of the file should be written by BWRITE on the same file with a sector count greater than 0.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Write to file&amp;#039;&amp;#039;&amp;#039;: When a file is addressed, the operation writes a sequence of sectors to the file. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the number of the first sector of the file, counting from 0. The physical locations of the sectors are subject to the sector allocation on the device; in particular, when fragmentation is required, the sectors are written to the new fragment.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Writing to device&amp;#039;&amp;#039;&amp;#039;: When a device is addressed, the operation writes a sequence of sectors to the device. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the physical sector number.&lt;br /&gt;
&lt;br /&gt;
After the BWRITE operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Updated buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Updated sector offset LSW&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Remaining sectors&lt;br /&gt;
| Updated sector offset MSB&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Updated buffer address&amp;#039;&amp;#039;&amp;#039; points to the memory address after the sectors were written (e.g. when 4 sectors are written, the pointer is increased by 4*256 = 1024).&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Updates sector offset&amp;#039;&amp;#039;&amp;#039; is the first sector that has not been written due to an error condition.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Remaining sectors&amp;#039;&amp;#039;&amp;#039; is the number of unwritten sectors due to an error condition.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;warn&amp;quot;&amp;gt;Unless you want to write directly to the device, make sure that the PAB contains a file name that points to a file, not to a device. For example, when the file name has been created by the program, and for some reason the file name is empty (like &amp;quot;HDS1.&amp;quot;), the following write operation will overwrite the volume information block and the allocation tables and &amp;#039;&amp;#039;&amp;#039;destroy the target device filesystem&amp;#039;&amp;#039;&amp;#039;.&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Tick here: [ ] Yes, I fully understood the danger of this operation.&lt;br /&gt;
&lt;br /&gt;
(The author lost a complete hard disk by this incident, and was lucky to have a backup.)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== PROTECT ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== RENAME ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== FORMAT ===&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=GeneveOS_Device_Operation&amp;diff=50637</id>
		<title>GeneveOS Device Operation</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=GeneveOS_Device_Operation&amp;diff=50637"/>
		<updated>2022-04-24T18:42:10Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* BREAD */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Accessing devices (floppy disk, hard disk, serial connector, printer) is possible in GeneveOS via XOP calls.&lt;br /&gt;
&lt;br /&gt;
== User-task XOPs ==&lt;br /&gt;
&lt;br /&gt;
User-task XOPs are available for use in application programs. Here is a typical example:&lt;br /&gt;
&lt;br /&gt;
 PABADD EQU  &amp;gt;F180   &lt;br /&gt;
 FILE   DATA 8&lt;br /&gt;
 ...&lt;br /&gt;
        LI   R0,PABADD&lt;br /&gt;
        XOP  @FILE,0&lt;br /&gt;
        MOVB @PABADD+2,R0&lt;br /&gt;
        JNE  ERROR&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
Similar as with the TI-99/4A device service routine concept (DSR), a Peripheral Access Block (PAB) must be set up prior to invoking the XOP.&lt;br /&gt;
&lt;br /&gt;
=== Device Service Routine Call ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Pointer to PAB || -&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The actual operation is contained in the Peripheral Access Block (PAB).&lt;br /&gt;
&lt;br /&gt;
=== Peripheral Access Block ===&lt;br /&gt;
&lt;br /&gt;
While the TI-99/4A DSRs expect the PAB to be stored in VDP RAM, the Geneve OS DSRs use CPU RAM for the PAB, which means the PAB need not be copied to the video RAM before use. Also, you can choose to have the I/O buffers in VDP or CPU RAM.&lt;br /&gt;
&lt;br /&gt;
The general layout of the PAB is as follows:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| Opcode &lt;br /&gt;
| Mode&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| Memory type&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Character count&lt;br /&gt;
| Status byte&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Note that the buffer addresses are 21-bit wide, represented by the [[MDOS Memory Management Functions#Local_.28virtual.29_pages | virtual address]] within the calling task. The address for this buffer should be derived from [[MDOS Memory Management Functions | memory allocation]] operations that were invoked earlier. &lt;br /&gt;
&lt;br /&gt;
After the operation, the PAB contains information about the results of the operation.&lt;br /&gt;
&lt;br /&gt;
=== Mode ===&lt;br /&gt;
&lt;br /&gt;
The mode bits are used to determine the operation direction (in/out/update/append), the encoding (display/internal), the structure (fixed/variable) and the access (sequential/relative).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainbits&amp;quot; style=&amp;quot;width:64%&amp;quot;&lt;br /&gt;
! 0&lt;br /&gt;
! 1&lt;br /&gt;
! 2&lt;br /&gt;
! 3&lt;br /&gt;
! 4&lt;br /&gt;
! 5&lt;br /&gt;
! 6&lt;br /&gt;
! 7&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width:24%&amp;quot; rowspan=&amp;quot;4&amp;quot; colspan=&amp;quot;3&amp;quot; | Unused (000)&lt;br /&gt;
| style=&amp;quot;width:8%&amp;quot; rowspan=&amp;quot;2&amp;quot; | Fixed=0&lt;br /&gt;
| style=&amp;quot;width:8%&amp;quot; rowspan=&amp;quot;2&amp;quot; | Display=0&lt;br /&gt;
| style=&amp;quot;width:16%&amp;quot; colspan=&amp;quot;2&amp;quot; | Update=00&lt;br /&gt;
| style=&amp;quot;width:8%&amp;quot; rowspan=&amp;quot;2&amp;quot; | Sequential=0&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Output=01&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Variable=1 &lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Internal=1&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Input=10&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Relative=1&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Append=11&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Error code ===&lt;br /&gt;
&lt;br /&gt;
The error code is an eight bit word with the following structure:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainbits&amp;quot; style=&amp;quot;float:left&amp;quot;&lt;br /&gt;
! 0&lt;br /&gt;
! 1&lt;br /&gt;
! 2&lt;br /&gt;
! 3&lt;br /&gt;
! 4&lt;br /&gt;
! 5&lt;br /&gt;
! 6&lt;br /&gt;
! 7&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot; | Detail code for error 7&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;float:left&amp;quot;&lt;br /&gt;
! Number&lt;br /&gt;
! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| No such device&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Write-protected&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Illegal open attribute&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Illegal operation&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Out of buffer space&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Read beyond EOF&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Device error&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| File error&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Operations ==&lt;br /&gt;
&lt;br /&gt;
=== OPEN ===&lt;br /&gt;
&lt;br /&gt;
Open a record-oriented file. A file must be opened before it can be read from or written to. This operation is not required for flat memory image files.&lt;br /&gt;
&lt;br /&gt;
The PAB must be set up prior to invoking the operation:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;00&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Records to reserve&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Open new file&amp;#039;&amp;#039;&amp;#039;: The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must be set to a value greater than 0. This will become the length of the records of the new file.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Open existing file&amp;#039;&amp;#039;&amp;#039;: The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must match the length of the records. If the length is unknown, 0 may be passed.&lt;br /&gt;
&lt;br /&gt;
After the OPEN operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Actual Record Length&lt;br /&gt;
| -&lt;br /&gt;
| 0&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | True Record Length&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Actual Record Length&amp;#039;&amp;#039;&amp;#039; is the length of the records of the existing file. When 0 was passed on the call, this delivers the record length of the file; otherwise this is the value that was passed for the call. The &amp;#039;&amp;#039;&amp;#039;True Record Length&amp;#039;&amp;#039;&amp;#039; is the record length of the file when it already exists.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== CLOSE ===&lt;br /&gt;
&lt;br /&gt;
Closes a record-oriented file. A file must be closed after being used. After closing, no further read or write operations may be invoked on the file. This operation is not required for flat memory image files.&lt;br /&gt;
&lt;br /&gt;
The PAB must be set up prior to invoking the operation:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;01&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
After the CLOSE operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== READ ===&lt;br /&gt;
&lt;br /&gt;
Reads a record from a record-oriented file. For flat memory image files, [[#LOAD | LOAD]] must be used. &lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;02&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| Memory&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Use the &amp;#039;&amp;#039;&amp;#039;Record number&amp;#039;&amp;#039;&amp;#039; field to specify the desired record in &amp;#039;&amp;#039;&amp;#039;fixed record length&amp;#039;&amp;#039;&amp;#039; files. This is not available for variable length record files.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must match the length of the records as specified by [[#OPEN | OPEN]].&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Memory&amp;#039;&amp;#039;&amp;#039; flag determines whether the data are read into CPU (0) or VDP (not 0) memory.&lt;br /&gt;
&lt;br /&gt;
After the READ operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of read bytes&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Next record number&amp;#039;&amp;#039;&amp;#039; applies for fixed record length files only.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== WRITE ===&lt;br /&gt;
&lt;br /&gt;
Writes a record to a record-oriented file. For flat memory image files, [[#SAVE | SAVE]] must be used. &lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;03&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| Memory&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of bytes to write&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Use the &amp;#039;&amp;#039;&amp;#039;Record number&amp;#039;&amp;#039;&amp;#039; field to specify the desired record in &amp;#039;&amp;#039;&amp;#039;fixed record length&amp;#039;&amp;#039;&amp;#039; files. This is not available for variable length record files.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must match the length of the records as specified by [[#OPEN | OPEN]].&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Memory&amp;#039;&amp;#039;&amp;#039; flag determines whether the data written from CPU (0) or VDP (not 0) memory.&lt;br /&gt;
&lt;br /&gt;
After the WRITE operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of read bytes&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Next record number&amp;#039;&amp;#039;&amp;#039; applies for fixed record length files only.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== RESTORE ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== LOAD ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== SAVE ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== DELETE ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== SCRATCH ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== STATUS ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== BREAD ===&lt;br /&gt;
&lt;br /&gt;
Reads a sector from a file or from a device. This is a level-2 operation, that is, it accesses the device based on sector addressing (or sectors of a file). BREAD allows for two operations:&lt;br /&gt;
&lt;br /&gt;
* Read sectors from a file.&lt;br /&gt;
* Read sectors from a device.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;0A&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Sector offset LSW&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| CPU / VDP&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Number of sectors&lt;br /&gt;
| Sector offset MSB&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The sector offset is defined by byte 0E as the most significant byte (value*65536) plus the word in bytes 06 and 07.&lt;br /&gt;
&lt;br /&gt;
Byte 0A specifies whether the buffer address belongs to the CPU (0) or the VDP (non-zero) address space. &lt;br /&gt;
&lt;br /&gt;
* CPU buffer address: ...p pppp.pppx xxxx.xxxx xxxx. The first three bits are ignored; the next 8 bits specify the page number in the user task, and the remaining 13 bits provide the offset in the page.&lt;br /&gt;
* VDP buffer address: .... ...x.xxxx xxxx.xxxx xxxx. The last 17 bits indicate the 128K video address.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Read from file&amp;#039;&amp;#039;&amp;#039;: When a file is addressed, the operation reads a sequence of sectors from the file. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the number of the first sector of the file, counting from 0. The physical locations of the sectors are subject to the sector allocation on the device.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Read from device&amp;#039;&amp;#039;&amp;#039;: When a device is addressed, the operation reads a sequence of sectors from the device. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the physical sector number.&lt;br /&gt;
&lt;br /&gt;
After the BREAD operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Updated buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Updated sector offset LSW&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Remaining sectors&lt;br /&gt;
| Updated sector offset MSB&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Error codes&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;60&amp;#039;&amp;#039;&amp;#039;: Bad operation; happens when the device is not a block device.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;A0&amp;#039;&amp;#039;&amp;#039;: Read beyond end of file or device. &lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;C0&amp;#039;&amp;#039;&amp;#039;: Media error. The system was unable to read the sector although it should be readable.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;E0&amp;#039;&amp;#039;&amp;#039;: General error; happens when the device does not contain the specified file.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Updated buffer address&amp;#039;&amp;#039;&amp;#039; points to the memory address after the sectors were read (e.g. when 4 sectors are read, the pointer is increased by 4*256 = 1024).&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Updated sector offset&amp;#039;&amp;#039;&amp;#039; is the first sector that has not been read due to an error condition.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Remaining sectors&amp;#039;&amp;#039;&amp;#039; is the number of unread sectors due to an error condition.&lt;br /&gt;
&lt;br /&gt;
=== BWRITE ===&lt;br /&gt;
&lt;br /&gt;
Writes a sector to a file or to a device. This is a level-2 operation, that is, it accesses the device based on sector addressing (or sectors of a file). BWRITE allows for three operations:&lt;br /&gt;
&lt;br /&gt;
* Create a new, empty file.&lt;br /&gt;
* Write sectors to a file.&lt;br /&gt;
* Write sectors to a device.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;0B&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Sector offset&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of sectors&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Create a file&amp;#039;&amp;#039;&amp;#039;: When a file is addressed, and the number of sectors is 0, a new file is created. The contents of the file should be written by BWRITE on the same file with a sector count greater than 0.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Write to file&amp;#039;&amp;#039;&amp;#039;: When a file is addressed, the operation writes a sequence of sectors to the file. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the number of the first sector of the file, counting from 0. The physical locations of the sectors are subject to the sector allocation on the device; in particular, when fragmentation is required, the sectors are written to the new fragment.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Writing to device&amp;#039;&amp;#039;&amp;#039;: When a device is addressed, the operation writes a sequence of sectors to the device. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the physical sector number.&lt;br /&gt;
&lt;br /&gt;
After the BWRITE operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Updated buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next unwritten sector&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| 0&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Number of unwritten sectors&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Updated buffer address&amp;#039;&amp;#039;&amp;#039; points to the memory address after the sectors were written (e.g. when 4 sectors are written, the pointer is increased by 4*256 = 1024).&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Next unwritten sector&amp;#039;&amp;#039;&amp;#039; is the first sector that has not been written due to an error condition.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Number of unwritten sectors&amp;#039;&amp;#039;&amp;#039; is the number of unwritten sectors due to an error condition.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;warn&amp;quot;&amp;gt;Unless you want to write directly to the device, make sure that the PAB contains a file name that points to a file, not to a device. For example, when the file name has been created by the program, and for some reason the file name is empty (like &amp;quot;HDS1.&amp;quot;), the following write operation will overwrite the volume information block and the allocation tables and &amp;#039;&amp;#039;&amp;#039;destroy the target device filesystem&amp;#039;&amp;#039;&amp;#039;.&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Tick here: [ ] Yes, I fully understood the danger of this operation.&lt;br /&gt;
&lt;br /&gt;
(The author lost a complete hard disk by this incident, and was lucky to have a backup.)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== PROTECT ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== RENAME ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== FORMAT ===&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=GeneveOS_Device_Operation&amp;diff=50636</id>
		<title>GeneveOS Device Operation</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=GeneveOS_Device_Operation&amp;diff=50636"/>
		<updated>2022-04-24T18:30:57Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* BREAD */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Accessing devices (floppy disk, hard disk, serial connector, printer) is possible in GeneveOS via XOP calls.&lt;br /&gt;
&lt;br /&gt;
== User-task XOPs ==&lt;br /&gt;
&lt;br /&gt;
User-task XOPs are available for use in application programs. Here is a typical example:&lt;br /&gt;
&lt;br /&gt;
 PABADD EQU  &amp;gt;F180   &lt;br /&gt;
 FILE   DATA 8&lt;br /&gt;
 ...&lt;br /&gt;
        LI   R0,PABADD&lt;br /&gt;
        XOP  @FILE,0&lt;br /&gt;
        MOVB @PABADD+2,R0&lt;br /&gt;
        JNE  ERROR&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
Similar as with the TI-99/4A device service routine concept (DSR), a Peripheral Access Block (PAB) must be set up prior to invoking the XOP.&lt;br /&gt;
&lt;br /&gt;
=== Device Service Routine Call ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Pointer to PAB || -&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The actual operation is contained in the Peripheral Access Block (PAB).&lt;br /&gt;
&lt;br /&gt;
=== Peripheral Access Block ===&lt;br /&gt;
&lt;br /&gt;
While the TI-99/4A DSRs expect the PAB to be stored in VDP RAM, the Geneve OS DSRs use CPU RAM for the PAB, which means the PAB need not be copied to the video RAM before use. Also, you can choose to have the I/O buffers in VDP or CPU RAM.&lt;br /&gt;
&lt;br /&gt;
The general layout of the PAB is as follows:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| Opcode &lt;br /&gt;
| Mode&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| Memory type&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Character count&lt;br /&gt;
| Status byte&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Note that the buffer addresses are 21-bit wide, represented by the [[MDOS Memory Management Functions#Local_.28virtual.29_pages | virtual address]] within the calling task. The address for this buffer should be derived from [[MDOS Memory Management Functions | memory allocation]] operations that were invoked earlier. &lt;br /&gt;
&lt;br /&gt;
After the operation, the PAB contains information about the results of the operation.&lt;br /&gt;
&lt;br /&gt;
=== Mode ===&lt;br /&gt;
&lt;br /&gt;
The mode bits are used to determine the operation direction (in/out/update/append), the encoding (display/internal), the structure (fixed/variable) and the access (sequential/relative).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainbits&amp;quot; style=&amp;quot;width:64%&amp;quot;&lt;br /&gt;
! 0&lt;br /&gt;
! 1&lt;br /&gt;
! 2&lt;br /&gt;
! 3&lt;br /&gt;
! 4&lt;br /&gt;
! 5&lt;br /&gt;
! 6&lt;br /&gt;
! 7&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width:24%&amp;quot; rowspan=&amp;quot;4&amp;quot; colspan=&amp;quot;3&amp;quot; | Unused (000)&lt;br /&gt;
| style=&amp;quot;width:8%&amp;quot; rowspan=&amp;quot;2&amp;quot; | Fixed=0&lt;br /&gt;
| style=&amp;quot;width:8%&amp;quot; rowspan=&amp;quot;2&amp;quot; | Display=0&lt;br /&gt;
| style=&amp;quot;width:16%&amp;quot; colspan=&amp;quot;2&amp;quot; | Update=00&lt;br /&gt;
| style=&amp;quot;width:8%&amp;quot; rowspan=&amp;quot;2&amp;quot; | Sequential=0&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Output=01&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Variable=1 &lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Internal=1&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Input=10&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Relative=1&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Append=11&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Error code ===&lt;br /&gt;
&lt;br /&gt;
The error code is an eight bit word with the following structure:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainbits&amp;quot; style=&amp;quot;float:left&amp;quot;&lt;br /&gt;
! 0&lt;br /&gt;
! 1&lt;br /&gt;
! 2&lt;br /&gt;
! 3&lt;br /&gt;
! 4&lt;br /&gt;
! 5&lt;br /&gt;
! 6&lt;br /&gt;
! 7&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot; | Detail code for error 7&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;float:left&amp;quot;&lt;br /&gt;
! Number&lt;br /&gt;
! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| No such device&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Write-protected&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Illegal open attribute&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Illegal operation&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Out of buffer space&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Read beyond EOF&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Device error&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| File error&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Operations ==&lt;br /&gt;
&lt;br /&gt;
=== OPEN ===&lt;br /&gt;
&lt;br /&gt;
Open a record-oriented file. A file must be opened before it can be read from or written to. This operation is not required for flat memory image files.&lt;br /&gt;
&lt;br /&gt;
The PAB must be set up prior to invoking the operation:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;00&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Records to reserve&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Open new file&amp;#039;&amp;#039;&amp;#039;: The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must be set to a value greater than 0. This will become the length of the records of the new file.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Open existing file&amp;#039;&amp;#039;&amp;#039;: The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must match the length of the records. If the length is unknown, 0 may be passed.&lt;br /&gt;
&lt;br /&gt;
After the OPEN operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Actual Record Length&lt;br /&gt;
| -&lt;br /&gt;
| 0&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | True Record Length&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Actual Record Length&amp;#039;&amp;#039;&amp;#039; is the length of the records of the existing file. When 0 was passed on the call, this delivers the record length of the file; otherwise this is the value that was passed for the call. The &amp;#039;&amp;#039;&amp;#039;True Record Length&amp;#039;&amp;#039;&amp;#039; is the record length of the file when it already exists.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== CLOSE ===&lt;br /&gt;
&lt;br /&gt;
Closes a record-oriented file. A file must be closed after being used. After closing, no further read or write operations may be invoked on the file. This operation is not required for flat memory image files.&lt;br /&gt;
&lt;br /&gt;
The PAB must be set up prior to invoking the operation:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;01&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
After the CLOSE operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== READ ===&lt;br /&gt;
&lt;br /&gt;
Reads a record from a record-oriented file. For flat memory image files, [[#LOAD | LOAD]] must be used. &lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;02&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| Memory&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Use the &amp;#039;&amp;#039;&amp;#039;Record number&amp;#039;&amp;#039;&amp;#039; field to specify the desired record in &amp;#039;&amp;#039;&amp;#039;fixed record length&amp;#039;&amp;#039;&amp;#039; files. This is not available for variable length record files.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must match the length of the records as specified by [[#OPEN | OPEN]].&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Memory&amp;#039;&amp;#039;&amp;#039; flag determines whether the data are read into CPU (0) or VDP (not 0) memory.&lt;br /&gt;
&lt;br /&gt;
After the READ operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of read bytes&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Next record number&amp;#039;&amp;#039;&amp;#039; applies for fixed record length files only.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== WRITE ===&lt;br /&gt;
&lt;br /&gt;
Writes a record to a record-oriented file. For flat memory image files, [[#SAVE | SAVE]] must be used. &lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;03&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| Memory&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of bytes to write&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Use the &amp;#039;&amp;#039;&amp;#039;Record number&amp;#039;&amp;#039;&amp;#039; field to specify the desired record in &amp;#039;&amp;#039;&amp;#039;fixed record length&amp;#039;&amp;#039;&amp;#039; files. This is not available for variable length record files.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must match the length of the records as specified by [[#OPEN | OPEN]].&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Memory&amp;#039;&amp;#039;&amp;#039; flag determines whether the data written from CPU (0) or VDP (not 0) memory.&lt;br /&gt;
&lt;br /&gt;
After the WRITE operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of read bytes&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Next record number&amp;#039;&amp;#039;&amp;#039; applies for fixed record length files only.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== RESTORE ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== LOAD ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== SAVE ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== DELETE ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== SCRATCH ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== STATUS ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== BREAD ===&lt;br /&gt;
&lt;br /&gt;
Reads a sector from a file or from a device. This is a level-2 operation, that is, it accesses the device based on sector addressing (or sectors of a file). BREAD allows for two operations:&lt;br /&gt;
&lt;br /&gt;
* Read sectors from a file.&lt;br /&gt;
* Read sectors from a device.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;0A&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Sector offset LSW&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| CPU / VDP&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Number of sectors&lt;br /&gt;
| Sector offset MSB&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The sector offset is defined by byte 0E as the most significant byte (value*65536) plus the word in bytes 06 and 07.&lt;br /&gt;
&lt;br /&gt;
Byte 0A specifies whether the buffer address belongs to the CPU (0) or the VDP (non-zero) address space.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Read from file&amp;#039;&amp;#039;&amp;#039;: When a file is addressed, the operation reads a sequence of sectors from the file. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the number of the first sector of the file, counting from 0. The physical locations of the sectors are subject to the sector allocation on the device.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Read from device&amp;#039;&amp;#039;&amp;#039;: When a device is addressed, the operation reads a sequence of sectors from the device. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the physical sector number.&lt;br /&gt;
&lt;br /&gt;
After the BREAD operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Updated buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Updated sector offset LSW&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Remaining sectors&lt;br /&gt;
| Updated sector offset MSB&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Error codes&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;60&amp;#039;&amp;#039;&amp;#039;: Bad operation; happens when the device is not a block device.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;A0&amp;#039;&amp;#039;&amp;#039;: Read beyond end of file or device. &lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;C0&amp;#039;&amp;#039;&amp;#039;: Media error. The system was unable to read the sector although it should be readable.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;E0&amp;#039;&amp;#039;&amp;#039;: General error; happens when the device does not contain the specified file.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Updated buffer address&amp;#039;&amp;#039;&amp;#039; points to the memory address after the sectors were read (e.g. when 4 sectors are read, the pointer is increased by 4*256 = 1024).&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Updated sector offset&amp;#039;&amp;#039;&amp;#039; is the first sector that has not been read due to an error condition.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Remaining sectors&amp;#039;&amp;#039;&amp;#039; is the number of unread sectors due to an error condition.&lt;br /&gt;
&lt;br /&gt;
=== BWRITE ===&lt;br /&gt;
&lt;br /&gt;
Writes a sector to a file or to a device. This is a level-2 operation, that is, it accesses the device based on sector addressing (or sectors of a file). BWRITE allows for three operations:&lt;br /&gt;
&lt;br /&gt;
* Create a new, empty file.&lt;br /&gt;
* Write sectors to a file.&lt;br /&gt;
* Write sectors to a device.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;0B&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Sector offset&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of sectors&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Create a file&amp;#039;&amp;#039;&amp;#039;: When a file is addressed, and the number of sectors is 0, a new file is created. The contents of the file should be written by BWRITE on the same file with a sector count greater than 0.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Write to file&amp;#039;&amp;#039;&amp;#039;: When a file is addressed, the operation writes a sequence of sectors to the file. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the number of the first sector of the file, counting from 0. The physical locations of the sectors are subject to the sector allocation on the device; in particular, when fragmentation is required, the sectors are written to the new fragment.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Writing to device&amp;#039;&amp;#039;&amp;#039;: When a device is addressed, the operation writes a sequence of sectors to the device. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the physical sector number.&lt;br /&gt;
&lt;br /&gt;
After the BWRITE operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Updated buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next unwritten sector&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| 0&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Number of unwritten sectors&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Updated buffer address&amp;#039;&amp;#039;&amp;#039; points to the memory address after the sectors were written (e.g. when 4 sectors are written, the pointer is increased by 4*256 = 1024).&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Next unwritten sector&amp;#039;&amp;#039;&amp;#039; is the first sector that has not been written due to an error condition.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Number of unwritten sectors&amp;#039;&amp;#039;&amp;#039; is the number of unwritten sectors due to an error condition.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;warn&amp;quot;&amp;gt;Unless you want to write directly to the device, make sure that the PAB contains a file name that points to a file, not to a device. For example, when the file name has been created by the program, and for some reason the file name is empty (like &amp;quot;HDS1.&amp;quot;), the following write operation will overwrite the volume information block and the allocation tables and &amp;#039;&amp;#039;&amp;#039;destroy the target device filesystem&amp;#039;&amp;#039;&amp;#039;.&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Tick here: [ ] Yes, I fully understood the danger of this operation.&lt;br /&gt;
&lt;br /&gt;
(The author lost a complete hard disk by this incident, and was lucky to have a backup.)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== PROTECT ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== RENAME ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== FORMAT ===&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=GeneveOS_Device_Operation&amp;diff=50635</id>
		<title>GeneveOS Device Operation</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=GeneveOS_Device_Operation&amp;diff=50635"/>
		<updated>2022-04-24T18:25:00Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* BREAD */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Accessing devices (floppy disk, hard disk, serial connector, printer) is possible in GeneveOS via XOP calls.&lt;br /&gt;
&lt;br /&gt;
== User-task XOPs ==&lt;br /&gt;
&lt;br /&gt;
User-task XOPs are available for use in application programs. Here is a typical example:&lt;br /&gt;
&lt;br /&gt;
 PABADD EQU  &amp;gt;F180   &lt;br /&gt;
 FILE   DATA 8&lt;br /&gt;
 ...&lt;br /&gt;
        LI   R0,PABADD&lt;br /&gt;
        XOP  @FILE,0&lt;br /&gt;
        MOVB @PABADD+2,R0&lt;br /&gt;
        JNE  ERROR&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
Similar as with the TI-99/4A device service routine concept (DSR), a Peripheral Access Block (PAB) must be set up prior to invoking the XOP.&lt;br /&gt;
&lt;br /&gt;
=== Device Service Routine Call ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Pointer to PAB || -&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The actual operation is contained in the Peripheral Access Block (PAB).&lt;br /&gt;
&lt;br /&gt;
=== Peripheral Access Block ===&lt;br /&gt;
&lt;br /&gt;
While the TI-99/4A DSRs expect the PAB to be stored in VDP RAM, the Geneve OS DSRs use CPU RAM for the PAB, which means the PAB need not be copied to the video RAM before use. Also, you can choose to have the I/O buffers in VDP or CPU RAM.&lt;br /&gt;
&lt;br /&gt;
The general layout of the PAB is as follows:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| Opcode &lt;br /&gt;
| Mode&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| Memory type&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Character count&lt;br /&gt;
| Status byte&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Note that the buffer addresses are 21-bit wide, represented by the [[MDOS Memory Management Functions#Local_.28virtual.29_pages | virtual address]] within the calling task. The address for this buffer should be derived from [[MDOS Memory Management Functions | memory allocation]] operations that were invoked earlier. &lt;br /&gt;
&lt;br /&gt;
After the operation, the PAB contains information about the results of the operation.&lt;br /&gt;
&lt;br /&gt;
=== Mode ===&lt;br /&gt;
&lt;br /&gt;
The mode bits are used to determine the operation direction (in/out/update/append), the encoding (display/internal), the structure (fixed/variable) and the access (sequential/relative).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainbits&amp;quot; style=&amp;quot;width:64%&amp;quot;&lt;br /&gt;
! 0&lt;br /&gt;
! 1&lt;br /&gt;
! 2&lt;br /&gt;
! 3&lt;br /&gt;
! 4&lt;br /&gt;
! 5&lt;br /&gt;
! 6&lt;br /&gt;
! 7&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width:24%&amp;quot; rowspan=&amp;quot;4&amp;quot; colspan=&amp;quot;3&amp;quot; | Unused (000)&lt;br /&gt;
| style=&amp;quot;width:8%&amp;quot; rowspan=&amp;quot;2&amp;quot; | Fixed=0&lt;br /&gt;
| style=&amp;quot;width:8%&amp;quot; rowspan=&amp;quot;2&amp;quot; | Display=0&lt;br /&gt;
| style=&amp;quot;width:16%&amp;quot; colspan=&amp;quot;2&amp;quot; | Update=00&lt;br /&gt;
| style=&amp;quot;width:8%&amp;quot; rowspan=&amp;quot;2&amp;quot; | Sequential=0&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Output=01&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Variable=1 &lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Internal=1&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Input=10&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Relative=1&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Append=11&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Error code ===&lt;br /&gt;
&lt;br /&gt;
The error code is an eight bit word with the following structure:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainbits&amp;quot; style=&amp;quot;float:left&amp;quot;&lt;br /&gt;
! 0&lt;br /&gt;
! 1&lt;br /&gt;
! 2&lt;br /&gt;
! 3&lt;br /&gt;
! 4&lt;br /&gt;
! 5&lt;br /&gt;
! 6&lt;br /&gt;
! 7&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot; | Detail code for error 7&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;float:left&amp;quot;&lt;br /&gt;
! Number&lt;br /&gt;
! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| No such device&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Write-protected&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Illegal open attribute&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Illegal operation&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Out of buffer space&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Read beyond EOF&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Device error&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| File error&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Operations ==&lt;br /&gt;
&lt;br /&gt;
=== OPEN ===&lt;br /&gt;
&lt;br /&gt;
Open a record-oriented file. A file must be opened before it can be read from or written to. This operation is not required for flat memory image files.&lt;br /&gt;
&lt;br /&gt;
The PAB must be set up prior to invoking the operation:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;00&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Records to reserve&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Open new file&amp;#039;&amp;#039;&amp;#039;: The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must be set to a value greater than 0. This will become the length of the records of the new file.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Open existing file&amp;#039;&amp;#039;&amp;#039;: The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must match the length of the records. If the length is unknown, 0 may be passed.&lt;br /&gt;
&lt;br /&gt;
After the OPEN operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Actual Record Length&lt;br /&gt;
| -&lt;br /&gt;
| 0&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | True Record Length&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Actual Record Length&amp;#039;&amp;#039;&amp;#039; is the length of the records of the existing file. When 0 was passed on the call, this delivers the record length of the file; otherwise this is the value that was passed for the call. The &amp;#039;&amp;#039;&amp;#039;True Record Length&amp;#039;&amp;#039;&amp;#039; is the record length of the file when it already exists.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== CLOSE ===&lt;br /&gt;
&lt;br /&gt;
Closes a record-oriented file. A file must be closed after being used. After closing, no further read or write operations may be invoked on the file. This operation is not required for flat memory image files.&lt;br /&gt;
&lt;br /&gt;
The PAB must be set up prior to invoking the operation:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;01&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
After the CLOSE operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== READ ===&lt;br /&gt;
&lt;br /&gt;
Reads a record from a record-oriented file. For flat memory image files, [[#LOAD | LOAD]] must be used. &lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;02&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| Memory&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Use the &amp;#039;&amp;#039;&amp;#039;Record number&amp;#039;&amp;#039;&amp;#039; field to specify the desired record in &amp;#039;&amp;#039;&amp;#039;fixed record length&amp;#039;&amp;#039;&amp;#039; files. This is not available for variable length record files.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must match the length of the records as specified by [[#OPEN | OPEN]].&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Memory&amp;#039;&amp;#039;&amp;#039; flag determines whether the data are read into CPU (0) or VDP (not 0) memory.&lt;br /&gt;
&lt;br /&gt;
After the READ operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of read bytes&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Next record number&amp;#039;&amp;#039;&amp;#039; applies for fixed record length files only.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== WRITE ===&lt;br /&gt;
&lt;br /&gt;
Writes a record to a record-oriented file. For flat memory image files, [[#SAVE | SAVE]] must be used. &lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;03&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| Memory&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of bytes to write&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Use the &amp;#039;&amp;#039;&amp;#039;Record number&amp;#039;&amp;#039;&amp;#039; field to specify the desired record in &amp;#039;&amp;#039;&amp;#039;fixed record length&amp;#039;&amp;#039;&amp;#039; files. This is not available for variable length record files.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must match the length of the records as specified by [[#OPEN | OPEN]].&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Memory&amp;#039;&amp;#039;&amp;#039; flag determines whether the data written from CPU (0) or VDP (not 0) memory.&lt;br /&gt;
&lt;br /&gt;
After the WRITE operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of read bytes&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Next record number&amp;#039;&amp;#039;&amp;#039; applies for fixed record length files only.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== RESTORE ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== LOAD ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== SAVE ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== DELETE ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== SCRATCH ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== STATUS ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== BREAD ===&lt;br /&gt;
&lt;br /&gt;
Reads a sector from a file or from a device. This is a level-2 operation, that is, it accesses the device based on sector addressing (or sectors of a file). BREAD allows for two operations:&lt;br /&gt;
&lt;br /&gt;
* Read sectors from a file.&lt;br /&gt;
* Read sectors from a device.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;0A&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Sector offset LSW&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| CPU / VDP&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Number of sectors&lt;br /&gt;
| Sector offset MSB&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The sector offset is defined by byte 0E as the most significant byte (value*65536) plus the word in bytes 06 and 07.&lt;br /&gt;
&lt;br /&gt;
Byte 0A specifies whether the buffer address belongs to the CPU (0) or the VDP (non-zero) address space.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Read from file&amp;#039;&amp;#039;&amp;#039;: When a file is addressed, the operation reads a sequence of sectors from the file. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the number of the first sector of the file, counting from 0. The physical locations of the sectors are subject to the sector allocation on the device.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Read from device&amp;#039;&amp;#039;&amp;#039;: When a device is addressed, the operation reads a sequence of sectors from the device. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the physical sector number.&lt;br /&gt;
&lt;br /&gt;
After the BREAD operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Updated buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Updated sector offset LSW&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Remaining sectors&lt;br /&gt;
| Updated sector offset MSB&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Updated buffer address&amp;#039;&amp;#039;&amp;#039; points to the memory address after the sectors were read (e.g. when 4 sectors are read, the pointer is increased by 4*256 = 1024).&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Updated sector offset&amp;#039;&amp;#039;&amp;#039; is the first sector that has not been read due to an error condition.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Remaining sectors&amp;#039;&amp;#039;&amp;#039; is the number of unread sectors due to an error condition.&lt;br /&gt;
&lt;br /&gt;
=== BWRITE ===&lt;br /&gt;
&lt;br /&gt;
Writes a sector to a file or to a device. This is a level-2 operation, that is, it accesses the device based on sector addressing (or sectors of a file). BWRITE allows for three operations:&lt;br /&gt;
&lt;br /&gt;
* Create a new, empty file.&lt;br /&gt;
* Write sectors to a file.&lt;br /&gt;
* Write sectors to a device.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;0B&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Sector offset&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of sectors&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Create a file&amp;#039;&amp;#039;&amp;#039;: When a file is addressed, and the number of sectors is 0, a new file is created. The contents of the file should be written by BWRITE on the same file with a sector count greater than 0.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Write to file&amp;#039;&amp;#039;&amp;#039;: When a file is addressed, the operation writes a sequence of sectors to the file. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the number of the first sector of the file, counting from 0. The physical locations of the sectors are subject to the sector allocation on the device; in particular, when fragmentation is required, the sectors are written to the new fragment.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Writing to device&amp;#039;&amp;#039;&amp;#039;: When a device is addressed, the operation writes a sequence of sectors to the device. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the physical sector number.&lt;br /&gt;
&lt;br /&gt;
After the BWRITE operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Updated buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next unwritten sector&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| 0&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Number of unwritten sectors&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Updated buffer address&amp;#039;&amp;#039;&amp;#039; points to the memory address after the sectors were written (e.g. when 4 sectors are written, the pointer is increased by 4*256 = 1024).&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Next unwritten sector&amp;#039;&amp;#039;&amp;#039; is the first sector that has not been written due to an error condition.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Number of unwritten sectors&amp;#039;&amp;#039;&amp;#039; is the number of unwritten sectors due to an error condition.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;warn&amp;quot;&amp;gt;Unless you want to write directly to the device, make sure that the PAB contains a file name that points to a file, not to a device. For example, when the file name has been created by the program, and for some reason the file name is empty (like &amp;quot;HDS1.&amp;quot;), the following write operation will overwrite the volume information block and the allocation tables and &amp;#039;&amp;#039;&amp;#039;destroy the target device filesystem&amp;#039;&amp;#039;&amp;#039;.&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Tick here: [ ] Yes, I fully understood the danger of this operation.&lt;br /&gt;
&lt;br /&gt;
(The author lost a complete hard disk by this incident, and was lucky to have a backup.)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== PROTECT ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== RENAME ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== FORMAT ===&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=GeneveOS_Device_Operation&amp;diff=50634</id>
		<title>GeneveOS Device Operation</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=GeneveOS_Device_Operation&amp;diff=50634"/>
		<updated>2022-04-24T18:01:59Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* BREAD */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Accessing devices (floppy disk, hard disk, serial connector, printer) is possible in GeneveOS via XOP calls.&lt;br /&gt;
&lt;br /&gt;
== User-task XOPs ==&lt;br /&gt;
&lt;br /&gt;
User-task XOPs are available for use in application programs. Here is a typical example:&lt;br /&gt;
&lt;br /&gt;
 PABADD EQU  &amp;gt;F180   &lt;br /&gt;
 FILE   DATA 8&lt;br /&gt;
 ...&lt;br /&gt;
        LI   R0,PABADD&lt;br /&gt;
        XOP  @FILE,0&lt;br /&gt;
        MOVB @PABADD+2,R0&lt;br /&gt;
        JNE  ERROR&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
Similar as with the TI-99/4A device service routine concept (DSR), a Peripheral Access Block (PAB) must be set up prior to invoking the XOP.&lt;br /&gt;
&lt;br /&gt;
=== Device Service Routine Call ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Pointer to PAB || -&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The actual operation is contained in the Peripheral Access Block (PAB).&lt;br /&gt;
&lt;br /&gt;
=== Peripheral Access Block ===&lt;br /&gt;
&lt;br /&gt;
While the TI-99/4A DSRs expect the PAB to be stored in VDP RAM, the Geneve OS DSRs use CPU RAM for the PAB, which means the PAB need not be copied to the video RAM before use. Also, you can choose to have the I/O buffers in VDP or CPU RAM.&lt;br /&gt;
&lt;br /&gt;
The general layout of the PAB is as follows:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| Opcode &lt;br /&gt;
| Mode&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| Memory type&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Character count&lt;br /&gt;
| Status byte&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Note that the buffer addresses are 21-bit wide, represented by the [[MDOS Memory Management Functions#Local_.28virtual.29_pages | virtual address]] within the calling task. The address for this buffer should be derived from [[MDOS Memory Management Functions | memory allocation]] operations that were invoked earlier. &lt;br /&gt;
&lt;br /&gt;
After the operation, the PAB contains information about the results of the operation.&lt;br /&gt;
&lt;br /&gt;
=== Mode ===&lt;br /&gt;
&lt;br /&gt;
The mode bits are used to determine the operation direction (in/out/update/append), the encoding (display/internal), the structure (fixed/variable) and the access (sequential/relative).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainbits&amp;quot; style=&amp;quot;width:64%&amp;quot;&lt;br /&gt;
! 0&lt;br /&gt;
! 1&lt;br /&gt;
! 2&lt;br /&gt;
! 3&lt;br /&gt;
! 4&lt;br /&gt;
! 5&lt;br /&gt;
! 6&lt;br /&gt;
! 7&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width:24%&amp;quot; rowspan=&amp;quot;4&amp;quot; colspan=&amp;quot;3&amp;quot; | Unused (000)&lt;br /&gt;
| style=&amp;quot;width:8%&amp;quot; rowspan=&amp;quot;2&amp;quot; | Fixed=0&lt;br /&gt;
| style=&amp;quot;width:8%&amp;quot; rowspan=&amp;quot;2&amp;quot; | Display=0&lt;br /&gt;
| style=&amp;quot;width:16%&amp;quot; colspan=&amp;quot;2&amp;quot; | Update=00&lt;br /&gt;
| style=&amp;quot;width:8%&amp;quot; rowspan=&amp;quot;2&amp;quot; | Sequential=0&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Output=01&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Variable=1 &lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Internal=1&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Input=10&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Relative=1&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Append=11&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Error code ===&lt;br /&gt;
&lt;br /&gt;
The error code is an eight bit word with the following structure:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainbits&amp;quot; style=&amp;quot;float:left&amp;quot;&lt;br /&gt;
! 0&lt;br /&gt;
! 1&lt;br /&gt;
! 2&lt;br /&gt;
! 3&lt;br /&gt;
! 4&lt;br /&gt;
! 5&lt;br /&gt;
! 6&lt;br /&gt;
! 7&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot; | Detail code for error 7&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;float:left&amp;quot;&lt;br /&gt;
! Number&lt;br /&gt;
! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| No such device&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Write-protected&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Illegal open attribute&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Illegal operation&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Out of buffer space&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Read beyond EOF&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Device error&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| File error&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Operations ==&lt;br /&gt;
&lt;br /&gt;
=== OPEN ===&lt;br /&gt;
&lt;br /&gt;
Open a record-oriented file. A file must be opened before it can be read from or written to. This operation is not required for flat memory image files.&lt;br /&gt;
&lt;br /&gt;
The PAB must be set up prior to invoking the operation:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;00&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Records to reserve&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Open new file&amp;#039;&amp;#039;&amp;#039;: The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must be set to a value greater than 0. This will become the length of the records of the new file.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Open existing file&amp;#039;&amp;#039;&amp;#039;: The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must match the length of the records. If the length is unknown, 0 may be passed.&lt;br /&gt;
&lt;br /&gt;
After the OPEN operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Actual Record Length&lt;br /&gt;
| -&lt;br /&gt;
| 0&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | True Record Length&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Actual Record Length&amp;#039;&amp;#039;&amp;#039; is the length of the records of the existing file. When 0 was passed on the call, this delivers the record length of the file; otherwise this is the value that was passed for the call. The &amp;#039;&amp;#039;&amp;#039;True Record Length&amp;#039;&amp;#039;&amp;#039; is the record length of the file when it already exists.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== CLOSE ===&lt;br /&gt;
&lt;br /&gt;
Closes a record-oriented file. A file must be closed after being used. After closing, no further read or write operations may be invoked on the file. This operation is not required for flat memory image files.&lt;br /&gt;
&lt;br /&gt;
The PAB must be set up prior to invoking the operation:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;01&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
After the CLOSE operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== READ ===&lt;br /&gt;
&lt;br /&gt;
Reads a record from a record-oriented file. For flat memory image files, [[#LOAD | LOAD]] must be used. &lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;02&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| Memory&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Use the &amp;#039;&amp;#039;&amp;#039;Record number&amp;#039;&amp;#039;&amp;#039; field to specify the desired record in &amp;#039;&amp;#039;&amp;#039;fixed record length&amp;#039;&amp;#039;&amp;#039; files. This is not available for variable length record files.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must match the length of the records as specified by [[#OPEN | OPEN]].&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Memory&amp;#039;&amp;#039;&amp;#039; flag determines whether the data are read into CPU (0) or VDP (not 0) memory.&lt;br /&gt;
&lt;br /&gt;
After the READ operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of read bytes&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Next record number&amp;#039;&amp;#039;&amp;#039; applies for fixed record length files only.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== WRITE ===&lt;br /&gt;
&lt;br /&gt;
Writes a record to a record-oriented file. For flat memory image files, [[#SAVE | SAVE]] must be used. &lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;03&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| Memory&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of bytes to write&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Use the &amp;#039;&amp;#039;&amp;#039;Record number&amp;#039;&amp;#039;&amp;#039; field to specify the desired record in &amp;#039;&amp;#039;&amp;#039;fixed record length&amp;#039;&amp;#039;&amp;#039; files. This is not available for variable length record files.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must match the length of the records as specified by [[#OPEN | OPEN]].&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Memory&amp;#039;&amp;#039;&amp;#039; flag determines whether the data written from CPU (0) or VDP (not 0) memory.&lt;br /&gt;
&lt;br /&gt;
After the WRITE operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of read bytes&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Next record number&amp;#039;&amp;#039;&amp;#039; applies for fixed record length files only.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== RESTORE ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== LOAD ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== SAVE ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== DELETE ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== SCRATCH ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== STATUS ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== BREAD ===&lt;br /&gt;
&lt;br /&gt;
Reads a sector from a file or from a device. This is a level-2 operation, that is, it accesses the device based on sector addressing (or sectors of a file). BREAD allows for two operations:&lt;br /&gt;
&lt;br /&gt;
* Read sectors from a file.&lt;br /&gt;
* Read sectors from a device.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;0A&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Sector offset LSW&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| CPU/VDP&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Number of sectors&lt;br /&gt;
| Sector offset MSB&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The sector offset is defined by byte 0E as the most significant byte (value*65536) plus the word in bytes 06 and 07.&lt;br /&gt;
&lt;br /&gt;
Byte 0A specifies whether the buffer address belongs to the CPU (0) or the VDP (non-zero) address space.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Read from file&amp;#039;&amp;#039;&amp;#039;: When a file is addressed, the operation reads a sequence of sectors from the file. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the number of the first sector of the file, counting from 0. The physical locations of the sectors are subject to the sector allocation on the device.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Read from device&amp;#039;&amp;#039;&amp;#039;: When a device is addressed, the operation reads a sequence of sectors from the device. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the physical sector number.&lt;br /&gt;
&lt;br /&gt;
After the BREAD operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Updated buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Updated sector offset LSW&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Remaining sectors&lt;br /&gt;
| Updated sector offset MSB&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Updated buffer address&amp;#039;&amp;#039;&amp;#039; points to the memory address after the sectors were written (e.g. when 4 sectors are written, the pointer is increased by 4*256 = 1024).&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Next unwritten sector&amp;#039;&amp;#039;&amp;#039; is the first sector that has not been written due to an error condition.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Number of unwritten sectors&amp;#039;&amp;#039;&amp;#039; is the number of unwritten sectors due to an error condition.&lt;br /&gt;
&lt;br /&gt;
=== BWRITE ===&lt;br /&gt;
&lt;br /&gt;
Writes a sector to a file or to a device. This is a level-2 operation, that is, it accesses the device based on sector addressing (or sectors of a file). BWRITE allows for three operations:&lt;br /&gt;
&lt;br /&gt;
* Create a new, empty file.&lt;br /&gt;
* Write sectors to a file.&lt;br /&gt;
* Write sectors to a device.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;0B&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Sector offset&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of sectors&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Create a file&amp;#039;&amp;#039;&amp;#039;: When a file is addressed, and the number of sectors is 0, a new file is created. The contents of the file should be written by BWRITE on the same file with a sector count greater than 0.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Write to file&amp;#039;&amp;#039;&amp;#039;: When a file is addressed, the operation writes a sequence of sectors to the file. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the number of the first sector of the file, counting from 0. The physical locations of the sectors are subject to the sector allocation on the device; in particular, when fragmentation is required, the sectors are written to the new fragment.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Writing to device&amp;#039;&amp;#039;&amp;#039;: When a device is addressed, the operation writes a sequence of sectors to the device. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the physical sector number.&lt;br /&gt;
&lt;br /&gt;
After the BWRITE operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Updated buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next unwritten sector&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| 0&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Number of unwritten sectors&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Updated buffer address&amp;#039;&amp;#039;&amp;#039; points to the memory address after the sectors were written (e.g. when 4 sectors are written, the pointer is increased by 4*256 = 1024).&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Next unwritten sector&amp;#039;&amp;#039;&amp;#039; is the first sector that has not been written due to an error condition.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Number of unwritten sectors&amp;#039;&amp;#039;&amp;#039; is the number of unwritten sectors due to an error condition.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;warn&amp;quot;&amp;gt;Unless you want to write directly to the device, make sure that the PAB contains a file name that points to a file, not to a device. For example, when the file name has been created by the program, and for some reason the file name is empty (like &amp;quot;HDS1.&amp;quot;), the following write operation will overwrite the volume information block and the allocation tables and &amp;#039;&amp;#039;&amp;#039;destroy the target device filesystem&amp;#039;&amp;#039;&amp;#039;.&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Tick here: [ ] Yes, I fully understood the danger of this operation.&lt;br /&gt;
&lt;br /&gt;
(The author lost a complete hard disk by this incident, and was lucky to have a backup.)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== PROTECT ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== RENAME ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== FORMAT ===&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=GeneveOS_Device_Operation&amp;diff=50633</id>
		<title>GeneveOS Device Operation</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=GeneveOS_Device_Operation&amp;diff=50633"/>
		<updated>2022-04-24T17:42:08Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* BREAD */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Accessing devices (floppy disk, hard disk, serial connector, printer) is possible in GeneveOS via XOP calls.&lt;br /&gt;
&lt;br /&gt;
== User-task XOPs ==&lt;br /&gt;
&lt;br /&gt;
User-task XOPs are available for use in application programs. Here is a typical example:&lt;br /&gt;
&lt;br /&gt;
 PABADD EQU  &amp;gt;F180   &lt;br /&gt;
 FILE   DATA 8&lt;br /&gt;
 ...&lt;br /&gt;
        LI   R0,PABADD&lt;br /&gt;
        XOP  @FILE,0&lt;br /&gt;
        MOVB @PABADD+2,R0&lt;br /&gt;
        JNE  ERROR&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
Similar as with the TI-99/4A device service routine concept (DSR), a Peripheral Access Block (PAB) must be set up prior to invoking the XOP.&lt;br /&gt;
&lt;br /&gt;
=== Device Service Routine Call ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Pointer to PAB || -&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The actual operation is contained in the Peripheral Access Block (PAB).&lt;br /&gt;
&lt;br /&gt;
=== Peripheral Access Block ===&lt;br /&gt;
&lt;br /&gt;
While the TI-99/4A DSRs expect the PAB to be stored in VDP RAM, the Geneve OS DSRs use CPU RAM for the PAB, which means the PAB need not be copied to the video RAM before use. Also, you can choose to have the I/O buffers in VDP or CPU RAM.&lt;br /&gt;
&lt;br /&gt;
The general layout of the PAB is as follows:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| Opcode &lt;br /&gt;
| Mode&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| Memory type&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Character count&lt;br /&gt;
| Status byte&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Note that the buffer addresses are 21-bit wide, represented by the [[MDOS Memory Management Functions#Local_.28virtual.29_pages | virtual address]] within the calling task. The address for this buffer should be derived from [[MDOS Memory Management Functions | memory allocation]] operations that were invoked earlier. &lt;br /&gt;
&lt;br /&gt;
After the operation, the PAB contains information about the results of the operation.&lt;br /&gt;
&lt;br /&gt;
=== Mode ===&lt;br /&gt;
&lt;br /&gt;
The mode bits are used to determine the operation direction (in/out/update/append), the encoding (display/internal), the structure (fixed/variable) and the access (sequential/relative).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainbits&amp;quot; style=&amp;quot;width:64%&amp;quot;&lt;br /&gt;
! 0&lt;br /&gt;
! 1&lt;br /&gt;
! 2&lt;br /&gt;
! 3&lt;br /&gt;
! 4&lt;br /&gt;
! 5&lt;br /&gt;
! 6&lt;br /&gt;
! 7&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width:24%&amp;quot; rowspan=&amp;quot;4&amp;quot; colspan=&amp;quot;3&amp;quot; | Unused (000)&lt;br /&gt;
| style=&amp;quot;width:8%&amp;quot; rowspan=&amp;quot;2&amp;quot; | Fixed=0&lt;br /&gt;
| style=&amp;quot;width:8%&amp;quot; rowspan=&amp;quot;2&amp;quot; | Display=0&lt;br /&gt;
| style=&amp;quot;width:16%&amp;quot; colspan=&amp;quot;2&amp;quot; | Update=00&lt;br /&gt;
| style=&amp;quot;width:8%&amp;quot; rowspan=&amp;quot;2&amp;quot; | Sequential=0&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Output=01&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Variable=1 &lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Internal=1&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Input=10&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Relative=1&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Append=11&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Error code ===&lt;br /&gt;
&lt;br /&gt;
The error code is an eight bit word with the following structure:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainbits&amp;quot; style=&amp;quot;float:left&amp;quot;&lt;br /&gt;
! 0&lt;br /&gt;
! 1&lt;br /&gt;
! 2&lt;br /&gt;
! 3&lt;br /&gt;
! 4&lt;br /&gt;
! 5&lt;br /&gt;
! 6&lt;br /&gt;
! 7&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot; | Detail code for error 7&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;float:left&amp;quot;&lt;br /&gt;
! Number&lt;br /&gt;
! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| No such device&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Write-protected&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Illegal open attribute&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Illegal operation&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Out of buffer space&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Read beyond EOF&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Device error&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| File error&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Operations ==&lt;br /&gt;
&lt;br /&gt;
=== OPEN ===&lt;br /&gt;
&lt;br /&gt;
Open a record-oriented file. A file must be opened before it can be read from or written to. This operation is not required for flat memory image files.&lt;br /&gt;
&lt;br /&gt;
The PAB must be set up prior to invoking the operation:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;00&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Records to reserve&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Open new file&amp;#039;&amp;#039;&amp;#039;: The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must be set to a value greater than 0. This will become the length of the records of the new file.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Open existing file&amp;#039;&amp;#039;&amp;#039;: The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must match the length of the records. If the length is unknown, 0 may be passed.&lt;br /&gt;
&lt;br /&gt;
After the OPEN operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Actual Record Length&lt;br /&gt;
| -&lt;br /&gt;
| 0&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | True Record Length&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Actual Record Length&amp;#039;&amp;#039;&amp;#039; is the length of the records of the existing file. When 0 was passed on the call, this delivers the record length of the file; otherwise this is the value that was passed for the call. The &amp;#039;&amp;#039;&amp;#039;True Record Length&amp;#039;&amp;#039;&amp;#039; is the record length of the file when it already exists.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== CLOSE ===&lt;br /&gt;
&lt;br /&gt;
Closes a record-oriented file. A file must be closed after being used. After closing, no further read or write operations may be invoked on the file. This operation is not required for flat memory image files.&lt;br /&gt;
&lt;br /&gt;
The PAB must be set up prior to invoking the operation:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;01&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
After the CLOSE operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== READ ===&lt;br /&gt;
&lt;br /&gt;
Reads a record from a record-oriented file. For flat memory image files, [[#LOAD | LOAD]] must be used. &lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;02&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| Memory&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Use the &amp;#039;&amp;#039;&amp;#039;Record number&amp;#039;&amp;#039;&amp;#039; field to specify the desired record in &amp;#039;&amp;#039;&amp;#039;fixed record length&amp;#039;&amp;#039;&amp;#039; files. This is not available for variable length record files.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must match the length of the records as specified by [[#OPEN | OPEN]].&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Memory&amp;#039;&amp;#039;&amp;#039; flag determines whether the data are read into CPU (0) or VDP (not 0) memory.&lt;br /&gt;
&lt;br /&gt;
After the READ operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of read bytes&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Next record number&amp;#039;&amp;#039;&amp;#039; applies for fixed record length files only.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== WRITE ===&lt;br /&gt;
&lt;br /&gt;
Writes a record to a record-oriented file. For flat memory image files, [[#SAVE | SAVE]] must be used. &lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;03&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| Memory&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of bytes to write&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Use the &amp;#039;&amp;#039;&amp;#039;Record number&amp;#039;&amp;#039;&amp;#039; field to specify the desired record in &amp;#039;&amp;#039;&amp;#039;fixed record length&amp;#039;&amp;#039;&amp;#039; files. This is not available for variable length record files.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must match the length of the records as specified by [[#OPEN | OPEN]].&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Memory&amp;#039;&amp;#039;&amp;#039; flag determines whether the data written from CPU (0) or VDP (not 0) memory.&lt;br /&gt;
&lt;br /&gt;
After the WRITE operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of read bytes&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Next record number&amp;#039;&amp;#039;&amp;#039; applies for fixed record length files only.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== RESTORE ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== LOAD ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== SAVE ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== DELETE ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== SCRATCH ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== STATUS ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== BREAD ===&lt;br /&gt;
&lt;br /&gt;
Reads a sector from a file or from a device. This is a level-2 operation, that is, it accesses the device based on sector addressing (or sectors of a file). BREAD allows for two operations:&lt;br /&gt;
&lt;br /&gt;
* Read sectors from a file.&lt;br /&gt;
* Read sectors from a device.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;0A&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Sector offset&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of sectors&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Read from file&amp;#039;&amp;#039;&amp;#039;: When a file is addressed, the operation reads a sequence of sectors from the file. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the number of the first sector of the file, counting from 0. The physical locations of the sectors are subject to the sector allocation on the device.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Read from device&amp;#039;&amp;#039;&amp;#039;: When a device is addressed, the operation reads a sequence of sectors from the device. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the physical sector number.&lt;br /&gt;
&lt;br /&gt;
After the BREAD operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Updated buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next unwritten sector&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| 0&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Number of unwritten sectors&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Updated buffer address&amp;#039;&amp;#039;&amp;#039; points to the memory address after the sectors were written (e.g. when 4 sectors are written, the pointer is increased by 4*256 = 1024).&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Next unwritten sector&amp;#039;&amp;#039;&amp;#039; is the first sector that has not been written due to an error condition.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Number of unwritten sectors&amp;#039;&amp;#039;&amp;#039; is the number of unwritten sectors due to an error condition.&lt;br /&gt;
&lt;br /&gt;
=== BWRITE ===&lt;br /&gt;
&lt;br /&gt;
Writes a sector to a file or to a device. This is a level-2 operation, that is, it accesses the device based on sector addressing (or sectors of a file). BWRITE allows for three operations:&lt;br /&gt;
&lt;br /&gt;
* Create a new, empty file.&lt;br /&gt;
* Write sectors to a file.&lt;br /&gt;
* Write sectors to a device.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;0B&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Sector offset&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of sectors&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Create a file&amp;#039;&amp;#039;&amp;#039;: When a file is addressed, and the number of sectors is 0, a new file is created. The contents of the file should be written by BWRITE on the same file with a sector count greater than 0.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Write to file&amp;#039;&amp;#039;&amp;#039;: When a file is addressed, the operation writes a sequence of sectors to the file. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the number of the first sector of the file, counting from 0. The physical locations of the sectors are subject to the sector allocation on the device; in particular, when fragmentation is required, the sectors are written to the new fragment.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Writing to device&amp;#039;&amp;#039;&amp;#039;: When a device is addressed, the operation writes a sequence of sectors to the device. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the physical sector number.&lt;br /&gt;
&lt;br /&gt;
After the BWRITE operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Updated buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next unwritten sector&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| 0&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Number of unwritten sectors&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Updated buffer address&amp;#039;&amp;#039;&amp;#039; points to the memory address after the sectors were written (e.g. when 4 sectors are written, the pointer is increased by 4*256 = 1024).&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Next unwritten sector&amp;#039;&amp;#039;&amp;#039; is the first sector that has not been written due to an error condition.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Number of unwritten sectors&amp;#039;&amp;#039;&amp;#039; is the number of unwritten sectors due to an error condition.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;warn&amp;quot;&amp;gt;Unless you want to write directly to the device, make sure that the PAB contains a file name that points to a file, not to a device. For example, when the file name has been created by the program, and for some reason the file name is empty (like &amp;quot;HDS1.&amp;quot;), the following write operation will overwrite the volume information block and the allocation tables and &amp;#039;&amp;#039;&amp;#039;destroy the target device filesystem&amp;#039;&amp;#039;&amp;#039;.&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Tick here: [ ] Yes, I fully understood the danger of this operation.&lt;br /&gt;
&lt;br /&gt;
(The author lost a complete hard disk by this incident, and was lucky to have a backup.)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== PROTECT ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== RENAME ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== FORMAT ===&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=GeneveOS_Device_Operation&amp;diff=50632</id>
		<title>GeneveOS Device Operation</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=GeneveOS_Device_Operation&amp;diff=50632"/>
		<updated>2022-04-24T17:40:35Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* BREAD */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Accessing devices (floppy disk, hard disk, serial connector, printer) is possible in GeneveOS via XOP calls.&lt;br /&gt;
&lt;br /&gt;
== User-task XOPs ==&lt;br /&gt;
&lt;br /&gt;
User-task XOPs are available for use in application programs. Here is a typical example:&lt;br /&gt;
&lt;br /&gt;
 PABADD EQU  &amp;gt;F180   &lt;br /&gt;
 FILE   DATA 8&lt;br /&gt;
 ...&lt;br /&gt;
        LI   R0,PABADD&lt;br /&gt;
        XOP  @FILE,0&lt;br /&gt;
        MOVB @PABADD+2,R0&lt;br /&gt;
        JNE  ERROR&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
Similar as with the TI-99/4A device service routine concept (DSR), a Peripheral Access Block (PAB) must be set up prior to invoking the XOP.&lt;br /&gt;
&lt;br /&gt;
=== Device Service Routine Call ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Pointer to PAB || -&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The actual operation is contained in the Peripheral Access Block (PAB).&lt;br /&gt;
&lt;br /&gt;
=== Peripheral Access Block ===&lt;br /&gt;
&lt;br /&gt;
While the TI-99/4A DSRs expect the PAB to be stored in VDP RAM, the Geneve OS DSRs use CPU RAM for the PAB, which means the PAB need not be copied to the video RAM before use. Also, you can choose to have the I/O buffers in VDP or CPU RAM.&lt;br /&gt;
&lt;br /&gt;
The general layout of the PAB is as follows:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;5%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| Opcode &lt;br /&gt;
| Mode&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| Memory type&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Character count&lt;br /&gt;
| Status byte&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Note that the buffer addresses are 21-bit wide, represented by the [[MDOS Memory Management Functions#Local_.28virtual.29_pages | virtual address]] within the calling task. The address for this buffer should be derived from [[MDOS Memory Management Functions | memory allocation]] operations that were invoked earlier. &lt;br /&gt;
&lt;br /&gt;
After the operation, the PAB contains information about the results of the operation.&lt;br /&gt;
&lt;br /&gt;
=== Mode ===&lt;br /&gt;
&lt;br /&gt;
The mode bits are used to determine the operation direction (in/out/update/append), the encoding (display/internal), the structure (fixed/variable) and the access (sequential/relative).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainbits&amp;quot; style=&amp;quot;width:64%&amp;quot;&lt;br /&gt;
! 0&lt;br /&gt;
! 1&lt;br /&gt;
! 2&lt;br /&gt;
! 3&lt;br /&gt;
! 4&lt;br /&gt;
! 5&lt;br /&gt;
! 6&lt;br /&gt;
! 7&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width:24%&amp;quot; rowspan=&amp;quot;4&amp;quot; colspan=&amp;quot;3&amp;quot; | Unused (000)&lt;br /&gt;
| style=&amp;quot;width:8%&amp;quot; rowspan=&amp;quot;2&amp;quot; | Fixed=0&lt;br /&gt;
| style=&amp;quot;width:8%&amp;quot; rowspan=&amp;quot;2&amp;quot; | Display=0&lt;br /&gt;
| style=&amp;quot;width:16%&amp;quot; colspan=&amp;quot;2&amp;quot; | Update=00&lt;br /&gt;
| style=&amp;quot;width:8%&amp;quot; rowspan=&amp;quot;2&amp;quot; | Sequential=0&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Output=01&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Variable=1 &lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Internal=1&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Input=10&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Relative=1&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Append=11&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Error code ===&lt;br /&gt;
&lt;br /&gt;
The error code is an eight bit word with the following structure:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainbits&amp;quot; style=&amp;quot;float:left&amp;quot;&lt;br /&gt;
! 0&lt;br /&gt;
! 1&lt;br /&gt;
! 2&lt;br /&gt;
! 3&lt;br /&gt;
! 4&lt;br /&gt;
! 5&lt;br /&gt;
! 6&lt;br /&gt;
! 7&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot; | Detail code for error 7&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;float:left&amp;quot;&lt;br /&gt;
! Number&lt;br /&gt;
! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| No such device&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Write-protected&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Illegal open attribute&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Illegal operation&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Out of buffer space&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Read beyond EOF&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Device error&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| File error&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Operations ==&lt;br /&gt;
&lt;br /&gt;
=== OPEN ===&lt;br /&gt;
&lt;br /&gt;
Open a record-oriented file. A file must be opened before it can be read from or written to. This operation is not required for flat memory image files.&lt;br /&gt;
&lt;br /&gt;
The PAB must be set up prior to invoking the operation:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;00&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Records to reserve&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Open new file&amp;#039;&amp;#039;&amp;#039;: The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must be set to a value greater than 0. This will become the length of the records of the new file.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Open existing file&amp;#039;&amp;#039;&amp;#039;: The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must match the length of the records. If the length is unknown, 0 may be passed.&lt;br /&gt;
&lt;br /&gt;
After the OPEN operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Actual Record Length&lt;br /&gt;
| -&lt;br /&gt;
| 0&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | True Record Length&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Actual Record Length&amp;#039;&amp;#039;&amp;#039; is the length of the records of the existing file. When 0 was passed on the call, this delivers the record length of the file; otherwise this is the value that was passed for the call. The &amp;#039;&amp;#039;&amp;#039;True Record Length&amp;#039;&amp;#039;&amp;#039; is the record length of the file when it already exists.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== CLOSE ===&lt;br /&gt;
&lt;br /&gt;
Closes a record-oriented file. A file must be closed after being used. After closing, no further read or write operations may be invoked on the file. This operation is not required for flat memory image files.&lt;br /&gt;
&lt;br /&gt;
The PAB must be set up prior to invoking the operation:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;01&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
After the CLOSE operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== READ ===&lt;br /&gt;
&lt;br /&gt;
Reads a record from a record-oriented file. For flat memory image files, [[#LOAD | LOAD]] must be used. &lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;02&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| Memory&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Use the &amp;#039;&amp;#039;&amp;#039;Record number&amp;#039;&amp;#039;&amp;#039; field to specify the desired record in &amp;#039;&amp;#039;&amp;#039;fixed record length&amp;#039;&amp;#039;&amp;#039; files. This is not available for variable length record files.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must match the length of the records as specified by [[#OPEN | OPEN]].&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Memory&amp;#039;&amp;#039;&amp;#039; flag determines whether the data are read into CPU (0) or VDP (not 0) memory.&lt;br /&gt;
&lt;br /&gt;
After the READ operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of read bytes&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Next record number&amp;#039;&amp;#039;&amp;#039; applies for fixed record length files only.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== WRITE ===&lt;br /&gt;
&lt;br /&gt;
Writes a record to a record-oriented file. For flat memory image files, [[#SAVE | SAVE]] must be used. &lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;03&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| Mode&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Record length&lt;br /&gt;
| Memory&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of bytes to write&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Use the &amp;#039;&amp;#039;&amp;#039;Record number&amp;#039;&amp;#039;&amp;#039; field to specify the desired record in &amp;#039;&amp;#039;&amp;#039;fixed record length&amp;#039;&amp;#039;&amp;#039; files. This is not available for variable length record files.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Record Length&amp;#039;&amp;#039;&amp;#039; must match the length of the records as specified by [[#OPEN | OPEN]].&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Memory&amp;#039;&amp;#039;&amp;#039; flag determines whether the data written from CPU (0) or VDP (not 0) memory.&lt;br /&gt;
&lt;br /&gt;
After the WRITE operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | -&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next record number&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of read bytes&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Next record number&amp;#039;&amp;#039;&amp;#039; applies for fixed record length files only.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== RESTORE ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== LOAD ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== SAVE ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== DELETE ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== SCRATCH ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== STATUS ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== BREAD ===&lt;br /&gt;
&lt;br /&gt;
Reads a sector from a file or from a device. This is a level-2 operation, that is, it accesses the device based on sector addressing (or sectors of a file). BREAD allows for two operations:&lt;br /&gt;
&lt;br /&gt;
* Read sectors from a file.&lt;br /&gt;
* Read sectors from a device.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;0B&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Sector offset&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of sectors&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Read from file&amp;#039;&amp;#039;&amp;#039;: When a file is addressed, the operation reads a sequence of sectors from the file. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the number of the first sector of the file, counting from 0. The physical locations of the sectors are subject to the sector allocation on the device.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Read from device&amp;#039;&amp;#039;&amp;#039;: When a device is addressed, the operation reads a sequence of sectors from the device. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the physical sector number.&lt;br /&gt;
&lt;br /&gt;
After the BREAD operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Updated buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next unwritten sector&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| 0&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Number of unwritten sectors&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Updated buffer address&amp;#039;&amp;#039;&amp;#039; points to the memory address after the sectors were written (e.g. when 4 sectors are written, the pointer is increased by 4*256 = 1024).&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Next unwritten sector&amp;#039;&amp;#039;&amp;#039; is the first sector that has not been written due to an error condition.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Number of unwritten sectors&amp;#039;&amp;#039;&amp;#039; is the number of unwritten sectors due to an error condition.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;warn&amp;quot;&amp;gt;Unless you want to write directly to the device, make sure that the PAB contains a file name that points to a file, not to a device. For example, when the file name has been created by the program, and for some reason the file name is empty (like &amp;quot;HDS1.&amp;quot;), the following write operation will overwrite the volume information block and the allocation tables and &amp;#039;&amp;#039;&amp;#039;destroy the target device filesystem&amp;#039;&amp;#039;&amp;#039;.&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Tick here: [ ] Yes, I fully understood the danger of this operation.&lt;br /&gt;
&lt;br /&gt;
(The author lost a complete hard disk by this incident, and was lucky to have a backup.)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== BWRITE ===&lt;br /&gt;
&lt;br /&gt;
Writes a sector to a file or to a device. This is a level-2 operation, that is, it accesses the device based on sector addressing (or sectors of a file). BWRITE allows for three operations:&lt;br /&gt;
&lt;br /&gt;
* Create a new, empty file.&lt;br /&gt;
* Write sectors to a file.&lt;br /&gt;
* Write sectors to a device.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;IN&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;0B&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Sector offset&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Number of sectors&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Create a file&amp;#039;&amp;#039;&amp;#039;: When a file is addressed, and the number of sectors is 0, a new file is created. The contents of the file should be written by BWRITE on the same file with a sector count greater than 0.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Write to file&amp;#039;&amp;#039;&amp;#039;: When a file is addressed, the operation writes a sequence of sectors to the file. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the number of the first sector of the file, counting from 0. The physical locations of the sectors are subject to the sector allocation on the device; in particular, when fragmentation is required, the sectors are written to the new fragment.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Writing to device&amp;#039;&amp;#039;&amp;#039;: When a device is addressed, the operation writes a sequence of sectors to the device. The &amp;#039;&amp;#039;&amp;#039;Sector offset&amp;#039;&amp;#039;&amp;#039; is the physical sector number.&lt;br /&gt;
&lt;br /&gt;
After the BWRITE operation has completed, the PAB delivers information about the result:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;OUT&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot; style=&amp;quot;margin-left:1%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 00 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 01 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 02 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 03 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 04 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 05 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 06 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 07 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 08 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 09 &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0A &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0B &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0C &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0D &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0E &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 0F &lt;br /&gt;
! width=&amp;quot;4%&amp;quot; | 10 ...&lt;br /&gt;
|-&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| Error code&lt;br /&gt;
| colspan=&amp;quot;3&amp;quot; | Updated buffer address&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Next unwritten sector&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | -&lt;br /&gt;
| -&lt;br /&gt;
| 0&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | Number of unwritten sectors&lt;br /&gt;
| -&lt;br /&gt;
| Name length&lt;br /&gt;
| Name&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Updated buffer address&amp;#039;&amp;#039;&amp;#039; points to the memory address after the sectors were written (e.g. when 4 sectors are written, the pointer is increased by 4*256 = 1024).&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Next unwritten sector&amp;#039;&amp;#039;&amp;#039; is the first sector that has not been written due to an error condition.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Number of unwritten sectors&amp;#039;&amp;#039;&amp;#039; is the number of unwritten sectors due to an error condition.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;warn&amp;quot;&amp;gt;Unless you want to write directly to the device, make sure that the PAB contains a file name that points to a file, not to a device. For example, when the file name has been created by the program, and for some reason the file name is empty (like &amp;quot;HDS1.&amp;quot;), the following write operation will overwrite the volume information block and the allocation tables and &amp;#039;&amp;#039;&amp;#039;destroy the target device filesystem&amp;#039;&amp;#039;&amp;#039;.&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Tick here: [ ] Yes, I fully understood the danger of this operation.&lt;br /&gt;
&lt;br /&gt;
(The author lost a complete hard disk by this incident, and was lucky to have a backup.)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== PROTECT ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== RENAME ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;margin-bottom:6em&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== FORMAT ===&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=GeneveOS_XOP_Definitions&amp;diff=50631</id>
		<title>GeneveOS XOP Definitions</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=GeneveOS_XOP_Definitions&amp;diff=50631"/>
		<updated>2022-04-24T17:28:53Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;GeneveOS makes frequent use of XOPs and offers them for user programs. An XOP (extended operation) is a special command of the TMS processor family which causes a [[Terminology#C|context switch]], transferring control to a location that is specified in a table.&lt;br /&gt;
&lt;br /&gt;
Compared to common architecture concepts, the XOP is TI&amp;#039;s way of implementing a &amp;#039;&amp;#039;&amp;#039;system call&amp;#039;&amp;#039;&amp;#039;. &lt;br /&gt;
&lt;br /&gt;
The XOP instruction takes two arguments; the first delivers data for the call, the second is a number from 0 to 15 and indicates the &amp;#039;&amp;#039;&amp;#039;XOP number&amp;#039;&amp;#039;&amp;#039;. In GeneveOS, all system calls are XOP 0 with specific arguments:&lt;br /&gt;
&lt;br /&gt;
 ARGUM  DATA &amp;lt;number&amp;gt;&lt;br /&gt;
        ...&lt;br /&gt;
        XOP  @ARGUM,0&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot;&lt;br /&gt;
! XOP argument&lt;br /&gt;
! Category&lt;br /&gt;
|-&lt;br /&gt;
| 5 || [[Geneve keyboard control | Keyboard ]]&lt;br /&gt;
|-&lt;br /&gt;
| 6 || [[GeneveOS Video Interface | Video display ]]&lt;br /&gt;
|-&lt;br /&gt;
| 7 || [[GeneveOS Memory Management Functions | Memory Management ]]&lt;br /&gt;
|-&lt;br /&gt;
| 8 || [[GeneveOS Device Operation | Devices (Files) ]]&lt;br /&gt;
|-&lt;br /&gt;
| 9 || [[GeneveOS Utility Functions | Utility functions]]&lt;br /&gt;
|-&lt;br /&gt;
| 10 || Mathematical functions&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[Category:MDOS]]&lt;br /&gt;
[[Category:Geneve]]&lt;br /&gt;
[[Category:Programming]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Geneve_Memory_Management_Functions&amp;diff=50630</id>
		<title>Geneve Memory Management Functions</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Geneve_Memory_Management_Functions&amp;diff=50630"/>
		<updated>2022-04-24T17:27:15Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: Mizapf moved page Geneve Memory Management Functions to GeneveOS Memory Management Functions&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[GeneveOS Memory Management Functions]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=GeneveOS_Memory_Management_Functions&amp;diff=50629</id>
		<title>GeneveOS Memory Management Functions</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=GeneveOS_Memory_Management_Functions&amp;diff=50629"/>
		<updated>2022-04-24T17:27:14Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: Mizapf moved page Geneve Memory Management Functions to GeneveOS Memory Management Functions&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Memory management in MDOS is available via XOP 7. In the MDOS sources the relevant files are &amp;#039;&amp;#039;manage2s&amp;#039;&amp;#039; and &amp;#039;&amp;#039;manage2t&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Fast RAM&amp;#039;&amp;#039;&amp;#039; is all memory with 0 wait states (SRAM), while &amp;#039;&amp;#039;&amp;#039;Slow RAM&amp;#039;&amp;#039;&amp;#039; is 1 wait state RAM, offered by the DRAM chips. The on-chip RAM is not considered here as it is not manageable by MDOS. The 256 bytes of on-chip RAM are always visible in the logical address area F000-F0FB and FFFC-FFFF. Any page that is mapped to the E000-FFFF area will be overwritten by operations in that area; this must be considered when using the E000-FFFF area.&lt;br /&gt;
&lt;br /&gt;
Two classes of memory management functions are available: XOPs for user tasks, and XOPs exclusively used by the operating system.&lt;br /&gt;
&lt;br /&gt;
== Introduction and terminology ==&lt;br /&gt;
&lt;br /&gt;
As it was common with TI application programming, buffers were usually placed statically in the program, that is, a portion of memory (like 256 bytes) was reserved in the program code to be used for storing data. Assembly language offers the [https://en.wikipedia.org/wiki/.bss BSS and BES] directives for this purpose. This is not recommended when the buffer is very big, like 12 KiB, since that space will bloat the program code that must be loaded on application start. It is impossible for even larger buffer spaces like 100 KiB. In that cases, dynamic allocation is the typical solution also known from other systems.&lt;br /&gt;
&lt;br /&gt;
The Geneve offers 512 KiB of slow RAM and another 32 KiB of fast RAM. Starting from MDOS 2.5, the fast RAM must be [[Geneve_9640#Memory_Expansion | expanded]] to 64 KiB. Obviously, this memory can only be effectively used with the help of memory management functions.&lt;br /&gt;
&lt;br /&gt;
=== Logical address space and execution pages ===&lt;br /&gt;
&lt;br /&gt;
The logical address space are the addresses as seen from the CPU. The Geneve uses a 16-bit address bus which means that the logical address space is 64 KiB large. The CPU itself is not capable of addressing more memory, which is a limitation for application design. It essentially means that a &amp;#039;&amp;#039;transparent&amp;#039;&amp;#039; access to more than 64 KiB is impossible.  The CPU is not capable of representing addresses outside this area.&lt;br /&gt;
&lt;br /&gt;
The connection between execution pages and the logical addresses can be seen here:&lt;br /&gt;
&lt;br /&gt;
[[file:executionpage.png|650px]]&lt;br /&gt;
&lt;br /&gt;
Execution pages are associated to logical addresses in a fixed way; the logical address allows to easily calculate the execution page: The first three bits determine the execution page. The remaining 13 bits are the 8 KiB memory space within this execution page.&lt;br /&gt;
&lt;br /&gt;
=== Physical address space and physical pages ===&lt;br /&gt;
&lt;br /&gt;
The physical address space comprises the complete memory area of the Geneve from the hardware viewpoint. There is no consideration of allocation to tasks. The physical address space consists of 256 &amp;#039;&amp;#039;&amp;#039;pages&amp;#039;&amp;#039;&amp;#039; of 8 KiB each, together 2 MiB. &lt;br /&gt;
&lt;br /&gt;
[[File:physicalpages.png]]&lt;br /&gt;
&lt;br /&gt;
The first 64 pages are the DRAM area (the slow RAM); from page number E8 to EF we find 8 pages of fast SRAM. The original configuration of the Geneve only included 32 KiB of SRAM, but in most cases this was expanded to 64 KiB. From page F0 to FF we find the Boot ROM; there is room for 128 KiB, but only 16 KiB are actually used, since the page address is not completely decoded in this area: All even pages are the same page, and all odd pages are the same page as well. &lt;br /&gt;
&lt;br /&gt;
Physical addresses can be represented as shown here:&lt;br /&gt;
&lt;br /&gt;
[[File:physicaladd.png]]&lt;br /&gt;
&lt;br /&gt;
Accordingly, physical addresses range from &amp;#039;&amp;#039;&amp;#039;000000&amp;#039;&amp;#039;&amp;#039; to &amp;#039;&amp;#039;&amp;#039;1FFFFF&amp;#039;&amp;#039;&amp;#039;. The TMS 9995, however, cannot use physical addresses since they do not match the 16 bit width of the address bus. The Geneve architecture includes a [[Geneve paged memory organization | memory mapper]] which is used to map the logical address space of the CPU to the physical address space. So when the CPU accesses the logical address A080 the mapper determines which physical address this corresponds with. For instance, if the mapper contains the value 2C for execution page 5, the logical address A080 maps to physical address 058080.&lt;br /&gt;
&lt;br /&gt;
=== Local (virtual) pages ===&lt;br /&gt;
&lt;br /&gt;
MDOS allows for multitasking. In principle, multitasking is possible whenever we have means of passing control between independent applications. This can be done explicitly (by &amp;#039;&amp;#039;yielding&amp;#039;&amp;#039;) or timer-controlled. Apart from that, when several tasks are running in the system we have to take care that each task gets a part of the global memory area exclusively for itself. But how can we know which application uses which part of the physical memory?&lt;br /&gt;
&lt;br /&gt;
Consequently, access to memory must be &amp;quot;virtualized&amp;quot;; we do not allow a direct access to the physical memory but map &amp;quot;local&amp;quot; or &amp;quot;virtual&amp;quot; addresses to a physical addresses. This indirection has an important advantage in multitasking systems: Every task may run as if it were the only one in the system. Although several tasks may access memory at the same local address, the actual physical memory is different. &lt;br /&gt;
&lt;br /&gt;
[[file:virtualpages.png]]&lt;br /&gt;
&lt;br /&gt;
The illustration shows how the &amp;quot;local pages&amp;quot; of the task are mapped to physical pages. We see that a task may use up to 256 local pages, covering the complete physical space. This is an untypical, extreme case, though. A task should only make use of those pages it really needs. This means that many, in fact most of the local pages are unassigned. &lt;br /&gt;
&lt;br /&gt;
In this example, another task has been loaded in the system, but although some of the local pages it uses are also used by the other task, they access different physical pages. Each task gets an own collection of pages from the &amp;#039;&amp;#039;&amp;#039;pool&amp;#039;&amp;#039;&amp;#039; of free physical pages. &lt;br /&gt;
&lt;br /&gt;
When a task is done the allocated memory should be returned to the system; otherwise the pool of pages dries up, and no more tasks can be loaded and run (a so-called &amp;#039;&amp;#039;memory hole&amp;#039;&amp;#039;).&lt;br /&gt;
&lt;br /&gt;
Now how do virtual addresses look like? - Just like physical addresses:&lt;br /&gt;
&lt;br /&gt;
[[file:virtualadd.png]]&lt;br /&gt;
&lt;br /&gt;
In fact the local page number just needs to be mapped to the physical page number if that local page has got such an associated physical page.&lt;br /&gt;
&lt;br /&gt;
As said above, the CPU can only work with logical addresses, not with physical addresses. This is also true for virtual addresses. We can, however, write programs that need to care for the proper paging, like the XOPs presented here.&lt;br /&gt;
&lt;br /&gt;
As you noticed we are using the terms &amp;#039;&amp;#039;&amp;#039;local&amp;#039;&amp;#039;&amp;#039; address/page and &amp;#039;&amp;#039;&amp;#039;virtual&amp;#039;&amp;#039;&amp;#039; address/page synonymously, but we would recommend to avoid the term virtual addressing and instead speak about local addresses. The reason is that virtual memory management has a distinctly different meaning in common computer architectures.&lt;br /&gt;
&lt;br /&gt;
=== Virtual address spaces in other architectures ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;Virtual memory&amp;#039;&amp;#039; and its management in PC architectures refers to the mechanism by which a part of the logical address is interpreted as an index in a page table (or a cascaded page table). The page table contains pointers to physical memory and also to space on a mass storage, allowing for swapping pages in and out. The Geneve memory organization is similar to this concept as a pointer into a table is translated to a prefix into the physical address space. &lt;br /&gt;
&lt;br /&gt;
However, and this is a notable difference, there is no support for virtual addresses in the TMS 9995. We may, of course, write applications that take care of memory mapping, but this is a pure software solution. The XOPs as shown below, and also other XOPs like those for file access, provide such a software implementation of virtual addressing. &lt;br /&gt;
&lt;br /&gt;
PC architectures with the Intel 8086 had an address bus of 20 bits (1 MiB address space), the Intel 80286 had an address bus width of 24 bit which already allowed for 16 MiB address space. Modern PCs have at least a 32-bit wide address. A process may thus make use of a maximum of 2&amp;lt;sup&amp;gt;32&amp;lt;/sup&amp;gt; bytes or 4 GiB. Here the address space is (or better was) &amp;#039;&amp;#039;larger&amp;#039;&amp;#039; than the amount of memory in the system. (Now you know why you might want to change to a 64-bit architecture.)&lt;br /&gt;
&lt;br /&gt;
With our Geneve, however, the address space is &amp;#039;&amp;#039;smaller&amp;#039;&amp;#039; than the amount of available memory, and we neither have any kind of segment registers as in the 8086. This means that we will never have a similarly simple, expandable memory model as known from the PC world. Any kind of memory paging must be handled on the software level. &lt;br /&gt;
&lt;br /&gt;
The concept as presented here as some similarities with the [http://en.wikipedia.org/wiki/Expanded_memory EMS technology] on PCs which was dropped with the availability of the 80386 and newer processors featuring the protected mode that allowed programs to make use of the virtual memory management.&lt;br /&gt;
&lt;br /&gt;
=== Shared memory ===&lt;br /&gt;
&lt;br /&gt;
Memory pages can be shared among tasks. This is simply done by deliberately assigning the same physical page to different tasks. Since shared pages can be used by many tasks they cannot be allocated and freed in the same way as the usual &amp;quot;private&amp;quot; pages. Also, we want to be able to share more than a single page and still retain some relation between them. Therefore, &amp;#039;&amp;#039;&amp;#039;shared page groups&amp;#039;&amp;#039;&amp;#039; are introduced. &lt;br /&gt;
&lt;br /&gt;
[[file:Sharedpages.png]]&lt;br /&gt;
&lt;br /&gt;
The illustration shows some important properties of the concept of shared pages:&lt;br /&gt;
&lt;br /&gt;
* Shared pages can only be shared as a complete group. If you take one, you take all. This is reasonable since the task that offers pages to be shared should assume that all pages to be shared are actually used in the other tasks.&lt;br /&gt;
* Shared groups are always contiguous, they have no holes. Assumed that in the example, task 1 wants to share the pages 6-8 and A-D, it must declare two distinct groups 1 and 2.&lt;br /&gt;
* Groups are identified by a &amp;#039;&amp;#039;&amp;#039;type&amp;#039;&amp;#039;&amp;#039;. Unlike in the illustration, a type is not a color but a number from 1 to 254 (not 0 and not 255). Types may be freely defined, but it may be reasonable to find a convention on the numbers so that, for instance, tasks know by the number whether the shared group contains code or data.&lt;br /&gt;
* The creation of a group is done by one of the tasks declaring some of its private pages as shared, assigning a type. The other tasks may then import these pages.&lt;br /&gt;
* The importing task must have a gap in its local page allocation which is large enough to contain the group.&lt;br /&gt;
* A type may be reused when all tasks have released the shared group of this type.&lt;br /&gt;
* Local page 0 may never be member of a shared group since it contains essential information for the task.&lt;br /&gt;
&lt;br /&gt;
== User-task XOPs ==&lt;br /&gt;
&lt;br /&gt;
User-task XOPs are available for use in application programs. Here is a typical example:&lt;br /&gt;
&lt;br /&gt;
 SEVEN  DATA 7&lt;br /&gt;
 ...&lt;br /&gt;
 GETMEM LI   R0,1       // Allocate&lt;br /&gt;
        LI   R1,5       // 5 pages (=40 KiB)&lt;br /&gt;
        LI   R2,10      // Starting at local page 10&lt;br /&gt;
        CLR  R3         // Slow mem is OK &lt;br /&gt;
        XOP  @SEVEN,0   // Invoke XOP&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
=== Available memory pages ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Opcode: 0&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Opcode (0000) || Error code (always 0)&lt;br /&gt;
|-&lt;br /&gt;
| R1&lt;br /&gt;
| || Number of free pages&lt;br /&gt;
|-&lt;br /&gt;
| R2 &lt;br /&gt;
| || Number of fast free pages&lt;br /&gt;
|-&lt;br /&gt;
| R3 &lt;br /&gt;
| || Total number of pages in system&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This function is used to query the free space in the system. No changes are applied. The total number of pages are the maximum number of allocatable pages.&lt;br /&gt;
 &lt;br /&gt;
=== Allocate pages ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Opcode: 1&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Opcode (0001) || Error code (0, 1, 7, 8)&lt;br /&gt;
|-&lt;br /&gt;
| R1&lt;br /&gt;
| Number of pages || Number of pages actually fetched to complete map as required&lt;br /&gt;
|-&lt;br /&gt;
| R2 &lt;br /&gt;
| Local page number || Number of fast pages fetched&lt;br /&gt;
|-&lt;br /&gt;
| R3 &lt;br /&gt;
| Speed flag || &lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Using this function a task can allocate pages from the pool of free pages. It must specify at which local page number the requested sequence of physical pages shall be installed. &lt;br /&gt;
&lt;br /&gt;
Using the speed flag the task can control the preference for the memory type. If set (not 0), the system tries to first allocate fast memory, and when nothing is left, continues with slow memory. The opposite happens when the flag is 0.&lt;br /&gt;
&lt;br /&gt;
Local pages are only once associated with physical pages until they are explicitly freed. If another allocation request overlaps with an earlier one, only new pages are allocated.&lt;br /&gt;
&lt;br /&gt;
[[file:allocate.png]]&lt;br /&gt;
&lt;br /&gt;
As shown here, first the pages 3-5 were allocated, returning 3 as the number of actually fetched pages. Then the pages 2-7 were allocated, but pages 3-5 are skipped, so again we get only 3 pages reported as allocated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Free pages ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Opcode: 2&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Opcode (0002) || Error code (0, 2)&lt;br /&gt;
|-&lt;br /&gt;
| R1&lt;br /&gt;
| Number of pages || &lt;br /&gt;
|-&lt;br /&gt;
| R2 &lt;br /&gt;
| Local page address || &lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Returns allocated pages to the pool of free pages. When the application terminates, all assigned pages are returned automatically, so this function is mainly used when the application explicitly wants to return memory during its runtime. Shared pages cannot be freed by this function.&lt;br /&gt;
&lt;br /&gt;
Error code 2 is returned when the local page address (R2) is 0; page 0 cannot be freed in this way.&lt;br /&gt;
&lt;br /&gt;
=== Map local page at execution page ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Opcode: 3&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Opcode (0003) || Error code (-, 2, 3)&lt;br /&gt;
|-&lt;br /&gt;
| R1&lt;br /&gt;
| Local page number || &lt;br /&gt;
|-&lt;br /&gt;
| R2 &lt;br /&gt;
| Execution page number || &lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
By this function, a local page is mapped into an execution page. While pure data operations (as done in some XOP functions) may be done using local (virtual) addresses, execution of &amp;#039;&amp;#039;&amp;#039;code&amp;#039;&amp;#039;&amp;#039; in these areas &amp;#039;&amp;#039;&amp;#039;requires&amp;#039;&amp;#039;&amp;#039; that the page be mapped into an execution page, which means it gets logical addresses. Logical addressing in the code usually requires that a particular execution page be used. For instance, if the code contains a subroutine that is expected at a certain address, it will fail to work when located elsewhere.&lt;br /&gt;
&lt;br /&gt;
Error code 2 is returned when the local page number or the execution page number is 0. &lt;br /&gt;
&lt;br /&gt;
Error code 3 is returned when the execution page number is higher than 7 or the local page is unassigned.&lt;br /&gt;
&lt;br /&gt;
=== Get address map ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Opcode: 4&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Opcode (0004) || Error code (-, 8)&lt;br /&gt;
|-&lt;br /&gt;
| R1&lt;br /&gt;
| Pointer to buffer || Count of pages reported&lt;br /&gt;
|-&lt;br /&gt;
| R2 &lt;br /&gt;
| Buffer size || &lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Gets the map table. On return of the call, the memory locations pointed to by R1 contain a sequence of physical page numbers that have been allocated. R1 contains the length of this sequence. The value at the pointer position is the physical page number associated with local page 0; next is the physical page number associated with local page 1 and so on. The table is trimmed, which means it stops with the last assigned page. Unassigned pages in between are indicated by a FF value.&lt;br /&gt;
&lt;br /&gt;
Error code 8 is returned when the buffer is too small to hold the list of numbers of allocated pages.&lt;br /&gt;
&lt;br /&gt;
=== Declare shared pages ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Opcode: 5&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Opcode (0005) || Error code&lt;br /&gt;
|-&lt;br /&gt;
| R1&lt;br /&gt;
| Number of pages to declare as shared || &lt;br /&gt;
|-&lt;br /&gt;
| R2 &lt;br /&gt;
| Local page number || &lt;br /&gt;
|-&lt;br /&gt;
| R3 &lt;br /&gt;
| Type to be assigned to shared pages || &lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Following the above description of [[#Shared memory|shared memory]], this function must be used to initially define a shared pages group. In the example, task 1 previously executed this function with R1=3, R2=6, R3=1 and once more with R1=4, R2=&amp;gt;0A, R3=2.&lt;br /&gt;
&lt;br /&gt;
=== Release shared pages ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Opcode: 6&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Opcode (0006) || Error code (0, 6, 8)&lt;br /&gt;
|-&lt;br /&gt;
| R1&lt;br /&gt;
| Type || &lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When a task does not need the shared memory anymore or wants to free its local pages, it calls this function to release the shared memory on its side. As we cannot release single pages, the group identifier (type) must be passed.&lt;br /&gt;
&lt;br /&gt;
Error code 6 occurs when the given type is unknown (not declared before).&lt;br /&gt;
&lt;br /&gt;
=== Import shared pages ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Opcode: 7&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Opcode (0007) || Error code (0, 2, 6, 7, 8)&lt;br /&gt;
|-&lt;br /&gt;
| R1&lt;br /&gt;
| Type || &lt;br /&gt;
|-&lt;br /&gt;
| R2 &lt;br /&gt;
| Local page number for start of shared area || &lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Imports a group of shared pages. In the [[#Shared memory|example]] above, task 2 has executed this function with R1=1, R2=3 and a second time with R1=2, R2=9.&lt;br /&gt;
&lt;br /&gt;
The error codes indicate that all worked well (0), that the task attempted to overlay local page 0, which is not allowed (2), that the group identifier is invalid or unknown (6), or that the task attempted to overlay an already assigned private page. Task 2 in the example would have run into this problem if it had tried to import group 2 starting at local page 0A, and local page 0D was already associated with physical page EF.&lt;br /&gt;
&lt;br /&gt;
=== Return size of shared page group ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Opcode: 8&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Opcode (0008) || Error code (0, 6)&lt;br /&gt;
|-&lt;br /&gt;
| R1&lt;br /&gt;
| Type || Number of pages in shared group&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
By this function the task can deterine the size of the indicated shared group. This is important to know so that we can successfully import the group at a gap that is large enough.&lt;br /&gt;
&lt;br /&gt;
== Privileged XOPs (only available for operating system) ==&lt;br /&gt;
&lt;br /&gt;
The following XOPs are used within MDOS itself to implement the memory management. They cannot be used from user tasks since they have good potential to spoil the entire management, requiring a system reboot. They may be of interest for people attempting to continue MDOS developnment.&lt;br /&gt;
&lt;br /&gt;
=== Release task ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Opcode: 9&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Opcode (0009) || Error code (0, -1)&lt;br /&gt;
|-&lt;br /&gt;
| R1&lt;br /&gt;
| First node ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Undoes all memory allocations for this task. This function is called by the system when a task terminates. The first node is the pointer in MEMLST. An error code of -1 (FFFF) is returned when this function is called by a user task.&lt;br /&gt;
&lt;br /&gt;
=== Get memory page ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Opcode: 10&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Opcode (000A) || Error code (0, 1, -1)&lt;br /&gt;
|-&lt;br /&gt;
| R1&lt;br /&gt;
| Physical page number || Pointer to node&lt;br /&gt;
|-&lt;br /&gt;
| R2&lt;br /&gt;
| Speed flag || (Page number from node)&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Gets the pointer to the node of a physical page. The node is used in the linked list of the page management. If R1 is from 0-FF, the result is the pointer to the node of the first free physical page. The speed flag is used to determine whether to find slow (0) or fast (not 0) memory. If R1 is logically higher than FF (usually with the high byte set to FF), the function will allocate a new page, depending on the speed flag.&lt;br /&gt;
&lt;br /&gt;
R0 contains 0 if no error occured (note that the page is still not assigned to a task); 1 means that the page is not available or there are no free pages. -1 is returned when the caller is a user task.&lt;br /&gt;
&lt;br /&gt;
=== Free memory page ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Opcode: 11&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Opcode (000B) || Error code (0, 8, -1)&lt;br /&gt;
|-&lt;br /&gt;
| R1&lt;br /&gt;
| Physical page number || Pointer to node&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The physical page is added to the list of free pages. Returns error code 8 if the page node could nbot be inserted in the list.&lt;br /&gt;
&lt;br /&gt;
=== Free memory node ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Opcode: 12&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Opcode (000C) || Error code (0, -1)&lt;br /&gt;
|-&lt;br /&gt;
| R1&lt;br /&gt;
| Pointer to node ||&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Management function. The node is added to the list of free nodes.&lt;br /&gt;
&lt;br /&gt;
=== Link memory node ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Opcode: 13&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Opcode (000D) || Error code (0, -1)&lt;br /&gt;
|-&lt;br /&gt;
| R1&lt;br /&gt;
| Pointer to node || &lt;br /&gt;
|-&lt;br /&gt;
| R2&lt;br /&gt;
| Pointer to node to link to ||&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Management function. Links memory nodes together to produce the linked list.&lt;br /&gt;
&lt;br /&gt;
=== Get address map (system) ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Opcode: 14&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;xoparg&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
! Input !! Output&lt;br /&gt;
|-&lt;br /&gt;
| R0&lt;br /&gt;
| Opcode (000E) || -1 or count of valid pages&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Used by [[Terminology#D|DSRs]]. Returns the mapping of local pages into location &amp;gt;1F00. R0 returns the length of the list which is also stored at 1FFE.&lt;br /&gt;
&lt;br /&gt;
== Error codes ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plain&amp;quot;&lt;br /&gt;
! Code&lt;br /&gt;
! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| 00&lt;br /&gt;
| No error&lt;br /&gt;
|-&lt;br /&gt;
| 01 &lt;br /&gt;
| Not enough free pages&lt;br /&gt;
|-&lt;br /&gt;
| 02&lt;br /&gt;
| Can&amp;#039;t remap execution page zero&lt;br /&gt;
|-&lt;br /&gt;
| 03&lt;br /&gt;
| No page at local address&lt;br /&gt;
|-&lt;br /&gt;
| 04&lt;br /&gt;
| User area not large enough for list&lt;br /&gt;
|-&lt;br /&gt;
| 05 &lt;br /&gt;
| Shared type already defined&lt;br /&gt;
|-&lt;br /&gt;
| 06&lt;br /&gt;
| Shared type doesn&amp;#039;t exist&lt;br /&gt;
|-&lt;br /&gt;
| 07&lt;br /&gt;
| Can&amp;#039;t overlay shared and private memory&lt;br /&gt;
|-&lt;br /&gt;
| 08&lt;br /&gt;
| Out of table space &lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[Category:Geneve]]&lt;br /&gt;
[[Category:MDOS]]&lt;br /&gt;
[[Category:Programming]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=GeneveOS_XOP_Definitions&amp;diff=50628</id>
		<title>GeneveOS XOP Definitions</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=GeneveOS_XOP_Definitions&amp;diff=50628"/>
		<updated>2022-04-24T17:26:04Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;GeneveOS makes frequent use of XOPs and offers them for user programs. An XOP (extended operation) is a special command of the TMS processor family which causes a [[Terminology#C|context switch]], transferring control to a location that is specified in a table.&lt;br /&gt;
&lt;br /&gt;
Compared to common architecture concepts, the XOP is TI&amp;#039;s way of implementing a &amp;#039;&amp;#039;&amp;#039;system call&amp;#039;&amp;#039;&amp;#039;. &lt;br /&gt;
&lt;br /&gt;
The XOP instruction takes two arguments; the first delivers data for the call, the second is a number from 0 to 15 and indicates the &amp;#039;&amp;#039;&amp;#039;XOP number&amp;#039;&amp;#039;&amp;#039;. In GeneveOS, all system calls are XOP 0 with specific arguments:&lt;br /&gt;
&lt;br /&gt;
 ARGUM  DATA &amp;lt;number&amp;gt;&lt;br /&gt;
        ...&lt;br /&gt;
        XOP  @ARGUM,0&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot;&lt;br /&gt;
! XOP argument&lt;br /&gt;
! Category&lt;br /&gt;
|-&lt;br /&gt;
| 5 || [[Geneve keyboard control | Keyboard ]]&lt;br /&gt;
|-&lt;br /&gt;
| 6 || [[GeneveOS Video Interface | Video display ]]&lt;br /&gt;
|-&lt;br /&gt;
| 7 || [[MDOS Memory Management Functions | Memory Management ]]&lt;br /&gt;
|-&lt;br /&gt;
| 8 || [[GeneveOS Device Operation | Device Operation ]]&lt;br /&gt;
|-&lt;br /&gt;
| 9 || [[GeneveOS Utility Functions | Utility functions]]&lt;br /&gt;
|-&lt;br /&gt;
| 10 || Mathematical functions&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[Category:MDOS]]&lt;br /&gt;
[[Category:Geneve]]&lt;br /&gt;
[[Category:Programming]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=MDOS_XOP_Definitions&amp;diff=50627</id>
		<title>MDOS XOP Definitions</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=MDOS_XOP_Definitions&amp;diff=50627"/>
		<updated>2022-04-24T17:24:09Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: Mizapf moved page MDOS XOP Definitions to GeneveOS XOP Definitions&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[GeneveOS XOP Definitions]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=GeneveOS_XOP_Definitions&amp;diff=50626</id>
		<title>GeneveOS XOP Definitions</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=GeneveOS_XOP_Definitions&amp;diff=50626"/>
		<updated>2022-04-24T17:24:09Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: Mizapf moved page MDOS XOP Definitions to GeneveOS XOP Definitions&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;MDOS makes frequent use of XOPs and offers them for user programs. An XOP (extended operation) is a special command of the TMS processor family which causes a [[Terminology#C|context switch]], transferring control to a location that is specified in a table.&lt;br /&gt;
&lt;br /&gt;
Compared to common architecture concepts, the XOP is TI&amp;#039;s way of implementing a &amp;#039;&amp;#039;&amp;#039;system call&amp;#039;&amp;#039;&amp;#039;. All system calls are XOP 0 with specific arguments:&lt;br /&gt;
&lt;br /&gt;
 ARGUM  DATA &amp;lt;number&amp;gt;&lt;br /&gt;
        ...&lt;br /&gt;
        XOP  @ARGUM,0&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;plainc&amp;quot;&lt;br /&gt;
! XOP argument&lt;br /&gt;
! Category&lt;br /&gt;
|-&lt;br /&gt;
| 5 || [[Geneve keyboard control | Keyboard ]]&lt;br /&gt;
|-&lt;br /&gt;
| 6 || [[GeneveOS Video Interface | Video display ]]&lt;br /&gt;
|-&lt;br /&gt;
| 7 || [[MDOS Memory Management Functions | Memory Management ]]&lt;br /&gt;
|-&lt;br /&gt;
| 8 || [[GeneveOS Device Operation | Device Operation ]]&lt;br /&gt;
|-&lt;br /&gt;
| 9 || [[GeneveOS Utility Functions | Utility functions]]&lt;br /&gt;
|-&lt;br /&gt;
| 10 || Mathematical functions&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[Category:MDOS]]&lt;br /&gt;
[[Category:Geneve]]&lt;br /&gt;
[[Category:Programming]]&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50625</id>
		<title>Programmable Systems Interface TMS 9901</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50625"/>
		<updated>2022-04-24T12:04:51Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The Programmable System Interface (PSI) is a circuit that is used on the TI-99/4A main board, on some expansion cards, and on the Geneve.&lt;br /&gt;
&lt;br /&gt;
The designation of the PSI is &amp;#039;&amp;#039;&amp;#039;TMS9901&amp;#039;&amp;#039;&amp;#039;, and this is the name that most users should be more familiar with.&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
&lt;br /&gt;
The TMS 9901 Programmable Systems Interface is a multi-purpose circuit that is used in the TI-99/4A and the Geneve computer, and also on some peripheral cards like the Corcomp Floppy Disk Controller.&lt;br /&gt;
&lt;br /&gt;
The 9901 has three main usage modes:&lt;br /&gt;
&lt;br /&gt;
* Input/Output ports&lt;br /&gt;
* Interrupt prioritizing&lt;br /&gt;
* Timer&lt;br /&gt;
&lt;br /&gt;
The I/O ports are mainly used for scanning the keyboard and joysticks, while the timer is used for cassette data encoding and decoding. The interrupt prioritizer is only used for the video interrupt which is routed from the VDP to the CPU through the 9901.&lt;br /&gt;
&lt;br /&gt;
== Package ==&lt;br /&gt;
&lt;br /&gt;
This is the pin assignment according to the 9901 specification.&lt;br /&gt;
&lt;br /&gt;
[[File:Pack9901.png|300px|left]]&lt;br /&gt;
&lt;br /&gt;
You can see pins of related functions marked by the same background color.&lt;br /&gt;
&lt;br /&gt;
Orange: Interrupt inputs&lt;br /&gt;
&lt;br /&gt;
Red: CRU bit selection (00000 - 11111, i.e. 0-31 decimal)&lt;br /&gt;
&lt;br /&gt;
Blue: CRU interface&lt;br /&gt;
&lt;br /&gt;
Violet: I/O ports&lt;br /&gt;
&lt;br /&gt;
Yellow: Outgoing interrupt and level&lt;br /&gt;
&lt;br /&gt;
Note that some pins have a double function, or in other words, two functions share the same pin. I will discuss this in the following sections.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Input/Output port operation ==&lt;br /&gt;
&lt;br /&gt;
The simplest way of working with the 9901 is input/output operation with the ports. The 9901 has 16 configurable ports that can be individually used as inputs or outputs: P0 to P15.&lt;br /&gt;
&lt;br /&gt;
=== Px port as input ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
The power-on preset for every port is input. If you want to probe the P2 status (bit 18), use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 TB   18&lt;br /&gt;
 JEQ  P2ACT&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the given CRU address into the Equal bit of the status register. JEQ will jump to the location when this input has a high level. The maximum input current is 100 µA. You should protect the input with a resistor; [[#Safety_precautions | see below]] for a more detailed discussion.&lt;br /&gt;
&lt;br /&gt;
If you want to read all 16 ports, use the STCR instruction:&lt;br /&gt;
&lt;br /&gt;
 LI   R12,32&lt;br /&gt;
 STCR R1,0&lt;br /&gt;
&lt;br /&gt;
Note that the CRU base address in R12 advances in steps of 2, so bit 16 is on address 32. Also, for reading 16 bits, you must specify a count of 0.&lt;br /&gt;
&lt;br /&gt;
Besides the 16 ports, the interrupt inputs of the 9901 can be used as input ports as well. More about that can be found in the section about the [[#Interrupt_mode| interrupt mode]].&lt;br /&gt;
&lt;br /&gt;
=== Px port as output ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Once you write a value to the CRU address of a 9901 port, this port switches to output mode. The value written to the port is the value that is shown on its pin. If we want to output a 1 on P6, we have to use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
&lt;br /&gt;
If we wanted to output a 0, SBZ would have to be used. In order to set several ports in one go, use LDCR.&lt;br /&gt;
&lt;br /&gt;
We can also read the output port just by using TB as usual (or STCR for several ports at once). &lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
 TB   22&lt;br /&gt;
 JEQ  EXPECT&lt;br /&gt;
&lt;br /&gt;
The TB operation should return the 1 which the previous SBO has set.&lt;br /&gt;
&lt;br /&gt;
You &amp;#039;&amp;#039;&amp;#039;cannot&amp;#039;&amp;#039;&amp;#039; switch back to input mode. This is only possible by resetting the 9901, either using the /RST1 input line, or by setting the /RST2 bit (bit 15) to 0 in clock mode.&lt;br /&gt;
&lt;br /&gt;
== Interrupt mode ==&lt;br /&gt;
&lt;br /&gt;
In interrupt mode, bits 1 to 15 are used to set the interrupt mask (write), or to query the interrupt inputs (read). That is, the meaning of reading and writing the bits is indeed different.&lt;br /&gt;
&lt;br /&gt;
This schema shows how the interrupt inputs are used.&lt;br /&gt;
&lt;br /&gt;
[[File:Int9901.png|600px]]&lt;br /&gt;
&lt;br /&gt;
This illustration is slightly simplified; there is a latch between each interrupt input and the mask AND gate. This is not relevant for our programming, though. For more details see the &amp;#039;&amp;#039;TMS 9901 Programmable Systems Interface Data Manual&amp;#039;&amp;#039; [[#ref2|[1]]].&lt;br /&gt;
&lt;br /&gt;
=== Checking the interrupt inputs ===&lt;br /&gt;
&lt;br /&gt;
In general, every interrupt input can be read via CRU access:&lt;br /&gt;
&lt;br /&gt;
  CLR R12&lt;br /&gt;
  TB  15&lt;br /&gt;
  JEQ NOINT15&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the specified CRU address to the Equal status bit. This means that JEQ jumps to the location when the bit is 1. Since all interrupt inputs are negative logic, reading a 1 means that there is no interrupt at this input (so I called it NOINT15).&lt;br /&gt;
&lt;br /&gt;
If we want to read several interrupt inputs by a single instruction, we have to use STCR.&lt;br /&gt;
&lt;br /&gt;
  LI   R12,16&lt;br /&gt;
  STCR R1,8&lt;br /&gt;
&lt;br /&gt;
This reads the states of the /INT8, /INT9, ..., /INT15 inputs and puts them in the most significant byte of R1. (See the specification of STCR and LDCR for transfers of up to 8 bits.)&lt;br /&gt;
&lt;br /&gt;
=== Interrupt levels ===&lt;br /&gt;
&lt;br /&gt;
Each interrupt input (/INT1 to /INT15) may be used to trigger an interrupt at another circuit, typically the CPU. For this purpose, the 9901 offers an /INTREQ outgoing line. In the TI console, it is routed to the /INTREQ input of the TMS9900 CPU. &lt;br /&gt;
&lt;br /&gt;
In addition there is a priority encoder, which is not shown here. This encoder outputs a 4-bit number which reflects the smallest number of the interrupt inputs where an interrupt has been sensed.&lt;br /&gt;
&lt;br /&gt;
That is, if /INT4 and /INT7 are currently set to 0 (active), the priority encoder outputs (0100), which means 4.&lt;br /&gt;
&lt;br /&gt;
The TMS9900 CPU can differentiate between these interrupt levels, and by using its LIMI command, we can set a threshold where interrupts are ignored. So when we use a LIMI 3, the interrupts of level 4, 5, 6 and higher will be ignored. &lt;br /&gt;
&lt;br /&gt;
The bad news is that Texas Instruments did not use these priority lines in the TI console. In fact, these outputs (IC0 .. IC3) are not connected, and the respective interrupt level inputs at the CPU are hardwired to (0001), which means that every interrupt that occurs in the TI console is automatically set to level 1. So in order to block all interrupts from the 9901&lt;br /&gt;
&lt;br /&gt;
 LIMI 0 &lt;br /&gt;
&lt;br /&gt;
must be executed. To allow all, &lt;br /&gt;
&lt;br /&gt;
 LIMI 1 &lt;br /&gt;
&lt;br /&gt;
is sufficient. For some unclear reason, TI used LIMI 2 in all occasions.&lt;br /&gt;
&lt;br /&gt;
=== Arming and disarming interrupts ===&lt;br /&gt;
&lt;br /&gt;
As said above, every interrupt input can be read at any time in interrupt mode. In that sense, we don&amp;#039;t really treat these inputs as interrupts inputs, but simply as input ports.&lt;br /&gt;
&lt;br /&gt;
If we want a port to trigger an interrupt, we must arm this interrupt input. This is done by setting the respective bit to 1.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  2&lt;br /&gt;
&lt;br /&gt;
This enables interrupt input /INT2 to cause an /INTREQ when its input changes to 0. In the TI console, /INT2 is connected to the interrupt output of the VDP (video processor). If you want to block interrupts on that input, use SBZ.&lt;br /&gt;
&lt;br /&gt;
It is a common misunderstanding that interrupts can be cleared by using a SBZ on an input. I also found this in the popular &amp;#039;&amp;#039;TI Intern&amp;#039;&amp;#039; book. Instead, SBZ n simply blocks interrupt input /INTn. In the case of the video processor, it is up to the VDP to clear its interrupt, and this is done by reading its status register.&lt;br /&gt;
&lt;br /&gt;
Consequently, you &amp;#039;&amp;#039;&amp;#039;cannot&amp;#039;&amp;#039;&amp;#039; read the current mask setting of the 9901. What you get from reading are the input levels of the pins. So if you plan to change the mask bits temporarily, be aware that you cannot ask the 9901 to give you the current setting which you want to restore later. You will have to find out by another way.&lt;br /&gt;
&lt;br /&gt;
== Clock mode ==&lt;br /&gt;
&lt;br /&gt;
Besides the I/O capabilities, the 9901 offers a &amp;quot;real-time&amp;quot; clock. This should not be understood as a precise timer with seconds, hundreds of seconds or other but as a countdown timer that is decremented every 64 CPU clock cycles.&lt;br /&gt;
&lt;br /&gt;
This schematic is taken from the Osborne book [[#ref1 |[2]]], redrawn by me in LibreOffice (and with added Load Buffer). &lt;br /&gt;
&lt;br /&gt;
[[File:Clock9901.png|550px]]&lt;br /&gt;
&lt;br /&gt;
=== Using the timer ===&lt;br /&gt;
&lt;br /&gt;
There are basically two ways of using the countdown timer:&lt;br /&gt;
&lt;br /&gt;
* Interval timer: Set the counter to a value; wait until the counter reaches zero. The 9901 can be set up to raise an interrupt at that instant.&lt;br /&gt;
* Event timer: Set the counter to a value; do something and then read the counter value.&lt;br /&gt;
&lt;br /&gt;
The counter has 14 bits; that is, its maximum value is 0x3FFF (16383). The counter is decremented every 64 clock cycles, and for the TI-99/4A and the Geneve with a clock cycle time of 333ns, this means that the counter resolution is 21.333 µs. The whole 16384 steps take 0.3495 seconds (349.5 ms).&lt;br /&gt;
&lt;br /&gt;
When the counter reaches 0, it is reloaded from the load buffer, and counting continues.&lt;br /&gt;
&lt;br /&gt;
It is essential to understand that the counter &amp;#039;&amp;#039;&amp;#039;always&amp;#039;&amp;#039;&amp;#039; counts down, independent of the mode. The specification says that the clock is enabled when a non-zero value is loaded into the load buffer, but tests on a real Geneve show that the clock decrements even when the load buffer is filled with 0.&lt;br /&gt;
&lt;br /&gt;
=== Setting the timer ===&lt;br /&gt;
&lt;br /&gt;
In order to set the load buffer and so to define the maximum counter value, we must switch to clock mode. This is done by setting bit 0 of the 9901 to 1. In the TI/Geneve systems, the 9901 is mapped to CRU base address 0, so we can do this&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  0&lt;br /&gt;
&lt;br /&gt;
to enter &amp;#039;&amp;#039;&amp;#039;clock mode&amp;#039;&amp;#039;&amp;#039;. Using SBZ 0 returns to &amp;#039;&amp;#039;&amp;#039;interrupt mode&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
The effect of switching to clock mode is that the load buffer becomes accessible by the CRU addresses 1 to 14. Supposed that we want to load the buffer with the value 0x00FF, we may use these lines:&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  LI   R1,&amp;gt;00FF&lt;br /&gt;
  INCT R12&lt;br /&gt;
  LDCR R1,14&lt;br /&gt;
&lt;br /&gt;
As we see, we must change the CRU base. The LDCR operation always starts at the address defined in R12; if this is not 0, we have to set it here. Note, however, that the CRU address is always half of the R12 value, since the rightmost bit, A15, is not part of the address. If we want to start at bit 1, we must increase R12 by 2. This is, by the way, done wrong in several places in the literature where R12 is only set to 1.&lt;br /&gt;
&lt;br /&gt;
We can combine the SBO and the LDCR to one operation if we define the rightmost bit of R1 as the bit 0 and the following bits as the timer value.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  LI   R1,&amp;gt;01FF    (0000 0001 1111 1111)&lt;br /&gt;
  LDCR R1,15&lt;br /&gt;
&lt;br /&gt;
Do &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; write all 16 bits. The last bit will change bit 15 of the 9901, which is the /RST2 bit. When we are in clock mode, writing a 0 to the /RST2 bit resets all I/O ports. This means that all pins are set to inputs, and devices that rely on them as outputs may be turned off unintentionally.&lt;br /&gt;
&lt;br /&gt;
=== Reading the timer ===&lt;br /&gt;
&lt;br /&gt;
We are interested for the current value of the timer at some occasions. However, since the counter is constantly changing (every 64 clock cycles), reading from the active counter may deliver a wrong result. Think, for instance, about a value 0x2000, which is decremented after we read the first bits, we may end up with 0x2FFF (2 from 0x2000, FFF from 0x1FFF). &lt;br /&gt;
&lt;br /&gt;
For this reason, the timer value is copied to a &amp;#039;&amp;#039;&amp;#039;read buffer&amp;#039;&amp;#039;&amp;#039;. This happens at every 64th clock cycle, when the counter is also decremented, but only when we are &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; in clock mode. &lt;br /&gt;
&lt;br /&gt;
To read the buffer, we must set the 9901 to clock mode first, which freezes the counter value in the read buffer. Then we can read the counter using STCR.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  STCR R1,15&lt;br /&gt;
  SRL  R1,1&lt;br /&gt;
  SBZ  0&lt;br /&gt;
&lt;br /&gt;
As we see, register 1 will contain bit 0 in its rightmost bit, so we shift R1 by one position to move it out.&lt;br /&gt;
&lt;br /&gt;
Do not forget to return to interrupt mode, or the buffer will not receive further updates.&lt;br /&gt;
&lt;br /&gt;
As mentioned, bit 15 is the /RST2 bit when we write to it. For reading, bit 15 reflects the /INTREQ output.&lt;br /&gt;
&lt;br /&gt;
=== The weird S0 input ===&lt;br /&gt;
&lt;br /&gt;
There is one peculiarity with the 9901. When the input line S0 is set to 1, the clock mode is &amp;#039;&amp;#039;&amp;#039;left temporarily&amp;#039;&amp;#039;&amp;#039; - until S0 is 0 again, and bit 0 is 1. If bit 0 is 0, this has no effect, as the clock mode is never entered.&lt;br /&gt;
&lt;br /&gt;
What is the reason for this? &lt;br /&gt;
&lt;br /&gt;
TI obviously considered use cases where the 9901 remains in clock mode for longer times, while the I/O ports must still be operated. The I/O ports are available on bits 16-31, which means that the S0 input is 1 for these addresses. So when we do a SBO 16, this is not related to the timer value, and thus should control the first I/O port. To allow this, the chip returns to interrupt/port mode for this operation, and when S0 is reset, returns to clock mode.&lt;br /&gt;
&lt;br /&gt;
In the TI console, the S0-S4 inputs are directly connected with the address bus lines A10-A14. This has a strange side effect: Whenever address bus line A10 changes to 1, the clock mode is left temporarily, and this also happens for ordinary memory accesses.&lt;br /&gt;
&lt;br /&gt;
Suppose that we are in clock mode (SBO 0). In this mode, the read buffer should not be updated, so when we read it, we should get the same value every time, although the real counter value is different. But this is difficult to achieve: If our program uses a memory address where A10 is 1 for some instruction, loading this instruction into the CPU will set A10 to 1, and thus leave clock mode, which updates the counter.&lt;br /&gt;
&lt;br /&gt;
Therefore, you may have the impression that the read register continues to be updated even though you set bit 0 to 1. But this is only happening because your program extends over a location where A10 is 1. So if you want to prove that in clock mode the read register remains fixed, your program must avoid all memory locations where A10 is 1.&lt;br /&gt;
&lt;br /&gt;
== Shared pins ==&lt;br /&gt;
&lt;br /&gt;
Some pins have a double meaning; for example, pin 34 is labeled /INT7 / P15. How do we select the functions?&lt;br /&gt;
&lt;br /&gt;
The shared pins are those for the interrupt inputs /INT7 to /INT15; they may also be used for P7 to P15 (in the opposite order).&lt;br /&gt;
&lt;br /&gt;
If the Pn port is set to &amp;#039;&amp;#039;&amp;#039;input&amp;#039;&amp;#039;&amp;#039;, the situation looks like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
This means that both port and interrupt input have the same values; each one is a mirror of the other&amp;#039;s state. We notice it by reading the bits of the associated CRU addresses, getting the same results. There is a difference, though: The interrupt input can be armed to trigger an outgoing interrupt, while the I/O port cannot.&lt;br /&gt;
&lt;br /&gt;
In the other case, the Pn port is an &amp;#039;&amp;#039;&amp;#039;output&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Is that possible? - Sure. The interrupt input will reflect the output of the port. When we set the port to 1, the interrupt input will get that 1 (same for 0). If we arm the interrupt input, setting the output port to 0 will trigger an interrupt via this input. The 9901 specification document recommends to disarm the interrupt trigger for the shared ports because of this possibly unwanted effect.&lt;br /&gt;
&lt;br /&gt;
== Safety precautions ==&lt;br /&gt;
&lt;br /&gt;
The TMS9901 PSI is an interrupt encoder and an I/O circuit. You may have heard that improper use of I/O circuits may damage the circuit so that it must be replaced. Is this possible with the 9901 as well? Is there a way to kill the 9901?&lt;br /&gt;
&lt;br /&gt;
Short answer: Yes.&lt;br /&gt;
&lt;br /&gt;
Longer answer: Yes, but it depends on the way how the ports are used.&lt;br /&gt;
&lt;br /&gt;
If the port is used as an input, nothing bad will happen, since the ports have high input impedance. You can put 0V or 5V directly to the input; of course, you should not use excessive voltage.&lt;br /&gt;
&lt;br /&gt;
=== How to damage the 9901 ports ===&lt;br /&gt;
&lt;br /&gt;
The problem is when the port is configured as an output port. In that case it may output 0V (0) or 5V (1). When you connect the output to the opposite voltage, you will cause a short circuit which quickly exceeds the chip&amp;#039;s maximum power dissipation of 750 mW. This means you will fry the chip.&lt;br /&gt;
&lt;br /&gt;
Consider the following setup (but don&amp;#039;t try it!).&lt;br /&gt;
&lt;br /&gt;
[[File:Badport9901.png|300px]]&lt;br /&gt;
&lt;br /&gt;
Obviously, port P4 is assumed to be an input. When the switch is open, port P4 will sense a 1 on its input. When the switch is closed, it will sense a 0.&lt;br /&gt;
&lt;br /&gt;
Now what happens when you execute this code?&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  20&lt;br /&gt;
&lt;br /&gt;
This will program port P4 to be an output, and to show a 1 (5V) at the output. SBZ will, in contrast, make this port output a 0. So how do you make it an input port? - This is done by resetting the chip. As long as you don&amp;#039;t use a SBZ/SBO (or a LDCR) on the ports, the ports are inputs.&lt;br /&gt;
&lt;br /&gt;
Nothing has happened up to now. But now imagine that you press the switch. This will put the 1 output to ground, causing the short circuit, and your 9901 is toast.&lt;br /&gt;
&lt;br /&gt;
=== What about the TI console? ===&lt;br /&gt;
&lt;br /&gt;
So can we damage the 9901 in the TI console by a suitable CRU operation? - Luckily not. &lt;br /&gt;
&lt;br /&gt;
The reason is the way TI used the ports on the mainboard. Most of the ports are configured as outputs. Nothing happens when you reset them to inputs.&lt;br /&gt;
&lt;br /&gt;
* The pure interrupt inputs (/INT1 to /INT6) are safe, as they cannot output anything. They have no output driver that could be fried.&lt;br /&gt;
* P0 and P1 are not connected.&lt;br /&gt;
* P2, P3, and P4 are designed as outputs, so they cannot be turned from an input to an output.&lt;br /&gt;
* P5 is the selector of the Alpha Lock key, so output as well.&lt;br /&gt;
* P6 and P7 (/INT15) are the cassette motor control outputs.&lt;br /&gt;
* P8 (/INT14) is the audio gate output.&lt;br /&gt;
* P9 (/INT13) is the cassette output.&lt;br /&gt;
* P10 (/INT12) is pulled up to 5V via a 10 kOhm resistor. If you put a 0 on this port (5V), the maximum current is 500 µA, which results in a power dissipation of 2.5 mW (far below the maximum 750 mW).&lt;br /&gt;
* P11 (/INT11) is the cassette input. However, there is a resistor of 2.2 kOhm (R413) which effectively guards the port of high currents.&lt;br /&gt;
* P12, P13, P14, P15 (/INT10 ... /INT7) are the keyboard input lines. Again, we have pull-ups of 10 kOhm against +5V, but also 470 Ohm on the line, which means a maximum of 8.5 mA or 42.5 mW. This is still well below the maximum value.&lt;br /&gt;
&lt;br /&gt;
So we see that resistors are the best way to protect the ports. And we may calm down; we cannot burn the 9901 in the TI console by a bug in our program.&lt;br /&gt;
&lt;br /&gt;
== Literature ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;ref1&amp;quot;&amp;gt;[1]&amp;lt;/span&amp;gt; Texas Instruments: &amp;#039;&amp;#039;TMS 9901 Programmable Systems Interface Data Manual&amp;#039;&amp;#039;, July 1977&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;ref2&amp;quot;&amp;gt;[2]&amp;lt;/span&amp;gt; Adam Osborne, Gerry Kane: &amp;#039;&amp;#039;Osborne 16-Bit Microprocessor Handbook / Includes 2900 Chip Slice Family&amp;#039;&amp;#039;. Published by OSBORNE/McGraw-Hill 1975, 1981. ISBN 0-931988-43-8.&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50624</id>
		<title>Programmable Systems Interface TMS 9901</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50624"/>
		<updated>2022-04-23T14:02:17Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Package */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The Programmable System Interface (PSI) is a circuit that is used on the TI-99/4A main board, on some expansion cards, and on the Geneve.&lt;br /&gt;
&lt;br /&gt;
The designation of the PSI is &amp;#039;&amp;#039;&amp;#039;TMS9901&amp;#039;&amp;#039;&amp;#039;, and this is the name that most users should be more familiar with.&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
&lt;br /&gt;
The TMS 9901 Programmable Systems Interface is a multi-purpose circuit that is used in the TI-99/4A and the Geneve computer, and also on some peripheral cards like the Corcomp Floppy Disk Controller.&lt;br /&gt;
&lt;br /&gt;
The 9901 has three main usage modes:&lt;br /&gt;
&lt;br /&gt;
* Input/Output ports&lt;br /&gt;
* Interrupt prioritizing&lt;br /&gt;
* Timer&lt;br /&gt;
&lt;br /&gt;
The I/O ports are mainly used for scanning the keyboard and joysticks, while the timer is used for cassette data encoding and decoding. The interrupt prioritizer is only used for the video interrupt which is routed from the VDP to the CPU through the 9901.&lt;br /&gt;
&lt;br /&gt;
== Package ==&lt;br /&gt;
&lt;br /&gt;
This is the pin assignment according to the 9901 specification.&lt;br /&gt;
&lt;br /&gt;
[[File:Pack9901.png|300px|left]]&lt;br /&gt;
&lt;br /&gt;
You can see pins of related functions marked by the same background color.&lt;br /&gt;
&lt;br /&gt;
Orange: Interrupt inputs&lt;br /&gt;
&lt;br /&gt;
Red: CRU bit selection (00000 - 11111, i.e. 0-31 decimal)&lt;br /&gt;
&lt;br /&gt;
Blue: CRU interface&lt;br /&gt;
&lt;br /&gt;
Violet: I/O ports&lt;br /&gt;
&lt;br /&gt;
Yellow: Outgoing interrupt and level&lt;br /&gt;
&lt;br /&gt;
Note that some pins have a double function, or in other words, two functions share the same pin. I will discuss this in the following sections.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Input/Output port operation ==&lt;br /&gt;
&lt;br /&gt;
The simplest way of working with the 9901 is input/output operation with the ports. The 9901 has 16 configurable ports that can be individually used as inputs or outputs: P0 to P15.&lt;br /&gt;
&lt;br /&gt;
=== Px port as input ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
The power-on preset for every port is input. If you want to probe the P2 status (bit 18), use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 TB   18&lt;br /&gt;
 JEQ  P2ACT&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the given CRU address into the Equal bit of the status register. JEQ will jump to the location when this input has a high level. The maximum input current is 100 µA. You should protect the input with a resistor; [[#Safety_precautions | see below]] for a more detailed discussion.&lt;br /&gt;
&lt;br /&gt;
If you want to read all 16 ports, use the STCR instruction:&lt;br /&gt;
&lt;br /&gt;
 LI   R12,32&lt;br /&gt;
 STCR R1,0&lt;br /&gt;
&lt;br /&gt;
Note that the CRU base address in R12 advances in steps of 2, so bit 16 is on address 32. Also, for reading 16 bits, you must specify a count of 0.&lt;br /&gt;
&lt;br /&gt;
The 9901 offers some more inputs than the 16 ports; we can use the interrupt inputs as well. More about that can be found in the section about the [[#Interrupt_mode| interrupt mode]].&lt;br /&gt;
&lt;br /&gt;
=== Px port as output ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Once you write a value to the CRU address of a 9901 port, this port switches to output mode. The value written to the port is the value that is shown on its pin. If we want to output a 1 on P6, we have to use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
&lt;br /&gt;
If we wanted to output a 0, SBZ would have to be used. In order to set several ports in one go, use LDCR.&lt;br /&gt;
&lt;br /&gt;
We can also read the output port just by using TB as usual (or STCR for several ports at once). &lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
 TB   22&lt;br /&gt;
 JEQ  EXPECT&lt;br /&gt;
&lt;br /&gt;
The TB operation should return the 1 which the previous SBO has set.&lt;br /&gt;
&lt;br /&gt;
You &amp;#039;&amp;#039;&amp;#039;cannot&amp;#039;&amp;#039;&amp;#039; switch back to input mode. This is only possible by resetting the 9901, either using the /RST1 input line, or by setting the /RST2 bit (bit 15) to 0 in clock mode.&lt;br /&gt;
&lt;br /&gt;
== Interrupt mode ==&lt;br /&gt;
&lt;br /&gt;
In interrupt mode, bits 1 to 15 are used to set the interrupt mask (write), or to query the interrupt inputs (read). That is, the meaning of reading and writing the bits is indeed different.&lt;br /&gt;
&lt;br /&gt;
This schema shows how the interrupt inputs are used.&lt;br /&gt;
&lt;br /&gt;
[[File:Int9901.png|600px]]&lt;br /&gt;
&lt;br /&gt;
This illustration is slightly simplified; there is a latch between each interrupt input and the mask AND gate. This is not relevant for our programming, though. For more details see the [[#ref2 | TMS 9901 Programmable Systems Interface Data Manual]].&lt;br /&gt;
&lt;br /&gt;
=== Checking the interrupt inputs ===&lt;br /&gt;
&lt;br /&gt;
In general, every interrupt input can be read via CRU access:&lt;br /&gt;
&lt;br /&gt;
  CLR R12&lt;br /&gt;
  TB  15&lt;br /&gt;
  JEQ NOINT15&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the specified CRU address to the Equal status bit. This means that JEQ jumps to the location when the bit is 1. Since all interrupt inputs are negative logic, reading a 1 means that there is no interrupt at this input (so I called it NOINT15).&lt;br /&gt;
&lt;br /&gt;
If we want to read several interrupt inputs by a single instruction, we have to use STCR.&lt;br /&gt;
&lt;br /&gt;
  LI   R12,16&lt;br /&gt;
  STCR R1,8&lt;br /&gt;
&lt;br /&gt;
This reads the states of the /INT8, /INT9, ..., /INT15 inputs and puts them in the most significant byte of R1. (See the specification of STCR and LDCR for transfers of up to 8 bits.)&lt;br /&gt;
&lt;br /&gt;
=== Interrupt levels ===&lt;br /&gt;
&lt;br /&gt;
Each interrupt input (/INT1 to /INT15) may be used to trigger an interrupt at another circuit, typically the CPU. For this purpose, the PSI offers an /INTREQ outgoing line. In the TI console, it is routed to the /INTREQ input of the TMS9900 CPU. &lt;br /&gt;
&lt;br /&gt;
In addition there is a priority encoder, which is not shown here. This encoder outputs a 4-bit number which reflects the smallest number of the interrupt inputs where an interrupt has been sensed.&lt;br /&gt;
&lt;br /&gt;
That is, if /INT4 and /INT7 are currently set to 0 (active), the priority encoder outputs (0100), which means 4.&lt;br /&gt;
&lt;br /&gt;
The TMS9900 CPU can differentiate between these interrupt levels, and by using its LIMI command, we can set a threshold where interrupts are ignored. So when we use a LIMI 3, the interrupts of level 4, 5, 6 and higher will be ignored. &lt;br /&gt;
&lt;br /&gt;
The bad news is that Texas Instruments did not use these priority lines in the TI console. In fact, these outputs (IC0 .. IC3) are not connected, and the respective interrupt level inputs at the CPU are hardwired to (0001), which means that every interrupt that occurs in the TI console is automatically set to level 1. So in order to block all interrupts from the 9901&lt;br /&gt;
&lt;br /&gt;
 LIMI 0 &lt;br /&gt;
&lt;br /&gt;
must be executed. To allow all, &lt;br /&gt;
&lt;br /&gt;
 LIMI 1 &lt;br /&gt;
&lt;br /&gt;
is sufficient. For some unclear reason, TI used LIMI 2 in all occasions.&lt;br /&gt;
&lt;br /&gt;
=== Arming and disarming interrupts ===&lt;br /&gt;
&lt;br /&gt;
As said above, every interrupt input can be read at any time in interrupt mode. In that sense, we don&amp;#039;t really treat these inputs as interrupts inputs, but simply as input ports.&lt;br /&gt;
&lt;br /&gt;
If we want a port to trigger an interrupt, we must arm this interrupt input. This is done by setting the respective bit to 1.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  2&lt;br /&gt;
&lt;br /&gt;
This enables interrupt input /INT2 to cause an /INTREQ when its input changes to 0. In the TI console, /INT2 is connected to the interrupt output of the VDP (video processor). If you want to block interrupts on that input, use SBZ.&lt;br /&gt;
&lt;br /&gt;
It is a common misunderstanding that interrupts can be cleared by using a SBZ on an input. I also found this in the popular &amp;#039;&amp;#039;TI Intern&amp;#039;&amp;#039; book. Instead, SBZ n simply blocks interrupt input /INTn. In the case of the video processor, it is up to the VDP to clear its interrupt, and this is done by reading its status register.&lt;br /&gt;
&lt;br /&gt;
Consequently, you &amp;#039;&amp;#039;&amp;#039;cannot&amp;#039;&amp;#039;&amp;#039; read the current mask setting of the 9901. What you get from reading are the input levels of the pins. So if you plan to change the mask bits temporarily, be aware that you cannot ask the 9901 to give you the current setting which you want to restore later. You will have to find out by another way.&lt;br /&gt;
&lt;br /&gt;
== Counter mode ==&lt;br /&gt;
&lt;br /&gt;
Besides the I/O capabilities, the PSI offers a &amp;quot;real-time&amp;quot; clock. This should not be understood as a precise timer with seconds, hundreds of seconds or other but as a countdown timer that is decremented every 64 CPU clock cycles.&lt;br /&gt;
&lt;br /&gt;
This schematic is taken from the [[#ref1 |Osborne book]], redrawn by me in LibreOffice (and with added Load Buffer). &lt;br /&gt;
&lt;br /&gt;
[[File:Clock9901.png|550px]]&lt;br /&gt;
&lt;br /&gt;
=== Using the timer ===&lt;br /&gt;
&lt;br /&gt;
There are basically two ways of using the countdown timer:&lt;br /&gt;
&lt;br /&gt;
* Interval timer: Set the counter to a value; wait until the counter reaches zero. The PSI can be set up to raise an interrupt at that instant.&lt;br /&gt;
* Event timer: Set the counter to a value; do something and then read the counter value.&lt;br /&gt;
&lt;br /&gt;
The counter has 14 bits; that is, its maximum value is 0x3FFF (16383). The counter is decremented every 64 clock cycles, and for the TI-99/4A and the Geneve with a clock cycle time of 333ns, this means that the counter resolution is 21.333 µs. The whole 16384 steps take 0.3495 seconds (349.5 ms).&lt;br /&gt;
&lt;br /&gt;
When the counter reaches 0, it is reloaded from the load buffer, and counting continues.&lt;br /&gt;
&lt;br /&gt;
It is essential to understand that the counter &amp;#039;&amp;#039;&amp;#039;always&amp;#039;&amp;#039;&amp;#039; counts down, independent of the mode. The specification says that the clock is enabled when a non-zero value is loaded into the load buffer, but tests on a real Geneve show that the clock decrements even when the load buffer is filled with 0.&lt;br /&gt;
&lt;br /&gt;
=== Setting the timer ===&lt;br /&gt;
&lt;br /&gt;
In order to set the load buffer and so to define the maximum counter value, we must switch to clock mode. This is achieved by setting bit 0 of the PSI to 1. In the TI/Geneve systems, the PSI is mapped to CRU base address 0, so we can do this&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  0&lt;br /&gt;
&lt;br /&gt;
to enter &amp;#039;&amp;#039;&amp;#039;clock mode&amp;#039;&amp;#039;&amp;#039;. Using SBZ 0 returns to &amp;#039;&amp;#039;&amp;#039;interrupt mode&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
The effect of switching to clock mode is that the load buffer becomes accessible by the CRU addresses 1 to 14. Supposed that we want to load the buffer with the value 0x00FF, we may use these lines:&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  LI   R1,&amp;gt;00FF&lt;br /&gt;
  INCT R12&lt;br /&gt;
  LDCR R1,14&lt;br /&gt;
&lt;br /&gt;
As we see, we must change the CRU base. The LDCR operation always starts at the address defined in R12; if this is not 0, we have to set it here. Note, however, that the CRU address is always half of the R12 value, since the rightmost bit, A15, is not part of the address. If we want to start at bit 1, we must increase R12 by 2. This is, by the way, done wrong in several places in the literature where R12 is only set to 1.&lt;br /&gt;
&lt;br /&gt;
We can combine the SBO and the LDCR to one operation if we define the rightmost bit of R1 as the bit 0 and the following bits as the timer value.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  LI   R1,&amp;gt;01FF    (0000 0001 1111 1111)&lt;br /&gt;
  LDCR R1,15&lt;br /&gt;
&lt;br /&gt;
Do &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; write all 16 bits. The last bit will change bit 15 of the PSI, which is the /RST2 bit. When we are in clock mode, writing a 0 to the /RST2 bit resets all I/O ports. This means that all pins are set to inputs, and devices that rely on them as outputs may be turned off unintentionally.&lt;br /&gt;
&lt;br /&gt;
=== Reading the timer ===&lt;br /&gt;
&lt;br /&gt;
We are interested for the current value of the timer at some occasions. However, since the counter is constantly changing (every 64 clock cycles), reading from the active counter may deliver a wrong result. Think, for instance, about a value 0x2000, which is decremented after we read the first bits, we may end up with 0x2FFF (2 from 0x2000, FFF from 0x1FFF). &lt;br /&gt;
&lt;br /&gt;
For this reason, the timer value is copied to a &amp;#039;&amp;#039;&amp;#039;read buffer&amp;#039;&amp;#039;&amp;#039;. This happens at every 64th clock cycle, when the counter is also decremented, but only when we are &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; in clock mode. &lt;br /&gt;
&lt;br /&gt;
To read the buffer, we must set the PSI to clock mode first, which freezes the counter value in the read buffer. Then we can read the counter using STCR.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  STCR R1,15&lt;br /&gt;
  SRL  R1,1&lt;br /&gt;
  SBZ  0&lt;br /&gt;
&lt;br /&gt;
As we see, register 1 will contain bit 0 in its rightmost bit, so we shift R1 by one position to move it out.&lt;br /&gt;
&lt;br /&gt;
Do not forget to return to interrupt mode, or the buffer will not receive further updates.&lt;br /&gt;
&lt;br /&gt;
As mentioned, bit 15 is the /RST2 bit when we write to it. For reading, bit 15 reflects the /INTREQ output.&lt;br /&gt;
&lt;br /&gt;
=== The weird S0 input ===&lt;br /&gt;
&lt;br /&gt;
There is one peculiarity with the PSI. When the input line S0 is set to 1, the clock mode is &amp;#039;&amp;#039;&amp;#039;left temporarily&amp;#039;&amp;#039;&amp;#039; - until S0 is 0 again, and bit 0 is 1. If bit 0 is 0, this has no effect, as the clock mode is never entered.&lt;br /&gt;
&lt;br /&gt;
What is the reason for this? &lt;br /&gt;
&lt;br /&gt;
TI obviously considered use cases where the PSI remains in clock mode for longer times, while the I/O ports must still be operated. The I/O ports are available on bits 16-31, which means that the S0 input is 1 for these addresses. So when we do a SBO 16, this is not related to the timer value, and thus should control the first I/O port. To allow this, the chip returns to interrupt/port mode for this operation, and when S0 is reset, returns to clock mode.&lt;br /&gt;
&lt;br /&gt;
In the TI console, the S0-S4 inputs are directly connected with the address bus lines A10-A14. This has a strange side effect: Whenever address bus line A10 changes to 1, the clock mode is left temporarily, and this also happens for ordinary memory accesses.&lt;br /&gt;
&lt;br /&gt;
Suppose that we are in clock mode (SBO 0). In this mode, the read buffer should not be updated, so when we read it, we should get the same value every time, although the real counter value is different. But this is difficult to achieve: If our program uses a memory address where A10 is 1 for some instruction, loading this instruction into the CPU will set A10 to 1, and thus leave clock mode, which updates the counter.&lt;br /&gt;
&lt;br /&gt;
Therefore, you may have the impression that the read register continues to be updated even though you set bit 0 to 1. But this is only happening because your program extends over a location where A10 is 1. So if you want to prove that in clock mode the read register remains fixed, your program must avoid all memory locations where A10 is 1.&lt;br /&gt;
&lt;br /&gt;
== Shared pins ==&lt;br /&gt;
&lt;br /&gt;
Some pins have a double meaning; for example, pin 34 is labeled /INT7 / P15. How do we select the functions?&lt;br /&gt;
&lt;br /&gt;
The shared pins are those for the interrupt inputs /INT7 to /INT15; they may also be used for P7 to P15 (in the opposite order).&lt;br /&gt;
&lt;br /&gt;
If the Pn port is set to &amp;#039;&amp;#039;&amp;#039;input&amp;#039;&amp;#039;&amp;#039;, the situation looks like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
This means that both port and interrupt input have the same values; each one is a mirror of the other&amp;#039;s state. We notice it by reading the bits of the associated CRU addresses, getting the same results. There is a difference, though: The interrupt input can be armed to trigger an outgoing interrupt, while the I/O port cannot.&lt;br /&gt;
&lt;br /&gt;
In the other case, the Pn port is an &amp;#039;&amp;#039;&amp;#039;output&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Is that possible? - Sure. The interrupt input will reflect the output of the port. When we set the port to 1, the interrupt input will get that 1 (same for 0). If we arm the interrupt input, setting the output port to 0 will trigger an interrupt via this input. The 9901 specification document recommends to disarm the interrupt trigger for the shared ports because of this possibly unwanted effect.&lt;br /&gt;
&lt;br /&gt;
== Safety precautions ==&lt;br /&gt;
&lt;br /&gt;
The TMS9901 PSI is an interrupt encoder and an I/O circuit. You may have heard that improper use of I/O circuits may damage the circuit so that it must be replaced. Is this possible with the 9901 as well? Is there a way to kill the 9901?&lt;br /&gt;
&lt;br /&gt;
Short answer: Yes.&lt;br /&gt;
&lt;br /&gt;
Longer answer: Yes, but it depends on the way how the ports are used.&lt;br /&gt;
&lt;br /&gt;
If the port is used as an input, nothing bad will happen, since the ports have high input impedance. You can put 0V or 5V directly to the input; of course, you should not use excessive voltage.&lt;br /&gt;
&lt;br /&gt;
=== How to damage the 9901 ports ===&lt;br /&gt;
&lt;br /&gt;
The problem is when the port is configured as an output port. In that case it may output 0V (0) or 5V (1). When you connect the output to the opposite voltage, you will cause a short circuit which quickly exceeds the chip&amp;#039;s maximum power dissipation of 750 mW. This means you will fry the chip.&lt;br /&gt;
&lt;br /&gt;
Consider the following setup (but don&amp;#039;t try it!).&lt;br /&gt;
&lt;br /&gt;
[[File:Badport9901.png|300px]]&lt;br /&gt;
&lt;br /&gt;
Obviously, port P4 is assumed to be an input. When the switch is open, port P4 will sense a 1 on its input. When the switch is closed, it will sense a 0.&lt;br /&gt;
&lt;br /&gt;
Now what happens when you execute this code?&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  20&lt;br /&gt;
&lt;br /&gt;
This will program port P4 to be an output, and to show a 1 (5V) at the output. SBZ will, in contrast, make this port output a 0. So how do you make it an input port? - This is done by resetting the chip. As long as you don&amp;#039;t use a SBZ/SBO (or a LDCR) on the ports, the ports are inputs.&lt;br /&gt;
&lt;br /&gt;
Nothing has happened up to now. But now imagine that you press the switch. This will put the 1 output to ground, causing the short circuit, and your 9901 is toast.&lt;br /&gt;
&lt;br /&gt;
=== What about the TI console? ===&lt;br /&gt;
&lt;br /&gt;
So can we damage the 9901 in the TI console by a suitable CRU operation? - Luckily not. &lt;br /&gt;
&lt;br /&gt;
The reason is the way how TI used the ports on the mainboard. Most of the ports are configured as outputs. Nothing happens when you reset them to inputs.&lt;br /&gt;
&lt;br /&gt;
* The pure interrupt inputs (/INT1 to /INT6) are safe, as they cannot output anything. They have no output driver that could be fried.&lt;br /&gt;
* P0 and P1 are not connected.&lt;br /&gt;
* P2, P3, and P4 are designed as outputs, so they cannot be turned from an input to an output.&lt;br /&gt;
* P5 is the selector of the Alpha Lock key, so output as well.&lt;br /&gt;
* P6 and P7 (/INT15) are the cassette motor control outputs.&lt;br /&gt;
* P8 (/INT14) is the audio gate output.&lt;br /&gt;
* P9 (/INT13) is the cassette output.&lt;br /&gt;
* P10 (/INT12) is pulled up to 5V via a 10 kOhm resistor. If you put a 0 on this port (5V), the maximum current is 500 µA, which results in a power dissipation of 2.5 mW (far below the maximum 750 mW).&lt;br /&gt;
* P11 (/INT11) is the cassette input. However, there is a resistor of 2.2 kOhm (R413) which effectively guards the port of high currents.&lt;br /&gt;
* P12, P13, P14, P15 (/INT10 ... /INT7) are the keyboard input lines. Again, we have pull-ups of 10 kOhm against +5V, but also 470 Ohm on the line, which means a maximum of 8.5 mA or 42.5 mW. This is still well below the maximum value.&lt;br /&gt;
&lt;br /&gt;
So we see that resistors are the best way to protect the ports. And we may calm down; we cannot burn the 9901 in the TI console by a bug in our program.&lt;br /&gt;
&lt;br /&gt;
== Literature ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;ref1&amp;quot;&amp;gt;[1]&amp;lt;/span&amp;gt; Texas Instruments: &amp;#039;&amp;#039;TMS 9901 Programmable Systems Interface Data Manual&amp;#039;&amp;#039;, July 1977&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;ref2&amp;quot;&amp;gt;[2]&amp;lt;/span&amp;gt; Adam Osborne, Gerry Kane: &amp;#039;&amp;#039;Osborne 16-Bit Microprocessor Handbook / Includes 2900 Chip Slice Family&amp;#039;&amp;#039;. Published by OSBORNE/McGraw-Hill 1975, 1981. ISBN 0-931988-43-8.&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=File:Pack9901.png&amp;diff=50623</id>
		<title>File:Pack9901.png</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=File:Pack9901.png&amp;diff=50623"/>
		<updated>2022-04-23T13:57:54Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: Mizapf uploaded a new version of File:Pack9901.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50622</id>
		<title>Programmable Systems Interface TMS 9901</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50622"/>
		<updated>2022-04-23T13:55:22Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Counter mode */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The Programmable System Interface (PSI) is a circuit that is used on the TI-99/4A main board, on some expansion cards, and on the Geneve.&lt;br /&gt;
&lt;br /&gt;
The designation of the PSI is &amp;#039;&amp;#039;&amp;#039;TMS9901&amp;#039;&amp;#039;&amp;#039;, and this is the name that most users should be more familiar with.&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
&lt;br /&gt;
The TMS 9901 Programmable Systems Interface is a multi-purpose circuit that is used in the TI-99/4A and the Geneve computer, and also on some peripheral cards like the Corcomp Floppy Disk Controller.&lt;br /&gt;
&lt;br /&gt;
The 9901 has three main usage modes:&lt;br /&gt;
&lt;br /&gt;
* Input/Output ports&lt;br /&gt;
* Interrupt prioritizing&lt;br /&gt;
* Timer&lt;br /&gt;
&lt;br /&gt;
The I/O ports are mainly used for scanning the keyboard and joysticks, while the timer is used for cassette data encoding and decoding. The interrupt prioritizer is only used for the video interrupt which is routed from the VDP to the CPU through the 9901.&lt;br /&gt;
&lt;br /&gt;
== Package ==&lt;br /&gt;
&lt;br /&gt;
This is the pin assignment according to the 9901 specification.&lt;br /&gt;
&lt;br /&gt;
[[File:Pack9901.png|300px|left]]&lt;br /&gt;
&lt;br /&gt;
You can see pins of related functions marked by the same background color.&lt;br /&gt;
&lt;br /&gt;
* Orange: Interrupt inputs&lt;br /&gt;
* Red: CRU bit selection (00000 - 11111, i.e. 0-31 decimal)&lt;br /&gt;
* Blue: CRU interface&lt;br /&gt;
* Violet: I/O ports&lt;br /&gt;
* Yellow: Outgoing interrupt and level&lt;br /&gt;
&lt;br /&gt;
Note that some pins have a double function, or in other words, two functions share the same pin. I will discuss this in the following sections.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Input/Output port operation ==&lt;br /&gt;
&lt;br /&gt;
The simplest way of working with the 9901 is input/output operation with the ports. The 9901 has 16 configurable ports that can be individually used as inputs or outputs: P0 to P15.&lt;br /&gt;
&lt;br /&gt;
=== Px port as input ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
The power-on preset for every port is input. If you want to probe the P2 status (bit 18), use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 TB   18&lt;br /&gt;
 JEQ  P2ACT&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the given CRU address into the Equal bit of the status register. JEQ will jump to the location when this input has a high level. The maximum input current is 100 µA. You should protect the input with a resistor; [[#Safety_precautions | see below]] for a more detailed discussion.&lt;br /&gt;
&lt;br /&gt;
If you want to read all 16 ports, use the STCR instruction:&lt;br /&gt;
&lt;br /&gt;
 LI   R12,32&lt;br /&gt;
 STCR R1,0&lt;br /&gt;
&lt;br /&gt;
Note that the CRU base address in R12 advances in steps of 2, so bit 16 is on address 32. Also, for reading 16 bits, you must specify a count of 0.&lt;br /&gt;
&lt;br /&gt;
The 9901 offers some more inputs than the 16 ports; we can use the interrupt inputs as well. More about that can be found in the section about the [[#Interrupt_mode| interrupt mode]].&lt;br /&gt;
&lt;br /&gt;
=== Px port as output ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Once you write a value to the CRU address of a 9901 port, this port switches to output mode. The value written to the port is the value that is shown on its pin. If we want to output a 1 on P6, we have to use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
&lt;br /&gt;
If we wanted to output a 0, SBZ would have to be used. In order to set several ports in one go, use LDCR.&lt;br /&gt;
&lt;br /&gt;
We can also read the output port just by using TB as usual (or STCR for several ports at once). &lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
 TB   22&lt;br /&gt;
 JEQ  EXPECT&lt;br /&gt;
&lt;br /&gt;
The TB operation should return the 1 which the previous SBO has set.&lt;br /&gt;
&lt;br /&gt;
You &amp;#039;&amp;#039;&amp;#039;cannot&amp;#039;&amp;#039;&amp;#039; switch back to input mode. This is only possible by resetting the 9901, either using the /RST1 input line, or by setting the /RST2 bit (bit 15) to 0 in clock mode.&lt;br /&gt;
&lt;br /&gt;
== Interrupt mode ==&lt;br /&gt;
&lt;br /&gt;
In interrupt mode, bits 1 to 15 are used to set the interrupt mask (write), or to query the interrupt inputs (read). That is, the meaning of reading and writing the bits is indeed different.&lt;br /&gt;
&lt;br /&gt;
This schema shows how the interrupt inputs are used.&lt;br /&gt;
&lt;br /&gt;
[[File:Int9901.png|600px]]&lt;br /&gt;
&lt;br /&gt;
This illustration is slightly simplified; there is a latch between each interrupt input and the mask AND gate. This is not relevant for our programming, though. For more details see the [[#ref2 | TMS 9901 Programmable Systems Interface Data Manual]].&lt;br /&gt;
&lt;br /&gt;
=== Checking the interrupt inputs ===&lt;br /&gt;
&lt;br /&gt;
In general, every interrupt input can be read via CRU access:&lt;br /&gt;
&lt;br /&gt;
  CLR R12&lt;br /&gt;
  TB  15&lt;br /&gt;
  JEQ NOINT15&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the specified CRU address to the Equal status bit. This means that JEQ jumps to the location when the bit is 1. Since all interrupt inputs are negative logic, reading a 1 means that there is no interrupt at this input (so I called it NOINT15).&lt;br /&gt;
&lt;br /&gt;
If we want to read several interrupt inputs by a single instruction, we have to use STCR.&lt;br /&gt;
&lt;br /&gt;
  LI   R12,16&lt;br /&gt;
  STCR R1,8&lt;br /&gt;
&lt;br /&gt;
This reads the states of the /INT8, /INT9, ..., /INT15 inputs and puts them in the most significant byte of R1. (See the specification of STCR and LDCR for transfers of up to 8 bits.)&lt;br /&gt;
&lt;br /&gt;
=== Interrupt levels ===&lt;br /&gt;
&lt;br /&gt;
Each interrupt input (/INT1 to /INT15) may be used to trigger an interrupt at another circuit, typically the CPU. For this purpose, the PSI offers an /INTREQ outgoing line. In the TI console, it is routed to the /INTREQ input of the TMS9900 CPU. &lt;br /&gt;
&lt;br /&gt;
In addition there is a priority encoder, which is not shown here. This encoder outputs a 4-bit number which reflects the smallest number of the interrupt inputs where an interrupt has been sensed.&lt;br /&gt;
&lt;br /&gt;
That is, if /INT4 and /INT7 are currently set to 0 (active), the priority encoder outputs (0100), which means 4.&lt;br /&gt;
&lt;br /&gt;
The TMS9900 CPU can differentiate between these interrupt levels, and by using its LIMI command, we can set a threshold where interrupts are ignored. So when we use a LIMI 3, the interrupts of level 4, 5, 6 and higher will be ignored. &lt;br /&gt;
&lt;br /&gt;
The bad news is that Texas Instruments did not use these priority lines in the TI console. In fact, these outputs (IC0 .. IC3) are not connected, and the respective interrupt level inputs at the CPU are hardwired to (0001), which means that every interrupt that occurs in the TI console is automatically set to level 1. So in order to block all interrupts from the 9901&lt;br /&gt;
&lt;br /&gt;
 LIMI 0 &lt;br /&gt;
&lt;br /&gt;
must be executed. To allow all, &lt;br /&gt;
&lt;br /&gt;
 LIMI 1 &lt;br /&gt;
&lt;br /&gt;
is sufficient. For some unclear reason, TI used LIMI 2 in all occasions.&lt;br /&gt;
&lt;br /&gt;
=== Arming and disarming interrupts ===&lt;br /&gt;
&lt;br /&gt;
As said above, every interrupt input can be read at any time in interrupt mode. In that sense, we don&amp;#039;t really treat these inputs as interrupts inputs, but simply as input ports.&lt;br /&gt;
&lt;br /&gt;
If we want a port to trigger an interrupt, we must arm this interrupt input. This is done by setting the respective bit to 1.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  2&lt;br /&gt;
&lt;br /&gt;
This enables interrupt input /INT2 to cause an /INTREQ when its input changes to 0. In the TI console, /INT2 is connected to the interrupt output of the VDP (video processor). If you want to block interrupts on that input, use SBZ.&lt;br /&gt;
&lt;br /&gt;
It is a common misunderstanding that interrupts can be cleared by using a SBZ on an input. I also found this in the popular &amp;#039;&amp;#039;TI Intern&amp;#039;&amp;#039; book. Instead, SBZ n simply blocks interrupt input /INTn. In the case of the video processor, it is up to the VDP to clear its interrupt, and this is done by reading its status register.&lt;br /&gt;
&lt;br /&gt;
Consequently, you &amp;#039;&amp;#039;&amp;#039;cannot&amp;#039;&amp;#039;&amp;#039; read the current mask setting of the 9901. What you get from reading are the input levels of the pins. So if you plan to change the mask bits temporarily, be aware that you cannot ask the 9901 to give you the current setting which you want to restore later. You will have to find out by another way.&lt;br /&gt;
&lt;br /&gt;
== Counter mode ==&lt;br /&gt;
&lt;br /&gt;
Besides the I/O capabilities, the PSI offers a &amp;quot;real-time&amp;quot; clock. This should not be understood as a precise timer with seconds, hundreds of seconds or other but as a countdown timer that is decremented every 64 CPU clock cycles.&lt;br /&gt;
&lt;br /&gt;
This schematic is taken from the [[#ref1 |Osborne book]], redrawn by me in LibreOffice (and with added Load Buffer). &lt;br /&gt;
&lt;br /&gt;
[[File:Clock9901.png|550px]]&lt;br /&gt;
&lt;br /&gt;
=== Using the timer ===&lt;br /&gt;
&lt;br /&gt;
There are basically two ways of using the countdown timer:&lt;br /&gt;
&lt;br /&gt;
* Interval timer: Set the counter to a value; wait until the counter reaches zero. The PSI can be set up to raise an interrupt at that instant.&lt;br /&gt;
* Event timer: Set the counter to a value; do something and then read the counter value.&lt;br /&gt;
&lt;br /&gt;
The counter has 14 bits; that is, its maximum value is 0x3FFF (16383). The counter is decremented every 64 clock cycles, and for the TI-99/4A and the Geneve with a clock cycle time of 333ns, this means that the counter resolution is 21.333 µs. The whole 16384 steps take 0.3495 seconds (349.5 ms).&lt;br /&gt;
&lt;br /&gt;
When the counter reaches 0, it is reloaded from the load buffer, and counting continues.&lt;br /&gt;
&lt;br /&gt;
It is essential to understand that the counter &amp;#039;&amp;#039;&amp;#039;always&amp;#039;&amp;#039;&amp;#039; counts down, independent of the mode. The specification says that the clock is enabled when a non-zero value is loaded into the load buffer, but tests on a real Geneve show that the clock decrements even when the load buffer is filled with 0.&lt;br /&gt;
&lt;br /&gt;
=== Setting the timer ===&lt;br /&gt;
&lt;br /&gt;
In order to set the load buffer and so to define the maximum counter value, we must switch to clock mode. This is achieved by setting bit 0 of the PSI to 1. In the TI/Geneve systems, the PSI is mapped to CRU base address 0, so we can do this&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  0&lt;br /&gt;
&lt;br /&gt;
to enter &amp;#039;&amp;#039;&amp;#039;clock mode&amp;#039;&amp;#039;&amp;#039;. Using SBZ 0 returns to &amp;#039;&amp;#039;&amp;#039;interrupt mode&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
The effect of switching to clock mode is that the load buffer becomes accessible by the CRU addresses 1 to 14. Supposed that we want to load the buffer with the value 0x00FF, we may use these lines:&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  LI   R1,&amp;gt;00FF&lt;br /&gt;
  INCT R12&lt;br /&gt;
  LDCR R1,14&lt;br /&gt;
&lt;br /&gt;
As we see, we must change the CRU base. The LDCR operation always starts at the address defined in R12; if this is not 0, we have to set it here. Note, however, that the CRU address is always half of the R12 value, since the rightmost bit, A15, is not part of the address. If we want to start at bit 1, we must increase R12 by 2. This is, by the way, done wrong in several places in the literature where R12 is only set to 1.&lt;br /&gt;
&lt;br /&gt;
We can combine the SBO and the LDCR to one operation if we define the rightmost bit of R1 as the bit 0 and the following bits as the timer value.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  LI   R1,&amp;gt;01FF    (0000 0001 1111 1111)&lt;br /&gt;
  LDCR R1,15&lt;br /&gt;
&lt;br /&gt;
Do &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; write all 16 bits. The last bit will change bit 15 of the PSI, which is the /RST2 bit. When we are in clock mode, writing a 0 to the /RST2 bit resets all I/O ports. This means that all pins are set to inputs, and devices that rely on them as outputs may be turned off unintentionally.&lt;br /&gt;
&lt;br /&gt;
=== Reading the timer ===&lt;br /&gt;
&lt;br /&gt;
We are interested for the current value of the timer at some occasions. However, since the counter is constantly changing (every 64 clock cycles), reading from the active counter may deliver a wrong result. Think, for instance, about a value 0x2000, which is decremented after we read the first bits, we may end up with 0x2FFF (2 from 0x2000, FFF from 0x1FFF). &lt;br /&gt;
&lt;br /&gt;
For this reason, the timer value is copied to a &amp;#039;&amp;#039;&amp;#039;read buffer&amp;#039;&amp;#039;&amp;#039;. This happens at every 64th clock cycle, when the counter is also decremented, but only when we are &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; in clock mode. &lt;br /&gt;
&lt;br /&gt;
To read the buffer, we must set the PSI to clock mode first, which freezes the counter value in the read buffer. Then we can read the counter using STCR.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  STCR R1,15&lt;br /&gt;
  SRL  R1,1&lt;br /&gt;
  SBZ  0&lt;br /&gt;
&lt;br /&gt;
As we see, register 1 will contain bit 0 in its rightmost bit, so we shift R1 by one position to move it out.&lt;br /&gt;
&lt;br /&gt;
Do not forget to return to interrupt mode, or the buffer will not receive further updates.&lt;br /&gt;
&lt;br /&gt;
As mentioned, bit 15 is the /RST2 bit when we write to it. For reading, bit 15 reflects the /INTREQ output.&lt;br /&gt;
&lt;br /&gt;
=== The weird S0 input ===&lt;br /&gt;
&lt;br /&gt;
There is one peculiarity with the PSI. When the input line S0 is set to 1, the clock mode is &amp;#039;&amp;#039;&amp;#039;left temporarily&amp;#039;&amp;#039;&amp;#039; - until S0 is 0 again, and bit 0 is 1. If bit 0 is 0, this has no effect, as the clock mode is never entered.&lt;br /&gt;
&lt;br /&gt;
What is the reason for this? &lt;br /&gt;
&lt;br /&gt;
TI obviously considered use cases where the PSI remains in clock mode for longer times, while the I/O ports must still be operated. The I/O ports are available on bits 16-31, which means that the S0 input is 1 for these addresses. So when we do a SBO 16, this is not related to the timer value, and thus should control the first I/O port. To allow this, the chip returns to interrupt/port mode for this operation, and when S0 is reset, returns to clock mode.&lt;br /&gt;
&lt;br /&gt;
In the TI console, the S0-S4 inputs are directly connected with the address bus lines A10-A14. This has a strange side effect: Whenever address bus line A10 changes to 1, the clock mode is left temporarily, and this also happens for ordinary memory accesses.&lt;br /&gt;
&lt;br /&gt;
Suppose that we are in clock mode (SBO 0). In this mode, the read buffer should not be updated, so when we read it, we should get the same value every time, although the real counter value is different. But this is difficult to achieve: If our program uses a memory address where A10 is 1 for some instruction, loading this instruction into the CPU will set A10 to 1, and thus leave clock mode, which updates the counter.&lt;br /&gt;
&lt;br /&gt;
Therefore, you may have the impression that the read register continues to be updated even though you set bit 0 to 1. But this is only happening because your program extends over a location where A10 is 1. So if you want to prove that in clock mode the read register remains fixed, your program must avoid all memory locations where A10 is 1.&lt;br /&gt;
&lt;br /&gt;
== Shared pins ==&lt;br /&gt;
&lt;br /&gt;
Some pins have a double meaning; for example, pin 34 is labeled /INT7 / P15. How do we select the functions?&lt;br /&gt;
&lt;br /&gt;
The shared pins are those for the interrupt inputs /INT7 to /INT15; they may also be used for P7 to P15 (in the opposite order).&lt;br /&gt;
&lt;br /&gt;
If the Pn port is set to &amp;#039;&amp;#039;&amp;#039;input&amp;#039;&amp;#039;&amp;#039;, the situation looks like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
This means that both port and interrupt input have the same values; each one is a mirror of the other&amp;#039;s state. We notice it by reading the bits of the associated CRU addresses, getting the same results. There is a difference, though: The interrupt input can be armed to trigger an outgoing interrupt, while the I/O port cannot.&lt;br /&gt;
&lt;br /&gt;
In the other case, the Pn port is an &amp;#039;&amp;#039;&amp;#039;output&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Is that possible? - Sure. The interrupt input will reflect the output of the port. When we set the port to 1, the interrupt input will get that 1 (same for 0). If we arm the interrupt input, setting the output port to 0 will trigger an interrupt via this input. The 9901 specification document recommends to disarm the interrupt trigger for the shared ports because of this possibly unwanted effect.&lt;br /&gt;
&lt;br /&gt;
== Safety precautions ==&lt;br /&gt;
&lt;br /&gt;
The TMS9901 PSI is an interrupt encoder and an I/O circuit. You may have heard that improper use of I/O circuits may damage the circuit so that it must be replaced. Is this possible with the 9901 as well? Is there a way to kill the 9901?&lt;br /&gt;
&lt;br /&gt;
Short answer: Yes.&lt;br /&gt;
&lt;br /&gt;
Longer answer: Yes, but it depends on the way how the ports are used.&lt;br /&gt;
&lt;br /&gt;
If the port is used as an input, nothing bad will happen, since the ports have high input impedance. You can put 0V or 5V directly to the input; of course, you should not use excessive voltage.&lt;br /&gt;
&lt;br /&gt;
=== How to damage the 9901 ports ===&lt;br /&gt;
&lt;br /&gt;
The problem is when the port is configured as an output port. In that case it may output 0V (0) or 5V (1). When you connect the output to the opposite voltage, you will cause a short circuit which quickly exceeds the chip&amp;#039;s maximum power dissipation of 750 mW. This means you will fry the chip.&lt;br /&gt;
&lt;br /&gt;
Consider the following setup (but don&amp;#039;t try it!).&lt;br /&gt;
&lt;br /&gt;
[[File:Badport9901.png|300px]]&lt;br /&gt;
&lt;br /&gt;
Obviously, port P4 is assumed to be an input. When the switch is open, port P4 will sense a 1 on its input. When the switch is closed, it will sense a 0.&lt;br /&gt;
&lt;br /&gt;
Now what happens when you execute this code?&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  20&lt;br /&gt;
&lt;br /&gt;
This will program port P4 to be an output, and to show a 1 (5V) at the output. SBZ will, in contrast, make this port output a 0. So how do you make it an input port? - This is done by resetting the chip. As long as you don&amp;#039;t use a SBZ/SBO (or a LDCR) on the ports, the ports are inputs.&lt;br /&gt;
&lt;br /&gt;
Nothing has happened up to now. But now imagine that you press the switch. This will put the 1 output to ground, causing the short circuit, and your 9901 is toast.&lt;br /&gt;
&lt;br /&gt;
=== What about the TI console? ===&lt;br /&gt;
&lt;br /&gt;
So can we damage the 9901 in the TI console by a suitable CRU operation? - Luckily not. &lt;br /&gt;
&lt;br /&gt;
The reason is the way how TI used the ports on the mainboard. Most of the ports are configured as outputs. Nothing happens when you reset them to inputs.&lt;br /&gt;
&lt;br /&gt;
* The pure interrupt inputs (/INT1 to /INT6) are safe, as they cannot output anything. They have no output driver that could be fried.&lt;br /&gt;
* P0 and P1 are not connected.&lt;br /&gt;
* P2, P3, and P4 are designed as outputs, so they cannot be turned from an input to an output.&lt;br /&gt;
* P5 is the selector of the Alpha Lock key, so output as well.&lt;br /&gt;
* P6 and P7 (/INT15) are the cassette motor control outputs.&lt;br /&gt;
* P8 (/INT14) is the audio gate output.&lt;br /&gt;
* P9 (/INT13) is the cassette output.&lt;br /&gt;
* P10 (/INT12) is pulled up to 5V via a 10 kOhm resistor. If you put a 0 on this port (5V), the maximum current is 500 µA, which results in a power dissipation of 2.5 mW (far below the maximum 750 mW).&lt;br /&gt;
* P11 (/INT11) is the cassette input. However, there is a resistor of 2.2 kOhm (R413) which effectively guards the port of high currents.&lt;br /&gt;
* P12, P13, P14, P15 (/INT10 ... /INT7) are the keyboard input lines. Again, we have pull-ups of 10 kOhm against +5V, but also 470 Ohm on the line, which means a maximum of 8.5 mA or 42.5 mW. This is still well below the maximum value.&lt;br /&gt;
&lt;br /&gt;
So we see that resistors are the best way to protect the ports. And we may calm down; we cannot burn the 9901 in the TI console by a bug in our program.&lt;br /&gt;
&lt;br /&gt;
== Literature ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;ref1&amp;quot;&amp;gt;[1]&amp;lt;/span&amp;gt; Texas Instruments: &amp;#039;&amp;#039;TMS 9901 Programmable Systems Interface Data Manual&amp;#039;&amp;#039;, July 1977&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;ref2&amp;quot;&amp;gt;[2]&amp;lt;/span&amp;gt; Adam Osborne, Gerry Kane: &amp;#039;&amp;#039;Osborne 16-Bit Microprocessor Handbook / Includes 2900 Chip Slice Family&amp;#039;&amp;#039;. Published by OSBORNE/McGraw-Hill 1975, 1981. ISBN 0-931988-43-8.&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50621</id>
		<title>Programmable Systems Interface TMS 9901</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50621"/>
		<updated>2022-04-23T13:54:48Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Interrupt mode */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The Programmable System Interface (PSI) is a circuit that is used on the TI-99/4A main board, on some expansion cards, and on the Geneve.&lt;br /&gt;
&lt;br /&gt;
The designation of the PSI is &amp;#039;&amp;#039;&amp;#039;TMS9901&amp;#039;&amp;#039;&amp;#039;, and this is the name that most users should be more familiar with.&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
&lt;br /&gt;
The TMS 9901 Programmable Systems Interface is a multi-purpose circuit that is used in the TI-99/4A and the Geneve computer, and also on some peripheral cards like the Corcomp Floppy Disk Controller.&lt;br /&gt;
&lt;br /&gt;
The 9901 has three main usage modes:&lt;br /&gt;
&lt;br /&gt;
* Input/Output ports&lt;br /&gt;
* Interrupt prioritizing&lt;br /&gt;
* Timer&lt;br /&gt;
&lt;br /&gt;
The I/O ports are mainly used for scanning the keyboard and joysticks, while the timer is used for cassette data encoding and decoding. The interrupt prioritizer is only used for the video interrupt which is routed from the VDP to the CPU through the 9901.&lt;br /&gt;
&lt;br /&gt;
== Package ==&lt;br /&gt;
&lt;br /&gt;
This is the pin assignment according to the 9901 specification.&lt;br /&gt;
&lt;br /&gt;
[[File:Pack9901.png|300px|left]]&lt;br /&gt;
&lt;br /&gt;
You can see pins of related functions marked by the same background color.&lt;br /&gt;
&lt;br /&gt;
* Orange: Interrupt inputs&lt;br /&gt;
* Red: CRU bit selection (00000 - 11111, i.e. 0-31 decimal)&lt;br /&gt;
* Blue: CRU interface&lt;br /&gt;
* Violet: I/O ports&lt;br /&gt;
* Yellow: Outgoing interrupt and level&lt;br /&gt;
&lt;br /&gt;
Note that some pins have a double function, or in other words, two functions share the same pin. I will discuss this in the following sections.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Input/Output port operation ==&lt;br /&gt;
&lt;br /&gt;
The simplest way of working with the 9901 is input/output operation with the ports. The 9901 has 16 configurable ports that can be individually used as inputs or outputs: P0 to P15.&lt;br /&gt;
&lt;br /&gt;
=== Px port as input ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
The power-on preset for every port is input. If you want to probe the P2 status (bit 18), use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 TB   18&lt;br /&gt;
 JEQ  P2ACT&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the given CRU address into the Equal bit of the status register. JEQ will jump to the location when this input has a high level. The maximum input current is 100 µA. You should protect the input with a resistor; [[#Safety_precautions | see below]] for a more detailed discussion.&lt;br /&gt;
&lt;br /&gt;
If you want to read all 16 ports, use the STCR instruction:&lt;br /&gt;
&lt;br /&gt;
 LI   R12,32&lt;br /&gt;
 STCR R1,0&lt;br /&gt;
&lt;br /&gt;
Note that the CRU base address in R12 advances in steps of 2, so bit 16 is on address 32. Also, for reading 16 bits, you must specify a count of 0.&lt;br /&gt;
&lt;br /&gt;
The 9901 offers some more inputs than the 16 ports; we can use the interrupt inputs as well. More about that can be found in the section about the [[#Interrupt_mode| interrupt mode]].&lt;br /&gt;
&lt;br /&gt;
=== Px port as output ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Once you write a value to the CRU address of a 9901 port, this port switches to output mode. The value written to the port is the value that is shown on its pin. If we want to output a 1 on P6, we have to use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
&lt;br /&gt;
If we wanted to output a 0, SBZ would have to be used. In order to set several ports in one go, use LDCR.&lt;br /&gt;
&lt;br /&gt;
We can also read the output port just by using TB as usual (or STCR for several ports at once). &lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
 TB   22&lt;br /&gt;
 JEQ  EXPECT&lt;br /&gt;
&lt;br /&gt;
The TB operation should return the 1 which the previous SBO has set.&lt;br /&gt;
&lt;br /&gt;
You &amp;#039;&amp;#039;&amp;#039;cannot&amp;#039;&amp;#039;&amp;#039; switch back to input mode. This is only possible by resetting the 9901, either using the /RST1 input line, or by setting the /RST2 bit (bit 15) to 0 in clock mode.&lt;br /&gt;
&lt;br /&gt;
== Interrupt mode ==&lt;br /&gt;
&lt;br /&gt;
In interrupt mode, bits 1 to 15 are used to set the interrupt mask (write), or to query the interrupt inputs (read). That is, the meaning of reading and writing the bits is indeed different.&lt;br /&gt;
&lt;br /&gt;
This schema shows how the interrupt inputs are used.&lt;br /&gt;
&lt;br /&gt;
[[File:Int9901.png|600px]]&lt;br /&gt;
&lt;br /&gt;
This illustration is slightly simplified; there is a latch between each interrupt input and the mask AND gate. This is not relevant for our programming, though. For more details see the [[#ref2 | TMS 9901 Programmable Systems Interface Data Manual]].&lt;br /&gt;
&lt;br /&gt;
=== Checking the interrupt inputs ===&lt;br /&gt;
&lt;br /&gt;
In general, every interrupt input can be read via CRU access:&lt;br /&gt;
&lt;br /&gt;
  CLR R12&lt;br /&gt;
  TB  15&lt;br /&gt;
  JEQ NOINT15&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the specified CRU address to the Equal status bit. This means that JEQ jumps to the location when the bit is 1. Since all interrupt inputs are negative logic, reading a 1 means that there is no interrupt at this input (so I called it NOINT15).&lt;br /&gt;
&lt;br /&gt;
If we want to read several interrupt inputs by a single instruction, we have to use STCR.&lt;br /&gt;
&lt;br /&gt;
  LI   R12,16&lt;br /&gt;
  STCR R1,8&lt;br /&gt;
&lt;br /&gt;
This reads the states of the /INT8, /INT9, ..., /INT15 inputs and puts them in the most significant byte of R1. (See the specification of STCR and LDCR for transfers of up to 8 bits.)&lt;br /&gt;
&lt;br /&gt;
=== Interrupt levels ===&lt;br /&gt;
&lt;br /&gt;
Each interrupt input (/INT1 to /INT15) may be used to trigger an interrupt at another circuit, typically the CPU. For this purpose, the PSI offers an /INTREQ outgoing line. In the TI console, it is routed to the /INTREQ input of the TMS9900 CPU. &lt;br /&gt;
&lt;br /&gt;
In addition there is a priority encoder, which is not shown here. This encoder outputs a 4-bit number which reflects the smallest number of the interrupt inputs where an interrupt has been sensed.&lt;br /&gt;
&lt;br /&gt;
That is, if /INT4 and /INT7 are currently set to 0 (active), the priority encoder outputs (0100), which means 4.&lt;br /&gt;
&lt;br /&gt;
The TMS9900 CPU can differentiate between these interrupt levels, and by using its LIMI command, we can set a threshold where interrupts are ignored. So when we use a LIMI 3, the interrupts of level 4, 5, 6 and higher will be ignored. &lt;br /&gt;
&lt;br /&gt;
The bad news is that Texas Instruments did not use these priority lines in the TI console. In fact, these outputs (IC0 .. IC3) are not connected, and the respective interrupt level inputs at the CPU are hardwired to (0001), which means that every interrupt that occurs in the TI console is automatically set to level 1. So in order to block all interrupts from the 9901&lt;br /&gt;
&lt;br /&gt;
 LIMI 0 &lt;br /&gt;
&lt;br /&gt;
must be executed. To allow all, &lt;br /&gt;
&lt;br /&gt;
 LIMI 1 &lt;br /&gt;
&lt;br /&gt;
is sufficient. For some unclear reason, TI used LIMI 2 in all occasions.&lt;br /&gt;
&lt;br /&gt;
=== Arming and disarming interrupts ===&lt;br /&gt;
&lt;br /&gt;
As said above, every interrupt input can be read at any time in interrupt mode. In that sense, we don&amp;#039;t really treat these inputs as interrupts inputs, but simply as input ports.&lt;br /&gt;
&lt;br /&gt;
If we want a port to trigger an interrupt, we must arm this interrupt input. This is done by setting the respective bit to 1.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  2&lt;br /&gt;
&lt;br /&gt;
This enables interrupt input /INT2 to cause an /INTREQ when its input changes to 0. In the TI console, /INT2 is connected to the interrupt output of the VDP (video processor). If you want to block interrupts on that input, use SBZ.&lt;br /&gt;
&lt;br /&gt;
It is a common misunderstanding that interrupts can be cleared by using a SBZ on an input. I also found this in the popular &amp;#039;&amp;#039;TI Intern&amp;#039;&amp;#039; book. Instead, SBZ n simply blocks interrupt input /INTn. In the case of the video processor, it is up to the VDP to clear its interrupt, and this is done by reading its status register.&lt;br /&gt;
&lt;br /&gt;
Consequently, you &amp;#039;&amp;#039;&amp;#039;cannot&amp;#039;&amp;#039;&amp;#039; read the current mask setting of the 9901. What you get from reading are the input levels of the pins. So if you plan to change the mask bits temporarily, be aware that you cannot ask the 9901 to give you the current setting which you want to restore later. You will have to find out by another way.&lt;br /&gt;
&lt;br /&gt;
== Counter mode ==&lt;br /&gt;
&lt;br /&gt;
Besides the I/O capabilities, the PSI offers a &amp;quot;real-time&amp;quot; clock. This should not be understood as a precise timer with seconds, hundreds of seconds or other but as a countdown timer that is decremented every 64 CPU clock cycles.&lt;br /&gt;
&lt;br /&gt;
This schematic is taken from the Osborne book, redrawn by me in LibreOffice (and with added Load Buffer). &lt;br /&gt;
&lt;br /&gt;
[[File:Clock9901.png|550px]]&lt;br /&gt;
&lt;br /&gt;
=== Using the timer ===&lt;br /&gt;
&lt;br /&gt;
There are basically two ways of using the countdown timer:&lt;br /&gt;
&lt;br /&gt;
* Interval timer: Set the counter to a value; wait until the counter reaches zero. The PSI can be set up to raise an interrupt at that instant.&lt;br /&gt;
* Event timer: Set the counter to a value; do something and then read the counter value.&lt;br /&gt;
&lt;br /&gt;
The counter has 14 bits; that is, its maximum value is 0x3FFF (16383). The counter is decremented every 64 clock cycles, and for the TI-99/4A and the Geneve with a clock cycle time of 333ns, this means that the counter resolution is 21.333 µs. The whole 16384 steps take 0.3495 seconds (349.5 ms).&lt;br /&gt;
&lt;br /&gt;
When the counter reaches 0, it is reloaded from the load buffer, and counting continues.&lt;br /&gt;
&lt;br /&gt;
It is essential to understand that the counter &amp;#039;&amp;#039;&amp;#039;always&amp;#039;&amp;#039;&amp;#039; counts down, independent of the mode. The specification says that the clock is enabled when a non-zero value is loaded into the load buffer, but tests on a real Geneve show that the clock decrements even when the load buffer is filled with 0.&lt;br /&gt;
&lt;br /&gt;
=== Setting the timer ===&lt;br /&gt;
&lt;br /&gt;
In order to set the load buffer and so to define the maximum counter value, we must switch to clock mode. This is achieved by setting bit 0 of the PSI to 1. In the TI/Geneve systems, the PSI is mapped to CRU base address 0, so we can do this&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  0&lt;br /&gt;
&lt;br /&gt;
to enter &amp;#039;&amp;#039;&amp;#039;clock mode&amp;#039;&amp;#039;&amp;#039;. Using SBZ 0 returns to &amp;#039;&amp;#039;&amp;#039;interrupt mode&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
The effect of switching to clock mode is that the load buffer becomes accessible by the CRU addresses 1 to 14. Supposed that we want to load the buffer with the value 0x00FF, we may use these lines:&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  LI   R1,&amp;gt;00FF&lt;br /&gt;
  INCT R12&lt;br /&gt;
  LDCR R1,14&lt;br /&gt;
&lt;br /&gt;
As we see, we must change the CRU base. The LDCR operation always starts at the address defined in R12; if this is not 0, we have to set it here. Note, however, that the CRU address is always half of the R12 value, since the rightmost bit, A15, is not part of the address. If we want to start at bit 1, we must increase R12 by 2. This is, by the way, done wrong in several places in the literature where R12 is only set to 1.&lt;br /&gt;
&lt;br /&gt;
We can combine the SBO and the LDCR to one operation if we define the rightmost bit of R1 as the bit 0 and the following bits as the timer value.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  LI   R1,&amp;gt;01FF    (0000 0001 1111 1111)&lt;br /&gt;
  LDCR R1,15&lt;br /&gt;
&lt;br /&gt;
Do &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; write all 16 bits. The last bit will change bit 15 of the PSI, which is the /RST2 bit. When we are in clock mode, writing a 0 to the /RST2 bit resets all I/O ports. This means that all pins are set to inputs, and devices that rely on them as outputs may be turned off unintentionally.&lt;br /&gt;
&lt;br /&gt;
=== Reading the timer ===&lt;br /&gt;
&lt;br /&gt;
We are interested for the current value of the timer at some occasions. However, since the counter is constantly changing (every 64 clock cycles), reading from the active counter may deliver a wrong result. Think, for instance, about a value 0x2000, which is decremented after we read the first bits, we may end up with 0x2FFF (2 from 0x2000, FFF from 0x1FFF). &lt;br /&gt;
&lt;br /&gt;
For this reason, the timer value is copied to a &amp;#039;&amp;#039;&amp;#039;read buffer&amp;#039;&amp;#039;&amp;#039;. This happens at every 64th clock cycle, when the counter is also decremented, but only when we are &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; in clock mode. &lt;br /&gt;
&lt;br /&gt;
To read the buffer, we must set the PSI to clock mode first, which freezes the counter value in the read buffer. Then we can read the counter using STCR.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  STCR R1,15&lt;br /&gt;
  SRL  R1,1&lt;br /&gt;
  SBZ  0&lt;br /&gt;
&lt;br /&gt;
As we see, register 1 will contain bit 0 in its rightmost bit, so we shift R1 by one position to move it out.&lt;br /&gt;
&lt;br /&gt;
Do not forget to return to interrupt mode, or the buffer will not receive further updates.&lt;br /&gt;
&lt;br /&gt;
As mentioned, bit 15 is the /RST2 bit when we write to it. For reading, bit 15 reflects the /INTREQ output.&lt;br /&gt;
&lt;br /&gt;
=== The weird S0 input ===&lt;br /&gt;
&lt;br /&gt;
There is one peculiarity with the PSI. When the input line S0 is set to 1, the clock mode is &amp;#039;&amp;#039;&amp;#039;left temporarily&amp;#039;&amp;#039;&amp;#039; - until S0 is 0 again, and bit 0 is 1. If bit 0 is 0, this has no effect, as the clock mode is never entered.&lt;br /&gt;
&lt;br /&gt;
What is the reason for this? &lt;br /&gt;
&lt;br /&gt;
TI obviously considered use cases where the PSI remains in clock mode for longer times, while the I/O ports must still be operated. The I/O ports are available on bits 16-31, which means that the S0 input is 1 for these addresses. So when we do a SBO 16, this is not related to the timer value, and thus should control the first I/O port. To allow this, the chip returns to interrupt/port mode for this operation, and when S0 is reset, returns to clock mode.&lt;br /&gt;
&lt;br /&gt;
In the TI console, the S0-S4 inputs are directly connected with the address bus lines A10-A14. This has a strange side effect: Whenever address bus line A10 changes to 1, the clock mode is left temporarily, and this also happens for ordinary memory accesses.&lt;br /&gt;
&lt;br /&gt;
Suppose that we are in clock mode (SBO 0). In this mode, the read buffer should not be updated, so when we read it, we should get the same value every time, although the real counter value is different. But this is difficult to achieve: If our program uses a memory address where A10 is 1 for some instruction, loading this instruction into the CPU will set A10 to 1, and thus leave clock mode, which updates the counter.&lt;br /&gt;
&lt;br /&gt;
Therefore, you may have the impression that the read register continues to be updated even though you set bit 0 to 1. But this is only happening because your program extends over a location where A10 is 1. So if you want to prove that in clock mode the read register remains fixed, your program must avoid all memory locations where A10 is 1.&lt;br /&gt;
&lt;br /&gt;
== Shared pins ==&lt;br /&gt;
&lt;br /&gt;
Some pins have a double meaning; for example, pin 34 is labeled /INT7 / P15. How do we select the functions?&lt;br /&gt;
&lt;br /&gt;
The shared pins are those for the interrupt inputs /INT7 to /INT15; they may also be used for P7 to P15 (in the opposite order).&lt;br /&gt;
&lt;br /&gt;
If the Pn port is set to &amp;#039;&amp;#039;&amp;#039;input&amp;#039;&amp;#039;&amp;#039;, the situation looks like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
This means that both port and interrupt input have the same values; each one is a mirror of the other&amp;#039;s state. We notice it by reading the bits of the associated CRU addresses, getting the same results. There is a difference, though: The interrupt input can be armed to trigger an outgoing interrupt, while the I/O port cannot.&lt;br /&gt;
&lt;br /&gt;
In the other case, the Pn port is an &amp;#039;&amp;#039;&amp;#039;output&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Is that possible? - Sure. The interrupt input will reflect the output of the port. When we set the port to 1, the interrupt input will get that 1 (same for 0). If we arm the interrupt input, setting the output port to 0 will trigger an interrupt via this input. The 9901 specification document recommends to disarm the interrupt trigger for the shared ports because of this possibly unwanted effect.&lt;br /&gt;
&lt;br /&gt;
== Safety precautions ==&lt;br /&gt;
&lt;br /&gt;
The TMS9901 PSI is an interrupt encoder and an I/O circuit. You may have heard that improper use of I/O circuits may damage the circuit so that it must be replaced. Is this possible with the 9901 as well? Is there a way to kill the 9901?&lt;br /&gt;
&lt;br /&gt;
Short answer: Yes.&lt;br /&gt;
&lt;br /&gt;
Longer answer: Yes, but it depends on the way how the ports are used.&lt;br /&gt;
&lt;br /&gt;
If the port is used as an input, nothing bad will happen, since the ports have high input impedance. You can put 0V or 5V directly to the input; of course, you should not use excessive voltage.&lt;br /&gt;
&lt;br /&gt;
=== How to damage the 9901 ports ===&lt;br /&gt;
&lt;br /&gt;
The problem is when the port is configured as an output port. In that case it may output 0V (0) or 5V (1). When you connect the output to the opposite voltage, you will cause a short circuit which quickly exceeds the chip&amp;#039;s maximum power dissipation of 750 mW. This means you will fry the chip.&lt;br /&gt;
&lt;br /&gt;
Consider the following setup (but don&amp;#039;t try it!).&lt;br /&gt;
&lt;br /&gt;
[[File:Badport9901.png|300px]]&lt;br /&gt;
&lt;br /&gt;
Obviously, port P4 is assumed to be an input. When the switch is open, port P4 will sense a 1 on its input. When the switch is closed, it will sense a 0.&lt;br /&gt;
&lt;br /&gt;
Now what happens when you execute this code?&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  20&lt;br /&gt;
&lt;br /&gt;
This will program port P4 to be an output, and to show a 1 (5V) at the output. SBZ will, in contrast, make this port output a 0. So how do you make it an input port? - This is done by resetting the chip. As long as you don&amp;#039;t use a SBZ/SBO (or a LDCR) on the ports, the ports are inputs.&lt;br /&gt;
&lt;br /&gt;
Nothing has happened up to now. But now imagine that you press the switch. This will put the 1 output to ground, causing the short circuit, and your 9901 is toast.&lt;br /&gt;
&lt;br /&gt;
=== What about the TI console? ===&lt;br /&gt;
&lt;br /&gt;
So can we damage the 9901 in the TI console by a suitable CRU operation? - Luckily not. &lt;br /&gt;
&lt;br /&gt;
The reason is the way how TI used the ports on the mainboard. Most of the ports are configured as outputs. Nothing happens when you reset them to inputs.&lt;br /&gt;
&lt;br /&gt;
* The pure interrupt inputs (/INT1 to /INT6) are safe, as they cannot output anything. They have no output driver that could be fried.&lt;br /&gt;
* P0 and P1 are not connected.&lt;br /&gt;
* P2, P3, and P4 are designed as outputs, so they cannot be turned from an input to an output.&lt;br /&gt;
* P5 is the selector of the Alpha Lock key, so output as well.&lt;br /&gt;
* P6 and P7 (/INT15) are the cassette motor control outputs.&lt;br /&gt;
* P8 (/INT14) is the audio gate output.&lt;br /&gt;
* P9 (/INT13) is the cassette output.&lt;br /&gt;
* P10 (/INT12) is pulled up to 5V via a 10 kOhm resistor. If you put a 0 on this port (5V), the maximum current is 500 µA, which results in a power dissipation of 2.5 mW (far below the maximum 750 mW).&lt;br /&gt;
* P11 (/INT11) is the cassette input. However, there is a resistor of 2.2 kOhm (R413) which effectively guards the port of high currents.&lt;br /&gt;
* P12, P13, P14, P15 (/INT10 ... /INT7) are the keyboard input lines. Again, we have pull-ups of 10 kOhm against +5V, but also 470 Ohm on the line, which means a maximum of 8.5 mA or 42.5 mW. This is still well below the maximum value.&lt;br /&gt;
&lt;br /&gt;
So we see that resistors are the best way to protect the ports. And we may calm down; we cannot burn the 9901 in the TI console by a bug in our program.&lt;br /&gt;
&lt;br /&gt;
== Literature ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;ref1&amp;quot;&amp;gt;[1]&amp;lt;/span&amp;gt; Texas Instruments: &amp;#039;&amp;#039;TMS 9901 Programmable Systems Interface Data Manual&amp;#039;&amp;#039;, July 1977&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;ref2&amp;quot;&amp;gt;[2]&amp;lt;/span&amp;gt; Adam Osborne, Gerry Kane: &amp;#039;&amp;#039;Osborne 16-Bit Microprocessor Handbook / Includes 2900 Chip Slice Family&amp;#039;&amp;#039;. Published by OSBORNE/McGraw-Hill 1975, 1981. ISBN 0-931988-43-8.&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50620</id>
		<title>Programmable Systems Interface TMS 9901</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50620"/>
		<updated>2022-04-23T13:53:57Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Literature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The Programmable System Interface (PSI) is a circuit that is used on the TI-99/4A main board, on some expansion cards, and on the Geneve.&lt;br /&gt;
&lt;br /&gt;
The designation of the PSI is &amp;#039;&amp;#039;&amp;#039;TMS9901&amp;#039;&amp;#039;&amp;#039;, and this is the name that most users should be more familiar with.&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
&lt;br /&gt;
The TMS 9901 Programmable Systems Interface is a multi-purpose circuit that is used in the TI-99/4A and the Geneve computer, and also on some peripheral cards like the Corcomp Floppy Disk Controller.&lt;br /&gt;
&lt;br /&gt;
The 9901 has three main usage modes:&lt;br /&gt;
&lt;br /&gt;
* Input/Output ports&lt;br /&gt;
* Interrupt prioritizing&lt;br /&gt;
* Timer&lt;br /&gt;
&lt;br /&gt;
The I/O ports are mainly used for scanning the keyboard and joysticks, while the timer is used for cassette data encoding and decoding. The interrupt prioritizer is only used for the video interrupt which is routed from the VDP to the CPU through the 9901.&lt;br /&gt;
&lt;br /&gt;
== Package ==&lt;br /&gt;
&lt;br /&gt;
This is the pin assignment according to the 9901 specification.&lt;br /&gt;
&lt;br /&gt;
[[File:Pack9901.png|300px|left]]&lt;br /&gt;
&lt;br /&gt;
You can see pins of related functions marked by the same background color.&lt;br /&gt;
&lt;br /&gt;
* Orange: Interrupt inputs&lt;br /&gt;
* Red: CRU bit selection (00000 - 11111, i.e. 0-31 decimal)&lt;br /&gt;
* Blue: CRU interface&lt;br /&gt;
* Violet: I/O ports&lt;br /&gt;
* Yellow: Outgoing interrupt and level&lt;br /&gt;
&lt;br /&gt;
Note that some pins have a double function, or in other words, two functions share the same pin. I will discuss this in the following sections.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Input/Output port operation ==&lt;br /&gt;
&lt;br /&gt;
The simplest way of working with the 9901 is input/output operation with the ports. The 9901 has 16 configurable ports that can be individually used as inputs or outputs: P0 to P15.&lt;br /&gt;
&lt;br /&gt;
=== Px port as input ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
The power-on preset for every port is input. If you want to probe the P2 status (bit 18), use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 TB   18&lt;br /&gt;
 JEQ  P2ACT&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the given CRU address into the Equal bit of the status register. JEQ will jump to the location when this input has a high level. The maximum input current is 100 µA. You should protect the input with a resistor; [[#Safety_precautions | see below]] for a more detailed discussion.&lt;br /&gt;
&lt;br /&gt;
If you want to read all 16 ports, use the STCR instruction:&lt;br /&gt;
&lt;br /&gt;
 LI   R12,32&lt;br /&gt;
 STCR R1,0&lt;br /&gt;
&lt;br /&gt;
Note that the CRU base address in R12 advances in steps of 2, so bit 16 is on address 32. Also, for reading 16 bits, you must specify a count of 0.&lt;br /&gt;
&lt;br /&gt;
The 9901 offers some more inputs than the 16 ports; we can use the interrupt inputs as well. More about that can be found in the section about the [[#Interrupt_mode| interrupt mode]].&lt;br /&gt;
&lt;br /&gt;
=== Px port as output ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Once you write a value to the CRU address of a 9901 port, this port switches to output mode. The value written to the port is the value that is shown on its pin. If we want to output a 1 on P6, we have to use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
&lt;br /&gt;
If we wanted to output a 0, SBZ would have to be used. In order to set several ports in one go, use LDCR.&lt;br /&gt;
&lt;br /&gt;
We can also read the output port just by using TB as usual (or STCR for several ports at once). &lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
 TB   22&lt;br /&gt;
 JEQ  EXPECT&lt;br /&gt;
&lt;br /&gt;
The TB operation should return the 1 which the previous SBO has set.&lt;br /&gt;
&lt;br /&gt;
You &amp;#039;&amp;#039;&amp;#039;cannot&amp;#039;&amp;#039;&amp;#039; switch back to input mode. This is only possible by resetting the 9901, either using the /RST1 input line, or by setting the /RST2 bit (bit 15) to 0 in clock mode.&lt;br /&gt;
&lt;br /&gt;
== Interrupt mode ==&lt;br /&gt;
&lt;br /&gt;
In interrupt mode, bits 1 to 15 are used to set the interrupt mask (write), or to query the interrupt inputs (read). That is, the meaning of reading and writing the bits is indeed different.&lt;br /&gt;
&lt;br /&gt;
This schema shows how the interrupt inputs are used.&lt;br /&gt;
&lt;br /&gt;
[[File:Int9901.png|600px]]&lt;br /&gt;
&lt;br /&gt;
This illustration is slightly simplified; there is a latch between each interrupt input and the mask AND gate. This is not relevant for our programming, though. For more details see the &amp;#039;&amp;#039;TMS 9901 Programmable Systems Interface Data Manual&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
=== Checking the interrupt inputs ===&lt;br /&gt;
&lt;br /&gt;
In general, every interrupt input can be read via CRU access:&lt;br /&gt;
&lt;br /&gt;
  CLR R12&lt;br /&gt;
  TB  15&lt;br /&gt;
  JEQ NOINT15&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the specified CRU address to the Equal status bit. This means that JEQ jumps to the location when the bit is 1. Since all interrupt inputs are negative logic, reading a 1 means that there is no interrupt at this input (so I called it NOINT15).&lt;br /&gt;
&lt;br /&gt;
If we want to read several interrupt inputs by a single instruction, we have to use STCR.&lt;br /&gt;
&lt;br /&gt;
  LI   R12,16&lt;br /&gt;
  STCR R1,8&lt;br /&gt;
&lt;br /&gt;
This reads the states of the /INT8, /INT9, ..., /INT15 inputs and puts them in the most significant byte of R1. (See the specification of STCR and LDCR for transfers of up to 8 bits.)&lt;br /&gt;
&lt;br /&gt;
=== Interrupt levels ===&lt;br /&gt;
&lt;br /&gt;
Each interrupt input (/INT1 to /INT15) may be used to trigger an interrupt at another circuit, typically the CPU. For this purpose, the PSI offers an /INTREQ outgoing line. In the TI console, it is routed to the /INTREQ input of the TMS9900 CPU. &lt;br /&gt;
&lt;br /&gt;
In addition there is a priority encoder, which is not shown here. This encoder outputs a 4-bit number which reflects the smallest number of the interrupt inputs where an interrupt has been sensed.&lt;br /&gt;
&lt;br /&gt;
That is, if /INT4 and /INT7 are currently set to 0 (active), the priority encoder outputs (0100), which means 4.&lt;br /&gt;
&lt;br /&gt;
The TMS9900 CPU can differentiate between these interrupt levels, and by using its LIMI command, we can set a threshold where interrupts are ignored. So when we use a LIMI 3, the interrupts of level 4, 5, 6 and higher will be ignored. &lt;br /&gt;
&lt;br /&gt;
The bad news is that Texas Instruments did not use these priority lines in the TI console. In fact, these outputs (IC0 .. IC3) are not connected, and the respective interrupt level inputs at the CPU are hardwired to (0001), which means that every interrupt that occurs in the TI console is automatically set to level 1. So in order to block all interrupts from the 9901&lt;br /&gt;
&lt;br /&gt;
 LIMI 0 &lt;br /&gt;
&lt;br /&gt;
must be executed. To allow all, &lt;br /&gt;
&lt;br /&gt;
 LIMI 1 &lt;br /&gt;
&lt;br /&gt;
is sufficient. For some unclear reason, TI used LIMI 2 in all occasions.&lt;br /&gt;
&lt;br /&gt;
=== Arming and disarming interrupts ===&lt;br /&gt;
&lt;br /&gt;
As said above, every interrupt input can be read at any time in interrupt mode. In that sense, we don&amp;#039;t really treat these inputs as interrupts inputs, but simply as input ports.&lt;br /&gt;
&lt;br /&gt;
If we want a port to trigger an interrupt, we must arm this interrupt input. This is done by setting the respective bit to 1.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  2&lt;br /&gt;
&lt;br /&gt;
This enables interrupt input /INT2 to cause an /INTREQ when its input changes to 0. In the TI console, /INT2 is connected to the interrupt output of the VDP (video processor). If you want to block interrupts on that input, use SBZ.&lt;br /&gt;
&lt;br /&gt;
It is a common misunderstanding that interrupts can be cleared by using a SBZ on an input. I also found this in the popular &amp;#039;&amp;#039;TI Intern&amp;#039;&amp;#039; book. Instead, SBZ n simply blocks interrupt input /INTn. In the case of the video processor, it is up to the VDP to clear its interrupt, and this is done by reading its status register.&lt;br /&gt;
&lt;br /&gt;
Consequently, you &amp;#039;&amp;#039;&amp;#039;cannot&amp;#039;&amp;#039;&amp;#039; read the current mask setting of the 9901. What you get from reading are the input levels of the pins. So if you plan to change the mask bits temporarily, be aware that you cannot ask the 9901 to give you the current setting which you want to restore later. You will have to find out by another way.&lt;br /&gt;
&lt;br /&gt;
== Counter mode ==&lt;br /&gt;
&lt;br /&gt;
Besides the I/O capabilities, the PSI offers a &amp;quot;real-time&amp;quot; clock. This should not be understood as a precise timer with seconds, hundreds of seconds or other but as a countdown timer that is decremented every 64 CPU clock cycles.&lt;br /&gt;
&lt;br /&gt;
This schematic is taken from the Osborne book, redrawn by me in LibreOffice (and with added Load Buffer). &lt;br /&gt;
&lt;br /&gt;
[[File:Clock9901.png|550px]]&lt;br /&gt;
&lt;br /&gt;
=== Using the timer ===&lt;br /&gt;
&lt;br /&gt;
There are basically two ways of using the countdown timer:&lt;br /&gt;
&lt;br /&gt;
* Interval timer: Set the counter to a value; wait until the counter reaches zero. The PSI can be set up to raise an interrupt at that instant.&lt;br /&gt;
* Event timer: Set the counter to a value; do something and then read the counter value.&lt;br /&gt;
&lt;br /&gt;
The counter has 14 bits; that is, its maximum value is 0x3FFF (16383). The counter is decremented every 64 clock cycles, and for the TI-99/4A and the Geneve with a clock cycle time of 333ns, this means that the counter resolution is 21.333 µs. The whole 16384 steps take 0.3495 seconds (349.5 ms).&lt;br /&gt;
&lt;br /&gt;
When the counter reaches 0, it is reloaded from the load buffer, and counting continues.&lt;br /&gt;
&lt;br /&gt;
It is essential to understand that the counter &amp;#039;&amp;#039;&amp;#039;always&amp;#039;&amp;#039;&amp;#039; counts down, independent of the mode. The specification says that the clock is enabled when a non-zero value is loaded into the load buffer, but tests on a real Geneve show that the clock decrements even when the load buffer is filled with 0.&lt;br /&gt;
&lt;br /&gt;
=== Setting the timer ===&lt;br /&gt;
&lt;br /&gt;
In order to set the load buffer and so to define the maximum counter value, we must switch to clock mode. This is achieved by setting bit 0 of the PSI to 1. In the TI/Geneve systems, the PSI is mapped to CRU base address 0, so we can do this&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  0&lt;br /&gt;
&lt;br /&gt;
to enter &amp;#039;&amp;#039;&amp;#039;clock mode&amp;#039;&amp;#039;&amp;#039;. Using SBZ 0 returns to &amp;#039;&amp;#039;&amp;#039;interrupt mode&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
The effect of switching to clock mode is that the load buffer becomes accessible by the CRU addresses 1 to 14. Supposed that we want to load the buffer with the value 0x00FF, we may use these lines:&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  LI   R1,&amp;gt;00FF&lt;br /&gt;
  INCT R12&lt;br /&gt;
  LDCR R1,14&lt;br /&gt;
&lt;br /&gt;
As we see, we must change the CRU base. The LDCR operation always starts at the address defined in R12; if this is not 0, we have to set it here. Note, however, that the CRU address is always half of the R12 value, since the rightmost bit, A15, is not part of the address. If we want to start at bit 1, we must increase R12 by 2. This is, by the way, done wrong in several places in the literature where R12 is only set to 1.&lt;br /&gt;
&lt;br /&gt;
We can combine the SBO and the LDCR to one operation if we define the rightmost bit of R1 as the bit 0 and the following bits as the timer value.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  LI   R1,&amp;gt;01FF    (0000 0001 1111 1111)&lt;br /&gt;
  LDCR R1,15&lt;br /&gt;
&lt;br /&gt;
Do &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; write all 16 bits. The last bit will change bit 15 of the PSI, which is the /RST2 bit. When we are in clock mode, writing a 0 to the /RST2 bit resets all I/O ports. This means that all pins are set to inputs, and devices that rely on them as outputs may be turned off unintentionally.&lt;br /&gt;
&lt;br /&gt;
=== Reading the timer ===&lt;br /&gt;
&lt;br /&gt;
We are interested for the current value of the timer at some occasions. However, since the counter is constantly changing (every 64 clock cycles), reading from the active counter may deliver a wrong result. Think, for instance, about a value 0x2000, which is decremented after we read the first bits, we may end up with 0x2FFF (2 from 0x2000, FFF from 0x1FFF). &lt;br /&gt;
&lt;br /&gt;
For this reason, the timer value is copied to a &amp;#039;&amp;#039;&amp;#039;read buffer&amp;#039;&amp;#039;&amp;#039;. This happens at every 64th clock cycle, when the counter is also decremented, but only when we are &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; in clock mode. &lt;br /&gt;
&lt;br /&gt;
To read the buffer, we must set the PSI to clock mode first, which freezes the counter value in the read buffer. Then we can read the counter using STCR.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  STCR R1,15&lt;br /&gt;
  SRL  R1,1&lt;br /&gt;
  SBZ  0&lt;br /&gt;
&lt;br /&gt;
As we see, register 1 will contain bit 0 in its rightmost bit, so we shift R1 by one position to move it out.&lt;br /&gt;
&lt;br /&gt;
Do not forget to return to interrupt mode, or the buffer will not receive further updates.&lt;br /&gt;
&lt;br /&gt;
As mentioned, bit 15 is the /RST2 bit when we write to it. For reading, bit 15 reflects the /INTREQ output.&lt;br /&gt;
&lt;br /&gt;
=== The weird S0 input ===&lt;br /&gt;
&lt;br /&gt;
There is one peculiarity with the PSI. When the input line S0 is set to 1, the clock mode is &amp;#039;&amp;#039;&amp;#039;left temporarily&amp;#039;&amp;#039;&amp;#039; - until S0 is 0 again, and bit 0 is 1. If bit 0 is 0, this has no effect, as the clock mode is never entered.&lt;br /&gt;
&lt;br /&gt;
What is the reason for this? &lt;br /&gt;
&lt;br /&gt;
TI obviously considered use cases where the PSI remains in clock mode for longer times, while the I/O ports must still be operated. The I/O ports are available on bits 16-31, which means that the S0 input is 1 for these addresses. So when we do a SBO 16, this is not related to the timer value, and thus should control the first I/O port. To allow this, the chip returns to interrupt/port mode for this operation, and when S0 is reset, returns to clock mode.&lt;br /&gt;
&lt;br /&gt;
In the TI console, the S0-S4 inputs are directly connected with the address bus lines A10-A14. This has a strange side effect: Whenever address bus line A10 changes to 1, the clock mode is left temporarily, and this also happens for ordinary memory accesses.&lt;br /&gt;
&lt;br /&gt;
Suppose that we are in clock mode (SBO 0). In this mode, the read buffer should not be updated, so when we read it, we should get the same value every time, although the real counter value is different. But this is difficult to achieve: If our program uses a memory address where A10 is 1 for some instruction, loading this instruction into the CPU will set A10 to 1, and thus leave clock mode, which updates the counter.&lt;br /&gt;
&lt;br /&gt;
Therefore, you may have the impression that the read register continues to be updated even though you set bit 0 to 1. But this is only happening because your program extends over a location where A10 is 1. So if you want to prove that in clock mode the read register remains fixed, your program must avoid all memory locations where A10 is 1.&lt;br /&gt;
&lt;br /&gt;
== Shared pins ==&lt;br /&gt;
&lt;br /&gt;
Some pins have a double meaning; for example, pin 34 is labeled /INT7 / P15. How do we select the functions?&lt;br /&gt;
&lt;br /&gt;
The shared pins are those for the interrupt inputs /INT7 to /INT15; they may also be used for P7 to P15 (in the opposite order).&lt;br /&gt;
&lt;br /&gt;
If the Pn port is set to &amp;#039;&amp;#039;&amp;#039;input&amp;#039;&amp;#039;&amp;#039;, the situation looks like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
This means that both port and interrupt input have the same values; each one is a mirror of the other&amp;#039;s state. We notice it by reading the bits of the associated CRU addresses, getting the same results. There is a difference, though: The interrupt input can be armed to trigger an outgoing interrupt, while the I/O port cannot.&lt;br /&gt;
&lt;br /&gt;
In the other case, the Pn port is an &amp;#039;&amp;#039;&amp;#039;output&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Is that possible? - Sure. The interrupt input will reflect the output of the port. When we set the port to 1, the interrupt input will get that 1 (same for 0). If we arm the interrupt input, setting the output port to 0 will trigger an interrupt via this input. The 9901 specification document recommends to disarm the interrupt trigger for the shared ports because of this possibly unwanted effect.&lt;br /&gt;
&lt;br /&gt;
== Safety precautions ==&lt;br /&gt;
&lt;br /&gt;
The TMS9901 PSI is an interrupt encoder and an I/O circuit. You may have heard that improper use of I/O circuits may damage the circuit so that it must be replaced. Is this possible with the 9901 as well? Is there a way to kill the 9901?&lt;br /&gt;
&lt;br /&gt;
Short answer: Yes.&lt;br /&gt;
&lt;br /&gt;
Longer answer: Yes, but it depends on the way how the ports are used.&lt;br /&gt;
&lt;br /&gt;
If the port is used as an input, nothing bad will happen, since the ports have high input impedance. You can put 0V or 5V directly to the input; of course, you should not use excessive voltage.&lt;br /&gt;
&lt;br /&gt;
=== How to damage the 9901 ports ===&lt;br /&gt;
&lt;br /&gt;
The problem is when the port is configured as an output port. In that case it may output 0V (0) or 5V (1). When you connect the output to the opposite voltage, you will cause a short circuit which quickly exceeds the chip&amp;#039;s maximum power dissipation of 750 mW. This means you will fry the chip.&lt;br /&gt;
&lt;br /&gt;
Consider the following setup (but don&amp;#039;t try it!).&lt;br /&gt;
&lt;br /&gt;
[[File:Badport9901.png|300px]]&lt;br /&gt;
&lt;br /&gt;
Obviously, port P4 is assumed to be an input. When the switch is open, port P4 will sense a 1 on its input. When the switch is closed, it will sense a 0.&lt;br /&gt;
&lt;br /&gt;
Now what happens when you execute this code?&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  20&lt;br /&gt;
&lt;br /&gt;
This will program port P4 to be an output, and to show a 1 (5V) at the output. SBZ will, in contrast, make this port output a 0. So how do you make it an input port? - This is done by resetting the chip. As long as you don&amp;#039;t use a SBZ/SBO (or a LDCR) on the ports, the ports are inputs.&lt;br /&gt;
&lt;br /&gt;
Nothing has happened up to now. But now imagine that you press the switch. This will put the 1 output to ground, causing the short circuit, and your 9901 is toast.&lt;br /&gt;
&lt;br /&gt;
=== What about the TI console? ===&lt;br /&gt;
&lt;br /&gt;
So can we damage the 9901 in the TI console by a suitable CRU operation? - Luckily not. &lt;br /&gt;
&lt;br /&gt;
The reason is the way how TI used the ports on the mainboard. Most of the ports are configured as outputs. Nothing happens when you reset them to inputs.&lt;br /&gt;
&lt;br /&gt;
* The pure interrupt inputs (/INT1 to /INT6) are safe, as they cannot output anything. They have no output driver that could be fried.&lt;br /&gt;
* P0 and P1 are not connected.&lt;br /&gt;
* P2, P3, and P4 are designed as outputs, so they cannot be turned from an input to an output.&lt;br /&gt;
* P5 is the selector of the Alpha Lock key, so output as well.&lt;br /&gt;
* P6 and P7 (/INT15) are the cassette motor control outputs.&lt;br /&gt;
* P8 (/INT14) is the audio gate output.&lt;br /&gt;
* P9 (/INT13) is the cassette output.&lt;br /&gt;
* P10 (/INT12) is pulled up to 5V via a 10 kOhm resistor. If you put a 0 on this port (5V), the maximum current is 500 µA, which results in a power dissipation of 2.5 mW (far below the maximum 750 mW).&lt;br /&gt;
* P11 (/INT11) is the cassette input. However, there is a resistor of 2.2 kOhm (R413) which effectively guards the port of high currents.&lt;br /&gt;
* P12, P13, P14, P15 (/INT10 ... /INT7) are the keyboard input lines. Again, we have pull-ups of 10 kOhm against +5V, but also 470 Ohm on the line, which means a maximum of 8.5 mA or 42.5 mW. This is still well below the maximum value.&lt;br /&gt;
&lt;br /&gt;
So we see that resistors are the best way to protect the ports. And we may calm down; we cannot burn the 9901 in the TI console by a bug in our program.&lt;br /&gt;
&lt;br /&gt;
== Literature ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;ref1&amp;quot;&amp;gt;[1]&amp;lt;/span&amp;gt; Texas Instruments: &amp;#039;&amp;#039;TMS 9901 Programmable Systems Interface Data Manual&amp;#039;&amp;#039;, July 1977&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;ref2&amp;quot;&amp;gt;[2]&amp;lt;/span&amp;gt; Adam Osborne, Gerry Kane: &amp;#039;&amp;#039;Osborne 16-Bit Microprocessor Handbook / Includes 2900 Chip Slice Family&amp;#039;&amp;#039;. Published by OSBORNE/McGraw-Hill 1975, 1981. ISBN 0-931988-43-8.&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50619</id>
		<title>Programmable Systems Interface TMS 9901</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50619"/>
		<updated>2022-04-23T13:53:41Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Literature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The Programmable System Interface (PSI) is a circuit that is used on the TI-99/4A main board, on some expansion cards, and on the Geneve.&lt;br /&gt;
&lt;br /&gt;
The designation of the PSI is &amp;#039;&amp;#039;&amp;#039;TMS9901&amp;#039;&amp;#039;&amp;#039;, and this is the name that most users should be more familiar with.&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
&lt;br /&gt;
The TMS 9901 Programmable Systems Interface is a multi-purpose circuit that is used in the TI-99/4A and the Geneve computer, and also on some peripheral cards like the Corcomp Floppy Disk Controller.&lt;br /&gt;
&lt;br /&gt;
The 9901 has three main usage modes:&lt;br /&gt;
&lt;br /&gt;
* Input/Output ports&lt;br /&gt;
* Interrupt prioritizing&lt;br /&gt;
* Timer&lt;br /&gt;
&lt;br /&gt;
The I/O ports are mainly used for scanning the keyboard and joysticks, while the timer is used for cassette data encoding and decoding. The interrupt prioritizer is only used for the video interrupt which is routed from the VDP to the CPU through the 9901.&lt;br /&gt;
&lt;br /&gt;
== Package ==&lt;br /&gt;
&lt;br /&gt;
This is the pin assignment according to the 9901 specification.&lt;br /&gt;
&lt;br /&gt;
[[File:Pack9901.png|300px|left]]&lt;br /&gt;
&lt;br /&gt;
You can see pins of related functions marked by the same background color.&lt;br /&gt;
&lt;br /&gt;
* Orange: Interrupt inputs&lt;br /&gt;
* Red: CRU bit selection (00000 - 11111, i.e. 0-31 decimal)&lt;br /&gt;
* Blue: CRU interface&lt;br /&gt;
* Violet: I/O ports&lt;br /&gt;
* Yellow: Outgoing interrupt and level&lt;br /&gt;
&lt;br /&gt;
Note that some pins have a double function, or in other words, two functions share the same pin. I will discuss this in the following sections.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Input/Output port operation ==&lt;br /&gt;
&lt;br /&gt;
The simplest way of working with the 9901 is input/output operation with the ports. The 9901 has 16 configurable ports that can be individually used as inputs or outputs: P0 to P15.&lt;br /&gt;
&lt;br /&gt;
=== Px port as input ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
The power-on preset for every port is input. If you want to probe the P2 status (bit 18), use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 TB   18&lt;br /&gt;
 JEQ  P2ACT&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the given CRU address into the Equal bit of the status register. JEQ will jump to the location when this input has a high level. The maximum input current is 100 µA. You should protect the input with a resistor; [[#Safety_precautions | see below]] for a more detailed discussion.&lt;br /&gt;
&lt;br /&gt;
If you want to read all 16 ports, use the STCR instruction:&lt;br /&gt;
&lt;br /&gt;
 LI   R12,32&lt;br /&gt;
 STCR R1,0&lt;br /&gt;
&lt;br /&gt;
Note that the CRU base address in R12 advances in steps of 2, so bit 16 is on address 32. Also, for reading 16 bits, you must specify a count of 0.&lt;br /&gt;
&lt;br /&gt;
The 9901 offers some more inputs than the 16 ports; we can use the interrupt inputs as well. More about that can be found in the section about the [[#Interrupt_mode| interrupt mode]].&lt;br /&gt;
&lt;br /&gt;
=== Px port as output ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Once you write a value to the CRU address of a 9901 port, this port switches to output mode. The value written to the port is the value that is shown on its pin. If we want to output a 1 on P6, we have to use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
&lt;br /&gt;
If we wanted to output a 0, SBZ would have to be used. In order to set several ports in one go, use LDCR.&lt;br /&gt;
&lt;br /&gt;
We can also read the output port just by using TB as usual (or STCR for several ports at once). &lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
 TB   22&lt;br /&gt;
 JEQ  EXPECT&lt;br /&gt;
&lt;br /&gt;
The TB operation should return the 1 which the previous SBO has set.&lt;br /&gt;
&lt;br /&gt;
You &amp;#039;&amp;#039;&amp;#039;cannot&amp;#039;&amp;#039;&amp;#039; switch back to input mode. This is only possible by resetting the 9901, either using the /RST1 input line, or by setting the /RST2 bit (bit 15) to 0 in clock mode.&lt;br /&gt;
&lt;br /&gt;
== Interrupt mode ==&lt;br /&gt;
&lt;br /&gt;
In interrupt mode, bits 1 to 15 are used to set the interrupt mask (write), or to query the interrupt inputs (read). That is, the meaning of reading and writing the bits is indeed different.&lt;br /&gt;
&lt;br /&gt;
This schema shows how the interrupt inputs are used.&lt;br /&gt;
&lt;br /&gt;
[[File:Int9901.png|600px]]&lt;br /&gt;
&lt;br /&gt;
This illustration is slightly simplified; there is a latch between each interrupt input and the mask AND gate. This is not relevant for our programming, though. For more details see the &amp;#039;&amp;#039;TMS 9901 Programmable Systems Interface Data Manual&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
=== Checking the interrupt inputs ===&lt;br /&gt;
&lt;br /&gt;
In general, every interrupt input can be read via CRU access:&lt;br /&gt;
&lt;br /&gt;
  CLR R12&lt;br /&gt;
  TB  15&lt;br /&gt;
  JEQ NOINT15&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the specified CRU address to the Equal status bit. This means that JEQ jumps to the location when the bit is 1. Since all interrupt inputs are negative logic, reading a 1 means that there is no interrupt at this input (so I called it NOINT15).&lt;br /&gt;
&lt;br /&gt;
If we want to read several interrupt inputs by a single instruction, we have to use STCR.&lt;br /&gt;
&lt;br /&gt;
  LI   R12,16&lt;br /&gt;
  STCR R1,8&lt;br /&gt;
&lt;br /&gt;
This reads the states of the /INT8, /INT9, ..., /INT15 inputs and puts them in the most significant byte of R1. (See the specification of STCR and LDCR for transfers of up to 8 bits.)&lt;br /&gt;
&lt;br /&gt;
=== Interrupt levels ===&lt;br /&gt;
&lt;br /&gt;
Each interrupt input (/INT1 to /INT15) may be used to trigger an interrupt at another circuit, typically the CPU. For this purpose, the PSI offers an /INTREQ outgoing line. In the TI console, it is routed to the /INTREQ input of the TMS9900 CPU. &lt;br /&gt;
&lt;br /&gt;
In addition there is a priority encoder, which is not shown here. This encoder outputs a 4-bit number which reflects the smallest number of the interrupt inputs where an interrupt has been sensed.&lt;br /&gt;
&lt;br /&gt;
That is, if /INT4 and /INT7 are currently set to 0 (active), the priority encoder outputs (0100), which means 4.&lt;br /&gt;
&lt;br /&gt;
The TMS9900 CPU can differentiate between these interrupt levels, and by using its LIMI command, we can set a threshold where interrupts are ignored. So when we use a LIMI 3, the interrupts of level 4, 5, 6 and higher will be ignored. &lt;br /&gt;
&lt;br /&gt;
The bad news is that Texas Instruments did not use these priority lines in the TI console. In fact, these outputs (IC0 .. IC3) are not connected, and the respective interrupt level inputs at the CPU are hardwired to (0001), which means that every interrupt that occurs in the TI console is automatically set to level 1. So in order to block all interrupts from the 9901&lt;br /&gt;
&lt;br /&gt;
 LIMI 0 &lt;br /&gt;
&lt;br /&gt;
must be executed. To allow all, &lt;br /&gt;
&lt;br /&gt;
 LIMI 1 &lt;br /&gt;
&lt;br /&gt;
is sufficient. For some unclear reason, TI used LIMI 2 in all occasions.&lt;br /&gt;
&lt;br /&gt;
=== Arming and disarming interrupts ===&lt;br /&gt;
&lt;br /&gt;
As said above, every interrupt input can be read at any time in interrupt mode. In that sense, we don&amp;#039;t really treat these inputs as interrupts inputs, but simply as input ports.&lt;br /&gt;
&lt;br /&gt;
If we want a port to trigger an interrupt, we must arm this interrupt input. This is done by setting the respective bit to 1.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  2&lt;br /&gt;
&lt;br /&gt;
This enables interrupt input /INT2 to cause an /INTREQ when its input changes to 0. In the TI console, /INT2 is connected to the interrupt output of the VDP (video processor). If you want to block interrupts on that input, use SBZ.&lt;br /&gt;
&lt;br /&gt;
It is a common misunderstanding that interrupts can be cleared by using a SBZ on an input. I also found this in the popular &amp;#039;&amp;#039;TI Intern&amp;#039;&amp;#039; book. Instead, SBZ n simply blocks interrupt input /INTn. In the case of the video processor, it is up to the VDP to clear its interrupt, and this is done by reading its status register.&lt;br /&gt;
&lt;br /&gt;
Consequently, you &amp;#039;&amp;#039;&amp;#039;cannot&amp;#039;&amp;#039;&amp;#039; read the current mask setting of the 9901. What you get from reading are the input levels of the pins. So if you plan to change the mask bits temporarily, be aware that you cannot ask the 9901 to give you the current setting which you want to restore later. You will have to find out by another way.&lt;br /&gt;
&lt;br /&gt;
== Counter mode ==&lt;br /&gt;
&lt;br /&gt;
Besides the I/O capabilities, the PSI offers a &amp;quot;real-time&amp;quot; clock. This should not be understood as a precise timer with seconds, hundreds of seconds or other but as a countdown timer that is decremented every 64 CPU clock cycles.&lt;br /&gt;
&lt;br /&gt;
This schematic is taken from the Osborne book, redrawn by me in LibreOffice (and with added Load Buffer). &lt;br /&gt;
&lt;br /&gt;
[[File:Clock9901.png|550px]]&lt;br /&gt;
&lt;br /&gt;
=== Using the timer ===&lt;br /&gt;
&lt;br /&gt;
There are basically two ways of using the countdown timer:&lt;br /&gt;
&lt;br /&gt;
* Interval timer: Set the counter to a value; wait until the counter reaches zero. The PSI can be set up to raise an interrupt at that instant.&lt;br /&gt;
* Event timer: Set the counter to a value; do something and then read the counter value.&lt;br /&gt;
&lt;br /&gt;
The counter has 14 bits; that is, its maximum value is 0x3FFF (16383). The counter is decremented every 64 clock cycles, and for the TI-99/4A and the Geneve with a clock cycle time of 333ns, this means that the counter resolution is 21.333 µs. The whole 16384 steps take 0.3495 seconds (349.5 ms).&lt;br /&gt;
&lt;br /&gt;
When the counter reaches 0, it is reloaded from the load buffer, and counting continues.&lt;br /&gt;
&lt;br /&gt;
It is essential to understand that the counter &amp;#039;&amp;#039;&amp;#039;always&amp;#039;&amp;#039;&amp;#039; counts down, independent of the mode. The specification says that the clock is enabled when a non-zero value is loaded into the load buffer, but tests on a real Geneve show that the clock decrements even when the load buffer is filled with 0.&lt;br /&gt;
&lt;br /&gt;
=== Setting the timer ===&lt;br /&gt;
&lt;br /&gt;
In order to set the load buffer and so to define the maximum counter value, we must switch to clock mode. This is achieved by setting bit 0 of the PSI to 1. In the TI/Geneve systems, the PSI is mapped to CRU base address 0, so we can do this&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  0&lt;br /&gt;
&lt;br /&gt;
to enter &amp;#039;&amp;#039;&amp;#039;clock mode&amp;#039;&amp;#039;&amp;#039;. Using SBZ 0 returns to &amp;#039;&amp;#039;&amp;#039;interrupt mode&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
The effect of switching to clock mode is that the load buffer becomes accessible by the CRU addresses 1 to 14. Supposed that we want to load the buffer with the value 0x00FF, we may use these lines:&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  LI   R1,&amp;gt;00FF&lt;br /&gt;
  INCT R12&lt;br /&gt;
  LDCR R1,14&lt;br /&gt;
&lt;br /&gt;
As we see, we must change the CRU base. The LDCR operation always starts at the address defined in R12; if this is not 0, we have to set it here. Note, however, that the CRU address is always half of the R12 value, since the rightmost bit, A15, is not part of the address. If we want to start at bit 1, we must increase R12 by 2. This is, by the way, done wrong in several places in the literature where R12 is only set to 1.&lt;br /&gt;
&lt;br /&gt;
We can combine the SBO and the LDCR to one operation if we define the rightmost bit of R1 as the bit 0 and the following bits as the timer value.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  LI   R1,&amp;gt;01FF    (0000 0001 1111 1111)&lt;br /&gt;
  LDCR R1,15&lt;br /&gt;
&lt;br /&gt;
Do &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; write all 16 bits. The last bit will change bit 15 of the PSI, which is the /RST2 bit. When we are in clock mode, writing a 0 to the /RST2 bit resets all I/O ports. This means that all pins are set to inputs, and devices that rely on them as outputs may be turned off unintentionally.&lt;br /&gt;
&lt;br /&gt;
=== Reading the timer ===&lt;br /&gt;
&lt;br /&gt;
We are interested for the current value of the timer at some occasions. However, since the counter is constantly changing (every 64 clock cycles), reading from the active counter may deliver a wrong result. Think, for instance, about a value 0x2000, which is decremented after we read the first bits, we may end up with 0x2FFF (2 from 0x2000, FFF from 0x1FFF). &lt;br /&gt;
&lt;br /&gt;
For this reason, the timer value is copied to a &amp;#039;&amp;#039;&amp;#039;read buffer&amp;#039;&amp;#039;&amp;#039;. This happens at every 64th clock cycle, when the counter is also decremented, but only when we are &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; in clock mode. &lt;br /&gt;
&lt;br /&gt;
To read the buffer, we must set the PSI to clock mode first, which freezes the counter value in the read buffer. Then we can read the counter using STCR.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  STCR R1,15&lt;br /&gt;
  SRL  R1,1&lt;br /&gt;
  SBZ  0&lt;br /&gt;
&lt;br /&gt;
As we see, register 1 will contain bit 0 in its rightmost bit, so we shift R1 by one position to move it out.&lt;br /&gt;
&lt;br /&gt;
Do not forget to return to interrupt mode, or the buffer will not receive further updates.&lt;br /&gt;
&lt;br /&gt;
As mentioned, bit 15 is the /RST2 bit when we write to it. For reading, bit 15 reflects the /INTREQ output.&lt;br /&gt;
&lt;br /&gt;
=== The weird S0 input ===&lt;br /&gt;
&lt;br /&gt;
There is one peculiarity with the PSI. When the input line S0 is set to 1, the clock mode is &amp;#039;&amp;#039;&amp;#039;left temporarily&amp;#039;&amp;#039;&amp;#039; - until S0 is 0 again, and bit 0 is 1. If bit 0 is 0, this has no effect, as the clock mode is never entered.&lt;br /&gt;
&lt;br /&gt;
What is the reason for this? &lt;br /&gt;
&lt;br /&gt;
TI obviously considered use cases where the PSI remains in clock mode for longer times, while the I/O ports must still be operated. The I/O ports are available on bits 16-31, which means that the S0 input is 1 for these addresses. So when we do a SBO 16, this is not related to the timer value, and thus should control the first I/O port. To allow this, the chip returns to interrupt/port mode for this operation, and when S0 is reset, returns to clock mode.&lt;br /&gt;
&lt;br /&gt;
In the TI console, the S0-S4 inputs are directly connected with the address bus lines A10-A14. This has a strange side effect: Whenever address bus line A10 changes to 1, the clock mode is left temporarily, and this also happens for ordinary memory accesses.&lt;br /&gt;
&lt;br /&gt;
Suppose that we are in clock mode (SBO 0). In this mode, the read buffer should not be updated, so when we read it, we should get the same value every time, although the real counter value is different. But this is difficult to achieve: If our program uses a memory address where A10 is 1 for some instruction, loading this instruction into the CPU will set A10 to 1, and thus leave clock mode, which updates the counter.&lt;br /&gt;
&lt;br /&gt;
Therefore, you may have the impression that the read register continues to be updated even though you set bit 0 to 1. But this is only happening because your program extends over a location where A10 is 1. So if you want to prove that in clock mode the read register remains fixed, your program must avoid all memory locations where A10 is 1.&lt;br /&gt;
&lt;br /&gt;
== Shared pins ==&lt;br /&gt;
&lt;br /&gt;
Some pins have a double meaning; for example, pin 34 is labeled /INT7 / P15. How do we select the functions?&lt;br /&gt;
&lt;br /&gt;
The shared pins are those for the interrupt inputs /INT7 to /INT15; they may also be used for P7 to P15 (in the opposite order).&lt;br /&gt;
&lt;br /&gt;
If the Pn port is set to &amp;#039;&amp;#039;&amp;#039;input&amp;#039;&amp;#039;&amp;#039;, the situation looks like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
This means that both port and interrupt input have the same values; each one is a mirror of the other&amp;#039;s state. We notice it by reading the bits of the associated CRU addresses, getting the same results. There is a difference, though: The interrupt input can be armed to trigger an outgoing interrupt, while the I/O port cannot.&lt;br /&gt;
&lt;br /&gt;
In the other case, the Pn port is an &amp;#039;&amp;#039;&amp;#039;output&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Is that possible? - Sure. The interrupt input will reflect the output of the port. When we set the port to 1, the interrupt input will get that 1 (same for 0). If we arm the interrupt input, setting the output port to 0 will trigger an interrupt via this input. The 9901 specification document recommends to disarm the interrupt trigger for the shared ports because of this possibly unwanted effect.&lt;br /&gt;
&lt;br /&gt;
== Safety precautions ==&lt;br /&gt;
&lt;br /&gt;
The TMS9901 PSI is an interrupt encoder and an I/O circuit. You may have heard that improper use of I/O circuits may damage the circuit so that it must be replaced. Is this possible with the 9901 as well? Is there a way to kill the 9901?&lt;br /&gt;
&lt;br /&gt;
Short answer: Yes.&lt;br /&gt;
&lt;br /&gt;
Longer answer: Yes, but it depends on the way how the ports are used.&lt;br /&gt;
&lt;br /&gt;
If the port is used as an input, nothing bad will happen, since the ports have high input impedance. You can put 0V or 5V directly to the input; of course, you should not use excessive voltage.&lt;br /&gt;
&lt;br /&gt;
=== How to damage the 9901 ports ===&lt;br /&gt;
&lt;br /&gt;
The problem is when the port is configured as an output port. In that case it may output 0V (0) or 5V (1). When you connect the output to the opposite voltage, you will cause a short circuit which quickly exceeds the chip&amp;#039;s maximum power dissipation of 750 mW. This means you will fry the chip.&lt;br /&gt;
&lt;br /&gt;
Consider the following setup (but don&amp;#039;t try it!).&lt;br /&gt;
&lt;br /&gt;
[[File:Badport9901.png|300px]]&lt;br /&gt;
&lt;br /&gt;
Obviously, port P4 is assumed to be an input. When the switch is open, port P4 will sense a 1 on its input. When the switch is closed, it will sense a 0.&lt;br /&gt;
&lt;br /&gt;
Now what happens when you execute this code?&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  20&lt;br /&gt;
&lt;br /&gt;
This will program port P4 to be an output, and to show a 1 (5V) at the output. SBZ will, in contrast, make this port output a 0. So how do you make it an input port? - This is done by resetting the chip. As long as you don&amp;#039;t use a SBZ/SBO (or a LDCR) on the ports, the ports are inputs.&lt;br /&gt;
&lt;br /&gt;
Nothing has happened up to now. But now imagine that you press the switch. This will put the 1 output to ground, causing the short circuit, and your 9901 is toast.&lt;br /&gt;
&lt;br /&gt;
=== What about the TI console? ===&lt;br /&gt;
&lt;br /&gt;
So can we damage the 9901 in the TI console by a suitable CRU operation? - Luckily not. &lt;br /&gt;
&lt;br /&gt;
The reason is the way how TI used the ports on the mainboard. Most of the ports are configured as outputs. Nothing happens when you reset them to inputs.&lt;br /&gt;
&lt;br /&gt;
* The pure interrupt inputs (/INT1 to /INT6) are safe, as they cannot output anything. They have no output driver that could be fried.&lt;br /&gt;
* P0 and P1 are not connected.&lt;br /&gt;
* P2, P3, and P4 are designed as outputs, so they cannot be turned from an input to an output.&lt;br /&gt;
* P5 is the selector of the Alpha Lock key, so output as well.&lt;br /&gt;
* P6 and P7 (/INT15) are the cassette motor control outputs.&lt;br /&gt;
* P8 (/INT14) is the audio gate output.&lt;br /&gt;
* P9 (/INT13) is the cassette output.&lt;br /&gt;
* P10 (/INT12) is pulled up to 5V via a 10 kOhm resistor. If you put a 0 on this port (5V), the maximum current is 500 µA, which results in a power dissipation of 2.5 mW (far below the maximum 750 mW).&lt;br /&gt;
* P11 (/INT11) is the cassette input. However, there is a resistor of 2.2 kOhm (R413) which effectively guards the port of high currents.&lt;br /&gt;
* P12, P13, P14, P15 (/INT10 ... /INT7) are the keyboard input lines. Again, we have pull-ups of 10 kOhm against +5V, but also 470 Ohm on the line, which means a maximum of 8.5 mA or 42.5 mW. This is still well below the maximum value.&lt;br /&gt;
&lt;br /&gt;
So we see that resistors are the best way to protect the ports. And we may calm down; we cannot burn the 9901 in the TI console by a bug in our program.&lt;br /&gt;
&lt;br /&gt;
== Literature ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;ref1&amp;quot;&amp;gt;[1]&amp;lt;/span&amp;gt; Texas Instruments: &amp;#039;&amp;#039;TMS 9901 Programmable Systems Interface Data Manual&amp;#039;&amp;#039;, July 1977&lt;br /&gt;
&amp;lt;span id=&amp;quot;ref2&amp;quot;&amp;gt;[2]&amp;lt;/span&amp;gt; Adam Osborne, Gerry Kane: &amp;#039;&amp;#039;Osborne 16-Bit Microprocessor Handbook / Includes 2900 Chip Slice Family&amp;#039;&amp;#039;. Published by OSBORNE/McGraw-Hill 1975, 1981. ISBN 0-931988-43-8.&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50618</id>
		<title>Programmable Systems Interface TMS 9901</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50618"/>
		<updated>2022-04-23T13:51:39Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The Programmable System Interface (PSI) is a circuit that is used on the TI-99/4A main board, on some expansion cards, and on the Geneve.&lt;br /&gt;
&lt;br /&gt;
The designation of the PSI is &amp;#039;&amp;#039;&amp;#039;TMS9901&amp;#039;&amp;#039;&amp;#039;, and this is the name that most users should be more familiar with.&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
&lt;br /&gt;
The TMS 9901 Programmable Systems Interface is a multi-purpose circuit that is used in the TI-99/4A and the Geneve computer, and also on some peripheral cards like the Corcomp Floppy Disk Controller.&lt;br /&gt;
&lt;br /&gt;
The 9901 has three main usage modes:&lt;br /&gt;
&lt;br /&gt;
* Input/Output ports&lt;br /&gt;
* Interrupt prioritizing&lt;br /&gt;
* Timer&lt;br /&gt;
&lt;br /&gt;
The I/O ports are mainly used for scanning the keyboard and joysticks, while the timer is used for cassette data encoding and decoding. The interrupt prioritizer is only used for the video interrupt which is routed from the VDP to the CPU through the 9901.&lt;br /&gt;
&lt;br /&gt;
== Package ==&lt;br /&gt;
&lt;br /&gt;
This is the pin assignment according to the 9901 specification.&lt;br /&gt;
&lt;br /&gt;
[[File:Pack9901.png|300px|left]]&lt;br /&gt;
&lt;br /&gt;
You can see pins of related functions marked by the same background color.&lt;br /&gt;
&lt;br /&gt;
* Orange: Interrupt inputs&lt;br /&gt;
* Red: CRU bit selection (00000 - 11111, i.e. 0-31 decimal)&lt;br /&gt;
* Blue: CRU interface&lt;br /&gt;
* Violet: I/O ports&lt;br /&gt;
* Yellow: Outgoing interrupt and level&lt;br /&gt;
&lt;br /&gt;
Note that some pins have a double function, or in other words, two functions share the same pin. I will discuss this in the following sections.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Input/Output port operation ==&lt;br /&gt;
&lt;br /&gt;
The simplest way of working with the 9901 is input/output operation with the ports. The 9901 has 16 configurable ports that can be individually used as inputs or outputs: P0 to P15.&lt;br /&gt;
&lt;br /&gt;
=== Px port as input ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
The power-on preset for every port is input. If you want to probe the P2 status (bit 18), use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 TB   18&lt;br /&gt;
 JEQ  P2ACT&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the given CRU address into the Equal bit of the status register. JEQ will jump to the location when this input has a high level. The maximum input current is 100 µA. You should protect the input with a resistor; [[#Safety_precautions | see below]] for a more detailed discussion.&lt;br /&gt;
&lt;br /&gt;
If you want to read all 16 ports, use the STCR instruction:&lt;br /&gt;
&lt;br /&gt;
 LI   R12,32&lt;br /&gt;
 STCR R1,0&lt;br /&gt;
&lt;br /&gt;
Note that the CRU base address in R12 advances in steps of 2, so bit 16 is on address 32. Also, for reading 16 bits, you must specify a count of 0.&lt;br /&gt;
&lt;br /&gt;
The 9901 offers some more inputs than the 16 ports; we can use the interrupt inputs as well. More about that can be found in the section about the [[#Interrupt_mode| interrupt mode]].&lt;br /&gt;
&lt;br /&gt;
=== Px port as output ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Once you write a value to the CRU address of a 9901 port, this port switches to output mode. The value written to the port is the value that is shown on its pin. If we want to output a 1 on P6, we have to use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
&lt;br /&gt;
If we wanted to output a 0, SBZ would have to be used. In order to set several ports in one go, use LDCR.&lt;br /&gt;
&lt;br /&gt;
We can also read the output port just by using TB as usual (or STCR for several ports at once). &lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
 TB   22&lt;br /&gt;
 JEQ  EXPECT&lt;br /&gt;
&lt;br /&gt;
The TB operation should return the 1 which the previous SBO has set.&lt;br /&gt;
&lt;br /&gt;
You &amp;#039;&amp;#039;&amp;#039;cannot&amp;#039;&amp;#039;&amp;#039; switch back to input mode. This is only possible by resetting the 9901, either using the /RST1 input line, or by setting the /RST2 bit (bit 15) to 0 in clock mode.&lt;br /&gt;
&lt;br /&gt;
== Interrupt mode ==&lt;br /&gt;
&lt;br /&gt;
In interrupt mode, bits 1 to 15 are used to set the interrupt mask (write), or to query the interrupt inputs (read). That is, the meaning of reading and writing the bits is indeed different.&lt;br /&gt;
&lt;br /&gt;
This schema shows how the interrupt inputs are used.&lt;br /&gt;
&lt;br /&gt;
[[File:Int9901.png|600px]]&lt;br /&gt;
&lt;br /&gt;
This illustration is slightly simplified; there is a latch between each interrupt input and the mask AND gate. This is not relevant for our programming, though. For more details see the &amp;#039;&amp;#039;TMS 9901 Programmable Systems Interface Data Manual&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
=== Checking the interrupt inputs ===&lt;br /&gt;
&lt;br /&gt;
In general, every interrupt input can be read via CRU access:&lt;br /&gt;
&lt;br /&gt;
  CLR R12&lt;br /&gt;
  TB  15&lt;br /&gt;
  JEQ NOINT15&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the specified CRU address to the Equal status bit. This means that JEQ jumps to the location when the bit is 1. Since all interrupt inputs are negative logic, reading a 1 means that there is no interrupt at this input (so I called it NOINT15).&lt;br /&gt;
&lt;br /&gt;
If we want to read several interrupt inputs by a single instruction, we have to use STCR.&lt;br /&gt;
&lt;br /&gt;
  LI   R12,16&lt;br /&gt;
  STCR R1,8&lt;br /&gt;
&lt;br /&gt;
This reads the states of the /INT8, /INT9, ..., /INT15 inputs and puts them in the most significant byte of R1. (See the specification of STCR and LDCR for transfers of up to 8 bits.)&lt;br /&gt;
&lt;br /&gt;
=== Interrupt levels ===&lt;br /&gt;
&lt;br /&gt;
Each interrupt input (/INT1 to /INT15) may be used to trigger an interrupt at another circuit, typically the CPU. For this purpose, the PSI offers an /INTREQ outgoing line. In the TI console, it is routed to the /INTREQ input of the TMS9900 CPU. &lt;br /&gt;
&lt;br /&gt;
In addition there is a priority encoder, which is not shown here. This encoder outputs a 4-bit number which reflects the smallest number of the interrupt inputs where an interrupt has been sensed.&lt;br /&gt;
&lt;br /&gt;
That is, if /INT4 and /INT7 are currently set to 0 (active), the priority encoder outputs (0100), which means 4.&lt;br /&gt;
&lt;br /&gt;
The TMS9900 CPU can differentiate between these interrupt levels, and by using its LIMI command, we can set a threshold where interrupts are ignored. So when we use a LIMI 3, the interrupts of level 4, 5, 6 and higher will be ignored. &lt;br /&gt;
&lt;br /&gt;
The bad news is that Texas Instruments did not use these priority lines in the TI console. In fact, these outputs (IC0 .. IC3) are not connected, and the respective interrupt level inputs at the CPU are hardwired to (0001), which means that every interrupt that occurs in the TI console is automatically set to level 1. So in order to block all interrupts from the 9901&lt;br /&gt;
&lt;br /&gt;
 LIMI 0 &lt;br /&gt;
&lt;br /&gt;
must be executed. To allow all, &lt;br /&gt;
&lt;br /&gt;
 LIMI 1 &lt;br /&gt;
&lt;br /&gt;
is sufficient. For some unclear reason, TI used LIMI 2 in all occasions.&lt;br /&gt;
&lt;br /&gt;
=== Arming and disarming interrupts ===&lt;br /&gt;
&lt;br /&gt;
As said above, every interrupt input can be read at any time in interrupt mode. In that sense, we don&amp;#039;t really treat these inputs as interrupts inputs, but simply as input ports.&lt;br /&gt;
&lt;br /&gt;
If we want a port to trigger an interrupt, we must arm this interrupt input. This is done by setting the respective bit to 1.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  2&lt;br /&gt;
&lt;br /&gt;
This enables interrupt input /INT2 to cause an /INTREQ when its input changes to 0. In the TI console, /INT2 is connected to the interrupt output of the VDP (video processor). If you want to block interrupts on that input, use SBZ.&lt;br /&gt;
&lt;br /&gt;
It is a common misunderstanding that interrupts can be cleared by using a SBZ on an input. I also found this in the popular &amp;#039;&amp;#039;TI Intern&amp;#039;&amp;#039; book. Instead, SBZ n simply blocks interrupt input /INTn. In the case of the video processor, it is up to the VDP to clear its interrupt, and this is done by reading its status register.&lt;br /&gt;
&lt;br /&gt;
Consequently, you &amp;#039;&amp;#039;&amp;#039;cannot&amp;#039;&amp;#039;&amp;#039; read the current mask setting of the 9901. What you get from reading are the input levels of the pins. So if you plan to change the mask bits temporarily, be aware that you cannot ask the 9901 to give you the current setting which you want to restore later. You will have to find out by another way.&lt;br /&gt;
&lt;br /&gt;
== Counter mode ==&lt;br /&gt;
&lt;br /&gt;
Besides the I/O capabilities, the PSI offers a &amp;quot;real-time&amp;quot; clock. This should not be understood as a precise timer with seconds, hundreds of seconds or other but as a countdown timer that is decremented every 64 CPU clock cycles.&lt;br /&gt;
&lt;br /&gt;
This schematic is taken from the Osborne book, redrawn by me in LibreOffice (and with added Load Buffer). &lt;br /&gt;
&lt;br /&gt;
[[File:Clock9901.png|550px]]&lt;br /&gt;
&lt;br /&gt;
=== Using the timer ===&lt;br /&gt;
&lt;br /&gt;
There are basically two ways of using the countdown timer:&lt;br /&gt;
&lt;br /&gt;
* Interval timer: Set the counter to a value; wait until the counter reaches zero. The PSI can be set up to raise an interrupt at that instant.&lt;br /&gt;
* Event timer: Set the counter to a value; do something and then read the counter value.&lt;br /&gt;
&lt;br /&gt;
The counter has 14 bits; that is, its maximum value is 0x3FFF (16383). The counter is decremented every 64 clock cycles, and for the TI-99/4A and the Geneve with a clock cycle time of 333ns, this means that the counter resolution is 21.333 µs. The whole 16384 steps take 0.3495 seconds (349.5 ms).&lt;br /&gt;
&lt;br /&gt;
When the counter reaches 0, it is reloaded from the load buffer, and counting continues.&lt;br /&gt;
&lt;br /&gt;
It is essential to understand that the counter &amp;#039;&amp;#039;&amp;#039;always&amp;#039;&amp;#039;&amp;#039; counts down, independent of the mode. The specification says that the clock is enabled when a non-zero value is loaded into the load buffer, but tests on a real Geneve show that the clock decrements even when the load buffer is filled with 0.&lt;br /&gt;
&lt;br /&gt;
=== Setting the timer ===&lt;br /&gt;
&lt;br /&gt;
In order to set the load buffer and so to define the maximum counter value, we must switch to clock mode. This is achieved by setting bit 0 of the PSI to 1. In the TI/Geneve systems, the PSI is mapped to CRU base address 0, so we can do this&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  0&lt;br /&gt;
&lt;br /&gt;
to enter &amp;#039;&amp;#039;&amp;#039;clock mode&amp;#039;&amp;#039;&amp;#039;. Using SBZ 0 returns to &amp;#039;&amp;#039;&amp;#039;interrupt mode&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
The effect of switching to clock mode is that the load buffer becomes accessible by the CRU addresses 1 to 14. Supposed that we want to load the buffer with the value 0x00FF, we may use these lines:&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  LI   R1,&amp;gt;00FF&lt;br /&gt;
  INCT R12&lt;br /&gt;
  LDCR R1,14&lt;br /&gt;
&lt;br /&gt;
As we see, we must change the CRU base. The LDCR operation always starts at the address defined in R12; if this is not 0, we have to set it here. Note, however, that the CRU address is always half of the R12 value, since the rightmost bit, A15, is not part of the address. If we want to start at bit 1, we must increase R12 by 2. This is, by the way, done wrong in several places in the literature where R12 is only set to 1.&lt;br /&gt;
&lt;br /&gt;
We can combine the SBO and the LDCR to one operation if we define the rightmost bit of R1 as the bit 0 and the following bits as the timer value.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  LI   R1,&amp;gt;01FF    (0000 0001 1111 1111)&lt;br /&gt;
  LDCR R1,15&lt;br /&gt;
&lt;br /&gt;
Do &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; write all 16 bits. The last bit will change bit 15 of the PSI, which is the /RST2 bit. When we are in clock mode, writing a 0 to the /RST2 bit resets all I/O ports. This means that all pins are set to inputs, and devices that rely on them as outputs may be turned off unintentionally.&lt;br /&gt;
&lt;br /&gt;
=== Reading the timer ===&lt;br /&gt;
&lt;br /&gt;
We are interested for the current value of the timer at some occasions. However, since the counter is constantly changing (every 64 clock cycles), reading from the active counter may deliver a wrong result. Think, for instance, about a value 0x2000, which is decremented after we read the first bits, we may end up with 0x2FFF (2 from 0x2000, FFF from 0x1FFF). &lt;br /&gt;
&lt;br /&gt;
For this reason, the timer value is copied to a &amp;#039;&amp;#039;&amp;#039;read buffer&amp;#039;&amp;#039;&amp;#039;. This happens at every 64th clock cycle, when the counter is also decremented, but only when we are &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; in clock mode. &lt;br /&gt;
&lt;br /&gt;
To read the buffer, we must set the PSI to clock mode first, which freezes the counter value in the read buffer. Then we can read the counter using STCR.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  STCR R1,15&lt;br /&gt;
  SRL  R1,1&lt;br /&gt;
  SBZ  0&lt;br /&gt;
&lt;br /&gt;
As we see, register 1 will contain bit 0 in its rightmost bit, so we shift R1 by one position to move it out.&lt;br /&gt;
&lt;br /&gt;
Do not forget to return to interrupt mode, or the buffer will not receive further updates.&lt;br /&gt;
&lt;br /&gt;
As mentioned, bit 15 is the /RST2 bit when we write to it. For reading, bit 15 reflects the /INTREQ output.&lt;br /&gt;
&lt;br /&gt;
=== The weird S0 input ===&lt;br /&gt;
&lt;br /&gt;
There is one peculiarity with the PSI. When the input line S0 is set to 1, the clock mode is &amp;#039;&amp;#039;&amp;#039;left temporarily&amp;#039;&amp;#039;&amp;#039; - until S0 is 0 again, and bit 0 is 1. If bit 0 is 0, this has no effect, as the clock mode is never entered.&lt;br /&gt;
&lt;br /&gt;
What is the reason for this? &lt;br /&gt;
&lt;br /&gt;
TI obviously considered use cases where the PSI remains in clock mode for longer times, while the I/O ports must still be operated. The I/O ports are available on bits 16-31, which means that the S0 input is 1 for these addresses. So when we do a SBO 16, this is not related to the timer value, and thus should control the first I/O port. To allow this, the chip returns to interrupt/port mode for this operation, and when S0 is reset, returns to clock mode.&lt;br /&gt;
&lt;br /&gt;
In the TI console, the S0-S4 inputs are directly connected with the address bus lines A10-A14. This has a strange side effect: Whenever address bus line A10 changes to 1, the clock mode is left temporarily, and this also happens for ordinary memory accesses.&lt;br /&gt;
&lt;br /&gt;
Suppose that we are in clock mode (SBO 0). In this mode, the read buffer should not be updated, so when we read it, we should get the same value every time, although the real counter value is different. But this is difficult to achieve: If our program uses a memory address where A10 is 1 for some instruction, loading this instruction into the CPU will set A10 to 1, and thus leave clock mode, which updates the counter.&lt;br /&gt;
&lt;br /&gt;
Therefore, you may have the impression that the read register continues to be updated even though you set bit 0 to 1. But this is only happening because your program extends over a location where A10 is 1. So if you want to prove that in clock mode the read register remains fixed, your program must avoid all memory locations where A10 is 1.&lt;br /&gt;
&lt;br /&gt;
== Shared pins ==&lt;br /&gt;
&lt;br /&gt;
Some pins have a double meaning; for example, pin 34 is labeled /INT7 / P15. How do we select the functions?&lt;br /&gt;
&lt;br /&gt;
The shared pins are those for the interrupt inputs /INT7 to /INT15; they may also be used for P7 to P15 (in the opposite order).&lt;br /&gt;
&lt;br /&gt;
If the Pn port is set to &amp;#039;&amp;#039;&amp;#039;input&amp;#039;&amp;#039;&amp;#039;, the situation looks like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
This means that both port and interrupt input have the same values; each one is a mirror of the other&amp;#039;s state. We notice it by reading the bits of the associated CRU addresses, getting the same results. There is a difference, though: The interrupt input can be armed to trigger an outgoing interrupt, while the I/O port cannot.&lt;br /&gt;
&lt;br /&gt;
In the other case, the Pn port is an &amp;#039;&amp;#039;&amp;#039;output&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Is that possible? - Sure. The interrupt input will reflect the output of the port. When we set the port to 1, the interrupt input will get that 1 (same for 0). If we arm the interrupt input, setting the output port to 0 will trigger an interrupt via this input. The 9901 specification document recommends to disarm the interrupt trigger for the shared ports because of this possibly unwanted effect.&lt;br /&gt;
&lt;br /&gt;
== Safety precautions ==&lt;br /&gt;
&lt;br /&gt;
The TMS9901 PSI is an interrupt encoder and an I/O circuit. You may have heard that improper use of I/O circuits may damage the circuit so that it must be replaced. Is this possible with the 9901 as well? Is there a way to kill the 9901?&lt;br /&gt;
&lt;br /&gt;
Short answer: Yes.&lt;br /&gt;
&lt;br /&gt;
Longer answer: Yes, but it depends on the way how the ports are used.&lt;br /&gt;
&lt;br /&gt;
If the port is used as an input, nothing bad will happen, since the ports have high input impedance. You can put 0V or 5V directly to the input; of course, you should not use excessive voltage.&lt;br /&gt;
&lt;br /&gt;
=== How to damage the 9901 ports ===&lt;br /&gt;
&lt;br /&gt;
The problem is when the port is configured as an output port. In that case it may output 0V (0) or 5V (1). When you connect the output to the opposite voltage, you will cause a short circuit which quickly exceeds the chip&amp;#039;s maximum power dissipation of 750 mW. This means you will fry the chip.&lt;br /&gt;
&lt;br /&gt;
Consider the following setup (but don&amp;#039;t try it!).&lt;br /&gt;
&lt;br /&gt;
[[File:Badport9901.png|300px]]&lt;br /&gt;
&lt;br /&gt;
Obviously, port P4 is assumed to be an input. When the switch is open, port P4 will sense a 1 on its input. When the switch is closed, it will sense a 0.&lt;br /&gt;
&lt;br /&gt;
Now what happens when you execute this code?&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  20&lt;br /&gt;
&lt;br /&gt;
This will program port P4 to be an output, and to show a 1 (5V) at the output. SBZ will, in contrast, make this port output a 0. So how do you make it an input port? - This is done by resetting the chip. As long as you don&amp;#039;t use a SBZ/SBO (or a LDCR) on the ports, the ports are inputs.&lt;br /&gt;
&lt;br /&gt;
Nothing has happened up to now. But now imagine that you press the switch. This will put the 1 output to ground, causing the short circuit, and your 9901 is toast.&lt;br /&gt;
&lt;br /&gt;
=== What about the TI console? ===&lt;br /&gt;
&lt;br /&gt;
So can we damage the 9901 in the TI console by a suitable CRU operation? - Luckily not. &lt;br /&gt;
&lt;br /&gt;
The reason is the way how TI used the ports on the mainboard. Most of the ports are configured as outputs. Nothing happens when you reset them to inputs.&lt;br /&gt;
&lt;br /&gt;
* The pure interrupt inputs (/INT1 to /INT6) are safe, as they cannot output anything. They have no output driver that could be fried.&lt;br /&gt;
* P0 and P1 are not connected.&lt;br /&gt;
* P2, P3, and P4 are designed as outputs, so they cannot be turned from an input to an output.&lt;br /&gt;
* P5 is the selector of the Alpha Lock key, so output as well.&lt;br /&gt;
* P6 and P7 (/INT15) are the cassette motor control outputs.&lt;br /&gt;
* P8 (/INT14) is the audio gate output.&lt;br /&gt;
* P9 (/INT13) is the cassette output.&lt;br /&gt;
* P10 (/INT12) is pulled up to 5V via a 10 kOhm resistor. If you put a 0 on this port (5V), the maximum current is 500 µA, which results in a power dissipation of 2.5 mW (far below the maximum 750 mW).&lt;br /&gt;
* P11 (/INT11) is the cassette input. However, there is a resistor of 2.2 kOhm (R413) which effectively guards the port of high currents.&lt;br /&gt;
* P12, P13, P14, P15 (/INT10 ... /INT7) are the keyboard input lines. Again, we have pull-ups of 10 kOhm against +5V, but also 470 Ohm on the line, which means a maximum of 8.5 mA or 42.5 mW. This is still well below the maximum value.&lt;br /&gt;
&lt;br /&gt;
So we see that resistors are the best way to protect the ports. And we may calm down; we cannot burn the 9901 in the TI console by a bug in our program.&lt;br /&gt;
&lt;br /&gt;
== Literature ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;ref1&amp;quot;&amp;gt;[1]&amp;lt;/span&amp;gt; Adam Osborne, Gerry Kane: &amp;#039;&amp;#039;Osborne 16-Bit Microprocessor Handbook / Includes 2900 Chip Slice Family&amp;#039;&amp;#039;. Published by OSBORNE/McGraw-Hill 1975, 1981. ISBN 0-931988-43-8.&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50617</id>
		<title>Programmable Systems Interface TMS 9901</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50617"/>
		<updated>2022-04-23T13:45:36Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Arming and disarming interrupts */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The Programmable System Interface (PSI) is a circuit that is used on the TI-99/4A main board, on some expansion cards, and on the Geneve.&lt;br /&gt;
&lt;br /&gt;
The designation of the PSI is &amp;#039;&amp;#039;&amp;#039;TMS9901&amp;#039;&amp;#039;&amp;#039;, and this is the name that most users should be more familiar with.&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
&lt;br /&gt;
The TMS 9901 Programmable Systems Interface is a multi-purpose circuit that is used in the TI-99/4A and the Geneve computer, and also on some peripheral cards like the Corcomp Floppy Disk Controller.&lt;br /&gt;
&lt;br /&gt;
The 9901 has three main usage modes:&lt;br /&gt;
&lt;br /&gt;
* Input/Output ports&lt;br /&gt;
* Interrupt prioritizing&lt;br /&gt;
* Timer&lt;br /&gt;
&lt;br /&gt;
The I/O ports are mainly used for scanning the keyboard and joysticks, while the timer is used for cassette data encoding and decoding. The interrupt prioritizer is only used for the video interrupt which is routed from the VDP to the CPU through the 9901.&lt;br /&gt;
&lt;br /&gt;
== Package ==&lt;br /&gt;
&lt;br /&gt;
This is the pin assignment according to the 9901 specification.&lt;br /&gt;
&lt;br /&gt;
[[File:Pack9901.png|300px|left]]&lt;br /&gt;
&lt;br /&gt;
You can see pins of related functions marked by the same background color.&lt;br /&gt;
&lt;br /&gt;
* Orange: Interrupt inputs&lt;br /&gt;
* Red: CRU bit selection (00000 - 11111, i.e. 0-31 decimal)&lt;br /&gt;
* Blue: CRU interface&lt;br /&gt;
* Violet: I/O ports&lt;br /&gt;
* Yellow: Outgoing interrupt and level&lt;br /&gt;
&lt;br /&gt;
Note that some pins have a double function, or in other words, two functions share the same pin. I will discuss this in the following sections.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Input/Output port operation ==&lt;br /&gt;
&lt;br /&gt;
The simplest way of working with the 9901 is input/output operation with the ports. The 9901 has 16 configurable ports that can be individually used as inputs or outputs: P0 to P15.&lt;br /&gt;
&lt;br /&gt;
=== Px port as input ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
The power-on preset for every port is input. If you want to probe the P2 status (bit 18), use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 TB   18&lt;br /&gt;
 JEQ  P2ACT&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the given CRU address into the Equal bit of the status register. JEQ will jump to the location when this input has a high level. The maximum input current is 100 µA. You should protect the input with a resistor; [[#Safety_precautions | see below]] for a more detailed discussion.&lt;br /&gt;
&lt;br /&gt;
If you want to read all 16 ports, use the STCR instruction:&lt;br /&gt;
&lt;br /&gt;
 LI   R12,32&lt;br /&gt;
 STCR R1,0&lt;br /&gt;
&lt;br /&gt;
Note that the CRU base address in R12 advances in steps of 2, so bit 16 is on address 32. Also, for reading 16 bits, you must specify a count of 0.&lt;br /&gt;
&lt;br /&gt;
The 9901 offers some more inputs than the 16 ports; we can use the interrupt inputs as well. More about that can be found in the section about the [[#Interrupt_mode| interrupt mode]].&lt;br /&gt;
&lt;br /&gt;
=== Px port as output ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Once you write a value to the CRU address of a 9901 port, this port switches to output mode. The value written to the port is the value that is shown on its pin. If we want to output a 1 on P6, we have to use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
&lt;br /&gt;
If we wanted to output a 0, SBZ would have to be used. In order to set several ports in one go, use LDCR.&lt;br /&gt;
&lt;br /&gt;
We can also read the output port just by using TB as usual (or STCR for several ports at once). &lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
 TB   22&lt;br /&gt;
 JEQ  EXPECT&lt;br /&gt;
&lt;br /&gt;
The TB operation should return the 1 which the previous SBO has set.&lt;br /&gt;
&lt;br /&gt;
You &amp;#039;&amp;#039;&amp;#039;cannot&amp;#039;&amp;#039;&amp;#039; switch back to input mode. This is only possible by resetting the 9901, either using the /RST1 input line, or by setting the /RST2 bit (bit 15) to 0 in clock mode.&lt;br /&gt;
&lt;br /&gt;
== Interrupt mode ==&lt;br /&gt;
&lt;br /&gt;
In interrupt mode, bits 1 to 15 are used to set the interrupt mask (write), or to query the interrupt inputs (read). That is, the meaning of reading and writing the bits is indeed different.&lt;br /&gt;
&lt;br /&gt;
This schema shows how the interrupt inputs are used.&lt;br /&gt;
&lt;br /&gt;
[[File:Int9901.png|600px]]&lt;br /&gt;
&lt;br /&gt;
This illustration is slightly simplified; there is a latch between each interrupt input and the mask AND gate. This is not relevant for our programming, though. For more details see the &amp;#039;&amp;#039;TMS 9901 Programmable Systems Interface Data Manual&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
=== Checking the interrupt inputs ===&lt;br /&gt;
&lt;br /&gt;
In general, every interrupt input can be read via CRU access:&lt;br /&gt;
&lt;br /&gt;
  CLR R12&lt;br /&gt;
  TB  15&lt;br /&gt;
  JEQ NOINT15&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the specified CRU address to the Equal status bit. This means that JEQ jumps to the location when the bit is 1. Since all interrupt inputs are negative logic, reading a 1 means that there is no interrupt at this input (so I called it NOINT15).&lt;br /&gt;
&lt;br /&gt;
If we want to read several interrupt inputs by a single instruction, we have to use STCR.&lt;br /&gt;
&lt;br /&gt;
  LI   R12,16&lt;br /&gt;
  STCR R1,8&lt;br /&gt;
&lt;br /&gt;
This reads the states of the /INT8, /INT9, ..., /INT15 inputs and puts them in the most significant byte of R1. (See the specification of STCR and LDCR for transfers of up to 8 bits.)&lt;br /&gt;
&lt;br /&gt;
=== Interrupt levels ===&lt;br /&gt;
&lt;br /&gt;
Each interrupt input (/INT1 to /INT15) may be used to trigger an interrupt at another circuit, typically the CPU. For this purpose, the PSI offers an /INTREQ outgoing line. In the TI console, it is routed to the /INTREQ input of the TMS9900 CPU. &lt;br /&gt;
&lt;br /&gt;
In addition there is a priority encoder, which is not shown here. This encoder outputs a 4-bit number which reflects the smallest number of the interrupt inputs where an interrupt has been sensed.&lt;br /&gt;
&lt;br /&gt;
That is, if /INT4 and /INT7 are currently set to 0 (active), the priority encoder outputs (0100), which means 4.&lt;br /&gt;
&lt;br /&gt;
The TMS9900 CPU can differentiate between these interrupt levels, and by using its LIMI command, we can set a threshold where interrupts are ignored. So when we use a LIMI 3, the interrupts of level 4, 5, 6 and higher will be ignored. &lt;br /&gt;
&lt;br /&gt;
The bad news is that Texas Instruments did not use these priority lines in the TI console. In fact, these outputs (IC0 .. IC3) are not connected, and the respective interrupt level inputs at the CPU are hardwired to (0001), which means that every interrupt that occurs in the TI console is automatically set to level 1. So in order to block all interrupts from the 9901&lt;br /&gt;
&lt;br /&gt;
 LIMI 0 &lt;br /&gt;
&lt;br /&gt;
must be executed. To allow all, &lt;br /&gt;
&lt;br /&gt;
 LIMI 1 &lt;br /&gt;
&lt;br /&gt;
is sufficient. For some unclear reason, TI used LIMI 2 in all occasions.&lt;br /&gt;
&lt;br /&gt;
=== Arming and disarming interrupts ===&lt;br /&gt;
&lt;br /&gt;
As said above, every interrupt input can be read at any time in interrupt mode. In that sense, we don&amp;#039;t really treat these inputs as interrupts inputs, but simply as input ports.&lt;br /&gt;
&lt;br /&gt;
If we want a port to trigger an interrupt, we must arm this interrupt input. This is done by setting the respective bit to 1.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  2&lt;br /&gt;
&lt;br /&gt;
This enables interrupt input /INT2 to cause an /INTREQ when its input changes to 0. In the TI console, /INT2 is connected to the interrupt output of the VDP (video processor). If you want to block interrupts on that input, use SBZ.&lt;br /&gt;
&lt;br /&gt;
It is a common misunderstanding that interrupts can be cleared by using a SBZ on an input. I also found this in the popular &amp;#039;&amp;#039;TI Intern&amp;#039;&amp;#039; book. Instead, SBZ n simply blocks interrupt input /INTn. In the case of the video processor, it is up to the VDP to clear its interrupt, and this is done by reading its status register.&lt;br /&gt;
&lt;br /&gt;
Consequently, you &amp;#039;&amp;#039;&amp;#039;cannot&amp;#039;&amp;#039;&amp;#039; read the current mask setting of the 9901. What you get from reading are the input levels of the pins. So if you plan to change the mask bits temporarily, be aware that you cannot ask the 9901 to give you the current setting which you want to restore later. You will have to find out by another way.&lt;br /&gt;
&lt;br /&gt;
== Counter mode ==&lt;br /&gt;
&lt;br /&gt;
Besides the I/O capabilities, the PSI offers a &amp;quot;real-time&amp;quot; clock. This should not be understood as a precise timer with seconds, hundreds of seconds or other but as a countdown timer that is decremented every 64 CPU clock cycles.&lt;br /&gt;
&lt;br /&gt;
This schematic is taken from the Osborne book, redrawn by me in LibreOffice (and with added Load Buffer). &lt;br /&gt;
&lt;br /&gt;
[[File:Clock9901.png|550px]]&lt;br /&gt;
&lt;br /&gt;
=== Using the timer ===&lt;br /&gt;
&lt;br /&gt;
There are basically two ways of using the countdown timer:&lt;br /&gt;
&lt;br /&gt;
* Interval timer: Set the counter to a value; wait until the counter reaches zero. The PSI can be set up to raise an interrupt at that instant.&lt;br /&gt;
* Event timer: Set the counter to a value; do something and then read the counter value.&lt;br /&gt;
&lt;br /&gt;
The counter has 14 bits; that is, its maximum value is 0x3FFF (16383). The counter is decremented every 64 clock cycles, and for the TI-99/4A and the Geneve with a clock cycle time of 333ns, this means that the counter resolution is 21.333 µs. The whole 16384 steps take 0.3495 seconds (349.5 ms).&lt;br /&gt;
&lt;br /&gt;
When the counter reaches 0, it is reloaded from the load buffer, and counting continues.&lt;br /&gt;
&lt;br /&gt;
It is essential to understand that the counter &amp;#039;&amp;#039;&amp;#039;always&amp;#039;&amp;#039;&amp;#039; counts down, independent of the mode. The specification says that the clock is enabled when a non-zero value is loaded into the load buffer, but tests on a real Geneve show that the clock decrements even when the load buffer is filled with 0.&lt;br /&gt;
&lt;br /&gt;
=== Setting the timer ===&lt;br /&gt;
&lt;br /&gt;
In order to set the load buffer and so to define the maximum counter value, we must switch to clock mode. This is achieved by setting bit 0 of the PSI to 1. In the TI/Geneve systems, the PSI is mapped to CRU base address 0, so we can do this&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  0&lt;br /&gt;
&lt;br /&gt;
to enter &amp;#039;&amp;#039;&amp;#039;clock mode&amp;#039;&amp;#039;&amp;#039;. Using SBZ 0 returns to &amp;#039;&amp;#039;&amp;#039;interrupt mode&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
The effect of switching to clock mode is that the load buffer becomes accessible by the CRU addresses 1 to 14. Supposed that we want to load the buffer with the value 0x00FF, we may use these lines:&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  LI   R1,&amp;gt;00FF&lt;br /&gt;
  INCT R12&lt;br /&gt;
  LDCR R1,14&lt;br /&gt;
&lt;br /&gt;
As we see, we must change the CRU base. The LDCR operation always starts at the address defined in R12; if this is not 0, we have to set it here. Note, however, that the CRU address is always half of the R12 value, since the rightmost bit, A15, is not part of the address. If we want to start at bit 1, we must increase R12 by 2. This is, by the way, done wrong in several places in the literature where R12 is only set to 1.&lt;br /&gt;
&lt;br /&gt;
We can combine the SBO and the LDCR to one operation if we define the rightmost bit of R1 as the bit 0 and the following bits as the timer value.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  LI   R1,&amp;gt;01FF    (0000 0001 1111 1111)&lt;br /&gt;
  LDCR R1,15&lt;br /&gt;
&lt;br /&gt;
Do &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; write all 16 bits. The last bit will change bit 15 of the PSI, which is the /RST2 bit. When we are in clock mode, writing a 0 to the /RST2 bit resets all I/O ports. This means that all pins are set to inputs, and devices that rely on them as outputs may be turned off unintentionally.&lt;br /&gt;
&lt;br /&gt;
=== Reading the timer ===&lt;br /&gt;
&lt;br /&gt;
We are interested for the current value of the timer at some occasions. However, since the counter is constantly changing (every 64 clock cycles), reading from the active counter may deliver a wrong result. Think, for instance, about a value 0x2000, which is decremented after we read the first bits, we may end up with 0x2FFF (2 from 0x2000, FFF from 0x1FFF). &lt;br /&gt;
&lt;br /&gt;
For this reason, the timer value is copied to a &amp;#039;&amp;#039;&amp;#039;read buffer&amp;#039;&amp;#039;&amp;#039;. This happens at every 64th clock cycle, when the counter is also decremented, but only when we are &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; in clock mode. &lt;br /&gt;
&lt;br /&gt;
To read the buffer, we must set the PSI to clock mode first, which freezes the counter value in the read buffer. Then we can read the counter using STCR.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  STCR R1,15&lt;br /&gt;
  SRL  R1,1&lt;br /&gt;
  SBZ  0&lt;br /&gt;
&lt;br /&gt;
As we see, register 1 will contain bit 0 in its rightmost bit, so we shift R1 by one position to move it out.&lt;br /&gt;
&lt;br /&gt;
Do not forget to return to interrupt mode, or the buffer will not receive further updates.&lt;br /&gt;
&lt;br /&gt;
As mentioned, bit 15 is the /RST2 bit when we write to it. For reading, bit 15 reflects the /INTREQ output.&lt;br /&gt;
&lt;br /&gt;
=== The weird S0 input ===&lt;br /&gt;
&lt;br /&gt;
There is one peculiarity with the PSI. When the input line S0 is set to 1, the clock mode is &amp;#039;&amp;#039;&amp;#039;left temporarily&amp;#039;&amp;#039;&amp;#039; - until S0 is 0 again, and bit 0 is 1. If bit 0 is 0, this has no effect, as the clock mode is never entered.&lt;br /&gt;
&lt;br /&gt;
What is the reason for this? &lt;br /&gt;
&lt;br /&gt;
TI obviously considered use cases where the PSI remains in clock mode for longer times, while the I/O ports must still be operated. The I/O ports are available on bits 16-31, which means that the S0 input is 1 for these addresses. So when we do a SBO 16, this is not related to the timer value, and thus should control the first I/O port. To allow this, the chip returns to interrupt/port mode for this operation, and when S0 is reset, returns to clock mode.&lt;br /&gt;
&lt;br /&gt;
In the TI console, the S0-S4 inputs are directly connected with the address bus lines A10-A14. This has a strange side effect: Whenever address bus line A10 changes to 1, the clock mode is left temporarily, and this also happens for ordinary memory accesses.&lt;br /&gt;
&lt;br /&gt;
Suppose that we are in clock mode (SBO 0). In this mode, the read buffer should not be updated, so when we read it, we should get the same value every time, although the real counter value is different. But this is difficult to achieve: If our program uses a memory address where A10 is 1 for some instruction, loading this instruction into the CPU will set A10 to 1, and thus leave clock mode, which updates the counter.&lt;br /&gt;
&lt;br /&gt;
Therefore, you may have the impression that the read register continues to be updated even though you set bit 0 to 1. But this is only happening because your program extends over a location where A10 is 1. So if you want to prove that in clock mode the read register remains fixed, your program must avoid all memory locations where A10 is 1.&lt;br /&gt;
&lt;br /&gt;
== Shared pins ==&lt;br /&gt;
&lt;br /&gt;
Some pins have a double meaning; for example, pin 34 is labeled /INT7 / P15. How do we select the functions?&lt;br /&gt;
&lt;br /&gt;
The shared pins are those for the interrupt inputs /INT7 to /INT15; they may also be used for P7 to P15 (in the opposite order).&lt;br /&gt;
&lt;br /&gt;
If the Pn port is set to &amp;#039;&amp;#039;&amp;#039;input&amp;#039;&amp;#039;&amp;#039;, the situation looks like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
This means that both port and interrupt input have the same values; each one is a mirror of the other&amp;#039;s state. We notice it by reading the bits of the associated CRU addresses, getting the same results. There is a difference, though: The interrupt input can be armed to trigger an outgoing interrupt, while the I/O port cannot.&lt;br /&gt;
&lt;br /&gt;
In the other case, the Pn port is an &amp;#039;&amp;#039;&amp;#039;output&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Is that possible? - Sure. The interrupt input will reflect the output of the port. When we set the port to 1, the interrupt input will get that 1 (same for 0). If we arm the interrupt input, setting the output port to 0 will trigger an interrupt via this input. The 9901 specification document recommends to disarm the interrupt trigger for the shared ports because of this possibly unwanted effect.&lt;br /&gt;
&lt;br /&gt;
== Safety precautions ==&lt;br /&gt;
&lt;br /&gt;
The TMS9901 PSI is an interrupt encoder and an I/O circuit. You may have heard that improper use of I/O circuits may damage the circuit so that it must be replaced. Is this possible with the 9901 as well? Is there a way to kill the 9901?&lt;br /&gt;
&lt;br /&gt;
Short answer: Yes.&lt;br /&gt;
&lt;br /&gt;
Longer answer: Yes, but it depends on the way how the ports are used.&lt;br /&gt;
&lt;br /&gt;
If the port is used as an input, nothing bad will happen, since the ports have high input impedance. You can put 0V or 5V directly to the input; of course, you should not use excessive voltage.&lt;br /&gt;
&lt;br /&gt;
=== How to damage the 9901 ports ===&lt;br /&gt;
&lt;br /&gt;
The problem is when the port is configured as an output port. In that case it may output 0V (0) or 5V (1). When you connect the output to the opposite voltage, you will cause a short circuit which quickly exceeds the chip&amp;#039;s maximum power dissipation of 750 mW. This means you will fry the chip.&lt;br /&gt;
&lt;br /&gt;
Consider the following setup (but don&amp;#039;t try it!).&lt;br /&gt;
&lt;br /&gt;
[[File:Badport9901.png|300px]]&lt;br /&gt;
&lt;br /&gt;
Obviously, port P4 is assumed to be an input. When the switch is open, port P4 will sense a 1 on its input. When the switch is closed, it will sense a 0.&lt;br /&gt;
&lt;br /&gt;
Now what happens when you execute this code?&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  20&lt;br /&gt;
&lt;br /&gt;
This will program port P4 to be an output, and to show a 1 (5V) at the output. SBZ will, in contrast, make this port output a 0. So how do you make it an input port? - This is done by resetting the chip. As long as you don&amp;#039;t use a SBZ/SBO (or a LDCR) on the ports, the ports are inputs.&lt;br /&gt;
&lt;br /&gt;
Nothing has happened up to now. But now imagine that you press the switch. This will put the 1 output to ground, causing the short circuit, and your 9901 is toast.&lt;br /&gt;
&lt;br /&gt;
=== What about the TI console? ===&lt;br /&gt;
&lt;br /&gt;
So can we damage the 9901 in the TI console by a suitable CRU operation? - Luckily not. &lt;br /&gt;
&lt;br /&gt;
The reason is the way how TI used the ports on the mainboard. Most of the ports are configured as outputs. Nothing happens when you reset them to inputs.&lt;br /&gt;
&lt;br /&gt;
* The pure interrupt inputs (/INT1 to /INT6) are safe, as they cannot output anything. They have no output driver that could be fried.&lt;br /&gt;
* P0 and P1 are not connected.&lt;br /&gt;
* P2, P3, and P4 are designed as outputs, so they cannot be turned from an input to an output.&lt;br /&gt;
* P5 is the selector of the Alpha Lock key, so output as well.&lt;br /&gt;
* P6 and P7 (/INT15) are the cassette motor control outputs.&lt;br /&gt;
* P8 (/INT14) is the audio gate output.&lt;br /&gt;
* P9 (/INT13) is the cassette output.&lt;br /&gt;
* P10 (/INT12) is pulled up to 5V via a 10 kOhm resistor. If you put a 0 on this port (5V), the maximum current is 500 µA, which results in a power dissipation of 2.5 mW (far below the maximum 750 mW).&lt;br /&gt;
* P11 (/INT11) is the cassette input. However, there is a resistor of 2.2 kOhm (R413) which effectively guards the port of high currents.&lt;br /&gt;
* P12, P13, P14, P15 (/INT10 ... /INT7) are the keyboard input lines. Again, we have pull-ups of 10 kOhm against +5V, but also 470 Ohm on the line, which means a maximum of 8.5 mA or 42.5 mW. This is still well below the maximum value.&lt;br /&gt;
&lt;br /&gt;
So we see that resistors are the best way to protect the ports. And we may calm down; we cannot burn the 9901 in the TI console by a bug in our program.&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50616</id>
		<title>Programmable Systems Interface TMS 9901</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50616"/>
		<updated>2022-04-23T13:40:37Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Input/Output port operation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The Programmable System Interface (PSI) is a circuit that is used on the TI-99/4A main board, on some expansion cards, and on the Geneve.&lt;br /&gt;
&lt;br /&gt;
The designation of the PSI is &amp;#039;&amp;#039;&amp;#039;TMS9901&amp;#039;&amp;#039;&amp;#039;, and this is the name that most users should be more familiar with.&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
&lt;br /&gt;
The TMS 9901 Programmable Systems Interface is a multi-purpose circuit that is used in the TI-99/4A and the Geneve computer, and also on some peripheral cards like the Corcomp Floppy Disk Controller.&lt;br /&gt;
&lt;br /&gt;
The 9901 has three main usage modes:&lt;br /&gt;
&lt;br /&gt;
* Input/Output ports&lt;br /&gt;
* Interrupt prioritizing&lt;br /&gt;
* Timer&lt;br /&gt;
&lt;br /&gt;
The I/O ports are mainly used for scanning the keyboard and joysticks, while the timer is used for cassette data encoding and decoding. The interrupt prioritizer is only used for the video interrupt which is routed from the VDP to the CPU through the 9901.&lt;br /&gt;
&lt;br /&gt;
== Package ==&lt;br /&gt;
&lt;br /&gt;
This is the pin assignment according to the 9901 specification.&lt;br /&gt;
&lt;br /&gt;
[[File:Pack9901.png|300px|left]]&lt;br /&gt;
&lt;br /&gt;
You can see pins of related functions marked by the same background color.&lt;br /&gt;
&lt;br /&gt;
* Orange: Interrupt inputs&lt;br /&gt;
* Red: CRU bit selection (00000 - 11111, i.e. 0-31 decimal)&lt;br /&gt;
* Blue: CRU interface&lt;br /&gt;
* Violet: I/O ports&lt;br /&gt;
* Yellow: Outgoing interrupt and level&lt;br /&gt;
&lt;br /&gt;
Note that some pins have a double function, or in other words, two functions share the same pin. I will discuss this in the following sections.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Input/Output port operation ==&lt;br /&gt;
&lt;br /&gt;
The simplest way of working with the 9901 is input/output operation with the ports. The 9901 has 16 configurable ports that can be individually used as inputs or outputs: P0 to P15.&lt;br /&gt;
&lt;br /&gt;
=== Px port as input ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
The power-on preset for every port is input. If you want to probe the P2 status (bit 18), use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 TB   18&lt;br /&gt;
 JEQ  P2ACT&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the given CRU address into the Equal bit of the status register. JEQ will jump to the location when this input has a high level. The maximum input current is 100 µA. You should protect the input with a resistor; [[#Safety_precautions | see below]] for a more detailed discussion.&lt;br /&gt;
&lt;br /&gt;
If you want to read all 16 ports, use the STCR instruction:&lt;br /&gt;
&lt;br /&gt;
 LI   R12,32&lt;br /&gt;
 STCR R1,0&lt;br /&gt;
&lt;br /&gt;
Note that the CRU base address in R12 advances in steps of 2, so bit 16 is on address 32. Also, for reading 16 bits, you must specify a count of 0.&lt;br /&gt;
&lt;br /&gt;
The 9901 offers some more inputs than the 16 ports; we can use the interrupt inputs as well. More about that can be found in the section about the [[#Interrupt_mode| interrupt mode]].&lt;br /&gt;
&lt;br /&gt;
=== Px port as output ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Once you write a value to the CRU address of a 9901 port, this port switches to output mode. The value written to the port is the value that is shown on its pin. If we want to output a 1 on P6, we have to use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
&lt;br /&gt;
If we wanted to output a 0, SBZ would have to be used. In order to set several ports in one go, use LDCR.&lt;br /&gt;
&lt;br /&gt;
We can also read the output port just by using TB as usual (or STCR for several ports at once). &lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
 TB   22&lt;br /&gt;
 JEQ  EXPECT&lt;br /&gt;
&lt;br /&gt;
The TB operation should return the 1 which the previous SBO has set.&lt;br /&gt;
&lt;br /&gt;
You &amp;#039;&amp;#039;&amp;#039;cannot&amp;#039;&amp;#039;&amp;#039; switch back to input mode. This is only possible by resetting the 9901, either using the /RST1 input line, or by setting the /RST2 bit (bit 15) to 0 in clock mode.&lt;br /&gt;
&lt;br /&gt;
== Interrupt mode ==&lt;br /&gt;
&lt;br /&gt;
In interrupt mode, bits 1 to 15 are used to set the interrupt mask (write), or to query the interrupt inputs (read). That is, the meaning of reading and writing the bits is indeed different.&lt;br /&gt;
&lt;br /&gt;
This schema shows how the interrupt inputs are used.&lt;br /&gt;
&lt;br /&gt;
[[File:Int9901.png|600px]]&lt;br /&gt;
&lt;br /&gt;
This illustration is slightly simplified; there is a latch between each interrupt input and the mask AND gate. This is not relevant for our programming, though. For more details see the &amp;#039;&amp;#039;TMS 9901 Programmable Systems Interface Data Manual&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
=== Checking the interrupt inputs ===&lt;br /&gt;
&lt;br /&gt;
In general, every interrupt input can be read via CRU access:&lt;br /&gt;
&lt;br /&gt;
  CLR R12&lt;br /&gt;
  TB  15&lt;br /&gt;
  JEQ NOINT15&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the specified CRU address to the Equal status bit. This means that JEQ jumps to the location when the bit is 1. Since all interrupt inputs are negative logic, reading a 1 means that there is no interrupt at this input (so I called it NOINT15).&lt;br /&gt;
&lt;br /&gt;
If we want to read several interrupt inputs by a single instruction, we have to use STCR.&lt;br /&gt;
&lt;br /&gt;
  LI   R12,16&lt;br /&gt;
  STCR R1,8&lt;br /&gt;
&lt;br /&gt;
This reads the states of the /INT8, /INT9, ..., /INT15 inputs and puts them in the most significant byte of R1. (See the specification of STCR and LDCR for transfers of up to 8 bits.)&lt;br /&gt;
&lt;br /&gt;
=== Interrupt levels ===&lt;br /&gt;
&lt;br /&gt;
Each interrupt input (/INT1 to /INT15) may be used to trigger an interrupt at another circuit, typically the CPU. For this purpose, the PSI offers an /INTREQ outgoing line. In the TI console, it is routed to the /INTREQ input of the TMS9900 CPU. &lt;br /&gt;
&lt;br /&gt;
In addition there is a priority encoder, which is not shown here. This encoder outputs a 4-bit number which reflects the smallest number of the interrupt inputs where an interrupt has been sensed.&lt;br /&gt;
&lt;br /&gt;
That is, if /INT4 and /INT7 are currently set to 0 (active), the priority encoder outputs (0100), which means 4.&lt;br /&gt;
&lt;br /&gt;
The TMS9900 CPU can differentiate between these interrupt levels, and by using its LIMI command, we can set a threshold where interrupts are ignored. So when we use a LIMI 3, the interrupts of level 4, 5, 6 and higher will be ignored. &lt;br /&gt;
&lt;br /&gt;
The bad news is that Texas Instruments did not use these priority lines in the TI console. In fact, these outputs (IC0 .. IC3) are not connected, and the respective interrupt level inputs at the CPU are hardwired to (0001), which means that every interrupt that occurs in the TI console is automatically set to level 1. So in order to block all interrupts from the 9901&lt;br /&gt;
&lt;br /&gt;
 LIMI 0 &lt;br /&gt;
&lt;br /&gt;
must be executed. To allow all, &lt;br /&gt;
&lt;br /&gt;
 LIMI 1 &lt;br /&gt;
&lt;br /&gt;
is sufficient. For some unclear reason, TI used LIMI 2 in all occasions.&lt;br /&gt;
&lt;br /&gt;
=== Arming and disarming interrupts ===&lt;br /&gt;
&lt;br /&gt;
As said above, every interrupt input can be read at any time in interrupt mode. In that sense, we don&amp;#039;t really treat these inputs as interrupts inputs, but simply as input ports.&lt;br /&gt;
&lt;br /&gt;
If we want a port to trigger an interrupt, we must arm this interrupt input. This is done by setting the respective bit to 1.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  2&lt;br /&gt;
&lt;br /&gt;
This enables interrupt input /INT2 to cause an /INTREQ when its input changes to 0. In the TI console, /INT2 is connected to the interrupt output of the VDP (video processor). If you want to block interrupts on that input, use SBZ.&lt;br /&gt;
&lt;br /&gt;
It is a common misunderstanding that interrupts can be cleared by using a SBZ on an input. This can, for example, be seen in the popular TI Intern book. Instead, SBZ n simply blocks interrupt input /INTn. In the case of the video processor, it is up to the VDP to clear its interrupt, and this is done by reading its status register.&lt;br /&gt;
&lt;br /&gt;
== Counter mode ==&lt;br /&gt;
&lt;br /&gt;
Besides the I/O capabilities, the PSI offers a &amp;quot;real-time&amp;quot; clock. This should not be understood as a precise timer with seconds, hundreds of seconds or other but as a countdown timer that is decremented every 64 CPU clock cycles.&lt;br /&gt;
&lt;br /&gt;
This schematic is taken from the Osborne book, redrawn by me in LibreOffice (and with added Load Buffer). &lt;br /&gt;
&lt;br /&gt;
[[File:Clock9901.png|550px]]&lt;br /&gt;
&lt;br /&gt;
=== Using the timer ===&lt;br /&gt;
&lt;br /&gt;
There are basically two ways of using the countdown timer:&lt;br /&gt;
&lt;br /&gt;
* Interval timer: Set the counter to a value; wait until the counter reaches zero. The PSI can be set up to raise an interrupt at that instant.&lt;br /&gt;
* Event timer: Set the counter to a value; do something and then read the counter value.&lt;br /&gt;
&lt;br /&gt;
The counter has 14 bits; that is, its maximum value is 0x3FFF (16383). The counter is decremented every 64 clock cycles, and for the TI-99/4A and the Geneve with a clock cycle time of 333ns, this means that the counter resolution is 21.333 µs. The whole 16384 steps take 0.3495 seconds (349.5 ms).&lt;br /&gt;
&lt;br /&gt;
When the counter reaches 0, it is reloaded from the load buffer, and counting continues.&lt;br /&gt;
&lt;br /&gt;
It is essential to understand that the counter &amp;#039;&amp;#039;&amp;#039;always&amp;#039;&amp;#039;&amp;#039; counts down, independent of the mode. The specification says that the clock is enabled when a non-zero value is loaded into the load buffer, but tests on a real Geneve show that the clock decrements even when the load buffer is filled with 0.&lt;br /&gt;
&lt;br /&gt;
=== Setting the timer ===&lt;br /&gt;
&lt;br /&gt;
In order to set the load buffer and so to define the maximum counter value, we must switch to clock mode. This is achieved by setting bit 0 of the PSI to 1. In the TI/Geneve systems, the PSI is mapped to CRU base address 0, so we can do this&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  0&lt;br /&gt;
&lt;br /&gt;
to enter &amp;#039;&amp;#039;&amp;#039;clock mode&amp;#039;&amp;#039;&amp;#039;. Using SBZ 0 returns to &amp;#039;&amp;#039;&amp;#039;interrupt mode&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
The effect of switching to clock mode is that the load buffer becomes accessible by the CRU addresses 1 to 14. Supposed that we want to load the buffer with the value 0x00FF, we may use these lines:&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  LI   R1,&amp;gt;00FF&lt;br /&gt;
  INCT R12&lt;br /&gt;
  LDCR R1,14&lt;br /&gt;
&lt;br /&gt;
As we see, we must change the CRU base. The LDCR operation always starts at the address defined in R12; if this is not 0, we have to set it here. Note, however, that the CRU address is always half of the R12 value, since the rightmost bit, A15, is not part of the address. If we want to start at bit 1, we must increase R12 by 2. This is, by the way, done wrong in several places in the literature where R12 is only set to 1.&lt;br /&gt;
&lt;br /&gt;
We can combine the SBO and the LDCR to one operation if we define the rightmost bit of R1 as the bit 0 and the following bits as the timer value.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  LI   R1,&amp;gt;01FF    (0000 0001 1111 1111)&lt;br /&gt;
  LDCR R1,15&lt;br /&gt;
&lt;br /&gt;
Do &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; write all 16 bits. The last bit will change bit 15 of the PSI, which is the /RST2 bit. When we are in clock mode, writing a 0 to the /RST2 bit resets all I/O ports. This means that all pins are set to inputs, and devices that rely on them as outputs may be turned off unintentionally.&lt;br /&gt;
&lt;br /&gt;
=== Reading the timer ===&lt;br /&gt;
&lt;br /&gt;
We are interested for the current value of the timer at some occasions. However, since the counter is constantly changing (every 64 clock cycles), reading from the active counter may deliver a wrong result. Think, for instance, about a value 0x2000, which is decremented after we read the first bits, we may end up with 0x2FFF (2 from 0x2000, FFF from 0x1FFF). &lt;br /&gt;
&lt;br /&gt;
For this reason, the timer value is copied to a &amp;#039;&amp;#039;&amp;#039;read buffer&amp;#039;&amp;#039;&amp;#039;. This happens at every 64th clock cycle, when the counter is also decremented, but only when we are &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; in clock mode. &lt;br /&gt;
&lt;br /&gt;
To read the buffer, we must set the PSI to clock mode first, which freezes the counter value in the read buffer. Then we can read the counter using STCR.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  STCR R1,15&lt;br /&gt;
  SRL  R1,1&lt;br /&gt;
  SBZ  0&lt;br /&gt;
&lt;br /&gt;
As we see, register 1 will contain bit 0 in its rightmost bit, so we shift R1 by one position to move it out.&lt;br /&gt;
&lt;br /&gt;
Do not forget to return to interrupt mode, or the buffer will not receive further updates.&lt;br /&gt;
&lt;br /&gt;
As mentioned, bit 15 is the /RST2 bit when we write to it. For reading, bit 15 reflects the /INTREQ output.&lt;br /&gt;
&lt;br /&gt;
=== The weird S0 input ===&lt;br /&gt;
&lt;br /&gt;
There is one peculiarity with the PSI. When the input line S0 is set to 1, the clock mode is &amp;#039;&amp;#039;&amp;#039;left temporarily&amp;#039;&amp;#039;&amp;#039; - until S0 is 0 again, and bit 0 is 1. If bit 0 is 0, this has no effect, as the clock mode is never entered.&lt;br /&gt;
&lt;br /&gt;
What is the reason for this? &lt;br /&gt;
&lt;br /&gt;
TI obviously considered use cases where the PSI remains in clock mode for longer times, while the I/O ports must still be operated. The I/O ports are available on bits 16-31, which means that the S0 input is 1 for these addresses. So when we do a SBO 16, this is not related to the timer value, and thus should control the first I/O port. To allow this, the chip returns to interrupt/port mode for this operation, and when S0 is reset, returns to clock mode.&lt;br /&gt;
&lt;br /&gt;
In the TI console, the S0-S4 inputs are directly connected with the address bus lines A10-A14. This has a strange side effect: Whenever address bus line A10 changes to 1, the clock mode is left temporarily, and this also happens for ordinary memory accesses.&lt;br /&gt;
&lt;br /&gt;
Suppose that we are in clock mode (SBO 0). In this mode, the read buffer should not be updated, so when we read it, we should get the same value every time, although the real counter value is different. But this is difficult to achieve: If our program uses a memory address where A10 is 1 for some instruction, loading this instruction into the CPU will set A10 to 1, and thus leave clock mode, which updates the counter.&lt;br /&gt;
&lt;br /&gt;
Therefore, you may have the impression that the read register continues to be updated even though you set bit 0 to 1. But this is only happening because your program extends over a location where A10 is 1. So if you want to prove that in clock mode the read register remains fixed, your program must avoid all memory locations where A10 is 1.&lt;br /&gt;
&lt;br /&gt;
== Shared pins ==&lt;br /&gt;
&lt;br /&gt;
Some pins have a double meaning; for example, pin 34 is labeled /INT7 / P15. How do we select the functions?&lt;br /&gt;
&lt;br /&gt;
The shared pins are those for the interrupt inputs /INT7 to /INT15; they may also be used for P7 to P15 (in the opposite order).&lt;br /&gt;
&lt;br /&gt;
If the Pn port is set to &amp;#039;&amp;#039;&amp;#039;input&amp;#039;&amp;#039;&amp;#039;, the situation looks like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
This means that both port and interrupt input have the same values; each one is a mirror of the other&amp;#039;s state. We notice it by reading the bits of the associated CRU addresses, getting the same results. There is a difference, though: The interrupt input can be armed to trigger an outgoing interrupt, while the I/O port cannot.&lt;br /&gt;
&lt;br /&gt;
In the other case, the Pn port is an &amp;#039;&amp;#039;&amp;#039;output&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Is that possible? - Sure. The interrupt input will reflect the output of the port. When we set the port to 1, the interrupt input will get that 1 (same for 0). If we arm the interrupt input, setting the output port to 0 will trigger an interrupt via this input. The 9901 specification document recommends to disarm the interrupt trigger for the shared ports because of this possibly unwanted effect.&lt;br /&gt;
&lt;br /&gt;
== Safety precautions ==&lt;br /&gt;
&lt;br /&gt;
The TMS9901 PSI is an interrupt encoder and an I/O circuit. You may have heard that improper use of I/O circuits may damage the circuit so that it must be replaced. Is this possible with the 9901 as well? Is there a way to kill the 9901?&lt;br /&gt;
&lt;br /&gt;
Short answer: Yes.&lt;br /&gt;
&lt;br /&gt;
Longer answer: Yes, but it depends on the way how the ports are used.&lt;br /&gt;
&lt;br /&gt;
If the port is used as an input, nothing bad will happen, since the ports have high input impedance. You can put 0V or 5V directly to the input; of course, you should not use excessive voltage.&lt;br /&gt;
&lt;br /&gt;
=== How to damage the 9901 ports ===&lt;br /&gt;
&lt;br /&gt;
The problem is when the port is configured as an output port. In that case it may output 0V (0) or 5V (1). When you connect the output to the opposite voltage, you will cause a short circuit which quickly exceeds the chip&amp;#039;s maximum power dissipation of 750 mW. This means you will fry the chip.&lt;br /&gt;
&lt;br /&gt;
Consider the following setup (but don&amp;#039;t try it!).&lt;br /&gt;
&lt;br /&gt;
[[File:Badport9901.png|300px]]&lt;br /&gt;
&lt;br /&gt;
Obviously, port P4 is assumed to be an input. When the switch is open, port P4 will sense a 1 on its input. When the switch is closed, it will sense a 0.&lt;br /&gt;
&lt;br /&gt;
Now what happens when you execute this code?&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  20&lt;br /&gt;
&lt;br /&gt;
This will program port P4 to be an output, and to show a 1 (5V) at the output. SBZ will, in contrast, make this port output a 0. So how do you make it an input port? - This is done by resetting the chip. As long as you don&amp;#039;t use a SBZ/SBO (or a LDCR) on the ports, the ports are inputs.&lt;br /&gt;
&lt;br /&gt;
Nothing has happened up to now. But now imagine that you press the switch. This will put the 1 output to ground, causing the short circuit, and your 9901 is toast.&lt;br /&gt;
&lt;br /&gt;
=== What about the TI console? ===&lt;br /&gt;
&lt;br /&gt;
So can we damage the 9901 in the TI console by a suitable CRU operation? - Luckily not. &lt;br /&gt;
&lt;br /&gt;
The reason is the way how TI used the ports on the mainboard. Most of the ports are configured as outputs. Nothing happens when you reset them to inputs.&lt;br /&gt;
&lt;br /&gt;
* The pure interrupt inputs (/INT1 to /INT6) are safe, as they cannot output anything. They have no output driver that could be fried.&lt;br /&gt;
* P0 and P1 are not connected.&lt;br /&gt;
* P2, P3, and P4 are designed as outputs, so they cannot be turned from an input to an output.&lt;br /&gt;
* P5 is the selector of the Alpha Lock key, so output as well.&lt;br /&gt;
* P6 and P7 (/INT15) are the cassette motor control outputs.&lt;br /&gt;
* P8 (/INT14) is the audio gate output.&lt;br /&gt;
* P9 (/INT13) is the cassette output.&lt;br /&gt;
* P10 (/INT12) is pulled up to 5V via a 10 kOhm resistor. If you put a 0 on this port (5V), the maximum current is 500 µA, which results in a power dissipation of 2.5 mW (far below the maximum 750 mW).&lt;br /&gt;
* P11 (/INT11) is the cassette input. However, there is a resistor of 2.2 kOhm (R413) which effectively guards the port of high currents.&lt;br /&gt;
* P12, P13, P14, P15 (/INT10 ... /INT7) are the keyboard input lines. Again, we have pull-ups of 10 kOhm against +5V, but also 470 Ohm on the line, which means a maximum of 8.5 mA or 42.5 mW. This is still well below the maximum value.&lt;br /&gt;
&lt;br /&gt;
So we see that resistors are the best way to protect the ports. And we may calm down; we cannot burn the 9901 in the TI console by a bug in our program.&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50615</id>
		<title>Programmable Systems Interface TMS 9901</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50615"/>
		<updated>2022-04-23T13:39:53Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Input/Output port operation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The Programmable System Interface (PSI) is a circuit that is used on the TI-99/4A main board, on some expansion cards, and on the Geneve.&lt;br /&gt;
&lt;br /&gt;
The designation of the PSI is &amp;#039;&amp;#039;&amp;#039;TMS9901&amp;#039;&amp;#039;&amp;#039;, and this is the name that most users should be more familiar with.&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
&lt;br /&gt;
The TMS 9901 Programmable Systems Interface is a multi-purpose circuit that is used in the TI-99/4A and the Geneve computer, and also on some peripheral cards like the Corcomp Floppy Disk Controller.&lt;br /&gt;
&lt;br /&gt;
The 9901 has three main usage modes:&lt;br /&gt;
&lt;br /&gt;
* Input/Output ports&lt;br /&gt;
* Interrupt prioritizing&lt;br /&gt;
* Timer&lt;br /&gt;
&lt;br /&gt;
The I/O ports are mainly used for scanning the keyboard and joysticks, while the timer is used for cassette data encoding and decoding. The interrupt prioritizer is only used for the video interrupt which is routed from the VDP to the CPU through the 9901.&lt;br /&gt;
&lt;br /&gt;
== Package ==&lt;br /&gt;
&lt;br /&gt;
This is the pin assignment according to the 9901 specification.&lt;br /&gt;
&lt;br /&gt;
[[File:Pack9901.png|300px|left]]&lt;br /&gt;
&lt;br /&gt;
You can see pins of related functions marked by the same background color.&lt;br /&gt;
&lt;br /&gt;
* Orange: Interrupt inputs&lt;br /&gt;
* Red: CRU bit selection (00000 - 11111, i.e. 0-31 decimal)&lt;br /&gt;
* Blue: CRU interface&lt;br /&gt;
* Violet: I/O ports&lt;br /&gt;
* Yellow: Outgoing interrupt and level&lt;br /&gt;
&lt;br /&gt;
Note that some pins have a double function, or in other words, two functions share the same pin. I will discuss this in the following sections.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Input/Output port operation ==&lt;br /&gt;
&lt;br /&gt;
The simplest way of working with the 9901 is input/output operation with the ports. The 9901 has 16 configurable ports that can be individually used as inputs or outputs: P0 to P15.&lt;br /&gt;
&lt;br /&gt;
=== Px port as input ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
The power-on preset for every port is input. If you want to probe the P2 status (bit 18), use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 TB   18&lt;br /&gt;
 JEQ  P2ACT&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the given CRU address into the Equal bit of the status register. JEQ will jump to the location when this input has a high level. The maximum input current is 100 µA. You should protect the input with a resistor; see below for a more detailed discussion.&lt;br /&gt;
&lt;br /&gt;
If you want to read all 16 ports, use the STCR instruction:&lt;br /&gt;
&lt;br /&gt;
 LI   R12,32&lt;br /&gt;
 STCR R1,0&lt;br /&gt;
&lt;br /&gt;
Note that the CRU base address in R12 advances in steps of 2, so bit 16 is on address 32. Also, for reading 16 bits, you must specify a count of 0.&lt;br /&gt;
&lt;br /&gt;
The 9901 offers some more inputs than the 16 ports; we can use the interrupt inputs as well. More about that can be found in the section about the [[#Interrupt_mode| interrupt mode]].&lt;br /&gt;
&lt;br /&gt;
=== Px port as output ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Once you write a value to the CRU address of a 9901 port, this port switches to output mode. The value written to the port is the value that is shown on its pin. If we want to output a 1 on P6, we have to use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
&lt;br /&gt;
If we wanted to output a 0, SBZ would have to be used. In order to set several ports in one go, use LDCR.&lt;br /&gt;
&lt;br /&gt;
We can also read the output port just by using TB as usual (or STCR for several ports at once). &lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
 TB   22&lt;br /&gt;
 JEQ  EXPECT&lt;br /&gt;
&lt;br /&gt;
The TB operation should return the 1 which the previous SBO has set.&lt;br /&gt;
&lt;br /&gt;
You &amp;#039;&amp;#039;&amp;#039;cannot&amp;#039;&amp;#039;&amp;#039; switch back to input mode. This is only possible by resetting the 9901, either using the /RST1 input line, or by setting the /RST2 bit (bit 15) to 0 in clock mode.&lt;br /&gt;
&lt;br /&gt;
== Interrupt mode ==&lt;br /&gt;
&lt;br /&gt;
In interrupt mode, bits 1 to 15 are used to set the interrupt mask (write), or to query the interrupt inputs (read). That is, the meaning of reading and writing the bits is indeed different.&lt;br /&gt;
&lt;br /&gt;
This schema shows how the interrupt inputs are used.&lt;br /&gt;
&lt;br /&gt;
[[File:Int9901.png|600px]]&lt;br /&gt;
&lt;br /&gt;
This illustration is slightly simplified; there is a latch between each interrupt input and the mask AND gate. This is not relevant for our programming, though. For more details see the &amp;#039;&amp;#039;TMS 9901 Programmable Systems Interface Data Manual&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
=== Checking the interrupt inputs ===&lt;br /&gt;
&lt;br /&gt;
In general, every interrupt input can be read via CRU access:&lt;br /&gt;
&lt;br /&gt;
  CLR R12&lt;br /&gt;
  TB  15&lt;br /&gt;
  JEQ NOINT15&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the specified CRU address to the Equal status bit. This means that JEQ jumps to the location when the bit is 1. Since all interrupt inputs are negative logic, reading a 1 means that there is no interrupt at this input (so I called it NOINT15).&lt;br /&gt;
&lt;br /&gt;
If we want to read several interrupt inputs by a single instruction, we have to use STCR.&lt;br /&gt;
&lt;br /&gt;
  LI   R12,16&lt;br /&gt;
  STCR R1,8&lt;br /&gt;
&lt;br /&gt;
This reads the states of the /INT8, /INT9, ..., /INT15 inputs and puts them in the most significant byte of R1. (See the specification of STCR and LDCR for transfers of up to 8 bits.)&lt;br /&gt;
&lt;br /&gt;
=== Interrupt levels ===&lt;br /&gt;
&lt;br /&gt;
Each interrupt input (/INT1 to /INT15) may be used to trigger an interrupt at another circuit, typically the CPU. For this purpose, the PSI offers an /INTREQ outgoing line. In the TI console, it is routed to the /INTREQ input of the TMS9900 CPU. &lt;br /&gt;
&lt;br /&gt;
In addition there is a priority encoder, which is not shown here. This encoder outputs a 4-bit number which reflects the smallest number of the interrupt inputs where an interrupt has been sensed.&lt;br /&gt;
&lt;br /&gt;
That is, if /INT4 and /INT7 are currently set to 0 (active), the priority encoder outputs (0100), which means 4.&lt;br /&gt;
&lt;br /&gt;
The TMS9900 CPU can differentiate between these interrupt levels, and by using its LIMI command, we can set a threshold where interrupts are ignored. So when we use a LIMI 3, the interrupts of level 4, 5, 6 and higher will be ignored. &lt;br /&gt;
&lt;br /&gt;
The bad news is that Texas Instruments did not use these priority lines in the TI console. In fact, these outputs (IC0 .. IC3) are not connected, and the respective interrupt level inputs at the CPU are hardwired to (0001), which means that every interrupt that occurs in the TI console is automatically set to level 1. So in order to block all interrupts from the 9901&lt;br /&gt;
&lt;br /&gt;
 LIMI 0 &lt;br /&gt;
&lt;br /&gt;
must be executed. To allow all, &lt;br /&gt;
&lt;br /&gt;
 LIMI 1 &lt;br /&gt;
&lt;br /&gt;
is sufficient. For some unclear reason, TI used LIMI 2 in all occasions.&lt;br /&gt;
&lt;br /&gt;
=== Arming and disarming interrupts ===&lt;br /&gt;
&lt;br /&gt;
As said above, every interrupt input can be read at any time in interrupt mode. In that sense, we don&amp;#039;t really treat these inputs as interrupts inputs, but simply as input ports.&lt;br /&gt;
&lt;br /&gt;
If we want a port to trigger an interrupt, we must arm this interrupt input. This is done by setting the respective bit to 1.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  2&lt;br /&gt;
&lt;br /&gt;
This enables interrupt input /INT2 to cause an /INTREQ when its input changes to 0. In the TI console, /INT2 is connected to the interrupt output of the VDP (video processor). If you want to block interrupts on that input, use SBZ.&lt;br /&gt;
&lt;br /&gt;
It is a common misunderstanding that interrupts can be cleared by using a SBZ on an input. This can, for example, be seen in the popular TI Intern book. Instead, SBZ n simply blocks interrupt input /INTn. In the case of the video processor, it is up to the VDP to clear its interrupt, and this is done by reading its status register.&lt;br /&gt;
&lt;br /&gt;
== Counter mode ==&lt;br /&gt;
&lt;br /&gt;
Besides the I/O capabilities, the PSI offers a &amp;quot;real-time&amp;quot; clock. This should not be understood as a precise timer with seconds, hundreds of seconds or other but as a countdown timer that is decremented every 64 CPU clock cycles.&lt;br /&gt;
&lt;br /&gt;
This schematic is taken from the Osborne book, redrawn by me in LibreOffice (and with added Load Buffer). &lt;br /&gt;
&lt;br /&gt;
[[File:Clock9901.png|550px]]&lt;br /&gt;
&lt;br /&gt;
=== Using the timer ===&lt;br /&gt;
&lt;br /&gt;
There are basically two ways of using the countdown timer:&lt;br /&gt;
&lt;br /&gt;
* Interval timer: Set the counter to a value; wait until the counter reaches zero. The PSI can be set up to raise an interrupt at that instant.&lt;br /&gt;
* Event timer: Set the counter to a value; do something and then read the counter value.&lt;br /&gt;
&lt;br /&gt;
The counter has 14 bits; that is, its maximum value is 0x3FFF (16383). The counter is decremented every 64 clock cycles, and for the TI-99/4A and the Geneve with a clock cycle time of 333ns, this means that the counter resolution is 21.333 µs. The whole 16384 steps take 0.3495 seconds (349.5 ms).&lt;br /&gt;
&lt;br /&gt;
When the counter reaches 0, it is reloaded from the load buffer, and counting continues.&lt;br /&gt;
&lt;br /&gt;
It is essential to understand that the counter &amp;#039;&amp;#039;&amp;#039;always&amp;#039;&amp;#039;&amp;#039; counts down, independent of the mode. The specification says that the clock is enabled when a non-zero value is loaded into the load buffer, but tests on a real Geneve show that the clock decrements even when the load buffer is filled with 0.&lt;br /&gt;
&lt;br /&gt;
=== Setting the timer ===&lt;br /&gt;
&lt;br /&gt;
In order to set the load buffer and so to define the maximum counter value, we must switch to clock mode. This is achieved by setting bit 0 of the PSI to 1. In the TI/Geneve systems, the PSI is mapped to CRU base address 0, so we can do this&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  0&lt;br /&gt;
&lt;br /&gt;
to enter &amp;#039;&amp;#039;&amp;#039;clock mode&amp;#039;&amp;#039;&amp;#039;. Using SBZ 0 returns to &amp;#039;&amp;#039;&amp;#039;interrupt mode&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
The effect of switching to clock mode is that the load buffer becomes accessible by the CRU addresses 1 to 14. Supposed that we want to load the buffer with the value 0x00FF, we may use these lines:&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  LI   R1,&amp;gt;00FF&lt;br /&gt;
  INCT R12&lt;br /&gt;
  LDCR R1,14&lt;br /&gt;
&lt;br /&gt;
As we see, we must change the CRU base. The LDCR operation always starts at the address defined in R12; if this is not 0, we have to set it here. Note, however, that the CRU address is always half of the R12 value, since the rightmost bit, A15, is not part of the address. If we want to start at bit 1, we must increase R12 by 2. This is, by the way, done wrong in several places in the literature where R12 is only set to 1.&lt;br /&gt;
&lt;br /&gt;
We can combine the SBO and the LDCR to one operation if we define the rightmost bit of R1 as the bit 0 and the following bits as the timer value.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  LI   R1,&amp;gt;01FF    (0000 0001 1111 1111)&lt;br /&gt;
  LDCR R1,15&lt;br /&gt;
&lt;br /&gt;
Do &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; write all 16 bits. The last bit will change bit 15 of the PSI, which is the /RST2 bit. When we are in clock mode, writing a 0 to the /RST2 bit resets all I/O ports. This means that all pins are set to inputs, and devices that rely on them as outputs may be turned off unintentionally.&lt;br /&gt;
&lt;br /&gt;
=== Reading the timer ===&lt;br /&gt;
&lt;br /&gt;
We are interested for the current value of the timer at some occasions. However, since the counter is constantly changing (every 64 clock cycles), reading from the active counter may deliver a wrong result. Think, for instance, about a value 0x2000, which is decremented after we read the first bits, we may end up with 0x2FFF (2 from 0x2000, FFF from 0x1FFF). &lt;br /&gt;
&lt;br /&gt;
For this reason, the timer value is copied to a &amp;#039;&amp;#039;&amp;#039;read buffer&amp;#039;&amp;#039;&amp;#039;. This happens at every 64th clock cycle, when the counter is also decremented, but only when we are &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; in clock mode. &lt;br /&gt;
&lt;br /&gt;
To read the buffer, we must set the PSI to clock mode first, which freezes the counter value in the read buffer. Then we can read the counter using STCR.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  STCR R1,15&lt;br /&gt;
  SRL  R1,1&lt;br /&gt;
  SBZ  0&lt;br /&gt;
&lt;br /&gt;
As we see, register 1 will contain bit 0 in its rightmost bit, so we shift R1 by one position to move it out.&lt;br /&gt;
&lt;br /&gt;
Do not forget to return to interrupt mode, or the buffer will not receive further updates.&lt;br /&gt;
&lt;br /&gt;
As mentioned, bit 15 is the /RST2 bit when we write to it. For reading, bit 15 reflects the /INTREQ output.&lt;br /&gt;
&lt;br /&gt;
=== The weird S0 input ===&lt;br /&gt;
&lt;br /&gt;
There is one peculiarity with the PSI. When the input line S0 is set to 1, the clock mode is &amp;#039;&amp;#039;&amp;#039;left temporarily&amp;#039;&amp;#039;&amp;#039; - until S0 is 0 again, and bit 0 is 1. If bit 0 is 0, this has no effect, as the clock mode is never entered.&lt;br /&gt;
&lt;br /&gt;
What is the reason for this? &lt;br /&gt;
&lt;br /&gt;
TI obviously considered use cases where the PSI remains in clock mode for longer times, while the I/O ports must still be operated. The I/O ports are available on bits 16-31, which means that the S0 input is 1 for these addresses. So when we do a SBO 16, this is not related to the timer value, and thus should control the first I/O port. To allow this, the chip returns to interrupt/port mode for this operation, and when S0 is reset, returns to clock mode.&lt;br /&gt;
&lt;br /&gt;
In the TI console, the S0-S4 inputs are directly connected with the address bus lines A10-A14. This has a strange side effect: Whenever address bus line A10 changes to 1, the clock mode is left temporarily, and this also happens for ordinary memory accesses.&lt;br /&gt;
&lt;br /&gt;
Suppose that we are in clock mode (SBO 0). In this mode, the read buffer should not be updated, so when we read it, we should get the same value every time, although the real counter value is different. But this is difficult to achieve: If our program uses a memory address where A10 is 1 for some instruction, loading this instruction into the CPU will set A10 to 1, and thus leave clock mode, which updates the counter.&lt;br /&gt;
&lt;br /&gt;
Therefore, you may have the impression that the read register continues to be updated even though you set bit 0 to 1. But this is only happening because your program extends over a location where A10 is 1. So if you want to prove that in clock mode the read register remains fixed, your program must avoid all memory locations where A10 is 1.&lt;br /&gt;
&lt;br /&gt;
== Shared pins ==&lt;br /&gt;
&lt;br /&gt;
Some pins have a double meaning; for example, pin 34 is labeled /INT7 / P15. How do we select the functions?&lt;br /&gt;
&lt;br /&gt;
The shared pins are those for the interrupt inputs /INT7 to /INT15; they may also be used for P7 to P15 (in the opposite order).&lt;br /&gt;
&lt;br /&gt;
If the Pn port is set to &amp;#039;&amp;#039;&amp;#039;input&amp;#039;&amp;#039;&amp;#039;, the situation looks like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
This means that both port and interrupt input have the same values; each one is a mirror of the other&amp;#039;s state. We notice it by reading the bits of the associated CRU addresses, getting the same results. There is a difference, though: The interrupt input can be armed to trigger an outgoing interrupt, while the I/O port cannot.&lt;br /&gt;
&lt;br /&gt;
In the other case, the Pn port is an &amp;#039;&amp;#039;&amp;#039;output&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Is that possible? - Sure. The interrupt input will reflect the output of the port. When we set the port to 1, the interrupt input will get that 1 (same for 0). If we arm the interrupt input, setting the output port to 0 will trigger an interrupt via this input. The 9901 specification document recommends to disarm the interrupt trigger for the shared ports because of this possibly unwanted effect.&lt;br /&gt;
&lt;br /&gt;
== Safety precautions ==&lt;br /&gt;
&lt;br /&gt;
The TMS9901 PSI is an interrupt encoder and an I/O circuit. You may have heard that improper use of I/O circuits may damage the circuit so that it must be replaced. Is this possible with the 9901 as well? Is there a way to kill the 9901?&lt;br /&gt;
&lt;br /&gt;
Short answer: Yes.&lt;br /&gt;
&lt;br /&gt;
Longer answer: Yes, but it depends on the way how the ports are used.&lt;br /&gt;
&lt;br /&gt;
If the port is used as an input, nothing bad will happen, since the ports have high input impedance. You can put 0V or 5V directly to the input; of course, you should not use excessive voltage.&lt;br /&gt;
&lt;br /&gt;
=== How to damage the 9901 ports ===&lt;br /&gt;
&lt;br /&gt;
The problem is when the port is configured as an output port. In that case it may output 0V (0) or 5V (1). When you connect the output to the opposite voltage, you will cause a short circuit which quickly exceeds the chip&amp;#039;s maximum power dissipation of 750 mW. This means you will fry the chip.&lt;br /&gt;
&lt;br /&gt;
Consider the following setup (but don&amp;#039;t try it!).&lt;br /&gt;
&lt;br /&gt;
[[File:Badport9901.png|300px]]&lt;br /&gt;
&lt;br /&gt;
Obviously, port P4 is assumed to be an input. When the switch is open, port P4 will sense a 1 on its input. When the switch is closed, it will sense a 0.&lt;br /&gt;
&lt;br /&gt;
Now what happens when you execute this code?&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  20&lt;br /&gt;
&lt;br /&gt;
This will program port P4 to be an output, and to show a 1 (5V) at the output. SBZ will, in contrast, make this port output a 0. So how do you make it an input port? - This is done by resetting the chip. As long as you don&amp;#039;t use a SBZ/SBO (or a LDCR) on the ports, the ports are inputs.&lt;br /&gt;
&lt;br /&gt;
Nothing has happened up to now. But now imagine that you press the switch. This will put the 1 output to ground, causing the short circuit, and your 9901 is toast.&lt;br /&gt;
&lt;br /&gt;
=== What about the TI console? ===&lt;br /&gt;
&lt;br /&gt;
So can we damage the 9901 in the TI console by a suitable CRU operation? - Luckily not. &lt;br /&gt;
&lt;br /&gt;
The reason is the way how TI used the ports on the mainboard. Most of the ports are configured as outputs. Nothing happens when you reset them to inputs.&lt;br /&gt;
&lt;br /&gt;
* The pure interrupt inputs (/INT1 to /INT6) are safe, as they cannot output anything. They have no output driver that could be fried.&lt;br /&gt;
* P0 and P1 are not connected.&lt;br /&gt;
* P2, P3, and P4 are designed as outputs, so they cannot be turned from an input to an output.&lt;br /&gt;
* P5 is the selector of the Alpha Lock key, so output as well.&lt;br /&gt;
* P6 and P7 (/INT15) are the cassette motor control outputs.&lt;br /&gt;
* P8 (/INT14) is the audio gate output.&lt;br /&gt;
* P9 (/INT13) is the cassette output.&lt;br /&gt;
* P10 (/INT12) is pulled up to 5V via a 10 kOhm resistor. If you put a 0 on this port (5V), the maximum current is 500 µA, which results in a power dissipation of 2.5 mW (far below the maximum 750 mW).&lt;br /&gt;
* P11 (/INT11) is the cassette input. However, there is a resistor of 2.2 kOhm (R413) which effectively guards the port of high currents.&lt;br /&gt;
* P12, P13, P14, P15 (/INT10 ... /INT7) are the keyboard input lines. Again, we have pull-ups of 10 kOhm against +5V, but also 470 Ohm on the line, which means a maximum of 8.5 mA or 42.5 mW. This is still well below the maximum value.&lt;br /&gt;
&lt;br /&gt;
So we see that resistors are the best way to protect the ports. And we may calm down; we cannot burn the 9901 in the TI console by a bug in our program.&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50614</id>
		<title>Programmable Systems Interface TMS 9901</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50614"/>
		<updated>2022-04-23T13:39:05Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Input/Output port operation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The Programmable System Interface (PSI) is a circuit that is used on the TI-99/4A main board, on some expansion cards, and on the Geneve.&lt;br /&gt;
&lt;br /&gt;
The designation of the PSI is &amp;#039;&amp;#039;&amp;#039;TMS9901&amp;#039;&amp;#039;&amp;#039;, and this is the name that most users should be more familiar with.&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
&lt;br /&gt;
The TMS 9901 Programmable Systems Interface is a multi-purpose circuit that is used in the TI-99/4A and the Geneve computer, and also on some peripheral cards like the Corcomp Floppy Disk Controller.&lt;br /&gt;
&lt;br /&gt;
The 9901 has three main usage modes:&lt;br /&gt;
&lt;br /&gt;
* Input/Output ports&lt;br /&gt;
* Interrupt prioritizing&lt;br /&gt;
* Timer&lt;br /&gt;
&lt;br /&gt;
The I/O ports are mainly used for scanning the keyboard and joysticks, while the timer is used for cassette data encoding and decoding. The interrupt prioritizer is only used for the video interrupt which is routed from the VDP to the CPU through the 9901.&lt;br /&gt;
&lt;br /&gt;
== Package ==&lt;br /&gt;
&lt;br /&gt;
This is the pin assignment according to the 9901 specification.&lt;br /&gt;
&lt;br /&gt;
[[File:Pack9901.png|300px|left]]&lt;br /&gt;
&lt;br /&gt;
You can see pins of related functions marked by the same background color.&lt;br /&gt;
&lt;br /&gt;
* Orange: Interrupt inputs&lt;br /&gt;
* Red: CRU bit selection (00000 - 11111, i.e. 0-31 decimal)&lt;br /&gt;
* Blue: CRU interface&lt;br /&gt;
* Violet: I/O ports&lt;br /&gt;
* Yellow: Outgoing interrupt and level&lt;br /&gt;
&lt;br /&gt;
Note that some pins have a double function, or in other words, two functions share the same pin. I will discuss this in the following sections.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Input/Output port operation ==&lt;br /&gt;
&lt;br /&gt;
The simplest way of working with the 9901 is input/output operation with the ports. The 9901 has 16 configurable ports that can be individually used as inputs or outputs: P0 to P15.&lt;br /&gt;
&lt;br /&gt;
=== Px port as input ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
The power-on preset for every port is input. If you want to probe the P2 status (bit 18), use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 TB   18&lt;br /&gt;
 JEQ  P2ACT&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the given CRU address into the Equal bit of the status register. JEQ will jump to the location when this input has a high level. The maximum input current is 100 µA. You should protect the input with a resistor; see below for a more detailed discussion.&lt;br /&gt;
&lt;br /&gt;
If you want to read all 16 ports, use the STCR instruction:&lt;br /&gt;
&lt;br /&gt;
 LI   R12,32&lt;br /&gt;
 STCR R1,0&lt;br /&gt;
&lt;br /&gt;
Note that the CRU base address in R12 advances in steps of 2, so bit 16 is on address 32. Also, for reading 16 bits, you must specify a count of 0.&lt;br /&gt;
&lt;br /&gt;
The 9901 offers some more inputs than the 16 ports; we can use the interrupt inputs as well. More about that can be found in the section about the [[#interrupt_mode| interrupt mode]].&lt;br /&gt;
&lt;br /&gt;
=== Px port as output ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Once you write a value to the CRU address of a 9901 port, this port switches to output mode. The value written to the port is the value that is shown on its pin. If we want to output a 1 on P6, we have to use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
&lt;br /&gt;
If we wanted to output a 0, SBZ would have to be used. In order to set several ports in one go, use LDCR.&lt;br /&gt;
&lt;br /&gt;
We can also read the output port just by using TB as usual (or STCR for several ports at once). &lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
 TB   22&lt;br /&gt;
 JEQ  EXPECT&lt;br /&gt;
&lt;br /&gt;
The TB operation should return the 1 which the previous SBO has set.&lt;br /&gt;
&lt;br /&gt;
You &amp;#039;&amp;#039;&amp;#039;cannot&amp;#039;&amp;#039;&amp;#039; switch back to input mode. This is only possible by resetting the 9901, either using the /RST1 input line, or by setting the /RST2 bit (bit 15) to 0 in clock mode.&lt;br /&gt;
&lt;br /&gt;
== Interrupt mode ==&lt;br /&gt;
&lt;br /&gt;
In interrupt mode, bits 1 to 15 are used to set the interrupt mask (write), or to query the interrupt inputs (read). That is, the meaning of reading and writing the bits is indeed different.&lt;br /&gt;
&lt;br /&gt;
This schema shows how the interrupt inputs are used.&lt;br /&gt;
&lt;br /&gt;
[[File:Int9901.png|600px]]&lt;br /&gt;
&lt;br /&gt;
This illustration is slightly simplified; there is a latch between each interrupt input and the mask AND gate. This is not relevant for our programming, though. For more details see the &amp;#039;&amp;#039;TMS 9901 Programmable Systems Interface Data Manual&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
=== Checking the interrupt inputs ===&lt;br /&gt;
&lt;br /&gt;
In general, every interrupt input can be read via CRU access:&lt;br /&gt;
&lt;br /&gt;
  CLR R12&lt;br /&gt;
  TB  15&lt;br /&gt;
  JEQ NOINT15&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the specified CRU address to the Equal status bit. This means that JEQ jumps to the location when the bit is 1. Since all interrupt inputs are negative logic, reading a 1 means that there is no interrupt at this input (so I called it NOINT15).&lt;br /&gt;
&lt;br /&gt;
If we want to read several interrupt inputs by a single instruction, we have to use STCR.&lt;br /&gt;
&lt;br /&gt;
  LI   R12,16&lt;br /&gt;
  STCR R1,8&lt;br /&gt;
&lt;br /&gt;
This reads the states of the /INT8, /INT9, ..., /INT15 inputs and puts them in the most significant byte of R1. (See the specification of STCR and LDCR for transfers of up to 8 bits.)&lt;br /&gt;
&lt;br /&gt;
=== Interrupt levels ===&lt;br /&gt;
&lt;br /&gt;
Each interrupt input (/INT1 to /INT15) may be used to trigger an interrupt at another circuit, typically the CPU. For this purpose, the PSI offers an /INTREQ outgoing line. In the TI console, it is routed to the /INTREQ input of the TMS9900 CPU. &lt;br /&gt;
&lt;br /&gt;
In addition there is a priority encoder, which is not shown here. This encoder outputs a 4-bit number which reflects the smallest number of the interrupt inputs where an interrupt has been sensed.&lt;br /&gt;
&lt;br /&gt;
That is, if /INT4 and /INT7 are currently set to 0 (active), the priority encoder outputs (0100), which means 4.&lt;br /&gt;
&lt;br /&gt;
The TMS9900 CPU can differentiate between these interrupt levels, and by using its LIMI command, we can set a threshold where interrupts are ignored. So when we use a LIMI 3, the interrupts of level 4, 5, 6 and higher will be ignored. &lt;br /&gt;
&lt;br /&gt;
The bad news is that Texas Instruments did not use these priority lines in the TI console. In fact, these outputs (IC0 .. IC3) are not connected, and the respective interrupt level inputs at the CPU are hardwired to (0001), which means that every interrupt that occurs in the TI console is automatically set to level 1. So in order to block all interrupts from the 9901&lt;br /&gt;
&lt;br /&gt;
 LIMI 0 &lt;br /&gt;
&lt;br /&gt;
must be executed. To allow all, &lt;br /&gt;
&lt;br /&gt;
 LIMI 1 &lt;br /&gt;
&lt;br /&gt;
is sufficient. For some unclear reason, TI used LIMI 2 in all occasions.&lt;br /&gt;
&lt;br /&gt;
=== Arming and disarming interrupts ===&lt;br /&gt;
&lt;br /&gt;
As said above, every interrupt input can be read at any time in interrupt mode. In that sense, we don&amp;#039;t really treat these inputs as interrupts inputs, but simply as input ports.&lt;br /&gt;
&lt;br /&gt;
If we want a port to trigger an interrupt, we must arm this interrupt input. This is done by setting the respective bit to 1.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  2&lt;br /&gt;
&lt;br /&gt;
This enables interrupt input /INT2 to cause an /INTREQ when its input changes to 0. In the TI console, /INT2 is connected to the interrupt output of the VDP (video processor). If you want to block interrupts on that input, use SBZ.&lt;br /&gt;
&lt;br /&gt;
It is a common misunderstanding that interrupts can be cleared by using a SBZ on an input. This can, for example, be seen in the popular TI Intern book. Instead, SBZ n simply blocks interrupt input /INTn. In the case of the video processor, it is up to the VDP to clear its interrupt, and this is done by reading its status register.&lt;br /&gt;
&lt;br /&gt;
== Counter mode ==&lt;br /&gt;
&lt;br /&gt;
Besides the I/O capabilities, the PSI offers a &amp;quot;real-time&amp;quot; clock. This should not be understood as a precise timer with seconds, hundreds of seconds or other but as a countdown timer that is decremented every 64 CPU clock cycles.&lt;br /&gt;
&lt;br /&gt;
This schematic is taken from the Osborne book, redrawn by me in LibreOffice (and with added Load Buffer). &lt;br /&gt;
&lt;br /&gt;
[[File:Clock9901.png|550px]]&lt;br /&gt;
&lt;br /&gt;
=== Using the timer ===&lt;br /&gt;
&lt;br /&gt;
There are basically two ways of using the countdown timer:&lt;br /&gt;
&lt;br /&gt;
* Interval timer: Set the counter to a value; wait until the counter reaches zero. The PSI can be set up to raise an interrupt at that instant.&lt;br /&gt;
* Event timer: Set the counter to a value; do something and then read the counter value.&lt;br /&gt;
&lt;br /&gt;
The counter has 14 bits; that is, its maximum value is 0x3FFF (16383). The counter is decremented every 64 clock cycles, and for the TI-99/4A and the Geneve with a clock cycle time of 333ns, this means that the counter resolution is 21.333 µs. The whole 16384 steps take 0.3495 seconds (349.5 ms).&lt;br /&gt;
&lt;br /&gt;
When the counter reaches 0, it is reloaded from the load buffer, and counting continues.&lt;br /&gt;
&lt;br /&gt;
It is essential to understand that the counter &amp;#039;&amp;#039;&amp;#039;always&amp;#039;&amp;#039;&amp;#039; counts down, independent of the mode. The specification says that the clock is enabled when a non-zero value is loaded into the load buffer, but tests on a real Geneve show that the clock decrements even when the load buffer is filled with 0.&lt;br /&gt;
&lt;br /&gt;
=== Setting the timer ===&lt;br /&gt;
&lt;br /&gt;
In order to set the load buffer and so to define the maximum counter value, we must switch to clock mode. This is achieved by setting bit 0 of the PSI to 1. In the TI/Geneve systems, the PSI is mapped to CRU base address 0, so we can do this&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  0&lt;br /&gt;
&lt;br /&gt;
to enter &amp;#039;&amp;#039;&amp;#039;clock mode&amp;#039;&amp;#039;&amp;#039;. Using SBZ 0 returns to &amp;#039;&amp;#039;&amp;#039;interrupt mode&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
The effect of switching to clock mode is that the load buffer becomes accessible by the CRU addresses 1 to 14. Supposed that we want to load the buffer with the value 0x00FF, we may use these lines:&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  LI   R1,&amp;gt;00FF&lt;br /&gt;
  INCT R12&lt;br /&gt;
  LDCR R1,14&lt;br /&gt;
&lt;br /&gt;
As we see, we must change the CRU base. The LDCR operation always starts at the address defined in R12; if this is not 0, we have to set it here. Note, however, that the CRU address is always half of the R12 value, since the rightmost bit, A15, is not part of the address. If we want to start at bit 1, we must increase R12 by 2. This is, by the way, done wrong in several places in the literature where R12 is only set to 1.&lt;br /&gt;
&lt;br /&gt;
We can combine the SBO and the LDCR to one operation if we define the rightmost bit of R1 as the bit 0 and the following bits as the timer value.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  LI   R1,&amp;gt;01FF    (0000 0001 1111 1111)&lt;br /&gt;
  LDCR R1,15&lt;br /&gt;
&lt;br /&gt;
Do &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; write all 16 bits. The last bit will change bit 15 of the PSI, which is the /RST2 bit. When we are in clock mode, writing a 0 to the /RST2 bit resets all I/O ports. This means that all pins are set to inputs, and devices that rely on them as outputs may be turned off unintentionally.&lt;br /&gt;
&lt;br /&gt;
=== Reading the timer ===&lt;br /&gt;
&lt;br /&gt;
We are interested for the current value of the timer at some occasions. However, since the counter is constantly changing (every 64 clock cycles), reading from the active counter may deliver a wrong result. Think, for instance, about a value 0x2000, which is decremented after we read the first bits, we may end up with 0x2FFF (2 from 0x2000, FFF from 0x1FFF). &lt;br /&gt;
&lt;br /&gt;
For this reason, the timer value is copied to a &amp;#039;&amp;#039;&amp;#039;read buffer&amp;#039;&amp;#039;&amp;#039;. This happens at every 64th clock cycle, when the counter is also decremented, but only when we are &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; in clock mode. &lt;br /&gt;
&lt;br /&gt;
To read the buffer, we must set the PSI to clock mode first, which freezes the counter value in the read buffer. Then we can read the counter using STCR.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  STCR R1,15&lt;br /&gt;
  SRL  R1,1&lt;br /&gt;
  SBZ  0&lt;br /&gt;
&lt;br /&gt;
As we see, register 1 will contain bit 0 in its rightmost bit, so we shift R1 by one position to move it out.&lt;br /&gt;
&lt;br /&gt;
Do not forget to return to interrupt mode, or the buffer will not receive further updates.&lt;br /&gt;
&lt;br /&gt;
As mentioned, bit 15 is the /RST2 bit when we write to it. For reading, bit 15 reflects the /INTREQ output.&lt;br /&gt;
&lt;br /&gt;
=== The weird S0 input ===&lt;br /&gt;
&lt;br /&gt;
There is one peculiarity with the PSI. When the input line S0 is set to 1, the clock mode is &amp;#039;&amp;#039;&amp;#039;left temporarily&amp;#039;&amp;#039;&amp;#039; - until S0 is 0 again, and bit 0 is 1. If bit 0 is 0, this has no effect, as the clock mode is never entered.&lt;br /&gt;
&lt;br /&gt;
What is the reason for this? &lt;br /&gt;
&lt;br /&gt;
TI obviously considered use cases where the PSI remains in clock mode for longer times, while the I/O ports must still be operated. The I/O ports are available on bits 16-31, which means that the S0 input is 1 for these addresses. So when we do a SBO 16, this is not related to the timer value, and thus should control the first I/O port. To allow this, the chip returns to interrupt/port mode for this operation, and when S0 is reset, returns to clock mode.&lt;br /&gt;
&lt;br /&gt;
In the TI console, the S0-S4 inputs are directly connected with the address bus lines A10-A14. This has a strange side effect: Whenever address bus line A10 changes to 1, the clock mode is left temporarily, and this also happens for ordinary memory accesses.&lt;br /&gt;
&lt;br /&gt;
Suppose that we are in clock mode (SBO 0). In this mode, the read buffer should not be updated, so when we read it, we should get the same value every time, although the real counter value is different. But this is difficult to achieve: If our program uses a memory address where A10 is 1 for some instruction, loading this instruction into the CPU will set A10 to 1, and thus leave clock mode, which updates the counter.&lt;br /&gt;
&lt;br /&gt;
Therefore, you may have the impression that the read register continues to be updated even though you set bit 0 to 1. But this is only happening because your program extends over a location where A10 is 1. So if you want to prove that in clock mode the read register remains fixed, your program must avoid all memory locations where A10 is 1.&lt;br /&gt;
&lt;br /&gt;
== Shared pins ==&lt;br /&gt;
&lt;br /&gt;
Some pins have a double meaning; for example, pin 34 is labeled /INT7 / P15. How do we select the functions?&lt;br /&gt;
&lt;br /&gt;
The shared pins are those for the interrupt inputs /INT7 to /INT15; they may also be used for P7 to P15 (in the opposite order).&lt;br /&gt;
&lt;br /&gt;
If the Pn port is set to &amp;#039;&amp;#039;&amp;#039;input&amp;#039;&amp;#039;&amp;#039;, the situation looks like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
This means that both port and interrupt input have the same values; each one is a mirror of the other&amp;#039;s state. We notice it by reading the bits of the associated CRU addresses, getting the same results. There is a difference, though: The interrupt input can be armed to trigger an outgoing interrupt, while the I/O port cannot.&lt;br /&gt;
&lt;br /&gt;
In the other case, the Pn port is an &amp;#039;&amp;#039;&amp;#039;output&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Is that possible? - Sure. The interrupt input will reflect the output of the port. When we set the port to 1, the interrupt input will get that 1 (same for 0). If we arm the interrupt input, setting the output port to 0 will trigger an interrupt via this input. The 9901 specification document recommends to disarm the interrupt trigger for the shared ports because of this possibly unwanted effect.&lt;br /&gt;
&lt;br /&gt;
== Safety precautions ==&lt;br /&gt;
&lt;br /&gt;
The TMS9901 PSI is an interrupt encoder and an I/O circuit. You may have heard that improper use of I/O circuits may damage the circuit so that it must be replaced. Is this possible with the 9901 as well? Is there a way to kill the 9901?&lt;br /&gt;
&lt;br /&gt;
Short answer: Yes.&lt;br /&gt;
&lt;br /&gt;
Longer answer: Yes, but it depends on the way how the ports are used.&lt;br /&gt;
&lt;br /&gt;
If the port is used as an input, nothing bad will happen, since the ports have high input impedance. You can put 0V or 5V directly to the input; of course, you should not use excessive voltage.&lt;br /&gt;
&lt;br /&gt;
=== How to damage the 9901 ports ===&lt;br /&gt;
&lt;br /&gt;
The problem is when the port is configured as an output port. In that case it may output 0V (0) or 5V (1). When you connect the output to the opposite voltage, you will cause a short circuit which quickly exceeds the chip&amp;#039;s maximum power dissipation of 750 mW. This means you will fry the chip.&lt;br /&gt;
&lt;br /&gt;
Consider the following setup (but don&amp;#039;t try it!).&lt;br /&gt;
&lt;br /&gt;
[[File:Badport9901.png|300px]]&lt;br /&gt;
&lt;br /&gt;
Obviously, port P4 is assumed to be an input. When the switch is open, port P4 will sense a 1 on its input. When the switch is closed, it will sense a 0.&lt;br /&gt;
&lt;br /&gt;
Now what happens when you execute this code?&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  20&lt;br /&gt;
&lt;br /&gt;
This will program port P4 to be an output, and to show a 1 (5V) at the output. SBZ will, in contrast, make this port output a 0. So how do you make it an input port? - This is done by resetting the chip. As long as you don&amp;#039;t use a SBZ/SBO (or a LDCR) on the ports, the ports are inputs.&lt;br /&gt;
&lt;br /&gt;
Nothing has happened up to now. But now imagine that you press the switch. This will put the 1 output to ground, causing the short circuit, and your 9901 is toast.&lt;br /&gt;
&lt;br /&gt;
=== What about the TI console? ===&lt;br /&gt;
&lt;br /&gt;
So can we damage the 9901 in the TI console by a suitable CRU operation? - Luckily not. &lt;br /&gt;
&lt;br /&gt;
The reason is the way how TI used the ports on the mainboard. Most of the ports are configured as outputs. Nothing happens when you reset them to inputs.&lt;br /&gt;
&lt;br /&gt;
* The pure interrupt inputs (/INT1 to /INT6) are safe, as they cannot output anything. They have no output driver that could be fried.&lt;br /&gt;
* P0 and P1 are not connected.&lt;br /&gt;
* P2, P3, and P4 are designed as outputs, so they cannot be turned from an input to an output.&lt;br /&gt;
* P5 is the selector of the Alpha Lock key, so output as well.&lt;br /&gt;
* P6 and P7 (/INT15) are the cassette motor control outputs.&lt;br /&gt;
* P8 (/INT14) is the audio gate output.&lt;br /&gt;
* P9 (/INT13) is the cassette output.&lt;br /&gt;
* P10 (/INT12) is pulled up to 5V via a 10 kOhm resistor. If you put a 0 on this port (5V), the maximum current is 500 µA, which results in a power dissipation of 2.5 mW (far below the maximum 750 mW).&lt;br /&gt;
* P11 (/INT11) is the cassette input. However, there is a resistor of 2.2 kOhm (R413) which effectively guards the port of high currents.&lt;br /&gt;
* P12, P13, P14, P15 (/INT10 ... /INT7) are the keyboard input lines. Again, we have pull-ups of 10 kOhm against +5V, but also 470 Ohm on the line, which means a maximum of 8.5 mA or 42.5 mW. This is still well below the maximum value.&lt;br /&gt;
&lt;br /&gt;
So we see that resistors are the best way to protect the ports. And we may calm down; we cannot burn the 9901 in the TI console by a bug in our program.&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50613</id>
		<title>Programmable Systems Interface TMS 9901</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50613"/>
		<updated>2022-04-23T13:30:24Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The Programmable System Interface (PSI) is a circuit that is used on the TI-99/4A main board, on some expansion cards, and on the Geneve.&lt;br /&gt;
&lt;br /&gt;
The designation of the PSI is &amp;#039;&amp;#039;&amp;#039;TMS9901&amp;#039;&amp;#039;&amp;#039;, and this is the name that most users should be more familiar with.&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
&lt;br /&gt;
The TMS 9901 Programmable Systems Interface is a multi-purpose circuit that is used in the TI-99/4A and the Geneve computer, and also on some peripheral cards like the Corcomp Floppy Disk Controller.&lt;br /&gt;
&lt;br /&gt;
The 9901 has three main usage modes:&lt;br /&gt;
&lt;br /&gt;
* Input/Output ports&lt;br /&gt;
* Interrupt prioritizing&lt;br /&gt;
* Timer&lt;br /&gt;
&lt;br /&gt;
The I/O ports are mainly used for scanning the keyboard and joysticks, while the timer is used for cassette data encoding and decoding. The interrupt prioritizer is only used for the video interrupt which is routed from the VDP to the CPU through the 9901.&lt;br /&gt;
&lt;br /&gt;
== Package ==&lt;br /&gt;
&lt;br /&gt;
This is the pin assignment according to the 9901 specification.&lt;br /&gt;
&lt;br /&gt;
[[File:Pack9901.png|300px|left]]&lt;br /&gt;
&lt;br /&gt;
You can see pins of related functions marked by the same background color.&lt;br /&gt;
&lt;br /&gt;
* Orange: Interrupt inputs&lt;br /&gt;
* Red: CRU bit selection (00000 - 11111, i.e. 0-31 decimal)&lt;br /&gt;
* Blue: CRU interface&lt;br /&gt;
* Violet: I/O ports&lt;br /&gt;
* Yellow: Outgoing interrupt and level&lt;br /&gt;
&lt;br /&gt;
Note that some pins have a double function, or in other words, two functions share the same pin. I will discuss this in the following sections.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Input/Output port operation ==&lt;br /&gt;
&lt;br /&gt;
The simplest way of working with the 9901 is input/output operation with the ports. The 9901 has 16 configurable ports that can be individually used as inputs or outputs: P0 to P15.&lt;br /&gt;
&lt;br /&gt;
=== Px port as input ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
The power-on preset for every port is input. If you want to probe the P2 status (bit 18), use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 TB   18&lt;br /&gt;
 JEQ  P2ACT&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the given CRU address into the Equal bit of the status register. JEQ will jump to the location when this input has a high level. The maximum input current is 100 µA. You should protect the input with a resistor; see below for a more detailed discussion.&lt;br /&gt;
&lt;br /&gt;
If you want to read all 16 ports, use the STCR instruction:&lt;br /&gt;
&lt;br /&gt;
 LI   R12,32&lt;br /&gt;
 STCR R1,0&lt;br /&gt;
&lt;br /&gt;
Note that the CRU base address in R12 advances in steps of 2, so bit 16 is on address 32. Also, for reading 16 bits, you must specify a count of 0.&lt;br /&gt;
&lt;br /&gt;
The 9901 offers some more inputs than the 16 ports; we can use the interrupt inputs as well. See below for a discussion of the interrupt mode.&lt;br /&gt;
&lt;br /&gt;
=== Px port as output ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Once you write a value to the CRU address of a 9901 port, this port switches to output mode. The value written to the port is the value that is shown on its pin. If we want to output a 1 on P6, we have to use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
&lt;br /&gt;
If we wanted to output a 0, SBZ would have to be used. In order to set several ports in one go, use LDCR.&lt;br /&gt;
&lt;br /&gt;
We can also read the output port just by using TB as usual (or STCR for several ports at once). &lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
 TB   22&lt;br /&gt;
 JEQ  EXPECT&lt;br /&gt;
&lt;br /&gt;
The TB operation should return the 1 which the previous SBO has set.&lt;br /&gt;
&lt;br /&gt;
You &amp;#039;&amp;#039;&amp;#039;cannot&amp;#039;&amp;#039;&amp;#039; switch back to input mode. This is only possible by resetting the 9901, either using the /RST1 input line, or by setting the /RST2 bit (bit 15) to 0 in clock mode.&lt;br /&gt;
&lt;br /&gt;
== Interrupt mode ==&lt;br /&gt;
&lt;br /&gt;
In interrupt mode, bits 1 to 15 are used to set the interrupt mask (write), or to query the interrupt inputs (read). That is, the meaning of reading and writing the bits is indeed different.&lt;br /&gt;
&lt;br /&gt;
This schema shows how the interrupt inputs are used.&lt;br /&gt;
&lt;br /&gt;
[[File:Int9901.png|600px]]&lt;br /&gt;
&lt;br /&gt;
This illustration is slightly simplified; there is a latch between each interrupt input and the mask AND gate. This is not relevant for our programming, though. For more details see the &amp;#039;&amp;#039;TMS 9901 Programmable Systems Interface Data Manual&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
=== Checking the interrupt inputs ===&lt;br /&gt;
&lt;br /&gt;
In general, every interrupt input can be read via CRU access:&lt;br /&gt;
&lt;br /&gt;
  CLR R12&lt;br /&gt;
  TB  15&lt;br /&gt;
  JEQ NOINT15&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the specified CRU address to the Equal status bit. This means that JEQ jumps to the location when the bit is 1. Since all interrupt inputs are negative logic, reading a 1 means that there is no interrupt at this input (so I called it NOINT15).&lt;br /&gt;
&lt;br /&gt;
If we want to read several interrupt inputs by a single instruction, we have to use STCR.&lt;br /&gt;
&lt;br /&gt;
  LI   R12,16&lt;br /&gt;
  STCR R1,8&lt;br /&gt;
&lt;br /&gt;
This reads the states of the /INT8, /INT9, ..., /INT15 inputs and puts them in the most significant byte of R1. (See the specification of STCR and LDCR for transfers of up to 8 bits.)&lt;br /&gt;
&lt;br /&gt;
=== Interrupt levels ===&lt;br /&gt;
&lt;br /&gt;
Each interrupt input (/INT1 to /INT15) may be used to trigger an interrupt at another circuit, typically the CPU. For this purpose, the PSI offers an /INTREQ outgoing line. In the TI console, it is routed to the /INTREQ input of the TMS9900 CPU. &lt;br /&gt;
&lt;br /&gt;
In addition there is a priority encoder, which is not shown here. This encoder outputs a 4-bit number which reflects the smallest number of the interrupt inputs where an interrupt has been sensed.&lt;br /&gt;
&lt;br /&gt;
That is, if /INT4 and /INT7 are currently set to 0 (active), the priority encoder outputs (0100), which means 4.&lt;br /&gt;
&lt;br /&gt;
The TMS9900 CPU can differentiate between these interrupt levels, and by using its LIMI command, we can set a threshold where interrupts are ignored. So when we use a LIMI 3, the interrupts of level 4, 5, 6 and higher will be ignored. &lt;br /&gt;
&lt;br /&gt;
The bad news is that Texas Instruments did not use these priority lines in the TI console. In fact, these outputs (IC0 .. IC3) are not connected, and the respective interrupt level inputs at the CPU are hardwired to (0001), which means that every interrupt that occurs in the TI console is automatically set to level 1. So in order to block all interrupts from the 9901&lt;br /&gt;
&lt;br /&gt;
 LIMI 0 &lt;br /&gt;
&lt;br /&gt;
must be executed. To allow all, &lt;br /&gt;
&lt;br /&gt;
 LIMI 1 &lt;br /&gt;
&lt;br /&gt;
is sufficient. For some unclear reason, TI used LIMI 2 in all occasions.&lt;br /&gt;
&lt;br /&gt;
=== Arming and disarming interrupts ===&lt;br /&gt;
&lt;br /&gt;
As said above, every interrupt input can be read at any time in interrupt mode. In that sense, we don&amp;#039;t really treat these inputs as interrupts inputs, but simply as input ports.&lt;br /&gt;
&lt;br /&gt;
If we want a port to trigger an interrupt, we must arm this interrupt input. This is done by setting the respective bit to 1.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  2&lt;br /&gt;
&lt;br /&gt;
This enables interrupt input /INT2 to cause an /INTREQ when its input changes to 0. In the TI console, /INT2 is connected to the interrupt output of the VDP (video processor). If you want to block interrupts on that input, use SBZ.&lt;br /&gt;
&lt;br /&gt;
It is a common misunderstanding that interrupts can be cleared by using a SBZ on an input. This can, for example, be seen in the popular TI Intern book. Instead, SBZ n simply blocks interrupt input /INTn. In the case of the video processor, it is up to the VDP to clear its interrupt, and this is done by reading its status register.&lt;br /&gt;
&lt;br /&gt;
== Counter mode ==&lt;br /&gt;
&lt;br /&gt;
Besides the I/O capabilities, the PSI offers a &amp;quot;real-time&amp;quot; clock. This should not be understood as a precise timer with seconds, hundreds of seconds or other but as a countdown timer that is decremented every 64 CPU clock cycles.&lt;br /&gt;
&lt;br /&gt;
This schematic is taken from the Osborne book, redrawn by me in LibreOffice (and with added Load Buffer). &lt;br /&gt;
&lt;br /&gt;
[[File:Clock9901.png|550px]]&lt;br /&gt;
&lt;br /&gt;
=== Using the timer ===&lt;br /&gt;
&lt;br /&gt;
There are basically two ways of using the countdown timer:&lt;br /&gt;
&lt;br /&gt;
* Interval timer: Set the counter to a value; wait until the counter reaches zero. The PSI can be set up to raise an interrupt at that instant.&lt;br /&gt;
* Event timer: Set the counter to a value; do something and then read the counter value.&lt;br /&gt;
&lt;br /&gt;
The counter has 14 bits; that is, its maximum value is 0x3FFF (16383). The counter is decremented every 64 clock cycles, and for the TI-99/4A and the Geneve with a clock cycle time of 333ns, this means that the counter resolution is 21.333 µs. The whole 16384 steps take 0.3495 seconds (349.5 ms).&lt;br /&gt;
&lt;br /&gt;
When the counter reaches 0, it is reloaded from the load buffer, and counting continues.&lt;br /&gt;
&lt;br /&gt;
It is essential to understand that the counter &amp;#039;&amp;#039;&amp;#039;always&amp;#039;&amp;#039;&amp;#039; counts down, independent of the mode. The specification says that the clock is enabled when a non-zero value is loaded into the load buffer, but tests on a real Geneve show that the clock decrements even when the load buffer is filled with 0.&lt;br /&gt;
&lt;br /&gt;
=== Setting the timer ===&lt;br /&gt;
&lt;br /&gt;
In order to set the load buffer and so to define the maximum counter value, we must switch to clock mode. This is achieved by setting bit 0 of the PSI to 1. In the TI/Geneve systems, the PSI is mapped to CRU base address 0, so we can do this&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  0&lt;br /&gt;
&lt;br /&gt;
to enter &amp;#039;&amp;#039;&amp;#039;clock mode&amp;#039;&amp;#039;&amp;#039;. Using SBZ 0 returns to &amp;#039;&amp;#039;&amp;#039;interrupt mode&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
The effect of switching to clock mode is that the load buffer becomes accessible by the CRU addresses 1 to 14. Supposed that we want to load the buffer with the value 0x00FF, we may use these lines:&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  LI   R1,&amp;gt;00FF&lt;br /&gt;
  INCT R12&lt;br /&gt;
  LDCR R1,14&lt;br /&gt;
&lt;br /&gt;
As we see, we must change the CRU base. The LDCR operation always starts at the address defined in R12; if this is not 0, we have to set it here. Note, however, that the CRU address is always half of the R12 value, since the rightmost bit, A15, is not part of the address. If we want to start at bit 1, we must increase R12 by 2. This is, by the way, done wrong in several places in the literature where R12 is only set to 1.&lt;br /&gt;
&lt;br /&gt;
We can combine the SBO and the LDCR to one operation if we define the rightmost bit of R1 as the bit 0 and the following bits as the timer value.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  LI   R1,&amp;gt;01FF    (0000 0001 1111 1111)&lt;br /&gt;
  LDCR R1,15&lt;br /&gt;
&lt;br /&gt;
Do &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; write all 16 bits. The last bit will change bit 15 of the PSI, which is the /RST2 bit. When we are in clock mode, writing a 0 to the /RST2 bit resets all I/O ports. This means that all pins are set to inputs, and devices that rely on them as outputs may be turned off unintentionally.&lt;br /&gt;
&lt;br /&gt;
=== Reading the timer ===&lt;br /&gt;
&lt;br /&gt;
We are interested for the current value of the timer at some occasions. However, since the counter is constantly changing (every 64 clock cycles), reading from the active counter may deliver a wrong result. Think, for instance, about a value 0x2000, which is decremented after we read the first bits, we may end up with 0x2FFF (2 from 0x2000, FFF from 0x1FFF). &lt;br /&gt;
&lt;br /&gt;
For this reason, the timer value is copied to a &amp;#039;&amp;#039;&amp;#039;read buffer&amp;#039;&amp;#039;&amp;#039;. This happens at every 64th clock cycle, when the counter is also decremented, but only when we are &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; in clock mode. &lt;br /&gt;
&lt;br /&gt;
To read the buffer, we must set the PSI to clock mode first, which freezes the counter value in the read buffer. Then we can read the counter using STCR.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  STCR R1,15&lt;br /&gt;
  SRL  R1,1&lt;br /&gt;
  SBZ  0&lt;br /&gt;
&lt;br /&gt;
As we see, register 1 will contain bit 0 in its rightmost bit, so we shift R1 by one position to move it out.&lt;br /&gt;
&lt;br /&gt;
Do not forget to return to interrupt mode, or the buffer will not receive further updates.&lt;br /&gt;
&lt;br /&gt;
As mentioned, bit 15 is the /RST2 bit when we write to it. For reading, bit 15 reflects the /INTREQ output.&lt;br /&gt;
&lt;br /&gt;
=== The weird S0 input ===&lt;br /&gt;
&lt;br /&gt;
There is one peculiarity with the PSI. When the input line S0 is set to 1, the clock mode is &amp;#039;&amp;#039;&amp;#039;left temporarily&amp;#039;&amp;#039;&amp;#039; - until S0 is 0 again, and bit 0 is 1. If bit 0 is 0, this has no effect, as the clock mode is never entered.&lt;br /&gt;
&lt;br /&gt;
What is the reason for this? &lt;br /&gt;
&lt;br /&gt;
TI obviously considered use cases where the PSI remains in clock mode for longer times, while the I/O ports must still be operated. The I/O ports are available on bits 16-31, which means that the S0 input is 1 for these addresses. So when we do a SBO 16, this is not related to the timer value, and thus should control the first I/O port. To allow this, the chip returns to interrupt/port mode for this operation, and when S0 is reset, returns to clock mode.&lt;br /&gt;
&lt;br /&gt;
In the TI console, the S0-S4 inputs are directly connected with the address bus lines A10-A14. This has a strange side effect: Whenever address bus line A10 changes to 1, the clock mode is left temporarily, and this also happens for ordinary memory accesses.&lt;br /&gt;
&lt;br /&gt;
Suppose that we are in clock mode (SBO 0). In this mode, the read buffer should not be updated, so when we read it, we should get the same value every time, although the real counter value is different. But this is difficult to achieve: If our program uses a memory address where A10 is 1 for some instruction, loading this instruction into the CPU will set A10 to 1, and thus leave clock mode, which updates the counter.&lt;br /&gt;
&lt;br /&gt;
Therefore, you may have the impression that the read register continues to be updated even though you set bit 0 to 1. But this is only happening because your program extends over a location where A10 is 1. So if you want to prove that in clock mode the read register remains fixed, your program must avoid all memory locations where A10 is 1.&lt;br /&gt;
&lt;br /&gt;
== Shared pins ==&lt;br /&gt;
&lt;br /&gt;
Some pins have a double meaning; for example, pin 34 is labeled /INT7 / P15. How do we select the functions?&lt;br /&gt;
&lt;br /&gt;
The shared pins are those for the interrupt inputs /INT7 to /INT15; they may also be used for P7 to P15 (in the opposite order).&lt;br /&gt;
&lt;br /&gt;
If the Pn port is set to &amp;#039;&amp;#039;&amp;#039;input&amp;#039;&amp;#039;&amp;#039;, the situation looks like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
This means that both port and interrupt input have the same values; each one is a mirror of the other&amp;#039;s state. We notice it by reading the bits of the associated CRU addresses, getting the same results. There is a difference, though: The interrupt input can be armed to trigger an outgoing interrupt, while the I/O port cannot.&lt;br /&gt;
&lt;br /&gt;
In the other case, the Pn port is an &amp;#039;&amp;#039;&amp;#039;output&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Is that possible? - Sure. The interrupt input will reflect the output of the port. When we set the port to 1, the interrupt input will get that 1 (same for 0). If we arm the interrupt input, setting the output port to 0 will trigger an interrupt via this input. The 9901 specification document recommends to disarm the interrupt trigger for the shared ports because of this possibly unwanted effect.&lt;br /&gt;
&lt;br /&gt;
== Safety precautions ==&lt;br /&gt;
&lt;br /&gt;
The TMS9901 PSI is an interrupt encoder and an I/O circuit. You may have heard that improper use of I/O circuits may damage the circuit so that it must be replaced. Is this possible with the 9901 as well? Is there a way to kill the 9901?&lt;br /&gt;
&lt;br /&gt;
Short answer: Yes.&lt;br /&gt;
&lt;br /&gt;
Longer answer: Yes, but it depends on the way how the ports are used.&lt;br /&gt;
&lt;br /&gt;
If the port is used as an input, nothing bad will happen, since the ports have high input impedance. You can put 0V or 5V directly to the input; of course, you should not use excessive voltage.&lt;br /&gt;
&lt;br /&gt;
=== How to damage the 9901 ports ===&lt;br /&gt;
&lt;br /&gt;
The problem is when the port is configured as an output port. In that case it may output 0V (0) or 5V (1). When you connect the output to the opposite voltage, you will cause a short circuit which quickly exceeds the chip&amp;#039;s maximum power dissipation of 750 mW. This means you will fry the chip.&lt;br /&gt;
&lt;br /&gt;
Consider the following setup (but don&amp;#039;t try it!).&lt;br /&gt;
&lt;br /&gt;
[[File:Badport9901.png|300px]]&lt;br /&gt;
&lt;br /&gt;
Obviously, port P4 is assumed to be an input. When the switch is open, port P4 will sense a 1 on its input. When the switch is closed, it will sense a 0.&lt;br /&gt;
&lt;br /&gt;
Now what happens when you execute this code?&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  20&lt;br /&gt;
&lt;br /&gt;
This will program port P4 to be an output, and to show a 1 (5V) at the output. SBZ will, in contrast, make this port output a 0. So how do you make it an input port? - This is done by resetting the chip. As long as you don&amp;#039;t use a SBZ/SBO (or a LDCR) on the ports, the ports are inputs.&lt;br /&gt;
&lt;br /&gt;
Nothing has happened up to now. But now imagine that you press the switch. This will put the 1 output to ground, causing the short circuit, and your 9901 is toast.&lt;br /&gt;
&lt;br /&gt;
=== What about the TI console? ===&lt;br /&gt;
&lt;br /&gt;
So can we damage the 9901 in the TI console by a suitable CRU operation? - Luckily not. &lt;br /&gt;
&lt;br /&gt;
The reason is the way how TI used the ports on the mainboard. Most of the ports are configured as outputs. Nothing happens when you reset them to inputs.&lt;br /&gt;
&lt;br /&gt;
* The pure interrupt inputs (/INT1 to /INT6) are safe, as they cannot output anything. They have no output driver that could be fried.&lt;br /&gt;
* P0 and P1 are not connected.&lt;br /&gt;
* P2, P3, and P4 are designed as outputs, so they cannot be turned from an input to an output.&lt;br /&gt;
* P5 is the selector of the Alpha Lock key, so output as well.&lt;br /&gt;
* P6 and P7 (/INT15) are the cassette motor control outputs.&lt;br /&gt;
* P8 (/INT14) is the audio gate output.&lt;br /&gt;
* P9 (/INT13) is the cassette output.&lt;br /&gt;
* P10 (/INT12) is pulled up to 5V via a 10 kOhm resistor. If you put a 0 on this port (5V), the maximum current is 500 µA, which results in a power dissipation of 2.5 mW (far below the maximum 750 mW).&lt;br /&gt;
* P11 (/INT11) is the cassette input. However, there is a resistor of 2.2 kOhm (R413) which effectively guards the port of high currents.&lt;br /&gt;
* P12, P13, P14, P15 (/INT10 ... /INT7) are the keyboard input lines. Again, we have pull-ups of 10 kOhm against +5V, but also 470 Ohm on the line, which means a maximum of 8.5 mA or 42.5 mW. This is still well below the maximum value.&lt;br /&gt;
&lt;br /&gt;
So we see that resistors are the best way to protect the ports. And we may calm down; we cannot burn the 9901 in the TI console by a bug in our program.&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50612</id>
		<title>Programmable Systems Interface TMS 9901</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50612"/>
		<updated>2022-04-23T12:37:52Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Input/Output port operation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The Programmable System Interface (PSI) is a circuit that is used on the TI-99/4A main board, on some expansion cards, and on the Geneve.&lt;br /&gt;
&lt;br /&gt;
The designation of the PSI is &amp;#039;&amp;#039;&amp;#039;TMS9901&amp;#039;&amp;#039;&amp;#039;, and this is the name that most users will be more familiar with.&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
&lt;br /&gt;
The TMS 9901 Programmable Systems Interface is a multi-purpose circuit that is used in the TI-99/4A and the Geneve computer, and also on some peripheral cards like the Corcomp Floppy Disk Controller.&lt;br /&gt;
&lt;br /&gt;
The 9901 has three main usage modes:&lt;br /&gt;
&lt;br /&gt;
* Input/Output ports&lt;br /&gt;
* Interrupt prioritizing&lt;br /&gt;
* Timer&lt;br /&gt;
&lt;br /&gt;
The I/O ports are mainly used for scanning the keyboard and joysticks, while the timer is used for cassette data encoding and decoding. The interrupt prioritizer is only used for the video interrupt which is routed from the VDP to the CPU through the 9901.&lt;br /&gt;
&lt;br /&gt;
== Package ==&lt;br /&gt;
&lt;br /&gt;
This is the pin assignment according to the 9901 specification.&lt;br /&gt;
&lt;br /&gt;
[[File:Pack9901.png|300px|left]]&lt;br /&gt;
&lt;br /&gt;
You can see pins of related functions marked by the same background color.&lt;br /&gt;
&lt;br /&gt;
* Orange: Interrupt inputs&lt;br /&gt;
* Red: CRU bit selection (00000 - 11111, i.e. 0-31 decimal)&lt;br /&gt;
* Blue: CRU interface&lt;br /&gt;
* Violet: I/O ports&lt;br /&gt;
* Yellow: Outgoing interrupt and level&lt;br /&gt;
&lt;br /&gt;
Note that some pins have a double function, or in other words, two functions share the same pin. I will discuss this in the following sections.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Input/Output port operation ==&lt;br /&gt;
&lt;br /&gt;
The simplest way of working with the 9901 is input/output operation with the ports. The 9901 has 16 configurable ports that can be individually used as inputs or outputs: P0 to P15.&lt;br /&gt;
&lt;br /&gt;
=== Px port as input ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
The power-on preset for every port is input. If you want to probe the P2 status (bit 18), use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 TB   18&lt;br /&gt;
 JEQ  P2ACT&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the given CRU address into the Equal bit of the status register. JEQ will jump to the location when this input has a high level. The maximum input current is 100 µA. You should protect the input with a resistor; see below for a more detailed discussion.&lt;br /&gt;
&lt;br /&gt;
If you want to read all 16 ports, use the STCR instruction:&lt;br /&gt;
&lt;br /&gt;
 LI   R12,32&lt;br /&gt;
 STCR R1,0&lt;br /&gt;
&lt;br /&gt;
Note that the CRU base address in R12 advances in steps of 2, so bit 16 is on address 32. Also, for reading 16 bits, you must specify a count of 0.&lt;br /&gt;
&lt;br /&gt;
The 9901 offers some more inputs than the 16 ports; we can use the interrupt inputs as well. See below for a discussion of the interrupt mode.&lt;br /&gt;
&lt;br /&gt;
=== Px port as output ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Once you write a value to the CRU address of a 9901 port, this port switches to output mode. The value written to the port is the value that is shown on its pin. If we want to output a 1 on P6, we have to use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
&lt;br /&gt;
If we wanted to output a 0, SBZ would have to be used. In order to set several ports in one go, use LDCR.&lt;br /&gt;
&lt;br /&gt;
We can also read the output port just by using TB as usual (or STCR for several ports at once). &lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
 TB   22&lt;br /&gt;
 JEQ  EXPECT&lt;br /&gt;
&lt;br /&gt;
The TB operation should return the 1 which the previous SBO has set.&lt;br /&gt;
&lt;br /&gt;
You &amp;#039;&amp;#039;&amp;#039;cannot&amp;#039;&amp;#039;&amp;#039; switch back to input mode. This is only possible by resetting the 9901, either using the /RST1 input line, or by setting the /RST2 bit (bit 15) to 0 in clock mode.&lt;br /&gt;
&lt;br /&gt;
== Interrupt mode ==&lt;br /&gt;
&lt;br /&gt;
In interrupt mode, bits 1 to 15 are used to set the interrupt mask (write), or to query the interrupt inputs (read). That is, the meaning of reading and writing the bits is indeed different.&lt;br /&gt;
&lt;br /&gt;
This schema shows how the interrupt inputs are used.&lt;br /&gt;
&lt;br /&gt;
[[File:Int9901.png|600px]]&lt;br /&gt;
&lt;br /&gt;
This illustration is slightly simplified; there is a latch between each interrupt input and the mask AND gate. This is not relevant for our programming, though. For more details see the &amp;#039;&amp;#039;TMS 9901 Programmable Systems Interface Data Manual&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
=== Checking the interrupt inputs ===&lt;br /&gt;
&lt;br /&gt;
In general, every interrupt input can be read via CRU access:&lt;br /&gt;
&lt;br /&gt;
  CLR R12&lt;br /&gt;
  TB  15&lt;br /&gt;
  JEQ NOINT15&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the specified CRU address to the Equal status bit. This means that JEQ jumps to the location when the bit is 1. Since all interrupt inputs are negative logic, reading a 1 means that there is no interrupt at this input (so I called it NOINT15).&lt;br /&gt;
&lt;br /&gt;
If we want to read several interrupt inputs by a single instruction, we have to use STCR.&lt;br /&gt;
&lt;br /&gt;
  LI   R12,16&lt;br /&gt;
  STCR R1,8&lt;br /&gt;
&lt;br /&gt;
This reads the states of the /INT8, /INT9, ..., /INT15 inputs and puts them in the most significant byte of R1. (See the specification of STCR and LDCR for transfers of up to 8 bits.)&lt;br /&gt;
&lt;br /&gt;
=== Interrupt levels ===&lt;br /&gt;
&lt;br /&gt;
Each interrupt input (/INT1 to /INT15) may be used to trigger an interrupt at another circuit, typically the CPU. For this purpose, the PSI offers an /INTREQ outgoing line. In the TI console, it is routed to the /INTREQ input of the TMS9900 CPU. &lt;br /&gt;
&lt;br /&gt;
In addition there is a priority encoder, which is not shown here. This encoder outputs a 4-bit number which reflects the smallest number of the interrupt inputs where an interrupt has been sensed.&lt;br /&gt;
&lt;br /&gt;
That is, if /INT4 and /INT7 are currently set to 0 (active), the priority encoder outputs (0100), which means 4.&lt;br /&gt;
&lt;br /&gt;
The TMS9900 CPU can differentiate between these interrupt levels, and by using its LIMI command, we can set a threshold where interrupts are ignored. So when we use a LIMI 3, the interrupts of level 4, 5, 6 and higher will be ignored. &lt;br /&gt;
&lt;br /&gt;
The bad news is that Texas Instruments did not use these priority lines in the TI console. In fact, these outputs (IC0 .. IC3) are not connected, and the respective interrupt level inputs at the CPU are hardwired to (0001), which means that every interrupt that occurs in the TI console is automatically set to level 1. So in order to block all interrupts from the 9901&lt;br /&gt;
&lt;br /&gt;
 LIMI 0 &lt;br /&gt;
&lt;br /&gt;
must be executed. To allow all, &lt;br /&gt;
&lt;br /&gt;
 LIMI 1 &lt;br /&gt;
&lt;br /&gt;
is sufficient. For some unclear reason, TI used LIMI 2 in all occasions.&lt;br /&gt;
&lt;br /&gt;
=== Arming and disarming interrupts ===&lt;br /&gt;
&lt;br /&gt;
As said above, every interrupt input can be read at any time in interrupt mode. In that sense, we don&amp;#039;t really treat these inputs as interrupts inputs, but simply as input ports.&lt;br /&gt;
&lt;br /&gt;
If we want a port to trigger an interrupt, we must arm this interrupt input. This is done by setting the respective bit to 1.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  2&lt;br /&gt;
&lt;br /&gt;
This enables interrupt input /INT2 to cause an /INTREQ when its input changes to 0. In the TI console, /INT2 is connected to the interrupt output of the VDP (video processor). If you want to block interrupts on that input, use SBZ.&lt;br /&gt;
&lt;br /&gt;
It is a common misunderstanding that interrupts can be cleared by using a SBZ on an input. This can, for example, be seen in the popular TI Intern book. Instead, SBZ n simply blocks interrupt input /INTn. In the case of the video processor, it is up to the VDP to clear its interrupt, and this is done by reading its status register.&lt;br /&gt;
&lt;br /&gt;
== Counter mode ==&lt;br /&gt;
&lt;br /&gt;
Besides the I/O capabilities, the PSI offers a &amp;quot;real-time&amp;quot; clock. This should not be understood as a precise timer with seconds, hundreds of seconds or other but as a countdown timer that is decremented every 64 CPU clock cycles.&lt;br /&gt;
&lt;br /&gt;
This schematic is taken from the Osborne book, redrawn by me in LibreOffice (and with added Load Buffer). &lt;br /&gt;
&lt;br /&gt;
[[File:Clock9901.png|550px]]&lt;br /&gt;
&lt;br /&gt;
=== Using the timer ===&lt;br /&gt;
&lt;br /&gt;
There are basically two ways of using the countdown timer:&lt;br /&gt;
&lt;br /&gt;
* Interval timer: Set the counter to a value; wait until the counter reaches zero. The PSI can be set up to raise an interrupt at that instant.&lt;br /&gt;
* Event timer: Set the counter to a value; do something and then read the counter value.&lt;br /&gt;
&lt;br /&gt;
The counter has 14 bits; that is, its maximum value is 0x3FFF (16383). The counter is decremented every 64 clock cycles, and for the TI-99/4A and the Geneve with a clock cycle time of 333ns, this means that the counter resolution is 21.333 µs. The whole 16384 steps take 0.3495 seconds (349.5 ms).&lt;br /&gt;
&lt;br /&gt;
When the counter reaches 0, it is reloaded from the load buffer, and counting continues.&lt;br /&gt;
&lt;br /&gt;
It is essential to understand that the counter &amp;#039;&amp;#039;&amp;#039;always&amp;#039;&amp;#039;&amp;#039; counts down, independent of the mode. The specification says that the clock is enabled when a non-zero value is loaded into the load buffer, but tests on a real Geneve show that the clock decrements even when the load buffer is filled with 0.&lt;br /&gt;
&lt;br /&gt;
=== Setting the timer ===&lt;br /&gt;
&lt;br /&gt;
In order to set the load buffer and so to define the maximum counter value, we must switch to clock mode. This is achieved by setting bit 0 of the PSI to 1. In the TI/Geneve systems, the PSI is mapped to CRU base address 0, so we can do this&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  0&lt;br /&gt;
&lt;br /&gt;
to enter &amp;#039;&amp;#039;&amp;#039;clock mode&amp;#039;&amp;#039;&amp;#039;. Using SBZ 0 returns to &amp;#039;&amp;#039;&amp;#039;interrupt mode&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
The effect of switching to clock mode is that the load buffer becomes accessible by the CRU addresses 1 to 14. Supposed that we want to load the buffer with the value 0x00FF, we may use these lines:&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  LI   R1,&amp;gt;00FF&lt;br /&gt;
  INCT R12&lt;br /&gt;
  LDCR R1,14&lt;br /&gt;
&lt;br /&gt;
As we see, we must change the CRU base. The LDCR operation always starts at the address defined in R12; if this is not 0, we have to set it here. Note, however, that the CRU address is always half of the R12 value, since the rightmost bit, A15, is not part of the address. If we want to start at bit 1, we must increase R12 by 2. This is, by the way, done wrong in several places in the literature where R12 is only set to 1.&lt;br /&gt;
&lt;br /&gt;
We can combine the SBO and the LDCR to one operation if we define the rightmost bit of R1 as the bit 0 and the following bits as the timer value.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  LI   R1,&amp;gt;01FF    (0000 0001 1111 1111)&lt;br /&gt;
  LDCR R1,15&lt;br /&gt;
&lt;br /&gt;
Do &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; write all 16 bits. The last bit will change bit 15 of the PSI, which is the /RST2 bit. When we are in clock mode, writing a 0 to the /RST2 bit resets all I/O ports. This means that all pins are set to inputs, and devices that rely on them as outputs may be turned off unintentionally.&lt;br /&gt;
&lt;br /&gt;
=== Reading the timer ===&lt;br /&gt;
&lt;br /&gt;
We are interested for the current value of the timer at some occasions. However, since the counter is constantly changing (every 64 clock cycles), reading from the active counter may deliver a wrong result. Think, for instance, about a value 0x2000, which is decremented after we read the first bits, we may end up with 0x2FFF (2 from 0x2000, FFF from 0x1FFF). &lt;br /&gt;
&lt;br /&gt;
For this reason, the timer value is copied to a &amp;#039;&amp;#039;&amp;#039;read buffer&amp;#039;&amp;#039;&amp;#039;. This happens at every 64th clock cycle, when the counter is also decremented, but only when we are &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; in clock mode. &lt;br /&gt;
&lt;br /&gt;
To read the buffer, we must set the PSI to clock mode first, which freezes the counter value in the read buffer. Then we can read the counter using STCR.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  STCR R1,15&lt;br /&gt;
  SRL  R1,1&lt;br /&gt;
  SBZ  0&lt;br /&gt;
&lt;br /&gt;
As we see, register 1 will contain bit 0 in its rightmost bit, so we shift R1 by one position to move it out.&lt;br /&gt;
&lt;br /&gt;
Do not forget to return to interrupt mode, or the buffer will not receive further updates.&lt;br /&gt;
&lt;br /&gt;
As mentioned, bit 15 is the /RST2 bit when we write to it. For reading, bit 15 reflects the /INTREQ output.&lt;br /&gt;
&lt;br /&gt;
=== The weird S0 input ===&lt;br /&gt;
&lt;br /&gt;
There is one peculiarity with the PSI. When the input line S0 is set to 1, the clock mode is &amp;#039;&amp;#039;&amp;#039;left temporarily&amp;#039;&amp;#039;&amp;#039; - until S0 is 0 again, and bit 0 is 1. If bit 0 is 0, this has no effect, as the clock mode is never entered.&lt;br /&gt;
&lt;br /&gt;
What is the reason for this? &lt;br /&gt;
&lt;br /&gt;
TI obviously considered use cases where the PSI remains in clock mode for longer times, while the I/O ports must still be operated. The I/O ports are available on bits 16-31, which means that the S0 input is 1 for these addresses. So when we do a SBO 16, this is not related to the timer value, and thus should control the first I/O port. To allow this, the chip returns to interrupt/port mode for this operation, and when S0 is reset, returns to clock mode.&lt;br /&gt;
&lt;br /&gt;
In the TI console, the S0-S4 inputs are directly connected with the address bus lines A10-A14. This has a strange side effect: Whenever address bus line A10 changes to 1, the clock mode is left temporarily, and this also happens for ordinary memory accesses.&lt;br /&gt;
&lt;br /&gt;
Suppose that we are in clock mode (SBO 0). In this mode, the read buffer should not be updated, so when we read it, we should get the same value every time, although the real counter value is different. But this is difficult to achieve: If our program uses a memory address where A10 is 1 for some instruction, loading this instruction into the CPU will set A10 to 1, and thus leave clock mode, which updates the counter.&lt;br /&gt;
&lt;br /&gt;
Therefore, you may have the impression that the read register continues to be updated even though you set bit 0 to 1. But this is only happening because your program extends over a location where A10 is 1. So if you want to prove that in clock mode the read register remains fixed, your program must avoid all memory locations where A10 is 1.&lt;br /&gt;
&lt;br /&gt;
== Shared pins ==&lt;br /&gt;
&lt;br /&gt;
Some pins have a double meaning; for example, pin 34 is labeled /INT7 / P15. How do we select the functions?&lt;br /&gt;
&lt;br /&gt;
The shared pins are those for the interrupt inputs /INT7 to /INT15; they may also be used for P7 to P15 (in the opposite order).&lt;br /&gt;
&lt;br /&gt;
If the Pn port is set to &amp;#039;&amp;#039;&amp;#039;input&amp;#039;&amp;#039;&amp;#039;, the situation looks like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
This means that both port and interrupt input have the same values; each one is a mirror of the other&amp;#039;s state. We notice it by reading the bits of the associated CRU addresses, getting the same results. There is a difference, though: The interrupt input can be armed to trigger an outgoing interrupt, while the I/O port cannot.&lt;br /&gt;
&lt;br /&gt;
In the other case, the Pn port is an &amp;#039;&amp;#039;&amp;#039;output&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Is that possible? - Sure. The interrupt input will reflect the output of the port. When we set the port to 1, the interrupt input will get that 1 (same for 0). If we arm the interrupt input, setting the output port to 0 will trigger an interrupt via this input. The 9901 specification document recommends to disarm the interrupt trigger for the shared ports because of this possibly unwanted effect.&lt;br /&gt;
&lt;br /&gt;
== Safety precautions ==&lt;br /&gt;
&lt;br /&gt;
The TMS9901 PSI is an interrupt encoder and an I/O circuit. You may have heard that improper use of I/O circuits may damage the circuit so that it must be replaced. Is this possible with the 9901 as well? Is there a way to kill the 9901?&lt;br /&gt;
&lt;br /&gt;
Short answer: Yes.&lt;br /&gt;
&lt;br /&gt;
Longer answer: Yes, but it depends on the way how the ports are used.&lt;br /&gt;
&lt;br /&gt;
If the port is used as an input, nothing bad will happen, since the ports have high input impedance. You can put 0V or 5V directly to the input; of course, you should not use excessive voltage.&lt;br /&gt;
&lt;br /&gt;
=== How to damage the 9901 ports ===&lt;br /&gt;
&lt;br /&gt;
The problem is when the port is configured as an output port. In that case it may output 0V (0) or 5V (1). When you connect the output to the opposite voltage, you will cause a short circuit which quickly exceeds the chip&amp;#039;s maximum power dissipation of 750 mW. This means you will fry the chip.&lt;br /&gt;
&lt;br /&gt;
Consider the following setup (but don&amp;#039;t try it!).&lt;br /&gt;
&lt;br /&gt;
[[File:Badport9901.png|300px]]&lt;br /&gt;
&lt;br /&gt;
Obviously, port P4 is assumed to be an input. When the switch is open, port P4 will sense a 1 on its input. When the switch is closed, it will sense a 0.&lt;br /&gt;
&lt;br /&gt;
Now what happens when you execute this code?&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  20&lt;br /&gt;
&lt;br /&gt;
This will program port P4 to be an output, and to show a 1 (5V) at the output. SBZ will, in contrast, make this port output a 0. So how do you make it an input port? - This is done by resetting the chip. As long as you don&amp;#039;t use a SBZ/SBO (or a LDCR) on the ports, the ports are inputs.&lt;br /&gt;
&lt;br /&gt;
Nothing has happened up to now. But now imagine that you press the switch. This will put the 1 output to ground, causing the short circuit, and your 9901 is toast.&lt;br /&gt;
&lt;br /&gt;
=== What about the TI console? ===&lt;br /&gt;
&lt;br /&gt;
So can we damage the 9901 in the TI console by a suitable CRU operation? - Luckily not. &lt;br /&gt;
&lt;br /&gt;
The reason is the way how TI used the ports on the mainboard. Most of the ports are configured as outputs. Nothing happens when you reset them to inputs.&lt;br /&gt;
&lt;br /&gt;
* The pure interrupt inputs (/INT1 to /INT6) are safe, as they cannot output anything. They have no output driver that could be fried.&lt;br /&gt;
* P0 and P1 are not connected.&lt;br /&gt;
* P2, P3, and P4 are designed as outputs, so they cannot be turned from an input to an output.&lt;br /&gt;
* P5 is the selector of the Alpha Lock key, so output as well.&lt;br /&gt;
* P6 and P7 (/INT15) are the cassette motor control outputs.&lt;br /&gt;
* P8 (/INT14) is the audio gate output.&lt;br /&gt;
* P9 (/INT13) is the cassette output.&lt;br /&gt;
* P10 (/INT12) is pulled up to 5V via a 10 kOhm resistor. If you put a 0 on this port (5V), the maximum current is 500 µA, which results in a power dissipation of 2.5 mW (far below the maximum 750 mW).&lt;br /&gt;
* P11 (/INT11) is the cassette input. However, there is a resistor of 2.2 kOhm (R413) which effectively guards the port of high currents.&lt;br /&gt;
* P12, P13, P14, P15 (/INT10 ... /INT7) are the keyboard input lines. Again, we have pull-ups of 10 kOhm against +5V, but also 470 Ohm on the line, which means a maximum of 8.5 mA or 42.5 mW. This is still well below the maximum value.&lt;br /&gt;
&lt;br /&gt;
So we see that resistors are the best way to protect the ports. And we may calm down; we cannot burn the 9901 in the TI console by a bug in our program.&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50611</id>
		<title>Programmable Systems Interface TMS 9901</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50611"/>
		<updated>2022-04-23T12:36:47Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Input/Output port operation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The Programmable System Interface (PSI) is a circuit that is used on the TI-99/4A main board, on some expansion cards, and on the Geneve.&lt;br /&gt;
&lt;br /&gt;
The designation of the PSI is &amp;#039;&amp;#039;&amp;#039;TMS9901&amp;#039;&amp;#039;&amp;#039;, and this is the name that most users will be more familiar with.&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
&lt;br /&gt;
The TMS 9901 Programmable Systems Interface is a multi-purpose circuit that is used in the TI-99/4A and the Geneve computer, and also on some peripheral cards like the Corcomp Floppy Disk Controller.&lt;br /&gt;
&lt;br /&gt;
The 9901 has three main usage modes:&lt;br /&gt;
&lt;br /&gt;
* Input/Output ports&lt;br /&gt;
* Interrupt prioritizing&lt;br /&gt;
* Timer&lt;br /&gt;
&lt;br /&gt;
The I/O ports are mainly used for scanning the keyboard and joysticks, while the timer is used for cassette data encoding and decoding. The interrupt prioritizer is only used for the video interrupt which is routed from the VDP to the CPU through the 9901.&lt;br /&gt;
&lt;br /&gt;
== Package ==&lt;br /&gt;
&lt;br /&gt;
This is the pin assignment according to the 9901 specification.&lt;br /&gt;
&lt;br /&gt;
[[File:Pack9901.png|300px|left]]&lt;br /&gt;
&lt;br /&gt;
You can see pins of related functions marked by the same background color.&lt;br /&gt;
&lt;br /&gt;
* Orange: Interrupt inputs&lt;br /&gt;
* Red: CRU bit selection (00000 - 11111, i.e. 0-31 decimal)&lt;br /&gt;
* Blue: CRU interface&lt;br /&gt;
* Violet: I/O ports&lt;br /&gt;
* Yellow: Outgoing interrupt and level&lt;br /&gt;
&lt;br /&gt;
Note that some pins have a double function, or in other words, two functions share the same pin. I will discuss this in the following sections.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Input/Output port operation ==&lt;br /&gt;
&lt;br /&gt;
The simplest way of working with the 9901 is input/output operation with the ports. The 9901 has 16 configurable ports that can be individually used as inputs or outputs: P0 to P15.&lt;br /&gt;
&lt;br /&gt;
=== Px port as input ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
The power-on preset for every port is input. If you want to probe the P2 status (bit 18), use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 TB   18&lt;br /&gt;
 JEQ  P2ACT&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the given CRU address into the Equal bit of the status register. JEQ will jump to the location when this input has a high level. The maximum input current is 100 µA. You should protect the input with a resistor; see below for a more detailed discussion.&lt;br /&gt;
&lt;br /&gt;
If you want to read all 16 ports, use the STCR instruction:&lt;br /&gt;
&lt;br /&gt;
 LI   R12,32&lt;br /&gt;
 STCR R1,0&lt;br /&gt;
&lt;br /&gt;
Note that the CRU base address in R12 advances in steps of 2, so bit 16 is on address 32. Also, for reading 16 bits, you must specify a count of 0.&lt;br /&gt;
&lt;br /&gt;
The 9901 offers some more inputs than the 16 ports; we can use the interrupt inputs as well. See below for a discussion of the interrupt mode.&lt;br /&gt;
&lt;br /&gt;
=== Px port as output ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Once you write a value to the CRU address of a 9901 port, this port switches to output mode. The value written to the port is the value that is shown on its pin. If we want to output a 1 on P6, we have to use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
&lt;br /&gt;
If we wanted to output a 0, SBZ would have to be used. In order to set several ports in one go, use LDCR.&lt;br /&gt;
&lt;br /&gt;
Note that you cannot switch back to input mode. This is only possible by resetting the 9901, either using the /RST1 input line, or by setting the /RST2 bit (bit 15) to 0 in clock mode.&lt;br /&gt;
&lt;br /&gt;
We can also read the output port just by using TB as usual (or STCR for several ports at once). &lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
 TB   22&lt;br /&gt;
 JEQ  EXPECT&lt;br /&gt;
&lt;br /&gt;
The TB operation should return the 1 which the previous SBO has set.&lt;br /&gt;
&lt;br /&gt;
== Interrupt mode ==&lt;br /&gt;
&lt;br /&gt;
In interrupt mode, bits 1 to 15 are used to set the interrupt mask (write), or to query the interrupt inputs (read). That is, the meaning of reading and writing the bits is indeed different.&lt;br /&gt;
&lt;br /&gt;
This schema shows how the interrupt inputs are used.&lt;br /&gt;
&lt;br /&gt;
[[File:Int9901.png|600px]]&lt;br /&gt;
&lt;br /&gt;
This illustration is slightly simplified; there is a latch between each interrupt input and the mask AND gate. This is not relevant for our programming, though. For more details see the &amp;#039;&amp;#039;TMS 9901 Programmable Systems Interface Data Manual&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
=== Checking the interrupt inputs ===&lt;br /&gt;
&lt;br /&gt;
In general, every interrupt input can be read via CRU access:&lt;br /&gt;
&lt;br /&gt;
  CLR R12&lt;br /&gt;
  TB  15&lt;br /&gt;
  JEQ NOINT15&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the specified CRU address to the Equal status bit. This means that JEQ jumps to the location when the bit is 1. Since all interrupt inputs are negative logic, reading a 1 means that there is no interrupt at this input (so I called it NOINT15).&lt;br /&gt;
&lt;br /&gt;
If we want to read several interrupt inputs by a single instruction, we have to use STCR.&lt;br /&gt;
&lt;br /&gt;
  LI   R12,16&lt;br /&gt;
  STCR R1,8&lt;br /&gt;
&lt;br /&gt;
This reads the states of the /INT8, /INT9, ..., /INT15 inputs and puts them in the most significant byte of R1. (See the specification of STCR and LDCR for transfers of up to 8 bits.)&lt;br /&gt;
&lt;br /&gt;
=== Interrupt levels ===&lt;br /&gt;
&lt;br /&gt;
Each interrupt input (/INT1 to /INT15) may be used to trigger an interrupt at another circuit, typically the CPU. For this purpose, the PSI offers an /INTREQ outgoing line. In the TI console, it is routed to the /INTREQ input of the TMS9900 CPU. &lt;br /&gt;
&lt;br /&gt;
In addition there is a priority encoder, which is not shown here. This encoder outputs a 4-bit number which reflects the smallest number of the interrupt inputs where an interrupt has been sensed.&lt;br /&gt;
&lt;br /&gt;
That is, if /INT4 and /INT7 are currently set to 0 (active), the priority encoder outputs (0100), which means 4.&lt;br /&gt;
&lt;br /&gt;
The TMS9900 CPU can differentiate between these interrupt levels, and by using its LIMI command, we can set a threshold where interrupts are ignored. So when we use a LIMI 3, the interrupts of level 4, 5, 6 and higher will be ignored. &lt;br /&gt;
&lt;br /&gt;
The bad news is that Texas Instruments did not use these priority lines in the TI console. In fact, these outputs (IC0 .. IC3) are not connected, and the respective interrupt level inputs at the CPU are hardwired to (0001), which means that every interrupt that occurs in the TI console is automatically set to level 1. So in order to block all interrupts from the 9901&lt;br /&gt;
&lt;br /&gt;
 LIMI 0 &lt;br /&gt;
&lt;br /&gt;
must be executed. To allow all, &lt;br /&gt;
&lt;br /&gt;
 LIMI 1 &lt;br /&gt;
&lt;br /&gt;
is sufficient. For some unclear reason, TI used LIMI 2 in all occasions.&lt;br /&gt;
&lt;br /&gt;
=== Arming and disarming interrupts ===&lt;br /&gt;
&lt;br /&gt;
As said above, every interrupt input can be read at any time in interrupt mode. In that sense, we don&amp;#039;t really treat these inputs as interrupts inputs, but simply as input ports.&lt;br /&gt;
&lt;br /&gt;
If we want a port to trigger an interrupt, we must arm this interrupt input. This is done by setting the respective bit to 1.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  2&lt;br /&gt;
&lt;br /&gt;
This enables interrupt input /INT2 to cause an /INTREQ when its input changes to 0. In the TI console, /INT2 is connected to the interrupt output of the VDP (video processor). If you want to block interrupts on that input, use SBZ.&lt;br /&gt;
&lt;br /&gt;
It is a common misunderstanding that interrupts can be cleared by using a SBZ on an input. This can, for example, be seen in the popular TI Intern book. Instead, SBZ n simply blocks interrupt input /INTn. In the case of the video processor, it is up to the VDP to clear its interrupt, and this is done by reading its status register.&lt;br /&gt;
&lt;br /&gt;
== Counter mode ==&lt;br /&gt;
&lt;br /&gt;
Besides the I/O capabilities, the PSI offers a &amp;quot;real-time&amp;quot; clock. This should not be understood as a precise timer with seconds, hundreds of seconds or other but as a countdown timer that is decremented every 64 CPU clock cycles.&lt;br /&gt;
&lt;br /&gt;
This schematic is taken from the Osborne book, redrawn by me in LibreOffice (and with added Load Buffer). &lt;br /&gt;
&lt;br /&gt;
[[File:Clock9901.png|550px]]&lt;br /&gt;
&lt;br /&gt;
=== Using the timer ===&lt;br /&gt;
&lt;br /&gt;
There are basically two ways of using the countdown timer:&lt;br /&gt;
&lt;br /&gt;
* Interval timer: Set the counter to a value; wait until the counter reaches zero. The PSI can be set up to raise an interrupt at that instant.&lt;br /&gt;
* Event timer: Set the counter to a value; do something and then read the counter value.&lt;br /&gt;
&lt;br /&gt;
The counter has 14 bits; that is, its maximum value is 0x3FFF (16383). The counter is decremented every 64 clock cycles, and for the TI-99/4A and the Geneve with a clock cycle time of 333ns, this means that the counter resolution is 21.333 µs. The whole 16384 steps take 0.3495 seconds (349.5 ms).&lt;br /&gt;
&lt;br /&gt;
When the counter reaches 0, it is reloaded from the load buffer, and counting continues.&lt;br /&gt;
&lt;br /&gt;
It is essential to understand that the counter &amp;#039;&amp;#039;&amp;#039;always&amp;#039;&amp;#039;&amp;#039; counts down, independent of the mode. The specification says that the clock is enabled when a non-zero value is loaded into the load buffer, but tests on a real Geneve show that the clock decrements even when the load buffer is filled with 0.&lt;br /&gt;
&lt;br /&gt;
=== Setting the timer ===&lt;br /&gt;
&lt;br /&gt;
In order to set the load buffer and so to define the maximum counter value, we must switch to clock mode. This is achieved by setting bit 0 of the PSI to 1. In the TI/Geneve systems, the PSI is mapped to CRU base address 0, so we can do this&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  0&lt;br /&gt;
&lt;br /&gt;
to enter &amp;#039;&amp;#039;&amp;#039;clock mode&amp;#039;&amp;#039;&amp;#039;. Using SBZ 0 returns to &amp;#039;&amp;#039;&amp;#039;interrupt mode&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
The effect of switching to clock mode is that the load buffer becomes accessible by the CRU addresses 1 to 14. Supposed that we want to load the buffer with the value 0x00FF, we may use these lines:&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  LI   R1,&amp;gt;00FF&lt;br /&gt;
  INCT R12&lt;br /&gt;
  LDCR R1,14&lt;br /&gt;
&lt;br /&gt;
As we see, we must change the CRU base. The LDCR operation always starts at the address defined in R12; if this is not 0, we have to set it here. Note, however, that the CRU address is always half of the R12 value, since the rightmost bit, A15, is not part of the address. If we want to start at bit 1, we must increase R12 by 2. This is, by the way, done wrong in several places in the literature where R12 is only set to 1.&lt;br /&gt;
&lt;br /&gt;
We can combine the SBO and the LDCR to one operation if we define the rightmost bit of R1 as the bit 0 and the following bits as the timer value.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  LI   R1,&amp;gt;01FF    (0000 0001 1111 1111)&lt;br /&gt;
  LDCR R1,15&lt;br /&gt;
&lt;br /&gt;
Do &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; write all 16 bits. The last bit will change bit 15 of the PSI, which is the /RST2 bit. When we are in clock mode, writing a 0 to the /RST2 bit resets all I/O ports. This means that all pins are set to inputs, and devices that rely on them as outputs may be turned off unintentionally.&lt;br /&gt;
&lt;br /&gt;
=== Reading the timer ===&lt;br /&gt;
&lt;br /&gt;
We are interested for the current value of the timer at some occasions. However, since the counter is constantly changing (every 64 clock cycles), reading from the active counter may deliver a wrong result. Think, for instance, about a value 0x2000, which is decremented after we read the first bits, we may end up with 0x2FFF (2 from 0x2000, FFF from 0x1FFF). &lt;br /&gt;
&lt;br /&gt;
For this reason, the timer value is copied to a &amp;#039;&amp;#039;&amp;#039;read buffer&amp;#039;&amp;#039;&amp;#039;. This happens at every 64th clock cycle, when the counter is also decremented, but only when we are &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; in clock mode. &lt;br /&gt;
&lt;br /&gt;
To read the buffer, we must set the PSI to clock mode first, which freezes the counter value in the read buffer. Then we can read the counter using STCR.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  STCR R1,15&lt;br /&gt;
  SRL  R1,1&lt;br /&gt;
  SBZ  0&lt;br /&gt;
&lt;br /&gt;
As we see, register 1 will contain bit 0 in its rightmost bit, so we shift R1 by one position to move it out.&lt;br /&gt;
&lt;br /&gt;
Do not forget to return to interrupt mode, or the buffer will not receive further updates.&lt;br /&gt;
&lt;br /&gt;
As mentioned, bit 15 is the /RST2 bit when we write to it. For reading, bit 15 reflects the /INTREQ output.&lt;br /&gt;
&lt;br /&gt;
=== The weird S0 input ===&lt;br /&gt;
&lt;br /&gt;
There is one peculiarity with the PSI. When the input line S0 is set to 1, the clock mode is &amp;#039;&amp;#039;&amp;#039;left temporarily&amp;#039;&amp;#039;&amp;#039; - until S0 is 0 again, and bit 0 is 1. If bit 0 is 0, this has no effect, as the clock mode is never entered.&lt;br /&gt;
&lt;br /&gt;
What is the reason for this? &lt;br /&gt;
&lt;br /&gt;
TI obviously considered use cases where the PSI remains in clock mode for longer times, while the I/O ports must still be operated. The I/O ports are available on bits 16-31, which means that the S0 input is 1 for these addresses. So when we do a SBO 16, this is not related to the timer value, and thus should control the first I/O port. To allow this, the chip returns to interrupt/port mode for this operation, and when S0 is reset, returns to clock mode.&lt;br /&gt;
&lt;br /&gt;
In the TI console, the S0-S4 inputs are directly connected with the address bus lines A10-A14. This has a strange side effect: Whenever address bus line A10 changes to 1, the clock mode is left temporarily, and this also happens for ordinary memory accesses.&lt;br /&gt;
&lt;br /&gt;
Suppose that we are in clock mode (SBO 0). In this mode, the read buffer should not be updated, so when we read it, we should get the same value every time, although the real counter value is different. But this is difficult to achieve: If our program uses a memory address where A10 is 1 for some instruction, loading this instruction into the CPU will set A10 to 1, and thus leave clock mode, which updates the counter.&lt;br /&gt;
&lt;br /&gt;
Therefore, you may have the impression that the read register continues to be updated even though you set bit 0 to 1. But this is only happening because your program extends over a location where A10 is 1. So if you want to prove that in clock mode the read register remains fixed, your program must avoid all memory locations where A10 is 1.&lt;br /&gt;
&lt;br /&gt;
== Shared pins ==&lt;br /&gt;
&lt;br /&gt;
Some pins have a double meaning; for example, pin 34 is labeled /INT7 / P15. How do we select the functions?&lt;br /&gt;
&lt;br /&gt;
The shared pins are those for the interrupt inputs /INT7 to /INT15; they may also be used for P7 to P15 (in the opposite order).&lt;br /&gt;
&lt;br /&gt;
If the Pn port is set to &amp;#039;&amp;#039;&amp;#039;input&amp;#039;&amp;#039;&amp;#039;, the situation looks like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
This means that both port and interrupt input have the same values; each one is a mirror of the other&amp;#039;s state. We notice it by reading the bits of the associated CRU addresses, getting the same results. There is a difference, though: The interrupt input can be armed to trigger an outgoing interrupt, while the I/O port cannot.&lt;br /&gt;
&lt;br /&gt;
In the other case, the Pn port is an &amp;#039;&amp;#039;&amp;#039;output&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Is that possible? - Sure. The interrupt input will reflect the output of the port. When we set the port to 1, the interrupt input will get that 1 (same for 0). If we arm the interrupt input, setting the output port to 0 will trigger an interrupt via this input. The 9901 specification document recommends to disarm the interrupt trigger for the shared ports because of this possibly unwanted effect.&lt;br /&gt;
&lt;br /&gt;
== Safety precautions ==&lt;br /&gt;
&lt;br /&gt;
The TMS9901 PSI is an interrupt encoder and an I/O circuit. You may have heard that improper use of I/O circuits may damage the circuit so that it must be replaced. Is this possible with the 9901 as well? Is there a way to kill the 9901?&lt;br /&gt;
&lt;br /&gt;
Short answer: Yes.&lt;br /&gt;
&lt;br /&gt;
Longer answer: Yes, but it depends on the way how the ports are used.&lt;br /&gt;
&lt;br /&gt;
If the port is used as an input, nothing bad will happen, since the ports have high input impedance. You can put 0V or 5V directly to the input; of course, you should not use excessive voltage.&lt;br /&gt;
&lt;br /&gt;
=== How to damage the 9901 ports ===&lt;br /&gt;
&lt;br /&gt;
The problem is when the port is configured as an output port. In that case it may output 0V (0) or 5V (1). When you connect the output to the opposite voltage, you will cause a short circuit which quickly exceeds the chip&amp;#039;s maximum power dissipation of 750 mW. This means you will fry the chip.&lt;br /&gt;
&lt;br /&gt;
Consider the following setup (but don&amp;#039;t try it!).&lt;br /&gt;
&lt;br /&gt;
[[File:Badport9901.png|300px]]&lt;br /&gt;
&lt;br /&gt;
Obviously, port P4 is assumed to be an input. When the switch is open, port P4 will sense a 1 on its input. When the switch is closed, it will sense a 0.&lt;br /&gt;
&lt;br /&gt;
Now what happens when you execute this code?&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  20&lt;br /&gt;
&lt;br /&gt;
This will program port P4 to be an output, and to show a 1 (5V) at the output. SBZ will, in contrast, make this port output a 0. So how do you make it an input port? - This is done by resetting the chip. As long as you don&amp;#039;t use a SBZ/SBO (or a LDCR) on the ports, the ports are inputs.&lt;br /&gt;
&lt;br /&gt;
Nothing has happened up to now. But now imagine that you press the switch. This will put the 1 output to ground, causing the short circuit, and your 9901 is toast.&lt;br /&gt;
&lt;br /&gt;
=== What about the TI console? ===&lt;br /&gt;
&lt;br /&gt;
So can we damage the 9901 in the TI console by a suitable CRU operation? - Luckily not. &lt;br /&gt;
&lt;br /&gt;
The reason is the way how TI used the ports on the mainboard. Most of the ports are configured as outputs. Nothing happens when you reset them to inputs.&lt;br /&gt;
&lt;br /&gt;
* The pure interrupt inputs (/INT1 to /INT6) are safe, as they cannot output anything. They have no output driver that could be fried.&lt;br /&gt;
* P0 and P1 are not connected.&lt;br /&gt;
* P2, P3, and P4 are designed as outputs, so they cannot be turned from an input to an output.&lt;br /&gt;
* P5 is the selector of the Alpha Lock key, so output as well.&lt;br /&gt;
* P6 and P7 (/INT15) are the cassette motor control outputs.&lt;br /&gt;
* P8 (/INT14) is the audio gate output.&lt;br /&gt;
* P9 (/INT13) is the cassette output.&lt;br /&gt;
* P10 (/INT12) is pulled up to 5V via a 10 kOhm resistor. If you put a 0 on this port (5V), the maximum current is 500 µA, which results in a power dissipation of 2.5 mW (far below the maximum 750 mW).&lt;br /&gt;
* P11 (/INT11) is the cassette input. However, there is a resistor of 2.2 kOhm (R413) which effectively guards the port of high currents.&lt;br /&gt;
* P12, P13, P14, P15 (/INT10 ... /INT7) are the keyboard input lines. Again, we have pull-ups of 10 kOhm against +5V, but also 470 Ohm on the line, which means a maximum of 8.5 mA or 42.5 mW. This is still well below the maximum value.&lt;br /&gt;
&lt;br /&gt;
So we see that resistors are the best way to protect the ports. And we may calm down; we cannot burn the 9901 in the TI console by a bug in our program.&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
	<entry>
		<id>http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50610</id>
		<title>Programmable Systems Interface TMS 9901</title>
		<link rel="alternate" type="text/html" href="http://www.ninerpedia.org/index.php?title=Programmable_Systems_Interface_TMS_9901&amp;diff=50610"/>
		<updated>2022-04-22T23:28:22Z</updated>

		<summary type="html">&lt;p&gt;Mizapf: /* Using the timer */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The Programmable System Interface (PSI) is a circuit that is used on the TI-99/4A main board, on some expansion cards, and on the Geneve.&lt;br /&gt;
&lt;br /&gt;
The designation of the PSI is &amp;#039;&amp;#039;&amp;#039;TMS9901&amp;#039;&amp;#039;&amp;#039;, and this is the name that most users will be more familiar with.&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
&lt;br /&gt;
The TMS 9901 Programmable Systems Interface is a multi-purpose circuit that is used in the TI-99/4A and the Geneve computer, and also on some peripheral cards like the Corcomp Floppy Disk Controller.&lt;br /&gt;
&lt;br /&gt;
The 9901 has three main usage modes:&lt;br /&gt;
&lt;br /&gt;
* Input/Output ports&lt;br /&gt;
* Interrupt prioritizing&lt;br /&gt;
* Timer&lt;br /&gt;
&lt;br /&gt;
The I/O ports are mainly used for scanning the keyboard and joysticks, while the timer is used for cassette data encoding and decoding. The interrupt prioritizer is only used for the video interrupt which is routed from the VDP to the CPU through the 9901.&lt;br /&gt;
&lt;br /&gt;
== Package ==&lt;br /&gt;
&lt;br /&gt;
This is the pin assignment according to the 9901 specification.&lt;br /&gt;
&lt;br /&gt;
[[File:Pack9901.png|300px|left]]&lt;br /&gt;
&lt;br /&gt;
You can see pins of related functions marked by the same background color.&lt;br /&gt;
&lt;br /&gt;
* Orange: Interrupt inputs&lt;br /&gt;
* Red: CRU bit selection (00000 - 11111, i.e. 0-31 decimal)&lt;br /&gt;
* Blue: CRU interface&lt;br /&gt;
* Violet: I/O ports&lt;br /&gt;
* Yellow: Outgoing interrupt and level&lt;br /&gt;
&lt;br /&gt;
Note that some pins have a double function, or in other words, two functions share the same pin. I will discuss this in the following sections.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear:both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Input/Output port operation ==&lt;br /&gt;
&lt;br /&gt;
The simplest way of working with the 9901 is input/output operation with the ports. The 9901 has 16 configurable ports that can be individually used as inputs or outputs: P0 to P15.&lt;br /&gt;
&lt;br /&gt;
=== Px port as input ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
The power-on preset for every port is input. If you want to probe the P2 status (bit 18), use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 TB   18&lt;br /&gt;
 JEQ  P2ACT&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the given CRU address into the Equal bit of the status register. JEQ will jump to the location when this input has a high level. The maximum input current is 100 µA. You should protect the input with a resistor; see below for a more detailed discussion.&lt;br /&gt;
&lt;br /&gt;
If you want to read all 16 ports, use the STCR instruction:&lt;br /&gt;
&lt;br /&gt;
 LI   R12,32&lt;br /&gt;
 STCR R1,0&lt;br /&gt;
&lt;br /&gt;
Note that the CRU base address in R12 advances in steps of 2, so bit 16 is on address 32. Also, for reading 16 bits, you must specify a count of 0.&lt;br /&gt;
&lt;br /&gt;
The 9901 offers some more inputs than the 16 ports; we can use the interrupt inputs as well. See below for a discussion of the interrupt mode.&lt;br /&gt;
&lt;br /&gt;
=== Px port as output ===&lt;br /&gt;
&lt;br /&gt;
[[File:Port9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Once you write a value to the CRU address of a 9901 port, this port switches to output mode. The value written to the port is the value that is shown on its pin. If we want to output a 1 on P6, we have to use&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  22&lt;br /&gt;
&lt;br /&gt;
If we wanted to output a 0, SBZ would have to be used. In order to set several ports in one go, use LDCR.&lt;br /&gt;
&lt;br /&gt;
Note that you cannot switch back to input mode. This is only possible by resetting the 9901, either using the /RST1 input line, or by setting the /RST2 bit (bit 15) to 0 in clock mode.&lt;br /&gt;
&lt;br /&gt;
== Interrupt mode ==&lt;br /&gt;
&lt;br /&gt;
In interrupt mode, bits 1 to 15 are used to set the interrupt mask (write), or to query the interrupt inputs (read). That is, the meaning of reading and writing the bits is indeed different.&lt;br /&gt;
&lt;br /&gt;
This schema shows how the interrupt inputs are used.&lt;br /&gt;
&lt;br /&gt;
[[File:Int9901.png|600px]]&lt;br /&gt;
&lt;br /&gt;
This illustration is slightly simplified; there is a latch between each interrupt input and the mask AND gate. This is not relevant for our programming, though. For more details see the &amp;#039;&amp;#039;TMS 9901 Programmable Systems Interface Data Manual&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
=== Checking the interrupt inputs ===&lt;br /&gt;
&lt;br /&gt;
In general, every interrupt input can be read via CRU access:&lt;br /&gt;
&lt;br /&gt;
  CLR R12&lt;br /&gt;
  TB  15&lt;br /&gt;
  JEQ NOINT15&lt;br /&gt;
&lt;br /&gt;
TB copies the bit at the specified CRU address to the Equal status bit. This means that JEQ jumps to the location when the bit is 1. Since all interrupt inputs are negative logic, reading a 1 means that there is no interrupt at this input (so I called it NOINT15).&lt;br /&gt;
&lt;br /&gt;
If we want to read several interrupt inputs by a single instruction, we have to use STCR.&lt;br /&gt;
&lt;br /&gt;
  LI   R12,16&lt;br /&gt;
  STCR R1,8&lt;br /&gt;
&lt;br /&gt;
This reads the states of the /INT8, /INT9, ..., /INT15 inputs and puts them in the most significant byte of R1. (See the specification of STCR and LDCR for transfers of up to 8 bits.)&lt;br /&gt;
&lt;br /&gt;
=== Interrupt levels ===&lt;br /&gt;
&lt;br /&gt;
Each interrupt input (/INT1 to /INT15) may be used to trigger an interrupt at another circuit, typically the CPU. For this purpose, the PSI offers an /INTREQ outgoing line. In the TI console, it is routed to the /INTREQ input of the TMS9900 CPU. &lt;br /&gt;
&lt;br /&gt;
In addition there is a priority encoder, which is not shown here. This encoder outputs a 4-bit number which reflects the smallest number of the interrupt inputs where an interrupt has been sensed.&lt;br /&gt;
&lt;br /&gt;
That is, if /INT4 and /INT7 are currently set to 0 (active), the priority encoder outputs (0100), which means 4.&lt;br /&gt;
&lt;br /&gt;
The TMS9900 CPU can differentiate between these interrupt levels, and by using its LIMI command, we can set a threshold where interrupts are ignored. So when we use a LIMI 3, the interrupts of level 4, 5, 6 and higher will be ignored. &lt;br /&gt;
&lt;br /&gt;
The bad news is that Texas Instruments did not use these priority lines in the TI console. In fact, these outputs (IC0 .. IC3) are not connected, and the respective interrupt level inputs at the CPU are hardwired to (0001), which means that every interrupt that occurs in the TI console is automatically set to level 1. So in order to block all interrupts from the 9901&lt;br /&gt;
&lt;br /&gt;
 LIMI 0 &lt;br /&gt;
&lt;br /&gt;
must be executed. To allow all, &lt;br /&gt;
&lt;br /&gt;
 LIMI 1 &lt;br /&gt;
&lt;br /&gt;
is sufficient. For some unclear reason, TI used LIMI 2 in all occasions.&lt;br /&gt;
&lt;br /&gt;
=== Arming and disarming interrupts ===&lt;br /&gt;
&lt;br /&gt;
As said above, every interrupt input can be read at any time in interrupt mode. In that sense, we don&amp;#039;t really treat these inputs as interrupts inputs, but simply as input ports.&lt;br /&gt;
&lt;br /&gt;
If we want a port to trigger an interrupt, we must arm this interrupt input. This is done by setting the respective bit to 1.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  2&lt;br /&gt;
&lt;br /&gt;
This enables interrupt input /INT2 to cause an /INTREQ when its input changes to 0. In the TI console, /INT2 is connected to the interrupt output of the VDP (video processor). If you want to block interrupts on that input, use SBZ.&lt;br /&gt;
&lt;br /&gt;
It is a common misunderstanding that interrupts can be cleared by using a SBZ on an input. This can, for example, be seen in the popular TI Intern book. Instead, SBZ n simply blocks interrupt input /INTn. In the case of the video processor, it is up to the VDP to clear its interrupt, and this is done by reading its status register.&lt;br /&gt;
&lt;br /&gt;
== Counter mode ==&lt;br /&gt;
&lt;br /&gt;
Besides the I/O capabilities, the PSI offers a &amp;quot;real-time&amp;quot; clock. This should not be understood as a precise timer with seconds, hundreds of seconds or other but as a countdown timer that is decremented every 64 CPU clock cycles.&lt;br /&gt;
&lt;br /&gt;
This schematic is taken from the Osborne book, redrawn by me in LibreOffice (and with added Load Buffer). &lt;br /&gt;
&lt;br /&gt;
[[File:Clock9901.png|550px]]&lt;br /&gt;
&lt;br /&gt;
=== Using the timer ===&lt;br /&gt;
&lt;br /&gt;
There are basically two ways of using the countdown timer:&lt;br /&gt;
&lt;br /&gt;
* Interval timer: Set the counter to a value; wait until the counter reaches zero. The PSI can be set up to raise an interrupt at that instant.&lt;br /&gt;
* Event timer: Set the counter to a value; do something and then read the counter value.&lt;br /&gt;
&lt;br /&gt;
The counter has 14 bits; that is, its maximum value is 0x3FFF (16383). The counter is decremented every 64 clock cycles, and for the TI-99/4A and the Geneve with a clock cycle time of 333ns, this means that the counter resolution is 21.333 µs. The whole 16384 steps take 0.3495 seconds (349.5 ms).&lt;br /&gt;
&lt;br /&gt;
When the counter reaches 0, it is reloaded from the load buffer, and counting continues.&lt;br /&gt;
&lt;br /&gt;
It is essential to understand that the counter &amp;#039;&amp;#039;&amp;#039;always&amp;#039;&amp;#039;&amp;#039; counts down, independent of the mode. The specification says that the clock is enabled when a non-zero value is loaded into the load buffer, but tests on a real Geneve show that the clock decrements even when the load buffer is filled with 0.&lt;br /&gt;
&lt;br /&gt;
=== Setting the timer ===&lt;br /&gt;
&lt;br /&gt;
In order to set the load buffer and so to define the maximum counter value, we must switch to clock mode. This is achieved by setting bit 0 of the PSI to 1. In the TI/Geneve systems, the PSI is mapped to CRU base address 0, so we can do this&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  0&lt;br /&gt;
&lt;br /&gt;
to enter &amp;#039;&amp;#039;&amp;#039;clock mode&amp;#039;&amp;#039;&amp;#039;. Using SBZ 0 returns to &amp;#039;&amp;#039;&amp;#039;interrupt mode&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
The effect of switching to clock mode is that the load buffer becomes accessible by the CRU addresses 1 to 14. Supposed that we want to load the buffer with the value 0x00FF, we may use these lines:&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  LI   R1,&amp;gt;00FF&lt;br /&gt;
  INCT R12&lt;br /&gt;
  LDCR R1,14&lt;br /&gt;
&lt;br /&gt;
As we see, we must change the CRU base. The LDCR operation always starts at the address defined in R12; if this is not 0, we have to set it here. Note, however, that the CRU address is always half of the R12 value, since the rightmost bit, A15, is not part of the address. If we want to start at bit 1, we must increase R12 by 2. This is, by the way, done wrong in several places in the literature where R12 is only set to 1.&lt;br /&gt;
&lt;br /&gt;
We can combine the SBO and the LDCR to one operation if we define the rightmost bit of R1 as the bit 0 and the following bits as the timer value.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  LI   R1,&amp;gt;01FF    (0000 0001 1111 1111)&lt;br /&gt;
  LDCR R1,15&lt;br /&gt;
&lt;br /&gt;
Do &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; write all 16 bits. The last bit will change bit 15 of the PSI, which is the /RST2 bit. When we are in clock mode, writing a 0 to the /RST2 bit resets all I/O ports. This means that all pins are set to inputs, and devices that rely on them as outputs may be turned off unintentionally.&lt;br /&gt;
&lt;br /&gt;
=== Reading the timer ===&lt;br /&gt;
&lt;br /&gt;
We are interested for the current value of the timer at some occasions. However, since the counter is constantly changing (every 64 clock cycles), reading from the active counter may deliver a wrong result. Think, for instance, about a value 0x2000, which is decremented after we read the first bits, we may end up with 0x2FFF (2 from 0x2000, FFF from 0x1FFF). &lt;br /&gt;
&lt;br /&gt;
For this reason, the timer value is copied to a &amp;#039;&amp;#039;&amp;#039;read buffer&amp;#039;&amp;#039;&amp;#039;. This happens at every 64th clock cycle, when the counter is also decremented, but only when we are &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; in clock mode. &lt;br /&gt;
&lt;br /&gt;
To read the buffer, we must set the PSI to clock mode first, which freezes the counter value in the read buffer. Then we can read the counter using STCR.&lt;br /&gt;
&lt;br /&gt;
  CLR  R12&lt;br /&gt;
  SBO  0&lt;br /&gt;
  STCR R1,15&lt;br /&gt;
  SRL  R1,1&lt;br /&gt;
  SBZ  0&lt;br /&gt;
&lt;br /&gt;
As we see, register 1 will contain bit 0 in its rightmost bit, so we shift R1 by one position to move it out.&lt;br /&gt;
&lt;br /&gt;
Do not forget to return to interrupt mode, or the buffer will not receive further updates.&lt;br /&gt;
&lt;br /&gt;
As mentioned, bit 15 is the /RST2 bit when we write to it. For reading, bit 15 reflects the /INTREQ output.&lt;br /&gt;
&lt;br /&gt;
=== The weird S0 input ===&lt;br /&gt;
&lt;br /&gt;
There is one peculiarity with the PSI. When the input line S0 is set to 1, the clock mode is &amp;#039;&amp;#039;&amp;#039;left temporarily&amp;#039;&amp;#039;&amp;#039; - until S0 is 0 again, and bit 0 is 1. If bit 0 is 0, this has no effect, as the clock mode is never entered.&lt;br /&gt;
&lt;br /&gt;
What is the reason for this? &lt;br /&gt;
&lt;br /&gt;
TI obviously considered use cases where the PSI remains in clock mode for longer times, while the I/O ports must still be operated. The I/O ports are available on bits 16-31, which means that the S0 input is 1 for these addresses. So when we do a SBO 16, this is not related to the timer value, and thus should control the first I/O port. To allow this, the chip returns to interrupt/port mode for this operation, and when S0 is reset, returns to clock mode.&lt;br /&gt;
&lt;br /&gt;
In the TI console, the S0-S4 inputs are directly connected with the address bus lines A10-A14. This has a strange side effect: Whenever address bus line A10 changes to 1, the clock mode is left temporarily, and this also happens for ordinary memory accesses.&lt;br /&gt;
&lt;br /&gt;
Suppose that we are in clock mode (SBO 0). In this mode, the read buffer should not be updated, so when we read it, we should get the same value every time, although the real counter value is different. But this is difficult to achieve: If our program uses a memory address where A10 is 1 for some instruction, loading this instruction into the CPU will set A10 to 1, and thus leave clock mode, which updates the counter.&lt;br /&gt;
&lt;br /&gt;
Therefore, you may have the impression that the read register continues to be updated even though you set bit 0 to 1. But this is only happening because your program extends over a location where A10 is 1. So if you want to prove that in clock mode the read register remains fixed, your program must avoid all memory locations where A10 is 1.&lt;br /&gt;
&lt;br /&gt;
== Shared pins ==&lt;br /&gt;
&lt;br /&gt;
Some pins have a double meaning; for example, pin 34 is labeled /INT7 / P15. How do we select the functions?&lt;br /&gt;
&lt;br /&gt;
The shared pins are those for the interrupt inputs /INT7 to /INT15; they may also be used for P7 to P15 (in the opposite order).&lt;br /&gt;
&lt;br /&gt;
If the Pn port is set to &amp;#039;&amp;#039;&amp;#039;input&amp;#039;&amp;#039;&amp;#039;, the situation looks like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901in.png|250px]]&lt;br /&gt;
&lt;br /&gt;
This means that both port and interrupt input have the same values; each one is a mirror of the other&amp;#039;s state. We notice it by reading the bits of the associated CRU addresses, getting the same results. There is a difference, though: The interrupt input can be armed to trigger an outgoing interrupt, while the I/O port cannot.&lt;br /&gt;
&lt;br /&gt;
In the other case, the Pn port is an &amp;#039;&amp;#039;&amp;#039;output&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:Shared9901out.png|250px]]&lt;br /&gt;
&lt;br /&gt;
Is that possible? - Sure. The interrupt input will reflect the output of the port. When we set the port to 1, the interrupt input will get that 1 (same for 0). If we arm the interrupt input, setting the output port to 0 will trigger an interrupt via this input. The 9901 specification document recommends to disarm the interrupt trigger for the shared ports because of this possibly unwanted effect.&lt;br /&gt;
&lt;br /&gt;
== Safety precautions ==&lt;br /&gt;
&lt;br /&gt;
The TMS9901 PSI is an interrupt encoder and an I/O circuit. You may have heard that improper use of I/O circuits may damage the circuit so that it must be replaced. Is this possible with the 9901 as well? Is there a way to kill the 9901?&lt;br /&gt;
&lt;br /&gt;
Short answer: Yes.&lt;br /&gt;
&lt;br /&gt;
Longer answer: Yes, but it depends on the way how the ports are used.&lt;br /&gt;
&lt;br /&gt;
If the port is used as an input, nothing bad will happen, since the ports have high input impedance. You can put 0V or 5V directly to the input; of course, you should not use excessive voltage.&lt;br /&gt;
&lt;br /&gt;
=== How to damage the 9901 ports ===&lt;br /&gt;
&lt;br /&gt;
The problem is when the port is configured as an output port. In that case it may output 0V (0) or 5V (1). When you connect the output to the opposite voltage, you will cause a short circuit which quickly exceeds the chip&amp;#039;s maximum power dissipation of 750 mW. This means you will fry the chip.&lt;br /&gt;
&lt;br /&gt;
Consider the following setup (but don&amp;#039;t try it!).&lt;br /&gt;
&lt;br /&gt;
[[File:Badport9901.png|300px]]&lt;br /&gt;
&lt;br /&gt;
Obviously, port P4 is assumed to be an input. When the switch is open, port P4 will sense a 1 on its input. When the switch is closed, it will sense a 0.&lt;br /&gt;
&lt;br /&gt;
Now what happens when you execute this code?&lt;br /&gt;
&lt;br /&gt;
 CLR  R12&lt;br /&gt;
 SBO  20&lt;br /&gt;
&lt;br /&gt;
This will program port P4 to be an output, and to show a 1 (5V) at the output. SBZ will, in contrast, make this port output a 0. So how do you make it an input port? - This is done by resetting the chip. As long as you don&amp;#039;t use a SBZ/SBO (or a LDCR) on the ports, the ports are inputs.&lt;br /&gt;
&lt;br /&gt;
Nothing has happened up to now. But now imagine that you press the switch. This will put the 1 output to ground, causing the short circuit, and your 9901 is toast.&lt;br /&gt;
&lt;br /&gt;
=== What about the TI console? ===&lt;br /&gt;
&lt;br /&gt;
So can we damage the 9901 in the TI console by a suitable CRU operation? - Luckily not. &lt;br /&gt;
&lt;br /&gt;
The reason is the way how TI used the ports on the mainboard. Most of the ports are configured as outputs. Nothing happens when you reset them to inputs.&lt;br /&gt;
&lt;br /&gt;
* The pure interrupt inputs (/INT1 to /INT6) are safe, as they cannot output anything. They have no output driver that could be fried.&lt;br /&gt;
* P0 and P1 are not connected.&lt;br /&gt;
* P2, P3, and P4 are designed as outputs, so they cannot be turned from an input to an output.&lt;br /&gt;
* P5 is the selector of the Alpha Lock key, so output as well.&lt;br /&gt;
* P6 and P7 (/INT15) are the cassette motor control outputs.&lt;br /&gt;
* P8 (/INT14) is the audio gate output.&lt;br /&gt;
* P9 (/INT13) is the cassette output.&lt;br /&gt;
* P10 (/INT12) is pulled up to 5V via a 10 kOhm resistor. If you put a 0 on this port (5V), the maximum current is 500 µA, which results in a power dissipation of 2.5 mW (far below the maximum 750 mW).&lt;br /&gt;
* P11 (/INT11) is the cassette input. However, there is a resistor of 2.2 kOhm (R413) which effectively guards the port of high currents.&lt;br /&gt;
* P12, P13, P14, P15 (/INT10 ... /INT7) are the keyboard input lines. Again, we have pull-ups of 10 kOhm against +5V, but also 470 Ohm on the line, which means a maximum of 8.5 mA or 42.5 mW. This is still well below the maximum value.&lt;br /&gt;
&lt;br /&gt;
So we see that resistors are the best way to protect the ports. And we may calm down; we cannot burn the 9901 in the TI console by a bug in our program.&lt;/div&gt;</summary>
		<author><name>Mizapf</name></author>
	</entry>
</feed>