├── LICENSE ├── README.md ├── firmware ├── .gitignore ├── ERIK1.bin ├── as.sh ├── boot.sh ├── boot99105.asm ├── boot99105_0000.bin ├── cpu_reset_off.bin ├── cpu_reset_on.bin ├── diskdsr.asm ├── diskdsr.lst ├── diskdsr_4000.bin ├── dsr.sh ├── gromsmal.bin ├── init.cmd ├── init.sh ├── kbd.sh ├── list.lst ├── load.cmd ├── reset.cmd ├── reset.sh ├── rxb.sh ├── vdp-raw.bin └── xb.sh ├── fpga ├── ep994a │ ├── ep994a.xise │ └── iseconfig │ │ ├── ep994a.projectmgr │ │ └── ep994a.xreport ├── load.bat ├── src │ ├── dac.vhd │ ├── ep994a.vhd │ ├── gromext.vhd │ ├── pager612.vhd │ ├── pepino.ucf │ ├── serial_rx.v │ ├── serial_tx.v │ ├── serloader.vhd │ ├── tms9918.vhd │ ├── tms9919.vhd │ └── vga_sync.vhd └── work │ ├── .gitignore │ ├── ep994a.bit │ └── ep994a_summary.html ├── memloader ├── .gitignore ├── Makefile ├── diskio.c ├── diskio.h ├── fpga-mem.h ├── memloader-vs │ ├── .gitignore │ ├── .vs │ │ └── memloader-vs │ │ │ └── v14 │ │ │ └── .suo │ ├── memloader-vs.VC.db │ ├── memloader-vs.sln │ └── memloader-vs │ │ ├── memloader-vs.vcxproj │ │ ├── memloader-vs.vcxproj.filters │ │ └── memloader-vs.vcxproj.user └── memloader.c ├── schematics └── tms99105.pdf └── status /LICENSE: -------------------------------------------------------------------------------- 1 | Contact the author speccery@gmail.com for other licensing options. 2 | Currently and by default the EP994A project source code is available 3 | under the LGPL license. The source code comes me with no warranty. 4 | 5 | 6 | 7 | GNU LESSER GENERAL PUBLIC LICENSE 8 | Version 3, 29 June 2007 9 | 10 | Copyright (C) 2007 Free Software Foundation, Inc. 11 | Everyone is permitted to copy and distribute verbatim copies 12 | of this license document, but changing it is not allowed. 13 | 14 | 15 | This version of the GNU Lesser General Public License incorporates 16 | the terms and conditions of version 3 of the GNU General Public 17 | License, supplemented by the additional permissions listed below. 18 | 19 | 0. Additional Definitions. 20 | 21 | As used herein, "this License" refers to version 3 of the GNU Lesser 22 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 23 | General Public License. 24 | 25 | "The Library" refers to a covered work governed by this License, 26 | other than an Application or a Combined Work as defined below. 27 | 28 | An "Application" is any work that makes use of an interface provided 29 | by the Library, but which is not otherwise based on the Library. 30 | Defining a subclass of a class defined by the Library is deemed a mode 31 | of using an interface provided by the Library. 32 | 33 | A "Combined Work" is a work produced by combining or linking an 34 | Application with the Library. The particular version of the Library 35 | with which the Combined Work was made is also called the "Linked 36 | Version". 37 | 38 | The "Minimal Corresponding Source" for a Combined Work means the 39 | Corresponding Source for the Combined Work, excluding any source code 40 | for portions of the Combined Work that, considered in isolation, are 41 | based on the Application, and not on the Linked Version. 42 | 43 | The "Corresponding Application Code" for a Combined Work means the 44 | object code and/or source code for the Application, including any data 45 | and utility programs needed for reproducing the Combined Work from the 46 | Application, but excluding the System Libraries of the Combined Work. 47 | 48 | 1. Exception to Section 3 of the GNU GPL. 49 | 50 | You may convey a covered work under sections 3 and 4 of this License 51 | without being bound by section 3 of the GNU GPL. 52 | 53 | 2. Conveying Modified Versions. 54 | 55 | If you modify a copy of the Library, and, in your modifications, a 56 | facility refers to a function or data to be supplied by an Application 57 | that uses the facility (other than as an argument passed when the 58 | facility is invoked), then you may convey a copy of the modified 59 | version: 60 | 61 | a) under this License, provided that you make a good faith effort to 62 | ensure that, in the event an Application does not supply the 63 | function or data, the facility still operates, and performs 64 | whatever part of its purpose remains meaningful, or 65 | 66 | b) under the GNU GPL, with none of the additional permissions of 67 | this License applicable to that copy. 68 | 69 | 3. Object Code Incorporating Material from Library Header Files. 70 | 71 | The object code form of an Application may incorporate material from 72 | a header file that is part of the Library. You may convey such object 73 | code under terms of your choice, provided that, if the incorporated 74 | material is not limited to numerical parameters, data structure 75 | layouts and accessors, or small macros, inline functions and templates 76 | (ten or fewer lines in length), you do both of the following: 77 | 78 | a) Give prominent notice with each copy of the object code that the 79 | Library is used in it and that the Library and its use are 80 | covered by this License. 81 | 82 | b) Accompany the object code with a copy of the GNU GPL and this license 83 | document. 84 | 85 | 4. Combined Works. 86 | 87 | You may convey a Combined Work under terms of your choice that, 88 | taken together, effectively do not restrict modification of the 89 | portions of the Library contained in the Combined Work and reverse 90 | engineering for debugging such modifications, if you also do each of 91 | the following: 92 | 93 | a) Give prominent notice with each copy of the Combined Work that 94 | the Library is used in it and that the Library and its use are 95 | covered by this License. 96 | 97 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 98 | document. 99 | 100 | c) For a Combined Work that displays copyright notices during 101 | execution, include the copyright notice for the Library among 102 | these notices, as well as a reference directing the user to the 103 | copies of the GNU GPL and this license document. 104 | 105 | d) Do one of the following: 106 | 107 | 0) Convey the Minimal Corresponding Source under the terms of this 108 | License, and the Corresponding Application Code in a form 109 | suitable for, and under terms that permit, the user to 110 | recombine or relink the Application with a modified version of 111 | the Linked Version to produce a modified Combined Work, in the 112 | manner specified by section 6 of the GNU GPL for conveying 113 | Corresponding Source. 114 | 115 | 1) Use a suitable shared library mechanism for linking with the 116 | Library. A suitable mechanism is one that (a) uses at run time 117 | a copy of the Library already present on the user's computer 118 | system, and (b) will operate properly with a modified version 119 | of the Library that is interface-compatible with the Linked 120 | Version. 121 | 122 | e) Provide Installation Information, but only if you would otherwise 123 | be required to provide such information under section 6 of the 124 | GNU GPL, and only to the extent that such information is 125 | necessary to install and execute a modified version of the 126 | Combined Work produced by recombining or relinking the 127 | Application with a modified version of the Linked Version. (If 128 | you use option 4d0, the Installation Information must accompany 129 | the Minimal Corresponding Source and Corresponding Application 130 | Code. If you use option 4d1, you must provide the Installation 131 | Information in the manner specified by section 6 of the GNU GPL 132 | for conveying Corresponding Source.) 133 | 134 | 5. Combined Libraries. 135 | 136 | You may place library facilities that are a work based on the 137 | Library side by side in a single library together with other library 138 | facilities that are not Applications and are not covered by this 139 | License, and convey such a combined library under terms of your 140 | choice, if you do both of the following: 141 | 142 | a) Accompany the combined library with a copy of the same work based 143 | on the Library, uncombined with any other library facilities, 144 | conveyed under the terms of this License. 145 | 146 | b) Give prominent notice with the combined library that part of it 147 | is a work based on the Library, and explaining where to find the 148 | accompanying uncombined form of the same work. 149 | 150 | 6. Revised Versions of the GNU Lesser General Public License. 151 | 152 | The Free Software Foundation may publish revised and/or new versions 153 | of the GNU Lesser General Public License from time to time. Such new 154 | versions will be similar in spirit to the present version, but may 155 | differ in detail to address new problems or concerns. 156 | 157 | Each version is given a distinguishing version number. If the 158 | Library as you received it specifies that a certain numbered version 159 | of the GNU Lesser General Public License "or any later version" 160 | applies to it, you have the option of following the terms and 161 | conditions either of that published version or of any later version 162 | published by the Free Software Foundation. If the Library as you 163 | received it does not specify a version number of the GNU Lesser 164 | General Public License, you may choose any version of the GNU Lesser 165 | General Public License ever published by the Free Software Foundation. 166 | 167 | If the Library as you received it specifies that a proxy can decide 168 | whether future versions of the GNU Lesser General Public License shall 169 | apply, that proxy's public statement of acceptance of any version is 170 | permanent authorization for you to choose that version for the 171 | Library. 172 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EP994A 2 | My TI-99/4A clone implemented with a TMS99105 CPU and FPGA (master branch). 3 | Another version of the clone (the latest development in soft-cpu-tms9902 branch) includes my own 4 | TMS9900 CPU core written in VHDL. 5 | 6 | See the file LICENSE for license terms. At least for now (without contributors from others) 7 | the source code is made available under the LGPL license terms. 8 | You need to retain copyright notices in the source code. 9 | 10 | Latest changes 11 | -------------- 12 | Commit 2019-01-31: 13 | Merged the soft-cpu-tms9902 branch to soft-cpu branch. I have not used enough git to be great at it, but this first major merge seems to have gone well. There are two generics in top level object: 14 | - **cfg_spi_memloader** enables SPI (microcontroller) boot instead of boot from PC. 15 | - **cfg_hw_keyboard** enables the use of an genuine TI-99/4A keyboard with the FPGA. The keyboard needs to be wired to the 20-pin GPIO connector. 16 | - I haven't tested these flags; they were features incorporated to the **soft-cpu** branch earlier, while my main development branch has been the **soft-cpu-tms9902** branch, which includes the TMS9902 compatible UART. Now the soft-cpu branch includes all features. 17 | - Before the merge I have been working on optimizing the CPU core, removing unneeded states which didn't do much and adding some shortcuts in the instruction decode states, avoiding some more unnecessary states. 18 | - The net effect of the optimizations so far is that my stupid Basic benchmark now runs at 39x faster than the original TI (when the cache is enabled). This is still slow, but probably the world's fastest TI-99/4A implementation still. 19 | 20 | 21 | Commit 2019-01-19: 22 | **cache** 23 | - Added a system level cache, outside of the CPU core (soft-cpu-tms9902 branch). 24 | - The cache size is is 1K byte for code and data. Occupies a 512x36bit RAM block. The bottom 16 bits of each of the 512 entries is the 16-bit data word, followed by tag bits. The tag is 10 bits. 25 | - The topmost bit, bit 35, indicates if a cache entry is valid. When set, the entry is valid. On reset the cache cycles through all 512 locations and zeroes them out, thus invalidating all entries of the cache. 26 | - The update policy is write-through. Thus all memory writes go to main memory. The cache will also allocate entries on writes, if the memory region being written to is cacheable. 27 | - During reads the read is simultaneously started for both main memory and the cache. Cache read takes one cycle. Another cycle is currently required for tag comparison. Thus read cycles which hit cache take two clocks. Any main memory cycles will be aborted if a cache hit is detected. 28 | - Using my simple stupid BASIC test program the cache improves performance by 22% in the current very simplistic setup. At 100MHz the system reaches over 30x the performance of the original TMS9900. 29 | 30 | ``` 31 | 10 for i=0 to 1000 32 | 20 print i;" "; 33 | 30 next i 34 | ``` 35 | **video** 36 | - A small modification to the TMS9918 core to better center horizontally the video output. 37 | 38 | Commit 2018-09-22: 39 | - After a long while worked on the project. This was pretty much trying to remember where I was in the project. 40 | 41 | - I synthesized again the master branch and also worked on the soft-cpu-tms9902 branch. The master branch is the branch which supports the TMS99105 CPU on the daughterboard / shield that I designed two years ago. Still works. 42 | 43 | - There was some actual progress on the aforementioned **soft-cpu-tms9902** branch. I clarified the naming and processing of reset signals. Thanks to this now two bugs are fixed: 44 | - Sound works (again?) now that the audio DAC is not constantly being reset. 45 | 46 | - The serloader component (handling communication from the host PC over USB serial port to the memory of the TMS9900 via DMA) was being reset by mistake while the CPU was placed to reset. Now if the host PC put the CPU to reset, that reset would also the serloader, effectively preventing any furhter communication with the system. This of course sucks big time, as the main use case for putting the CPU to reset in the first place is to load software to the memory of the TMS9900 system. 47 | 48 | - I know that my FPGA CPU core has bugs, and I found a repeatable one: running BASIC and doing a simple multiplication with PRINT 1*-1 yields always 1 (plus one) with my FPGA CPU, while an actual TI-99/4A or my TMS99105 FPGA system (i.e. the master branch using real CPU silicon) yields -1 as they should. So this bug can be observed with high level software... There we go. It is a miracle BASIC runs in the first place. The bug is probably related to CPU flag handling. I also have been reported by **pnr** that my divider implementation does not work properly in all cases, so need to check that too. 49 | 50 | Good to be back with the project. 51 | 52 | 53 | Commit 2018-01-03: 54 | - So I have been very lazy at updating this README file. There has been a ton of changes. 55 | Note that there are two branches, master branch contains the **TMS99105** version and soft-cpu contains the **FPGA CPU** version. 56 | 57 | 58 | Commit 2016-11-13: 59 | 60 | - Added firmware/diskdsr.asm which is a Device Service Routine for disk I/O support. It currently 61 | registers DSK1 and DSK2. It support LOAD and SAVE opcodes. Support means that it will 62 | pass the PAB to the PC host to read by copying it to system RAM at address 0x8020. 63 | There is a command buffer at address 0x800A..0x8013 which is used for communication between 64 | the TMS99105 system and the host PC. 65 | 66 | - Refactored memloader code: 67 | - Added disk io support. Now if memloader is started with the command "-k" it 68 | will not only poll keyboard but also poll memory location updated by the DSR when 69 | disk I/O requests happen. 70 | 71 | - Memloader now parses command line arguments better. Output is less verbose. 72 | 73 | - FPGA code now supports SAMS memory extension, currently configured to 256Kbytes. 74 | This required a bunch of other changes, as the scratchpad area needs to be unpaged. 75 | This is done by remapping the scratchpad above the 256K area used by SAMS. 76 | 77 | Hackaday 78 | -------- 79 | Project is documented to an extent at Hackaday and AtariAge TI-99/4A forums. 80 | 81 | https://hackaday.io/project/15430-rc201699-ti-994a-clone-using-tms99105-cpu 82 | 83 | AtariAge 84 | -------- 85 | The AtariAge forum thread talks about my other FPGA project as well, but contains information about 86 | http://atariage.com/forums/topic/255855-ti-994a-with-a-pipistrello-fpga-board/page-8 87 | 88 | About the directories 89 | --------------------- 90 | **firmware** test software I used to debug the hardware. Written in assembler. Also some loading scripts. 91 | - 2016-11-13 now here is also the diskdsr.asm assembly module, which implements a starting point for disk access. Currently it relies on support by the PC program "memloader". 92 | 93 | **fpga** the VHDL code implementing the TI-99/4A (except the CPU). 94 | 95 | **memloader** a program for Windows (compiled with Cygwin) to transfer data from PC to the FPGA. This program is used for a few purposes: 96 | - load software from PC to the memory of the EP994A 97 | - reset the EP994A 98 | - pass keypresses from host PC to the EP994A 99 | - 2016-11-13: poll certain memory locations to enable disk access, i.e. saving and loading 100 | - 2016-11-13: Now there are project files for Visual Studio 2015 community edition. This is just a great IDE and speeds up programming. 101 | 102 | **schematics** the schematics of the protoboard (incl. CPU, clock, a buffer chip) connected to the FPGA board. Note: the schematics are in a need of an update, the current version lacks to wires: 103 | - CPU reset from FPGA to buffer to CPU 104 | - VDP interrupt signal from FPGA to buffer to CPU 105 | -------------------------------------------------------------------------------- /firmware/.gitignore: -------------------------------------------------------------------------------- 1 | 994AGROM.Bin 2 | 994aROM.Bin 3 | TIExtC.Bin 4 | TIExtD.Bin 5 | TIExtG.Bin 6 | 10000.bin 7 | 11000.bin 8 | 12000.bin 9 | 800C-read.bin 10 | 800C-read.bin.bak 11 | 800C.bin 12 | 800C.bin.bak 13 | DSK1/ 14 | ams-orig-hex.txt 15 | ams.bin 16 | ams.bin.bak 17 | dsk1_/ 18 | dsk2/ 19 | pad.bin 20 | pad11.bin 21 | pad2.bin 22 | pad3.bin 23 | pad4.bin 24 | read-ams-hex.txt 25 | read-amstest4.bin 26 | screen-amstest.c 27 | spad.bin 28 | sprites.bin 29 | -------------------------------------------------------------------------------- /firmware/ERIK1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Speccery/EP994A/4a469a13e57afc1def67a5ad3dd15186b6e03374/firmware/ERIK1.bin -------------------------------------------------------------------------------- /firmware/as.sh: -------------------------------------------------------------------------------- 1 | xas99.py -b -R -L list.lst boot99105.asm -------------------------------------------------------------------------------- /firmware/boot.sh: -------------------------------------------------------------------------------- 1 | # boot.sh 2 | # EP 2016-11-13 3 | ../memloader/memloader -5 100008 cpu_reset_on.bin 4 | ../memloader/memloader -5 BA000 boot99105_0000.bin 5 | # CPU out of reset 6 | ../memloader/memloader -5 100008 cpu_reset_off.bin 7 | ../memloader/memloader -5 -k 8 | -------------------------------------------------------------------------------- /firmware/boot99105.asm: -------------------------------------------------------------------------------- 1 | * Erik Piehl (C) 2016 October 2 | * boot99105.asm 3 | * 4 | * This is a test program intended to test various pieces of 5 | * functionality of the EP994A. 6 | * 7 | * Compile with: xas99.py -b -R -L list.lst boot99105.asm 8 | * Load with: ../memloader/memloader 0 boot99105_0000.bin 9 | * 10 | 11 | IDT 'BOOT99' 12 | 13 | WRKSP EQU >8300 14 | PRINTWS EQU >8320 15 | PRTR1P EQU >8322 ; Input for PRINTR1 16 | DELAYWS EQU >8340 17 | TMPBUF EQU >A040 18 | INTWS EQU >A000 ; interrupt workspace 19 | 20 | OUTP EQU >B000 21 | VDPRD EQU >8800 22 | VDPST EQU >8802 23 | VDPWD EQU >8C00 * VDP write data 24 | VDPWA EQU >8C02 * VDP set read/write address 25 | 26 | *--------------------------------------------- 27 | * Macro: printString 28 | * (would be xop ,14 for the TMS9995 BB) 29 | *--------------------------------------------- 30 | .defm printString 31 | BLWP @PRINTS 32 | DATA #1 33 | .endm 34 | 35 | *--------------------------------------------- 36 | * Macro: printNumber 37 | * (would be xop ,10 for the TMS9995 BB) 38 | *--------------------------------------------- 39 | .defm printNumber 40 | MOV #1,@PRTR1P ; Store HEX value to print 41 | BLWP @PRINTR1 ; Go print R1 42 | .endm 43 | 44 | *--------------------------------------------- 45 | * Macro: printCrLf 46 | *--------------------------------------------- 47 | .defm printCrLf 48 | BLWP @PRINTS 49 | DATA TXTCRLF 50 | .endm 51 | 52 | 53 | *--------------------------------------------- 54 | * Macro: printChar 55 | * (would be xop ,12 for the TMS9995 BB) 56 | *--------------------------------------------- 57 | .defm printChar 58 | MOVB #1,@PRTR1P 59 | BLWP @PRINTCH 60 | .endm 61 | 62 | 63 | ; AORG 0 64 | DATA WRKSP,BOOT 65 | DATA INTWS,VDPINT 66 | DATA INTWS,VDPINT 67 | 68 | PRINTS DATA PRINTWS,GOPRINT 69 | PRINTR1 DATA PRINTWS,GOPR1 70 | PRINTCH DATA PRINTWS,GOPRCHAR 71 | CLS DATA PRINTWS,GOCLS 72 | 73 | NICEDELAY DATA DELAYWS,GODELAY 74 | 75 | ; Our interrupt routine 76 | VDPINT 77 | LIMI 0 78 | INC R0 ; R0 counts interrupts 79 | CLR R12 80 | STCR R2,0 ; See the TMS9901 register, low 16 bits 81 | TB 2 82 | JNE !ok 83 | LI R3,>FFFF ; error 84 | JMP ! 85 | !ok 86 | LI R3,>1234 ; OK 87 | ! 88 | MOVB @VDPST,R1 ; clear the VDP interrupt request 89 | LI R5,>100 90 | AB R5,@>8379 ; Increment frame counter in scratchpad 91 | RTWP 92 | 93 | 94 | BOOT 95 | LIMI 0 96 | ; BOOTLP 97 | ; JMP BOOTLP 98 | ; NOP 99 | ; NOP 100 | ; JMP BOOTLP 101 | 102 | LWPI WRKSP 103 | CLR @INTWS ; Zero INTWS.R0, the interrupt counter. 104 | 105 | MOVB R3,@VDPWD ; Dummy write to data, to reset latch 106 | ; Write initial values to VDP registers 107 | LI R1,VDPSEQ 108 | LI R2,>8000 ; command, write register 0 109 | VLP 110 | MOVB *R1+,@VDPWA ; write data 111 | MOVB R2,@VDPWA ; write register number 112 | AI R2,>0100 ; next register 113 | CI R2,>8800 114 | JNE VLP 115 | 116 | ; Clear VDP RAM 117 | CLR R0 118 | LI R1,>4000 119 | SWPB R1 120 | MOVB R1,@VDPWA ; Address to zero 121 | SWPB R1 122 | MOVB R1,@VDPWA 123 | ! 124 | MOVB R0,@VDPWD 125 | DEC R1 126 | JNE -! 127 | 128 | BL @COPYFONTS 129 | 130 | ; Initialize color table with >17 times 32 131 | LI R0,>0380 ; address of color table 132 | BL @SETUPVDPA 133 | LI R1,32 134 | LI R2,>1700 135 | ! 136 | MOVB R2,@VDPWD 137 | DEC R1 138 | JNE -! 139 | 140 | JMP GROM1 141 | 142 | CLR @PRINTWS ; Initial display position 143 | CLR R1 144 | LI R2,>1234 145 | LI R3,>FFFF 146 | LI R4,>2000 147 | LI R5,20 148 | LI R6,>DEF0 149 | LI R7,'3'*256 150 | LINES1 151 | .printNumber R1 152 | .printChar R4 153 | ; LI R6,24 154 | MOV R1,R6 155 | INC R6 156 | ! 157 | .printChar R7 158 | DEC R6 159 | JNE -! 160 | .printCrLf 161 | INC R1 162 | ANDI R1,>1F 163 | CI R1,24 164 | JNE LINES1 165 | 166 | 167 | GROM1 168 | ; Test GROM address counter read and write 169 | LI R0,>40 170 | MOV R0,@PRINTWS 171 | LI R0,>1234 ; write GROM address 172 | LI R9,3 173 | !gromloop 174 | .printString GROMTESTS 175 | MOVB R0,@>9C02 176 | SWPB R0 177 | MOVB R0,@>9C02 178 | SWPB R0 179 | ; read two bytes GROM 180 | CLR R1 181 | MOVB @>9800,R1 182 | MOVB @>9800,R2 183 | SRL R2,8 184 | SOC R2,R1 185 | .printNumber R1 186 | ; and read two more bytes GROM 187 | CLR R1 188 | MOVB @>9800,R1 189 | MOVB @>9800,R2 190 | SRL R2,8 191 | SOC R2,R1 192 | .printNumber R1 193 | ; Now read GROM address and display it 194 | CLR R1 195 | MOVB @>9802,R1 196 | MOVB @>9802,R2 197 | SRL R2,8 198 | SOC R2,R1 199 | .printNumber R1 200 | AI R0,>2000 ; next GROM 201 | DEC R9 202 | JNE -!gromloop 203 | ; Try to read keyboard button '1', but first enable VDP interrupts 204 | CLR R12 ; CRU pointer 205 | SBZ 0 ; Make sure we are not in timer mode 206 | SBO 2 ; Enable VDP interrupts 207 | LIMI 2 ; Enable interrupts 208 | 209 | LI R3,1000 210 | !k CLR R0 211 | MOV R0,@PRINTWS 212 | ! 213 | LI R12,>24 214 | LDCR R0,3 215 | LI R12,6 ; Address to read rows 216 | STCR R1,8 217 | .printNumber R1 218 | .printCrLf 219 | AI R0,>100 220 | CI R0,>600 221 | JNE -! 222 | MOV @INTWS,R1 ; Interrupt counter 223 | .printNumber R1 224 | .printCrLf 225 | .printString CRU0STR 226 | MOV @INTWS+4,R1 ; read reg 2 from interrupt context (CRU bits) 227 | .printNumber R1 ; show them 228 | .printCrLf 229 | MOV @INTWS+6,R1 ; Also show reg 3 230 | .printNumber R1 ; show them 231 | .printCrLf 232 | CLR R12 233 | STCR R1,0 ; Read 16 bits (count of 0 means 16) 234 | .printNumber R1 235 | .printCrLf 236 | DEC R3 237 | JNE -!k 238 | 239 | ; Check if we have defender loaded. If we have let's go! 240 | MOV @>6000,R1 241 | CI R1,>AA01 242 | JNE ! 243 | MOV @>600E,R1 244 | CI R1,>6072 245 | JNE ! 246 | B @>6072 247 | ! 248 | 249 | ;; BLWP @NICEDELAY 250 | ; Do test of GROM memory. Calculate GROM checksums 251 | GROMCHECK 252 | ;; BLWP @CLS 253 | ;; CLR R0 254 | ;; MOV R0,@PRINTWS 255 | .printString GTT 256 | 257 | ; Start of checksum loop 258 | LI R4,3 ; Iteration counter 259 | !gr3 260 | CLR R0 261 | !gr2 262 | MOVB R0,@>9C02 ; Setup GROM address to zero 263 | SWPB R0 264 | MOVB R0,@>9C02 265 | SWPB R0 266 | .printNumber R0 267 | 268 | CLR R1 ; init checksum 269 | LI R2,>1800 270 | !grr 271 | CLR R3 272 | MOVB @>9800,R3 ; get byte 273 | SRC R1,1 274 | A R3,R1 275 | DEC R2 276 | JNE -!grr 277 | .printNumber R1 ; print checksum 278 | ; Get address and print it 279 | CLR R1 280 | MOVB @>9802,R1 281 | MOVB @>9802,R2 282 | SRL R2,8 283 | SOC R2,R1 284 | .printNumber R1 285 | .printCrLf 286 | 287 | AI R0,>2000 288 | CI R0,>6000 289 | JNE -!gr2 290 | 291 | DEC R4 292 | JNE -!gr3 ; next iteration 293 | 294 | BLWP @NICEDELAY 295 | 296 | 297 | JMP MAINSTART 298 | 299 | NOCLEAR 300 | ; Write fonts 301 | LI R2,CHARS 302 | LI R1,ENDCHARS-CHARS ; count 303 | LI R3,>4300 ; address 300 304 | SWPB R3 305 | MOVB R3,@VDPWA ; low byte of address 306 | SWPB R3 307 | MOVB R3,@VDPWA ; high byte of address 308 | 309 | ! MOVB *R2+,@VDPWD 310 | DEC R1 311 | JNE -! 312 | 313 | TESTCHARS 314 | ; write a few characters 315 | LI R3,>4000 ; address 0 316 | SWPB R3 317 | MOVB R3,@VDPWA ; low byte of address 318 | SWPB R3 319 | MOVB R3,@VDPWA ; high byte of address 320 | 321 | LI R5,>80 322 | LPLP 323 | LI R0,>2021 ; >6061 324 | MOVB R0,@VDPWD 325 | SWPB R0 326 | MOVB R0,@VDPWD 327 | SWPB R0 328 | LI R0,>3031 ;>6263 329 | MOVB R0,@VDPWD 330 | SWPB R0 331 | MOVB R0,@VDPWD 332 | SWPB R0 333 | DEC R5 334 | JNE LPLP 335 | 336 | 337 | MAINSTART 338 | LI R1,OUTP 339 | LI R0,>100 340 | LI R3,20 341 | 342 | MAINLOOP 343 | MOVB R0,*R1 344 | SLA R0,1 345 | JNE ! 346 | LI R0,>100 347 | ! CLR R2 348 | !delay 349 | DEC R2 350 | JNE -!delay 351 | DEC R3 352 | JNE MAINLOOP 353 | ; Copy the dump to VDP memory 354 | LI R0,0 355 | BL @SETUPVDPA 356 | LI R1,VDPDUMP 357 | LI R2,DUMPEND-VDPDUMP 358 | VCOPYLP 359 | MOVB *R1+,@VDPWD 360 | DEC R2 361 | ; MOVB *R1+,@VDPWD 362 | ; MOVB *R1+,@VDPWD 363 | ; DECT R2 364 | JNE VCOPYLP 365 | 366 | ; Now delay loop 367 | LI R1,10 368 | ! DEC R2 369 | JNE -! 370 | DEC R1 371 | JNE -! 372 | ; Make a small animation 373 | LI R4,VDPDUMP+768-32 ; -32 for message 374 | LI R5,0 ; Scroll offset 375 | LI R6,0 376 | LI R8,32 377 | MOV R1,R2 378 | 379 | LI R9,10000 ; our delay 380 | 381 | !again 382 | LI R1,VDPDUMP 383 | CLR R0 384 | BL @SETUPVDPA 385 | ; Animate 1 row 386 | !row 387 | MOV R6,R7 388 | A R5,R7 389 | ANDI R7,>1F 390 | A R1,R7 391 | MOVB *R7,@VDPWD 392 | INC R6 ; R6 counts our columns 393 | C R6,R8 394 | JNE -!row 395 | CLR R6 396 | AI R1,32 397 | C R1,R4 398 | JNE -!row 399 | ; Increment our offset 400 | INC R5 401 | ANDI R5,31 402 | ; Make our small delay 403 | CI R9,0 404 | JEQ -!again ; if delay is zero, no more printing 405 | 406 | MOV R9,R7 407 | !del DEC R7 408 | JNE -!del 409 | 410 | ; decrement delay 411 | AI R9,-10 412 | ; Display a message from our sponsor 413 | LI R0,23*32 414 | MOV R0,@PRINTWS 415 | .printString TMS99105 416 | .printNumber R9 417 | .printString SPACES 418 | ; Proceed to second row directly 419 | AI R1,32 420 | 421 | 422 | JMP -!again 423 | 424 | 425 | 426 | 427 | JMP MAINSTART 428 | 429 | *--------------------------------------------- 430 | * Set VDP address from R0 431 | *--------------------------------------------- 432 | SETUPVDPA 433 | ANDI R0,>3FFF 434 | SWPB R0 435 | MOVB R0,@VDPWA * Send low byte of VDP RAM write address 436 | SWPB R0 437 | ORI R0,>4000 * Set read/write bits 14 and 15 to write (01) 438 | MOVB R0,@VDPWA * Send high byte of VDP RAM write address 439 | RT 440 | 441 | *--------------------------------------------- 442 | * Scroll up - test VDP reads 443 | * For reads top 2 bits are zero 444 | *--------------------------------------------- 445 | SCROLLUP 446 | MOV R11,R10 * Save return address 447 | LI R6,>20 * VRAM read address 448 | CLR R0 * VRAM write address 449 | LI R7,23 * 23 lines 450 | 451 | !scrollloop 452 | SWPB R6 453 | MOVB R6,@VDPWA * Send low byte of VDP RAM read address 454 | SWPB R6 455 | MOVB R6,@VDPWA 456 | LI R2,>20 457 | LI R5,TMPBUF 458 | LI R1,VDPRD * VDP read address 459 | !rdloop 460 | MOVB *R1,*R5+ * read byte from VRAM 461 | MOVB *R1,*R5+ * read byte from VRAM 462 | DECT R2 463 | JNE -!rdloop 464 | ; Next write the same stuff to the previous line 465 | BL @SETUPVDPA 466 | LI R2,>20 467 | LI R5,TMPBUF 468 | LI R1,VDPWD * VDP read address 469 | !wrloop 470 | MOVB *R5+,*R1 471 | MOVB *R5+,*R1 472 | DECT R2 473 | jne -!wrloop 474 | AI R0,>20 475 | AI R6,>20 476 | DEC R7 477 | JNE -!scrollloop 478 | B *R10 * Return 479 | 480 | * copy fonts from GROMs to pattern table 481 | COPYFONTS 482 | MOV R11,R10 ; Save return address 483 | LI R0,>6B4 * setup GROM source address of font table 484 | LI R7,GROM0 485 | A R0,R7 486 | ; MOVB R0,@>9C02 487 | ; SWPB R0 488 | ; MOVB R0,@>9C02 489 | LI R0,>800+(32*8) * destination address in VRAM 490 | BL @SETUPVDPA 491 | LI R0,62 * 62 characters to copy 492 | CLR R2 493 | !ch2 494 | LI R1,7 * 7 bytes per char 495 | !char 496 | MOVB *R7+,@VDPWD * move byte from GROM to VDP 497 | DEC R1 498 | JNE -!char 499 | MOVB R2,@VDPWD * 8th byte just zero 500 | DEC R0 501 | JNE -!ch2 502 | B *R10 ; return 503 | 504 | 505 | HEXWORD ; EP display R1 in hex in the current VDP location 506 | LI R3,4 507 | HEXLOOP 508 | MOV R11,R10 509 | MOV R1,R2 510 | ! BL @HEXNIBBLE 511 | SLA R2,4 512 | MOV R2,R1 513 | DEC R3 514 | JNE -! 515 | B *R10 516 | 517 | HEXBYTE ; Display most significant byte of R1 in the current VDP location 518 | LI R3,2 519 | JMP HEXLOOP 520 | 521 | 522 | HEXNIBBLE ; EP display top 4 bits of R1 in current VDP RAM location 523 | SRL R1,4 524 | ANDI R1,>0F00 525 | AI R1,>3000 ; Convert to ASCII 526 | CI R1,>3A00 527 | JL ! 528 | AI R1,>0700 529 | ! MOVB R1,@VDPWD 530 | RT 531 | 532 | *-------------------------------------------- 533 | * GOPR1 534 | * Print contents of R1 in the PRINTWS 535 | * workspace. 536 | *-------------------------------------------- 537 | GOPR1 538 | LIMI 0 539 | BL @SETUPVDPA ; Setup dest address 540 | BL @HEXWORD ; print 541 | AI R0,4 ; Advance VDP ptr 542 | RTWP 543 | 544 | *-------------------------------------------- 545 | * *** Print a string *** 546 | * Entered with BLWP, so return with RTWP 547 | * PC points to string pointer, so use R14 548 | * to access it and inc R14 past it. 549 | * R0 in this workspace is VDP RAM pointer. 550 | *-------------------------------------------- 551 | GOPRINT 552 | LIMI 0 ; no more interrupts until RTWP 553 | MOV *R14+,R1 ; Fetch string pointer to R1 554 | BL @SETUPVDPA 555 | ! MOVB *R1+,R2 556 | JEQ !done ; zero ends -> !done 557 | CI R2,>0D00 558 | JEQ -! ; Skip 0xD's 559 | CI R2,>0A00 ; is it linefeed? 560 | JNE !write ; no -> !write 561 | ; Here we are with a linefeed. Update to next line. 562 | AI R0,32 563 | ANDI R0,>FFE0 564 | BL @SETUPVDPA 565 | JMP -! 566 | ; Write the character to the VDP 567 | !write 568 | INC R0 ; update our VDP address 569 | MOVB R2,@VDPWD 570 | JMP -! 571 | !done 572 | RTWP 573 | 574 | *-------------------------------------------- 575 | * GOPRCHAR 576 | * Print a character from R1 high byte, PRINTWS 577 | * workspace. 578 | *-------------------------------------------- 579 | GOPRCHAR 580 | LIMI 0 581 | BL @SETUPVDPA ; Setup dest address 582 | INC R0 ; update our VDP address 583 | MOVB R1,@VDPWD 584 | RTWP 585 | 586 | *-------------------------------------------- 587 | * CLS 588 | * Clear the screen. Enter with BLWP 589 | *-------------------------------------------- 590 | GOCLS 591 | LIMI 0 592 | CLR R0 * Start at top left corner of the screen 593 | LI R1,>2000 * Write a space (>20 hex is 32 decimal) 594 | LI R2,768 * Number of bytes to write 595 | 596 | MOVB @PRINTWS+1,@VDPWA ;* Send low byte of VDP RAM write address 597 | ORI R0,>4000 * Set read/write bits 14 and 15 to write (01) 598 | MOVB R0,@VDPWA * Send high byte of VDP RAM write address 599 | 600 | ! MOVB R1,@VDPWD * Write byte to VDP RAM 601 | DEC R2 * Byte counter 602 | JNE -! * Check if done 603 | 604 | LI R0,1 * second column 605 | BL @SETUPVDPA 606 | RTWP 607 | 608 | *-------------------------------------------- 609 | * DELAY that takes enough time for a human 610 | * to see something on the screen. 611 | * Enter with BLWP 612 | *-------------------------------------------- 613 | GODELAY 614 | ; Stop here for a while 615 | CLR R0 616 | LI R1,10 617 | !mydelay 618 | DEC R0 619 | JNE -!mydelay 620 | DEC R1 621 | JNE -!mydelay 622 | ; Next scroll the screen up 623 | BL @SCROLLUP 624 | ; Stop here for a while 625 | CLR R0 626 | LI R1,10 627 | !mydelay 628 | DEC R0 629 | JNE -!mydelay 630 | DEC R1 631 | JNE -!mydelay 632 | RTWP 633 | 634 | EVEN 635 | CHARS 636 | BYTE 0,0,0,0,0,0,0,0 637 | BYTE >00,>18,>24,>42,>42,>7E,>42,>42 638 | BYTE >80,>80,>80,>80,>80,>80,>80,>80 639 | BYTE >01,>03,>01,>01,>01,>01,>01,>01 640 | EVEN 641 | ENDCHARS 642 | ; Initial VDP register values 643 | VDPSEQ 644 | BYTE >00,>E0,>00,>0E ; Interrupts enabled 645 | ; BYTE >00,>C0,>00,>0E ; Interrupts disabled 646 | BYTE >01,>06,>00,>F7 647 | TXTCRLF 648 | BYTE >0D,>0A 649 | BYTE >00 650 | EVEN 651 | 652 | GROM0 BCOPY "GROMSMAL.BIN" 653 | EVEN 654 | VDPDUMP BCOPY "vdp-raw.bin" 655 | DUMPEND 656 | TMS99105 TEXT 'TMS99105 AND FPGA AT WORK! ' 657 | BYTE 0 658 | EVEN 659 | SPACES TEXT ' ' 660 | BYTE 0 661 | GTT TEXT 'GROM CHECKSUM TEST:' 662 | BYTE 10,13,0 663 | CRU0STR TEXT 'CRU FROM 0: ' 664 | BYTE 0 665 | EVEN 666 | GROMTESTS 667 | TEXT 'GROM READ TEST:1234>' 668 | BYTE 0 669 | EVEN 670 | 671 | SLAST END BOOT 672 | 673 | -------------------------------------------------------------------------------- /firmware/boot99105_0000.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Speccery/EP994A/4a469a13e57afc1def67a5ad3dd15186b6e03374/firmware/boot99105_0000.bin -------------------------------------------------------------------------------- /firmware/cpu_reset_off.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Speccery/EP994A/4a469a13e57afc1def67a5ad3dd15186b6e03374/firmware/cpu_reset_off.bin -------------------------------------------------------------------------------- /firmware/cpu_reset_on.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Speccery/EP994A/4a469a13e57afc1def67a5ad3dd15186b6e03374/firmware/cpu_reset_on.bin -------------------------------------------------------------------------------- /firmware/diskdsr.asm: -------------------------------------------------------------------------------- 1 | * diskdsr.asm 2 | * Disk DSR module for the TMS99105 system EP994A 3 | * Started by Erik Piehl on Oct 7th 2016 4 | * Based on original TI sample from a long time ago. 5 | * 6 | * Compile with: xas99.py -b -R -L diskdsr.lst diskdsr.asm 7 | * 8 | 9 | VDPWD EQU >8C00 * VDP write data 10 | VDPWA EQU >8C02 * VDP set read/write address 11 | VDPRD EQU >8800 * VDP read data 12 | VDPPTR EQU >8890 * VDP extension: VRAM address pointer 13 | 14 | MYRAM EQU >8000 * EP994A scratchpad is 1k in size, starts at >8000 15 | ENTRYN EQU >8002 * Save entry point number 16 | PABSTA EQU >8004 * Start of PAB in VDP memory (also used by host program) 17 | MARKER EQU >8006 18 | RCOUNT EQU >8008 * DSR call count 19 | 20 | *--------------------------------------------------------- 21 | * Variables for host to TMS99105 IOP communication 22 | *--------------------------------------------------------- 23 | DSRWAIT EQU >800A * Set when DSR waits for host 24 | ARG1 EQU >800C * parameters for host to TMS99105 25 | ARG2 EQU >800E 26 | ARG3 EQU >8010 27 | HOSTOP EQU >8012 * Command from host to TMS99105 28 | *--------------------------------------------------------- 29 | MYPABN EQU >801E * Length of my PAB 30 | MYPAB EQU >8020 * PAB is copied from VDP RAM to here. 31 | 32 | RORG >4000 33 | BYTE >AA 34 | BYTE 1 ; version number 35 | DATA 0 ; leave at 0 36 | DATA PWRLINK 37 | DATA 0 38 | DATA DSRLINK 39 | DATA 0 40 | DATA INTLINK 41 | DATA 0 42 | 43 | MYMSG 44 | TEXT 'TMS99105 FPGA SYSTEM ERIK PIEHL' 45 | BYTE 0 46 | EVEN 47 | 48 | * Powerup routine 49 | PWRLINK 50 | DATA 0 ; Linkage - none 51 | DATA PWRUP 52 | BYTE 0 ; Name length 53 | EVEN ; 54 | 55 | ; Display something on the screen 56 | PWRUP 57 | MOV R11,R4 ; Save return address 58 | LI R0,(13*32+0) 59 | LI R1,MYMSG 60 | BL @SETUPVDPA 61 | ! 62 | MOVB *R1+,R2 63 | JEQ !done 64 | MOVB R2,@VDPWD 65 | JMP -! 66 | 67 | !done 68 | 69 | ; Clear 256 bytes from >8000 onwards 70 | ; CLR @RCOUNT 71 | ; CLR @DSRWAIT 72 | ; CLR @HOSTOP 73 | LI R0,MYRAM 74 | LI R1,256 75 | ! CLR *R0+ 76 | DECT R1 77 | JNE -! 78 | 79 | LI R0,>5678 80 | MOV R0,@MARKER 81 | LI R0,>BEEF 82 | MOV R0,@MARKER+2 83 | 84 | 85 | B *R4 86 | 87 | * Interrupt routine 88 | INTLINK 89 | DATA 0 90 | DATA INTDSR 91 | BYTE 0 92 | EVEN 93 | INTDSR ; Erik we do nothing here 94 | RT 95 | 96 | * Main device service routine. 97 | DSRLINK 98 | DATA DSRLINK2 99 | DATA ENTRY1 100 | BYTE 3 101 | TEXT 'DSK' 102 | EVEN 103 | DSRLINK2 104 | DATA DSRLINK3 105 | DATA ENTRY2 106 | BYTE 4 107 | TEXT 'DSK1' 108 | EVEN 109 | DSRLINK3 110 | DATA 0 ; No next device 111 | DATA ENTRY3 112 | BYTE 4 113 | TEXT 'DSK2' 114 | EVEN 115 | 116 | ENTRY1 117 | CLR R0 118 | JMP GO4IT 119 | 120 | ENTRY2 121 | LI R0,1 122 | JMP GO4IT 123 | 124 | ENTRY3 125 | LI R0,2 126 | 127 | * The common start of the actual DSR 128 | GO4IT 129 | MOV R11,@MYRAM ; Save return address 130 | MOV R0,@ENTRYN 131 | INC @RCOUNT 132 | ; LI R1,>1234 133 | STST R1 ; Read CPU status register 134 | MOV R1,@MARKER 135 | BL @PAB2RAM * Copy PAB to CPU RAM 136 | 137 | * Let's signal the host PC that we have got something nice in here 138 | wait_cmd 139 | CLR @HOSTOP * reset previous host operation 140 | LI R1,1 141 | MOV R1,@DSRWAIT * Host wait flag set 142 | * Now wait for the host to tell us something 143 | !wait_loop 144 | MOV @HOSTOP,R1 145 | JEQ -!wait_loop 146 | CLR @DSRWAIT * Clear flag 147 | * We received a command from the host, do something 148 | * Operations are: 149 | * Read VDP memory: 1 (ARG1=VDP addr, ARG2=CPU RAM addr, ARG3=bytecount) 150 | * Write VDP memory: 2 (ARG1=CPU addr, ARG2=VDP RAM addr, ARG3=bytecount) 151 | * Exit DSR: 3 (ARG1=error code or success, top 3 bits ored with PAB status) 152 | DEC R1 153 | JEQ !read_vdp 154 | DEC R1 155 | JEQ !write_vdp 156 | DEC R1 157 | JEQ !set_return 158 | JMP wait_cmd ; If we come here the host code was unexpected. Ignore it. 159 | 160 | !read_vdp 161 | MOV @ARG1,R0 ; VDP source addr 162 | MOV @ARG2,R1 ; CPU RAM addr 163 | MOV @ARG3,R2 164 | BL @VDPREADA 165 | ! MOVB @VDPRD,*R1+ 166 | DEC R2 167 | JNE -! 168 | JMP wait_cmd 169 | 170 | !write_vdp 171 | MOV @ARG1,R1 ; CPU RAM source addr 172 | MOV @ARG2,R0 ; VDP RAM addr 173 | MOV @ARG3,R2 174 | BL @SETUPVDPA ; Setup VDP write address 175 | ! MOVB *R1+,@VDPWD 176 | DEC R2 177 | JNE -! 178 | JMP wait_cmd 179 | 180 | !set_return 181 | * Let's report a device error back, error code 6 182 | MOVB @MYPAB+1,R1 ; read status byte 183 | ANDI R1,>1FFF 184 | SOC @ARG1,R1 ; Or in the bits set by host 185 | ;; ORI R1,>C000 ; 6 << 5 << 8 - OR the status 6 in there 186 | MOV @PABSTA,R0 187 | INC R0 ; point to status byte 188 | BL @SETUPVDPA 189 | MOVB R1,@VDPWD * Write back the status, indicating error 190 | 191 | * Return to console software 192 | EXIT 193 | MOV @MYRAM,R11 194 | INCT R11 195 | RT 196 | 197 | * Copy the PAB from VDP RAM to system RAM to make it 198 | * visible to the PC. 199 | PAB2RAM 200 | MOV @>8354,R1 * DSR Name length 201 | MOV @>8356,R2 * Ptr to first char after name in VDP memory. 202 | MOV R2,R3 203 | S R1,R3 204 | AI R3,-10 ; -10: R3 is now the pointer to start of PAB 205 | 206 | MOV R3,R0 * VDP address to R0 207 | MOV R3,@PABSTA * Store PAB start for later 208 | AI R1,10 * 10 bytes plus name length 209 | * The length now is to the period character, so for example to 210 | * DSK1.HELLO it would be just after DSK1. 211 | MOV R1,@MYPABN * save length of my PAB 212 | LI R3,MYPAB 213 | MOV R11,R2 * Save return address 214 | BL @VDPREADA * Setup VDP read address to start of PAB 215 | * Copy to RAM loop 216 | ! MOVB @VDPRD,*R3+ 217 | DEC R1 218 | JNE -! 219 | * Now that we have the PAB in CPU memory, let's also read 220 | * the filename to CPU memory. VDP memory pointer already points 221 | * to the right place. 222 | MOVB @MYPAB+9,R1 ; name length (DSR name + filename) 223 | SRL R1,8 ; shift to lower byte 224 | S @>8354,R1 ; substract DSR name length 225 | JEQ !done ; if zero exit 226 | ! MOVB @VDPRD,*R3+ ; Otherwise copy filename 227 | DEC R1 228 | JNE -! 229 | !done 230 | B *R2 231 | 232 | 233 | *--------------------------------------------- 234 | * Set VDP read address from R0 235 | *--------------------------------------------- 236 | VDPREADA 237 | ANDI R0,>3FFF * make sure it is a read command 238 | SWPB R0 239 | MOVB R0,@VDPWA * Send low byte of VDP RAM write address 240 | SWPB R0 241 | MOVB R0,@VDPWA * Send high byte of VDP RAM write address 242 | RT 243 | 244 | *--------------------------------------------- 245 | * Set VDP address from R0 246 | *--------------------------------------------- 247 | SETUPVDPA 248 | SWPB R0 249 | MOVB R0,@VDPWA * Send low byte of VDP RAM write address 250 | SWPB R0 251 | ORI R0,>4000 * Set read/write bits 14 and 15 to write (01) 252 | MOVB R0,@VDPWA * Send high byte of VDP RAM write address 253 | RT -------------------------------------------------------------------------------- /firmware/diskdsr.lst: -------------------------------------------------------------------------------- 1 | XAS99 CROSS-ASSEMBLER VERSION 1.5.3 2 | **** **** **** > diskdsr.asm 3 | 0001 * diskdsr.asm 4 | 0002 * Disk DSR module for the TMS99105 system EP994A 5 | 0003 * Started by Erik Piehl on Oct 7th 2016 6 | 0004 * Based on original TI sample from a long time ago. 7 | 0005 * 8 | 0006 * Compile with: xas99.py -b -R -L diskdsr.lst diskdsr.asm 9 | 0007 * 10 | 0008 11 | 0009 8C00 VDPWD EQU >8C00 * VDP write data 12 | 0010 8C02 VDPWA EQU >8C02 * VDP set read/write address 13 | 0011 8800 VDPRD EQU >8800 * VDP read data 14 | 0012 8890 VDPPTR EQU >8890 * VDP extension: VRAM address pointer 15 | 0013 16 | 0014 8000 MYRAM EQU >8000 * EP994A scratchpad is 1k in size, starts at >8000 17 | 0015 8002 ENTRYN EQU >8002 * Save entry point number 18 | 0016 8004 PABSTA EQU >8004 * Start of PAB in VDP memory 19 | 0017 8006 MARKER EQU >8006 20 | 0018 8008 RCOUNT EQU >8008 * DSR call count 21 | 0019 22 | 0020 *--------------------------------------------------------- 23 | 0021 * Variables for host to TMS99105 IOP communication 24 | 0022 *--------------------------------------------------------- 25 | 0023 800A DSRWAIT EQU >800A * Set when DSR waits for host 26 | 0024 800C ARG1 EQU >800C * parameters for host to TMS99105 27 | 0025 800E ARG2 EQU >800E 28 | 0026 8010 ARG3 EQU >8010 29 | 0027 8012 HOSTOP EQU >8012 * Command from host to TMS99105 30 | 0028 *--------------------------------------------------------- 31 | 0029 801E MYPABN EQU >801E * Length of my PAB 32 | 0030 8020 MYPAB EQU >8020 * PAB is copied from VDP RAM to here. 33 | 0031 34 | 0032 RORG >4000 35 | 0033 4000 AA01 BYTE >AA 36 | 0034 BYTE 1 ; version number 37 | 0035 4002 0000 DATA 0 ; leave at 0 38 | 0036 4004 4030r DATA PWRLINK 39 | 0037 4006 0000 DATA 0 40 | 0038 4008 4076r DATA DSRLINK 41 | 0039 400A 0000 DATA 0 42 | 0040 400C 406Er DATA INTLINK 43 | 0041 400E 0000 DATA 0 44 | 0042 45 | 0043 MYMSG 46 | 0044 4010 .... TEXT 'TMS99105 FPGA SYSTEM ERIK PIEHL' 47 | 0045 BYTE 0 48 | 0046 EVEN 49 | 0047 50 | 0048 * Powerup routine 51 | 0049 PWRLINK 52 | 0050 4030 0000 DATA 0 ; Linkage - none 53 | 0051 4032 4036r DATA PWRUP 54 | 0052 4034 0000 BYTE 0 ; Name length 55 | 0053 EVEN ; 56 | 0054 57 | 0055 ; Display something on the screen 58 | 0056 PWRUP 59 | 0057 4036 C10B 18 MOV R11,R4 ; Save return address 60 | 0058 4038 0200 20 LI R0,(13*32+0) 61 | 403A 01A0 62 | 0059 403C 0201 20 LI R1,MYMSG 63 | 403E 4010r 64 | 0060 4040 06A0 32 BL @SETUPVDPA 65 | 4042 4188r 66 | 0061 ! 67 | 0062 4044 D0B1 28 MOVB *R1+,R2 68 | 0063 4046 1303 14 JEQ !done 69 | 0064 4048 D802 38 MOVB R2,@VDPWD 70 | 404A 8C00 71 | 0065 404C 10FB 14 JMP -! 72 | 0066 73 | 0067 !done 74 | 0068 75 | 0069 ; Clear 256 bytes from >8000 onwards 76 | 0070 ; CLR @RCOUNT 77 | 0071 ; CLR @DSRWAIT 78 | 0072 ; CLR @HOSTOP 79 | 0073 404E 0200 20 LI R0,MYRAM 80 | 4050 8000 81 | 0074 4052 0201 20 LI R1,256 82 | 4054 0100 83 | 0075 4056 04F0 30 ! CLR *R0+ 84 | 0076 4058 0641 14 DECT R1 85 | 0077 405A 16FD 14 JNE -! 86 | 0078 87 | 0079 405C 0200 20 LI R0,>5678 88 | 405E 5678 89 | 0080 4060 C800 38 MOV R0,@MARKER 90 | 4062 8006 91 | 0081 4064 0200 20 LI R0,>BEEF 92 | 4066 BEEF 93 | 0082 4068 C800 38 MOV R0,@MARKER+2 94 | 406A 8008 95 | 0083 96 | 0084 97 | 0085 406C 0454 20 B *R4 98 | 0086 99 | 0087 * Interrupt routine 100 | 0088 INTLINK 101 | 0089 406E 0000 DATA 0 102 | 0090 4070 4074r DATA INTDSR 103 | 0091 4072 0000 BYTE 0 104 | 0092 EVEN 105 | 0093 INTDSR ; Erik we do nothing here 106 | 0094 4074 045B 20 RT 107 | 0095 108 | 0096 * Main device service routine. 109 | 0097 DSRLINK 110 | 0098 4076 407Er DATA DSRLINK2 111 | 0099 4078 4092r DATA ENTRY1 112 | 0100 407A 0344 BYTE 3 113 | 0101 407B .... TEXT 'DSK' 114 | 0102 EVEN 115 | 0103 DSRLINK2 116 | 0104 407E 4088r DATA DSRLINK3 117 | 0105 4080 4096r DATA ENTRY2 118 | 0106 4082 0444 BYTE 4 119 | 0107 4083 .... TEXT 'DSK1' 120 | 0108 EVEN 121 | 0109 DSRLINK3 122 | 0110 4088 0000 DATA 0 ; No next device 123 | 0111 408A 409Cr DATA ENTRY3 124 | 0112 408C 0444 BYTE 4 125 | 0113 408D .... TEXT 'DSK2' 126 | 0114 EVEN 127 | 0115 128 | 0116 ENTRY1 129 | 0117 4092 04C0 14 CLR R0 130 | 0118 4094 1005 14 JMP GO4IT 131 | 0119 132 | 0120 ENTRY2 133 | 0121 4096 0200 20 LI R0,1 134 | 4098 0001 135 | 0122 409A 1002 14 JMP GO4IT 136 | 0123 137 | 0124 ENTRY3 138 | 0125 409C 0200 20 LI R0,2 139 | 409E 0002 140 | 0126 141 | 0127 * The common start of the actual DSR 142 | 0128 GO4IT 143 | 0129 40A0 C80B 38 MOV R11,@MYRAM ; Save return address 144 | 40A2 8000 145 | 0130 40A4 C800 38 MOV R0,@ENTRYN 146 | 40A6 8002 147 | 0131 40A8 05A0 34 INC @RCOUNT 148 | 40AA 8008 149 | 0132 ; LI R1,>1234 150 | 0133 40AC 02C1 12 STST R1 ; Read CPU status register 151 | 0134 40AE C801 38 MOV R1,@MARKER 152 | 40B0 8006 153 | 0135 40B2 06A0 32 BL @PAB2RAM * Copy PAB to CPU RAM 154 | 40B4 4130r 155 | 0136 156 | 0137 * Let's signal the host PC that we have got something nice in here 157 | 0138 wait_cmd 158 | 0139 40B6 04E0 34 CLR @HOSTOP * reset previous host operation 159 | 40B8 8012 160 | 0140 40BA 0201 20 LI R1,1 161 | 40BC 0001 162 | 0141 40BE C801 38 MOV R1,@DSRWAIT * Host wait flag set 163 | 40C0 800A 164 | 0142 * Now wait for the host to tell us something 165 | 0143 !wait_loop 166 | 0144 40C2 C060 34 MOV @HOSTOP,R1 167 | 40C4 8012 168 | 0145 40C6 13FD 14 JEQ -!wait_loop 169 | 0146 40C8 04E0 34 CLR @DSRWAIT * Clear flag 170 | 40CA 800A 171 | 0147 * We received a command from the host, do something 172 | 0148 * Operations are: 173 | 0149 * Read VDP memory: 1 (ARG1=VDP addr, ARG2=CPU RAM addr, ARG3=bytecount) 174 | 0150 * Write VDP memory: 2 (ARG1=CPU addr, ARG2=VDP RAM addr, ARG3=bytecount) 175 | 0151 * Exit DSR: 3 (ARG1=error code or success, top 3 bits ored with PAB status) 176 | 0152 40CC 0601 14 DEC R1 177 | 0153 40CE 1305 14 JEQ !read_vdp 178 | 0154 40D0 0601 14 DEC R1 179 | 0155 40D2 1310 14 JEQ !write_vdp 180 | 0156 40D4 0601 14 DEC R1 181 | 0157 40D6 131B 14 JEQ !set_return 182 | 0158 40D8 10EE 14 JMP wait_cmd ; If we come here the host code was unexpected. Ignore it. 183 | 0159 184 | 0160 !read_vdp 185 | 0161 40DA C020 34 MOV @ARG1,R0 ; VDP source addr 186 | 40DC 800C 187 | 0162 40DE C060 34 MOV @ARG2,R1 ; CPU RAM addr 188 | 40E0 800E 189 | 0163 40E2 C0A0 34 MOV @ARG3,R2 190 | 40E4 8010 191 | 0164 40E6 06A0 32 BL @VDPREADA 192 | 40E8 4176r 193 | 0165 40EA DC60 48 ! MOVB @VDPRD,*R1+ 194 | 40EC 8800 195 | 0166 40EE 0602 14 DEC R2 196 | 0167 40F0 16FC 14 JNE -! 197 | 0168 40F2 10E1 14 JMP wait_cmd 198 | 0169 199 | 0170 !write_vdp 200 | 0171 40F4 C060 34 MOV @ARG1,R1 ; CPU RAM source addr 201 | 40F6 800C 202 | 0172 40F8 C020 34 MOV @ARG2,R0 ; VDP RAM addr 203 | 40FA 800E 204 | 0173 40FC C0A0 34 MOV @ARG3,R2 205 | 40FE 8010 206 | 0174 4100 06A0 32 BL @SETUPVDPA ; Setup VDP write address 207 | 4102 4188r 208 | 0175 4104 D831 48 ! MOVB *R1+,@VDPWD 209 | 4106 8C00 210 | 0176 4108 0602 14 DEC R2 211 | 0177 410A 16FC 14 JNE -! 212 | 0178 410C 10D4 14 JMP wait_cmd 213 | 0179 214 | 0180 !set_return 215 | 0181 * Let's report a device error back, error code 6 216 | 0182 410E D060 34 MOVB @MYPAB+1,R1 ; read status byte 217 | 4110 8021 218 | 0183 4112 0241 22 ANDI R1,>1FFF 219 | 4114 1FFF 220 | 0184 4116 E060 34 SOC @ARG1,R1 ; Or in the bits set by host 221 | 4118 800C 222 | 0185 ;; ORI R1,>C000 ; 6 << 5 << 8 - OR the status 6 in there 223 | 0186 411A C020 34 MOV @PABSTA,R0 224 | 411C 8004 225 | 0187 411E 0580 14 INC R0 ; point to status byte 226 | 0188 4120 06A0 32 BL @SETUPVDPA 227 | 4122 4188r 228 | 0189 4124 D801 38 MOVB R1,@VDPWD * Write back the status, indicating error 229 | 4126 8C00 230 | 0190 231 | 0191 * Return to console software 232 | 0192 EXIT 233 | 0193 4128 C2E0 34 MOV @MYRAM,R11 234 | 412A 8000 235 | 0194 412C 05CB 14 INCT R11 236 | 0195 412E 045B 20 RT 237 | 0196 238 | 0197 * Copy the PAB from VDP RAM to system RAM to make it 239 | 0198 * visible to the PC. 240 | 0199 PAB2RAM 241 | 0200 4130 C060 34 MOV @>8354,R1 * DSR Name length 242 | 4132 8354 243 | 0201 4134 C0A0 34 MOV @>8356,R2 * Ptr to first char after name in VDP memory. 244 | 4136 8356 245 | 0202 4138 C0C2 18 MOV R2,R3 246 | 0203 413A 60C1 18 S R1,R3 247 | 0204 413C 0223 22 AI R3,-10 ; -10: R3 is now the pointer to start of PAB 248 | 413E FFF6 249 | 0205 250 | 0206 4140 C003 18 MOV R3,R0 * VDP address to R0 251 | 0207 4142 C803 38 MOV R3,@PABSTA * Store PAB start for later 252 | 4144 8004 253 | 0208 4146 0221 22 AI R1,10 * 10 bytes plus name length 254 | 4148 000A 255 | 0209 * The length now is to the period character, so for example to 256 | 0210 * DSK1.HELLO it would be just after DSK1. 257 | 0211 414A C801 38 MOV R1,@MYPABN * save length of my PAB 258 | 414C 801E 259 | 0212 414E 0203 20 LI R3,MYPAB 260 | 4150 8020 261 | 0213 4152 C08B 18 MOV R11,R2 * Save return address 262 | 0214 4154 06A0 32 BL @VDPREADA * Setup VDP read address to start of PAB 263 | 4156 4176r 264 | 0215 * Copy to RAM loop 265 | 0216 4158 DCE0 48 ! MOVB @VDPRD,*R3+ 266 | 415A 8800 267 | 0217 415C 0601 14 DEC R1 268 | 0218 415E 16FC 14 JNE -! 269 | 0219 * Now that we have the PAB in CPU memory, let's also read 270 | 0220 * the filename to CPU memory. VDP memory pointer already points 271 | 0221 * to the right place. 272 | 0222 4160 D060 34 MOVB @MYPAB+9,R1 ; name length (DSR name + filename) 273 | 4162 8029 274 | 0223 4164 0981 56 SRL R1,8 ; shift to lower byte 275 | 0224 4166 6060 34 S @>8354,R1 ; substract DSR name length 276 | 4168 8354 277 | 0225 416A 1304 14 JEQ !done ; if zero exit 278 | 0226 416C DCE0 48 ! MOVB @VDPRD,*R3+ ; Otherwise copy filename 279 | 416E 8800 280 | 0227 4170 0601 14 DEC R1 281 | 0228 4172 16FC 14 JNE -! 282 | 0229 !done 283 | 0230 4174 0452 20 B *R2 284 | 0231 285 | 0232 286 | 0233 *--------------------------------------------- 287 | 0234 * Set VDP read address from R0 288 | 0235 *--------------------------------------------- 289 | 0236 VDPREADA 290 | 0237 4176 0240 22 ANDI R0,>3FFF * make sure it is a read command 291 | 4178 3FFF 292 | 0238 417A 06C0 14 SWPB R0 293 | 0239 417C D800 38 MOVB R0,@VDPWA * Send low byte of VDP RAM write address 294 | 417E 8C02 295 | 0240 4180 06C0 14 SWPB R0 296 | 0241 4182 D800 38 MOVB R0,@VDPWA * Send high byte of VDP RAM write address 297 | 4184 8C02 298 | 0242 4186 045B 20 RT 299 | 0243 300 | 0244 *--------------------------------------------- 301 | 0245 * Set VDP address from R0 302 | 0246 *--------------------------------------------- 303 | 0247 SETUPVDPA 304 | 0248 4188 06C0 14 SWPB R0 305 | 0249 418A D800 38 MOVB R0,@VDPWA * Send low byte of VDP RAM write address 306 | 418C 8C02 307 | 0250 418E 06C0 14 SWPB R0 308 | 0251 4190 0260 22 ORI R0,>4000 * Set read/write bits 14 and 15 to write (01) 309 | 4192 4000 310 | 0252 4194 D800 38 MOVB R0,@VDPWA * Send high byte of VDP RAM write address 311 | 4196 8C02 312 | 0253 4198 045B 20 RT 313 | -------------------------------------------------------------------------------- /firmware/diskdsr_4000.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Speccery/EP994A/4a469a13e57afc1def67a5ad3dd15186b6e03374/firmware/diskdsr_4000.bin -------------------------------------------------------------------------------- /firmware/dsr.sh: -------------------------------------------------------------------------------- 1 | xas99.py -b -R -L diskdsr.lst diskdsr.asm 2 | -------------------------------------------------------------------------------- /firmware/gromsmal.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Speccery/EP994A/4a469a13e57afc1def67a5ad3dd15186b6e03374/firmware/gromsmal.bin -------------------------------------------------------------------------------- /firmware/init.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM init.cmd 3 | REM EP (C) 2018-09-26 memory map now matching the FPGA CPU's memory map 4 | REM GROM 80000 5 | REM Cart 00000 6 | REM DSR B0000 7 | REM ROM BA000 (console 8k ROM) 8 | REM (Scratcpad from 68000 to B8000 but not used here) 9 | REM 10 | set CONSOLE_GROM=80000 11 | set CART_GROM=86000 12 | set CART_ROM=0000 13 | set CART_ROM2=2000 14 | set CONSOLE_ROM=BA000 15 | set DSR_ROM=B0000 16 | set PORT=-7 17 | 18 | 19 | memloader %PORT% 100008 cpu_reset_on.bin 20 | REM clear some optional areas: DSR ROM, GROM extensions, ROM extension 21 | memloader %PORT% %DSR_ROM% zeros256.bin 22 | memloader %PORT% %CART_GROM% zeros256.bin 23 | memloader %PORT% %CART_ROM% zeros256.bin 24 | 25 | memloader %PORT% %CONSOLE_ROM% 994aROM.Bin 26 | memloader %PORT% %CONSOLE_GROM% 994aGROM.Bin 27 | REM memloader %PORT% 100000 keyinit.bin 28 | 29 | REM DSR for disk support 30 | memloader %PORT% %DSR_ROM% diskdsr_4000.bin 31 | 32 | REM Extended Basic 33 | rem memloader %PORT% %CART_ROM% TIExtC.Bin 34 | rem memloader %PORT% %CART_ROM2% TIExtD.Bin 35 | rem memloader %PORT% %CART_GROM% TIExtG.Bin 36 | 37 | REM Erik Test Cartridge 38 | REM memloader %PORT% %CART_ROM% ERIK1.bin 39 | 40 | REM Memory extension test 41 | REM memloader %PORT% %CART_ROM% AMSTEST4-8.BIN 42 | 43 | REM Editor/Assembler 44 | rem memloader %PORT% %CART_GROM% TIEAG.BIN 45 | 46 | REM RXB 47 | REM memloader %PORT% %CART_ROM% RXBC.Bin 48 | REM memloader %PORT% %CART_ROM2% RXBD.Bin 49 | REM memloader %PORT% %CART_GROM% RXBG.Bin 50 | 51 | REM TI Invaders 52 | REM memloader %PORT% %CART_ROM% TI-InvaC.bin 53 | REM memloader %PORT% %CART_GROM% TI-InvaG.bin 54 | 55 | REM ERIK test ROM 56 | REM cp ../../../ticart/ASCART.bin . 57 | REM memloader %CART_ROM% ASCART.bin 58 | 59 | REM Defender 60 | REM memloader %CART_ROM% Defender.C.bin 61 | 62 | REM TI Parsec 63 | REM memloader %PORT% %CART_ROM% PARSECC.bin 64 | REM memloader %PORT% %CART_GROM% PARSECG.bin 65 | 66 | REM Alpiner 67 | REM memloader %CART_ROM% ALPINERC.BIN 68 | REM memloader %CART_GROM% ALPINERG.BIN 69 | 70 | REM Megademo 71 | REM memloader %PORT% %CART_ROM% dontmess.bin 72 | 73 | REM Megademo - version which I compiled myself 74 | memloader %PORT% %CART_ROM% demo8.bin 75 | 76 | REM CPU out of reset 77 | memloader %PORT% 100008 cpu_reset_off.bin 78 | 79 | memloader %PORT% -k 80 | -------------------------------------------------------------------------------- /firmware/init.sh: -------------------------------------------------------------------------------- 1 | # init.sh 2 | # EP (C) 2016 Oct - Nov 3 | # EP 2016-11-16 remapped certain addresses 4 | # GROM 30000 -> 80000 5 | # Cart 70000 -> 90000 6 | # DSR 60000 -> B0000 7 | # ROM 00000 -> BA000 (console 8k ROM) 8 | # (Scratcpad from 68000 to B8000 but not used here) 9 | # 10 | export CONSOLE_GROM=80000 11 | export CART_GROM=86000 12 | export CART_ROM=90000 13 | export CART_ROM2=92000 14 | export CONSOLE_ROM=BA000 15 | export DSR_ROM=B0000 16 | export PORT=-5 17 | 18 | 19 | ../memloader/memloader $PORT 100008 cpu_reset_on.bin 20 | ../memloader/memloader $PORT $CONSOLE_ROM 994aROM.Bin 21 | ../memloader/memloader $PORT $CONSOLE_GROM 994aGROM.Bin 22 | ../memloader/memloader $PORT 100000 keyinit.bin 23 | 24 | # DSR for disk support 25 | ../memloader/memloader $PORT $DSR_ROM diskdsr_4000.bin 26 | 27 | # Extended Basic 28 | # ../memloader/memloader $PORT $CART_ROM ../memloader/TIExtC.Bin 29 | # ../memloader/memloader $PORT $CART_ROM2 ../memloader/TIExtD.Bin 30 | # ../memloader/memloader $PORT $CART_GROM ../memloader/TIExtG.Bin 31 | 32 | # Erik Test Cartridge 33 | # ../memloader/memloader $PORT $CART_ROM ERIK1.bin 34 | 35 | # Memory extension test 36 | # ../memloader/memloader $PORT $CART_ROM ../memloader/AMSTEST4-8.BIN 37 | 38 | # Editor/Assembler 39 | # ../memloader/memloader $PORT $CART_GROM ../memloader/TIEAG.BIN 40 | 41 | # RXB 42 | ../memloader/memloader $PORT $CART_ROM ../memloader/RXBC.Bin 43 | ../memloader/memloader $PORT $CART_ROM2 ../memloader/RXBD.Bin 44 | ../memloader/memloader $PORT $CART_GROM ../memloader/RXBG.Bin 45 | 46 | # TI Invaders 47 | # ../memloader/memloader $PORT $CART_ROM ../memloader/TI-InvaC.bin 48 | # ../memloader/memloader $PORT $CART_GROM ../memloader/TI-InvaG.bin 49 | 50 | # ERIK test ROM 51 | # cp ../../../ticart/ASCART.bin . 52 | # ../memloader/memloader $CART_ROM ASCART.bin 53 | 54 | # Defender 55 | # ../memloader/memloader $CART_ROM Defender.C.bin 56 | 57 | # TI Parsec 58 | # ../memloader/memloader $PORT $CART_ROM PARSECC.bin 59 | # ../memloader/memloader $PORT $CART_GROM PARSECG.bin 60 | 61 | # Alpiner 62 | # ../memloader/memloader $CART_ROM ALPINERC.BIN 63 | # ../memloader/memloader $CART_GROM ALPINERG.BIN 64 | 65 | # CPU out of reset 66 | ../memloader/memloader $PORT 100008 cpu_reset_off.bin 67 | 68 | ../memloader/memloader $PORT -k 69 | -------------------------------------------------------------------------------- /firmware/kbd.sh: -------------------------------------------------------------------------------- 1 | ../memloader/memloader -k $1 2 | 3 | -------------------------------------------------------------------------------- /firmware/load.cmd: -------------------------------------------------------------------------------- 1 | cd ..\fpga 2 | call load 3 | cd ..\firmware 4 | -------------------------------------------------------------------------------- /firmware/reset.cmd: -------------------------------------------------------------------------------- 1 | REM Reset CPU 2 | memloader %PORT% 100008 cpu_reset_on.bin 3 | memloader %PORT% 100008 cpu_reset_off.bin 4 | 5 | memloader %PORT% -k -------------------------------------------------------------------------------- /firmware/reset.sh: -------------------------------------------------------------------------------- 1 | ../memloader/memloader 100008 cpu_reset_on.bin 2 | ../memloader/memloader 100008 cpu_reset_off.bin 3 | -------------------------------------------------------------------------------- /firmware/rxb.sh: -------------------------------------------------------------------------------- 1 | ../memloader/memloader 100008 cpu_reset_on.bin 2 | ../memloader/memloader 0 994aROM.Bin 3 | ../memloader/memloader 30000 994aGROM.Bin 4 | ../memloader/memloader 100000 keyinit.bin 5 | 6 | # DSR for disk support 7 | ../memloader/memloader 60000 diskdsr_4000.bin 8 | 9 | # Extended Basic 10 | # ../memloader/memloader 70000 ../memloader/TIExtC.Bin 11 | # ../memloader/memloader 72000 ../memloader/TIExtD.Bin 12 | # ../memloader/memloader 36000 ../memloader/TIExtG.Bin 13 | 14 | # Memory extension test 15 | # ../memloader/memloader 70000 ../memloader/AMSTEST4-8.BIN 16 | 17 | # Editor/Assembler 18 | # ../memloader/memloader 36000 ../memloader/TIEAG.BIN 19 | 20 | # RXB 21 | ../memloader/memloader 70000 ../memloader/RXBC.Bin 22 | ../memloader/memloader 72000 ../memloader/RXBD.Bin 23 | ../memloader/memloader 36000 ../memloader/RXBG.Bin 24 | 25 | # TI Invaders 26 | # ../memloader/memloader 70000 ../memloader/TI-InvaC.bin 27 | # ../memloader/memloader 36000 ../memloader/TI-InvaG.bin 28 | 29 | # ERIK test ROM 30 | # cp ../../../ticart/ASCART.bin . 31 | # ../memloader/memloader 70000 ASCART.bin 32 | 33 | # Defender 34 | # ../memloader/memloader 70000 Defender.C.bin 35 | 36 | # TI Parsec 37 | # ../memloader/memloader 70000 PARSECC.bin 38 | # ../memloader/memloader 36000 PARSECG.bin 39 | 40 | # Alpiner 41 | # ../memloader/memloader 70000 ALPINERC.BIN 42 | # ../memloader/memloader 36000 ALPINERG.BIN 43 | 44 | # CPU out of reset 45 | ../memloader/memloader 100008 cpu_reset_off.bin 46 | 47 | ../memloader/memloader -k 48 | -------------------------------------------------------------------------------- /firmware/vdp-raw.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Speccery/EP994A/4a469a13e57afc1def67a5ad3dd15186b6e03374/firmware/vdp-raw.bin -------------------------------------------------------------------------------- /firmware/xb.sh: -------------------------------------------------------------------------------- 1 | # EP 2016-10-28 2 | ../memloader/memloader 100008 cpu_reset_on.bin 3 | ../memloader/memloader 0 994aROM.Bin 4 | ../memloader/memloader 30000 994aGROM.Bin 5 | ../memloader/memloader 100000 keyinit.bin 6 | 7 | 8 | # Extended Basic 9 | ../memloader/memloader 70000 TIExtC.Bin 10 | ../memloader/memloader 72000 TIExtD.Bin 11 | ../memloader/memloader 36000 TIExtG.Bin 12 | 13 | # CPU out of reset 14 | ../memloader/memloader 100008 cpu_reset_off.bin 15 | ../memloader/memloader -k 16 | 17 | -------------------------------------------------------------------------------- /fpga/ep994a/iseconfig/ep994a.projectmgr: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 2 10 | /ep994a - Behavioral C:|Users|Erik Piehl|Dropbox|Omat|trunk|EP994A|fpga|src|ep994a.vhd/command_processor - serloader - serloader_Behavioral 11 | /ep994a - Behavioral C:|Users|Erik Piehl|Dropbox|Omat|trunk|EP994A|fpga|src|ep994a.vhd/vdp - tms9918 - Behavioral 12 | /pepiserial - Behavioral C:|Users|Erik Piehl|Dropbox|Omat|trunk|EP994A|fpga|src|ep994a.vhd/command_processor - serloader - serloader_Behavioral 13 | /pepiserial - Behavioral C:|Users|Erik Piehl|Dropbox|Omat|trunk|EP994A|fpga|src|ep994a.vhd/vdp - tms9918 - Behavioral 14 | 15 | 16 | ep994a - Behavioral (W:/Omat/trunk/EP994A/fpga/src/ep994a.vhd) 17 | 18 | 0 19 | 0 20 | 000000ff0000000000000001000000010000000000000000000000000000000002020000000100000001000000640000027e000000020000000000000000000000000200000064ffffffff0000008100000003000000020000027e0000000100000003000000000000000100000003 21 | true 22 | ep994a - Behavioral (W:/Omat/trunk/EP994A/fpga/src/ep994a.vhd) 23 | 24 | 25 | 26 | 1 27 | Design Utilities/Compile HDL Simulation Libraries 28 | 29 | 30 | Design Utilities 31 | 32 | 0 33 | 0 34 | 000000ff00000000000000010000000100000000000000000000000000000000000000000000000159000000010000000100000000000000000000000064ffffffff000000810000000000000001000001590000000100000000 35 | false 36 | Design Utilities 37 | 38 | 39 | 40 | 1 41 | 42 | 43 | 0 44 | 0 45 | 000000ff000000000000000100000000000000000100000000000000000000000000000000000002e7000000040101000100000000000000000000000064ffffffff0000008100000000000000040000004200000001000000000000002400000001000000000000006600000001000000000000021b0000000100000000 46 | false 47 | W:\Omat\trunk\EP994A\fpga\src\dac.vhd 48 | 49 | 50 | 51 | 1 52 | work 53 | 54 | 55 | 0 56 | 0 57 | 000000ff00000000000000010000000000000000010000000000000000000000000000000000000109000000010001000100000000000000000000000064ffffffff000000810000000000000001000001090000000100000000 58 | false 59 | work 60 | 61 | 62 | 63 | 1 64 | Configure Target Device 65 | Design Utilities 66 | Implement Design 67 | Synthesize - XST 68 | User Constraints 69 | 70 | 71 | Generate Programming File 72 | 73 | 0 74 | 0 75 | 000000ff000000000000000100000001000000000000000000000000000000000000000000000002b0000000010000000100000000000000000000000064ffffffff000000810000000000000001000002b00000000100000000 76 | false 77 | Generate Programming File 78 | 79 | 80 | 81 | 1 82 | User Constraints 83 | 84 | 85 | 86 | 87 | 0 88 | 0 89 | 000000ff0000000000000001000000010000000000000000000000000000000000000000000000018c000000010000000100000000000000000000000064ffffffff0000008100000000000000010000018c0000000100000000 90 | false 91 | 92 | 93 | 000000ff00000000000000020000011b0000011b01000000050100000002 94 | Implementation 95 | 96 | -------------------------------------------------------------------------------- /fpga/ep994a/iseconfig/ep994a.xreport: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 2018-11-14T16:24:28 5 | ep994a 6 | 2018-09-26T22:16:38 7 | W:/Omat/trunk/EP994A/fpga/ep994a/iseconfig/ep994a.xreport 8 | W:/Omat/trunk/EP994A/fpga/work\ 9 | 2016-11-04T07:00:18 10 | false 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 32 | 33 | 37 | 38 | 43 | 44 | 45 | 56 | 57 | 83 | 148 | 149 | 214 | 215 |
216 | -------------------------------------------------------------------------------- /fpga/load.bat: -------------------------------------------------------------------------------- 1 | ..\..\..\Electronics\Pipistrello\fpgaprog\fpgaprog.exe -v -d "Pepino LX9 A" -f work\ep994a.bit 2 | -------------------------------------------------------------------------------- /fpga/src/dac.vhd: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------- 2 | -- 3 | -- Delta-Sigma DAC 4 | -- 5 | -- $Id: dac.vhd,v 1.1 2006/05/10 20:57:06 arnim Exp $ 6 | -- 7 | -- Refer to Xilinx Application Note XAPP154. 8 | -- 9 | -- This DAC requires an external RC low-pass filter: 10 | -- 11 | -- dac_o 0---XXXXX---+---0 analog audio 12 | -- 3k3 | 13 | -- === 4n7 14 | -- | 15 | -- GND 16 | -- 17 | ------------------------------------------------------------------------------- 18 | 19 | library ieee; 20 | use ieee.std_logic_1164.all; 21 | use ieee.numeric_std.all; 22 | 23 | entity dac is 24 | 25 | generic ( 26 | msbi_g : integer := 7 27 | ); 28 | port ( 29 | clk_i : in std_logic; 30 | res_n_i : in std_logic; 31 | dac_i : in std_logic_vector(msbi_g downto 0); 32 | dac_o : out std_logic 33 | ); 34 | 35 | end dac; 36 | 37 | architecture rtl of dac is 38 | signal sig_in : unsigned(msbi_g+2 downto 0); 39 | 40 | begin 41 | seq: process (clk_i, res_n_i) 42 | begin 43 | if res_n_i = '0' then 44 | sig_in <= to_unsigned(2**(msbi_g+1), sig_in'length); 45 | dac_o <= '0'; 46 | elsif rising_edge(clk_i) then 47 | sig_in <= sig_in + unsigned(sig_in(msbi_g+2) & sig_in(msbi_g+2) & dac_i); 48 | dac_o <= sig_in(msbi_g+2); 49 | end if; 50 | end process seq; 51 | end rtl; 52 | -------------------------------------------------------------------------------- /fpga/src/gromext.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------- 2 | -- gromext.vhd 3 | -- 4 | -- GROM memory implementation code. 5 | -- 6 | -- This file is part of the ep994a design, a TI-99/4A clone 7 | -- designed by Erik Piehl in October 2016. 8 | -- Erik Piehl, Kauniainen, Finland, speccery@gmail.com 9 | -- 10 | -- This is copyrighted software. 11 | -- Please see the file LICENSE for license terms. 12 | -- 13 | -- NO WARRANTY, THE SOURCE CODE IS PROVIDED "AS IS". 14 | -- THE SOURCE IS PROVIDED WITHOUT ANY GUARANTEE THAT IT WILL WORK 15 | -- FOR ANY PARTICULAR USE. IN NO EVENT IS THE AUTHOR LIABLE FOR ANY 16 | -- DIRECT OR INDIRECT DAMAGE CAUSED BY THE USE OF THE SOFTWARE. 17 | -- 18 | -- Synthesized with Xilinx ISE 14.7. 19 | ---------------------------------------------------------------------------------- 20 | -- Description: Implementation of GROM for external memory. 21 | -- Basically here we map GROM accesses to external RAM addresses. 22 | -- Since we're not using internal block RAM, we can use 8K 23 | -- for each of the GROMs. 24 | -- This is the address space layout for 20 bit addresses: 25 | -- 1 1 1 1 1 1 1 1 1 1 26 | -- 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 27 | -- |<--- in grom addr --->| 28 | -- |<->| 3 bit GROM chip select (0,1,2 are console in all bases) 29 | -- |<--->| 4 bit base select 30 | -- 31 | ---------------------------------------------------------------------------------- 32 | library IEEE; 33 | use IEEE.STD_LOGIC_1164.ALL; 34 | use IEEE.STD_LOGIC_UNSIGNED.ALL; 35 | 36 | -- Uncomment the following library declaration if using 37 | -- arithmetic functions with Signed or Unsigned values 38 | use IEEE.NUMERIC_STD.ALL; 39 | 40 | -- Uncomment the following library declaration if instantiating 41 | -- any Xilinx primitives in this code. 42 | --library UNISIM; 43 | --use UNISIM.VComponents.all; 44 | 45 | entity gromext is 46 | Port ( din : in STD_LOGIC_VECTOR (7 downto 0); -- data in, write bus for addresses 47 | dout : out STD_LOGIC_VECTOR (7 downto 0); -- data out, read bus 48 | clk : in STD_LOGIC; 49 | we : in STD_LOGIC; -- write enable, 1 cycle long 50 | rd : in STD_LOGIC; -- read signal, may be up for multiple cycles 51 | selected : out STD_LOGIC; -- high when this GROM is enabled during READ 52 | -- when high, databus should be driven 53 | mode : in STD_LOGIC_VECTOR(4 downto 0); -- A5..A1 (4 bits for GROM base select, 1 bit for register select) 54 | reset : in STD_LOGIC; 55 | addr : out STD_LOGIC_VECTOR(19 downto 0) -- 1 megabyte GROM address out 56 | ); 57 | end gromext; 58 | 59 | architecture Behavioral of gromext is 60 | signal offset : std_logic_vector(12 downto 0); 61 | signal grom_sel : std_logic_vector(2 downto 0); -- top 3 bits of GROM address 62 | signal rom_addr : std_logic_vector(15 downto 0); 63 | signal grom_base : std_logic_vector(3 downto 0); 64 | signal read_addr : std_logic_vector(15 downto 0); 65 | signal read_addr_refresh : std_logic; 66 | signal old_rd : std_logic; 67 | begin 68 | 69 | -- selected <= '1' when unsigned(grom_base) = x"0" else '0'; 70 | selected <= '1'; -- Our GROMs cover all the bases currently. 71 | addr <= grom_base & grom_sel & offset; 72 | dout <= read_addr(15 downto 8); 73 | 74 | process(clk, reset) 75 | begin 76 | if reset = '1' then 77 | grom_sel <= "000"; 78 | read_addr_refresh <= '0'; 79 | elsif rising_edge(clk) then 80 | -- we handle only two scenarios: 81 | -- write to GROM address counter 82 | -- read from GROM data 83 | 84 | if we = '1' and mode(0) = '1' then 85 | -- write to address counter 86 | offset(7 downto 0) <= din; 87 | offset(12 downto 8) <= offset(4 downto 0); 88 | grom_sel <= offset(7 downto 5); 89 | grom_base <= mode(4 downto 1); 90 | read_addr_refresh <= '1'; 91 | end if; 92 | 93 | old_rd <= rd; 94 | if old_rd = '1' and rd = '0' then 95 | if mode(0)='0' then 96 | offset <= offset + 1; 97 | read_addr_refresh <= '1'; 98 | else 99 | -- address byte read just finished 100 | read_addr(15 downto 8) <= read_addr(7 downto 0); 101 | end if; 102 | end if; 103 | 104 | if read_addr_refresh='1' then 105 | read_addr <= grom_sel & (offset+1); 106 | read_addr_refresh <= '0'; 107 | end if; 108 | 109 | end if; 110 | end process; 111 | 112 | end Behavioral; 113 | 114 | -------------------------------------------------------------------------------- /fpga/src/pager612.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------- 2 | -- Company: 3 | -- Engineer: Erik Piehl 4 | -- 5 | -- Create Date: 22:27:32 08/18/2016 6 | -- Design Name: 7 | -- Module Name: pager612 - Behavioral 8 | -- Project Name: 9 | -- Target Devices: 10 | -- Tool versions: 11 | -- Description: 12 | -- 13 | -- Dependencies: 14 | -- 15 | -- Revision: 16 | -- Revision 0.01 - File Created 17 | -- Additional Comments: 18 | -- 19 | ---------------------------------------------------------------------------------- 20 | library IEEE; 21 | use IEEE.STD_LOGIC_1164.ALL; 22 | use IEEE.STD_LOGIC_UNSIGNED.ALL; 23 | use IEEE.NUMERIC_STD.ALL; 24 | 25 | entity pager612 is 26 | Port ( clk : in STD_LOGIC; 27 | abus_high : in STD_LOGIC_VECTOR (15 downto 12); 28 | abus_low : in STD_LOGIC_VECTOR (3 downto 0); 29 | dbus_in : in STD_LOGIC_VECTOR (15 downto 0); 30 | dbus_out : out STD_LOGIC_VECTOR (15 downto 0); 31 | mapen : in STD_LOGIC; -- 1 = enable mapping 32 | write_enable : in STD_LOGIC; -- 0 = write to register when sel_regs = 1 33 | page_reg_read : in STD_LOGIC; -- 0 = read from register when sel_regs = 1 34 | translated_addr : out STD_LOGIC_VECTOR (15 downto 0); 35 | access_regs : in STD_LOGIC -- 1 = read/write registers 36 | ); 37 | end pager612; 38 | 39 | architecture Behavioral of pager612 is 40 | type abank is array (natural range 0 to 15) of std_logic_vector(15 downto 0); 41 | signal regs : abank; 42 | begin 43 | process(clk) 44 | begin 45 | if rising_edge(clk) then 46 | if access_regs = '1' and write_enable = '1' then 47 | -- write to paging register 48 | regs(to_integer(unsigned(abus_low(3 downto 0)))) <= dbus_in; 49 | end if; 50 | end if; 51 | end process; 52 | 53 | translated_addr <= 54 | regs(to_integer(unsigned(abus_high(15 downto 12)))) when mapen = '1' and access_regs = '0' else 55 | x"000" & abus_high(15 downto 12); -- mapping off 56 | 57 | dbus_out <= regs(to_integer(unsigned(abus_low(3 downto 0)))) when page_reg_read = '1' and access_regs = '1' else 58 | x"BEEF"; 59 | end Behavioral; 60 | 61 | -------------------------------------------------------------------------------- /fpga/src/pepino.ucf: -------------------------------------------------------------------------------- 1 | 2 | CONFIG VCCAUX = "3.3" ; 3 | 4 | NET "clock" LOC = "J16" | IOSTANDARD = LVTTL; 5 | NET "clock" TNM_NET = "clock" ; 6 | TIMESPEC "TS_clock" = PERIOD "clock" 20000 ps INPUT_JITTER 200 ps; 7 | 8 | # FTDI FT2232-chB used as USB UART 9 | NET "rxd" LOC = "B15" | IOSTANDARD = LVTTL | PULLUP; 10 | NET "txd" LOC = "B16" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ; 11 | 12 | NET "LED[0]" LOC = "N16" | IOSTANDARD = LVTTL; 13 | NET "LED[1]" LOC = "N14" | IOSTANDARD = LVTTL; 14 | NET "LED[2]" LOC = "M14" | IOSTANDARD = LVTTL; 15 | NET "LED[3]" LOC = "M13" | IOSTANDARD = LVTTL; 16 | NET "LED[4]" LOC = "L12" | IOSTANDARD = LVTTL; 17 | NET "LED[5]" LOC = "L13" | IOSTANDARD = LVTTL; 18 | NET "LED[6]" LOC = "L14" | IOSTANDARD = LVTTL; 19 | NET "LED[7]" LOC = "L16" | IOSTANDARD = LVTTL; 20 | 21 | # SRAM 22 | NET "SRAM_CE0" LOC = "H4" | IOSTANDARD=LVCMOS33; #active low 23 | NET "SRAM_CE1" LOC = "J4" | IOSTANDARD=LVCMOS33; #active low 24 | NET "SRAM_WE" LOC = "J1" | IOSTANDARD=LVCMOS33; #active low 25 | NET "SRAM_OE" LOC = "B6" | IOSTANDARD=LVCMOS33; #active low 26 | NET "SRAM_BE[0]" LOC = "B3" | IOSTANDARD=LVCMOS33; #active low 27 | NET "SRAM_BE[1]" LOC = "C3" | IOSTANDARD=LVCMOS33; #active low 28 | NET "SRAM_BE[2]" LOC = "R1" | IOSTANDARD=LVCMOS33; #active low 29 | NET "SRAM_BE[3]" LOC = "R2" | IOSTANDARD=LVCMOS33; #active low 30 | NET "SRAM_ADR[0]" LOC = "E3" | IOSTANDARD=LVCMOS33; 31 | NET "SRAM_ADR[1]" LOC = "E4" | IOSTANDARD=LVCMOS33; 32 | NET "SRAM_ADR[2]" LOC = "F3" | IOSTANDARD=LVCMOS33; 33 | NET "SRAM_ADR[3]" LOC = "F4" | IOSTANDARD=LVCMOS33; 34 | NET "SRAM_ADR[4]" LOC = "H3" | IOSTANDARD=LVCMOS33; 35 | NET "SRAM_ADR[5]" LOC = "J3" | IOSTANDARD=LVCMOS33; 36 | NET "SRAM_ADR[6]" LOC = "L4" | IOSTANDARD=LVCMOS33; 37 | NET "SRAM_ADR[7]" LOC = "L5" | IOSTANDARD=LVCMOS33; 38 | NET "SRAM_ADR[8]" LOC = "K1" | IOSTANDARD=LVCMOS33; 39 | NET "SRAM_ADR[9]" LOC = "K2" | IOSTANDARD=LVCMOS33; 40 | NET "SRAM_ADR[10]" LOC = "D5" | IOSTANDARD=LVCMOS33; 41 | NET "SRAM_ADR[11]" LOC = "C5" | IOSTANDARD=LVCMOS33; 42 | NET "SRAM_ADR[12]" LOC = "A4" | IOSTANDARD=LVCMOS33; 43 | NET "SRAM_ADR[13]" LOC = "B5" | IOSTANDARD=LVCMOS33; 44 | NET "SRAM_ADR[14]" LOC = "A5" | IOSTANDARD=LVCMOS33; 45 | NET "SRAM_ADR[15]" LOC = "A6" | IOSTANDARD=LVCMOS33; 46 | NET "SRAM_ADR[16]" LOC = "C7" | IOSTANDARD=LVCMOS33; 47 | NET "SRAM_ADR[17]" LOC = "A7" | IOSTANDARD=LVCMOS33; 48 | NET "SRAM_ADR[18]" LOC = "D6" | IOSTANDARD=LVCMOS33; # only used on 2MB boards 49 | NET "SRAM_DAT[0]" LOC = "E1" | IOSTANDARD=LVCMOS33; 50 | NET "SRAM_DAT[1]" LOC = "E2" | IOSTANDARD=LVCMOS33; 51 | NET "SRAM_DAT[2]" LOC = "F1" | IOSTANDARD=LVCMOS33; 52 | NET "SRAM_DAT[3]" LOC = "F2" | IOSTANDARD=LVCMOS33; 53 | NET "SRAM_DAT[4]" LOC = "G1" | IOSTANDARD=LVCMOS33; 54 | NET "SRAM_DAT[5]" LOC = "G3" | IOSTANDARD=LVCMOS33; 55 | NET "SRAM_DAT[6]" LOC = "H1" | IOSTANDARD=LVCMOS33; 56 | NET "SRAM_DAT[7]" LOC = "H2" | IOSTANDARD=LVCMOS33; 57 | NET "SRAM_DAT[8]" LOC = "A3" | IOSTANDARD=LVCMOS33; 58 | NET "SRAM_DAT[9]" LOC = "A2" | IOSTANDARD=LVCMOS33; 59 | NET "SRAM_DAT[10]" LOC = "B1" | IOSTANDARD=LVCMOS33; 60 | NET "SRAM_DAT[11]" LOC = "B2" | IOSTANDARD=LVCMOS33; 61 | NET "SRAM_DAT[12]" LOC = "C1" | IOSTANDARD=LVCMOS33; 62 | NET "SRAM_DAT[13]" LOC = "C2" | IOSTANDARD=LVCMOS33; 63 | NET "SRAM_DAT[14]" LOC = "D1" | IOSTANDARD=LVCMOS33; 64 | NET "SRAM_DAT[15]" LOC = "D3" | IOSTANDARD=LVCMOS33; 65 | NET "SRAM_DAT[16]" LOC = "P4" | IOSTANDARD=LVCMOS33; 66 | NET "SRAM_DAT[17]" LOC = "T4" | IOSTANDARD=LVCMOS33; 67 | NET "SRAM_DAT[18]" LOC = "R5" | IOSTANDARD=LVCMOS33; 68 | NET "SRAM_DAT[19]" LOC = "T5" | IOSTANDARD=LVCMOS33; 69 | NET "SRAM_DAT[20]" LOC = "P6" | IOSTANDARD=LVCMOS33; 70 | NET "SRAM_DAT[21]" LOC = "T6" | IOSTANDARD=LVCMOS33; 71 | NET "SRAM_DAT[22]" LOC = "R7" | IOSTANDARD=LVCMOS33; 72 | NET "SRAM_DAT[23]" LOC = "T7" | IOSTANDARD=LVCMOS33; 73 | NET "SRAM_DAT[24]" LOC = "L1" | IOSTANDARD=LVCMOS33; 74 | NET "SRAM_DAT[25]" LOC = "L3" | IOSTANDARD=LVCMOS33; 75 | NET "SRAM_DAT[26]" LOC = "M1" | IOSTANDARD=LVCMOS33; 76 | NET "SRAM_DAT[27]" LOC = "M2" | IOSTANDARD=LVCMOS33; 77 | NET "SRAM_DAT[28]" LOC = "N1" | IOSTANDARD=LVCMOS33; 78 | NET "SRAM_DAT[29]" LOC = "N3" | IOSTANDARD=LVCMOS33; 79 | NET "SRAM_DAT[30]" LOC = "P1" | IOSTANDARD=LVCMOS33; 80 | NET "SRAM_DAT[31]" LOC = "P2" | IOSTANDARD=LVCMOS33; 81 | 82 | # I/O interfaces 83 | # TMS99105 shield pin assignment for Pepino 84 | NET "ALATCH" IOSTANDARD = LVCMOS33 | PULLDOWN | LOC=A14; 85 | NET "BUS_OE_n" IOSTANDARD = LVCMOS33 | PULLDOWN | LOC=D8; 86 | NET "CTRL_RD_n" IOSTANDARD = LVCMOS33 | PULLDOWN | LOC=F9; 87 | NET "RD_n" IOSTANDARD = LVCMOS33 | PULLDOWN | LOC=C10; 88 | NET "CTRL_CP" IOSTANDARD = LVCMOS33 | PULLDOWN | LOC=D9; 89 | NET "BUSDIR" IOSTANDARD = LVCMOS33 | PULLDOWN | LOC=C8; 90 | 91 | # external Clock -> ALATCH 92 | #NET "ALATCH" IOSTANDARD = LVCMOS33 | PULLDOWN; 93 | #NET "ALATCH" LOC = C10; 94 | # NET "MEM_n" IOSTANDARD = LVCMOS33 | PULLDOWN; 95 | # NET "MEM_n" LOC = A14; 96 | 97 | # DIR low = input 98 | #NET "ch1_dir" LOC = D9 | IOSTANDARD = LVCMOS33 | SLEW = FAST | DRIVE = 8; 99 | #NET "ch2_dir" LOC = C8 | IOSTANDARD = LVCMOS33 | SLEW = FAST | DRIVE = 8; 100 | 101 | # EN active low 102 | #NET "ch1_en" LOC = D8 | IOSTANDARD = LVCMOS33 | SLEW = FAST | DRIVE = 8; 103 | #NET "ch2_en" LOC = F9 | IOSTANDARD = LVCMOS33 | SLEW = FAST | DRIVE = 8; 104 | 105 | # indata[0-15] 106 | NET "indata[0]" IOSTANDARD = LVCMOS33 | PULLDOWN; 107 | NET "indata[0]" SLEW = FAST; 108 | NET "indata[0]" DRIVE = 8; 109 | NET "indata[0]" LOC = N5; # IO1P 110 | NET "indata[1]" IOSTANDARD = LVCMOS33 | PULLDOWN; 111 | NET "indata[1]" SLEW = FAST; 112 | NET "indata[1]" DRIVE = 8; 113 | NET "indata[1]" LOC = M6; # IO2P 114 | NET "indata[2]" IOSTANDARD = LVCMOS33 | PULLDOWN; 115 | NET "indata[2]" SLEW = FAST; 116 | NET "indata[2]" DRIVE = 8; 117 | NET "indata[2]" LOC = P7; # IO3P 118 | NET "indata[3]" IOSTANDARD = LVCMOS33 | PULLDOWN; 119 | NET "indata[3]" SLEW = FAST; 120 | NET "indata[3]" DRIVE = 8; 121 | NET "indata[3]" LOC = P8; 122 | NET "indata[4]" IOSTANDARD = LVCMOS33 | PULLDOWN; 123 | NET "indata[4]" SLEW = FAST; 124 | NET "indata[4]" DRIVE = 8; 125 | NET "indata[4]" LOC = R9; 126 | NET "indata[5]" IOSTANDARD = LVCMOS33 | PULLDOWN; 127 | NET "indata[5]" SLEW = FAST; 128 | NET "indata[5]" DRIVE = 8; 129 | NET "indata[5]" LOC = M9; 130 | NET "indata[6]" IOSTANDARD = LVCMOS33 | PULLDOWN; 131 | NET "indata[6]" SLEW = FAST; 132 | NET "indata[6]" DRIVE = 8; 133 | NET "indata[6]" LOC = L10; 134 | NET "indata[7]" IOSTANDARD = LVCMOS33 | PULLDOWN; 135 | NET "indata[7]" SLEW = FAST; 136 | NET "indata[7]" DRIVE = 8; 137 | NET "indata[7]" LOC = R14; 138 | NET "indata[8]" IOSTANDARD = LVCMOS33 | PULLDOWN; 139 | NET "indata[8]" SLEW = FAST; 140 | NET "indata[8]" DRIVE = 8; 141 | NET "indata[8]" LOC = P5; # IO1N 142 | NET "indata[9]" IOSTANDARD = LVCMOS33 | PULLDOWN; 143 | NET "indata[9]" SLEW = FAST; 144 | NET "indata[9]" DRIVE = 8; 145 | NET "indata[9]" LOC = N6; # IO2N 146 | NET "indata[10]" IOSTANDARD = LVCMOS33 | PULLDOWN; 147 | NET "indata[10]" SLEW = FAST; 148 | NET "indata[10]" DRIVE = 8; 149 | NET "indata[10]" LOC = M7; 150 | NET "indata[11]" IOSTANDARD = LVCMOS33 | PULLDOWN; 151 | NET "indata[11]" SLEW = FAST; 152 | NET "indata[11]" DRIVE = 8; 153 | NET "indata[11]" LOC = T8; 154 | NET "indata[12]" IOSTANDARD = LVCMOS33 | PULLDOWN; 155 | NET "indata[12]" SLEW = FAST; 156 | NET "indata[12]" DRIVE = 8; 157 | NET "indata[12]" LOC = T9; 158 | NET "indata[13]" IOSTANDARD = LVCMOS33 | PULLDOWN; 159 | NET "indata[13]" SLEW = FAST; 160 | NET "indata[13]" DRIVE = 8; 161 | NET "indata[13]" LOC = N8; 162 | NET "indata[14]" IOSTANDARD = LVCMOS33 | PULLDOWN; 163 | NET "indata[14]" SLEW = FAST; 164 | NET "indata[14]" DRIVE = 8; 165 | NET "indata[14]" LOC = M10; 166 | NET "indata[15]" IOSTANDARD = LVCMOS33 | PULLDOWN; 167 | NET "indata[15]" SLEW = FAST; 168 | NET "indata[15]" DRIVE = 8; 169 | NET "indata[15]" LOC = T15; 170 | 171 | # === VGA port === 172 | NET "VGA_HSYNC" LOC = D11 | IOSTANDARD = LVCMOS33; 173 | NET "VGA_VSYNC" LOC = E11 | IOSTANDARD = LVCMOS33; 174 | NET "VGA_RED<0>" LOC = B10 | IOSTANDARD = LVCMOS33; 175 | NET "VGA_RED<1>" LOC = A10 | IOSTANDARD = LVCMOS33; 176 | NET "VGA_RED<2>" LOC = C11 | IOSTANDARD = LVCMOS33; 177 | NET "VGA_GREEN<0>" LOC = A11 | IOSTANDARD = LVCMOS33; 178 | NET "VGA_GREEN<1>" LOC = B12 | IOSTANDARD = LVCMOS33; 179 | NET "VGA_GREEN<2>" LOC = A12 | IOSTANDARD = LVCMOS33; 180 | NET "VGA_BLUE<0>" LOC = C13 | IOSTANDARD = LVCMOS33; 181 | NET "VGA_BLUE<1>" LOC = A13 | IOSTANDARD = LVCMOS33; 182 | 183 | # switch 184 | NET "switch" LOC = "K14" | IOSTANDARD = LVTTL | PULLDOWN; 185 | 186 | # TMS99105 #RD and #WE/#IOCLK 187 | # #RD = PMOD2-IO3 "B8" 188 | # #WE = PMOD2-IO1 "A8" 189 | NET "MEM_n_ext" LOC = "B8" | IOSTANDARD = LVCMOS33; 190 | # NET "RD_n" LOC = "B8" | IOSTANDARD = LVCMOS33; 191 | NET "WE_n_ext" LOC = "A8" | IOSTANDARD = LVCMOS33; 192 | 193 | # Debugging pins (PS2 keyboard port) 194 | # Actually taken into real use. 195 | NET "DEBUG2" LOC = "C9" | IOSTANDARD = LVCMOS33; # PMOD2-IO4 (pin 5) 196 | NET "DEBUG1" LOC = "A9" | IOSTANDARD = LVCMOS33; # PMOD2-IO2 (pin 1) 197 | 198 | 199 | # audio 200 | NET "AUDIO_L" LOC = "K15"| IOSTANDARD = LVCMOS33; 201 | NET "AUDIO_R" LOC = "K16"| IOSTANDARD = LVCMOS33; 202 | 203 | 204 | ## Add jitter uncertainy to clock... 205 | SYSTEM_JITTER = 0.2 ns; 206 | 207 | -------------------------------------------------------------------------------- /fpga/src/serial_rx.v: -------------------------------------------------------------------------------- 1 | module serial_rx #( 2 | parameter CLK_PER_BIT = 434 3 | )( 4 | input clk, 5 | input rst, 6 | input rx, 7 | output [7:0] data, 8 | output new_data 9 | ); 10 | 11 | // clog2 is 'ceiling of log base 2' which gives you the number of bits needed to store a value 12 | parameter CTR_SIZE = $clog2(CLK_PER_BIT); 13 | 14 | localparam STATE_SIZE = 2; 15 | localparam IDLE = 2'd0, 16 | WAIT_HALF = 2'd1, 17 | WAIT_FULL = 2'd2, 18 | WAIT_HIGH = 2'd3; 19 | 20 | reg [CTR_SIZE-1:0] ctr_d, ctr_q; 21 | reg [2:0] bit_ctr_d, bit_ctr_q; 22 | reg [7:0] data_d, data_q; 23 | reg new_data_d, new_data_q; 24 | reg [STATE_SIZE-1:0] state_d, state_q = IDLE; 25 | reg rx_d, rx_q; 26 | 27 | assign new_data = new_data_q; 28 | assign data = data_q; 29 | 30 | always @(*) begin 31 | rx_d = rx; 32 | state_d = state_q; 33 | ctr_d = ctr_q; 34 | bit_ctr_d = bit_ctr_q; 35 | data_d = data_q; 36 | new_data_d = 1'b0; 37 | 38 | case (state_q) 39 | IDLE: begin 40 | bit_ctr_d = 3'b0; 41 | ctr_d = 1'b0; 42 | if (rx_q == 1'b0) begin 43 | state_d = WAIT_HALF; 44 | end 45 | end 46 | WAIT_HALF: begin 47 | ctr_d = ctr_q + 1'b1; 48 | if (ctr_q == (CLK_PER_BIT >> 1)) begin 49 | ctr_d = 1'b0; 50 | state_d = WAIT_FULL; 51 | end 52 | end 53 | WAIT_FULL: begin 54 | ctr_d = ctr_q + 1'b1; 55 | if (ctr_q == CLK_PER_BIT - 1) begin 56 | data_d = {rx_q, data_q[7:1]}; 57 | bit_ctr_d = bit_ctr_q + 1'b1; 58 | ctr_d = 1'b0; 59 | if (bit_ctr_q == 3'd7) begin 60 | state_d = WAIT_HIGH; 61 | new_data_d = 1'b1; 62 | end 63 | end 64 | end 65 | WAIT_HIGH: begin 66 | if (rx_q == 1'b1) begin 67 | state_d = IDLE; 68 | end 69 | end 70 | default: begin 71 | state_d = IDLE; 72 | end 73 | endcase 74 | 75 | end 76 | 77 | always @(posedge clk) begin 78 | if (rst) begin 79 | ctr_q <= 1'b0; 80 | bit_ctr_q <= 3'b0; 81 | new_data_q <= 1'b0; 82 | state_q <= IDLE; 83 | end else begin 84 | ctr_q <= ctr_d; 85 | bit_ctr_q <= bit_ctr_d; 86 | new_data_q <= new_data_d; 87 | state_q <= state_d; 88 | end 89 | 90 | rx_q <= rx_d; 91 | data_q <= data_d; 92 | end 93 | 94 | endmodule 95 | -------------------------------------------------------------------------------- /fpga/src/serial_tx.v: -------------------------------------------------------------------------------- 1 | module serial_tx #( 2 | parameter CLK_PER_BIT = 434 // This is set to 50MHz/115200=434 was 50 3 | )( 4 | input clk, 5 | input rst, 6 | output tx, 7 | input block_tx, 8 | output busy, 9 | input [7:0] data, 10 | input new_data 11 | ); 12 | 13 | // clog2 is 'ceiling of log base 2' which gives you the number of bits needed to store a value 14 | parameter CTR_SIZE = $clog2(CLK_PER_BIT); 15 | 16 | localparam STATE_SIZE = 2; 17 | localparam IDLE = 2'd0, 18 | START_BIT = 2'd1, 19 | DATA = 2'd2, 20 | STOP_BIT = 2'd3; 21 | 22 | reg [CTR_SIZE-1:0] ctr_d, ctr_q; 23 | reg [2:0] bit_ctr_d, bit_ctr_q; 24 | reg [7:0] data_d, data_q; 25 | reg [STATE_SIZE-1:0] state_d, state_q = IDLE; 26 | reg tx_d, tx_q; 27 | reg busy_d, busy_q; 28 | reg block_d, block_q; 29 | 30 | assign tx = tx_q; 31 | assign busy = busy_q; 32 | 33 | always @(*) begin 34 | block_d = block_tx; 35 | ctr_d = ctr_q; 36 | bit_ctr_d = bit_ctr_q; 37 | data_d = data_q; 38 | state_d = state_q; 39 | busy_d = busy_q; 40 | 41 | case (state_q) 42 | IDLE: begin 43 | if (block_q) begin 44 | busy_d = 1'b1; 45 | tx_d = 1'b1; 46 | end else begin 47 | busy_d = 1'b0; 48 | tx_d = 1'b1; 49 | bit_ctr_d = 3'b0; 50 | ctr_d = 1'b0; 51 | if (new_data) begin 52 | data_d = data; 53 | state_d = START_BIT; 54 | busy_d = 1'b1; 55 | end 56 | end 57 | end 58 | START_BIT: begin 59 | busy_d = 1'b1; 60 | ctr_d = ctr_q + 1'b1; 61 | tx_d = 1'b0; 62 | if (ctr_q == CLK_PER_BIT - 1) begin 63 | ctr_d = 1'b0; 64 | state_d = DATA; 65 | end 66 | end 67 | DATA: begin 68 | busy_d = 1'b1; 69 | tx_d = data_q[bit_ctr_q]; 70 | ctr_d = ctr_q + 1'b1; 71 | if (ctr_q == CLK_PER_BIT - 1) begin 72 | ctr_d = 1'b0; 73 | bit_ctr_d = bit_ctr_q + 1'b1; 74 | if (bit_ctr_q == 7) begin 75 | state_d = STOP_BIT; 76 | end 77 | end 78 | end 79 | STOP_BIT: begin 80 | busy_d = 1'b1; 81 | tx_d = 1'b1; 82 | ctr_d = ctr_q + 1'b1; 83 | if (ctr_q == CLK_PER_BIT - 1) begin 84 | state_d = IDLE; 85 | end 86 | end 87 | default: begin 88 | state_d = IDLE; 89 | end 90 | endcase 91 | end 92 | 93 | always @(posedge clk) begin 94 | if (rst) begin 95 | state_q <= IDLE; 96 | tx_q <= 1'b1; 97 | end else begin 98 | state_q <= state_d; 99 | tx_q <= tx_d; 100 | end 101 | 102 | block_q <= block_d; 103 | data_q <= data_d; 104 | bit_ctr_q <= bit_ctr_d; 105 | ctr_q <= ctr_d; 106 | busy_q <= busy_d; 107 | end 108 | 109 | endmodule 110 | -------------------------------------------------------------------------------- /fpga/src/serloader.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------- 2 | -- serloader.vhd 3 | -- 4 | -- State machine for commands received over a serial port. 5 | -- This file is part of the ep994a design, a TI-99/4A clone 6 | -- designed by Erik Piehl in October 2016. 7 | -- Erik Piehl, Kauniainen, Finland, speccery@gmail.com 8 | -- 9 | -- This is copyrighted software. 10 | -- Please see the file LICENSE for license terms. 11 | -- 12 | -- NO WARRANTY, THE SOURCE CODE IS PROVIDED "AS IS". 13 | -- THE SOURCE IS PROVIDED WITHOUT ANY GUARANTEE THAT IT WILL WORK 14 | -- FOR ANY PARTICULAR USE. IN NO EVENT IS THE AUTHOR LIABLE FOR ANY 15 | -- DIRECT OR INDIRECT DAMAGE CAUSED BY THE USE OF THE SOFTWARE. 16 | -- 17 | -- Synthesized with Xilinx ISE 14.7. 18 | ---------------------------------------------------------------------------------- 19 | library IEEE; 20 | use IEEE.STD_LOGIC_1164.ALL; 21 | 22 | -- Uncomment the following library declaration if using 23 | -- arithmetic functions with Signed or Unsigned values 24 | use IEEE.NUMERIC_STD.ALL; 25 | 26 | -- Uncomment the following library declaration if instantiating 27 | -- any Xilinx primitives in this code. 28 | --library UNISIM; 29 | --use UNISIM.VComponents.all; 30 | 31 | entity serloader is 32 | Port ( clk : in STD_LOGIC; 33 | rst : in STD_LOGIC; 34 | tx : out STD_LOGIC; 35 | rx : in STD_LOGIC; 36 | mem_addr : out STD_LOGIC_VECTOR (31 downto 0); 37 | mem_data_out : out STD_LOGIC_VECTOR (7 downto 0); 38 | mem_data_in : in STD_LOGIC_VECTOR (7 downto 0); 39 | mem_read_rq : out STD_LOGIC; 40 | mem_read_ack : in STD_LOGIC; 41 | mem_write_rq : out STD_LOGIC; 42 | mem_write_ack : in STD_LOGIC); 43 | end serloader; 44 | 45 | architecture serloader_Behavioral of serloader is 46 | type state_type is ( 47 | idle, 48 | set_mode, do_auto_inc, set_count_0, set_count_1, 49 | rd_count_1, rd_count_2, 50 | wr_a0, wr_a1, wr_a2, wr_a3, 51 | wr_dat0, wr_dat1, wr_dat2, wr_dat_inc, 52 | rd_dat0, rd_dat1, rd_dat2, rd_dat_inc 53 | ); 54 | signal state : state_type; 55 | signal return_state : state_type; -- return state after autoincrement operation 56 | signal ab0, ab1, ab2, ab3 : std_logic_vector(7 downto 0); 57 | signal rx_byte_latch : std_logic_vector(7 downto 0); 58 | signal rx_byte_ready : std_logic; 59 | signal tx_data_latch : std_logic_vector(7 downto 0); 60 | signal wr_data_latch : std_logic_vector(7 downto 0); 61 | signal mychar : integer; 62 | signal mode : std_logic_vector(1 downto 0); -- repeat mode, autoincrement mode 63 | signal rpt_count : std_logic_vector(15 downto 0); 64 | 65 | signal rx_data : STD_LOGIC_VECTOR (7 downto 0); -- data from serial port 66 | signal rx_new_data: STD_LOGIC; 67 | signal tx_data : STD_LOGIC_VECTOR (7 downto 0); -- data to serial port 68 | signal tx_now : STD_LOGIC; -- transmit tx_data NOW 69 | signal tx_busy : STD_LOGIC; -- transmitter is busy 70 | 71 | signal cnt_minus1 : std_logic_vector(15 downto 0); 72 | signal ack_w_high : integer; 73 | signal mem_write_rq_state : std_logic; 74 | signal prev_ack : std_logic; -- debugging signal 75 | 76 | component serial_tx port ( 77 | clk : in std_logic; 78 | rst : in std_logic; 79 | tx : out std_logic; 80 | block_tx : in std_logic; -- pause transmission (i.e. other side not ready to handle more right now) 81 | busy : out std_logic; 82 | data : in std_logic_vector(7 downto 0); 83 | new_data : in std_logic 84 | ); 85 | end component; 86 | 87 | component serial_rx port ( 88 | clk : in std_logic; 89 | rst : in std_logic; 90 | rx : in std_logic; 91 | data : out std_logic_vector(7 downto 0); 92 | new_data : out std_logic 93 | ); 94 | end component; 95 | 96 | begin 97 | 98 | tx_data <= tx_data_latch; 99 | mem_addr <= ab3 & ab2 & ab1 & ab0; 100 | mem_data_out <= wr_data_latch; 101 | mychar <= to_integer(unsigned(rx_byte_latch)); 102 | mem_write_rq <= mem_write_rq_state; 103 | 104 | 105 | cnt_minus1 <= std_logic_vector(to_unsigned(to_integer(unsigned(rpt_count)) - 1, cnt_minus1'length)); 106 | 107 | process(clk, rst) 108 | variable k : integer; 109 | variable kbits : std_logic_vector(31 downto 0); 110 | variable k16 : std_logic_vector(15 downto 0); 111 | variable cnt : integer; 112 | variable cnt16 : std_logic_vector(15 downto 0); 113 | begin 114 | if rst = '1' then 115 | state <= idle; 116 | ab0 <= (others => '0'); 117 | ab1 <= (others => '0'); 118 | ab2 <= (others => '0'); 119 | ab3 <= (others => '0'); 120 | mem_read_rq <= '0'; 121 | mem_write_rq_state <= '0'; 122 | rx_byte_ready <= '0'; 123 | mode <= "00"; 124 | ack_w_high <= 0; 125 | elsif rising_edge(clk) then 126 | 127 | tx_now <= '0'; -- assume nothing is sent, this may change below 128 | 129 | if rx_new_data = '1' then 130 | -- we got a byte from serial port. Latch it. 131 | -- The state machine will eat it later. 132 | rx_byte_latch <= rx_data; 133 | rx_byte_ready <= '1'; 134 | end if; 135 | 136 | -- for how many cycles is the memory requst high *after* getting the ack? 137 | prev_ack <= mem_write_ack; 138 | if prev_ack = '0' and mem_write_ack = '1' then 139 | ack_w_high <= 0; 140 | elsif mem_write_rq_state = '1' then 141 | ack_w_high <= ack_w_high + 1; 142 | end if; 143 | 144 | if rx_byte_ready = '1' then 145 | case state is 146 | when idle => 147 | case mychar is 148 | when 46 => -- . 149 | if tx_busy = '0' then 150 | tx_data_latch <= std_logic_vector(to_unsigned(46,8)); -- echo back . 151 | tx_now <= '1'; 152 | rx_byte_ready <= '0'; -- here we consume the character. 153 | end if; 154 | when 65 => state <= wr_a0; -- A 155 | rx_byte_ready <= '0'; -- char consumed 156 | when 66 => state <= wr_a1; -- B 157 | rx_byte_ready <= '0'; -- char consumed 158 | when 67 => state <= wr_a2; -- C 159 | rx_byte_ready <= '0'; -- char consumed 160 | when 68 => state <= wr_a3; -- D 161 | rx_byte_ready <= '0'; -- char consumed 162 | when 69 => -- E 163 | if tx_busy = '0' then 164 | tx_data_latch <= ab0; 165 | tx_now <= '1'; 166 | rx_byte_ready <= '0'; -- here we consume the character. 167 | end if; 168 | when 70 => -- F 169 | if tx_busy = '0' then 170 | tx_data_latch <= ab1; 171 | tx_now <= '1'; 172 | rx_byte_ready <= '0'; -- here we consume the character. 173 | end if; 174 | when 71 => -- G 175 | if tx_busy = '0' then 176 | tx_data_latch <= ab2; 177 | tx_now <= '1'; 178 | rx_byte_ready <= '0'; -- here we consume the character. 179 | end if; 180 | when 72 => -- H 181 | if tx_busy = '0' then 182 | tx_data_latch <= ab3; 183 | tx_now <= '1'; 184 | rx_byte_ready <= '0'; -- here we consume the character. 185 | end if; 186 | when 33 => -- ! write byte 187 | state <= wr_dat0; 188 | rx_byte_ready <= '0'; -- char consumed 189 | when 64 => -- @ read byte 190 | state <= rd_dat0; 191 | rx_byte_ready <= '0'; -- char consumed 192 | when 43 => -- + increment lowest address byte 193 | rx_byte_ready <= '0'; -- char consumed 194 | ab0 <= std_logic_vector(to_unsigned(to_integer(unsigned(ab0)) + 1, ab0'length)); 195 | when 77 => -- set mode 'M' 196 | rx_byte_ready <= '0'; 197 | state <= set_mode; 198 | when 78 => -- read mode 'N' 199 | if tx_busy = '0' then 200 | tx_data_latch <= x"3" & "00" & mode; -- '0', '1', '2' or '3' as ASCII 201 | tx_now <= '1'; 202 | rx_byte_ready <= '0'; -- here we consume the character. 203 | end if; 204 | when 86 => -- get version V 205 | if tx_busy = '0' then 206 | tx_data_latch <= x"30"; -- '0' 207 | tx_now <= '1'; 208 | rx_byte_ready <= '0'; -- here we consume the character. 209 | end if; 210 | when 84 => -- 'T' set 16-bit repeat count 211 | rx_byte_ready <= '0'; 212 | state <= set_count_0; 213 | when 80 => -- 'P' get repeat count (low, high) 214 | if tx_busy = '0' then 215 | tx_data_latch <= rpt_count(7 downto 0); 216 | tx_now <= '1'; 217 | state <= rd_count_1; 218 | rx_byte_ready <= '0'; -- Char consumed. 219 | end if; 220 | when 81 => -- 'Q' get repeat count (high) 221 | if tx_busy = '0' then 222 | tx_data_latch <= rpt_count(15 downto 8); 223 | tx_now <= '1'; 224 | rx_byte_ready <= '0'; -- here we consume the character. 225 | end if; 226 | when 88 => -- 'X' read ack signal counter 227 | if tx_busy = '0' then 228 | tx_data_latch <= x"3" & std_logic_vector(to_unsigned(ack_w_high, 4)); 229 | tx_now <= '1'; 230 | rx_byte_ready <= '0'; -- character consumed 231 | end if; 232 | when others => 233 | state <= idle; -- no change 234 | rx_byte_ready <= '0'; -- consume the character, i.e. throw it away 235 | end case; -- end of case mychar 236 | when set_count_0 => -- low byte of repeat count 237 | rx_byte_ready <= '0'; 238 | rpt_count <= rpt_count(15 downto 8) & rx_byte_latch; 239 | state <= set_count_1; 240 | when set_count_1 => -- high byte of repeat count 241 | rx_byte_ready <= '0'; 242 | rpt_count <= rx_byte_latch & rpt_count(7 downto 0); 243 | state <= idle; 244 | when set_mode => 245 | rx_byte_ready <= '0'; -- capture low 2 bits as mode 246 | mode <= rx_byte_latch(1 downto 0); 247 | state <= idle; 248 | when wr_a0 => 249 | rx_byte_ready <= '0'; 250 | ab0 <= rx_byte_latch; 251 | state <= idle; 252 | when wr_a1 => 253 | rx_byte_ready <= '0'; 254 | ab1 <= rx_byte_latch; 255 | state <= idle; 256 | when wr_a2 => 257 | rx_byte_ready <= '0'; 258 | ab2 <= rx_byte_latch; 259 | state <= idle; 260 | when wr_a3 => 261 | rx_byte_ready <= '0'; 262 | ab3 <= rx_byte_latch; 263 | state <= idle; 264 | when wr_dat0 => 265 | return_state <= wr_dat0; -- If there is an autoincrement repeat, come back here. 266 | rx_byte_ready <= '0'; 267 | wr_data_latch <= rx_byte_latch; 268 | state <= wr_dat1; 269 | ack_w_high <= 0; 270 | when others => 271 | -- go back to idle state - also aborts things in progress 272 | -- Note: keeps rx_byte_ready signal active, idle state will consume it. 273 | 274 | -- EP actually do nothing, because the state machine is handled in two parts 275 | -- which actually sucks. 276 | 277 | -- state <= idle; 278 | -- mem_read_rq <= '0'; 279 | -- mem_write_rq <= '0'; 280 | end case; 281 | end if; -- new_data = 1 282 | 283 | -- state transitions which are not driven by data receive but by clock 284 | -- cycles or other signals i.e memory activity 285 | case state is 286 | when wr_dat1 => 287 | mem_write_rq_state <= '1'; 288 | state <= wr_dat2; 289 | when wr_dat2 => 290 | if mem_write_ack = '1' then 291 | mem_write_rq_state <= '0'; 292 | if mode(0) = '1' then 293 | state <= do_auto_inc; -- return to idle via autoinc 294 | else 295 | state <= idle; 296 | end if; 297 | end if; 298 | when rd_dat0 => 299 | return_state <= rd_dat0; -- If there is an autoincrement repeat, come back here. 300 | mem_read_rq <= '1'; 301 | state <= rd_dat1; 302 | when rd_dat1 => 303 | if mem_read_ack = '1' then 304 | mem_read_rq <= '0'; 305 | state <= rd_dat2; 306 | end if; 307 | when rd_dat2 => 308 | if tx_busy = '0' then 309 | tx_data_latch <= -- std_logic_vector(to_unsigned(42,8)); -- return * for now 310 | mem_data_in; 311 | tx_now <= '1'; 312 | if mode(0) = '1' then 313 | state <= do_auto_inc; -- return to idle via autoinc 314 | else 315 | state <= idle; 316 | end if; 317 | end if; 318 | when do_auto_inc => 319 | -- handle autoincrement. 320 | k := to_integer(unsigned(ab1 & ab0)) + 1; 321 | k16 := std_logic_vector(to_unsigned(k, k16'length)); 322 | ab0 <= k16(7 downto 0); 323 | ab1 <= k16(15 downto 8); 324 | -- hard coded repeat for reading data 325 | if mode(1) = '1' then 326 | rpt_count <= cnt_minus1; 327 | if rpt_count = x"0001" then 328 | state <= idle; 329 | else 330 | state <= return_state; -- go to rd_dat0 or wr_dat0 depending on how we got here. 331 | end if; 332 | else 333 | state <= idle; 334 | end if; 335 | when rd_count_1 => 336 | state <= rd_count_2; -- waste 1 clock cycle, not sure how fast tx_busy goes to zero 337 | when rd_count_2 => 338 | if tx_busy = '0' then -- return high byte of repeat counter 339 | tx_data_latch <= rpt_count(15 downto 8); 340 | tx_now <= '1'; 341 | state <= idle; 342 | end if; 343 | when others => 344 | -- do nothing 345 | end case; 346 | end if; 347 | end process; 348 | 349 | uart_tx: serial_tx port map ( 350 | clk => clk, 351 | rst => rst, 352 | tx => tx, 353 | block_tx => '0', 354 | busy => tx_busy, 355 | data => tx_data, 356 | new_data => tx_now 357 | ); 358 | 359 | uart_receiver : serial_rx port map ( 360 | clk => clk, 361 | rst => rst, 362 | rx => rx, 363 | data => rx_data, 364 | new_data => rx_new_data 365 | ); 366 | 367 | 368 | end serloader_Behavioral; 369 | 370 | -------------------------------------------------------------------------------- /fpga/src/tms9919.vhd: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------- 2 | -- tms9919.vhd 3 | -- 4 | -- Implementation of the TMS9919 sound chip. 5 | -- The module is not 100% compatible with the orignal design. 6 | -- 7 | -- This file is part of the ep994a design, a TI-99/4A clone 8 | -- designed by Erik Piehl in October 2016. 9 | -- Erik Piehl, Kauniainen, Finland, speccery@gmail.com 10 | -- 11 | -- This is copyrighted software. 12 | -- Please see the file LICENSE for license terms. 13 | -- 14 | -- NO WARRANTY, THE SOURCE CODE IS PROVIDED "AS IS". 15 | -- THE SOURCE IS PROVIDED WITHOUT ANY GUARANTEE THAT IT WILL WORK 16 | -- FOR ANY PARTICULAR USE. IN NO EVENT IS THE AUTHOR LIABLE FOR ANY 17 | -- DIRECT OR INDIRECT DAMAGE CAUSED BY THE USE OF THE SOFTWARE. 18 | -- 19 | -- Synthesized with Xilinx ISE 14.7. 20 | ----------------------------------------------------------------------------------- 21 | library IEEE; 22 | use IEEE.STD_LOGIC_1164.ALL; 23 | 24 | -- Uncomment the following library declaration if using 25 | -- arithmetic functions with Signed or Unsigned values 26 | use IEEE.NUMERIC_STD.ALL; 27 | 28 | -- Uncomment the following library declaration if instantiating 29 | -- any Xilinx primitives in this code. 30 | --library UNISIM; 31 | --use UNISIM.VComponents.all; 32 | 33 | entity tms9919 is 34 | Port ( clk : in STD_LOGIC; -- 100MHz clock 35 | reset : in STD_LOGIC; -- reset active high 36 | we : in STD_LOGIC; -- high for one clock for a write to sound chip 37 | data_in : in STD_LOGIC_VECTOR (7 downto 0); -- data bus in 38 | dac_out : out STD_LOGIC_VECTOR (7 downto 0)); -- output to audio DAC 39 | end tms9919; 40 | 41 | architecture tms9919_Behavioral of tms9919 is 42 | signal latch_high : std_logic_vector(6 downto 0); -- written when MSB (bit 7) is set 43 | signal tone1_div_val : std_logic_vector(9 downto 0); -- divider value 44 | signal tone1_att : std_logic_vector(3 downto 0); -- attenuator value 45 | signal tone2_div_val : std_logic_vector(9 downto 0); -- divider value 46 | signal tone2_att : std_logic_vector(3 downto 0); -- attenuator value 47 | signal tone3_div_val : std_logic_vector(9 downto 0); -- divider value 48 | signal tone3_att : std_logic_vector(3 downto 0); -- attenuator value 49 | signal noise_div_val : std_logic_vector(3 downto 0); -- Noise generator divisor 50 | signal noise_att : std_logic_vector(3 downto 0); -- attenuator value 51 | 52 | signal tone1_counter : std_logic_vector(9 downto 0); 53 | signal tone2_counter : std_logic_vector(9 downto 0); 54 | signal tone3_counter : std_logic_vector(9 downto 0); 55 | signal noise_counter : std_logic_vector(10 downto 0); 56 | signal master_divider : integer; 57 | 58 | signal tone1_out : std_logic; 59 | signal tone2_out : std_logic; 60 | signal tone3_out : std_logic; 61 | signal noise_out : std_logic; 62 | signal bump_noise : std_logic; 63 | signal noise_lfsr : std_logic_vector(15 downto 0); 64 | 65 | signal add_value : std_logic_vector(3 downto 0); 66 | signal add_flag : std_logic; 67 | 68 | 69 | type tone_proc_type is ( 70 | chan0, chan1, chan2, noise, prepare, output 71 | ); 72 | signal tone_proc : tone_proc_type; 73 | signal acc : std_logic_vector(7 downto 0); 74 | 75 | function volume_lookup( 76 | att : std_logic_vector(3 downto 0)) 77 | return std_logic_vector is 78 | begin 79 | case to_bitvector(att) is 80 | when x"0" => return "00111100"; -- this is not good for now 81 | when x"1" => return "00110000"; 82 | when x"2" => return "00100010"; 83 | when x"3" => return "00010011"; 84 | when x"4" => return "00001111"; 85 | when x"5" => return "00001111"; 86 | when x"6" => return "00001111"; 87 | when x"7" => return "00001111"; 88 | when x"8" => return "00001100"; 89 | when x"9" => return "00001000"; 90 | when x"A" => return "00000110"; 91 | when x"B" => return "00000100"; 92 | when x"C" => return "00000011"; 93 | when x"D" => return "00000010"; 94 | when x"E" => return "00000001"; -- minimum change 95 | when x"F" => return "00000000"; -- no change 96 | end case; 97 | end; 98 | 99 | 100 | begin 101 | 102 | process(clk, reset) 103 | variable k : std_logic; 104 | begin 105 | if reset = '1' then 106 | latch_high <= (others => '0'); 107 | tone1_att <= "1111"; -- off 108 | tone2_att <= "1111"; -- off 109 | tone3_att <= "1111"; -- off 110 | noise_att <= "1111"; -- off 111 | master_divider <= 0; 112 | add_value <= (others => '0'); 113 | add_flag <= '0'; 114 | elsif rising_edge(clk) then 115 | if we='1' then 116 | -- data write 117 | if data_in(7) = '1' then 118 | latch_high <= data_in(6 downto 0); -- store for later re-use 119 | case data_in(6 downto 4) is 120 | when "000" => tone1_div_val(3 downto 0) <= data_in(3 downto 0); 121 | when "001" => tone1_att <= data_in(3 downto 0); 122 | when "010" => tone2_div_val(3 downto 0) <= data_in(3 downto 0); 123 | when "011" => tone2_att <= data_in(3 downto 0); 124 | when "100" => tone3_div_val(3 downto 0) <= data_in(3 downto 0); 125 | when "101" => tone3_att <= data_in(3 downto 0); 126 | when "110" => noise_div_val <= data_in(3 downto 0); 127 | noise_lfsr <= x"0001"; -- initialize noise generator 128 | when "111" => noise_att <= data_in(3 downto 0); 129 | when others => 130 | end case; 131 | else 132 | -- Write with MSB set to zero. Use latched register value. 133 | case latch_high(6 downto 4) is 134 | when "000" => tone1_div_val(9 downto 4) <= data_in(5 downto 0); 135 | when "010" => tone2_div_val(9 downto 4) <= data_in(5 downto 0); 136 | when "100" => tone3_div_val(9 downto 4) <= data_in(5 downto 0); 137 | when others => 138 | end case; 139 | end if; 140 | end if; 141 | 142 | -- Ok. Now handle the actual sound generators. 143 | -- The input freuency on the TI-99/4A is 3.58MHz which is divided by 32, this is 111875Hz. 144 | -- Our clock is 100MHz. As the first approximation we will divide 100MHz by 894. 145 | -- That gives a clock of 111857Hz which is good enough. 146 | -- After checking that actually yields half of the desired frequency. So let's go with 447. 147 | master_divider <= master_divider + 1; 148 | if master_divider >= 446 then 149 | master_divider <= 0; 150 | tone1_counter <= std_logic_vector(to_unsigned(to_integer(unsigned(tone1_counter)) - 1, tone1_counter'length)); 151 | tone2_counter <= std_logic_vector(to_unsigned(to_integer(unsigned(tone2_counter)) - 1, tone2_counter'length)); 152 | tone3_counter <= std_logic_vector(to_unsigned(to_integer(unsigned(tone3_counter)) - 1, tone3_counter'length)); 153 | noise_counter <= std_logic_vector(to_unsigned(to_integer(unsigned(noise_counter)) - 1, noise_counter'length)); 154 | 155 | if unsigned(tone1_counter) = 0 then 156 | tone1_out <= not tone1_out; 157 | tone1_counter <= tone1_div_val; 158 | end if; 159 | if unsigned(tone2_counter) = 0 then 160 | tone2_out <= not tone2_out; 161 | tone2_counter <= tone2_div_val; 162 | end if; 163 | bump_noise <= '0'; 164 | if unsigned(tone3_counter) = 0 then 165 | tone3_out <= not tone3_out; 166 | tone3_counter <= tone3_div_val; 167 | if noise_div_val(1 downto 0) = "11" then 168 | bump_noise <= '1'; 169 | end if; 170 | end if; 171 | 172 | if noise_counter(8 downto 0) = "000000000" then 173 | case noise_div_val(1 downto 0) is 174 | when "00" => bump_noise <= '1'; -- 512 175 | when "01" => 176 | if noise_counter(9)='0' then -- 1024 177 | bump_noise <= '1'; 178 | end if; 179 | when "10" => 180 | if noise_counter(10 downto 9)="00" then -- 2048 181 | bump_noise <= '1'; 182 | end if; 183 | when others => 184 | end case; 185 | end if; 186 | 187 | if bump_noise='1' then 188 | if noise_div_val(2)='1' then 189 | -- white noise 190 | k := noise_lfsr(14) xor noise_lfsr(13); 191 | else 192 | k := noise_lfsr(14); -- just feedback 193 | end if; 194 | noise_lfsr <= noise_lfsr(14 downto 0) & k; 195 | if noise_lfsr(14) = '1' then 196 | noise_out <= not noise_out; 197 | end if; 198 | end if; 199 | 200 | end if; 201 | 202 | if add_flag='1' then 203 | acc <= std_logic_vector(to_unsigned( 204 | to_integer(unsigned(acc)) + to_integer(unsigned(volume_lookup(add_value))), 205 | acc'length)); 206 | else 207 | acc <= std_logic_vector(to_unsigned( 208 | to_integer(unsigned(acc)) - to_integer(unsigned(volume_lookup(add_value))), 209 | acc'length)); 210 | end if; 211 | 212 | -- Ok now combine the tone_out values 213 | case tone_proc is 214 | when chan0 => 215 | add_value <= tone1_att; 216 | add_flag <= tone1_out; 217 | tone_proc <= chan1; 218 | when chan1 => 219 | add_value <= tone2_att; 220 | add_flag <= tone2_out; 221 | tone_proc <= chan2; 222 | when chan2 => 223 | add_value <= tone3_att; 224 | add_flag <= tone3_out; 225 | tone_proc <= noise; 226 | when noise => 227 | add_value <= noise_att; 228 | add_flag <= noise_out; 229 | tone_proc <= prepare; 230 | when prepare => 231 | -- During this step the acc gets updated with noise value 232 | add_value <= "1111"; -- silence, this stage is just a wait state to pick up noise 233 | tone_proc <= output; 234 | when others => -- output stage 235 | dac_out <= acc; 236 | add_value <= "1111"; -- no change 237 | acc <= x"80"; 238 | tone_proc <= chan0; 239 | end case; 240 | 241 | end if; 242 | end process; 243 | 244 | end tms9919_Behavioral; 245 | 246 | -------------------------------------------------------------------------------- /fpga/src/vga_sync.vhd: -------------------------------------------------------------------------------- 1 | library IEEE; 2 | use IEEE.STD_LOGIC_1164.all; 3 | use IEEE.STD_LOGIC_ARITH.all; 4 | use IEEE.STD_LOGIC_UNSIGNED.all; 5 | 6 | ENTITY VGA_SYNC IS 7 | PORT( clk, ena : IN STD_LOGIC; 8 | video_on, horiz_sync_out, vert_sync_out : OUT STD_LOGIC; 9 | pixel_row, pixel_column: OUT STD_LOGIC_VECTOR(9 DOWNTO 0)); 10 | END VGA_SYNC; 11 | 12 | ARCHITECTURE a OF VGA_SYNC IS 13 | SIGNAL horiz_sync, vert_sync : STD_LOGIC; 14 | SIGNAL video_onx, video_on_v, video_on_h : STD_LOGIC; 15 | SIGNAL h_count, v_count :STD_LOGIC_VECTOR(9 DOWNTO 0); 16 | signal vbuf : std_logic; 17 | BEGIN 18 | 19 | video_on <= video_onx; 20 | -- video_onx is high only when RGB data is displayed 21 | video_onx <= video_on_H AND video_on_V; 22 | 23 | 24 | --Generate Horizontal and Vertical Timing Signals for Video Signal 25 | -- H_count counts pixels (640 + extra time for sync signals) 26 | -- 27 | -- Horiz_sync ------------------------------------__________-------- 28 | -- H_count 0 640 659 755 799 29 | -- 30 | do_hcount: process(clk, ena) 31 | begin 32 | if(rising_edge(clk) and ena='1') then 33 | IF (h_count = 799) THEN 34 | h_count <= "0000000000"; 35 | if v_count = 524 then 36 | v_count <= "0000000000"; 37 | else 38 | v_count <= v_count + 1; 39 | end if; 40 | ELSE 41 | h_count <= h_count + 1; 42 | END IF; 43 | end if; 44 | end process; 45 | 46 | --Generate Horizontal Sync Signal using H_count 47 | do_hsync: process(clk, ena) 48 | begin 49 | if (rising_edge(clk) and ena='1') then 50 | IF (h_count <= 755) AND (h_count >= 659) THEN 51 | horiz_sync <= '0'; 52 | ELSE 53 | horiz_sync <= '1'; 54 | END IF; 55 | end if; 56 | end process; 57 | 58 | --V_count counts rows of pixels (480 + extra time for sync signals) 59 | -- 60 | -- Vert_sync -----------------------------------------------_______------------ 61 | -- V_count 0 480 493-494 524 62 | -- 63 | 64 | 65 | 66 | 67 | -- Generate Vertical Sync Signal using V_count 68 | do_vs: process(clk, ena) 69 | begin 70 | if (rising_edge(clk) and ena='1') then 71 | IF (v_count <= 494) AND (v_count >= 493) THEN 72 | vert_sync <= '0'; 73 | ELSE 74 | vert_sync <= '1'; 75 | END IF; 76 | end if; 77 | end process; 78 | 79 | -- Generate Video on Screen Signals for Pixel Data 80 | do_video_on: process(clk, ena) 81 | begin 82 | if (rising_edge(clk) and ena='1') then 83 | pixel_column <= h_count; 84 | IF (h_count <= 639) THEN 85 | video_on_h <= '1'; 86 | --pixel_column <= h_count; 87 | ELSE 88 | video_on_h <= '0'; 89 | END IF; 90 | 91 | pixel_row <= v_count; 92 | IF (v_count <= 479) THEN 93 | video_on_v <= '1'; 94 | -- pixel_row <= v_count; 95 | ELSE 96 | video_on_v <= '0'; 97 | END IF; 98 | end if; 99 | end process; 100 | 101 | horiz_sync_out <= horiz_sync; 102 | vert_sync_out <= vert_sync; 103 | 104 | END a; 105 | -------------------------------------------------------------------------------- /fpga/work/.gitignore: -------------------------------------------------------------------------------- 1 | _ngo/ 2 | _xmsgs/ 3 | ep994a.bgn 4 | ep994a.bld 5 | ep994a.cmd_log 6 | ep994a.drc 7 | ep994a.gise 8 | ep994a.lso 9 | ep994a.ncd 10 | ep994a.ngc 11 | ep994a.ngd 12 | ep994a.ngr 13 | ep994a.pad 14 | ep994a.par 15 | ep994a.pcf 16 | ep994a.prj 17 | ep994a.ptwx 18 | ep994a.stx 19 | ep994a.syr 20 | ep994a.twr 21 | ep994a.twx 22 | ep994a.unroutes 23 | ep994a.ut 24 | ep994a.xpi 25 | ep994a.xst 26 | ep994a_bitgen.xwbt 27 | ep994a_envsettings.html 28 | ep994a_guide.ncd 29 | ep994a_map.map 30 | ep994a_map.mrp 31 | ep994a_map.ncd 32 | ep994a_map.ngm 33 | ep994a_map.xrpt 34 | ep994a_ngdbuild.xrpt 35 | ep994a_pad.csv 36 | ep994a_pad.txt 37 | ep994a_par.xrpt 38 | ep994a_summary.xml 39 | ep994a_usage.xml 40 | ep994a_xst.xrpt 41 | par_usage_statistics.html 42 | pepiserial_summary.html 43 | usage_statistics_webtalk.html 44 | webtalk.log 45 | webtalk_pn.xml 46 | xlnx_auto_0_xdb/ 47 | xst/ 48 | fpga/work/tb_tms9900_beh.prj 49 | fpga/work/tb_tms9900_stx_beh.prj 50 | -------------------------------------------------------------------------------- /fpga/work/ep994a.bit: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Speccery/EP994A/4a469a13e57afc1def67a5ad3dd15186b6e03374/fpga/work/ep994a.bit -------------------------------------------------------------------------------- /fpga/work/ep994a_summary.html: -------------------------------------------------------------------------------- 1 | Xilinx Design Summary 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | 43 | 44 | 45 | 49 | 50 | 51 | 52 |
ep994a Project Status (09/26/2018 - 22:16:38)
Project File:ep994a.xiseParser Errors: No Errors
Module Name:ep994aImplementation State:Programming File Generated
Target Device:xc6slx9-2ftg256
  • Errors:
 
Product Version:ISE 14.7
  • Warnings:
 
Design Goal:Balanced
  • Routing Results:
34 | All Signals Completely Routed
Design Strategy:Xilinx Default (unlocked)
  • Timing Constraints:
41 | All Constraints Met
Environment: 46 | 47 | System Settings 48 |
  • Final Timing Score:
0  (Timing Report)
53 | 54 | 55 | 56 |  
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 |
Device Utilization Summary [-]
Slice Logic UtilizationUsedAvailableUtilizationNote(s)
Number of Slice Registers97011,4408% 
    Number used as Flip Flops969   
    Number used as Latches1   
    Number used as Latch-thrus0   
    Number used as AND/OR logics0   
Number of Slice LUTs1,3795,72024% 
    Number used as logic1,3585,72023% 
        Number using O6 output only886   
        Number using O5 output only124   
        Number using O5 and O6348   
        Number used as ROM0   
    Number used as Memory41,4401% 
        Number used as Dual Port RAM0   
        Number used as Single Port RAM4   
            Number using O6 output only0   
            Number using O5 output only0   
            Number using O5 and O64   
        Number used as Shift Register0   
    Number used exclusively as route-thrus17   
        Number with same-slice register load9   
        Number with same-slice carry load8   
        Number with other load0   
Number of occupied Slices5161,43036% 
Number of MUXCYs used3282,86011% 
Number of LUT Flip Flop pairs used1,584   
    Number with an unused Flip Flop6921,58443% 
    Number with an unused LUT2051,58412% 
    Number of fully used LUT-FF pairs6871,58443% 
    Number of unique control sets82   
    Number of slice register sites lost
        to control set restrictions
19811,4401% 
Number of bonded IOBs10918658% 
    Number of LOCed IOBs109109100% 
Number of RAMB16BWERs93228% 
Number of RAMB8BWERs0640% 
Number of BUFIO2/BUFIO2_2CLKs1323% 
    Number used as BUFIO2s1   
    Number used as BUFIO2_2CLKs0   
Number of BUFIO2FB/BUFIO2FB_2CLKs1323% 
    Number used as BUFIO2FBs1   
    Number used as BUFIO2FB_2CLKs0   
Number of BUFG/BUFGMUXs21612% 
    Number used as BUFGs2   
    Number used as BUFGMUX0   
Number of DCM/DCM_CLKGENs1425% 
    Number used as DCMs1   
    Number used as DCM_CLKGENs0   
Number of ILOGIC2/ISERDES2s02000% 
Number of IODELAY2/IODRP2/IODRP2_MCBs02000% 
Number of OLOGIC2/OSERDES2s02000% 
Number of BSCANs040% 
Number of BUFHs01280% 
Number of BUFPLLs080% 
Number of BUFPLL_MCBs040% 
Number of DSP48A1s0160% 
Number of ICAPs010% 
Number of MCBs020% 
Number of PCILOGICSEs020% 
Number of PLL_ADVs020% 
Number of PMVs010% 
Number of STARTUPs010% 
Number of SUSPEND_SYNCs010% 
Average Fanout of Non-Clock Nets3.94   
434 | 435 | 436 | 437 |  
438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 448 | 449 | 450 | 451 | 452 | 453 | 455 | 456 | 457 |
Performance Summary [-]
Final Timing Score:0 (Setup: 0, Hold: 0, Component Switching Limit: 0)Pinout Data:Pinout Report
Routing Results: 447 | All Signals Completely RoutedClock Data:Clock Report
Timing Constraints: 454 | All Constraints Met  
458 | 459 | 460 | 461 |  
462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 |
Detailed Reports [-]
Report NameStatusGeneratedErrorsWarningsInfos
Synthesis ReportCurrentsu 7. loka 01:02:13 20180154 Warnings (154 new)26 Infos (26 new)
Translation ReportCurrentsu 7. loka 01:02:19 2018001 Info (0 new)
Map ReportCurrentsu 7. loka 01:02:36 2018   
Place and Route ReportCurrentsu 7. loka 01:02:46 2018   
Power Report     
Post-PAR Static Timing ReportCurrentsu 7. loka 01:02:52 2018003 Infos (0 new)
Bitgen ReportCurrentsu 7. loka 01:03:01 201805 Warnings (0 new)1 Info (0 new)
473 |  
474 | 475 | 476 | 477 | 478 |
Secondary Reports [-]
Report NameStatusGenerated
WebTalk ReportCurrentsu 7. loka 01:03:01 2018
WebTalk Log FileCurrentsu 7. loka 01:03:05 2018
479 | 480 | 481 |
Date Generated: 11/14/2018 - 16:24:28
482 | -------------------------------------------------------------------------------- /memloader/.gitignore: -------------------------------------------------------------------------------- 1 | memloader.exe 2 | AMSTEST4-8.BIN 3 | RXBC.Bin 4 | RXBD.Bin 5 | RXBG.Bin 6 | TI-InvaC.Bin 7 | TI-InvaG.Bin 8 | TIEAG.BIN 9 | memloader-vs/Debug/ 10 | memloader-vs/memloader-vs.VC.VC.opendb 11 | memloader-vs/memloader-vs/Debug/ 12 | memloader-vs/.vs 13 | 14 | -------------------------------------------------------------------------------- /memloader/Makefile: -------------------------------------------------------------------------------- 1 | all: memloader.c 2 | gcc -g -o memloader memloader.c diskio.c 3 | 4 | clean: 5 | rm memloader.o diskio.o memloader.exe 6 | 7 | -------------------------------------------------------------------------------- /memloader/diskio.c: -------------------------------------------------------------------------------- 1 | // diskio.c 2 | // Started by Erik Piehl Nov 2016 3 | 4 | // Define the following for cygwin compatibility 5 | #define _CRT_SECURE_NO_WARNINGS 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | // #include 12 | #include 13 | #include 14 | 15 | #include "diskio.h" 16 | #include "fpga-mem.h" 17 | 18 | void print_pab(const char *msg, unsigned short pab_vdp_addr, struct ti_pab *p); 19 | 20 | 21 | #if defined (__GNUC__) 22 | // functions corresponding to Visual C++ secure runtime - although these are not secure. 23 | int vsprintf_s(char *buf, size_t bufsize, const char *format, ...) 24 | { 25 | va_list a; 26 | va_start(a, format); 27 | vsprintf(buf, format, a); 28 | printf(buf); 29 | va_end(a); 30 | } 31 | 32 | char *strcpy_s(char *dest, size_t destsize, const char *src) { 33 | strcpy(dest, src); 34 | return dest; 35 | } 36 | #endif 37 | 38 | struct tifile { 39 | FILE *m_file; 40 | unsigned char *m_data; //m_file != NULL) { 67 | fclose(files[i]->m_file); 68 | } 69 | if (files[i]->m_data != NULL) { 70 | free(files[i]->m_data); 71 | } 72 | free(files[i]); 73 | files[i] = NULL; 74 | } 75 | 76 | int find_tifile_handle(unsigned short vdp_pab_addr) { 77 | for (int i = 0; i < sizeof(files) / sizeof(files[0]); i++) { 78 | if (files[i] != NULL && files[i]->m_pab == vdp_pab_addr) 79 | return i; 80 | } 81 | return -1; 82 | } 83 | 84 | int create_new_file(int i, struct ti_pab *pab, unsigned short pab_vdp_addr) { 85 | struct tifiles_header *th; 86 | // create a new file. 87 | files[i]->m_file = fopen(files[i]->m_name, "wb"); 88 | if (files[i]->m_file == NULL) { 89 | free_slot(i); 90 | return -3; 91 | } 92 | // Ok. We can open the file, but let's close it for now. 93 | fclose(files[i]->m_file); 94 | files[i]->m_file = NULL; 95 | 96 | files[i]->m_var = (pab->flags & 0x10) ? variable : fixed; 97 | // initialize the header. 98 | th = &files[i]->m_header; 99 | memcpy(th->id, "\x07TIFILES", 8); 100 | th->FileType = pab->flags & 0x10 ? 0x80 : 0; // bit: record type fixed=0 or variable=1 101 | // Setup internal / display: in PAB flags bit 3: 0=display, 1=internal 102 | // in TIFILES header bit 1: 0 = display, 1=internal 103 | th->FileType |= pab->flags & 0x08 ? 2 : 0; // bit 1: 0=internal 1=display 104 | // bit 0 of filetype is 0 for data or 1 for program. we leave it at zero. 105 | 106 | if (pab->record_length > 254) 107 | pab->record_length = 254; 108 | if (pab->record_length == 0) 109 | pab->record_length = 80; 110 | th->RecordLength = pab->record_length; 111 | if (files[i]->m_var == variable) 112 | th->RecordsPerSector = 256 / (1 + pab->record_length); 113 | else 114 | th->RecordsPerSector = 256 / pab->record_length; 115 | // I guess we are done. 116 | files[i]->m_pab = pab_vdp_addr; 117 | return i; 118 | } 119 | 120 | // returns index to tifiles table, or negative value on error 121 | int open_tifile(const char *name, unsigned short pab_addr, struct ti_pab *pab, int writeback) { 122 | int i; 123 | int found = 0; 124 | for (i = 0; i < sizeof(files) / sizeof(files[0]); i++) { 125 | if (files[i] == NULL) { 126 | found = 1; 127 | break; 128 | } 129 | } 130 | if (!found) 131 | return ERR_FILEERROR; // No free slot available. 132 | files[i] = malloc(sizeof(struct tifile)); 133 | if (!files[i]) 134 | return ERR_FILEERROR; 135 | memset(files[i], 0, sizeof(struct tifile)); 136 | strcpy_s(files[i]->m_name, sizeof(files[i]->m_name), name); 137 | files[i]->m_mode = (pab->flags >> 1) & 3; 138 | 139 | int no_file_buffering = 0; 140 | 141 | switch(files[i]->m_mode) { 142 | case filemode_output: 143 | { 144 | int r = create_new_file(i, pab, pab_addr); 145 | if (r < 0) { 146 | free_slot(i); 147 | return ERR_FILEERROR; 148 | } 149 | no_file_buffering = 1; 150 | } 151 | break; 152 | case filemode_update: 153 | { 154 | #if defined (__GNUC__) 155 | struct stat statbuf; 156 | #define STAT stat 157 | #else 158 | // Visual Studio 2015 159 | struct _stat statbuf; 160 | #define STAT _stat 161 | #endif 162 | int could_be_ok = 0; 163 | if (STAT(files[i]->m_name, &statbuf) == 0) { 164 | // The file exists. Validate it's size. 165 | if (statbuf.st_size >= sizeof(struct tifiles_header)) 166 | could_be_ok = 1; 167 | } 168 | if (!could_be_ok) { 169 | // it is a new file, create it like with filemode_output 170 | int r = create_new_file(i, pab, pab_addr); 171 | if (r < 0) { 172 | free_slot(i); 173 | return ERR_FILEERROR; 174 | } 175 | no_file_buffering = 1; 176 | break; 177 | } 178 | } 179 | // we fall through if we were able to open the file in read mode. 180 | case filemode_append: 181 | case filemode_input: 182 | // filemode_input, filemode_append, filemode_update - read the file. 183 | files[i]->m_file = fopen(files[i]->m_name, "rb"); 184 | if (files[i]->m_file == NULL) { 185 | free_slot(i); 186 | return ERR_FILEERROR; // -3; 187 | } 188 | int r = fread(&files[i]->m_header, sizeof(struct tifiles_header), 1, files[i]->m_file); 189 | if (r != 1) { 190 | free_slot(i); 191 | return ERR_FILEERROR; // -4; 192 | } 193 | // Validate header here 194 | if (strncmp(files[i]->m_header.id, "\x07TIFILES", 8) != 0) { 195 | free_slot(i); 196 | return ERR_FILEERROR; // -5; 197 | } 198 | 199 | // Make sure the types match: fixed/variable settings must match in file and PAB 200 | if ((files[i]->m_header.FileType & TIFILES_VARIABLE) != ((pab->flags & 0x10) << 3)) { 201 | free_slot(i); 202 | fprintf(stderr, "Returning ERR_BADATTRIBUTE, types do not match\n"); 203 | return ERR_BADATTRIBUTE; 204 | } 205 | 206 | files[i]->m_pab = pab_addr; 207 | unsigned short len = files[i]->m_header.LengthSectors; 208 | files[i]->m_header.LengthSectors = (len << 8) | (len >> 8); // swap bytes 209 | break; 210 | } 211 | 212 | // Allocate a large buffer for the file. 213 | files[i]->m_size = 512 * 1024; 214 | files[i]->m_data = malloc(files[i]->m_size); // for now allocate 0.5M and assume that's good 215 | memset(files[i]->m_data, 0, files[i]->m_size);// quick and dirty. 216 | files[i]->m_var = files[i]->m_header.FileType & TIFILES_VARIABLE ? variable : fixed; 217 | files[i]->m_cur_data = files[i]->m_data; 218 | files[i]->m_record = 0; 219 | 220 | // Read the whole buffer at this point in time. 221 | if (!no_file_buffering) { 222 | printf("buffer_tifile(%d) returned: %d\n", i, buffer_tifile(i)); 223 | } 224 | 225 | // Write the response to the TI. This time we write back the entire PAB. 226 | if (writeback) { 227 | struct ti_pab ret_pab = *pab; // copy original as basis 228 | ret_pab.record_number = 0; 229 | ret_pab.record_length = files[i]->m_header.RecordLength; 230 | // swap endianess on 16-bit fields 231 | swap_word_bytes(&ret_pab.addr); 232 | swap_word_bytes(&ret_pab.record_number); 233 | ret_pab.flags &= 0x1F; // clear top 3 bits -> OK 234 | print_pab("Return PAB: ", pab_addr, &ret_pab); 235 | WriteMemoryBlock((unsigned char *)&ret_pab, DISK_BUFFER_ADDR_PC, sizeof(ret_pab)); 236 | issue_cmd(2, DISK_BUFFER_ADDR_TI, pab_addr, sizeof(ret_pab)); 237 | } 238 | 239 | return ERR_NOERROR; 240 | } 241 | 242 | int buffer_tifile(int i) { 243 | // read the file a la classic99, basically we fill files[i]->m_data with what we get from the file. 244 | // the code below is a direct ripoff from classic99/FiadDisk.cpp - BufferFiadFile 245 | int idx = 0; // count up the records read 246 | int nSector = 256; // bytes left in this sector 247 | FILE *fp = files[i]->m_file; 248 | unsigned char tmpbuf[256]; 249 | fseek(fp, sizeof(struct tifiles_header), SEEK_SET);// skip the header 250 | unsigned char *pData = files[i]->m_data; 251 | 252 | // we need to let the embedded code decide the terminating rule 253 | for (;;) { 254 | if (feof(fp)) { 255 | debug_write("Premature EOF - truncating read."); 256 | files[i]->m_header.NumberRecords = idx; // note: this modifies the header we read from disk 257 | break; 258 | } 259 | 260 | if (variable == files[i]->m_var) { 261 | // read a variable record 262 | int nLen = fgetc(fp); 263 | if (EOF == nLen) { 264 | debug_write("Corrupt file - truncating read."); 265 | files[i]->m_header.NumberRecords = idx; 266 | break; 267 | } 268 | 269 | nSector--; 270 | if (nLen == 0xff) { 271 | // end of sector indicator, no record read, skip rest of sector 272 | fread(tmpbuf, 1, nSector, fp); 273 | nSector = 256; 274 | files[i]->m_header.NumberRecords--; 275 | // are we done? 276 | if (files[i]->m_header.NumberRecords == 0) { 277 | // yes we are, get the true count 278 | files[i]->m_header.NumberRecords = idx; 279 | break; 280 | } 281 | } 282 | else { 283 | // check for buffer resize 284 | if ((files[i]->m_data + files[i]->m_size) - pData < (files[i]->m_header.RecordLength + 2) * 10) { 285 | int nOffset = pData - files[i]->m_data; // in case the buffer moves 286 | // time to grow the buffer - add another 100 lines 287 | files[i]->m_size += (100) * (files[i]->m_header.RecordLength + 2); 288 | files[i]->m_data = (unsigned char*)realloc(files[i]->m_data, files[i]->m_size); 289 | pData = files[i]->m_data + nOffset; 290 | } 291 | 292 | // clear buffer 293 | memset(pData, 0, files[i]->m_header.RecordLength + 2); 294 | 295 | // check again 296 | if (nSector < nLen) { 297 | debug_write("Corrupted file - truncating read."); 298 | files[i]->m_header.NumberRecords = idx; 299 | break; 300 | } 301 | 302 | // we got some data, read it in and count off the record 303 | // verify it (don't get screwed up by a bad file) 304 | if (nLen > files[i]->m_header.RecordLength) { 305 | debug_write("Potentially corrupt file - skipping end of record."); 306 | 307 | // store length data 308 | *(unsigned short*)pData = files[i]->m_header.RecordLength; 309 | pData += 2; 310 | 311 | fread(pData, 1, files[i]->m_header.RecordLength, fp); 312 | nSector -= nLen; 313 | // skip the excess and trim down nLen 314 | fread(tmpbuf, 1, nLen - files[i]->m_header.RecordLength, fp); 315 | nLen = files[i]->m_header.RecordLength; 316 | } 317 | else { 318 | // record is okay (normal case) 319 | 320 | // write length data 321 | *(unsigned short*)pData = nLen; 322 | pData += 2; 323 | 324 | fread(pData, 1, nLen, fp); 325 | nSector -= nLen; 326 | } 327 | // count off a valid record and update the pointer 328 | idx++; 329 | pData += files[i]->m_header.RecordLength; 330 | } 331 | } 332 | else { 333 | // are we done? 334 | if (idx >= files[i]->m_header.NumberRecords) { 335 | break; 336 | } 337 | 338 | // clear buffer 339 | memset(pData, 0, files[i]->m_header.RecordLength + 2); 340 | 341 | // read a fixed record 342 | if (nSector < files[i]->m_header.RecordLength) { 343 | // not enough room for another record, skip to the next sector 344 | fread(tmpbuf, 1, nSector, fp); 345 | nSector = 256; 346 | } 347 | else { 348 | // a little simpler, we just need to read the data 349 | *(unsigned short*)pData = files[i]->m_header.RecordLength; 350 | pData += 2; 351 | 352 | fread(pData, 1, files[i]->m_header.RecordLength, fp); 353 | nSector -= files[i]->m_header.RecordLength; 354 | idx++; 355 | pData += files[i]->m_header.RecordLength; 356 | } 357 | } 358 | } 359 | 360 | fclose(fp); 361 | files[i]->m_file = NULL; 362 | return 1; 363 | } 364 | 365 | void dump_record(int j, unsigned char *pData) { 366 | unsigned short len = *(unsigned short*)pData; 367 | pData += 2; 368 | printf("Record %d [%d]: ", j, len); 369 | for (int k = 0; k < len; k++) { 370 | if (pData[k] < 32 || pData[k] > 127) 371 | putchar('.'); 372 | else 373 | putchar(pData[k]); 374 | } 375 | printf("\n"); 376 | } 377 | 378 | int dump_records(int i) { 379 | if (i < 0) { 380 | debug_write("dump_records, broken id %d\n", i); 381 | return 0; 382 | } 383 | unsigned char *pData = files[i]->m_data; 384 | for (int j = 0; j < files[i]->m_header.NumberRecords; j++) { 385 | unsigned short len = *(unsigned short*)pData; 386 | dump_record(j, pData); 387 | pData += 2; 388 | pData += files[i]->m_header.RecordLength; 389 | } 390 | return 1; 391 | } 392 | 393 | // Return TI-99/4A success code 394 | int read_record(int index, const struct ti_pab *pab) { 395 | if (index < 0) 396 | return ERR_FILEERROR; 397 | struct tifile *f = files[index]; 398 | if (f->m_record >= f->m_header.NumberRecords) 399 | return ERR_READPASTEOF; 400 | // Dump the record 401 | unsigned char *pData = f->m_cur_data; 402 | dump_record(f->m_record, pData); 403 | unsigned short len = *(unsigned short*)f->m_cur_data; 404 | f->m_cur_data += 2; 405 | // Transfer to TI memory - start 406 | if (pab != NULL) { 407 | // Write record to VDP memory via the transfer buffer 408 | WriteMemoryBlock(f->m_cur_data, DISK_BUFFER_ADDR_PC, len); 409 | unsigned vdp_addr = pab->addr; 410 | issue_cmd(2, DISK_BUFFER_ADDR_TI, vdp_addr, len); 411 | // Update byte 5 of PAB to be record length (low byte of len) 412 | vdp_addr = files[index]->m_pab + 5; 413 | WriteMemoryBlock((unsigned char *)&len, DISK_BUFFER_ADDR_PC, 1); 414 | issue_cmd(2, DISK_BUFFER_ADDR_TI, vdp_addr, 1); 415 | } 416 | // Transfer to TI memory - end 417 | f->m_cur_data += f->m_header.RecordLength; // point to next 418 | f->m_record++; 419 | return ERR_NOERROR; 420 | } 421 | 422 | void write_entire_file(int index) { 423 | struct tifile *f = files[index]; 424 | f->m_file = fopen(f->m_name, "wb"); 425 | if (f->m_file == NULL) { 426 | fprintf(stderr, "write_entire_file: file open (%s) failed.\n", f->m_name); 427 | return; 428 | } 429 | if (f->m_var == variable) 430 | *f->m_cur_data++ = 255; // put in the terminating byte 431 | fseek(f->m_file, 0, SEEK_SET); 432 | f->m_header.LengthSectors = 1 + f->m_header.NumberRecords / f->m_header.RecordsPerSector; 433 | f->m_header.BytesInLastSector = 256 - (f->m_header.RecordsPerSector + 1)*f->m_header.RecordLength; 434 | // DO BYTE SWAPPING 435 | struct tifiles_header k = f->m_header; 436 | swap_word_bytes(&k.LengthSectors); 437 | fwrite(&k, 1, sizeof(k), f->m_file); 438 | // Loop through the records. 439 | unsigned char *p = f->m_data; 440 | int records = 0; 441 | int sector_bytes = 256; 442 | for (p = f->m_data; p < f->m_cur_data; ) { 443 | // write the lenght of the record only for variable records. The lenghth is a BYTE value. 444 | unsigned short len = *(unsigned short *)p; 445 | // The record cannot cross the 256 byte sector boundary. If that is about to happen, 446 | // we need to move to the next sector. 447 | if (len == 255 && f->m_var == variable) { 448 | // End of data. Write end marker and exit. 449 | fputc(len, f->m_file); 450 | break; 451 | } 452 | // See if there is room for this record in this sector. 453 | if ((f->m_var == variable && sector_bytes < len + 1) || (f->m_var == fixed && sector_bytes < len)) { 454 | // We need to move to the next sector. Pad this one with zeros. 455 | while (sector_bytes-- > 0) { 456 | fputc(0, f->m_file); 457 | } 458 | sector_bytes = 256; 459 | } 460 | // Continue with normal processing 461 | if (f->m_var == variable) { 462 | fputc(len, f->m_file); 463 | sector_bytes--; 464 | } 465 | if (len > 255 || len == 0) { 466 | fprintf(stderr, "write_entire_file: internal error len=%d, records=%d\n", len, records); 467 | len = 254; 468 | } 469 | // next we write the actual bytes 470 | fwrite(p + 2, 1, len, f->m_file); 471 | p += 2 + f->m_header.RecordLength; 472 | sector_bytes -= len; 473 | ++records; 474 | } 475 | // Pad to the end of sector 476 | while (sector_bytes-- > 0) { 477 | fputc(0, f->m_file); 478 | } 479 | // Theoretically we are done 480 | if (records != f->m_header.NumberRecords) { 481 | fprintf(stderr, "write_entire_file: internal error records=%d != NumberRecords %d\n", records, f->m_header.NumberRecords); 482 | } 483 | fclose(f->m_file); 484 | f->m_file = NULL; 485 | } 486 | 487 | int close_tifile(int index) { 488 | if (index < 0) 489 | return ERR_FILEERROR; 490 | if (files[index]->m_mode == filemode_output) { 491 | write_entire_file(index); 492 | } 493 | free_slot(index); 494 | return ERR_NOERROR; 495 | } 496 | 497 | int write_record(int index, const struct ti_pab *pab) { 498 | // read the data from the TI to my buffer. 499 | unsigned short vdp_addr = pab->addr; 500 | unsigned short chunk = pab->count; 501 | if (files[index]->m_var == fixed) 502 | chunk = files[index]->m_header.RecordLength; 503 | *(unsigned short *)files[index]->m_cur_data = chunk; 504 | if (chunk == 0) { 505 | fprintf(stderr, "ERROR: Serious file error, read count would be zero!\n"); 506 | return ERR_BUFFERFULL; 507 | } 508 | issue_cmd(1, vdp_addr, DISK_BUFFER_ADDR_TI, chunk); 509 | ReadMemoryBlock(files[index]->m_cur_data+2, DISK_BUFFER_ADDR_PC, chunk); 510 | files[index]->m_cur_data += 2 + files[index]->m_header.RecordLength; 511 | files[index]->m_header.NumberRecords++; 512 | return ERR_NOERROR; 513 | } 514 | 515 | const char *get_name(struct ti_pab *p) { 516 | static char name[80]; 517 | int i; 518 | for (i = 0; iname_length; i++) 519 | name[i] = p->name[i]; 520 | name[i] = '\0'; 521 | return name; 522 | } 523 | 524 | void print_pab(const char *msg, unsigned short pab_vdp_addr, struct ti_pab *p) { 525 | char name[80]; 526 | char *op = "Unkown"; 527 | switch (p->opcode) { 528 | case 0: op = "Open"; break; 529 | case 1: op = "Close"; break; 530 | case 2: op = "Read"; break; 531 | case 3: op = "Write"; break; 532 | case 4: op = "Restore"; break; 533 | case 5: op = "Load"; break; 534 | case 6: op = "Save"; break; 535 | case 7: op = "Delete"; break; 536 | case 8: op = "Scratch"; break; 537 | case 9: op = "Status"; break; 538 | } 539 | char flags[128]; 540 | strcpy(flags, "["); 541 | strcat(flags, p->flags & 1 ? "REL " : "SEQ "); 542 | char *mode[] = { "Update ", "Output ", "Input ", "Append " }; 543 | strcat(flags, mode[(p->flags >> 1) & 3]); 544 | strcat(flags, p->flags & 8 ? "INTE " : "DISP "); 545 | strcat(flags, p->flags & 16 ? "VAR" : "FIX"); 546 | strcat(flags, "]"); 547 | strcpy(name, get_name(p)); 548 | printf("%10s %04X: %s %s err=%d addr=0x%04X rec=%d cnt=%d n=%d offs=%d %s\n", 549 | msg ? msg : "", 550 | pab_vdp_addr, op, flags, p->flags >> 5, p->addr, p->record_length, p->count, 551 | p->record_number, p->screen_offset, name); 552 | unsigned char *t = (unsigned char *)p; 553 | for (int i = 0; i < 10; i++) 554 | printf("%02X ", t[i]); 555 | printf("\n"); 556 | } 557 | 558 | int swap_word_bytes(unsigned short *k) { 559 | unsigned short t = *k; 560 | t = (t >> 8) | (t << 8); 561 | *k = t; 562 | return t; 563 | } 564 | 565 | void send_cmd(const struct dsr_cmd *p) { 566 | struct dsr_cmd mycmd; 567 | memcpy(&mycmd, p, sizeof(mycmd)); 568 | swap_word_bytes(&mycmd.arg1); 569 | swap_word_bytes(&mycmd.arg2); 570 | swap_word_bytes(&mycmd.arg3); 571 | swap_word_bytes(&mycmd.cmd); 572 | WriteMemoryBlock((char *)&mycmd, CMD_ADDR, 8); 573 | } 574 | 575 | void issue_cmd(unsigned short cmd, unsigned short arg1, unsigned short arg2, unsigned short arg3) { 576 | struct dsr_cmd k; 577 | k.cmd = cmd; 578 | k.arg1 = arg1; 579 | k.arg2 = arg2; 580 | k.arg3 = arg3; 581 | send_cmd(&k); 582 | } 583 | 584 | int wait_cmd_complete(const char *msg) { 585 | int timeout = 10; 586 | struct dsr_cmd mycmd; 587 | while (timeout > 0) { 588 | ReadMemoryBlock((char *)&mycmd, CMD_ADDR, 8); 589 | if (mycmd.cmd == 0) 590 | return 1; // Done 591 | timeout--; 592 | } 593 | fprintf(stderr, "Error: wait_cmd_complete(%s) timeout\n", msg); 594 | return 0; 595 | } 596 | 597 | void generate_filename(char *destname, const char *name) { 598 | char tmp[256]; 599 | char *fname = NULL; 600 | int first_dot = 1; 601 | int i; 602 | for (i = 0; i8100 638 | int len = pab->byte_count; 639 | int vdp_addr = pab->addr; 640 | while (len > 0) { 641 | int chunk = len; 642 | if (chunk > 256) 643 | chunk = 256; 644 | issue_cmd(1, vdp_addr, DISK_BUFFER_ADDR_TI, chunk); 645 | // Wait for the TMS99105 to do the job 646 | if (!wait_cmd_complete("chunk read")) { 647 | return; 648 | } 649 | // Ok we did get our chunk, let's read it to PC memory and write it to disk 650 | unsigned char buf[256]; 651 | ReadMemoryBlock(buf, DISK_BUFFER_ADDR_PC, chunk); 652 | fwrite(buf, 1, chunk, f); 653 | len -= chunk; 654 | vdp_addr += chunk; 655 | } 656 | // We are done with saving! 657 | fclose(f); 658 | // Exit the DSR 659 | issue_cmd(3, 0, 0, 0); 660 | printf("Saved %d bytes\n", pab->byte_count); 661 | } 662 | 663 | void DoLoad(const char *name, const struct ti_pab *pab) { 664 | char filename[256]; 665 | generate_filename(filename, name); 666 | FILE *f = fopen(filename, "rb"); 667 | if (f == NULL) { 668 | // Failure, return error. 669 | fprintf(stderr, "Error: DoLoad was unable to open %s\n", filename); 670 | issue_cmd(3, 0x7000, 0, 0); 671 | return; 672 | } 673 | // LOAD data in PAB: 674 | // 2 & 3 = Start address of memory dump area 675 | // 6,7 Number of bytes available 676 | unsigned vdp_addr = pab->addr; 677 | unsigned pab_count = pab->byte_count; // max size 678 | int chunk = 0; 679 | int length = 0; 680 | do { 681 | unsigned char buf[256]; 682 | chunk = fread(buf, 1, sizeof(buf), f); 683 | // Huge hack: if we have TIFILES header, just skip it. 684 | if (length == 0 && strncmp(buf, "\x07TIFILES", 8) == 0) { 685 | // We have TIFILES header, let's skip it. 686 | memcpy(buf, buf + 128, chunk - 128); 687 | chunk -= 128; 688 | printf("Skipping TIFILES header\n"); 689 | } 690 | else if (length == 0 && strncmp(buf, "AMSTEST4", 8) == 0) { 691 | // This is V9T9 format apparently - again skip 128 bytes 692 | memcpy(buf, buf + 128, chunk - 128); 693 | chunk -= 128; 694 | printf("Skipping V9T9 header for AMSTEST4\n"); 695 | } 696 | 697 | // Check that chunk does not bring us over the available space 698 | if (length + chunk > pab_count) 699 | chunk = pab_count - length; 700 | if (chunk > 0) { 701 | WriteMemoryBlock(buf, DISK_BUFFER_ADDR_PC, chunk); 702 | issue_cmd(2, DISK_BUFFER_ADDR_TI, vdp_addr, chunk); // Write to VDP memory 703 | } 704 | length += chunk; 705 | vdp_addr += chunk; 706 | } while (chunk > 0 && length < pab_count); 707 | // We are done with saving! 708 | fclose(f); 709 | // Exit the DSR 710 | issue_cmd(3, 0, 0, 0); 711 | printf("Loaded %d bytes\n", length); 712 | } 713 | 714 | int DoDiskProcess() { 715 | char cmd_buf[64]; 716 | ReadMemoryBlock(cmd_buf, SCRATCHPAD, sizeof(cmd_buf)); 717 | if (!(cmd_buf[10] == 0 && cmd_buf[11] == 1)) 718 | return 0; // Nothing to be done 719 | // We have a command from the CPU. PAB is at offset 32. 720 | struct ti_pab *p = (struct ti_pab *)&cmd_buf[32]; 721 | unsigned short pabsta = *(unsigned short *)&cmd_buf[DSR_PABSTA - SCRATCHPAD]; 722 | print_pab("Got PAB: ", pabsta, p); 723 | swap_word_bytes(&pabsta); 724 | swap_word_bytes(&p->addr); 725 | swap_word_bytes(&p->record_number); 726 | if (p->opcode == OP_SAVE) { 727 | // Save operation, the TI wants to save a program to our disk. 728 | // DEBUG: Save sprite table 729 | if (0) { 730 | struct dsr_cmd k; 731 | k.cmd = 1; // read VDP RAM 732 | k.arg1 = 0x300; // From sprite attribute table 733 | k.arg2 = DISK_BUFFER_ADDR_TI; 734 | k.arg3 = 256; 735 | send_cmd(&k); 736 | // Wait for the TMS99105 to do the job 737 | if (!wait_cmd_complete("sprite read")) { 738 | fprintf(stderr, "debug code error\n"); 739 | } 740 | char sprites[256]; 741 | ReadMemoryBlock(sprites, DISK_BUFFER_ADDR_PC, 256); 742 | FILE *f = fopen("sprites.bin", "wb"); 743 | if (f) { 744 | fwrite(sprites, 1, 256, f); 745 | fclose(f); 746 | } 747 | } 748 | DoSave(get_name(p), p); 749 | } 750 | else if (p->opcode == OP_LOAD) { 751 | DoLoad(get_name(p), p); 752 | } 753 | else if (p->opcode == OP_OPEN) { 754 | char filename[256]; 755 | generate_filename(filename, get_name(p)); 756 | int r = open_tifile(filename, pabsta, p, 1); 757 | if (r != 0) { 758 | // Let's return an error (file not found) 759 | fprintf(stderr, "Error: OP_OPEN was unable to open %s\n", get_name(p)); 760 | } 761 | issue_cmd(3, r << 13, 0, 0); 762 | } 763 | else if (p->opcode == OP_READ) { 764 | int h = find_tifile_handle(pabsta); 765 | int r = read_record(h, p); 766 | issue_cmd(3, r << 13, 0, 0); // return with code 767 | } 768 | else if (p->opcode == OP_CLOSE) { 769 | int h = find_tifile_handle(pabsta); 770 | int r = close_tifile(h); 771 | // print_pab("Return PAB: ", pab_addr, &ret_pab); 772 | issue_cmd(3, r << 13, 0, 0); // return with code 773 | } 774 | else if (p->opcode == OP_WRITE) { 775 | int h = find_tifile_handle(pabsta); 776 | int r = write_record(h, p); 777 | issue_cmd(3, r << 13, 0, 0); // return with code 778 | } 779 | else if (p->opcode == OP_RESTORE) { 780 | int h = find_tifile_handle(pabsta); 781 | if (h == -1) { 782 | issue_cmd(3, ERR_FILEERROR << 13, 0, 0); 783 | } 784 | else { 785 | // Position READ/WRITE pointer either to the beginning of the file, 786 | // or in the case of a relative record file, to the record specified int bytes six and seven of the PAB. 787 | int pos = p->record_number; 788 | if (pos != 0) { 789 | // We currently can only handle zero offset... 790 | issue_cmd(3, ERR_FILEERROR << 13, 0, 0); 791 | } else { 792 | files[h]->m_cur_data = files[h]->m_data; 793 | files[h]->m_record = 0; 794 | issue_cmd(3, ERR_NOERROR << 13, 0, 0); 795 | } 796 | } 797 | } 798 | else { 799 | // Return an error 800 | fprintf(stderr, "ERROR, OPCODE %d NOT SUPPORTED YET!\n", p->opcode); 801 | issue_cmd(3, ERR_FILEERROR << 13, 0, 0); 802 | } 803 | return 0; 804 | } 805 | -------------------------------------------------------------------------------- /memloader/diskio.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // diskio.h 3 | 4 | #pragma pack(push, 1) 5 | 6 | struct ti_pab { 7 | unsigned char opcode; 8 | unsigned char flags; 9 | unsigned short addr; 10 | unsigned char record_length; 11 | unsigned char count; 12 | union { 13 | unsigned short record_number; 14 | unsigned short byte_count; 15 | }; 16 | unsigned char screen_offset; 17 | unsigned char name_length; 18 | unsigned char name[1]; 19 | }; 20 | 21 | #define SCRATCHPAD 0xB8000 // Scratchpad base address 22 | #define CMD_ADDR 0xB800C 23 | #define DSR_PABSTA 0xB8004 // TMS99105 stores PAB start address here. 24 | #define DISK_BUFFER_ADDR_TI 0x8100 // Address in TMS99105 terms 25 | #define DISK_BUFFER_ADDR_PC 0xB8100 // Address from PC 26 | 27 | struct dsr_cmd { 28 | unsigned short arg1, arg2, arg3; 29 | unsigned short cmd; 30 | }; 31 | 32 | struct tifiles_header { 33 | char id[8]; 34 | // Names in the following from classic99 / diskclass.h 35 | unsigned short LengthSectors; 36 | unsigned char FileType; 37 | unsigned char RecordsPerSector; 38 | unsigned char BytesInLastSector; 39 | unsigned char RecordLength; 40 | unsigned short NumberRecords; // note: even for variable, we translate the value on output 41 | 42 | char crap[0x70]; // Pad the length to 128, but we couldn't care less about this 43 | }; 44 | 45 | // The following definitions are from classic99/diskclass.h for naming consistency 46 | // but basically they are from 99-4A_Console_Peripheral_Expansion_System_Technical_Data.pdf 47 | 48 | // The following is from diskclass.h / classic99 and defines the bits in the FileType byte above 49 | // Filetype enums for TIFILES (same for V9T9?) - these go into FileInfo::FileType and come from the file 50 | #define TIFILES_VARIABLE 0x80 // else Fixed 51 | #define TIFILES_PROTECTED 0x08 // else not protected 52 | #define TIFILES_INTERNAL 0x02 // else Display 53 | #define TIFILES_PROGRAM 0x01 // else Data 54 | // others undefined - for the mask, ignore protection bit 55 | #define TIFILES_MASK (TIFILES_VARIABLE|TIFILES_INTERNAL|TIFILES_PROGRAM) 56 | 57 | // 58 | // return bits for the STATUS command (saved in Screen Offset byte) 59 | #define STATUS_NOSUCHFILE 0x80 60 | #define STATUS_PROTECTED 0x40 61 | #define STATUS_INTERNAL 0x10 62 | #define STATUS_PROGRAM 0x08 63 | #define STATUS_VARIABLE 0x04 64 | #define STATUS_DISKFULL 0x02 65 | #define STATUS_EOF 0x01 66 | 67 | // File operation codes 68 | #define OP_OPEN 0 69 | #define OP_CLOSE 1 70 | #define OP_READ 2 71 | #define OP_WRITE 3 72 | #define OP_RESTORE 4 73 | #define OP_LOAD 5 74 | #define OP_SAVE 6 75 | #define OP_DELETE 7 76 | #define OP_SCRATCH 8 77 | #define OP_STATUS 9 78 | 79 | // PAB error codes 80 | #define ERR_NOERROR 0 // This also means the DSR was not found! 81 | #define ERR_BADBAME 0 // thus the duplicate definition 82 | #define ERR_WRITEPROTECT 1 83 | #define ERR_BADATTRIBUTE 2 84 | #define ERR_ILLEGALOPERATION 3 85 | #define ERR_BUFFERFULL 4 86 | #define ERR_READPASTEOF 5 87 | #define ERR_DEVICEERROR 6 88 | #define ERR_FILEERROR 7 89 | 90 | // Modes for opening files. These are bits in tipab.flags 91 | // Bit 0 is sequential / relative: 92 | #define PAB_REL 1 // when flags.0 = 0 sequential, flags.0=1 relative (supports seeking) 93 | // Bits 2 & 1 of tipab.flags give the mode 94 | enum filemode_t { filemode_update = 0, filemode_output = 1, filemode_input = 2, filemode_append = 3 }; 95 | // FILEMODE_UPDATE 0 // supports both reading and writing. 96 | // FILEMODE_OUTPUT 1 // supports only writing. 97 | // FILEMODE_INPUT 2 // supports only reading. 98 | // FILEMODE_APPEND 3 // supports writing to the end of the file. 99 | 100 | 101 | 102 | #pragma pack(pop) 103 | 104 | int DoDiskProcess(); 105 | 106 | int open_tifile(const char *name, unsigned short pab_addr, struct ti_pab *pab, int writeback); 107 | int buffer_tifile(int i); 108 | int dump_records(int i); 109 | int swap_word_bytes(unsigned short *k); 110 | int read_record(int index, const struct ti_pab *pab); 111 | int close_tifile(int i); 112 | -------------------------------------------------------------------------------- /memloader/fpga-mem.h: -------------------------------------------------------------------------------- 1 | // fpga-mem.h 2 | 3 | #pragma once 4 | 5 | void ReadMemoryBlock(unsigned char *dest, unsigned address, int len); 6 | 7 | int WriteMemoryBlock(unsigned char *source, unsigned address, int len); 8 | 9 | 10 | -------------------------------------------------------------------------------- /memloader/memloader-vs/.gitignore: -------------------------------------------------------------------------------- 1 | memloader-vs.VC.db 2 | .vs 3 | 4 | -------------------------------------------------------------------------------- /memloader/memloader-vs/.vs/memloader-vs/v14/.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Speccery/EP994A/4a469a13e57afc1def67a5ad3dd15186b6e03374/memloader/memloader-vs/.vs/memloader-vs/v14/.suo -------------------------------------------------------------------------------- /memloader/memloader-vs/memloader-vs.VC.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Speccery/EP994A/4a469a13e57afc1def67a5ad3dd15186b6e03374/memloader/memloader-vs/memloader-vs.VC.db -------------------------------------------------------------------------------- /memloader/memloader-vs/memloader-vs.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "memloader-vs", "memloader-vs\memloader-vs.vcxproj", "{46074A11-3294-4C62-B682-DBB67226141B}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {46074A11-3294-4C62-B682-DBB67226141B}.Debug|x64.ActiveCfg = Debug|x64 17 | {46074A11-3294-4C62-B682-DBB67226141B}.Debug|x64.Build.0 = Debug|x64 18 | {46074A11-3294-4C62-B682-DBB67226141B}.Debug|x86.ActiveCfg = Debug|Win32 19 | {46074A11-3294-4C62-B682-DBB67226141B}.Debug|x86.Build.0 = Debug|Win32 20 | {46074A11-3294-4C62-B682-DBB67226141B}.Release|x64.ActiveCfg = Release|x64 21 | {46074A11-3294-4C62-B682-DBB67226141B}.Release|x64.Build.0 = Release|x64 22 | {46074A11-3294-4C62-B682-DBB67226141B}.Release|x86.ActiveCfg = Release|Win32 23 | {46074A11-3294-4C62-B682-DBB67226141B}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /memloader/memloader-vs/memloader-vs/memloader-vs.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {46074A11-3294-4C62-B682-DBB67226141B} 23 | Win32Proj 24 | memloadervs 25 | 10.0.14393.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v141 32 | MultiByte 33 | 34 | 35 | Application 36 | false 37 | v141 38 | true 39 | MultiByte 40 | 41 | 42 | Application 43 | true 44 | v141 45 | Unicode 46 | 47 | 48 | Application 49 | false 50 | v141 51 | true 52 | Unicode 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | true 74 | 75 | 76 | true 77 | 78 | 79 | false 80 | 81 | 82 | false 83 | 84 | 85 | 86 | 87 | 88 | Level3 89 | Disabled 90 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 91 | 92 | 93 | Console 94 | true 95 | winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) 96 | 97 | 98 | 99 | 100 | 101 | 102 | Level3 103 | Disabled 104 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 105 | 106 | 107 | Console 108 | true 109 | 110 | 111 | 112 | 113 | Level3 114 | 115 | 116 | MaxSpeed 117 | true 118 | true 119 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 120 | MultiThreaded 121 | 122 | 123 | Console 124 | true 125 | true 126 | true 127 | winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) 128 | 129 | 130 | 131 | 132 | Level3 133 | 134 | 135 | MaxSpeed 136 | true 137 | true 138 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 139 | 140 | 141 | Console 142 | true 143 | true 144 | true 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /memloader/memloader-vs/memloader-vs/memloader-vs.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | 26 | 27 | Header Files 28 | 29 | 30 | Header Files 31 | 32 | 33 | -------------------------------------------------------------------------------- /memloader/memloader-vs/memloader-vs/memloader-vs.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -8 -k 5 | WindowsLocalDebugger 6 | ..\..\..\firmware 7 | 8 | 9 | -10 -k 10 | WindowsLocalDebugger 11 | 12 | -------------------------------------------------------------------------------- /schematics/tms99105.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Speccery/EP994A/4a469a13e57afc1def67a5ad3dd15186b6e03374/schematics/tms99105.pdf -------------------------------------------------------------------------------- /status: -------------------------------------------------------------------------------- 1 | # Show status of files that have been modified or added 2 | svn status | grep -v '^?' 3 | --------------------------------------------------------------------------------