├── .gitignore ├── Makefile ├── README.md ├── blackice-ii.pcf ├── create-mem-from-bin.py ├── debugcart ├── VDP9938.bin ├── asm9938.sh ├── compile.sh ├── pad.c ├── vdp9938.asm ├── vdp9938.lst ├── vdpload.asm └── vdpload.lst ├── editrom.sh ├── esp32 ├── cmdline │ ├── mount.py │ └── spiram.py └── osd │ ├── ld_nes.py │ ├── ld_ti99_4a.py │ └── osd.py ├── explain_vdp.py ├── gpl-opt ├── analyze_tracebuf.py └── analyzerom.py ├── initsd.sh ├── lcd ├── pmodoledrgb_controller.v ├── ram_source.v └── ram_template.hex ├── osd ├── font2readmemb.py ├── font_bizcat8x16.mem ├── osd.mem ├── osd.v ├── spi_osd.v ├── spi_ram_btn.v └── spirw_slave_v.v ├── rom16.v ├── roms ├── lblacart.bin ├── tipi.bin └── zeros256.mem ├── snippets.py ├── src ├── alu9900.v ├── dualport_par.v ├── dvi.v ├── ecp5pll.sv ├── erik_pll.v ├── go_tms9918_tb.sh ├── gromext.v ├── lcd_sys.v ├── pager612.v ├── ps2kb.v ├── ram2.v ├── rom.v ├── sdram.sv ├── sdram_cortex.v ├── serial_rx.v ├── serial_tx.v ├── serloader.v ├── spi_slave.v ├── sys.v ├── tmds_encoder.v ├── tms9900.v ├── tms9901.v ├── tms9902.v ├── tms9918.v ├── tms9918_tb.v ├── tms9919.v ├── vga2dvid.v ├── vga_sync.v └── xmemctrl.v ├── ti994a_ulx3s.bit ├── tipi ├── crubits.v ├── latch_8bit.v ├── mux2_8bit.v ├── shift_pload_sout.v ├── shift_sin_pout.v ├── tipi_module.v └── tristate_8bit.v ├── tools ├── editrom.c └── serialtool.c ├── top_blackice2.v ├── top_flea.v ├── top_pepino.v ├── top_ulx3s.v ├── ulx3s.lpf ├── upload.sh └── upload9900.sh /.gitignore: -------------------------------------------------------------------------------- 1 | roms/994agrom.mem 2 | roms/994arom.mem 3 | roms/minimemc.mem 4 | roms/minimemg.mem 5 | next9900.asc 6 | next9900.bin 7 | next9900.blif 8 | next9900.json 9 | kbd.xmd 10 | kbd.cmd 11 | load.cmd 12 | Makefile.bak 13 | top_ulx3s.v.bak 14 | ulx3s.lpf.bak 15 | debugcart/VDPLOAD.bin 16 | debugcart/dump0.bin 17 | debugcart/dump1.bin 18 | debugcart/dump2.bin 19 | debugcart/dump3.bin 20 | debugcart/dump4.bin 21 | debugcart/dump4_.bin 22 | debugcart/dumploac.bin 23 | debugcart/dumpload-1c.bin 24 | debugcart/vdpload0.bin 25 | ti994a_ulx3s.json 26 | ti994a_ulx3s_out.cfg 27 | src/tms9918_tb 28 | src/tms9918_test.gtkw 29 | debugcart/layout.xml 30 | debugcart/meta-inf.xml 31 | .DS_Store 32 | bitstreams 33 | src/sim-tms9918_tb.lxt 34 | roms/994AGROM.Bin 35 | roms/994aROM.Bin 36 | debugcart/vdp9938.rpk 37 | debugcart/vdpload.rpk 38 | 39 | # Erik added 2023-11-27 the following 40 | Eriks-MBP.lan/ 41 | XB_Man_linked.pdf 42 | debugcart/VDPDUMP-MULTICOLOR.BIN 43 | debugcart/VDPDUMP.BIN 44 | debugcart/VDPDUMP1.BIN 45 | debugcart/cart/ 46 | debugcart/pad 47 | debugcart/strangecart.asm 48 | dev-sd/ 49 | disk/ 50 | grom_ti994a_ulx3s.bit 51 | i9900/ 52 | icy99picture.bin 53 | roms/99opt.bin 54 | roms/AMSTEST4-8.BIN 55 | roms/cortex80.bin 56 | roms/optimized.txt 57 | roms/original.txt 58 | tools/old-serialtool 59 | tools/serialtool 60 | vdpregs.bin 61 | ws-icy99.code-workspace 62 | ws-mbp.code-workspace 63 | gpl-opt/dis_mac 64 | sd-2gb-backup/ 65 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Erik Piehl (C) 2020 2 | # Makefile for icy99 FPGA system 3 | 4 | ifdef APIO 5 | YOSYS = ~/.apio/packages/toolchain-yosys/bin/yosys 6 | NEXTPNR_ECP5 = ~/.apio/packages/toolchain-ecp5/bin/nextpnr-ecp5 7 | NEXTPNR_ICE40 = ~/.apio/packages/toolchain-ice40/bin/nextpnr-ice40 8 | ECPPACK = ~/.apio/packages/toolchain-ecp5/bin/ecppack 9 | ICEPACK_ICE40 = ~/.apio/packages/toolchain-ice40/bin/icepack 10 | else 11 | YOSYS = yosys 12 | NEXTPNR_ECP5 = nextpnr-ecp5 13 | NEXTPNR_ICE40 = nextpnr-ice40 14 | ECPPACK = ecppack 15 | ICEPACK_ICE40 = icepack 16 | endif 17 | 18 | HOSTNAME := $(shell hostname) 19 | 20 | # TI-99/4A FGPA implementation for various FPGA boards. 21 | VERILOGS = src/ram2.v \ 22 | src/sys.v src/rom.v \ 23 | src/tms9900.v src/alu9900.v src/tms9902.v \ 24 | src/erik_pll.v src/tms9918.v src/vga_sync.v \ 25 | src/xmemctrl.v src/gromext.v \ 26 | src/serloader.v src/serial_rx.v \ 27 | src/serial_tx.v src/spi_slave.v src/tms9901.v \ 28 | src/dualport_par.v src/ps2kb.v \ 29 | src/tms9919.v src/pager612.v 30 | 31 | TIPI_VERILOGS = \ 32 | tipi/crubits.v \ 33 | tipi/shift_pload_sout.v \ 34 | tipi/tipi_module.v \ 35 | tipi/mux2_8bit.v \ 36 | tipi/shift_sin_pout.v \ 37 | tipi/tristate_8bit.v 38 | 39 | LCD_VERILOGS = \ 40 | src/lcd_sys.v lcd/pmodoledrgb_controller.v lcd/ram_source.v 41 | 42 | all: $(HOSTNAME)/ti994a_ulx3s.bit 43 | 44 | erik9900.blif: $(VERILOGS) top_blackice2.v blackice-ii.pcf Makefile 45 | yosys -q -DEXTERNAL_VRAM -p "synth_ice40 -top top_blackice2 -abc2 -blif erik9900.blif" $(VERILOGS) top_blackice2.v 46 | 47 | erik9900.txt: erik9900.blif 48 | arachne-pnr -r -d 8k -P tq144:4k -p blackice-ii.pcf erik9900.blif -o erik9900.txt 49 | #skipped:# icebox_explain erik9900.txt > erik9900.ex 50 | 51 | erik9900.bin: erik9900.txt 52 | icepack erik9900.txt erik9900.bin 53 | # icemulti -p0 erik9900.bin > erik9900.bin && rm j1a0.bin 54 | 55 | # NEXTPNR ROUTING 56 | next9900.json: $(VERILOGS) top_blackice2.v blackice-ii.pcf Makefile 57 | $(YOSYS) -q -DEXTERNAL_VRAM -p 'synth_ice40 -json next9900.json -top top_blackice2 -blif next9900.blif' $(VERILOGS) top_blackice2.v 58 | 59 | next9900.asc: next9900.json 60 | $(NEXTPNR_ICE40) --hx8k --asc next9900.asc --json next9900.json --package tq144:4k --pcf blackice-ii.pcf --pcf-allow-unconstrained 61 | 62 | next9900.bin: next9900.asc 63 | $(ICEPACK_ICE40) next9900.asc next9900.bin 64 | 65 | 66 | # ECP5 FleaFPGA Ohm 67 | flea.json: $(VERILOGS) top_flea.v src/dvi.v Makefile 68 | $(YOSYS) -q -p "synth_ecp5 -json flea.json" src/dvi.v top_flea.v rom16.v $(VERILOGS) 69 | 70 | 71 | flea_ohm.bit: Makefile flea.json 72 | $(NEXTPNR_ECP5) --25k --package CABGA381 --json flea.json --lpf flea_ohm.lpf --textcfg flea_out.cfg 73 | $(ECPPACK) flea_out.cfg flea_ohm.bit 74 | 75 | # ECP5 ULX3S ECP5-85 board 76 | VERILOGS_ULX3S = \ 77 | top_ulx3s.v \ 78 | src/ecp5pll.sv \ 79 | src/sdram_cortex.v \ 80 | src/dvi.v \ 81 | src/vga2dvid.v \ 82 | src/tmds_encoder.v \ 83 | osd/osd.v \ 84 | osd/spi_osd.v \ 85 | osd/spi_ram_btn.v \ 86 | osd/spirw_slave_v.v 87 | 88 | $(HOSTNAME)/ti994a_ulx3s.json: $(VERILOGS) $(VERILOGS_ULX3S) $(TIPI_VERILOGS) $(LCD_VERILOGS) Makefile 89 | @mkdir -p $(@D) 90 | $(YOSYS) \ 91 | -p "read -sv $(VERILOGS_ULX3S) rom16.v $(VERILOGS) $(TIPI_VERILOGS)" \ 92 | -p "hierarchy -top top_ulx3s" \ 93 | -p "synth_ecp5 -abc9 -json $@" 94 | # these moved to top_ulx3s.v 95 | # -DTIPI_SUPPORT -DLCD_SUPPORT -q -DUSE_SDRAM 96 | 97 | 98 | $(HOSTNAME)/ti994a_ulx3s.bit: Makefile $(HOSTNAME)/ti994a_ulx3s.json 99 | $(NEXTPNR_ECP5) --85k --package CABGA381 --json $(HOSTNAME)/ti994a_ulx3s.json --lpf ulx3s.lpf --textcfg $(HOSTNAME)/ti994a_ulx3s_out.cfg 100 | # --lpf-allow-unconstrained 101 | # $(ECPPACK) --compress $(HOSTNAME)/ti994a_ulx3s_out.cfg $@ 102 | $(ECPPACK) --compress --freq 62.0 $(HOSTNAME)/ti994a_ulx3s_out.cfg $@ 103 | 104 | 105 | clean: 106 | rm -f erik9900.blif erik9900.txt erik9900.bin next9900.bin next9900.asc next9900.json 107 | rm -f flea.json flea_ohm.bit 108 | rm -f $(HOSTNAME)/ti994a_ulx3s.bit $(HOSTNAME)/ti994a_ulx3s.json 109 | 110 | .PHONY: clean 111 | .PHONY: erik9900 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # icy99 2 | TI-99/4A FPGA implementation for the Icestorm toolchain. 3 | Primary target board currently the ULX3S FPGA board. Tested with the version 3.0.3 of the board with the ECP5 85F FPGA chip. 4 | 5 | 2023-11-28 Updated for new toolchain 6 | ==================================== 7 | Wow it has been a long time. I had received reports that the code no longer works with new versions of the **oss-cad-suite** toolchain. No wonder, as I haven't worked on the project for way too long. The project now works with the 2023-11-18 release which I used during debugging. 8 | 9 | After several evenings of debugging, it now works with ULX3S. I had to disable SDRAM, since the primitive **IFS1P3BX** is no longer there and not recognized by nextpnr-ecp5 anymore. I need to check what replaced it to get the SDRAM interface working again. 10 | 11 | In addition to disabling SDRAM, to simplify debugging, I disabled TIPI support too. That probably was the culprit for non-SDRAM not working anymore, since the **db_in** selection mux no longer worked properly, as the signal **tipi_ioreg_en** was constantly low and disabled access to ROM or RAM. In the debugging process I extended the tracebuffer from 36 to 72 bits wide, and that enabled me to see what was wrong and fix it. 12 | 13 | Only tested with ULX3S 85F. The current version uses a lot of block RAMs on the ULX3S since SDRAM is not used. 14 | I have not tried the BlackIce II version in long time either. 15 | 16 | 2020-11-27 TIPI support added 17 | ============================= 18 | * Added support for the TIPI system (TI - Raspberry PI interface) 19 | * Through TIPI disk support is provided 20 | * The DSR routine tipi.bin must be loaded using ESP32. The ESP32 code now recognizes that entried in the directory /sd/ti99_4a/dsr are DSR routines and are loaded to the DSR area. The area currently only supports the TIPI. 21 | 22 | The pin mapping currently between ULX3S and TIPI is (refer to top_ulx3s.v): 23 | 24 | |TIPI | ULX3S | comment | 25 | |--------|-------|---------| 26 | |GPIO_6 | GP20 | | 27 | |GPIO_13 | GP21 | | 28 | |GPIO_19 | GP22 | | 29 | |GPIO_16 | GP23 | | 30 | |GPIO_21 | GP24 | | 31 | |GPIO_20 | GP26 | (output from ULX3S, DIN) | 32 | |GPIO_26 | GP25 | (output from ULX3S, RESET) | 33 | 34 | 2020-11-20 Audio and LCD support 35 | ================================================== 36 | * Added support for 96x64 LCD (Displays only the top left corner of TI-99/4A screen) 37 | * Added support for audio - finally! 38 | * The audio DAC needs work, now it blatantly uses the top 4 bits of the audio data to drive the 4-bit DAC, and that sounds terrible, need to test delta-sigma technique with 4-bit output. Basically drive the DAC at master clock 25MHz. 39 | 40 | 2020-10-27 Sprite fixes etc (tested only on ULX3S) 41 | ================================================== 42 | * Updated ESP32 micropython code to have a few support methods for testing 43 | * Added support for OSD navigation with PS/2 keyboard. F1 brings up the OSD, cursor keys navigate. 44 | * Megademo "Don't mess with Texas" now works (except for splitscreen demo). The part which was stuck had a problem with coincidence flag detection. 45 | * Fixed a lot of bugs with TMS9918 implementation, including coincidence, 5th sprite per scanline detection, etc. 46 | 47 | 48 | 2020-10-16 ULX3S Supports loading with ESP32 49 | ============================================= 50 | * Updated ESP32 micropython code slightly (esp32/osd/osd.py and esp32/osd/ld_ti99_4a.py), mainly to support the 2M ROM cartridge region from 2M to 4M in physical address space. 51 | * Changed src/sys.v to support the ESP32 bootloader. Now there is a new parameter for synthesis, enabling the use of external memory controller instead of serloader (i.e. initializing with UART connected to US1). 52 | * Still work in progress. 53 | * "Don't mess with Texas" demo still does not work properly. There is at least a bug in the 5th sprite per line detection, and the demo eventually gets stuck. Also the very first part of the demo looks bogus, this might be a bug in the Verilog version of my CPU core. 54 | * Thus if you try with this 512K demo cartridge, just be patient and wait for the first phase of the demo to end, it is just garbage perhaps for the first two minutes. 55 | * I did not test this latest build with BlackIce-II board at all. 56 | * Credits: ULX3S intial port by emard. DVI encoder, TMS9902 and SDRAM controller cores by pnr. 57 | * My own code: toplevel modules, generic system module implementing the TI-99/4A, TMS9900 CPU core, TMS9901 I/O controller core, TMS9918 Video processor core, serloader, spi-slave, TI-99/4A GROM system and the multiport memory controller xmemctrl. And of course the EP994A VHDL SoC this icy99 system is based on. 58 | 59 | 2020-10-15 SDRAM support and 80 column output 60 | ============================================= 61 | * Did a whole bunch of updates and bug fixes to the VDP. Now the 40 column text mode should be bug free, and also added 80-column text mode, which is the same as with F18A, 9938 and 9958 VDPs. Tested with the TI-99/4A TurboForth. 62 | * On the ULX3S added support for SDRAM. This is still minimally used, the but now the 32K memory expansion and the scratchpad memory are stored in SDRAM instead of internal block RAM. 63 | * Extended the address buses to 24-bits. The next step is to add memory paging to be able to benefit from the larger memory capacity. 64 | 65 | 2020-07-14 A couple of changes 66 | ============================== 67 | Tested these changes with ULX3S and Blackice-II boards. 68 | * Keyboard input from PS/2 keyboard improved. Now the cursor keys work, fixed the "minus" key, and backspace is the same as cursor left. Much easier to use. 69 | * New definition EXTERNAL_VRAM can be used to define whether VRAM is in external memory or not. For the Blackice-II target it must be defined, as there is not enough block RAM on-chip. But for larger FPGAs, this can be left undefined, and then FPGA's internal block RAM will be used for VRAM. This is useful as in the future SDRAM support will be added for the ULX3S and hopefully Flea Ohm board too. With VRAM accesses out of the way, the CPU can access leisurely SDRAM even with a slow SDRAM controller, that is the idea. 70 | * Internal block RAMs are all operated with 25MHz clock if EXTERNAL_VRAM is defined. If it is not defined, the VRAM clocks on the toplevel module run off the 125MHz (DVI video PLL). 71 | 72 | Initial commit 2020-06-15 73 | ========================= 74 | I will add documentation once I have a bit more time. The software stucture is a bit of a mess. I am moving the files into this github repository from my own repository, please let me know if some files are missing. 75 | 76 | The system here is a verilog port from my existing EP994A VHDL version of the TI-99/4A. The main difference between this version is that a unified memory architecture (UMA) model is supported, allowing this core to run even on the Blackice-II board, with the modest ICE40HX4K FPGA. This FPGA does not have enough block RAM to support the video system memory on-chip. Thus, thanks to UMA, the external 256K x 16bit memory is used for both CPU RAM, ROM, GROM and VDP RAM memories. 77 | 78 | The archictecture runs at 25MHz, the performance is around 7x of the original TI-99/4A. My initial goal with the EP994 was to build a very fast TI-99/4A implementation. Due to this the core has never been cycle accurate. I may accurate this later. 79 | 80 | The repository does not include ROM files. The necessary ROM files are: 81 | * 994AGROM.Bin system GROM file 82 | * 994aROM.Bin system ROM 83 | 84 | create-mem-from-bin.py is a Python 3 program which will generate Verilog memory initialization files from binary files. 85 | 86 | The design targets the following FPGA boards: 87 | | Board | top module | make target | 88 | |-------------|-----------------|-------------| 89 | | ULX3S | top_ulx3s.v | ti994a_ulx3s.bit | 90 | | Blackice-II | top_blackice2.v | next9900.bin | 91 | | Flea Ohm | top_flea.v | flea_ohm.bit | 92 | 93 | Thus, for example to build the version for flea_ohm board: 94 | 95 | make flea_ohm.bit 96 | 97 | The ULX3S and Flea Ohm versions of this system embed the TI-99/4A ROMS as FGPA block RAMs. For the BlackIce-II board this is not possible, as the internal block RAM capacity is too small. For that system (and also the others) the ROMs can be initilized with the MEMLOADER program. This is a Windows program enabling the memories to be initialized over a serial port. I believe I have documented some of this in the EP994A. I will add the memloader source code to this repository later as well. 98 | 99 | I have used a mixed computer setup for development: a windows box running Windows Subsystem for Linux as the main development environment to run Icestorm (yosys, nextpnr), a Mac for some of the development with the same toolchain. Windows is required currently to run MEMLOADER. 100 | -------------------------------------------------------------------------------- /blackice-ii.pcf: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # # 3 | # Copyright 2017 myStorm Copyright and related # 4 | # rights are licensed under the Solderpad Hardware License, Version 0.51 # 5 | # (the “License”); you may not use this file except in compliance with # 6 | # the License. You may obtain a copy of the License at # 7 | # http://solderpad.org/licenses/SHL-0.51. Unless required by applicable # 8 | # law or agreed to in writing, software, hardware and materials # 9 | # distributed under this License is distributed on an “AS IS” BASIS, # 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # 11 | # implied. See the License for the specific language governing # 12 | # permissions and limitations under the License. # 13 | # # 14 | ############################################################################### 15 | 16 | # User Constraint File for myStorm BlackIce II - Ken Boak 09-12-17 17 | 18 | #pmod 1 (Uart pmod) 19 | # set_io PMOD[0] 94 # rd6 20 | # set_io PMOD[1] 91 # rd4 shared with RTS and pin 128 see below 21 | # set_io PMOD[2] 88 # rd2 22 | # set_io PMOD[3] 85 # rd0 23 | 24 | set_io PMOD0 94 # rd6 25 | set_io PMOD1 91 # rd4 shared with RTS and pin 128 see below 26 | set_io UART_RX 88 # rd2 27 | set_io UART_TX 85 # rd0 28 | 29 | #pmod 2 30 | set_io PMOD4 95 # rd7 31 | set_io PMOD5 93 # rd5 32 | set_io PMOD6 90 # rd3 33 | set_io PMOD7 87 # rd1 34 | 35 | #pmod 3 GPIO 36 | set_io PMOD_IO[8] 105 # c5 37 | set_io PMOD_IO[9] 102 # c3 38 | set_io PMOD_IO[10] 99 # c1 39 | set_io PMOD_IO[11] 97 # i_tx 40 | 41 | #pmod 4 42 | set_io PMOD_IO[12] 104 # c4 43 | set_io PMOD_IO[13] 101 # c2 44 | set_io PMOD_IO[14] 98 # c0 45 | set_io PMOD_IO[15] 96 # i_rx 46 | 47 | #pmod 5 GPIO 48 | set_io PMOD5_1 143 #g2 49 | set_io PMOD5_2 114 #c11 50 | set_io PMOD5_3 112 #c9 51 | set_io PMOD5_4 107 #c7 52 | 53 | #set_io PMOD_IO[16] 143 #g2 54 | #set_io PMOD_IO[17] 114 #c11 55 | #set_io PMOD_IO[18] 112 #c9 56 | #set_io PMOD_IO[19] 107 #c7 57 | 58 | #pmod 6 GPIO 59 | set_io PMOD6_1 144 #G1 60 | set_io PMOD6_2 113 #C10 61 | set_io PMOD6_3 110 #C8 62 | set_io PMOD6_4 106 #C6 63 | #set_io PMOD_IO[20] 144 #G1 64 | #set_io PMOD_IO[21] 113 #C10 65 | #set_io PMOD_IO[22] 110 #C8 66 | #set_io PMOD_IO[23] 106 #C6 67 | 68 | #pmod 7 lvds pairs 2 & 5 69 | 70 | # VGA Output - Matches Digilent PmodVGA plugged into PMOD7/8/9/10 71 | set_io red[3] 15 # PMOD35 = PMOD 9/10 pin 5 72 | set_io red[2] 16 # PMOD34 = PMOD 9/10 pin 7 73 | set_io red[1] 19 # PMOD33 = PMOD 9/10 pin 9 74 | set_io red[0] 20 # PMOD32 = PMOD 9/10 pin 11 75 | 76 | set_io green[3] 1 # PMOD27 = PMOD 7/8 pin 5 77 | set_io green[2] 2 # PMOD26 = PMOD 7/8 pin 7 78 | set_io green[1] 9 # PMOD25 = PMOD 7/8 pin 9 79 | set_io green[0] 10 # PMOD24 = PMOD 7/8 pin 11 80 | 81 | set_io blue[3] 11 # PMOD39 = PMOD 9/10 pin 6 82 | set_io blue[2] 12 # PMOD38 = PMOD 9/10 pin 8 83 | set_io blue[1] 17 # PMOD37 = PMOD 9/10 pin 10 84 | set_io blue[0] 18 # PMOD36 = PMOD 9/10 pin 12 85 | 86 | set_io hsync 8 # PMOD28 = PMOD 7/8 pin 12 87 | set_io vsync 7 # PMOD29 = PMOD 7/8 pin 10 88 | 89 | # # EP Renumbered PMOD[24..31] to PMOD[0..7] since nextpnr seems to assume buses start from 0 90 | # set_io PMOD_IO[0] 10 # 5b 91 | # set_io PMOD_IO[1] 9 # 5a 92 | # set_io PMOD_IO[2] 2 # 2b 93 | # set_io PMOD_IO[3] 1 # 2a 94 | 95 | #pmod 8 lvds pairs 3 & 4 96 | # set_io PMOD_IO[4] 8 # 4b 97 | # set_io PMOD_IO[5] 7 # 4a 98 | 99 | # EP 2019-09-29 converting these to support VGA output, these pins 3 and 4 100 | # are not used I guess. But they still occupy the same PMOD connector (I guess). 101 | #set_io PMOD_IO[6] 4 # 3b 102 | #set_io PMOD_IO[7] 3 # 3a 103 | 104 | ##pmod 9 lvds pairs 10 & 13 105 | #set_io PMOD32 20 # 13b 106 | #set_io PMOD33 19 # 13a 107 | #set_io PMOD34 16 # 10b 108 | #set_io PMOD35 15 # 10a 109 | 110 | #pmod 10 lvds pairs 8 & 12 111 | #set_io PMOD36 18 # 12b 112 | #set_io PMOD[37] 17 # 12a 113 | #set_io PMOD[38] 12 # 8b 114 | #set_io PMOD[39] 11 # 8a 115 | 116 | #pmod 11 lvds pairs 14 & 25 117 | set_io PMOD[40] 34 # 25b 118 | set_io PMOD[41] 33 # 25a 119 | set_io PMOD[42] 22 # 14B 120 | set_io PMOD[43] 21 # 14a 121 | 122 | #pmod 12 lvds pairs 18 & 24 123 | ## set_io PMOD[44] 32 # 24b 124 | ## set_io PMOD[45] 31 # 24a 125 | ## set_io PMOD[46] 26 # 18b 126 | ## set_io PMOD[47] 25 # 18a 127 | set_io ps2_clk 26 # PMOD 11/12 pin 8 128 | set_io ps2_data 32 # PMOD 11/12 pin 12 129 | 130 | #pmod 13 DIG16-DIG19 131 | 132 | # set_io PMOD[48] 37 # DIG19 133 | # set_io PMOD[49] 38 # DIG18 134 | # set_io PMOD[50] 39 # DIG17 135 | # set_io PMOD[51] 41 # DIG16 136 | 137 | set_io DIG19 37 # DIG19 138 | set_io DIG18 38 # DIG18 139 | set_io DIG17 39 # DIG17 140 | set_io DIG16 41 # DIG16 141 | 142 | 143 | #pmod 14 SPI muxed with leds 144 | # EP Renumbered PMOD[52..55] to LED[0..3] 145 | set_io LED[0] 71 #LD4,!SS,p14_1 146 | set_io LED[1] 67 #LD3,MISO,p14_2 147 | set_io LED[2] 68 #LD2,MOSI,p14_3 148 | set_io LED[3] 70 #LD1,SCL,p14_4 149 | # Buttons 150 | set_io B1 63 # Push Button 1 151 | set_io B2 64 # Push Button 2# 152 | 153 | 154 | # SRAM 155 | set_io ADR[0] 137 156 | set_io ADR[1] 138 157 | set_io ADR[2] 139 158 | set_io ADR[3] 141 159 | set_io ADR[4] 142 160 | set_io ADR[5] 42 161 | set_io ADR[6] 43 162 | set_io ADR[7] 44 163 | set_io ADR[8] 73 164 | set_io ADR[9] 74 165 | set_io ADR[10] 75 166 | set_io ADR[11] 76 167 | set_io ADR[12] 115 168 | set_io ADR[13] 116 169 | set_io ADR[14] 117 170 | set_io ADR[15] 118 171 | set_io ADR[16] 119 172 | set_io ADR[17] 78 173 | 174 | set_io DAT[0] 136 175 | set_io DAT[1] 135 176 | set_io DAT[2] 134 177 | set_io DAT[3] 130 178 | set_io DAT[4] 125 179 | set_io DAT[5] 124 180 | set_io DAT[6] 122 181 | set_io DAT[7] 121 182 | set_io DAT[8] 62 183 | set_io DAT[9] 61 184 | set_io DAT[10] 60 185 | set_io DAT[11] 56 186 | set_io DAT[12] 55 187 | set_io DAT[13] 48 188 | set_io DAT[14] 47 189 | set_io DAT[15] 45 190 | 191 | set_io RAMOE 29 192 | set_io RAMWE 120 193 | set_io RAMCS 23 194 | set_io RAMUB 28 195 | set_io RAMLB 24 196 | 197 | # QUAD SPI 198 | set_io QSPICSN 81 199 | set_io QSPICK 82 200 | set_io QSPIDQ[0] 83 201 | set_io QSPIDQ[1] 84 202 | set_io QSPIDQ[2] 79 203 | set_io QSPIDQ[3] 80 204 | 205 | # Debug 206 | set_io DONE 52 # DONE - GBIN4 207 | # EP Commented DBG1 due to nextpnr PLL placement problem 208 | ## set_io DBG1 49 # DBG1 - GBIN5 209 | 210 | # Internal global reset 211 | set_io GRESET 128 # Connected to CH340 RTS and also P1 (uart Pmod above) 212 | 213 | # Onboard 100Mhz oscillator 214 | set_io clk100 129 215 | 216 | -------------------------------------------------------------------------------- /create-mem-from-bin.py: -------------------------------------------------------------------------------- 1 | # create-mem-from-bin.py 2 | # An utility to read a binfile and write Verilog mem file which can be read 3 | # using $readmemh. 4 | # EP 2019-11-18 5 | 6 | import sys 7 | 8 | # write 16-bit words 9 | def conv_to_hex_16(infile, outfile): 10 | count = 0 11 | try: 12 | src = open(infile, "rb") 13 | dst = open(outfile, "wt") 14 | 15 | while True: 16 | byte1 = src.read(1) 17 | byte2 = src.read(1) 18 | if byte1 == b"": 19 | break 20 | val = ord(byte1)*256+ord(byte2) 21 | dst.write("{:04X} // address 0x{:04X}\n".format(val, count*2)) 22 | count = count+1 23 | finally: 24 | src.close() 25 | dst.close() 26 | return count*2 27 | 28 | def conv_to_hex_8(infile, outfile): 29 | count = 0 30 | try: 31 | src = open(infile, "rb") 32 | dst = open(outfile, "wt") 33 | 34 | while True: 35 | byte1 = src.read(1) 36 | if byte1 == b"": 37 | break 38 | val = ord(byte1) 39 | dst.write("{:02X} // address 0x{:04X}\n".format(val, count)) 40 | count = count+1 41 | finally: 42 | src.close() 43 | dst.close() 44 | return count 45 | 46 | 47 | 48 | if __name__ == '__main__': 49 | # Haven't yet learned how to use argument parsing helpers, let's keep this very simple. 50 | if (len(sys.argv) < 3): 51 | print("Usage: create-mem-from-bin [-8] src dest") 52 | print("\tThe option -8 creates 8 bit byte based mem file, otherwise the file will contain 16-bit words.") 53 | exit(0) 54 | words = True 55 | i = 1; 56 | while sys.argv[i][0] == '-' and i dumploac.bin 24 | echo "Generated dumploac.bin" 25 | 26 | -------------------------------------------------------------------------------- /debugcart/pad.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char **argv) 5 | { 6 | if (argc != 4) 7 | { 8 | printf("usage: %s infile outfile size\n", argv[0]); 9 | exit(1); 10 | } 11 | 12 | int size = atoi(argv[3]); 13 | FILE *i = fopen(argv[1], "rb"); 14 | if (!i) 15 | { 16 | printf("Unable to open file %s for read\n", argv[1]); 17 | exit(1); 18 | } 19 | FILE *o = fopen(argv[2], "wb"); 20 | if (!o) 21 | { 22 | printf("Unable to open file %s for write\n", argv[2]); 23 | fclose(i); 24 | exit(1); 25 | } 26 | unsigned char byte; 27 | while (size > 0) 28 | { 29 | if (fread(&byte, 1, 1, i) > 0) 30 | { 31 | fwrite(&byte, 1, 1, o); 32 | } 33 | else 34 | { 35 | byte = 0; 36 | fwrite(&byte, 1, 1, o); 37 | } 38 | size--; 39 | } 40 | fclose(i); 41 | fclose(o); 42 | return 0; 43 | } -------------------------------------------------------------------------------- /debugcart/vdp9938.asm: -------------------------------------------------------------------------------- 1 | * EXECUTABLE CART IMAGE 2 | * Erik Piehl (C) 2020 December 3 | * 4 | * VDP9938.ASM 5 | * 6 | * Try out the VDP9938 registers. 7 | 8 | IDT 'VDP9938' 9 | 10 | VDPWD EQU >8C00 * VDP write data 11 | VDPWA EQU >8C02 * VDP set read/write address 12 | VDPRD EQU >8800 * VDP read data 13 | VDPPTR EQU >8890 * VDP extension: VRAM address pointer 14 | BANK0 EQU >6000 15 | BANK4 EQU >6008 16 | DATABLK EQU >7000 * 17 | 18 | *--------------------------------------------- 19 | * Locations in workspace RAM. 20 | WRKSP EQU >8300 * Workspace memory in fast RAM (for paging tests) 21 | R0LB EQU WRKSP+1 * Register zero low byte address 22 | *--------------------------------------------- 23 | 24 | EVEN 25 | 26 | *--------------------------------------------- 27 | * Set VDP read address from R0 28 | *--------------------------------------------- 29 | VDPREADA 30 | ANDI R0,>3FFF * make sure it is a read command 31 | SWPB R0 32 | MOVB R0,@VDPWA * Send low byte of VDP RAM write address 33 | SWPB R0 34 | MOVB R0,@VDPWA * Send high byte of VDP RAM write address 35 | RT 36 | 37 | *--------------------------------------------- 38 | * Set VDP address from R0 39 | *--------------------------------------------- 40 | SETUPVDPA 41 | SWPB R0 42 | MOVB R0,@VDPWA * Send low byte of VDP RAM write address 43 | SWPB R0 44 | ORI R0,>4000 * Set read/write bits 14 and 15 to write (01) 45 | MOVB R0,@VDPWA * Send high byte of VDP RAM write address 46 | RT 47 | 48 | *--------------------------------------------- 49 | * Write VDP register 50 | *--------------------------------------------- 51 | VWREG ORI R0,>8000 52 | CMD SWPB R0 53 | MOVB R0,@>8C02 54 | SWPB R0 55 | MOVB R0,@>8C02 56 | B *R11 57 | 58 | *--------------------------------------------- 59 | * MAIN 60 | *--------------------------------------------- 61 | MAIN LIMI 0 * Disable interrupts 62 | LWPI WRKSP * Load the workspace pointer to fast RAM 63 | LI R1,VDPMODE 64 | ! MOVB *R1+,R0 ; Register number 65 | SWPB R0 66 | MOVB *R1+,R0 ; Data to register 67 | SWPB R0 68 | CI R0,>FFFF 69 | JEQ REGS_DONE 70 | BL @VWREG 71 | JMP -! 72 | ; Done. Setup VDP VRAM pointer to zero. 73 | REGS_DONE 74 | BL @COPY_FONTS 75 | ; Write a string to VRAM 30 times 76 | LI R2,0 ; our VRAM destination address 77 | LI R3,0 ; line counter 78 | GO: 79 | MOV R2,R0 80 | BL @SETUPVDPA 81 | CLR R0 82 | LI R1,VDPTEXT 83 | ! MOVB *R1+,@VDPWD 84 | JNE -! 85 | ; Write an increasing character at the END 86 | MOV R3,R4 87 | SWPB R4 88 | AI R4,' '*256 89 | MOVB R4,@VDPWD 90 | 91 | AI R2,80 ; Advance to next line 92 | 93 | INC R3 94 | CI R3,30 95 | JNE GO 96 | 97 | ; Test the new CPU instruction MOVU and see what happens. 98 | ; First write some hex test stuff 99 | LI R0,20 100 | BL @SETUPVDPA 101 | LI R0,0 102 | BL @HEX_R0 103 | LI R0,>DEAD 104 | BL @HEX_R0 105 | LI R0,>AA55 106 | BL @HEX_R0 107 | 108 | ; Go to next line and test MOVU 109 | LI R0,100 110 | BL @SETUPVDPA 111 | LI R3,HEXTEXT 112 | LI R5,0 ; Byte operations 113 | DATA >038B ; MOVU *R3,R0 114 | BL @HEX_R0 115 | INC R3 116 | DATA >38B ; MOVU *R3,R0 117 | BL @HEX_R0 118 | INC R3 119 | DATA >38B ; MOVU *R3,R0 120 | BL @HEX_R0 121 | 122 | LI R5,>100 ; Word operations 123 | DATA >38B ; MOVU *R3,R0 124 | BL @HEX_R0 125 | INC R3 126 | DATA >38B ; MOVU *R3,R0 127 | BL @HEX_R0 128 | INC R3 129 | DATA >38B ; MOVU *R3,R0 130 | BL @HEX_R0 131 | INC R3 132 | DATA >38B ; MOVU *R3,R0 133 | BL @HEX_R0 134 | 135 | LI R2,':'*256 136 | MOVB R2,@VDPWD 137 | 138 | ; test MOVU *R0,R0 139 | LI R0,HEXTEXT+9 140 | DATA >388 141 | BL @HEX_R0 142 | ; test MOVU *R1,R0 143 | LI R1,HEXTEXT+10 144 | DATA >389 145 | MOV R1,R8 ; Save to R8, since HEX_R0 will mess up R1 146 | BL @HEX_R0 147 | MOV R8,R0 148 | BL @HEX_R0 149 | ; Display finally base address of HEXTEXT 150 | LI R0,HEXTEXT 151 | BL @HEX_R0 152 | ; One more test, with single negative byte, to see sign extension 153 | LI R1,TEST82 154 | CLR R5 155 | DATA >389 ; MOVU *R1,R0 156 | BL @HEX_R0 157 | 158 | ; Wait a little bit 159 | LI R1,250 160 | LP2: CLR R2 161 | LP1: DEC R2 162 | JNE LP1 163 | DEC R1 164 | JNE LP2 165 | 166 | ; Jump back to ROM 167 | BLWP @0 168 | 169 | HEX_R0: ; Display contents of R0 in hex. 170 | MOV R0,R2 171 | SRL R2,12 172 | MOVB @HEXTEXT(R2),@VDPWD ; Write to VDP memory 173 | MOV R0,R2 174 | SRL R2,8 175 | ANDI R2,>000F 176 | MOVB @HEXTEXT(R2),@VDPWD ; Write to VDP memory 177 | MOV R0,R2 178 | SRL R2,4 179 | ANDI R2,>000F 180 | MOVB @HEXTEXT(R2),@VDPWD ; Write to VDP memory 181 | MOV R0,R2 182 | ANDI R2,>000F 183 | MOVB @HEXTEXT(R2),@VDPWD ; Write to VDP memory 184 | MOVB @HEXTEXT+16(R2),@VDPWD ; write space 185 | RT 186 | 187 | 188 | COPY_FONTS: 189 | MOV R11,R9 190 | * copy fonts from GROMs to pattern table 191 | LI R0,>6B4 * setup GROM source address of font table 192 | MOVB R0,@>9C02 193 | SWPB R0 194 | MOVB R0,@>9C02 195 | LI R0,>800+(32*8) * destination address in VRAM 196 | BL @SETUPVDPA 197 | LI R0,62 * 62 characters to copy 198 | CLR R2 199 | !ch2 200 | LI R1,7 * 7 bytes per char 201 | !char 202 | MOVB @>9800,@VDPWD * move byte from GROM to VDP 203 | DEC R1 204 | JNE -!char 205 | MOVB R2,@VDPWD * 8th byte just zero 206 | DEC R0 207 | JNE -!ch2 208 | B *R9 209 | 210 | TEXT 'MARKER' 211 | ; test code to go to ROM 07E0 to handle VDP indirect / direct access using my custom instruction. 212 | label_07e0: 213 | JEQ do_indirect 214 | do_job: 215 | INCT R4 216 | MOVB @>83E3,*R15 ; Write address VDP 217 | MOVB R1,*R15 218 | SLA R0,8 219 | MOVB @>FBFE(R15),R0 ; Data in R0 220 | MOVB R5,R5 ; Word? 221 | JEQ do_byte 222 | MOVB @>FBFE(R15),@>83E1 ; 2nd byte in R 223 | B *R11 224 | do_byte: 225 | SRA R0,8 226 | B *R11 227 | 228 | do_indirect: 229 | MOVB @>8300(R1),R0 ; Fetch value 230 | MOVB @>8301(R1),@>83E1 231 | MOV R0,R1 ; Value in R1 232 | jmp do_job 233 | 234 | 235 | VDPMODE BYTE 0,>04 ; 04=80 columns mode >00 236 | BYTE 1,>F0 237 | BYTE 2,>00 238 | BYTE 3,>0E 239 | BYTE 4,>01 240 | BYTE 5,>06 241 | BYTE 6,>00 242 | BYTE 7,>F4 243 | BYTE 63,>FF ; Enable 9938 MODE 244 | BYTE 9,>80 ; 26.5 lines mode 245 | ; BYTE 49,>40 ; F18A 30 lines mode 246 | BYTE >FF,>FF 247 | 248 | EVEN 249 | VDPTEXT TEXT 'HELLO' 250 | BYTE 0,0,0 251 | HEXTEXT TEXT '0123456789ABCDEF ' 252 | EVEN 253 | TEST82 BYTE >82,0 254 | 255 | END MAIN 256 | -------------------------------------------------------------------------------- /debugcart/vdpload.asm: -------------------------------------------------------------------------------- 1 | * EXECUTABLE CART IMAGE 2 | * Erik Piehl (C) 2020 October 3 | * 4 | * VDPTest, this is a cartridge with 5 banks. 5 | * Each bank has this same executable in the bottom 4K, padded to 4K. 6 | * The top 4K contain data to be written to VDP memory, basically 7 | * the 16K VDP + regs image split to four 4K blocks and the last having 8 | * the registers. 9 | * Taken from a VDPDUMP.BIN file created with classic99. 10 | 11 | IDT 'VDPTEST' 12 | 13 | VDPWD EQU >8C00 * VDP write data 14 | VDPWA EQU >8C02 * VDP set read/write address 15 | VDPRD EQU >8800 * VDP read data 16 | VDPPTR EQU >8890 * VDP extension: VRAM address pointer 17 | BANK0 EQU >6000 18 | BANK4 EQU >6008 19 | DATABLK EQU >7000 * 20 | 21 | *--------------------------------------------- 22 | * Locations in workspace RAM. 23 | WRKSP EQU >8300 * Workspace memory in fast RAM (for paging tests) 24 | R0LB EQU WRKSP+1 * Register zero low byte address 25 | *--------------------------------------------- 26 | 27 | EVEN 28 | 29 | *--------------------------------------------- 30 | * Set VDP read address from R0 31 | *--------------------------------------------- 32 | VDPREADA 33 | ANDI R0,>3FFF * make sure it is a read command 34 | SWPB R0 35 | MOVB R0,@VDPWA * Send low byte of VDP RAM write address 36 | SWPB R0 37 | MOVB R0,@VDPWA * Send high byte of VDP RAM write address 38 | RT 39 | 40 | *--------------------------------------------- 41 | * Set VDP address from R0 42 | *--------------------------------------------- 43 | SETUPVDPA 44 | SWPB R0 45 | MOVB R0,@VDPWA * Send low byte of VDP RAM write address 46 | SWPB R0 47 | ORI R0,>4000 * Set read/write bits 14 and 15 to write (01) 48 | MOVB R0,@VDPWA * Send high byte of VDP RAM write address 49 | RT 50 | 51 | *--------------------------------------------- 52 | * Write VDP register 53 | *--------------------------------------------- 54 | VWREG ORI R0,>8000 55 | CMD SWPB R0 56 | MOVB R0,@>8C02 57 | SWPB R0 58 | MOVB R0,@>8C02 59 | B *R11 60 | 61 | *--------------------------------------------- 62 | * MAIN 63 | *--------------------------------------------- 64 | MAIN LIMI 0 * Disable interrupts 65 | LWPI WRKSP * Load the workspace pointer to fast RAM 66 | ; Switch to the graphics mode specified in ROM bank 4 67 | CLR @BANK4 * Switch to ROM bank 4 68 | LI R1,DATABLK 69 | CLR R0 70 | LI R2,8 71 | ! SWPB R0 ; Move VDP reg number to lower byte 72 | MOVB *R1+,R0 ; Fetch register value byte 73 | SWPB R0 ; Now reg num in high byte, data in low byte 74 | BL @VWREG 75 | AI R0,>0100 ; Inc reg number 76 | DEC R2 77 | JNE -! 78 | ; Done. Setup VDP VRAM pointer to zero. 79 | LI R0,0 80 | BL @SETUPVDPA 81 | ; Next go through banks 0,1,2,3 and copy 4K data from each to VRAM. 82 | LI R3,BANK0 83 | BANKLOOP: 84 | CLR *R3 ; Change to a bank 85 | LI R1,>1000 ; 4K data to Move 86 | LI R2,DATABLK ; Source 87 | ; Loop through the block of 4K 88 | ! MOVB *R2+,@VDPWD 89 | DEC R1 90 | JNE -! 91 | ; Done, go to next bank 92 | INCT R3 93 | CI R3,BANK4 ; Point to bank 4? 94 | JNE BANKLOOP ; No, go back 95 | ; Done. stop here. 96 | STOP JMP STOP 97 | 98 | END MAIN 99 | -------------------------------------------------------------------------------- /debugcart/vdpload.lst: -------------------------------------------------------------------------------- 1 | XAS99 CROSS-ASSEMBLER VERSION 1.5.2 2 | **** **** **** > vdpload.asm 3 | 0001 * EXECUTABLE CART IMAGE 4 | 0002 * Erik Piehl (C) 2020 October 5 | 0003 * 6 | 0004 * VDPTest, this is a cartridge with 5 banks. 7 | 0005 * Each bank has this same executable in the bottom 4K, padded to 4K. 8 | 0006 * The top 4K contain data to be written to VDP memory, basically 9 | 0007 * the 16K VDP + regs image split to four 4K blocks and the last having 10 | 0008 * the registers. 11 | 0009 * Taken from a VDPDUMP.BIN file created with classic99. 12 | 0010 13 | 0011 IDT 'VDPTEST' 14 | 0012 15 | 0013 8C00 VDPWD EQU >8C00 * VDP write data 16 | 0014 8C02 VDPWA EQU >8C02 * VDP set read/write address 17 | 0015 8800 VDPRD EQU >8800 * VDP read data 18 | 0016 8890 VDPPTR EQU >8890 * VDP extension: VRAM address pointer 19 | 0017 6000 BANK0 EQU >6000 20 | 0018 6008 BANK4 EQU >6008 21 | 0019 7000 DATABLK EQU >7000 * 22 | 0020 23 | 0021 *--------------------------------------------- 24 | 0022 * Locations in workspace RAM. 25 | 0023 8300 WRKSP EQU >8300 * Workspace memory in fast RAM (for paging tests) 26 | 0024 8301 R0LB EQU WRKSP+1 * Register zero low byte address 27 | 0025 *--------------------------------------------- 28 | 0026 29 | 0027 EVEN 30 | 0028 31 | 0029 *--------------------------------------------- 32 | 0030 * Set VDP read address from R0 33 | 0031 *--------------------------------------------- 34 | 0032 VDPREADA 35 | 0033 0000 0240 22 ANDI R0,>3FFF * make sure it is a read command 36 | 0002 3FFF 37 | 0034 0004 06C0 14 SWPB R0 38 | 0035 0006 D800 38 MOVB R0,@VDPWA * Send low byte of VDP RAM write address 39 | 0008 8C02 40 | 0036 000A 06C0 14 SWPB R0 41 | 0037 000C D800 38 MOVB R0,@VDPWA * Send high byte of VDP RAM write address 42 | 000E 8C02 43 | 0038 0010 045B 20 RT 44 | 0039 45 | 0040 *--------------------------------------------- 46 | 0041 * Set VDP address from R0 47 | 0042 *--------------------------------------------- 48 | 0043 SETUPVDPA 49 | 0044 0012 06C0 14 SWPB R0 50 | 0045 0014 D800 38 MOVB R0,@VDPWA * Send low byte of VDP RAM write address 51 | 0016 8C02 52 | 0046 0018 06C0 14 SWPB R0 53 | 0047 001A 0260 22 ORI R0,>4000 * Set read/write bits 14 and 15 to write (01) 54 | 001C 4000 55 | 0048 001E D800 38 MOVB R0,@VDPWA * Send high byte of VDP RAM write address 56 | 0020 8C02 57 | 0049 0022 045B 20 RT 58 | 0050 59 | 0051 *--------------------------------------------- 60 | 0052 * Write VDP register 61 | 0053 *--------------------------------------------- 62 | 0054 0024 0260 22 VWREG ORI R0,>8000 63 | 0026 8000 64 | 0055 0028 06C0 14 CMD SWPB R0 65 | 0056 002A D800 38 MOVB R0,@>8C02 66 | 002C 8C02 67 | 0057 002E 06C0 14 SWPB R0 68 | 0058 0030 D800 38 MOVB R0,@>8C02 69 | 0032 8C02 70 | 0059 0034 045B 20 B *R11 71 | 0060 72 | 0061 *--------------------------------------------- 73 | 0062 * MAIN 74 | 0063 *--------------------------------------------- 75 | 0064 0036 0300 24 MAIN LIMI 0 * Disable interrupts 76 | 0038 0000 77 | 0065 003A 02E0 18 LWPI WRKSP * Load the workspace pointer to fast RAM 78 | 003C 8300 79 | 0066 ; Switch to the graphics mode specified in ROM bank 4 80 | 0067 003E 04E0 34 CLR @BANK4 * Switch to ROM bank 4 81 | 0040 6008 82 | 0068 0042 0201 20 LI R1,DATABLK 83 | 0044 7000 84 | 0069 0046 04C0 14 CLR R0 85 | 0070 0048 0202 20 LI R2,8 86 | 004A 0008 87 | 0071 004C 06C0 14 ! SWPB R0 ; Move VDP reg number to lower byte 88 | 0072 004E D031 28 MOVB *R1+,R0 ; Fetch register value byte 89 | 0073 0050 06C0 14 SWPB R0 ; Now reg num in high byte, data in low byte 90 | 0074 0052 06A0 32 BL @VWREG 91 | 0054 0024r 92 | 0075 0056 0220 22 AI R0,>0100 ; Inc reg number 93 | 0058 0100 94 | 0076 005A 0602 14 DEC R2 95 | 0077 005C 16F7 14 JNE -! 96 | 0078 ; Done. Setup VDP VRAM pointer to zero. 97 | 0079 005E 0200 20 LI R0,0 98 | 0060 0000 99 | 0080 0062 06A0 32 BL @SETUPVDPA 100 | 0064 0012r 101 | 0081 ; Next go through banks 0,1,2,3 and copy 4K data from each to VRAM. 102 | 0082 0066 0203 20 LI R3,BANK0 103 | 0068 6000 104 | 0083 BANKLOOP: 105 | 0084 006A 04D3 26 CLR *R3 ; Change to a bank 106 | 0085 006C 0201 20 LI R1,>1000 ; 4K data to Move 107 | 006E 1000 108 | 0086 0070 0202 20 LI R2,DATABLK ; Source 109 | 0072 7000 110 | 0087 ; Loop through the block of 4K 111 | 0088 0074 D832 48 ! MOVB *R2+,@VDPWD 112 | 0076 8C00 113 | 0089 0078 0601 14 DEC R1 114 | 0090 007A 16FC 14 JNE -! 115 | 0091 ; Done, go to next bank 116 | 0092 007C 05C3 14 INCT R3 117 | 0093 007E 0283 22 CI R3,BANK4 ; Point to bank 4? 118 | 0080 6008 119 | 0094 0082 16F3 14 JNE BANKLOOP ; No, go back 120 | 0095 ; Done. stop here. 121 | 0096 0084 10FF 14 STOP JMP STOP 122 | 0097 123 | 0098 END MAIN 124 | -------------------------------------------------------------------------------- /editrom.sh: -------------------------------------------------------------------------------- 1 | # editrom.sh 2 | gcc tools/editrom.c -o editrom 3 | ./editrom roms/994aROM.Bin roms/99opt.bin 4 | hexdump -C roms/994aRom.Bin > roms/original.txt 5 | hexdump -C roms/99opt.bin > roms/optimized.txt 6 | fmdiff roms/original.txt roms/optimized.txt 7 | -------------------------------------------------------------------------------- /esp32/cmdline/mount.py: -------------------------------------------------------------------------------- 1 | 2 | import os, gc, ecp5, machine 3 | os.mount(machine.SDCard(slot=3),"/sd") 4 | 5 | # the following are not supposed to be in root directory 6 | a = ['ti994a_ulx3s.bit', 'VDP9938.bin', '994aROM.Bin'] 7 | for i in a: 8 | os.remove(i) 9 | 10 | -------------------------------------------------------------------------------- /esp32/cmdline/spiram.py: -------------------------------------------------------------------------------- 1 | # micropython ESP32 2 | # SPI RAM test R/W 3 | 4 | # AUTHOR=EMARD 5 | # LICENSE=BSD 6 | 7 | # this code is SPI master to FPGA SPI slave 8 | 9 | import os, gc 10 | from machine import SPI, Pin, SDCard 11 | from micropython import const 12 | from struct import unpack 13 | from uctypes import addressof 14 | 15 | class spiram: 16 | def __init__(self): 17 | self.led = Pin(5,Pin.OUT) 18 | self.led.off() 19 | self.spi_channel = const(2) 20 | self.init_pinout_sd() 21 | self.spi_freq = const(200000) 22 | self.hwspi=SPI(self.spi_channel, baudrate=self.spi_freq, polarity=0, phase=0, bits=8, firstbit=SPI.MSB, sck=Pin(self.gpio_sck), mosi=Pin(self.gpio_mosi), miso=Pin(self.gpio_miso)) 23 | 24 | @micropython.viper 25 | def init_pinout_sd(self): 26 | self.gpio_sck = const(16) 27 | self.gpio_mosi = const(4) 28 | self.gpio_miso = const(12) 29 | 30 | # read from file -> write to SPI RAM 31 | def load_stream(self, filedata, addr=0, maxlen=0x10000, blocksize=1024): 32 | block = bytearray(blocksize) 33 | # Request load 34 | self.led.on() 35 | self.hwspi.write(bytearray([0,(addr >> 24) & 0xFF, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF])) 36 | bytes_loaded = 0 37 | while bytes_loaded < maxlen: 38 | if filedata.readinto(block): 39 | self.hwspi.write(block) 40 | bytes_loaded += blocksize 41 | else: 42 | break 43 | self.led.off() 44 | 45 | # read from SPI RAM -> write to file 46 | def save_stream(self, filedata, addr=0, length=1024, blocksize=1024): 47 | bytes_saved = 0 48 | block = bytearray(blocksize) 49 | # Request save 50 | self.led.on() 51 | self.hwspi.write(bytearray([1,(addr >> 24) & 0xFF, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF, 0])) 52 | while bytes_saved < length: 53 | self.hwspi.readinto(block) 54 | filedata.write(block) 55 | bytes_saved += len(block) 56 | self.led.off() 57 | 58 | def ctrl(self,i): 59 | self.led.on() 60 | self.hwspi.write(bytearray([0, 0xFF, 0xFF, 0xFF, 0xFF, i])) 61 | self.led.off() 62 | 63 | def cpu_halt(self): 64 | self.ctrl(2) 65 | 66 | def cpu_continue(self): 67 | self.ctrl(0) 68 | 69 | def store_rom(self,length=32): 70 | self.stored_code=bytearray(length) 71 | self.led.on() 72 | self.hwspi.write(bytearray([1, 0,0,(self.code_addr>>8)&0xFF,self.code_addr&0xFF, 0])) 73 | self.hwspi.readinto(self.stored_code) 74 | self.led.off() 75 | self.stored_vector=bytearray(2) 76 | self.led.on() 77 | self.hwspi.write(bytearray([1, 0,0,(self.vector_addr>>8)&0xFF,self.vector_addr&0xFF, 0])) 78 | self.hwspi.readinto(self.stored_vector) 79 | self.led.off() 80 | 81 | def restore_rom(self): 82 | self.led.on() 83 | self.hwspi.write(bytearray([0, 0,0,(self.code_addr>>8)&0xFF,self.code_addr&0xFF])) 84 | self.hwspi.write(self.stored_code) 85 | self.led.off() 86 | self.led.on() 87 | self.hwspi.write(bytearray([0, 0,0,(self.vector_addr>>8)&0xFF,self.vector_addr&0xFF])) 88 | self.hwspi.write(self.stored_vector) 89 | self.led.off() 90 | 91 | def patch_rom(self,regs): 92 | self.led.on() 93 | self.hwspi.write(bytearray([0, 0,0,(self.vector_addr>>8)&0xFF,self.vector_addr&0xFF, self.code_addr&0xFF, (self.code_addr>>8)&0xFF])) # overwrite reset vector at 0xFFFC 94 | self.led.off() 95 | self.led.on() 96 | self.hwspi.write(bytearray([0, 0,0,(self.code_addr>>8)&0xFF,self.code_addr&0xFF])) # overwrite code 97 | self.led.off() 98 | self.led.on() 99 | 100 | 101 | def load(filename, addr=0): 102 | s=spiram() 103 | s.cpu_halt() 104 | s.load_stream(open(filename, "rb"), addr=addr) 105 | s.cpu_continue() 106 | 107 | def save(filename, addr=0, length=0x8000): 108 | s=spiram() 109 | f=open(filename, "wb") 110 | s.cpu_halt() 111 | s.save_stream(f, addr, length) 112 | s.cpu_continue() 113 | f.close() 114 | 115 | def ctrl(i): 116 | s=spiram() 117 | s.led.on() 118 | s.hwspi.write(bytearray([0, 0xFF, 0xFF, 0xFF, 0xFF, i])) 119 | s.led.off() 120 | 121 | def peek(addr,length=1): 122 | s=spiram() 123 | s.cpu_halt() 124 | s.led.on() 125 | s.hwspi.write(bytearray([1,(addr >> 24) & 0xFF, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF, 0])) 126 | b=bytearray(length) 127 | s.hwspi.readinto(b) 128 | s.led.off() 129 | s.cpu_continue() 130 | return b 131 | 132 | def poke(addr,data): 133 | s=spiram() 134 | s.cpu_halt() 135 | s.led.on() 136 | s.hwspi.write(bytearray([0,(addr >> 24) & 0xFF, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF])) 137 | s.hwspi.write(data) 138 | s.led.off() 139 | s.cpu_continue() 140 | 141 | def help(): 142 | print("spiram.load(\"file.bin\",addr=0)") 143 | print("spiram.save(\"file.bin\",addr=0,length=0x8000)") 144 | 145 | os.mount(SDCard(slot=3),"/sd") 146 | #ecp5.prog("/sd/msx/bitstreams/ulx3s_85f_msx1.bit") 147 | gc.collect() 148 | -------------------------------------------------------------------------------- /esp32/osd/ld_nes.py: -------------------------------------------------------------------------------- 1 | # micropython ESP32 2 | # NES ROM image loader 3 | 4 | # AUTHOR=EMARD 5 | # LICENSE=BSD 6 | 7 | # this code is SPI master to FPGA SPI slave 8 | # FPGA sends pulse to GPIO after BTN state is changed. 9 | # on GPIO pin interrupt from FPGA: 10 | # btn_state = SPI_read 11 | # SPI_write(buffer) 12 | # FPGA SPI slave will accept image and start it 13 | 14 | #from machine import SPI, Pin, SDCard, Timer 15 | #from micropython import const, alloc_emergency_exception_buf 16 | #from uctypes import addressof 17 | from struct import unpack 18 | #from time import sleep_ms 19 | #import os 20 | 21 | #import ecp5 22 | #import gc 23 | 24 | class ld_nes: 25 | def __init__(self,spi,cs): 26 | self.spi=spi 27 | self.cs=cs 28 | self.cs.off() 29 | #self.rom="/sd/zxspectrum/roms/opense.rom" 30 | 31 | # LOAD/SAVE and CPU control 32 | 33 | # read from file -> write to SPI RAM 34 | def load_stream(self, filedata, addr=0, maxlen=0x10000, blocksize=1024): 35 | block = bytearray(blocksize) 36 | # Request load 37 | self.cs.on() 38 | self.spi.write(bytearray([0,(addr >> 24) & 0xFF, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF])) 39 | bytes_loaded = 0 40 | while bytes_loaded < maxlen: 41 | if filedata.readinto(block): 42 | self.spi.write(block) 43 | bytes_loaded += blocksize 44 | else: 45 | break 46 | self.cs.off() 47 | 48 | # read from SPI RAM -> write to file 49 | def save_stream(self, filedata, addr=0, length=1024, blocksize=1024): 50 | bytes_saved = 0 51 | block = bytearray(blocksize) 52 | # Request save 53 | self.cs.on() 54 | self.spi.write(bytearray([1,(addr >> 24) & 0xFF, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF, 0])) 55 | while bytes_saved < length: 56 | self.spi.readinto(block) 57 | filedata.write(block) 58 | bytes_saved += len(block) 59 | self.cs.off() 60 | 61 | def ctrl(self,i): 62 | self.cs.on() 63 | self.spi.write(bytearray([0, 0xFF, 0xFF, 0xFF, 0xFF, i])) 64 | self.cs.off() 65 | 66 | def cpu_halt(self): 67 | self.ctrl(2) 68 | 69 | def cpu_continue(self): 70 | self.ctrl(0) 71 | 72 | -------------------------------------------------------------------------------- /esp32/osd/ld_ti99_4a.py: -------------------------------------------------------------------------------- 1 | # micropython ESP32 2 | # TI99/4A (icy99) ROM image loader 3 | 4 | # AUTHOR=EMARD 5 | # LICENSE=BSD 6 | 7 | class ld_ti99_4a: 8 | def __init__(self,spi,cs): 9 | self.spi=spi 10 | self.spi.init(baudrate=1000000) # 1 MHz 11 | self.cs=cs 12 | self.cs.off() 13 | self.cart_rom_region = 0x200000 14 | self.cart_grom_region = 0x16000 15 | self.cart_tipi_region = 0x30000 16 | 17 | # LOAD/SAVE and CPU control 18 | 19 | # read from file -> write to SPI RAM 20 | def load_stream(self, filedata, addr=0, maxlen=0x200000, blocksize=1024): 21 | print("load_stream: addr={}".format(addr)) 22 | block = bytearray(blocksize) 23 | # Request load 24 | self.cs.on() 25 | self.spi.write(bytearray([0,(addr >> 24) & 0xFF, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF])) 26 | bytes_loaded = 0 27 | while bytes_loaded < maxlen: 28 | if filedata.readinto(block): 29 | self.spi.write(block) 30 | bytes_loaded += blocksize 31 | else: 32 | break 33 | self.cs.off() 34 | return bytes_loaded 35 | 36 | # tight GROM file (no padding) 37 | # each 6K block from this file is written to even 8K address 38 | def load_grom_tight(self, filedata, addr=0, maxlen=0x80000, blocksize=1024): 39 | block = bytearray(blocksize) 40 | bytes_loaded = 0 41 | while bytes_loaded < maxlen: 42 | # Request load 43 | self.cs.on() 44 | self.spi.write(bytearray([0,(addr >> 24) & 0xFF, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF])) 45 | grom_loaded = 0 46 | while grom_loaded < 6144: 47 | if filedata.readinto(block): 48 | self.spi.write(block) 49 | grom_loaded += blocksize 50 | else: 51 | bytes_loaded = maxlen # force exit of outer loop 52 | break 53 | self.cs.off() 54 | bytes_loaded += 6144 55 | addr += 8192 56 | 57 | # filename used to detect what ROM it is and where to load 58 | def load_rom_auto(self, filedata, filename=""): 59 | addr=self.cart_rom_region 60 | tight=0 61 | fname = filename.lower() 62 | if fname.startswith("/sd/ti99_4a/cart"): 63 | addr=self.cart_rom_region 64 | if fname.startswith("/sd/ti99_4a/grom"): 65 | addr=self.cart_grom_region 66 | if fname.startswith("/sd/ti99_4a/dsr"): 67 | addr=self.cart_tipi_region # DSR routines to drive TIPI 68 | if fname.endswith("8.bin"): 69 | addr=self.cart_rom_region 70 | if fname.endswith("c.bin"): 71 | addr=self.cart_rom_region 72 | if fname.endswith("d.bin"): 73 | addr=self.cart_rom_region+0x2000 74 | if fname.endswith("g.bin"): 75 | addr=self.cart_grom_region 76 | if fname.endswith("g3.bin"): 77 | addr=self.cart_grom_region 78 | if fname.endswith("g4.bin"): 79 | addr=self.cart_grom_region+0x2000 80 | if fname.endswith("g5.bin"): 81 | addr=self.cart_grom_region+0x4000 82 | if fname.endswith("g6.bin"): 83 | addr=self.cart_grom_region+0x6000 84 | if fname.endswith("g7.bin"): 85 | addr=self.cart_grom_region+0x8000 86 | if fname.endswith("6k.bin"): 87 | addr=self.cart_grom_region 88 | tight=1 89 | if tight: 90 | self.load_grom_tight(filedata,addr) 91 | else: 92 | self.load_stream(filedata,addr) 93 | print("Loaded {} at {}".format(filename, hex(addr))) 94 | self.reset_on() 95 | self.reset_off() 96 | 97 | # read from SPI RAM -> write to file 98 | def save_stream(self, filedata, addr=0, length=1024, blocksize=1024): 99 | bytes_saved = 0 100 | block = bytearray(blocksize) 101 | # Request save 102 | self.cs.on() 103 | self.spi.write(bytearray([1,(addr >> 24) & 0xFF, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF, 0])) 104 | while bytes_saved < length: 105 | self.spi.readinto(block) 106 | filedata.write(block) 107 | bytes_saved += len(block) 108 | self.cs.off() 109 | 110 | def read_bytes(self, addr=0x1000000, length=32): 111 | block = bytearray(length) 112 | # Request save 113 | self.cs.on() 114 | self.spi.write(bytearray([1,(addr >> 24) & 0xFF, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF, 0])) 115 | self.spi.readinto(block) 116 | self.cs.off() 117 | return block 118 | 119 | def ctrl(self,i): 120 | self.cs.on() 121 | self.spi.write(bytearray([0, 0x01, 0x00, 0x00, 0x08, i, i])) 122 | self.cs.off() 123 | 124 | def reset_on(self): 125 | self.ctrl(0xFC) 126 | 127 | def reset_off(self): 128 | self.ctrl(0xFF) 129 | -------------------------------------------------------------------------------- /explain_vdp.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # explain_vdp.py 3 | # Erik Piehl (C) 2020 4 | # decode VDP register values to display what is going on 5 | 6 | 7 | def explain(r, names): 8 | s = "" 9 | for i in range(0,8): 10 | if r & (1 << (7-i)): 11 | s = s+" "+names[i] 12 | return s 13 | 14 | 15 | def show_mode(regs): 16 | addr2 = (regs[2] & 0xF) << 10 # (4 bits) 1K boundaries 17 | addr3 = regs[3] << 6 # (8 bits) color table 64 byte boundaries 18 | addr4 = (regs[4] & 7) << 11 # (3 bits) pattern generator 2K boundaries 19 | addr5 = (regs[5] & 0x7F) << 7 # (7 bits) sprite attribute table 128 byte boundaries 20 | addr6 = (regs[6] & 7) << 11 # (3 bits) sprite pattern generator 2K boundaries 21 | 22 | s0 = explain(regs[0], [ "", "", "", "", "", "", "Bitmap", "Ext_vid"]) 23 | s1 = explain(regs[1], [ "16K", "/Blank", "Int_on", "Text", "Multicolor", "", "16x16", "2x2"]) 24 | print("R0 {:02X} {}".format(regs[0], s0), end='') 25 | print("R1 {:02X} {}".format(regs[1], s1)) 26 | m3 = regs[0] & 2 27 | m1 = regs[1] & 0x10 28 | m2 = regs[1] & 0x08 # Text mode 29 | m4 = regs[0] & 4 # 80 column mode 30 | if (not m1 and not m2 and not m3): 31 | modes="Graphics 1 mode" 32 | elif (not m1 and not m2 and m3): 33 | # in grahics mode 2 only the top bit of character and color table matter. 34 | modes="Graphics 2 mode" 35 | addr4 = addr4 & 0x2000 36 | addr3 = addr3 & 0x2000 37 | elif (not m1 and m2 and not m3): 38 | modes="Multicolor mode" 39 | elif (m1 and not m2 and not m3 and not m4): 40 | modes="40 column text mode" 41 | elif (m1 and not m2 and not m3 and m4): 42 | modes="80 column text mode" 43 | else: 44 | modes="Non-standard video mode" 45 | print("m1={} m2={} m3={} m4={}: {}".format(m1,m2,m3, m4, modes)) 46 | 47 | 48 | print("Image table at {:04X} R2, character numbers.".format(addr2)) 49 | print("Color table at {:04X} R3 (not used in text mode, multicolor mode)".format(addr3)) 50 | print("Character table at {:04X} R4, character table, i.e. fonts".format(addr4)) 51 | print("Sprite attribute table {:04X} R5, 4 bytes per sprite".format(addr5)) 52 | print("Sprite pattern table {:04X} R6, 8 or 32 bytes per sprite".format(addr6)) 53 | print("Foreground color {:2X} R7 high nibble".format(regs[7] >> 4)) 54 | print("Background color {:2X} R7 low nibble".format(regs[7] & 0xF)) 55 | 56 | def explain_regs(name, regs): 57 | print("-------------") 58 | print(name) 59 | print("-------------") 60 | show_mode(regs) 61 | 62 | #Main, go through some games 63 | explain_regs("Menu", [0x00, 0xE0, 0xF0, 0x0E, 0xF9, 0x86, 0xF8, 0xF7 ]) 64 | explain_regs("Invaders", [0x00, 0xE2, 0xF0, 0x0E, 0xF9, 0x86, 0xF8, 0xF1 ]) 65 | explain_regs("Parsec", [0x02, 0xE2, 0x06, 0xFF, 0x03, 0x36, 0x03, 0x11 ]) 66 | explain_regs("Defender", [0x00, 0xE2, 0x00, 0x0E, 0x01, 0x3E, 0x02, 0x01]) 67 | explain_regs("TurboForth", [0x00, 0xf0, 0x00, 0x0e, 0x01, 0x06, 0x00,0xf4]) 68 | explain_regs("Megademo part 1", [0x02, 0xE2, 0x0A, 0x9F, 0x00, 0x56, 0x03, 0x07]) 69 | explain_regs("Megedemo multicolor", [0x00, 0xEA, 0x04, 0x00, 0x01, 0x26, 0x03, 0x00]) 70 | explain_regs("Megademo splitscreen3", [0x00, 0xA2, 0x00, 0xC0, 0x04, 0x70, 0x05, 0x00]) 71 | 72 | -------------------------------------------------------------------------------- /gpl-opt/analyze_tracebuf.py: -------------------------------------------------------------------------------- 1 | # analyze_tracebuf.py 2 | # EP (C) 2019-11-02 3 | # EP updated 2023-11-28 with argparse 4 | 5 | import subprocess 6 | import argparse 7 | 8 | parser = argparse.ArgumentParser(description="icy99 Tracebuffer Analyzer") 9 | parser.add_argument("--src", type=str, help='Source tracebuffer file, 8k in size', default='gpl-opt/tb1.bin') 10 | parser.add_argument("--rom", type=str, help='Path to TI ROM file for disassembly', default='roms/994aROM.bin') 11 | parser.add_argument("--dis", type=str, help='Path to disassembler executable', default='./dis') 12 | args = parser.parse_args() 13 | 14 | # Read the binary dumpfile 15 | # src = open("readback/status-blackice.bin", "rb") 16 | try: 17 | src = open(args.src, "rb") 18 | except FileNotFoundError: 19 | print(f"Source file {args.src} not found.") 20 | quit() 21 | 22 | data = src.read(8192) 23 | src.close() 24 | 25 | # Be ready to compare with system rom 26 | try: 27 | romf=open(args.rom, "rb") 28 | except FileNotFoundError: 29 | print(f"Unable to open ROM file {args.rom}") 30 | quit() 31 | 32 | romdata = romf.read(8192) 33 | romf.close() 34 | 35 | # Bring up our disassmebler 36 | disassembled = "" 37 | 38 | # Ok now data is our 8192 byte array. The interesting data is the last 4K. 39 | # First read the index byte and determine which was the last written entry+1. 40 | index = data[4096+2] 41 | # now let's just roll through the data and display it 42 | for i in range(0,255): 43 | j = index+i+1 44 | if j > 255: 45 | j=j-256 46 | adr = (data[4096+j*16+6] << 8) + data[4096+j*16+7] 47 | dat = (data[4096+j*16+4] << 8) + data[4096+j*16+5] 48 | ctrl = data[4096+j*16+3] 49 | # new tracebuf2 signals 50 | dat1 = (data[4096+j*16+14] << 8) + data[4096+j*16+15] 51 | dat2 = (data[4096+j*16+12] << 8) + data[4096+j*16+13] 52 | ctrl2 = data[4096+j*16+11] 53 | s = "" 54 | 55 | # if we have a read from ROM, compare the content to expected 56 | mismatch="" 57 | if (ctrl & 1) and adr < 8192 and (adr & 1) == 0: 58 | romword = (romdata[adr]<<8) + romdata[adr+1] 59 | if romword != dat: 60 | mismatch = "mismatch {:04X} {:04X}".format(romword,dat) 61 | 62 | 63 | if(ctrl & 8): 64 | s += "IAQ " 65 | dis = subprocess.Popen(args.dis, stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True, bufsize=0) 66 | dis.stdin.write("{:04X}\n".format(dat)) 67 | k = dis.communicate() 68 | disassembled = k[0] 69 | dis.wait(100) 70 | disassembled = disassembled[:-1] 71 | else: 72 | s += " " 73 | disassembled = "" 74 | if(ctrl & 4): 75 | s += "INT " # interrupt request active 76 | else: 77 | s += " " 78 | 79 | if(ctrl & 2): 80 | s += "WR " 81 | else: 82 | s += " " 83 | 84 | if(ctrl & 1): 85 | s += "RD " 86 | else: 87 | s += " " 88 | 89 | if (i & 7) == 0: 90 | # RD 0000 0000 X8 0024 0024 (mismatch 83E0 0000) 91 | print(" ADDR-DAT---X-DAT2-DAT1") 92 | 93 | # Add the additional fields after X 94 | tb2 = f"X{ctrl2:X} {dat2:04X} {dat1:04X} " 95 | 96 | if len(mismatch) > 0: 97 | print("{} {:04X} {:04X} {} ({}) {}".format(s, adr, dat, tb2, mismatch, disassembled)) 98 | else: 99 | print(f"{s} {adr:04X} {dat:04X} {tb2} {disassembled}") 100 | -------------------------------------------------------------------------------- /gpl-opt/analyzerom.py: -------------------------------------------------------------------------------- 1 | # analyze-rom.py 2 | # 3 | # EP 2020-12-16 find certain instruction sequences in ROM. 4 | 5 | 6 | import subprocess 7 | 8 | def disassemble(dat): 9 | dis = subprocess.Popen("./dis_mac", stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True, bufsize=0) 10 | # dis = subprocess.Popen("../dis", stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True, bufsize=0) 11 | dis.stdin.write("{:04X}\n".format(dat)) 12 | k = dis.communicate() 13 | disassembled = k[0] 14 | dis.wait(100) 15 | disassembled = disassembled[:-1] 16 | return disassembled 17 | 18 | def find_word(romwords, term): 19 | addresses = [] 20 | for i in range(0, len(romwords)-len(term)): 21 | match = True 22 | for j in range(0, len(term)): 23 | if romwords[i+j] != term[j]: 24 | match = False 25 | if match: 26 | addresses.append(i) 27 | return addresses 28 | 29 | 30 | # Be ready to compare with system rom 31 | romf=open("../roms/994aROM.bin", "rb") 32 | romdata = romf.read(8192) 33 | romf.close() 34 | 35 | 36 | # print(len(romdata)) 37 | 38 | # Modify ROM to 16 bit values 39 | rom16 = [] 40 | word_count = int(len(romdata)/2-1) 41 | print(word_count) 42 | for i in range(0,word_count): 43 | print(i) 44 | rom16.append( (romdata[i*2] << 8) + romdata[i*2+1] ) 45 | 46 | # Bring up our disassembler 47 | disassembled = "" 48 | for k in range(0x70, 0x80, 2): 49 | print("{:04X} {:04X} {}".format(k, rom16[k >> 1], disassemble(rom16[k >> 1]))) 50 | 51 | print("Looking for 77A") 52 | a = find_word(rom16, [0x77A]) 53 | [print(hex(k<<1)) for k in a] 54 | 55 | print("Looking for BL 77A") 56 | a = find_word(rom16, [0x06a0, 0x77A]) 57 | [print(hex(k<<1)) for k in a] 58 | 59 | print("Looking for all BL @ADDR instructions") 60 | a = find_word(rom16, [0x06a0]) 61 | [print(hex(k<<1)) for k in a] 62 | print("Found {} instructions".format(len(a))) 63 | -------------------------------------------------------------------------------- /initsd.sh: -------------------------------------------------------------------------------- 1 | # initsd.sh 2 | # 2020-12-20 3 | # create the directory structure on ESP32 4 | 5 | ftp 192.168.0.160 << EOT 6 | 7 | bin 8 | cd /sd 9 | mkdir ti99_4a 10 | cd ti99_4a 11 | mkdir grom 12 | mkdir cart 13 | mkdir rom 14 | mkdir bitstreams 15 | mkdir dsr 16 | 17 | cd /sd/ti99_4a/bitstreams 18 | pwd 19 | put ti994a_ulx3s.bit 20 | cd / 21 | pwd 22 | lcd esp32/osd 23 | put osd.py 24 | put ld_ti99_4a.py 25 | 26 | cd /sd/ti99_4a/cart 27 | pwd 28 | lcd ../../debugcart 29 | put VDP9938.bin 30 | 31 | lcd ../roms 32 | 33 | cd /sd/ti99_4a/rom 34 | pwd 35 | put 994aROM.Bin 36 | 37 | cd ../grom 38 | pwd 39 | put 994AGROM.Bin 40 | 41 | cd ../dsr 42 | pwd 43 | put tipi.bin 44 | 45 | bye 46 | EOT 47 | -------------------------------------------------------------------------------- /lcd/ram_source.v: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Joel Holdsworth 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. Neither the name of copyright holder nor the names of its 14 | * contributors may be used to endorse or promote products derived from 15 | * this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | /** 31 | * A pixel source that reads pixels from BRAM. 32 | */ 33 | module ram_source(clk, reset, frame_begin, sample_pixel, pixel_index, 34 | pixel_data, wr_clk, wr_en, wr_addr, wr_data); 35 | input clk, reset; 36 | output frame_begin, sample_pixel; 37 | input [12:0] pixel_index; 38 | output reg [15:0] pixel_data; 39 | input wr_clk, wr_en; 40 | input [12:0] wr_addr; 41 | input [15:0] wr_data; 42 | 43 | localparam PixelCount = 64 * 96; 44 | 45 | reg [15:0] mem[0:PixelCount-1]; 46 | 47 | always @(posedge clk) 48 | if (sample_pixel) 49 | pixel_data <= mem[pixel_index]; 50 | 51 | always @(posedge wr_clk) 52 | if (wr_en) 53 | mem[wr_addr] <= wr_data; 54 | 55 | initial $readmemh("lcd/ram_template.hex", mem); 56 | 57 | endmodule 58 | -------------------------------------------------------------------------------- /osd/font2readmemb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # convert binary file (usually 2K or 4K to verilog $readmemb() 4 | import sys 5 | 6 | font_height=16 7 | msb_first=1 8 | 9 | def printer(n, b): 10 | if n >= 32 and n <= 127: 11 | print("// 0x%02X %c" % (n, n)) 12 | else: 13 | print("// 0x%02X" % n) 14 | if msb_first: 15 | for c in b: 16 | for i in range(8): 17 | print((c&0x80)>>7, end="") 18 | c <<= 1 19 | print("") 20 | else: # lsb first 21 | for c in b: 22 | for i in range(8): 23 | print(c&1, end="") 24 | c >>= 1 25 | print("") 26 | 27 | def converter(): 28 | f=sys.stdin 29 | print('// spi_osd.v: initial $readmemb("font_bizcat8x16.mem", font);') 30 | b = bytearray(font_height) 31 | i = 0 32 | while True: 33 | b = f.buffer.read(16) 34 | if b: 35 | printer(i,b) 36 | i += 1 37 | else: 38 | break 39 | f.close() 40 | 41 | converter() 42 | -------------------------------------------------------------------------------- /osd/osd.mem: -------------------------------------------------------------------------------- 1 | 4f 2 | 53 3 | 44 4 | 20 5 | 28 6 | 6f 7 | 6e 8 | 2d 9 | 73 10 | 63 11 | 72 12 | 65 13 | 65 14 | 6e 15 | 20 16 | 64 17 | 69 18 | 73 19 | 70 20 | 6c 21 | 61 22 | 79 23 | 29 24 | 20 25 | 76 26 | 69 27 | 64 28 | 65 29 | 6f 30 | 20 31 | 32 | -------------------------------------------------------------------------------- /osd/osd.v: -------------------------------------------------------------------------------- 1 | // intercept video stream and make a window 2 | 3 | module osd 4 | #( 5 | parameter C_x_start = 128, 6 | parameter C_x_stop = 383, 7 | parameter C_y_start = 128, 8 | parameter C_y_stop = 383, 9 | parameter C_transparency = 0 10 | ) 11 | ( 12 | input wire clk_pixel, clk_pixel_ena, 13 | input wire [7:0] i_r, 14 | input wire [7:0] i_g, 15 | input wire [7:0] i_b, 16 | input wire i_hsync, i_vsync, i_blank, 17 | input wire i_osd_en, 18 | input wire [7:0] i_osd_r, 19 | input wire [7:0] i_osd_g, 20 | input wire [7:0] i_osd_b, 21 | output wire [9:0] o_osd_x, 22 | output wire [9:0] o_osd_y, 23 | output wire [7:0] o_r, 24 | output wire [7:0] o_g, 25 | output wire [7:0] o_b, 26 | output wire o_hsync, o_vsync, o_blank 27 | ); 28 | 29 | 30 | reg osd_en, osd_xen, osd_yen; 31 | reg R_xcount_en, R_ycount_en; 32 | reg R_hsync_prev; 33 | reg [9:0] R_xcount, R_ycount; // relative to screen 34 | reg [9:0] R_osd_x, R_osd_y; // relative to OSD 35 | always @(posedge clk_pixel) 36 | begin 37 | if(clk_pixel_ena) 38 | begin 39 | if(i_vsync) 40 | begin 41 | R_ycount <= 0; 42 | R_ycount_en <= 0; // wait for blank before counting 43 | end 44 | else 45 | begin 46 | if(i_blank == 1'b0) // display unblanked 47 | R_ycount_en <= 1'b1; 48 | if(R_hsync_prev == 1'b0 && i_hsync == 1'b1) 49 | begin // hsync rising edge 50 | R_xcount <= 0; 51 | R_xcount_en <= 0; 52 | if(R_ycount_en) 53 | R_ycount <= R_ycount + 1; 54 | if(R_ycount == C_y_start) 55 | begin 56 | osd_yen <= 1; 57 | R_osd_y <= 0; 58 | end 59 | if(osd_yen) 60 | R_osd_y <= R_osd_y + 1; 61 | if(R_ycount == C_y_stop) 62 | begin 63 | osd_yen <= 0; 64 | end 65 | end 66 | else 67 | begin 68 | if(i_blank == 1'b0) // display unblanked 69 | R_xcount_en <= 1'b1; 70 | if(R_xcount_en) 71 | R_xcount <= R_xcount + 1; 72 | if(R_xcount == C_x_start) 73 | begin 74 | osd_xen <= 1; 75 | R_osd_x <= 0; 76 | end 77 | if(osd_xen) 78 | R_osd_x <= R_osd_x + 1; 79 | if(R_xcount == C_x_stop) 80 | begin 81 | osd_xen <= 0; 82 | end 83 | end 84 | R_hsync_prev <= i_hsync; 85 | end 86 | osd_en <= osd_xen & osd_yen; 87 | end 88 | end 89 | 90 | reg [7:0] R_vga_r, R_vga_g, R_vga_b; 91 | reg R_hsync, R_vsync, R_blank; 92 | generate 93 | if(C_transparency) 94 | always @(posedge clk_pixel) 95 | begin 96 | if(clk_pixel_ena) 97 | begin 98 | if(osd_en & i_osd_en) 99 | begin 100 | R_vga_r <= {i_osd_r[7],i_osd_r[6:0]|i_r[7:1]}; 101 | R_vga_g <= {i_osd_g[7],i_osd_g[6:0]|i_g[7:1]}; 102 | R_vga_b <= {i_osd_b[7],i_osd_b[6:0]|i_b[7:1]}; 103 | end 104 | else 105 | begin 106 | R_vga_r <= i_r; 107 | R_vga_g <= i_g; 108 | R_vga_b <= i_b; 109 | end 110 | R_hsync <= i_hsync; 111 | R_vsync <= i_vsync; 112 | R_blank <= i_blank; 113 | end 114 | end 115 | else // C_transparency == 0 116 | always @(posedge clk_pixel) 117 | begin 118 | if(clk_pixel_ena) 119 | begin 120 | if(osd_en & i_osd_en) 121 | begin 122 | R_vga_r <= i_osd_r; 123 | R_vga_g <= i_osd_g; 124 | R_vga_b <= i_osd_b; 125 | end 126 | else 127 | begin 128 | R_vga_r <= i_r; 129 | R_vga_g <= i_g; 130 | R_vga_b <= i_b; 131 | end 132 | R_hsync <= i_hsync; 133 | R_vsync <= i_vsync; 134 | R_blank <= i_blank; 135 | end 136 | end 137 | endgenerate 138 | 139 | assign o_osd_x = R_osd_x; 140 | assign o_osd_y = R_osd_y; 141 | assign o_r = R_vga_r; 142 | assign o_g = R_vga_g; 143 | assign o_b = R_vga_b; 144 | assign o_hsync = R_hsync; 145 | assign o_vsync = R_vsync; 146 | assign o_blank = R_blank; 147 | 148 | endmodule 149 | -------------------------------------------------------------------------------- /osd/spi_osd.v: -------------------------------------------------------------------------------- 1 | // SPI receiver for OSD text window 2 | // VGA video stream pipeline processor 3 | 4 | module spi_osd 5 | #( 6 | parameter [7:0] c_addr_enable = 8'hFE, // high addr byte of enable byte 7 | parameter [7:0] c_addr_display = 8'hFD, // high addr byte of display data, +0x10000 for inverted 8 | parameter c_start_x = 64, // x1 pixel window h-position 9 | parameter c_start_y = 48, // x1 pixel window v-position 10 | parameter c_chars_x = 64, // x8 pixel window h-size 11 | parameter c_chars_y = 24, // x16 pixel window v-size 12 | parameter c_init_on = 1, // 0:default OFF 1:default ON 13 | parameter c_inverse = 1, // 0:no inverse, 1:inverse support 14 | parameter c_transparency = 0, // 1:see-thru OSD menu 0:opaque 15 | parameter [23:0] c_bgcolor = 24'h503020, // RRGGBB menu background color 16 | parameter c_char_file = "osd/osd.mem", // initial window content, 2 ASCII HEX digits per line 17 | parameter c_font_file = "osd/font_bizcat8x16.mem" // font bitmap, 8 ASCII BIN digits per line 18 | ) 19 | ( 20 | input wire clk_pixel, clk_pixel_ena, 21 | input wire [7:0] i_r, 22 | input wire [7:0] i_g, 23 | input wire [7:0] i_b, 24 | input wire i_hsync, i_vsync, i_blank, 25 | input wire i_csn, i_sclk, i_mosi, 26 | inout wire o_miso, 27 | output wire [7:0] o_r, 28 | output wire [7:0] o_g, 29 | output wire [7:0] o_b, 30 | output wire o_hsync, o_vsync, o_blank 31 | ); 32 | 33 | reg [7+c_inverse:0] tile_map [0:c_chars_x*c_chars_y-1]; // tile memory (character map) 34 | initial 35 | $readmemh(c_char_file, tile_map); 36 | 37 | wire ram_wr; 38 | wire [31:0] ram_addr; 39 | wire [7:0] ram_di; 40 | reg [7:0] ram_do = 8'h00; // EP added initializer to surpress warning, this is never used. 41 | 42 | spirw_slave_v 43 | #( 44 | .c_addr_bits(32), 45 | .c_sclk_capable_pin(1'b0) 46 | ) 47 | spirw_slave_inst 48 | ( 49 | .clk(clk_pixel), 50 | .csn(i_csn), 51 | .sclk(i_sclk), 52 | .mosi(i_mosi), 53 | .miso(o_miso), 54 | .wr(ram_wr), 55 | .addr(ram_addr), 56 | .data_in(ram_do), 57 | .data_out(ram_di) 58 | ); 59 | always @(posedge clk_pixel) 60 | begin 61 | if(ram_wr && (ram_addr[31:24] == c_addr_display)) 62 | if(c_inverse) 63 | tile_map[ram_addr] <= {ram_addr[16],ram_di}; // ASCII to 0xFDx0xxxx normal, 0xFDx1xxxx inverted 64 | else 65 | tile_map[ram_addr] <= ram_di; 66 | //ram_do <= tile_map[ram_addr]; 67 | end 68 | 69 | reg osd_en = c_init_on; 70 | always @(posedge clk_pixel) 71 | begin 72 | if(ram_wr && (ram_addr[31:24] == c_addr_enable)) // write to 0xFExxxxxx enables/disables OSD 73 | osd_en <= ram_di[0]; 74 | end 75 | 76 | wire [9:0] osd_x, osd_y; 77 | reg [7:0] font[0:4095]; 78 | initial 79 | $readmemb(c_font_file, font); 80 | reg [7:0] data_out; 81 | wire [11:0] tileaddr = (osd_y >> 4) * c_chars_x + (osd_x >> 3); 82 | generate 83 | if(c_inverse) 84 | always @(posedge clk_pixel) 85 | data_out[7:0] <= font[{tile_map[tileaddr], osd_y[3:0]}] ^ {8{tile_map[tileaddr][8]}}; 86 | else 87 | always @(posedge clk_pixel) 88 | data_out[7:0] <= font[{tile_map[tileaddr], osd_y[3:0]}]; 89 | endgenerate 90 | wire [7:0] data_out_align = {data_out[0], data_out[7:1]}; 91 | wire osd_pixel = data_out_align[7-osd_x[2:0]]; 92 | 93 | wire [7:0] osd_r = osd_pixel ? 8'hff : c_bgcolor[23:16]; 94 | wire [7:0] osd_g = osd_pixel ? 8'hff : c_bgcolor[15:8]; 95 | wire [7:0] osd_b = osd_pixel ? 8'hff : c_bgcolor[7:0]; 96 | 97 | // OSD overlay 98 | osd 99 | #( 100 | .C_x_start(c_start_x), 101 | .C_x_stop (c_start_x+8*c_chars_x-1), 102 | .C_y_start(c_start_y), 103 | .C_y_stop (c_start_y+16*c_chars_y-1), 104 | .C_transparency(c_transparency) 105 | ) 106 | osd_instance 107 | ( 108 | .clk_pixel(clk_pixel), 109 | .clk_pixel_ena(1'b1), 110 | .i_r(i_r), 111 | .i_g(i_g), 112 | .i_b(i_b), 113 | .i_hsync(i_hsync), 114 | .i_vsync(i_vsync), 115 | .i_blank(i_blank), 116 | .i_osd_en(osd_en), 117 | .o_osd_x(osd_x), 118 | .o_osd_y(osd_y), 119 | .i_osd_r(osd_r), 120 | .i_osd_g(osd_g), 121 | .i_osd_b(osd_b), 122 | .o_r(o_r), 123 | .o_g(o_g), 124 | .o_b(o_b), 125 | .o_hsync(o_hsync), 126 | .o_vsync(o_vsync), 127 | .o_blank(o_blank) 128 | ); 129 | 130 | endmodule 131 | -------------------------------------------------------------------------------- /osd/spi_ram_btn.v: -------------------------------------------------------------------------------- 1 | // SPI RAM RW slave with BTN IRQ 2 | 3 | // AUTHOR=EMARD 4 | // LICENSE=BSD 5 | 6 | // 0xFBxxxxxx: {irq,btn[6:0]} 7 | // others : SPI RAM 8 | 9 | // read with dummy byte which should be discarded 10 | 11 | // write 00 12 | // read 01 13 | 14 | module spi_ram_btn 15 | #( 16 | parameter [7:0] c_addr_btn = 8'hFB, // high addr byte of BTNs 17 | parameter [7:0] c_addr_irq = 8'hF1, // high addr byte of IRQ flag 18 | parameter c_debounce_bits = 20, // more -> slower BTNs 19 | parameter c_addr_bits = 32, // don't touch 20 | parameter c_sclk_capable_pin = 0 //, // 0-sclk is generic pin, 1-sclk is clock capable pin 21 | ) 22 | ( 23 | input wire clk, // faster than SPI clock 24 | input wire csn, sclk, mosi, // SPI lines to be sniffed 25 | inout wire miso, // 3-state line, active when csn=0 26 | // BTNs 27 | input wire [6:0] btn, 28 | output wire irq, 29 | // BRAM interface 30 | output wire rd, wr, 31 | output wire [c_addr_bits-1:0] addr, 32 | input wire [7:0] data_in, 33 | output wire [7:0] data_out, 34 | // F1 key pressed or not 35 | input wire f1_pressed, 36 | input wire [3:0] cursor_keys_pressed 37 | ); 38 | 39 | // IRQ controller tracks BTN state 40 | reg R_btn_irq; 41 | reg [6:0] R_btn, R_btn_latch; 42 | reg R_spi_rd; 43 | reg [c_debounce_bits-1:0] R_btn_debounce; 44 | always @(posedge clk) 45 | begin 46 | R_spi_rd <= rd; 47 | if(rd == 1'b0 && R_spi_rd == 1'b1 && addr[c_addr_bits-1:c_addr_bits-8] == c_addr_irq) 48 | R_btn_irq <= 1'b0; 49 | else // BTN state is read from 0xFBxxxxxx 50 | begin 51 | R_btn_latch <= f1_pressed ? 52 | 7'h78 : // F1 pressed = all directional buttons pressed 53 | (btn | { cursor_keys_pressed, 3'b000 }); // otherwise either on-board buttons or PS2 KBD cursor keys 54 | if(R_btn != R_btn_latch && R_btn_debounce[$bits(R_btn_debounce)-1] == 1 && R_btn_irq == 0) 55 | begin 56 | R_btn_irq <= 1'b1; 57 | R_btn_debounce <= 0; 58 | R_btn <= R_btn_latch; 59 | end 60 | else 61 | if(R_btn_debounce[$bits(R_btn_debounce)-1] == 1'b0) 62 | R_btn_debounce <= R_btn_debounce + 1; 63 | end 64 | end 65 | 66 | wire [7:0] mux_data_in = addr[c_addr_bits-1:c_addr_bits-8] == c_addr_irq ? {R_btn_irq,7'b0} 67 | : addr[c_addr_bits-1:c_addr_bits-8] == c_addr_btn ? {1'b0,R_btn} 68 | : data_in; 69 | assign irq = R_btn_irq; 70 | 71 | spirw_slave_v 72 | #( 73 | .c_sclk_capable_pin(c_sclk_capable_pin), 74 | .c_addr_bits(c_addr_bits) 75 | ) 76 | spirw_slave_v_inst 77 | ( 78 | .clk(clk), 79 | .csn(csn), 80 | .sclk(sclk), 81 | .mosi(mosi), 82 | .miso(miso), 83 | .wr(wr), 84 | .rd(rd), 85 | .addr(addr), 86 | .data_in(mux_data_in), 87 | .data_out(data_out) 88 | ); 89 | 90 | endmodule 91 | -------------------------------------------------------------------------------- /osd/spirw_slave_v.v: -------------------------------------------------------------------------------- 1 | // SPI RW slave 2 | 3 | // AUTHOR=EMARD 4 | // LICENSE=BSD 5 | 6 | // read with dummy byte which should be discarded 7 | 8 | // write 00 9 | // read 01 10 | 11 | module spirw_slave_v 12 | #( 13 | parameter c_addr_bits = 16, 14 | parameter c_sclk_capable_pin = 0 //, // 0-sclk is generic pin, 1-sclk is clock capable pin 15 | ) 16 | ( 17 | input wire clk, // faster than SPI clock 18 | input wire csn, sclk, mosi, // SPI lines to be sniffed 19 | inout wire miso, // 3-state line, active when csn=0 20 | // BRAM interface 21 | output wire rd, wr, 22 | output wire [c_addr_bits-1:0] addr, 23 | input wire [7:0] data_in, 24 | output wire [7:0] data_out 25 | ); 26 | reg [c_addr_bits:0] R_raddr; 27 | //reg [8-1:0] R_MISO; 28 | //reg [8-1:0] R_MOSI; 29 | reg [8-1:0] R_byte; 30 | reg R_request_read; 31 | reg R_request_write; 32 | localparam c_count_bits=$clog2(c_addr_bits+8)+1; 33 | reg [c_count_bits-1:0] R_bit_count; 34 | generate 35 | if(c_sclk_capable_pin) 36 | begin 37 | // sclk is clock capable pin 38 | always @(posedge sclk or posedge csn) 39 | begin 40 | if(csn) 41 | begin 42 | R_request_read <= 1'b0; 43 | R_request_write <= 1'b0; 44 | R_bit_count <= c_addr_bits+7; // 24 bits = 3 bytes to read cmd/addr, then data 45 | end 46 | else // csn == 0 47 | begin 48 | if(R_request_read) 49 | R_byte <= data_in; 50 | else 51 | R_byte <= { R_byte[8-2:0], mosi }; 52 | if(R_bit_count[c_count_bits-1] == 1'b0) // first 3 bytes 53 | begin 54 | R_raddr <= { R_raddr[c_addr_bits-1:0], mosi }; 55 | R_bit_count <= R_bit_count - 1; 56 | end 57 | else // after first 3 bytes 58 | begin 59 | if(R_bit_count[3:0] == 4'd7) // first bit in new byte, increment address from 5th SPI byte on 60 | R_raddr[c_addr_bits-1:0] <= R_raddr[c_addr_bits-1:0] + 1; 61 | if(R_bit_count[2:0] == 3'd1) 62 | R_request_read <= R_raddr[c_addr_bits]; 63 | else 64 | R_request_read <= 1'b0; 65 | if(R_bit_count[2:0] == 3'd0) // last bit in byte 66 | begin 67 | if(R_raddr[c_addr_bits] == 1'b0) 68 | R_request_write <= 1'b1; // write 69 | R_bit_count[3] <= 1'b0; // allow to inc address from 5th SPI byte on 70 | end 71 | else 72 | R_request_write <= 1'b0; 73 | R_bit_count[2:0] <= R_bit_count[2:0] - 1; 74 | end // after 1st 3 bytes 75 | end // csn = 0, rising sclk edge 76 | end // always 77 | end // generate 78 | else // sclk is not CLK capable pin 79 | begin 80 | // sclk is generic pin 81 | // it needs clock synchronous edge detection 82 | reg R_mosi; 83 | reg [1:0] R_sclk; 84 | always @(posedge clk) 85 | begin 86 | R_sclk <= {R_sclk[0], sclk}; 87 | R_mosi <= mosi; 88 | end 89 | always @(posedge clk) 90 | begin 91 | if(csn) 92 | begin 93 | R_request_read <= 1'b0; 94 | R_request_write <= 1'b0; 95 | R_bit_count <= c_addr_bits+7; // 24 bits = 3 bytes to read cmd/addr, then data 96 | end 97 | else // csn == 0 98 | begin 99 | if(R_sclk == 2'b01) // rising edge 100 | begin 101 | if(R_request_read) 102 | R_byte <= data_in; 103 | else 104 | R_byte <= { R_byte[8-2:0], mosi }; 105 | if(R_bit_count[c_count_bits-1] == 1'b0) // first 3 bytes 106 | begin 107 | R_raddr <= { R_raddr[c_addr_bits-1:0], R_mosi }; 108 | R_bit_count <= R_bit_count - 1; 109 | end 110 | else // after first 3 bytes 111 | begin 112 | if(R_bit_count[3:0] == 4'd7) // first bit in new byte, increment address from 5th SPI byte on 113 | R_raddr[c_addr_bits-1:0] <= R_raddr[c_addr_bits-1:0] + 1; 114 | if(R_bit_count[2:0] == 3'd1) 115 | R_request_read <= R_raddr[c_addr_bits]; 116 | else 117 | R_request_read <= 1'b0; 118 | if(R_bit_count[2:0] == 3'd0) // last bit in byte 119 | begin 120 | if(R_raddr[c_addr_bits] == 1'b0) 121 | R_request_write <= 1'b1; // write 122 | R_bit_count[3] <= 1'b0; // allow to inc address from 5th SPI byte on 123 | end 124 | else 125 | R_request_write <= 1'b0; 126 | R_bit_count[2:0] <= R_bit_count[2:0] - 1; 127 | end // after 1st 3 bytes 128 | end // sclk rising edge 129 | end // csn=0 130 | end // always 131 | end // generate 132 | endgenerate 133 | assign rd = R_request_read; 134 | assign wr = R_request_write; 135 | assign addr = R_raddr[c_addr_bits-1:0]; 136 | assign miso = csn ? 1'bz : R_byte[8-1]; 137 | assign data_out = R_byte; 138 | endmodule 139 | -------------------------------------------------------------------------------- /rom16.v: -------------------------------------------------------------------------------- 1 | // rom16.v 2 | // EP (C) 2019-11-19 3 | // A simple ROM with init file support. 4 | 5 | module rom16 6 | #(parameter WIDTH=8, // 9 bits of data width 7 | parameter DEPTH=9, 8 | parameter RANGE=512, 9 | parameter INITFILE="roms/zeros256.mem") // 9 bits of address 10 | ( 11 | input clk, 12 | input [DEPTH-1:0] addr, 13 | output reg [WIDTH-1:0] dout, 14 | ); 15 | 16 | reg [WIDTH-1:0] rom [0:RANGE-1]; 17 | 18 | always @(posedge clk) 19 | begin 20 | dout <= rom[addr]; 21 | end 22 | 23 | initial begin 24 | $readmemh(INITFILE, rom); 25 | end 26 | endmodule 27 | -------------------------------------------------------------------------------- /roms/lblacart.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Speccery/icy99/512915b98e8088e6a23d63ffd55bed9a84c25634/roms/lblacart.bin -------------------------------------------------------------------------------- /roms/tipi.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Speccery/icy99/512915b98e8088e6a23d63ffd55bed9a84c25634/roms/tipi.bin -------------------------------------------------------------------------------- /roms/zeros256.mem: -------------------------------------------------------------------------------- 1 | 0000 // address 0x0000 2 | 0000 // address 0x0002 3 | 0000 // address 0x0004 4 | 0000 // address 0x0006 5 | 0000 // address 0x0008 6 | 0000 // address 0x000A 7 | 0000 // address 0x000C 8 | 0000 // address 0x000E 9 | 0000 // address 0x0010 10 | 0000 // address 0x0012 11 | 0000 // address 0x0014 12 | 0000 // address 0x0016 13 | 0000 // address 0x0018 14 | 0000 // address 0x001A 15 | 0000 // address 0x001C 16 | 0000 // address 0x001E 17 | 0000 // address 0x0020 18 | 0000 // address 0x0022 19 | 0000 // address 0x0024 20 | 0000 // address 0x0026 21 | 0000 // address 0x0028 22 | 0000 // address 0x002A 23 | 0000 // address 0x002C 24 | 0000 // address 0x002E 25 | 0000 // address 0x0030 26 | 0000 // address 0x0032 27 | 0000 // address 0x0034 28 | 0000 // address 0x0036 29 | 0000 // address 0x0038 30 | 0000 // address 0x003A 31 | 0000 // address 0x003C 32 | 0000 // address 0x003E 33 | 0000 // address 0x0040 34 | 0000 // address 0x0042 35 | 0000 // address 0x0044 36 | 0000 // address 0x0046 37 | 0000 // address 0x0048 38 | 0000 // address 0x004A 39 | 0000 // address 0x004C 40 | 0000 // address 0x004E 41 | 0000 // address 0x0050 42 | 0000 // address 0x0052 43 | 0000 // address 0x0054 44 | 0000 // address 0x0056 45 | 0000 // address 0x0058 46 | 0000 // address 0x005A 47 | 0000 // address 0x005C 48 | 0000 // address 0x005E 49 | 0000 // address 0x0060 50 | 0000 // address 0x0062 51 | 0000 // address 0x0064 52 | 0000 // address 0x0066 53 | 0000 // address 0x0068 54 | 0000 // address 0x006A 55 | 0000 // address 0x006C 56 | 0000 // address 0x006E 57 | 0000 // address 0x0070 58 | 0000 // address 0x0072 59 | 0000 // address 0x0074 60 | 0000 // address 0x0076 61 | 0000 // address 0x0078 62 | 0000 // address 0x007A 63 | 0000 // address 0x007C 64 | 0000 // address 0x007E 65 | 0000 // address 0x0080 66 | 0000 // address 0x0082 67 | 0000 // address 0x0084 68 | 0000 // address 0x0086 69 | 0000 // address 0x0088 70 | 0000 // address 0x008A 71 | 0000 // address 0x008C 72 | 0000 // address 0x008E 73 | 0000 // address 0x0090 74 | 0000 // address 0x0092 75 | 0000 // address 0x0094 76 | 0000 // address 0x0096 77 | 0000 // address 0x0098 78 | 0000 // address 0x009A 79 | 0000 // address 0x009C 80 | 0000 // address 0x009E 81 | 0000 // address 0x00A0 82 | 0000 // address 0x00A2 83 | 0000 // address 0x00A4 84 | 0000 // address 0x00A6 85 | 0000 // address 0x00A8 86 | 0000 // address 0x00AA 87 | 0000 // address 0x00AC 88 | 0000 // address 0x00AE 89 | 0000 // address 0x00B0 90 | 0000 // address 0x00B2 91 | 0000 // address 0x00B4 92 | 0000 // address 0x00B6 93 | 0000 // address 0x00B8 94 | 0000 // address 0x00BA 95 | 0000 // address 0x00BC 96 | 0000 // address 0x00BE 97 | 0000 // address 0x00C0 98 | 0000 // address 0x00C2 99 | 0000 // address 0x00C4 100 | 0000 // address 0x00C6 101 | 0000 // address 0x00C8 102 | 0000 // address 0x00CA 103 | 0000 // address 0x00CC 104 | 0000 // address 0x00CE 105 | 0000 // address 0x00D0 106 | 0000 // address 0x00D2 107 | 0000 // address 0x00D4 108 | 0000 // address 0x00D6 109 | 0000 // address 0x00D8 110 | 0000 // address 0x00DA 111 | 0000 // address 0x00DC 112 | 0000 // address 0x00DE 113 | 0000 // address 0x00E0 114 | 0000 // address 0x00E2 115 | 0000 // address 0x00E4 116 | 0000 // address 0x00E6 117 | 0000 // address 0x00E8 118 | 0000 // address 0x00EA 119 | 0000 // address 0x00EC 120 | 0000 // address 0x00EE 121 | 0000 // address 0x00F0 122 | 0000 // address 0x00F2 123 | 0000 // address 0x00F4 124 | 0000 // address 0x00F6 125 | 0000 // address 0x00F8 126 | 0000 // address 0x00FA 127 | 0000 // address 0x00FC 128 | 0000 // address 0x00FE 129 | -------------------------------------------------------------------------------- /snippets.py: -------------------------------------------------------------------------------- 1 | # python snippets for ESP32 2 | # EP 2020-10-16 copied here 3 | 4 | f=open("main.py","w") 5 | f.write("import network\n") 6 | f.write("sta_if = network.WLAN(network.STA_IF)\n") 7 | f.write("sta_if.active(True)\n") 8 | f.write('sta_if.connect("EP300N", "hulabaloo39")\n') 9 | f.write("import uftpd\n") 10 | f.close() 11 | 12 | 13 | 14 | f=open("main.py") 15 | k=f.readlines() 16 | k 17 | f.close() 18 | 19 | 20 | 21 | 22 | import os 23 | os.listdir() 24 | 25 | from machine import SPI, Pin, SDCard, Timer 26 | os.mount(SDCard(slot=3),"/sd") 27 | os.listdir("/sd") 28 | -------------------------------------------------------------------------------- /src/alu9900.v: -------------------------------------------------------------------------------- 1 | // alu9900.v 2 | // EP 2019-02-07 3 | // Conversion of VHDL ALU code to verilog. 4 | // I put this into a separate module to test how to port VHDL code to verilog 5 | // in a little smaller units. 6 | // This module is purely combinatorial logic. 7 | module alu9900( 8 | input [16:0] arg1, // 17-bit input to support DIV steps 9 | input [15:0] arg2, 10 | input [3:0] ope, 11 | input compare, // for compare, set this to 1 and ope to sub. 12 | output [15:0] alu_result, 13 | output alu_logical_gt, 14 | output alu_arithmetic_gt, 15 | output alu_flag_zero, 16 | output alu_flag_carry, 17 | output alu_flag_overflow, 18 | output alu_flag_parity, 19 | output alu_flag_parity_source 20 | ); 21 | localparam load1=4'h0, load2=4'h1, add =4'h2, sub =4'h3, 22 | abs =4'h4, aor =4'h5, aand=4'h6, axor=4'h7, 23 | andn =4'h8, coc =4'h9, czc =4'ha, swpb=4'hb, 24 | sla =4'hc, sra =4'hd, src =4'he, srl =4'hf; 25 | 26 | wire [16:0] alu_out; 27 | 28 | // arg1 is DA, arg2 is SA when ALU used for instruction execute 29 | assign alu_out = 30 | (ope == load1) ? arg1 : 31 | (ope == load2) ? { 1'b0, arg2 } : 32 | (ope == add) ? arg1 + { 1'b0, arg2 } : 33 | (ope == sub) ? arg1 - { 1'b0, arg2 } : 34 | (ope == aor) ? arg1 | { 1'b0, arg2 } : 35 | (ope == aand) ? arg1 & { 1'b0, arg2 } : 36 | (ope == axor) ? arg1 ^ { 1'b0, arg2 } : 37 | (ope == andn) ? arg1 & { 1'b0, ~arg2 } : 38 | (ope == coc) ? (arg1 ^ { 1'b0, arg2 }) & arg1 : // compare ones corresponding 39 | (ope == czc) ? (arg1 ^ { 1'b0, ~arg2 }) & arg1 : // compare zeros corresponding 40 | (ope == swpb) ? { 1'b0, arg2[7:0], arg2[15:8] }: // swap bytes of arg2 41 | (ope == abs) ? (arg2[15] ? arg1 - { 1'b0, arg2 } : { 1'b0, arg2 }) : 42 | (ope == sla) ? { arg2, 1'b0 } : 43 | (ope == sra) ? { arg2[0], arg2[15], arg2[15:1] } : 44 | (ope == src) ? { arg2[0], arg2[0], arg2[15:1] } : 45 | { arg2[0], 1'b0, arg2[15:1] }; // srl 46 | 47 | assign alu_result = alu_out[15:0]; 48 | // ST0 ST1 ST2 ST3 ST4 ST5 49 | // L> A> = C O P 50 | // ST0 - when looking at data sheet arg1 is (DA) and arg2 is (SA), sub is (DA)-(SA). 51 | assign alu_logical_gt = compare ? (arg2[15] && !arg1[15]) || (arg1[15]==arg2[15] && alu_result[15]) 52 | : alu_result != 16'd0; 53 | 54 | // ST1 55 | assign alu_arithmetic_gt = compare ? (!arg2[15] && arg1[15]) || (arg1[15]==arg2[15] && alu_result[15]) 56 | : (ope == abs) ? arg2[15] == 1'b0 && arg2 != 16'd0 57 | : alu_result[15]==1'b0 && alu_result != 16'd0; 58 | // ST2 59 | assign alu_flag_zero = !(|alu_result); 60 | // ST3 61 | assign alu_flag_carry = (ope == sub) ? !alu_out[16] : alu_out[16]; // for sub carry out is inverted 62 | // ST4 overflow 63 | assign alu_flag_overflow = (ope == sla) ? alu_result[15] != arg2[15] : // sla condition: if MSB changes during shift 64 | (compare || ope==sub || ope==abs) ? (arg1[15] != arg2[15] && alu_result[15] != arg1[15]) : 65 | (arg1[15] == arg2[15] && alu_result[15] != arg1[15]); 66 | // ST5 parity 67 | assign alu_flag_parity = ^alu_result[15:8]; 68 | // source parity used with CB and MOVB instructions 69 | assign alu_flag_parity_source = ^arg2[15:8]; 70 | 71 | endmodule 72 | -------------------------------------------------------------------------------- /src/dualport_par.v: -------------------------------------------------------------------------------- 1 | // dualport_par.v 2 | // EP (C) 2019-11-02 3 | // Simple dual port memory with separate read and write ports and configurable depth and width. 4 | // For example a simple 1K 8-bit wide RAM: 5 | // dualport_par #(8, 10) ram1k (...) 6 | // 7 | module dualport_par 8 | #(parameter WIDTH=9, // 9 bits of data width 9 | parameter DEPTH=9) // 9 bits of address 10 | ( 11 | // Port A 12 | input clk_a, 13 | input we_a, 14 | input [DEPTH-1:0] addr_a, 15 | input [WIDTH-1:0] din_a, 16 | // Port B 17 | input clk_b, 18 | input [DEPTH-1:0] addr_b, 19 | output reg [WIDTH-1:0] dout_b 20 | ); 21 | 22 | parameter RAM_RANGE = 1 << DEPTH; 23 | reg [WIDTH-1:0] ram [0:RAM_RANGE-1]; 24 | 25 | always @(posedge clk_a) 26 | begin 27 | if (we_a) 28 | ram[addr_a] <= din_a; 29 | end 30 | 31 | always @(posedge clk_b) 32 | begin 33 | dout_b <= ram[addr_b]; 34 | end 35 | 36 | endmodule 37 | -------------------------------------------------------------------------------- /src/dvi.v: -------------------------------------------------------------------------------- 1 | // module port 2 | // 3 | module DVI_out 4 | #( 5 | parameter generic_ddr = 1, 6 | parameter ecp5_ddr = 0 7 | ) 8 | ( 9 | input wire pixclk, 10 | input wire pixclk_x5, 11 | input wire [7:0] red, green, blue, 12 | input wire vde, hSync, vSync, 13 | output wire [1:0] tmds_c, tmds_r, tmds_g, tmds_b 14 | ); 15 | 16 | // 10b8b TMDS encoding of RGB and Sync 17 | // 18 | wire [9:0] TMDS_red, TMDS_green, TMDS_blue; 19 | TMDS_encoder encode_R(.clk(pixclk), .VD(red ), .CD(2'b00) , .VDE(vde), .TMDS(TMDS_red)); 20 | TMDS_encoder encode_G(.clk(pixclk), .VD(green), .CD(2'b00) , .VDE(vde), .TMDS(TMDS_green)); 21 | TMDS_encoder encode_B(.clk(pixclk), .VD(blue ), .CD({vSync,hSync}), .VDE(vde), .TMDS(TMDS_blue)); 22 | 23 | // shift out 10 bits each pix clock (2 DDR bits at a 5x rate) 24 | // 25 | reg [2:0] ctr_mod5 = 0; 26 | reg shift_ld = 0; 27 | 28 | always @(posedge pixclk_x5) 29 | begin 30 | shift_ld <= (ctr_mod5==4'd4); 31 | ctr_mod5 <= (ctr_mod5==4'd4) ? 4'd0 : ctr_mod5 + 4'd1; 32 | end 33 | 34 | reg [9:0] shift_R = 0, shift_G = 0, shift_B = 0, shift_C = 0; 35 | 36 | always @(posedge pixclk_x5) 37 | begin 38 | shift_R <= shift_ld ? TMDS_red : shift_R[9:2]; 39 | shift_G <= shift_ld ? TMDS_green : shift_G[9:2]; 40 | shift_B <= shift_ld ? TMDS_blue : shift_B[9:2]; 41 | shift_C <= shift_ld ? 10'h3e0 : shift_C[9:2]; 42 | end 43 | 44 | assign tmds_c = shift_C[1:0]; 45 | assign tmds_r = shift_R[1:0]; 46 | assign tmds_g = shift_G[1:0]; 47 | assign tmds_b = shift_B[1:0]; 48 | endmodule 49 | 50 | 51 | // DVI-D 10b8b TMDS encoder module 52 | // 53 | module TMDS_encoder( 54 | input clk, // pix clock 55 | input [7:0] VD, // video data (red, green or blue) 56 | input [1:0] CD, // control data 57 | input VDE, // video data enable, to choose between CD (when VDE=0) and VD (when VDE=1) 58 | output reg [9:0] TMDS = 0 59 | ); 60 | 61 | genvar i; 62 | reg [3:0] dc_bias = 0; 63 | 64 | // compute data word 65 | wire [3:0] ones = VD[0] + VD[1] + VD[2] + VD[3] + VD[4] + VD[5] + VD[6] + VD[7]; 66 | wire XNOR = (ones>4'd4) || (ones==4'd4 && VD[0]==1'b0); 67 | 68 | // Replace the following with the generator below to avoid signal loop warnings 69 | // from Yosys. 70 | // wire [8:0] dw = { ~XNOR, dw[6:0] ^ VD[7:1] ^ {7{XNOR}}, VD[0] }; 71 | 72 | wire [8:0] dw; 73 | assign dw[8] = ~XNOR; 74 | for(i=1; i<=7; i++) assign dw[i] = dw[i-1] ^ VD[i] ^ XNOR; 75 | assign dw[0] = VD[0]; 76 | 77 | 78 | // calculate 1/0 disparity & invert as needed to minimize dc bias 79 | wire [3:0] dw_disp = dw[0] + dw[1] + dw[2] + dw[3] + dw[4] + dw[5] + dw[6] + dw[7] + 4'b1100; 80 | wire sign_eq = (dw_disp[3] == dc_bias[3]); 81 | 82 | wire [3:0] delta = dw_disp - ({dw[8] ^ ~sign_eq} & ~(dw_disp==0 || dc_bias==0)); 83 | wire inv_dw = (dw_disp==0 || dc_bias==0) ? ~dw[8] : sign_eq; 84 | 85 | wire [3:0] dc_bias_d = inv_dw ? dc_bias - delta : dc_bias + delta; 86 | 87 | // set output signals 88 | wire [9:0] TMDS_data = { inv_dw, dw[8], dw[7:0] ^ {8{inv_dw}} }; 89 | wire [9:0] TMDS_code = CD[1] ? (CD[0] ? 10'b1010101011 : 10'b0101010100) 90 | : (CD[0] ? 10'b0010101011 : 10'b1101010100); 91 | 92 | always @(posedge clk) TMDS <= VDE ? TMDS_data : TMDS_code; 93 | always @(posedge clk) dc_bias <= VDE ? dc_bias_d : 0; 94 | 95 | endmodule -------------------------------------------------------------------------------- /src/ecp5pll.sv: -------------------------------------------------------------------------------- 1 | // (c)EMARD 2 | // License=BSD 3 | 4 | // parametric ECP5 PLL generator in systemverilog 5 | // actual frequency can be equal or higher than requested 6 | // to see actual frequencies 7 | // trellis log/stdout : search for "MHz", "Derived", "frequency" 8 | // to see actual phase shifts 9 | // diamond log/*.mrp : search for "Phase", "Desired" 10 | 11 | module ecp5pll 12 | #( 13 | parameter integer in_hz = 25000000, 14 | parameter integer out0_hz = 25000000, 15 | parameter integer out0_deg = 0, // keep 0 16 | parameter integer out0_tol_hz= 0, // tolerance: if freq differs more, then error 17 | parameter integer out1_hz = 25000000, 18 | parameter integer out1_deg = 0, 19 | parameter integer out1_tol_hz= 0, 20 | parameter integer out2_hz = 25000000, 21 | parameter integer out2_deg = 0, 22 | parameter integer out2_tol_hz= 0, 23 | parameter integer out3_hz = 25000000, 24 | parameter integer out3_deg = 0, 25 | parameter integer out3_tol_hz= 0, 26 | parameter integer reset_en = 0, 27 | parameter integer standby_en = 0, 28 | parameter integer dynamic_en = 0 29 | ) 30 | ( 31 | input clk_i, 32 | output [3:0] clk_o, 33 | input reset, 34 | input standby, 35 | input [1:0] phasesel, 36 | input phasedir, phasestep, phaseloadreg, 37 | output locked 38 | ); 39 | 40 | localparam PFD_MIN = 3125000; 41 | localparam PFD_MAX = 400000000; 42 | localparam VCO_MIN = 400000000; 43 | localparam VCO_MAX = 800000000; 44 | localparam VCO_OPTIMAL = (VCO_MIN+VCO_MAX)/2; 45 | 46 | function integer abs(input integer x); 47 | abs = x > 0 ? x : -x; 48 | endfunction 49 | 50 | function integer F_ecp5pll(input integer x); 51 | integer input_div, input_div_min, input_div_max; 52 | integer output_div, output_div_min, output_div_max; 53 | integer feedback_div, feedback_div_min, feedback_div_max; 54 | integer fvco, fout; 55 | integer error, error_prev; 56 | integer params_fvco; 57 | integer div1, div2, div3; 58 | 59 | integer params_refclk_div; 60 | integer params_feedback_div; 61 | integer params_output_div; 62 | 63 | params_fvco = 0; 64 | error_prev = 999999999; 65 | input_div_min = in_hz/PFD_MAX; 66 | if(input_div_min < 1) 67 | input_div_min = 1; 68 | input_div_max = in_hz/PFD_MIN; 69 | if(input_div_max > 128) 70 | input_div_max = 128; 71 | for(input_div = input_div_min; input_div <= input_div_max; input_div=input_div+1) 72 | begin 73 | if(out0_hz / 1000000 * input_div < 2000) 74 | feedback_div = out0_hz * input_div / in_hz; 75 | else 76 | feedback_div = out0_hz / in_hz * input_div; 77 | feedback_div_min = feedback_div; 78 | feedback_div_max = feedback_div+1; 79 | if(feedback_div_min < 1) 80 | feedback_div_min = 1; 81 | if(feedback_div_max > 80) 82 | feedback_div_max = 80; 83 | for(feedback_div = feedback_div_min; feedback_div <= feedback_div_max; feedback_div = feedback_div+1) 84 | begin 85 | output_div_min = (VCO_MIN/feedback_div) / (in_hz/input_div); 86 | if(output_div_min < 1) 87 | output_div_min = 1; 88 | output_div_max = (VCO_MAX/feedback_div) / (in_hz/input_div); 89 | if(output_div_max > 128) 90 | output_div_max = 128; 91 | fout = in_hz * feedback_div / input_div; 92 | for(output_div = output_div_min; output_div <= output_div_max; output_div=output_div+1) 93 | begin 94 | fvco = fout * output_div; 95 | error = abs(fout-out0_hz) 96 | + (out1_hz > 0 ? abs(fvco/(fvco >= out1_hz ? fvco/out1_hz : 1)-out1_hz) : 0) 97 | + (out2_hz > 0 ? abs(fvco/(fvco >= out2_hz ? fvco/out2_hz : 1)-out2_hz) : 0) 98 | + (out3_hz > 0 ? abs(fvco/(fvco >= out3_hz ? fvco/out3_hz : 1)-out3_hz) : 0); 99 | if( error < error_prev 100 | || (error == error_prev && abs(fvco-VCO_OPTIMAL) < abs(params_fvco-VCO_OPTIMAL)) ) 101 | begin 102 | error_prev = error; 103 | params_refclk_div = input_div; 104 | params_feedback_div = feedback_div; 105 | params_output_div = output_div; 106 | params_fvco = fvco; 107 | end 108 | end 109 | end 110 | end 111 | // FIXME in the future when yosys supports struct 112 | if(x==0) 113 | F_ecp5pll = params_refclk_div; 114 | if(x==1) 115 | F_ecp5pll = params_feedback_div; 116 | if(x==2) 117 | F_ecp5pll = params_output_div; 118 | endfunction 119 | 120 | function integer F_primary_phase(input integer output_div, deg); 121 | integer phase_compensation; 122 | integer phase_count_x8; 123 | 124 | phase_compensation = (output_div+1)/2*8-8+output_div/2*8; // output_div/2*8 = 180 deg shift 125 | phase_count_x8 = phase_compensation + 8*output_div*deg/360; 126 | if(phase_count_x8 > 1023) 127 | phase_count_x8 = phase_count_x8 % (output_div*8); // wraparound 360 deg 128 | F_primary_phase = phase_count_x8; 129 | endfunction 130 | 131 | // FIXME it is inefficient to call F_ecp5pll multiple times 132 | localparam params_refclk_div = F_ecp5pll(0); 133 | localparam params_feedback_div = F_ecp5pll(1); 134 | localparam params_output_div = F_ecp5pll(2); 135 | localparam params_fout = in_hz * params_feedback_div / params_refclk_div; 136 | localparam params_fvco = params_fout * params_output_div; 137 | 138 | localparam params_primary_phase_x8 = F_ecp5pll(3); 139 | localparam params_primary_cphase = F_primary_phase(params_output_div, out0_deg) / 8; 140 | localparam params_primary_fphase = F_primary_phase(params_output_div, out0_deg) % 8; 141 | 142 | function integer F_secondary_divisor(input integer sfreq); 143 | F_secondary_divisor = 1; 144 | if(sfreq > 0) 145 | if(params_fvco >= sfreq) 146 | F_secondary_divisor = params_fvco/sfreq; 147 | endfunction 148 | 149 | function integer F_secondary_phase(input integer sfreq, sphase); 150 | integer div, freq; 151 | integer phase_compensation, phase_count_x8; 152 | 153 | phase_count_x8 = 0; 154 | if(sfreq > 0) 155 | begin 156 | div = 1; 157 | if(params_fvco >= sfreq) 158 | div = params_fvco/sfreq; 159 | freq = params_fvco/div; 160 | phase_compensation = div*8-8; 161 | phase_count_x8 = phase_compensation + 8*div*sphase/360; 162 | if(phase_count_x8 > 1023) 163 | phase_count_x8 = phase_count_x8 % (div*8); // wraparound 360 deg 164 | end 165 | 166 | F_secondary_phase = phase_count_x8; 167 | endfunction 168 | 169 | localparam params_secondary1_div = F_secondary_divisor(out1_hz); 170 | localparam params_secondary1_cphase = F_secondary_phase (out1_hz, out1_deg) / 8; 171 | localparam params_secondary1_fphase = F_secondary_phase (out1_hz, out1_deg) % 8; 172 | localparam params_secondary2_div = F_secondary_divisor(out2_hz); 173 | localparam params_secondary2_cphase = F_secondary_phase (out2_hz, out2_deg) / 8; 174 | localparam params_secondary2_fphase = F_secondary_phase (out2_hz, out2_deg) % 8; 175 | localparam params_secondary3_div = F_secondary_divisor(out3_hz); 176 | localparam params_secondary3_cphase = F_secondary_phase (out3_hz, out3_deg) / 8; 177 | localparam params_secondary3_fphase = F_secondary_phase (out3_hz, out3_deg) % 8; 178 | 179 | // check if generated frequencies are out of range 180 | localparam error_out0_hz = abs(out0_hz - params_fout) > out0_tol_hz; 181 | localparam error_out1_hz = out1_hz > 0 ? abs(out1_hz - params_fvco / params_secondary1_div) > out1_tol_hz : 0; 182 | localparam error_out2_hz = out2_hz > 0 ? abs(out2_hz - params_fvco / params_secondary2_div) > out2_tol_hz : 0; 183 | localparam error_out3_hz = out3_hz > 0 ? abs(out3_hz - params_fvco / params_secondary3_div) > out3_tol_hz : 0; 184 | // diamond: won't compile this, comment it out. Workaround follows using division by zero 185 | /* 186 | if(error_out0_hz) $error("out0_hz tolerance exceeds out0_tol_hz"); 187 | if(error_out1_hz) $error("out1_hz tolerance exceeds out1_tol_hz"); 188 | if(error_out2_hz) $error("out2_hz tolerance exceeds out2_tol_hz"); 189 | if(error_out3_hz) $error("out3_hz tolerance exceeds out3_tol_hz"); 190 | */ 191 | // diamond: trigger error with division by zero, doesn't accept $error() 192 | localparam trig_out0_hz = error_out0_hz ? 1/0 : 0; 193 | localparam trig_out1_hz = error_out1_hz ? 1/0 : 0; 194 | localparam trig_out2_hz = error_out2_hz ? 1/0 : 0; 195 | localparam trig_out3_hz = error_out3_hz ? 1/0 : 0; 196 | 197 | wire [1:0] PHASESEL_HW = phasesel-1; 198 | wire CLKOP; // internal 199 | 200 | // TODO: frequencies in MHz if passed as "attributes" 201 | // will appear in diamond *.mrp file like "Output Clock(P) Frequency (MHz):" 202 | // but I don't know how to pass string parameters for this: 203 | // (* FREQUENCY_PIN_CLKI="025.000000" *) 204 | // (* FREQUENCY_PIN_CLKOP="023.345678" *) 205 | // (* FREQUENCY_PIN_CLKOS="034.234567" *) 206 | // (* FREQUENCY_PIN_CLKOS2="111.345678" *) 207 | // (* FREQUENCY_PIN_CLKOS3="123.456789" *) 208 | (* ICP_CURRENT="12" *) (* LPF_RESISTOR="8" *) (* MFG_ENABLE_FILTEROPAMP="1" *) (* MFG_GMCREF_SEL="2" *) 209 | EHXPLLL 210 | #( 211 | .CLKI_DIV (params_refclk_div), 212 | .CLKFB_DIV (params_feedback_div), 213 | .FEEDBK_PATH ("CLKOP"), 214 | 215 | .OUTDIVIDER_MUXA("DIVA"), 216 | .CLKOP_ENABLE ("ENABLED"), 217 | .CLKOP_DIV (params_output_div), 218 | .CLKOP_CPHASE (params_primary_cphase), 219 | .CLKOP_FPHASE (params_primary_fphase), 220 | 221 | .OUTDIVIDER_MUXB("DIVB"), 222 | .CLKOS_ENABLE (out1_hz > 0 ? "ENABLED" : "DISABLED"), 223 | .CLKOS_DIV (params_secondary1_div), 224 | .CLKOS_CPHASE (params_secondary1_cphase), 225 | .CLKOS_FPHASE (params_secondary1_fphase), 226 | 227 | .OUTDIVIDER_MUXC("DIVC"), 228 | .CLKOS2_ENABLE(out2_hz > 0 ? "ENABLED" : "DISABLED"), 229 | .CLKOS2_DIV (params_secondary2_div), 230 | .CLKOS2_CPHASE(params_secondary2_cphase), 231 | .CLKOS2_FPHASE(params_secondary2_fphase), 232 | 233 | .OUTDIVIDER_MUXD("DIVD"), 234 | .CLKOS3_ENABLE(out3_hz > 0 ? "ENABLED" : "DISABLED"), 235 | .CLKOS3_DIV (params_secondary3_div), 236 | .CLKOS3_CPHASE(params_secondary3_cphase), 237 | .CLKOS3_FPHASE(params_secondary3_fphase), 238 | 239 | .INTFB_WAKE ("DISABLED"), 240 | .STDBY_ENABLE (standby_en ? "ENABLED" : "DISABLED"), 241 | .PLLRST_ENA (standby_en ? "ENABLED" : "DISABLED"), 242 | .DPHASE_SOURCE(standby_en ? "ENABLED" : "DISABLED"), 243 | .PLL_LOCK_MODE(0) 244 | ) 245 | pll_inst 246 | ( 247 | .RST(1'b0), 248 | .STDBY(1'b0), 249 | .CLKI(clk_i), 250 | .CLKOP(CLKOP), 251 | .CLKOS (clk_o[1]), 252 | .CLKOS2(clk_o[2]), 253 | .CLKOS3(clk_o[3]), 254 | .CLKFB(CLKOP), 255 | .CLKINTFB(), 256 | .PHASESEL1(PHASESEL_HW[1]), 257 | .PHASESEL0(PHASESEL_HW[0]), 258 | .PHASEDIR(phasedir), 259 | .PHASESTEP(phasestep), 260 | .PHASELOADREG(phaseloadreg), 261 | .PLLWAKESYNC(1'b0), 262 | .ENCLKOP(1'b0), 263 | .ENCLKOS(1'b0), 264 | .ENCLKOS2(1'b0), 265 | .ENCLKOS3(1'b0), 266 | .LOCK(locked) 267 | ); 268 | assign clk_o[0] = CLKOP; 269 | 270 | endmodule 271 | -------------------------------------------------------------------------------- /src/erik_pll.v: -------------------------------------------------------------------------------- 1 | /** 2 | * PLL configuration 3 | * 4 | * This Verilog module was generated automatically 5 | * using the icepll tool from the IceStorm project. 6 | * Use at your own risk. 7 | * 8 | * Given input frequency: 100.000 MHz 9 | * Requested output frequency: 25.000 MHz 10 | * Achieved output frequency: 25.000 MHz 11 | */ 12 | 13 | module pll( 14 | input clock_in, 15 | output clock_out, 16 | output locked 17 | ); 18 | 19 | SB_PLL40_CORE #( 20 | .FEEDBACK_PATH("SIMPLE"), 21 | .DIVR(4'b0000), // DIVR = 0 22 | .DIVF(7'b0000111), // DIVF = 7 23 | .DIVQ(3'b101), // DIVQ = 5 24 | .FILTER_RANGE(3'b101) // FILTER_RANGE = 5 25 | ) uut ( 26 | .LOCK(locked), 27 | .RESETB(1'b1), 28 | .BYPASS(1'b0), 29 | .REFERENCECLK(clock_in), 30 | .PLLOUTCORE(clock_out) 31 | ); 32 | 33 | endmodule 34 | -------------------------------------------------------------------------------- /src/go_tms9918_tb.sh: -------------------------------------------------------------------------------- 1 | rm tms9918_tb 2 | iverilog -Wall -o tms9918_tb tms9918_tb.v tms9918.v vga_sync.v dualport_par.v 3 | ./tms9918_tb -lxt2 4 | 5 | -------------------------------------------------------------------------------- /src/gromext.v: -------------------------------------------------------------------------------- 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 | 33 | module gromext( 34 | input wire [7:0] din, // data in, write bus for addresses 35 | output wire [7:0] dout, // data out, read bus 36 | input wire clk, 37 | input wire we, // write enable, 1 cycle long 38 | input wire rd, // read signal, may be up for multiple cycles 39 | output wire selected, // High when this GROM is enabled during READ. 40 | // When high, databus should be driven by data from addr below. 41 | output wire reg_out, // When high, the GROM registers are read. 42 | input wire [4:0] mode, // A5..A1 (4 bits for GROM base select, 1 bit for register select) 43 | input wire reset, 44 | output wire [19:0] addr // 1 megabyte GROM address out 45 | ); 46 | 47 | reg [12:0] offset; 48 | reg [2:0] grom_sel; // top 3 bits of GROM address 49 | wire [15:0] rom_addr; 50 | reg [3:0] grom_base; 51 | reg [15:0] read_addr; 52 | reg read_addr_refresh; 53 | reg old_rd; 54 | wire [12:0] next_offset = offset + 1; 55 | 56 | // assign selected = (/*grom_base == 4'h0 && */ grom_sel == 3'd0 && grom_sel == 3'd1 && grom_sel == 3'd2) 57 | // && rd == 1'b1 && mode[0] == 1'b0; 58 | assign selected = rd == 1'b1 && mode[0] == 1'b0; 59 | assign reg_out = rd == 1'b1 && mode[0] == 1'b1; 60 | // Our GROMs cover all the bases currently. 61 | assign addr = {grom_base,grom_sel,offset}; 62 | assign dout = read_addr[15:8]; 63 | always @(posedge clk, posedge reset) begin 64 | if(reset == 1'b1) begin 65 | grom_sel <= 3'b000; 66 | read_addr_refresh <= 1'b0; 67 | offset <= {13{1'b0}}; 68 | end else begin 69 | // we handle only two scenarios: 70 | // write to GROM address counter 71 | // read from GROM data 72 | if(we == 1'b1 && mode[0] == 1'b1) begin 73 | // write to address counter 74 | offset[7:0] <= din; 75 | offset[12:8] <= offset[4:0]; 76 | grom_sel <= offset[7:5]; 77 | grom_base <= mode[4:1]; 78 | read_addr_refresh <= 1'b1; 79 | end 80 | old_rd <= rd; 81 | if(old_rd == 1'b1 && rd == 1'b0) begin 82 | if(mode[0] == 1'b0) begin 83 | offset <= next_offset; 84 | read_addr_refresh <= 1'b1; 85 | end 86 | else begin 87 | // address byte read just finished 88 | read_addr[15:8] <= read_addr[7:0]; 89 | end 90 | end 91 | if(read_addr_refresh == 1'b1) begin 92 | read_addr <= {grom_sel, next_offset }; 93 | read_addr_refresh <= 1'b0; 94 | end 95 | end 96 | end 97 | 98 | 99 | endmodule 100 | -------------------------------------------------------------------------------- /src/lcd_sys.v: -------------------------------------------------------------------------------- 1 | // lcd_sys.v 2 | // EP based on the open tech lab LCD controller stuff 3 | 4 | module lcd_sys( 5 | input wire clk, 6 | input wire reset, 7 | output wire cs, 8 | output wire sdin, 9 | output wire sclk, 10 | output wire d_cn, 11 | output wire resn, 12 | output wire vccen, 13 | output wire pmoden, 14 | // LCD RAM buffer memory 15 | input wire ram_wr, 16 | input wire [12:0] ram_addr, 17 | input wire [15:0] ram_data 18 | ); 19 | 20 | // output cs, sdin, sclk, d_cn, resn, vccen, pmoden; 21 | 22 | 23 | wire frame_begin, sending_pixels, sample_pixel; 24 | wire [12:0] pixel_index; 25 | wire [15:0] pixel_data; 26 | wire [6:0] x; 27 | wire [5:0] y; 28 | 29 | // Use a quarter of clk as our spi_clk 30 | reg spi_clk; 31 | reg [1:0] div = 2'b00; 32 | always @(posedge clk) begin 33 | spi_clk <= div[1]; 34 | div <= div + 2'd1; 35 | end 36 | 37 | // Need to write our pixels with ram_addr, ram_data_ram_wr 38 | 39 | ram_source ram_source(spi_clk, reset, frame_begin, sample_pixel, 40 | pixel_index, pixel_data, clk, ram_wr, ram_addr, ram_data); 41 | 42 | // SPI Clock Generator 43 | parameter ClkFreq = 25000000; // Hz 44 | localparam SpiDesiredFreq = 6250000; // Hz 45 | localparam SpiPeriod = (ClkFreq + (SpiDesiredFreq * 2) - 1) / (SpiDesiredFreq * 2); 46 | localparam SpiFreq = ClkFreq / (SpiPeriod * 2); 47 | 48 | pmodoledrgb_controller #(SpiFreq) pmodoledrgb_controller(spi_clk, reset, 49 | frame_begin, sending_pixels, sample_pixel, pixel_index, pixel_data, 50 | cs, sdin, sclk, d_cn, resn, vccen, pmoden); 51 | 52 | endmodule -------------------------------------------------------------------------------- /src/pager612.v: -------------------------------------------------------------------------------- 1 | // pager612.v 2 | // 3 | // Erik Piehl (C) 2020 based on my earlier VHDL code 4 | 5 | module pager612( 6 | input wire clk, 7 | input wire [15:12] abus_high, 8 | input wire [3:0] abus_low, 9 | input wire [11:0] dbus_in, 10 | output wire [11:0] dbus_out, 11 | input wire mapen, 12 | input wire write_enable, 13 | input wire page_reg_read, 14 | output wire [11:0] translated_addr, 15 | input wire access_regs 16 | ); 17 | 18 | // 1 = enable mapping 19 | // 0 = write to register when sel_regs = 1 20 | // 0 = read from register when sel_regs = 1 21 | // 1 = read/write registers 22 | 23 | reg [11:0] regs[0:15]; 24 | 25 | always @(posedge clk) begin 26 | if(access_regs == 1'b1 && write_enable == 1'b1) begin 27 | // write to paging register 28 | regs[abus_low[3:0]] <= dbus_in; 29 | // EP simplified for Verilog conversion 30 | end 31 | end 32 | 33 | assign translated_addr = mapen == 1'b1 && access_regs == 1'b0 ? 34 | regs[abus_high[15:12]] : // mapping on 35 | {8'h00,abus_high[15:12]}; // mapping off 36 | assign dbus_out = (page_reg_read == 1'b1 && access_regs == 1'b1) ? regs[abus_low[3:0]] : 16'hBEEF; 37 | 38 | endmodule 39 | -------------------------------------------------------------------------------- /src/ps2kb.v: -------------------------------------------------------------------------------- 1 | // PS/2 KBD interface (input only) 2 | // 3 | // Algorithm based on a VHDL routine by Grant Searle. 4 | // TI-99/2 implementation and Verilog conversion by Paul Ruizendaal. 5 | // TI-99/4A version by Erik Piehl. 6 | // 2019 Nov 7 | // 8 | module PS2KBD( 9 | input wire clk, 10 | 11 | input wire ps2_clk, 12 | input wire ps2_data, 13 | 14 | output reg [7:0] ps2_code, 15 | output reg strobe, 16 | output reg err 17 | ); 18 | 19 | // sync ps2_data 20 | // 21 | reg serin; 22 | always @(posedge clk) serin <= ps2_data; 23 | 24 | // sync & 'debounce' ps2_clock 25 | // 26 | parameter LEN = 8; 27 | reg bitclk = 0; 28 | reg [LEN:0] stable = 0; 29 | 30 | always @(posedge clk) 31 | begin 32 | stable = { stable[LEN-1:0], ps2_clk }; 33 | if ( &stable) bitclk <= 1; 34 | if (~|stable) bitclk <= 0; 35 | end 36 | 37 | wire bitedge = bitclk && (~|stable[LEN-1:0]); 38 | 39 | // clock in KBD bits (start - 8 data - odd parity - stop) 40 | // 41 | reg [8:0] shift = 0; 42 | reg [3:0] bitcnt = 0; 43 | reg parity = 0; 44 | 45 | always @(posedge clk) 46 | begin 47 | strobe <= 0; err <= 0; 48 | if (bitedge) begin 49 | // wait for start bit 50 | if (bitcnt==0) begin 51 | parity <= 0; 52 | if (!serin) bitcnt <= bitcnt + 1; 53 | end 54 | // shift in 9 bits (8 data + parity) 55 | else if (bitcnt<10) begin 56 | shift <= { serin, shift[8:1] }; 57 | parity <= parity ^ serin; 58 | bitcnt <= bitcnt + 1; 59 | end 60 | // check stop bit, parity 61 | else begin 62 | bitcnt <= 0; 63 | if (parity && serin) begin 64 | ps2_code <= shift[7:0]; 65 | strobe <= 1; 66 | end 67 | else 68 | err <= 1; 69 | end 70 | end 71 | end 72 | 73 | endmodule 74 | 75 | /////////////////////////////////////////////////////////// 76 | // Keyboard encoder 77 | /////////////////////////////////////////////////////////// 78 | module ps2matrix( 79 | input wire clk, 80 | input wire ps2clk, ps2data, 81 | input wire [2:0] line_sel, 82 | output reg [7:0] keyline, // 8 bits out 83 | 84 | output wire f1_pressed, // 1=F1 is pressed 85 | output wire f9_pressed, // 1=F9 is pressed 86 | // bit order matches ULX3S: MSB->LSB: right, left, down, up 87 | output wire [3:0] cursor_keys_pressed 88 | ); 89 | 90 | wire [7:0] code; 91 | wire strobe; 92 | PS2KBD kdb(clk, ps2clk, ps2data, code, strobe, ); 93 | 94 | reg [7:0] matrix[0:7]; 95 | reg extended = 0, action = 0; 96 | 97 | initial begin 98 | matrix[0] = 8'hff; matrix[1] = 8'hff; 99 | matrix[2] = 8'hff; matrix[3] = 8'hff; 100 | matrix[4] = 8'hff; matrix[5] = 8'hff; 101 | matrix[6] = 8'hff; matrix[7] = 8'hff; 102 | end 103 | 104 | reg f1_state = 1'b0; 105 | assign f1_pressed = f1_state; 106 | reg f9_state = 1'b0; 107 | assign f9_pressed = f9_state; 108 | assign cursor_keys_pressed = cursor_keys_state; 109 | reg [3:0] cursor_keys_state = 4'b0000; 110 | 111 | // Debug outputs 112 | // 113 | reg special = 0; 114 | wire shifted = !matrix[0][5]; // !matrix[4][7] | !matrix[5][1]; 115 | 116 | // Read out KBD matrix 117 | // 118 | always @(posedge clk) 119 | begin 120 | keyline <= matrix[line_sel]; 121 | end 122 | 123 | // Convert PS/2 scan codes into TI99/4 KBD matrix state 124 | // 125 | reg [7:0] decode; 126 | 127 | always @(posedge clk) 128 | begin 129 | if (strobe) begin 130 | if (code==8'he0) 131 | extended <= 1; 132 | else if (code==8'hf0) 133 | action <= 1; // up 134 | else begin 135 | extended <= 0; 136 | action <= 0; // down 137 | 138 | /* Convenient key remaps: 139 | shift-/ => fctn-i (?) 140 | shift-- => fctn-u (_) 141 | [ => fctn-r 142 | ] => fctn-t 143 | shift-[ => fctn-f 144 | shift-] => fctn-g 145 | \ => fctn-z 146 | ` => fctn-c 147 | shift-` => fctn-w (~) 148 | up => fctn-e 149 | left => fctn-s 150 | right => fctn-d 151 | down => fctn-x 152 | */ 153 | // Convenience mappings 154 | decode = code; 155 | special <= !action; 156 | /* 157 | if (!shifted) begin 158 | case (code) 159 | 8'h4e: decode = 8'h4a; // - -> shift-/ 160 | 8'h52: decode = 8'h44; // ' -> shift-O 161 | default: special <= 0; 162 | endcase 163 | end 164 | else begin 165 | case (code) 166 | // 8'h4a: decode = 8'h43; // ? -> shift-I 167 | 8'h52: decode = 8'h4d; // " -> shift-P 168 | default: special <= 0; 169 | endcase 170 | end 171 | */ 172 | case (decode) 173 | 8'h05: f1_state <= !action; // F1 key 174 | 8'h01: f9_state <= !action; // F9 key 175 | 8'h16: matrix[5][4] <= action; // 1 176 | 8'h1e: matrix[1][4] <= action; // 2 177 | 8'h26: matrix[2][4] <= action; // 3 178 | 8'h25: matrix[3][4] <= action; // 4 179 | 8'h2e: matrix[4][4] <= action; // 5 180 | 8'h36: matrix[4][3] <= action; // 6 181 | 8'h3d: matrix[3][3] <= action; // 7 182 | 8'h3e: matrix[2][3] <= action; // 8 183 | 8'h46: matrix[1][3] <= action; // 9 184 | 8'h45: matrix[5][3] <= action; // 0 185 | 8'h4e: matrix[5][0] <= action; // / duplicate 186 | 8'h55: matrix[0][0] <= action; // = 187 | // Backspace: 188 | 8'h66: begin 189 | matrix[0][4] <= action; // FCTN 190 | matrix[1][5] <= action; // S 191 | end 192 | // Left arrow E0 6B 193 | 8'h6b: begin 194 | if (extended) begin 195 | matrix[0][4] <= action; // FCTN 196 | matrix[1][5] <= action; // S 197 | 198 | matrix[6][1] <= action; // Joystick 1 left 199 | 200 | cursor_keys_state[2] <= !action; 201 | end 202 | end 203 | // Right arrow E0 74 204 | 8'h74: begin 205 | if (extended) begin 206 | matrix[0][4] <= action; // FCTN 207 | matrix[2][5] <= action; // D 208 | 209 | matrix[6][2] <= action; // Joystick 1 right 210 | cursor_keys_state[3] <= !action; 211 | end 212 | end 213 | // Up arrow E0 75 214 | 8'h75: begin 215 | if (extended) begin 216 | matrix[0][4] <= action; // FCTN 217 | matrix[2][6] <= action; // E 218 | 219 | matrix[6][4] <= action; // Joystick 1 up 220 | cursor_keys_state[0] <= !action; 221 | end 222 | end 223 | // Down arrow E0 72 224 | 8'h72: begin 225 | if (extended) begin 226 | matrix[0][4] <= action; // FCTN 227 | matrix[1][7] <= action; // X 228 | 229 | matrix[6][3] <= action; // Joystick 1 down 230 | cursor_keys_state[1] <= !action; 231 | end 232 | end 233 | // Delete E0 71 234 | 8'h71: begin 235 | if (extended) begin 236 | matrix[0][4] <= action; // FCTN 237 | matrix[5][4] <= action; // 1 238 | end 239 | end 240 | 241 | // 8'h0d: // TAB 242 | 8'h15: matrix[5][6] <= action; // Q 243 | 8'h1d: matrix[1][6] <= action; // W 244 | 8'h24: matrix[2][6] <= action; // E 245 | 8'h2d: matrix[3][6] <= action; // R 246 | 8'h2c: matrix[4][6] <= action; // T 247 | 8'h35: matrix[4][2] <= action; // Y 248 | 8'h3c: matrix[3][2] <= action; // U 249 | 8'h43: matrix[2][2] <= action; // I 250 | 8'h44: matrix[1][2] <= action; // O 251 | 8'h4d: matrix[5][2] <= action; // P 252 | // 8'h54: // [ 253 | // 8'h5b: // ] 254 | // 8'h0e: // Backslash 255 | 256 | // 8'h58: // Caps lock 257 | 8'h1c: matrix[5][5] <= action; // A 258 | 8'h1b: matrix[1][5] <= action; // S 259 | 8'h23: matrix[2][5] <= action; // D 260 | 8'h2b: matrix[3][5] <= action; // F 261 | 8'h34: matrix[4][5] <= action; // G 262 | 8'h33: matrix[4][1] <= action; // H 263 | 8'h3b: matrix[3][1] <= action; // J 264 | 8'h42: matrix[2][1] <= action; // K 265 | 8'h4b: matrix[1][1] <= action; // L 266 | 8'h4c: matrix[5][1] <= action; // ; 267 | // 8'h52: // ' 268 | 8'h5a: matrix[0][2] <= action; // ENTER 269 | 270 | 8'h12: matrix[0][5] <= action; // L-SHIFT 271 | 8'h1a: matrix[5][7] <= action; // Z 272 | 8'h22: matrix[1][7] <= action; // X 273 | 8'h21: matrix[2][7] <= action; // C 274 | 8'h2a: matrix[3][7] <= action; // V 275 | 8'h32: matrix[4][7] <= action; // B 276 | 8'h31: matrix[4][0] <= action; // N 277 | 8'h3a: matrix[3][0] <= action; // M 278 | 8'h41: matrix[2][0] <= action; // , 279 | 8'h49: matrix[1][0] <= action; // . 280 | 8'h4a: matrix[5][0] <= action; // / 281 | 8'h59: matrix[0][5] <= action; // R-SHIFT 282 | 283 | // 8'h76: matrix[5][7] <= action; // ESC = BREAK 284 | 8'h29: matrix[0][1] <= action; // SPACE 285 | 8'h14: begin 286 | matrix[0][6] <= action; // CTRL 287 | if(extended) 288 | matrix[6][0] <= action; // Right control is joystick 1 fire 289 | end 290 | 8'h11: matrix[0][4] <= action; // L-ALT = FCTN 291 | endcase 292 | end 293 | end 294 | end 295 | 296 | endmodule 297 | -------------------------------------------------------------------------------- /src/ram2.v: -------------------------------------------------------------------------------- 1 | // 2 | // Simplistic 4096x16 RAM module 3 | // 4 | // This source code is public domain 5 | // 6 | 7 | module RAM(CLK, nCS, nWE, ADDR, DI, DO); 8 | 9 | // Port definition 10 | input CLK, nCS, nWE; 11 | input [11:0] ADDR; 12 | input [15:0] DI; 13 | output [15:0] DO; 14 | 15 | wire CLK, nCS, nWE; 16 | wire [11:0] ADDR; 17 | wire [15:0] DI; 18 | reg [15:0] DO; 19 | 20 | // Implementation 21 | reg [15:0] mem[1023:0]; // mem[4095:0]; 22 | 23 | always @(posedge CLK) 24 | begin 25 | if (!nCS) begin 26 | if (!nWE) mem[ADDR[9:0]] <= DI; // 11 27 | end 28 | end 29 | 30 | always @(posedge CLK) 31 | begin 32 | if (!nCS) begin 33 | DO <= mem[ADDR[11:0]]; 34 | end 35 | end 36 | 37 | endmodule 38 | -------------------------------------------------------------------------------- /src/sdram.sv: -------------------------------------------------------------------------------- 1 | // 2 | // sdram.v 3 | // 4 | // sdram controller implementation 5 | // Copyright (c) 2018 Sorgelig 6 | // 7 | // This source file is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published 9 | // by the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This source file is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | module sdram 22 | ( 23 | 24 | // interface to the MT48LC16M16 chip 25 | inout [15:0] SDRAM_DQ, // 16 bit bidirectional data bus 26 | output reg [12:0] SDRAM_A, // 13 bit multiplexed address bus 27 | output reg SDRAM_DQML, // byte mask 28 | output reg SDRAM_DQMH, // byte mask 29 | output reg [1:0] SDRAM_BA, // two banks 30 | output reg SDRAM_nCS, // a single chip select 31 | output reg SDRAM_nWE, // write enable 32 | output reg SDRAM_nRAS, // row address select 33 | output reg SDRAM_nCAS, // columns address select 34 | // output SDRAM_CKE, 35 | 36 | // cpu/chipset interface 37 | input init = 1'b0, // init signal after FPGA config to initialize RAM 38 | input clk, // sdram is accessed at up to 128MHz 39 | 40 | input [24:0] addr, 41 | input rd, 42 | input wr, 43 | input [1:0] wbs, // write byte select 44 | input word = 1'b1, // always 1 45 | input [15:0] din, 46 | output [15:0] dout, 47 | output reg busy 48 | ); 49 | 50 | //assign SDRAM_CKE = 1; 51 | assign {SDRAM_DQMH,SDRAM_DQML} = SDRAM_A[12:11]; 52 | 53 | localparam RASCAS_DELAY = 3'd1; // tRCD=20ns -> 2 cycles@85MHz 54 | localparam BURST_LENGTH = 3'd0; // 0=1, 1=2, 2=4, 3=8, 7=full page 55 | localparam ACCESS_TYPE = 1'd0; // 0=sequential, 1=interleaved 56 | localparam CAS_LATENCY = 3'd2; // 2/3 allowed 57 | localparam OP_MODE = 2'd0; // only 0 (standard operation) allowed 58 | localparam NO_WRITE_BURST = 1'd1; // 0=write burst enabled, 1=only single access write 59 | 60 | localparam MODE = { 3'b000, NO_WRITE_BURST, OP_MODE, CAS_LATENCY, ACCESS_TYPE, BURST_LENGTH}; 61 | 62 | localparam STATE_IDLE = 3'd0; // state to check the requests 63 | localparam STATE_START = STATE_IDLE+1'd1; // state in which a new command is started 64 | localparam STATE_CONT = STATE_START+RASCAS_DELAY; 65 | localparam STATE_READY = STATE_CONT+CAS_LATENCY+1'd1; 66 | localparam STATE_LAST = STATE_READY; // last state in cycle 67 | 68 | wire [15:0] dq_di; 69 | reg [15:0] dq_do; 70 | reg dq_oe; 71 | assign SDRAM_DQ = dq_oe ? dq_do : {16{1'bz}}; 72 | assign dq_di = SDRAM_DQ; 73 | 74 | reg [2:0] state; 75 | reg [24:0] a; 76 | reg [15:0] data; 77 | reg we; 78 | reg ds; 79 | reg [1:0] r_wbs; 80 | reg ram_req=0; 81 | wire ram_req_test = (we || (a[24:1] != addr[24:1])); 82 | reg [15:0] last_data; 83 | 84 | // access manager 85 | always @(posedge clk) begin : blk_am 86 | reg old_ref; 87 | reg old_rd,old_wr; 88 | 89 | old_rd <= old_rd & rd; 90 | old_wr <= old_wr & wr; 91 | 92 | if(state == STATE_IDLE && mode == MODE_NORMAL) begin 93 | if((~old_rd & rd) | (~old_wr & wr)) begin 94 | old_rd <= rd; 95 | old_wr <= wr; 96 | we <= wr; 97 | ds <= word; 98 | r_wbs <= wbs; 99 | busy <= 1; 100 | state <= STATE_START; 101 | end 102 | end 103 | 104 | if(state == STATE_START && busy) begin 105 | a <= addr; 106 | data <= word ? din : {din[7:0],din[7:0]}; 107 | ram_req <= ram_req_test; 108 | end 109 | 110 | if(state == STATE_READY && busy) begin 111 | ram_req <= 0; 112 | we <= 0; 113 | busy <= 0; 114 | if(ram_req) begin 115 | if(we) begin 116 | a <= '1; 117 | end 118 | else begin 119 | last_data <= dq_di; 120 | end 121 | end 122 | end 123 | 124 | if(mode != MODE_NORMAL || state != STATE_IDLE || reset) begin 125 | state <= state + 1'd1; 126 | if(state == STATE_LAST) state <= STATE_IDLE; 127 | end 128 | end 129 | 130 | assign dout = ((~ds & a[0]) ? {last_data[7:0],last_data[15:8]} : last_data); 131 | 132 | localparam MODE_NORMAL = 2'b00; 133 | localparam MODE_RESET = 2'b01; 134 | localparam MODE_LDM = 2'b10; 135 | localparam MODE_PRE = 2'b11; 136 | 137 | // initialization 138 | reg [1:0] mode; 139 | reg [4:0] reset = 5'b11111; 140 | reg init_old=0; 141 | 142 | always @(posedge clk) begin : blk_init 143 | init_old <= init; 144 | 145 | if(init_old & ~init) reset <= 5'b11111; 146 | else if(state == STATE_LAST) begin 147 | if(reset != 0) begin 148 | reset <= reset - 5'd1; 149 | if(reset == 14) mode <= MODE_PRE; 150 | else if(reset == 3) mode <= MODE_LDM; 151 | else mode <= MODE_RESET; 152 | end 153 | else mode <= MODE_NORMAL; 154 | end 155 | end 156 | 157 | localparam CMD_INHIBIT = 4'b1111; 158 | localparam CMD_NOP = 4'b0111; 159 | localparam CMD_ACTIVE = 4'b0011; 160 | localparam CMD_READ = 4'b0101; 161 | localparam CMD_WRITE = 4'b0100; 162 | localparam CMD_BURST_TERMINATE = 4'b0110; 163 | localparam CMD_PRECHARGE = 4'b0010; 164 | localparam CMD_AUTO_REFRESH = 4'b0001; 165 | localparam CMD_LOAD_MODE = 4'b0000; 166 | 167 | // wire [1:0] dqm = {we & ~ds & ~a[0], we & ~ds & a[0]}; 168 | wire [1:0] dqm = {we & r_wbs[1], we & r_wbs[0]}; 169 | 170 | // SDRAM state machines 171 | always @(posedge clk) begin 172 | if(state == STATE_START) SDRAM_BA <= (mode == MODE_NORMAL) ? addr[24:23] : 2'b00; 173 | 174 | dq_oe <= 1'b0; 175 | casex({ram_req,we,mode,state}) 176 | {2'bXX, MODE_NORMAL, STATE_START}: {SDRAM_nCS, SDRAM_nRAS, SDRAM_nCAS, SDRAM_nWE} <= ram_req_test ? CMD_ACTIVE : CMD_AUTO_REFRESH; 177 | {2'b11, MODE_NORMAL, STATE_CONT }: {SDRAM_nCS, SDRAM_nRAS, SDRAM_nCAS, SDRAM_nWE, dq_do, dq_oe} <= {CMD_WRITE, data, 1'b1}; 178 | {2'b10, MODE_NORMAL, STATE_CONT }: {SDRAM_nCS, SDRAM_nRAS, SDRAM_nCAS, SDRAM_nWE} <= CMD_READ; 179 | 180 | // init 181 | {2'bXX, MODE_LDM, STATE_START}: {SDRAM_nCS, SDRAM_nRAS, SDRAM_nCAS, SDRAM_nWE} <= CMD_LOAD_MODE; 182 | {2'bXX, MODE_PRE, STATE_START}: {SDRAM_nCS, SDRAM_nRAS, SDRAM_nCAS, SDRAM_nWE} <= CMD_PRECHARGE; 183 | 184 | default: {SDRAM_nCS, SDRAM_nRAS, SDRAM_nCAS, SDRAM_nWE} <= CMD_INHIBIT; 185 | endcase 186 | 187 | if(mode == MODE_NORMAL) begin 188 | casex(state) 189 | STATE_START: SDRAM_A <= addr[13:1]; 190 | STATE_CONT: SDRAM_A <= {dqm, 2'b10, a[22:14]}; 191 | endcase; 192 | end 193 | else if(mode == MODE_LDM && state == STATE_START) SDRAM_A <= MODE; 194 | else if(mode == MODE_PRE && state == STATE_START) SDRAM_A <= 13'b0010000000000; 195 | else SDRAM_A <= 0; 196 | end 197 | /* 198 | altddio_out 199 | #( 200 | .extend_oe_disable("OFF"), 201 | .intended_device_family("Cyclone V"), 202 | .invert_output("OFF"), 203 | .lpm_hint("UNUSED"), 204 | .lpm_type("altddio_out"), 205 | .oe_reg("UNREGISTERED"), 206 | .power_up_high("OFF"), 207 | .width(1) 208 | ) 209 | sdramclk_ddr 210 | ( 211 | .datain_h(1'b0), 212 | .datain_l(1'b1), 213 | .outclock(clk), 214 | .dataout(SDRAM_CLK), 215 | .aclr(1'b0), 216 | .aset(1'b0), 217 | .oe(1'b1), 218 | .outclocken(1'b1), 219 | .sclr(1'b0), 220 | .sset(1'b0) 221 | ); 222 | */ 223 | 224 | endmodule 225 | -------------------------------------------------------------------------------- /src/sdram_cortex.v: -------------------------------------------------------------------------------- 1 | // 2 | // sdram.v 3 | // 4 | // This source code is public domain 5 | // 6 | // TMS99000 SDRAM controller. Operate at >4x CPU clock speed (i.e. 16x CPU clkout) 7 | // PC133 (grade 7) chips work up to 143Mhz 8 | // PC166 (grade 6) chips work up to 166MHz 9 | // 10 | 11 | module SDRAM ( 12 | input clk_in, // controller clock 13 | 14 | // interface to the chip 15 | inout [15:0] sd_data, // 16 bit databus 16 | output reg [12:0] sd_addr, // 13 bit multiplexed address bus 17 | output reg [1:0] sd_dqm, // two byte masks 18 | output reg [1:0] sd_ba, // two banks 19 | output sd_cs, // chip select 20 | output sd_we, // write enable 21 | output sd_ras, // row address select 22 | output sd_cas, // columns address select 23 | output sd_cke, // clock enable 24 | output sd_clk, // chip clock (inverted from input clk) 25 | 26 | // TMS99000 interface 27 | input [15:0] din, // data input from cpu 28 | output reg [15:0] dout, // data output to cpu 29 | input [23:0] ad, // 24 bit word address 30 | input as, // as, start of new CPU machine cycle 31 | input nwr, // cpu write cycle 32 | input rst, // cpu reset 33 | output reg ack // Erik: ack out 34 | ); 35 | 36 | localparam RASCAS_DELAY = 3'd3; // tRCD=20ns -> 3 cycles @ >100MHz 37 | localparam BURST_LENGTH = 3'b000; // 000=1, 001=2, 010=4, 011=8 38 | localparam ACCESS_TYPE = 1'b0; // 0=sequential, 1=interleaved 39 | localparam CAS_LATENCY = 3'd3; // 3 needed @ >100Mhz 40 | localparam OP_MODE = 2'b00; // only 00 (standard operation) allowed 41 | localparam NO_WRITE_BURST = 1'b1; // 0=write burst enabled, 1=only single access write 42 | 43 | localparam MODE = { 3'b000, NO_WRITE_BURST, OP_MODE, CAS_LATENCY, ACCESS_TYPE, BURST_LENGTH}; 44 | 45 | // Sync the address bus -- unnecessary, but makes NextPNR timing analysis happy 46 | reg [23:0] addr; 47 | always @(posedge clk_in) addr <= ad; 48 | 49 | // --------------------------------------------------------------------- 50 | // ------------------------ cycle state machine ------------------------ 51 | // --------------------------------------------------------------------- 52 | 53 | // The state machine runs at 125Mhz, synchronous to the CPU 54 | // Each CPU machine cycle is a r/w cycle followed by a refresh cycle 55 | 56 | localparam STATE_FIRST = 0; // idle state, prep command 57 | localparam STATE_CMD_CAS = STATE_FIRST + RASCAS_DELAY; // prep CAS cycle 58 | localparam STATE_READ = STATE_CMD_CAS + CAS_LATENCY + 1; 59 | localparam STATE_CMD_RFSH = STATE_READ + 1; 60 | localparam STATE_CMD_DONE = 10; // Erik: here we send ack out 61 | 62 | reg [3:0] t; 63 | 64 | // --------------------------------------------------------------------- 65 | // --------------------------- startup/reset --------------------------- 66 | // --------------------------------------------------------------------- 67 | 68 | // make sure rst lasts long enough (recommended 100us) 69 | reg [4:0] reset; 70 | always @(posedge clk_in) begin 71 | reset <= (|reset) ? reset - 5'd1 : 0; 72 | if(rst) reset <= 5'd25; 73 | end 74 | 75 | // --------------------------------------------------------------------- 76 | // ------------------ generate ram control signals --------------------- 77 | // --------------------------------------------------------------------- 78 | 79 | // all possible commands 80 | localparam CMD_INHIBIT = 4'b1111; 81 | localparam CMD_NOP = 4'b0111; 82 | localparam CMD_ACTIVE = 4'b0011; 83 | localparam CMD_READ = 4'b0101; 84 | localparam CMD_WRITE = 4'b0100; 85 | localparam CMD_BURST_TERMINATE = 4'b0110; 86 | localparam CMD_PRECHARGE = 4'b0010; 87 | localparam CMD_AUTO_REFRESH = 4'b0001; 88 | localparam CMD_LOAD_MODE = 4'b0000; 89 | 90 | reg [3:0] sd_cmd = CMD_INHIBIT; // current command sent to sd ram 91 | 92 | assign sd_clk = !clk_in; // chip clock shifted 180 deg. 93 | assign sd_cke = 1'b1; 94 | 95 | // drive control signals according to current command 96 | assign sd_cs = sd_cmd[3]; 97 | assign sd_ras = sd_cmd[2]; 98 | assign sd_cas = sd_cmd[1]; 99 | assign sd_we = sd_cmd[0]; 100 | 101 | // sdram tri-state databus interaction 102 | reg sd_data_wr = 1'b0; 103 | wire sd_data_rd = (t==STATE_READ) & nwr; 104 | assign sd_data = sd_data_wr ? din : 16'hzzzz; 105 | `ifdef __ICARUS__ 106 | always @(posedge sd_clk) if(sd_data_rd) dout <= sd_data; 107 | `else 108 | wire rd_clk; 109 | DLY2NS dly(.in(sd_clk), .out(rd_clk)); 110 | IFS1P3BX dbi_FF[15:0] (.SCLK(rd_clk), .SP(sd_data_rd), .Q(dout), .D(sd_data), .PD(1'b0)); 111 | `endif 112 | 113 | // controller state machine 114 | 115 | always @(posedge clk_in) begin 116 | sd_cmd <= CMD_INHIBIT; // default: idle 117 | ack <= 1'b0; // default: no ack 118 | 119 | // move to next state 120 | t <= t + !(&t); 121 | if(rst | as) t <= STATE_FIRST; 122 | 123 | if(reset != 0) begin // reset operation 124 | case(reset) 125 | 22: sd_ba <= 2'b00; 126 | 21: sd_addr[10] <= 1'b1; // prep for precharge all banks 127 | 20: sd_cmd <= CMD_PRECHARGE; 128 | 11: sd_addr <= MODE; 129 | 10: sd_cmd <= CMD_LOAD_MODE; 130 | endcase 131 | end 132 | 133 | else begin // normal operation 134 | case(t) 135 | // RAS phase 136 | STATE_FIRST: begin 137 | sd_addr <= { 1'b0, addr[19:8] }; 138 | sd_ba <= addr[21:20]; 139 | if (!as) sd_cmd <= CMD_ACTIVE; 140 | end 141 | // CAS phase 142 | STATE_CMD_CAS-1: begin // set up output data early 143 | sd_data_wr <= !nwr; 144 | sd_dqm <= 2'b00; 145 | sd_addr <= { 4'b0010, addr[22], addr[7:0] }; 146 | end 147 | STATE_CMD_CAS: sd_cmd <= nwr ? CMD_READ : CMD_WRITE; 148 | STATE_CMD_CAS+1: sd_data_wr <= 1'b0; // revert sd_data to hi-z 149 | // refresh phase 150 | STATE_CMD_RFSH: sd_cmd <= CMD_AUTO_REFRESH; 151 | STATE_CMD_DONE: ack <= 1'b1; // Show the ack for 5 cycles to make sure it seen 152 | STATE_CMD_DONE+1: ack <= 1'b1; // by the lower clock speed side. 153 | STATE_CMD_DONE+2: ack <= 1'b1; 154 | STATE_CMD_DONE+3: ack <= 1'b1; 155 | STATE_CMD_DONE+4: ack <= 1'b1; 156 | endcase 157 | end 158 | end 159 | 160 | endmodule 161 | 162 | // poor solution to generate ~2ns delay 163 | // 164 | `ifndef __ICARUS__ 165 | 166 | module DLY2NS ( 167 | input wire in, 168 | output wire out 169 | ); 170 | 171 | (* keep *) wire x1 = !in; 172 | (* keep *) wire x2 = !x1; 173 | (* keep *) wire x3 = !x2; 174 | (* keep *) wire x4 = !x3; 175 | assign out = x4; 176 | 177 | endmodule 178 | 179 | `endif 180 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/spi_slave.v: -------------------------------------------------------------------------------- 1 | // File src/spi_slave.vhd translated with vhd2vl v3.0 VHDL to Verilog RTL translator 2 | // vhd2vl settings: 3 | // * Verilog Module Declaration Style: 2001 4 | 5 | // vhd2vl is Free (libre) Software: 6 | // Copyright (C) 2001 Vincenzo Liguori - Ocean Logic Pty Ltd 7 | // http://www.ocean-logic.com 8 | // Modifications Copyright (C) 2006 Mark Gonzales - PMC Sierra Inc 9 | // Modifications (C) 2010 Shankar Giri 10 | // Modifications Copyright (C) 2002-2017 Larry Doolittle 11 | // http://doolittle.icarus.com/~larry/vhd2vl/ 12 | // Modifications (C) 2017 Rodrigo A. Melo 13 | // 14 | // vhd2vl comes with ABSOLUTELY NO WARRANTY. Always check the resulting 15 | // Verilog for correctness, ideally with a formal verification tool. 16 | // 17 | // You are welcome to redistribute vhd2vl under certain conditions. 18 | // See the license (GPLv2) file included with the source for details. 19 | 20 | // The result of translation follows. Its copyright status should be 21 | // considered unchanged from the original VHDL. 22 | 23 | //-------------------------------------------------------------------------------- 24 | // Company: 25 | // Engineer: Erik Piehl 26 | // 27 | // Create Date: 10:34:14 01/07/2018 28 | // Design Name: 29 | // Module Name: spi_slave - Behavioral 30 | // Project Name: 31 | // Target Devices: 32 | // Tool versions: 33 | // Description: 34 | // 35 | // Dependencies: 36 | // 37 | // Revision: 38 | // Revision 0.01 - File Created 39 | // Additional Comments: 40 | // 41 | //-------------------------------------------------------------------------------- 42 | // Uncomment the following library declaration if using 43 | // arithmetic functions with Signed or Unsigned values 44 | // Uncomment the following library declaration if instantiating 45 | // any Xilinx primitives in this code. 46 | //library UNISIM; 47 | //use UNISIM.VComponents.all; 48 | // no timescale needed 49 | 50 | module spi_slave( 51 | input wire clk, 52 | input wire rst, 53 | input wire cs_n, 54 | input wire spi_clk, 55 | input wire mosi, 56 | output wire miso, 57 | output wire spi_rq, 58 | output reg [7:0] rx_data, 59 | output reg rx_ready, 60 | input wire [7:0] tx_data, 61 | output wire tx_busy, 62 | input wire tx_new_data 63 | ); 64 | 65 | // debug for now - data was wll received or sent 66 | // launch transmission of new data 67 | 68 | `define false 0 69 | `define true 1 70 | 71 | //----------------------------------------------------------------------------- 72 | // Signals for LPC1343 SPI controller receiver 73 | //----------------------------------------------------------------------------- 74 | reg [7:0] lastCS = 8'h00; 75 | reg [7:0] spi_tx_shifter; 76 | reg [31:0] spi_bitcount; 77 | reg spi_ready = `false; 78 | reg [31:0] spi_test_count = 0; 79 | reg [2:0] spi_clk_sampler = 3'b000; 80 | reg spi_rx_bit; 81 | reg wait_clock = `false; 82 | reg transmitter_busy; 83 | 84 | assign spi_rq = spi_ready == `true ? 1'b1 : 1'b0; 85 | // indicates data well received / sent 86 | assign miso = cs_n == 1'b0 ? spi_tx_shifter[7] : 1'bZ; 87 | assign tx_busy = transmitter_busy; 88 | always @(posedge clk) begin 89 | if(rst == 1'b1) begin 90 | lastCS <= 8'hFF; 91 | spi_ready <= `false; 92 | spi_test_count <= 0; 93 | spi_clk_sampler <= 3'b000; 94 | wait_clock <= `false; 95 | transmitter_busy <= 1'b1; 96 | spi_tx_shifter <= 8'hFF; 97 | end 98 | else begin 99 | spi_clk_sampler <= {spi_clk_sampler[1:0],spi_clk}; 100 | lastCS <= {lastCS[6:0],cs_n}; 101 | rx_ready <= 1'b0; 102 | if(lastCS[7:5] == 3'b111 && lastCS[1:0] == 2'b00 && cs_n == 1'b0 && wait_clock == `false) begin 103 | // falling edge of CS 104 | spi_bitcount <= 0; 105 | spi_ready <= `false; 106 | // spi_test_count <= spi_test_count + 1; 107 | // spi_tx_shifter <= std_logic_vector(to_unsigned(spi_test_count,8)); 108 | wait_clock <= `true; 109 | end 110 | if(spi_clk_sampler == 3'b011 && lastCS[0] == 1'b0 && cs_n == 1'b0) begin 111 | // rising edge of clock, receive shift 112 | spi_rx_bit <= mosi; 113 | spi_ready <= `false; 114 | wait_clock <= `false; 115 | end 116 | if(spi_clk_sampler == 3'b110 && lastCS[0] == 1'b0 && cs_n == 1'b0) begin 117 | // falling edge of clock, transmit shift 118 | spi_tx_shifter <= {spi_tx_shifter[6:0],spi_rx_bit}; 119 | spi_bitcount <= spi_bitcount + 1; 120 | if(spi_bitcount == 7) begin 121 | spi_bitcount <= 0; 122 | spi_ready <= `true; 123 | rx_data <= {spi_tx_shifter[6:0],spi_rx_bit}; 124 | rx_ready <= 1'b1; 125 | // a single clock cycle pulse 126 | transmitter_busy <= 1'b0; 127 | // ready transmit a byte (if there are subsequent clocks) 128 | end 129 | end 130 | if(transmitter_busy == 1'b0 && tx_new_data == 1'b1) begin 131 | transmitter_busy <= 1'b1; 132 | spi_tx_shifter <= tx_data; 133 | end 134 | end 135 | // reset 136 | end 137 | 138 | 139 | endmodule 140 | -------------------------------------------------------------------------------- /src/tmds_encoder.v: -------------------------------------------------------------------------------- 1 | // File tmds_encoder.vhd translated with vhd2vl v3.0 VHDL to Verilog RTL translator 2 | // vhd2vl settings: 3 | // * Verilog Module Declaration Style: 2001 4 | 5 | // vhd2vl is Free (libre) Software: 6 | // Copyright (C) 2001 Vincenzo Liguori - Ocean Logic Pty Ltd 7 | // http://www.ocean-logic.com 8 | // Modifications Copyright (C) 2006 Mark Gonzales - PMC Sierra Inc 9 | // Modifications (C) 2010 Shankar Giri 10 | // Modifications Copyright (C) 2002-2017 Larry Doolittle 11 | // http://doolittle.icarus.com/~larry/vhd2vl/ 12 | // Modifications (C) 2017 Rodrigo A. Melo 13 | // 14 | // vhd2vl comes with ABSOLUTELY NO WARRANTY. Always check the resulting 15 | // Verilog for correctness, ideally with a formal verification tool. 16 | // 17 | // You are welcome to redistribute vhd2vl under certain conditions. 18 | // See the license (GPLv2) file included with the source for details. 19 | 20 | // The result of translation follows. Its copyright status should be 21 | // considered unchanged from the original VHDL. 22 | 23 | //-------------------------------------------------------------------------------- 24 | // Engineer: Mike Field 25 | // 26 | // Description: TMDS Encoder 27 | // 8 bits colour, 2 control bits and one blanking bits in 28 | // 10 bits of TMDS encoded data out 29 | // Clocked at the pixel clock 30 | // 31 | //-------------------------------------------------------------------------------- 32 | // See: http://hamsterworks.co.nz/mediawiki/index.php/Dvid_test 33 | // http://hamsterworks.co.nz/mediawiki/index.php/FPGA_Projects 34 | // 35 | // Copyright (c) 2012 Mike Field 36 | // 37 | // Permission is hereby granted, free of charge, to any person obtaining a copy 38 | // of this software and associated documentation files (the "Software"), to deal 39 | // in the Software without restriction, including without limitation the rights 40 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 41 | // copies of the Software, and to permit persons to whom the Software is 42 | // furnished to do so, subject to the following conditions: 43 | // 44 | // The above copyright notice and this permission notice shall be included in 45 | // all copies or substantial portions of the Software. 46 | // 47 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 48 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 49 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 50 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 51 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 52 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 53 | // THE SOFTWARE. 54 | // 55 | // no timescale needed 56 | 57 | module tmds_encoder( 58 | input wire clk, 59 | input wire [7:0] data, 60 | input wire [1:0] c, 61 | input wire blank, 62 | output reg [9:0] encoded 63 | ); 64 | 65 | 66 | 67 | 68 | wire [8:0] xored; 69 | wire [8:0] xnored; 70 | wire [3:0] ones; 71 | reg [8:0] data_word; 72 | reg [8:0] data_word_inv; 73 | wire [3:0] data_word_disparity; 74 | reg [3:0] dc_bias = 1'b0; 75 | 76 | // Work our the two different encodings for the byte 77 | assign xored[0] = data[0]; 78 | assign xored[1] = data[1] ^ xored[0]; 79 | assign xored[2] = data[2] ^ xored[1]; 80 | assign xored[3] = data[3] ^ xored[2]; 81 | assign xored[4] = data[4] ^ xored[3]; 82 | assign xored[5] = data[5] ^ xored[4]; 83 | assign xored[6] = data[6] ^ xored[5]; 84 | assign xored[7] = data[7] ^ xored[6]; 85 | assign xored[8] = 1'b1; 86 | assign xnored[0] = data[0]; 87 | assign xnored[1] = ~(data[1] ^ xnored[0]); 88 | assign xnored[2] = ~(data[2] ^ xnored[1]); 89 | assign xnored[3] = ~(data[3] ^ xnored[2]); 90 | assign xnored[4] = ~(data[4] ^ xnored[3]); 91 | assign xnored[5] = ~(data[5] ^ xnored[4]); 92 | assign xnored[6] = ~(data[6] ^ xnored[5]); 93 | assign xnored[7] = ~(data[7] ^ xnored[6]); 94 | assign xnored[8] = 1'b0; 95 | // Count how many ones are set in data 96 | assign ones = 4'b0000 + data[0] + data[1] + data[2] + data[3] + data[4] + data[5] + data[6] + data[7]; 97 | // Decide which encoding to use 98 | always @(ones, data[0], xnored, xored) begin 99 | if(ones > 4 || (ones == 4 && data[0] == 1'b0)) begin 100 | data_word <= xnored; 101 | data_word_inv <= ~(xnored); 102 | end 103 | else begin 104 | data_word <= xored; 105 | data_word_inv <= ~(xored); 106 | end 107 | end 108 | 109 | // Work out the DC bias of the dataword; 110 | assign data_word_disparity = 4'b1100 + data_word[0] + data_word[1] + data_word[2] + data_word[3] + data_word[4] + data_word[5] + data_word[6] + data_word[7]; 111 | // Now work out what the output should be 112 | always @(posedge clk) begin 113 | if(blank == 1'b1) begin 114 | // In the control periods, all values have and have balanced bit count 115 | case(c) 116 | 2'b00 : begin 117 | encoded <= 10'b1101010100; 118 | end 119 | 2'b01 : begin 120 | encoded <= 10'b0010101011; 121 | end 122 | 2'b10 : begin 123 | encoded <= 10'b0101010100; 124 | end 125 | default : begin 126 | encoded <= 10'b1010101011; 127 | end 128 | endcase 129 | dc_bias <= {4{1'b0}}; 130 | end 131 | else begin 132 | if(dc_bias == 5'b00000 || data_word_disparity == 0) begin 133 | // dataword has no disparity 134 | if(data_word[8] == 1'b1) begin 135 | encoded <= {2'b01,data_word[7:0]}; 136 | dc_bias <= dc_bias + data_word_disparity; 137 | end 138 | else begin 139 | encoded <= {2'b10,data_word_inv[7:0]}; 140 | dc_bias <= dc_bias - data_word_disparity; 141 | end 142 | end 143 | else if((dc_bias[3] == 1'b0 && data_word_disparity[3] == 1'b0) || (dc_bias[3] == 1'b1 && data_word_disparity[3] == 1'b1)) begin 144 | encoded <= {1'b1,data_word[8],data_word_inv[7:0]}; 145 | dc_bias <= dc_bias + data_word[8] - data_word_disparity; 146 | end 147 | else begin 148 | encoded <= {1'b0,data_word}; 149 | dc_bias <= dc_bias - data_word_inv[8] + data_word_disparity; 150 | end 151 | end 152 | end 153 | 154 | 155 | endmodule 156 | -------------------------------------------------------------------------------- /src/tms9901.v: -------------------------------------------------------------------------------- 1 | // tms9901.v 2 | // EP 2019-10-19 3 | // Bringing together earlier scattered code to have a clear implementation 4 | // of the TMS9901. 5 | // Note: lacks the interrupt priority generation (IC lines below) to save logic 6 | // and time as this is not required for the TI-99/4A. 7 | 8 | module tms9901( 9 | input wire clk, 10 | input wire n_reset, 11 | input wire n_ce, 12 | input wire cruin, 13 | input wire cruclk, 14 | output wire cruout, 15 | input [4:0] S, 16 | output wire n_intreq, 17 | // output [3:0] IC, 18 | // IO pins 19 | input wire [6:1] n_INT, // 6 input only interrupt pins 20 | // These following 16 pins can be either inputs or outputs. 21 | // If they are ever written they become outputs, and only reset can change that. 22 | // Pins 7 to 15 have interrupt capability. 23 | output reg [15:0] POUT, 24 | input wire [15:0] PIN, 25 | output reg [15:0] DIR // Pin direction, 1=output 26 | ); 27 | 28 | `define TI994A_INTERRUPTS_ONLY 29 | 30 | // Interrupt mask bits are cru_bits[15:1] in accordance to the following table: 31 | // 1 n_int[1] 32 | // 2 n_int[2] 33 | // 3 n_int[3] (also timer interrupt) 34 | // 4 n_int[4] 35 | // 5 n_int[5] 36 | // 6 n_int[6] 37 | // 7 n_int7 /P15 38 | // 8 n_int8 /P14 39 | // 9 n_int9 /P13 40 | // 10 n_int10/P12 41 | // 11 n_int11/P11 42 | // 12 n_int12/P10 43 | // 13 n_int13/P9 44 | // 14 n_int14/P8 45 | // 15 n_int15/P7 46 | 47 | reg [31:0] cru_bits; // 32 write bits to 9901, when cru9901(0)='0' 48 | reg [15:0] cru9901_clock; // 15 write bits of 9901 when cru9901(0)='1' (bit 0 not used here) 49 | reg [13:0] decrementer; // 14 bit decrementer value 50 | reg [15:0] dec_read; // Decrementer read register (bits 14:1 are used) 51 | reg last_cruclk; 52 | // BUGBUG: In an actual TMS9901 every 64th clock decrements the decrementer 53 | // Here we use currently 8-bit counter to divide by 256. 54 | reg [7:0] clk_divider = 8'd0; 55 | reg timer_int_pending; 56 | reg disconnect_int3 = 1'b0; 57 | 58 | wire go_cruclk = cruclk && !last_cruclk; // 1 clock cycle long pulse 59 | 60 | // read logic, these are in a specific order. 61 | wire [0:31] read_bits = { 62 | cru_bits[0], n_INT[1], n_INT[2], n_INT[3], n_INT[4], n_INT[5], n_INT[6], PIN[15], 63 | PIN[14], PIN[13], PIN[12], PIN[11], PIN[10], PIN[9] , PIN[8], PIN[7], 64 | PIN[0], PIN[1], PIN[2], PIN[3], PIN[4], PIN[5], PIN[6], PIN[7], 65 | PIN[8], PIN[9], PIN[10], PIN[11], PIN[12], PIN[13], PIN[14], PIN[15] 66 | }; 67 | 68 | wire timer_mode = cru_bits[0] == 1'b1; 69 | 70 | assign cruout = n_ce ? 1'b1 : // Just return 1 when not selected, should be 1'bz but yosys is not cool with that 71 | (S == 5'd0 || S[4] == 1'b1 || !timer_mode) ? read_bits[S[4:0]] : 72 | (S == 5'd15 && timer_mode) ? n_intreq : // Timer mode addr 15 73 | dec_read[S]; 74 | 75 | // Interrupt reguest generation, active low output. There are 15 interrupt pins. 76 | `ifdef TI994A_INTERRUPTS_ONLY 77 | assign n_intreq = !( 78 | (cru_bits[ 1] & ~n_INT[1]) || // n_INT 1 (peripherals on TI-99/4A) 79 | (cru_bits[ 2] & ~n_INT[2]) || // n_INT 2 (VDP on TI-99/4A) 80 | (cru_bits[ 3] & timer_int_pending) 81 | ); 82 | `else 83 | assign n_intreq = !( 84 | (cru_bits[ 1] & ~n_INT[1]) || // n_INT 1 (peripherals on TI-99/4A) 85 | (cru_bits[ 2] & ~n_INT[2]) || // n_INT 2 (VDP on TI-99/4A) 86 | ((cru_bits[3] & ~n_INT[3] & ~disconnect_int3) && cru9901_clock[14:1]==14'h0000) || // pin n_INT 3 is not an interrupt if timer is active 87 | (cru_bits[ 4] & ~n_INT[4]) || // n_INT 4,5,6 88 | (cru_bits[ 5] & ~n_INT[5]) || // n_INT 4,5,6 89 | (cru_bits[ 6] & ~n_INT[6]) || // n_INT 4,5,6 90 | (cru_bits[ 7] & ~PIN[15]) || (cru_bits[ 8] & ~PIN[14]) || 91 | (cru_bits[ 9] & ~PIN[13]) || (cru_bits[10] & ~PIN[12]) || 92 | (cru_bits[11] & ~PIN[11]) || (cru_bits[12] & ~PIN[10]) || 93 | (cru_bits[13] & ~PIN[ 9]) || (cru_bits[14] & ~PIN[ 8]) || 94 | (cru_bits[15] & ~PIN[ 7]) || (cru_bits[ 3] & timer_int_pending)); 95 | `endif 96 | 97 | // output pins 98 | genvar i; 99 | generate 100 | for(i=0; i<16; i=i+1) begin : OUTPUT_PINS_BLOCK 101 | always @(posedge clk) begin 102 | POUT[i] <= DIR[i] ? cru_bits[16+i] : 1'b0; 103 | end 104 | end 105 | endgenerate 106 | 107 | 108 | 109 | always @(posedge clk) 110 | begin 111 | if (!n_reset) begin 112 | DIR <= 16'h0; // Initially pins are inputs 113 | cru_bits <= 32'h0; 114 | last_cruclk <= 1'b0; 115 | timer_int_pending <= 1'b0; 116 | cru9901_clock <= 16'd0; 117 | disconnect_int3 <= 1'b0; 118 | end else begin 119 | clk_divider <= clk_divider - 8'd1; 120 | 121 | last_cruclk <= cruclk; 122 | if (go_cruclk && !n_ce) begin 123 | // Write to a register 124 | if (timer_mode && S[4] == 1'b0) begin 125 | cru9901_clock[S[3:0]] <= cruin; 126 | if (S[3:0] == 4'd15 && cruin == 1'b0) begin 127 | // Software reset, reset pin directions to input 128 | DIR <= 16'h0; 129 | // Should we also reset interrupt masks? I do it here. However, timer values remain. 130 | cru_bits <= 32'h0; 131 | end 132 | end else begin 133 | cru_bits[S[4:0]] <= cruin; 134 | if (S == 5'd3 && !timer_mode) 135 | timer_int_pending <= 1'b0; // regardless of written data interrupt is cleared. 136 | if (S[4] == 1'b1) begin 137 | DIR[S[3:0]] <= 1'b1; // This pin became permanently an output 138 | end 139 | end 140 | end 141 | // decrementer loaded by writing 0 to CRU bit 0 or accessing bit higher than 15 142 | if (go_cruclk && !n_ce && ((S == 5'b00000 && cruin == 1'b0) || S[4]==1'b1) && timer_mode) begin 143 | decrementer = cru9901_clock[14:1]; 144 | cru_bits[0] <= 1'b0; 145 | end 146 | if (clk_divider == 8'd0) begin 147 | if (decrementer == 14'd0) begin 148 | decrementer = cru9901_clock[14:1]; 149 | timer_int_pending <= 1'b1; 150 | disconnect_int3 <= 1'b1; 151 | end else if (cru9901_clock[14:1] != 14'd0) begin 152 | decrementer = decrementer - 14'd1; 153 | end 154 | end 155 | if (!timer_mode) begin 156 | dec_read <= decrementer; // The read register updated when not in timer access mode 157 | end 158 | end 159 | end 160 | 161 | endmodule 162 | -------------------------------------------------------------------------------- /src/tms9919.v: -------------------------------------------------------------------------------- 1 | // File src/tms9919.vhd translated with vhd2vl v3.0 VHDL to Verilog RTL translator 2 | // vhd2vl settings: 3 | // * Verilog Module Declaration Style: 2001 4 | 5 | // vhd2vl is Free (libre) Software: 6 | // Copyright (C) 2001 Vincenzo Liguori - Ocean Logic Pty Ltd 7 | // http://www.ocean-logic.com 8 | // Modifications Copyright (C) 2006 Mark Gonzales - PMC Sierra Inc 9 | // Modifications (C) 2010 Shankar Giri 10 | // Modifications Copyright (C) 2002-2017 Larry Doolittle 11 | // http://doolittle.icarus.com/~larry/vhd2vl/ 12 | // Modifications (C) 2017 Rodrigo A. Melo 13 | // 14 | // vhd2vl comes with ABSOLUTELY NO WARRANTY. Always check the resulting 15 | // Verilog for correctness, ideally with a formal verification tool. 16 | // 17 | // You are welcome to redistribute vhd2vl under certain conditions. 18 | // See the license (GPLv2) file included with the source for details. 19 | 20 | // The result of translation follows. Its copyright status should be 21 | // considered unchanged from the original VHDL. 22 | 23 | //-------------------------------------------------------------------------------- 24 | // tms9919.vhd 25 | // 26 | // Implementation of the TMS9919 sound chip. 27 | // The module is not 100% compatible with the orignal design. 28 | // 29 | // This file is part of the ep994a design, a TI-99/4A clone 30 | // designed by Erik Piehl in October 2016. 31 | // Erik Piehl, Kauniainen, Finland, speccery@gmail.com 32 | // 33 | // This is copyrighted software. 34 | // Please see the file LICENSE for license terms. 35 | // 36 | // NO WARRANTY, THE SOURCE CODE IS PROVIDED "AS IS". 37 | // THE SOURCE IS PROVIDED WITHOUT ANY GUARANTEE THAT IT WILL WORK 38 | // FOR ANY PARTICULAR USE. IN NO EVENT IS THE AUTHOR LIABLE FOR ANY 39 | // DIRECT OR INDIRECT DAMAGE CAUSED BY THE USE OF THE SOFTWARE. 40 | // 41 | // Synthesized with Xilinx ISE 14.7. 42 | //--------------------------------------------------------------------------------- 43 | 44 | module tms9919( 45 | input wire clk, 46 | input wire reset, 47 | input wire we, 48 | input wire [7:0] data_in, 49 | output reg [7:0] dac_out 50 | ); 51 | 52 | // 25MHz clock 53 | // reset active high 54 | // high for one clock for a write to sound chip 55 | // data bus in 56 | 57 | 58 | // output to audio DAC 59 | 60 | reg [6:0] latch_high; // written when MSB (bit 7) is set 61 | reg [9:0] tone1_div_val; // divider value 62 | reg [3:0] tone1_att; // attenuator value 63 | reg [9:0] tone2_div_val; // divider value 64 | reg [3:0] tone2_att; // attenuator value 65 | reg [9:0] tone3_div_val; // divider value 66 | reg [3:0] tone3_att; // attenuator value 67 | reg [3:0] noise_div_val; // Noise generator divisor 68 | reg [3:0] noise_att; // attenuator value 69 | reg [9:0] tone1_counter; 70 | reg [9:0] tone2_counter; 71 | reg [9:0] tone3_counter; 72 | reg [10:0] noise_counter; 73 | reg [10:0] master_divider; 74 | reg tone1_out; 75 | reg tone2_out; 76 | reg tone3_out; 77 | reg noise_out; 78 | reg bump_noise; 79 | reg [15:0] noise_lfsr; 80 | reg [3:0] add_value; 81 | reg add_flag; 82 | parameter [2:0] 83 | chan0 = 0, 84 | chan1 = 1, 85 | chan2 = 2, 86 | noise = 3, 87 | prepare = 4, 88 | output_stuff = 5; 89 | 90 | reg [2:0] tone_proc; 91 | reg [7:0] acc; 92 | 93 | // type volume_lookup_array is array (0 to 15) of std_logic_vector(7 downto 0); 94 | // constant volume_lookup : volume_lookup_array := ( 95 | // "00111100", "00110000", "00100010", "00010011", 96 | // "00001111", "00001111", "00001111", "00001111", 97 | // "00001100", "00001000", "00000110", "00000100", 98 | // "00000011", "00000010", "00000001", "00000000" 99 | // ); 100 | 101 | reg [7:0] volume_lookup[0:15]; 102 | initial begin 103 | volume_lookup[0] = 60; 104 | volume_lookup[1] = 48; 105 | volume_lookup[2] = 34; 106 | volume_lookup[3] = 19; 107 | volume_lookup[4] = 15; 108 | volume_lookup[5] = 15; 109 | volume_lookup[6] = 15; 110 | volume_lookup[7] = 15; 111 | volume_lookup[8] = 12; 112 | volume_lookup[9] = 8; 113 | volume_lookup[10] = 6; 114 | volume_lookup[11] = 4; 115 | volume_lookup[12] = 3; 116 | volume_lookup[13] = 2; 117 | volume_lookup[14] = 1; 118 | volume_lookup[15] = 0; 119 | end 120 | 121 | always @(posedge clk, posedge reset) begin : P1 122 | reg k; 123 | 124 | if(reset == 1'b1) begin 125 | latch_high <= {7{1'b0}}; 126 | tone1_att <= 4'b1111; 127 | // off 128 | tone2_att <= 4'b1111; 129 | // off 130 | tone3_att <= 4'b1111; 131 | // off 132 | noise_att <= 4'b1111; 133 | // off 134 | master_divider <= 0; 135 | add_value <= {4{1'b0}}; 136 | add_flag <= 1'b0; 137 | end else begin 138 | if(we == 1'b1) begin 139 | // data write 140 | if(data_in[7] == 1'b1) begin 141 | latch_high <= data_in[6:0]; 142 | // store for later re-use 143 | case(data_in[6:4]) 144 | 3'b000 : begin 145 | tone1_div_val[3:0] <= data_in[3:0]; 146 | end 147 | 3'b001 : begin 148 | tone1_att <= data_in[3:0]; 149 | end 150 | 3'b010 : begin 151 | tone2_div_val[3:0] <= data_in[3:0]; 152 | end 153 | 3'b011 : begin 154 | tone2_att <= data_in[3:0]; 155 | end 156 | 3'b100 : begin 157 | tone3_div_val[3:0] <= data_in[3:0]; 158 | end 159 | 3'b101 : begin 160 | tone3_att <= data_in[3:0]; 161 | end 162 | 3'b110 : begin 163 | noise_div_val <= data_in[3:0]; 164 | noise_lfsr <= 16'h0001; 165 | // initialize noise generator 166 | end 167 | 3'b111 : begin 168 | noise_att <= data_in[3:0]; 169 | end 170 | default : begin 171 | end 172 | endcase 173 | end 174 | else begin 175 | // Write with MSB set to zero. Use latched register value. 176 | case(latch_high[6:4]) 177 | 3'b000 : begin 178 | tone1_div_val[9:4] <= data_in[5:0]; 179 | end 180 | 3'b010 : begin 181 | tone2_div_val[9:4] <= data_in[5:0]; 182 | end 183 | 3'b100 : begin 184 | tone3_div_val[9:4] <= data_in[5:0]; 185 | end 186 | default : begin 187 | end 188 | endcase 189 | end 190 | end 191 | 192 | // Ok. Now handle the actual sound generators. 193 | // The input freuency on the TI-99/4A is 3.58MHz which is divided by 32, this is 111875Hz. 194 | // Our clock is 25MHz. As the first approximation we will divide 25MHz by 223 (exact 223.46). 195 | // That gives a clock of 112107Hz - not sure if this is good enough. 196 | // After checking that actually yields half of the desired frequency. So let's go with 112. 197 | // This would give us 25e6/(2*112) = 111607 Hz. The error is 111875/111607 = 1.0024, so 0.2%. 198 | master_divider <= master_divider + 1; 199 | if(master_divider >= 111) begin 200 | master_divider <= 0; 201 | tone1_counter <= (tone1_counter) - 1; 202 | // tone1_counter'length)); 203 | tone2_counter <= (tone2_counter) - 1; 204 | tone3_counter <= (tone3_counter) - 1; 205 | noise_counter <= (noise_counter) - 1; 206 | if((tone1_counter) == 0) begin 207 | tone1_out <= ~tone1_out; 208 | tone1_counter <= tone1_div_val; 209 | end 210 | if((tone2_counter) == 0) begin 211 | tone2_out <= ~tone2_out; 212 | tone2_counter <= tone2_div_val; 213 | end 214 | bump_noise <= 1'b0; 215 | if((tone3_counter) == 0) begin 216 | tone3_out <= ~tone3_out; 217 | tone3_counter <= tone3_div_val; 218 | if(noise_div_val[1:0] == 2'b11) begin 219 | bump_noise <= 1'b1; 220 | end 221 | end 222 | if(noise_counter[8:0] == 9'b000000000) begin 223 | case(noise_div_val[1:0]) 224 | 2'b00 : begin 225 | bump_noise <= 1'b1; 226 | // 512 227 | end 228 | 2'b01 : begin 229 | if(noise_counter[9] == 1'b0) begin 230 | // 1024 231 | bump_noise <= 1'b1; 232 | end 233 | end 234 | 2'b10 : begin 235 | if(noise_counter[10:9] == 2'b00) begin 236 | // 2048 237 | bump_noise <= 1'b1; 238 | end 239 | end 240 | default : begin 241 | end 242 | endcase 243 | end 244 | if(bump_noise == 1'b1) begin 245 | if(noise_div_val[2] == 1'b1) begin 246 | // white noise 247 | k = noise_lfsr[14] ^ noise_lfsr[13]; 248 | end 249 | else begin 250 | k = noise_lfsr[14]; 251 | // just feedback 252 | end 253 | noise_lfsr <= {noise_lfsr[14:0],k}; 254 | if(noise_lfsr[14] == 1'b1) begin 255 | noise_out <= ~noise_out; 256 | end 257 | end 258 | end 259 | if(add_flag == 1'b1) begin 260 | acc <= acc + volume_lookup[add_value]; 261 | end 262 | else begin 263 | acc <= acc - volume_lookup[add_value]; 264 | end 265 | // Ok now combine the tone_out values 266 | case(tone_proc) 267 | chan0 : begin 268 | add_value <= tone1_att; 269 | add_flag <= tone1_out; 270 | tone_proc <= chan1; 271 | end 272 | chan1 : begin 273 | add_value <= tone2_att; 274 | add_flag <= tone2_out; 275 | tone_proc <= chan2; 276 | end 277 | chan2 : begin 278 | add_value <= tone3_att; 279 | add_flag <= tone3_out; 280 | tone_proc <= noise; 281 | end 282 | noise : begin 283 | add_value <= noise_att; 284 | add_flag <= noise_out; 285 | tone_proc <= prepare; 286 | end 287 | prepare : begin 288 | // During this step the acc gets updated with noise value 289 | add_value <= 4'b1111; 290 | // silence, this stage is just a wait state to pick up noise 291 | tone_proc <= output_stuff; 292 | end 293 | default : begin 294 | // output_stuff stage 295 | dac_out <= acc; 296 | add_value <= 4'b1111; 297 | // no change 298 | acc <= 8'h80; 299 | tone_proc <= chan0; 300 | end 301 | endcase 302 | end 303 | end 304 | 305 | 306 | endmodule 307 | -------------------------------------------------------------------------------- /src/vga2dvid.v: -------------------------------------------------------------------------------- 1 | // File vga2dvid.vhd translated with vhd2vl v3.0 VHDL to Verilog RTL translator 2 | // vhd2vl settings: 3 | // * Verilog Module Declaration Style: 2001 4 | 5 | // vhd2vl is Free (libre) Software: 6 | // Copyright (C) 2001 Vincenzo Liguori - Ocean Logic Pty Ltd 7 | // http://www.ocean-logic.com 8 | // Modifications Copyright (C) 2006 Mark Gonzales - PMC Sierra Inc 9 | // Modifications (C) 2010 Shankar Giri 10 | // Modifications Copyright (C) 2002-2017 Larry Doolittle 11 | // http://doolittle.icarus.com/~larry/vhd2vl/ 12 | // Modifications (C) 2017 Rodrigo A. Melo 13 | // 14 | // vhd2vl comes with ABSOLUTELY NO WARRANTY. Always check the resulting 15 | // Verilog for correctness, ideally with a formal verification tool. 16 | // 17 | // You are welcome to redistribute vhd2vl under certain conditions. 18 | // See the license (GPLv2) file included with the source for details. 19 | 20 | // The result of translation follows. Its copyright status should be 21 | // considered unchanged from the original VHDL. 22 | 23 | //------------------------------------------------------------------------------ 24 | // Engineer: Mike Field 25 | // Description: Converts VGA signals into DVID bitstreams. 26 | // 27 | // 'clk_shift' 10x clk_pixel for SDR 28 | // 'clk_shift' 5x clk_pixel for DDR 29 | // 30 | // 'blank' should be asserted during the non-display 31 | // portions of the frame 32 | //------------------------------------------------------------------------------ 33 | // See: http://hamsterworks.co.nz/mediawiki/index.php/Dvid_test 34 | // http://hamsterworks.co.nz/mediawiki/index.php/FPGA_Projects 35 | // 36 | // Copyright (c) 2012 Mike Field 37 | // 38 | // Permission is hereby granted, free of charge, to any person obtaining a copy 39 | // of this software and associated documentation files (the "Software"), to deal 40 | // in the Software without restriction, including without limitation the rights 41 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 42 | // copies of the Software, and to permit persons to whom the Software is 43 | // furnished to do so, subject to the following conditions: 44 | // 45 | // The above copyright notice and this permission notice shall be included in 46 | // all copies or substantial portions of the Software. 47 | // 48 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 49 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 50 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 51 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 52 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 53 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 54 | // THE SOFTWARE. 55 | // 56 | // takes VGA input and prepares output 57 | // for SDR buffer, which send 1 bit per 1 clock period output out_red(0), out_green(0), ... etc. 58 | // for DDR buffers, which send 2 bits per 1 clock period output out_red(1 downto 0), ... 59 | // EMARD unified SDR and DDR into one module 60 | // no timescale needed 61 | 62 | module vga2dvid( 63 | input wire clk_pixel, 64 | input wire clk_shift, 65 | input wire [C_depth - 1:0] in_red, 66 | input wire [C_depth - 1:0] in_green, 67 | input wire [C_depth - 1:0] in_blue, 68 | input wire in_blank, 69 | input wire in_hsync, 70 | input wire in_vsync, 71 | output wire [9:0] outp_red, 72 | output wire [9:0] outp_green, 73 | output wire [9:0] outp_blue, 74 | output wire [1:0] out_red, 75 | output wire [1:0] out_green, 76 | output wire [1:0] out_blue, 77 | output wire [1:0] out_clock 78 | ); 79 | 80 | parameter C_shift_clock_synchronizer=1'b1; 81 | parameter C_parallel=1'b1; 82 | parameter C_serial=1'b1; 83 | parameter C_ddr=1'b0; 84 | parameter [31:0] C_depth=8; 85 | // VGA pixel clock, 25 MHz for 640x480 86 | // SDR: 10x clk_pixel, DDR: 5x clk_pixel, in phase with clk_pixel 87 | // parallel outputs 88 | // serial outputs 89 | 90 | 91 | 92 | wire [9:0] encoded_red; wire [9:0] encoded_green; wire [9:0] encoded_blue; 93 | reg [9:0] latched_red = 1'b0; reg [9:0] latched_green = 1'b0; reg [9:0] latched_blue = 1'b0; 94 | reg [9:0] shift_red = 1'b0; reg [9:0] shift_green = 1'b0; reg [9:0] shift_blue = 1'b0; 95 | parameter C_shift_clock_initial = 10'b0000011111; 96 | reg [9:0] shift_clock = C_shift_clock_initial; 97 | reg R_shift_clock_off_sync = 1'b0; 98 | reg [7:0] R_shift_clock_synchronizer = 1'b0; 99 | reg [6:0] R_sync_fail; // counts sync fails, after too many, reinitialize shift_clock 100 | parameter c_red = 1'b0; 101 | parameter c_green = 1'b0; 102 | wire [1:0] c_blue; 103 | wire [7:0] red_d; 104 | wire [7:0] green_d; 105 | wire [7:0] blue_d; 106 | 107 | assign c_blue = {in_vsync,in_hsync}; 108 | assign red_d[7:8 - C_depth] = in_red[C_depth - 1:0]; 109 | assign green_d[7:8 - C_depth] = in_green[C_depth - 1:0]; 110 | assign blue_d[7:8 - C_depth] = in_blue[C_depth - 1:0]; 111 | // fill vacant low bits with value repeated (so min/max value is always 0 or 255) 112 | generate if (C_depth < 8) begin: G_vacant_bits 113 | genvar i; 114 | generate for (i=0; i <= 8 - C_depth - 1; i = i + 1) begin: G_bits 115 | assign red_d[i] = in_red[0]; 116 | assign green_d[i] = in_green[0]; 117 | assign blue_d[i] = in_blue[0]; 118 | end 119 | endgenerate 120 | end 121 | endgenerate 122 | generate if (C_shift_clock_synchronizer == 1'b1) begin: G_shift_clock_synchronizer 123 | // sampler verifies is shift_clock state synchronous with pixel_clock 124 | always @(posedge clk_pixel) begin 125 | // does 0 to 1 transition at bits 5 downto 4 happen at rising_edge of clk_pixel? 126 | // if shift_clock = C_shift_clock_initial then 127 | if(shift_clock[5:4] == C_shift_clock_initial[5:4]) begin 128 | // same as above line but simplified 129 | R_shift_clock_off_sync <= 1'b0; 130 | end 131 | else begin 132 | R_shift_clock_off_sync <= 1'b1; 133 | end 134 | end 135 | 136 | // every N cycles of clk_shift: signal to skip 1 cycle in order to get in sync 137 | always @(posedge clk_shift) begin 138 | if(R_shift_clock_off_sync == 1'b1) begin 139 | if(R_shift_clock_synchronizer[(7)] == 1'b1) begin 140 | R_shift_clock_synchronizer <= {8{1'b0}}; 141 | end 142 | else begin 143 | R_shift_clock_synchronizer <= R_shift_clock_synchronizer + 1; 144 | end 145 | end 146 | else begin 147 | R_shift_clock_synchronizer <= {8{1'b0}}; 148 | end 149 | end 150 | 151 | end 152 | endgenerate 153 | // shift_clock_synchronizer 154 | tmds_encoder u21( 155 | .clk(clk_pixel), 156 | .data(red_d), 157 | .c(c_red), 158 | .blank(in_blank), 159 | .encoded(encoded_red)); 160 | 161 | tmds_encoder u22( 162 | .clk(clk_pixel), 163 | .data(green_d), 164 | .c(c_green), 165 | .blank(in_blank), 166 | .encoded(encoded_green)); 167 | 168 | tmds_encoder u23( 169 | .clk(clk_pixel), 170 | .data(blue_d), 171 | .c(c_blue), 172 | .blank(in_blank), 173 | .encoded(encoded_blue)); 174 | 175 | always @(posedge clk_pixel) begin 176 | latched_red <= encoded_red; 177 | latched_green <= encoded_green; 178 | latched_blue <= encoded_blue; 179 | end 180 | 181 | generate if (C_parallel == 1'b1) begin: G_parallel 182 | assign outp_red = latched_red; 183 | assign outp_green = latched_green; 184 | assign outp_blue = latched_blue; 185 | end 186 | endgenerate 187 | generate if ((C_serial & ~C_ddr) == 1'b1) begin: G_SDR 188 | always @(posedge clk_shift) begin 189 | //if shift_clock = "0000011111" then 190 | if(shift_clock[5:4] == C_shift_clock_initial[5:4]) begin 191 | // same as above line but simplified 192 | shift_red <= latched_red; 193 | shift_green <= latched_green; 194 | shift_blue <= latched_blue; 195 | end 196 | else begin 197 | shift_red <= {1'b0,shift_red[9:1]}; 198 | shift_green <= {1'b0,shift_green[9:1]}; 199 | shift_blue <= {1'b0,shift_blue[9:1]}; 200 | end 201 | if(R_shift_clock_synchronizer[(7)] == 1'b0) begin 202 | shift_clock <= {shift_clock[0],shift_clock[9:1]}; 203 | end 204 | else begin 205 | // synchronization failed. 206 | // after too many fails, reinitialize shift_clock 207 | if(R_sync_fail[(6)] == 1'b1) begin 208 | shift_clock <= C_shift_clock_initial; 209 | R_sync_fail <= {7{1'b0}}; 210 | end 211 | else begin 212 | R_sync_fail <= R_sync_fail + 1; 213 | end 214 | end 215 | end 216 | 217 | end 218 | endgenerate 219 | generate if ((C_serial & C_ddr) == 1'b1) begin: G_DDR 220 | always @(posedge clk_shift) begin 221 | //if shift_clock = "0000011111" then 222 | if(shift_clock[5:4] == C_shift_clock_initial[5:4]) begin 223 | // same as above line but simplified 224 | shift_red <= latched_red; 225 | shift_green <= latched_green; 226 | shift_blue <= latched_blue; 227 | end 228 | else begin 229 | shift_red <= {2'b00,shift_red[9:2]}; 230 | shift_green <= {2'b00,shift_green[9:2]}; 231 | shift_blue <= {2'b00,shift_blue[9:2]}; 232 | end 233 | if(R_shift_clock_synchronizer[(7)] == 1'b0) begin 234 | shift_clock <= {shift_clock[1:0],shift_clock[9:2]}; 235 | end 236 | else begin 237 | // synchronization failed. 238 | // after too many fails, reinitialize shift_clock 239 | if(R_sync_fail[(6)] == 1'b1) begin 240 | shift_clock <= C_shift_clock_initial; 241 | R_sync_fail <= {7{1'b0}}; 242 | end 243 | else begin 244 | R_sync_fail <= R_sync_fail + 1; 245 | end 246 | end 247 | end 248 | 249 | end 250 | endgenerate 251 | // SDR: use only bit 0 from each out_* channel 252 | // DDR: 2 bits per 1 clock period, 253 | // (one bit output on rising edge, other on falling edge of clk_shift) 254 | generate if (C_serial == 1'b1) begin: G_serial 255 | assign out_red = shift_red[1:0]; 256 | assign out_green = shift_green[1:0]; 257 | assign out_blue = shift_blue[1:0]; 258 | assign out_clock = shift_clock[1:0]; 259 | end 260 | endgenerate 261 | 262 | endmodule 263 | -------------------------------------------------------------------------------- /src/vga_sync.v: -------------------------------------------------------------------------------- 1 | // File src/vga_sync.vhd translated with vhd2vl v3.0 VHDL to Verilog RTL translator 2 | module VGA_SYNC( 3 | input wire clk, 4 | output wire video_on, 5 | output reg horiz_sync, 6 | output reg vert_sync, 7 | output wire [9:0] pixel_row, 8 | output wire [9:0] pixel_column 9 | ); 10 | 11 | reg [9:0] h_count=0; 12 | reg [9:0] v_count=0; 13 | assign pixel_column = h_count; 14 | assign pixel_row = v_count; 15 | 16 | assign video_on = (h_count < 640) && (v_count < 480); 17 | //Generate Horizontal and Vertical Timing Signals for Video Signal 18 | // H_count counts pixels (640 + extra time for sync signals) 19 | // 20 | // Horiz_sync ------------------------------------__________-------- 21 | // H_count 0 640 659 755 799 22 | // 23 | always @(posedge clk) 24 | begin 25 | if(h_count == 10'd799) begin 26 | h_count <= 10'd0; 27 | v_count <= (v_count == 10'd519) ? 10'd0 : v_count + 10'd1; 28 | end else begin 29 | h_count <= h_count + 10'd1; 30 | end 31 | end 32 | 33 | // Generate sync signals 34 | always @(posedge clk) 35 | begin 36 | horiz_sync <= (h_count >= 659 && h_count <= 755) ? 1'b0 : 1'b1; 37 | vert_sync <= (v_count >= 493 && v_count <= 494) ? 1'b0 : 1'b1; 38 | end 39 | 40 | endmodule 41 | -------------------------------------------------------------------------------- /ti994a_ulx3s.bit: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Speccery/icy99/512915b98e8088e6a23d63ffd55bed9a84c25634/ti994a_ulx3s.bit -------------------------------------------------------------------------------- /tipi/crubits.v: -------------------------------------------------------------------------------- 1 | `ifndef _crubits_vh_ 2 | `define _crubits_vh_ 3 | 4 | // 4 bits of CRU 5 | module crubits( 6 | // select 7 | input [0:3]cru_base, 8 | // TI clock input 9 | input ti_cru_clk, 10 | // TI mem enable (when high, not a memory operation 11 | input ti_memen, 12 | // FPGA synchronous clock 13 | input clk, 14 | // cru_address 15 | input [0:14]addr, 16 | // input 17 | input ti_cru_out, 18 | // input to TMS9900 to allow reading curring addressed bit 19 | output ti_cru_in, 20 | // bits 21 | output [0:3]bits 22 | ); 23 | 24 | reg [0:3] bits_q; 25 | reg last_cruclk; 26 | 27 | always @(posedge clk) begin 28 | 29 | if (!last_cruclk && ti_cru_clk && (addr[0:3] == 4'b0001) && (addr[4:7] == cru_base)) begin 30 | if (addr[8:14] == 7'h00) bits_q[0] <= ti_cru_out; 31 | else if (addr[8:14] == 7'h01) bits_q[1] <= ti_cru_out; 32 | else if (addr[8:14] == 7'h02) bits_q[2] <= ti_cru_out; 33 | else if (addr[8:14] == 7'h03) bits_q[3] <= ti_cru_out; 34 | end 35 | last_cruclk <= ti_cru_clk; 36 | end 37 | 38 | assign bits = bits_q; 39 | 40 | // Here we just show the relevant bit. The higher level logic uses it when relevant. 41 | // There is no way of knowing when CRU bits are read by the CPU, so reading cannot cause state changes. 42 | assign ti_cru_in = bits_q[ addr[13:14] ]; 43 | 44 | endmodule 45 | 46 | `endif 47 | -------------------------------------------------------------------------------- /tipi/latch_8bit.v: -------------------------------------------------------------------------------- 1 | `ifndef _latch_8bit_vh_ 2 | `define _latch_8bit_vh_ 3 | 4 | // Simple 8 bit latch 5 | module latch_8bit( 6 | // clock input 7 | input le, 8 | // input 9 | input [0:7]din, 10 | // output 11 | output [0:7]dout 12 | ); 13 | 14 | reg [0:7] latch_q; 15 | 16 | always @(posedge le) begin 17 | latch_q <= din; 18 | end 19 | 20 | assign dout = latch_q; 21 | 22 | endmodule 23 | 24 | `endif 25 | -------------------------------------------------------------------------------- /tipi/mux2_8bit.v: -------------------------------------------------------------------------------- 1 | `ifndef _rreg_mux_vh_ 2 | `define _rreg_mux_vh_ 3 | 4 | module mux2_8bit(a_addr, a, b_addr, b, c_addr, c, d_addr, d, o); 5 | input a_addr; 6 | input b_addr; 7 | input c_addr; 8 | input d_addr; 9 | input [7:0]a; 10 | input [7:0]b; 11 | input [7:0]c; 12 | input [7:0]d; 13 | output [7:0]o; 14 | reg [7:0]tmp; 15 | 16 | always @(a_addr, b_addr, c_addr, d_addr) begin 17 | if (a_addr) tmp <= a; 18 | else if (b_addr) tmp <= b; 19 | else if (c_addr) tmp <= c; 20 | else if (d_addr) tmp <= d; 21 | else tmp <= 8'h00; 22 | end 23 | 24 | assign o = tmp; 25 | 26 | endmodule 27 | 28 | `endif 29 | -------------------------------------------------------------------------------- /tipi/shift_pload_sout.v: -------------------------------------------------------------------------------- 1 | `ifndef _shift_pload_sout_vh_ 2 | `define _shift_pload_sout_vh_ 3 | 4 | module shift_pload_sout ( 5 | // Clock for shifting 6 | input clk, 7 | // Select 8 | input select, 9 | // load tmp with data to shift out. 10 | input aload, 11 | // Data to load from 12 | input [7:0]data, 13 | // output bit from the left. 14 | output sout 15 | ); 16 | 17 | reg [8:0]tmp; 18 | 19 | always @(posedge clk) begin 20 | if (aload && select) tmp = { data, ^data }; 21 | else if (select) tmp = { tmp[7:0], 1'b0 }; 22 | end 23 | 24 | assign sout = tmp[8]; 25 | 26 | endmodule 27 | 28 | `endif 29 | -------------------------------------------------------------------------------- /tipi/shift_sin_pout.v: -------------------------------------------------------------------------------- 1 | `ifndef _shift_sin_pout_vh_ 2 | `define _shift_sin_pout_vh_ 3 | 4 | // 8 bit serial in, parallel out shift register. 5 | module shift_sin_pout( 6 | // clock input 7 | input clk, 8 | // select line 9 | input select, 10 | // latch data to expose internal shifter. 11 | input le, 12 | // input 13 | input din, 14 | // output 15 | output [0:7]dout, 16 | // parity signal 17 | output parity 18 | ); 19 | 20 | reg [0:7] latch_q; 21 | reg [0:7] shift_q; 22 | 23 | always @(posedge clk) begin 24 | if (select) begin 25 | if (le) latch_q <= shift_q; 26 | else shift_q <= { shift_q[1:7], din }; 27 | end 28 | end 29 | 30 | assign dout = latch_q; 31 | assign parity = ^shift_q; 32 | 33 | endmodule 34 | 35 | `endif 36 | -------------------------------------------------------------------------------- /tipi/tipi_module.v: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ps 2 | ////////////////////////////////////////////////////////////////////////////////// 3 | // Company: 4 | // Engineer: 5 | // 6 | // Create Date: 13:20:25 07/15/2017 7 | // Design Name: 8 | // Module Name: tipi_top 9 | // Project Name: 10 | // Target Devices: 11 | // Tool versions: 12 | // Description: 13 | // 14 | // Dependencies: 15 | // 16 | // Revision: 17 | // Revision 0.01 - File Created 18 | // Additional Comments: 19 | // 20 | ////////////////////////////////////////////////////////////////////////////////// 21 | `include "crubits.v" 22 | `include "latch_8bit.v" 23 | `include "shift_pload_sout.v" 24 | `include "shift_sin_pout.v" 25 | `include "tristate_8bit.v" 26 | `include "mux2_8bit.v" 27 | module tipi_module( 28 | input clk, // out synchronous clock 29 | output led0, 30 | 31 | input[0:3] crub, // CRU base 32 | 33 | output db_dir, 34 | output db_en, 35 | output dsr_b0, 36 | output dsr_b1, 37 | output dsr_en, // Zero when accessing TIPI DSR ROM 38 | output ioreg_en, // Zero when accessing TIPI registers (read or write) 39 | output tipi_enabled, // When high TIPI is enabled (i.e. CRU bit is set) 40 | 41 | input r_clk, 42 | // 0 = Data or 1 = Control byte selection 43 | input r_cd, 44 | input r_dout, 45 | input r_le, 46 | // R|T 0 = RPi or 1 = TI originating data 47 | input r_rt, 48 | output r_din, 49 | output r_reset, 50 | 51 | input ti_cruclk, 52 | input ti_dbin, 53 | input ti_memen, 54 | input ti_we, 55 | output ti_cruin, 56 | input ti_cruout, 57 | // output ti_extint, 58 | 59 | input[0:15] ti_a, 60 | input [0:7] ti_din, 61 | output [0:7] ti_dout 62 | ); 63 | 64 | // Unused 65 | // assign ti_extint = 1'bz; // try to avoid triggering this interrupt ( temporarily an input ) 66 | 67 | // Process CRU bits 68 | wire [0:3]cru_state; 69 | crubits cru( 70 | .cru_base(crub), 71 | .ti_cru_clk(ti_cruclk), 72 | .ti_memen(ti_memen), 73 | .clk(clk), 74 | .addr(ti_a[0:14]), 75 | .ti_cru_out(ti_cruout), 76 | .ti_cru_in(ti_cruin), 77 | .bits(cru_state)); 78 | wire cru_dev_en = cru_state[0]; 79 | assign tipi_enabled = cru_dev_en; 80 | 81 | assign r_reset = ~cru_state[1]; 82 | // For a 32k 27C256 chip, these control bank switching. 83 | assign dsr_b0 = cru_state[2]; 84 | assign dsr_b1 = cru_state[3]; 85 | // For a 8k 27C64 chip, these need to stay constant 86 | // assign dsr_b0 = 1'bz; // not connected on 27C64 87 | // assign dsr_b1 = 1'b1; // Active LOW is PGM on 27C64 88 | 89 | // Latches && Shift Registers for TI to RPi communication - TC & TD 90 | 91 | // Register selection: 92 | // r_rt and r_dc combine to select the rd rc td and tc registers. 93 | // we will assert that r_rt == 0 is RPi output register 94 | // r_rt == 1 is TI output register 95 | // r_dc == 0 is data register 96 | // r_dc == 1 is control register 97 | // The following aliases should help. 98 | wire tipi_rc = ~r_rt && ~r_cd; 99 | wire tipi_rd = ~r_rt && r_cd; 100 | wire tipi_tc = r_rt && ~r_cd; 101 | wire tipi_td = r_rt && r_cd; 102 | 103 | // address comparisons 104 | wire rc_addr = ti_a == 16'h5ff9; 105 | wire rd_addr = ti_a == 16'h5ffb; 106 | wire tc_addr = ti_a == 16'h5ffd; 107 | wire td_addr = ti_a == 16'h5fff; 108 | 109 | assign ioreg_en = !(cru_dev_en && (rc_addr || rd_addr || tc_addr || td_addr)); // Accessing TIPI memory mapped registers 110 | 111 | // TD Latch 112 | // wire tipi_td_le = (cru_dev_en && ~ti_we && ~ti_memen && td_addr); 113 | // wire [0:7]rpi_td; 114 | // latch_8bit td(tipi_td_le, ti_din, rpi_td); 115 | 116 | // TC Latch 117 | // wire tipi_tc_le = (cru_dev_en && ~ti_we && ~ti_memen && tc_addr); 118 | // wire [0:7]rpi_tc; 119 | // latch_8bit tc(tipi_tc_le, ti_din, rpi_tc); 120 | 121 | reg [0:7] rpi_td; 122 | reg [0:7] rpi_tc; 123 | 124 | always @(posedge clk) 125 | begin 126 | if (cru_dev_en && ~ti_we && ~ti_memen) begin 127 | if( tc_addr) 128 | rpi_tc <= ti_din; 129 | if (td_addr) 130 | rpi_td <= ti_din; 131 | end 132 | end 133 | 134 | 135 | // TD Shift output 136 | wire td_out; 137 | shift_pload_sout shift_td(r_clk, tipi_td, r_le, rpi_td, td_out); 138 | 139 | // TC Shift output 140 | wire tc_out; 141 | shift_pload_sout shift_tc(r_clk, tipi_tc, r_le, rpi_tc, tc_out); 142 | 143 | 144 | // Data from the RPi, to be read by the TI. 145 | 146 | // RD 147 | wire [0:7]tipi_db_rd; 148 | wire rd_parity; 149 | shift_sin_pout shift_rd(r_clk, tipi_rd, r_le, r_dout, tipi_db_rd, rd_parity); 150 | 151 | // RC 152 | wire [0:7]tipi_db_rc; 153 | wire rc_parity; 154 | shift_sin_pout shift_rc(r_clk, tipi_rc, r_le, r_dout, tipi_db_rc, rc_parity); 155 | 156 | // Select if output is from the data or control register 157 | reg r_din_mux; 158 | always @(posedge r_clk) begin 159 | if (r_rt & r_cd) r_din_mux <= td_out; 160 | else if (r_rt & ~r_cd) r_din_mux <= tc_out; 161 | else if (~r_rt & r_cd) r_din_mux <= rd_parity; 162 | else r_din_mux <= rc_parity; 163 | end 164 | assign r_din = r_din_mux; 165 | 166 | 167 | //-- Databus control 168 | wire tipi_read = cru_dev_en && ~ti_memen && ti_dbin; 169 | wire tipi_dsr_en = tipi_read && ti_a >= 16'h4000 && ti_a < 16'h5ff8; 170 | 171 | // drive the dsr eprom oe and cs lines. 172 | assign dsr_en = ~(tipi_dsr_en); 173 | // drive the 74hct245 oe and dir lines. 174 | assign db_en = ~(cru_dev_en && ti_a >= 16'h4000 && ti_a < 16'h6000); 175 | assign db_dir = tipi_read; 176 | 177 | // register to databus output selection 178 | wire [0:7]rreg_mux_out; 179 | mux2_8bit rreg_mux(rc_addr, tipi_db_rc, rd_addr, tipi_db_rd, tc_addr, rpi_tc, td_addr, rpi_td, rreg_mux_out); 180 | 181 | /* We don't need the 3-state stuff with the FPGA 182 | 183 | wire [0:7]tp_d_buf; 184 | wire dbus_ts_en = cru_state[0] && ~ti_memen && ti_dbin && ( ti_a >= 16'h5ff8 && ti_a < 16'h6000 ); 185 | 186 | tristate_8bit dbus_ts(dbus_ts_en, rreg_mux_out, tp_d_buf); 187 | assign ti_dout = tp_d_buf; 188 | */ 189 | assign ti_dout = rreg_mux_out; 190 | 191 | 192 | assign led0 = cru_state[0]; // && db_en; 193 | 194 | 195 | endmodule 196 | -------------------------------------------------------------------------------- /tipi/tristate_8bit.v: -------------------------------------------------------------------------------- 1 | `ifndef _tristate_8bit_vh_ 2 | `define _tristate_8bit_vh_ 3 | 4 | module tristate_8bit( 5 | input T, 6 | input [7:0] I, 7 | output [7:0] O 8 | ); 9 | 10 | assign O = T ? I: 8'bZ; 11 | 12 | endmodule 13 | 14 | `endif 15 | -------------------------------------------------------------------------------- /tools/editrom.c: -------------------------------------------------------------------------------- 1 | // editrom.c 2 | // EP 2020-12-23 3 | // Edit the TI-99/4A ROM to contain new wonderful instructions 4 | // to speed up processing of GPL. 5 | 6 | #include 7 | #include // memcpy 8 | 9 | void modify(unsigned char *addr, int offset, unsigned short w) { 10 | addr[offset] = w >> 8; 11 | addr[offset+1] = w & 0xFF; 12 | } 13 | 14 | int main(int argc,char *argv[]) { 15 | if(argc < 3) { 16 | fprintf(stderr, "Usage: editrom sourcerom.bin destrom.bin\n"); 17 | return 2; 18 | } 19 | FILE *src = fopen(argv[1], "rb"); 20 | if(!src) { 21 | fprintf(stderr, "Unable to open source file: %s\n", argv[1]); 22 | return 3; 23 | } 24 | 25 | unsigned char rom[8192]; 26 | if(fread(rom, 1, 8192, src) != 8192) { 27 | fprintf(stderr, "Unable to read ROM\n"); 28 | fclose(src); 29 | return 4; 30 | } 31 | fclose(src); src=NULL; 32 | 33 | // Modify ROM. 34 | modify(rom, 0x77a , 0x0381); // Insert custom instruction. GPLS 35 | modify(rom, 0x77a+2, 0x045B); // B *R11 36 | 37 | /* 38 | modify(rom, 0x008E, 0x0381); // GPLS 39 | modify(rom, 0x0090, 0x1000); // NOP 40 | 41 | modify(rom, 0x00BC, 0x0381); // GPLS 42 | modify(rom, 0x00BE, 0x1000); // NOP 43 | 44 | modify(rom, 0x0624, 0x0381); // GPLS 45 | modify(rom, 0x0626, 0x1000); // NOP 46 | 47 | modify(rom, 0x058E, 0x4C5); // CLR R5 48 | modify(rom, 0x0590, 0x0381); // GPLS 49 | 50 | modify(rom, 0x0596, 0x4C5); // CLR R5 51 | modify(rom, 0x0598, 0x0381); // GPLS 52 | */ 53 | 54 | // Also need to insert new code to 07E0 to handle VDP direct / indirect cases. 55 | if (0) { 56 | FILE *f = fopen("debugcart/VDP9938.bin", "rb"); 57 | if(!f) { 58 | fprintf(stderr, "unable to open VDP file\n"); 59 | } else { 60 | fseek(f, 0x1E4, SEEK_SET); 61 | fread(rom+0x7E0, 1, 0x212-0x1E4, f); 62 | fclose(f); 63 | } 64 | } 65 | 66 | // Patch 0x07A8 routine with new MOVU instruction. 67 | // Note that the code jumps into this routine from multiple places, also to address 0x07AA. 68 | // So copy part of the original routine from 0x7A8 to 0x1346 (cassette write) and 69 | // patch BL @>07AA instruction at 0x00AA to call 0x1346 instead. 70 | memcpy(rom+0x1346, rom+0x7AA, 0x7BA-0x7AA); 71 | modify(rom, 0x00AC, 0x1346); 72 | // Now we can insert our custom instruction. 73 | modify(rom, 0x7A8, 0x389); // Insert MOVU *R1,R0 74 | modify(rom, 0x7A8+2, 0x045B); // B *R11 75 | // Fill in a section of memory with NOPs 76 | // for(int i=0x7A8+4; i<0x7BA; i += 2) 77 | // modify(rom, i, 0x1000); // NOP 78 | 79 | FILE *dst = fopen(argv[2], "wb"); 80 | if(!dst) { 81 | fprintf(stderr, "Unable to open dest file: %s\n", argv[2]); 82 | return 3; 83 | } 84 | int t = fwrite(rom, 1, 8192, dst); 85 | if(t != 8192) { 86 | fprintf(stderr, "Dst write failed, fwrite returned %d\n", t); 87 | fclose(dst); 88 | } 89 | fclose(dst); 90 | return 0; 91 | } -------------------------------------------------------------------------------- /tools/serialtool.c: -------------------------------------------------------------------------------- 1 | // serialtool.c 2 | // Serial port communication from Mac OS. 3 | // Inspiration: https://www.pololu.com/docs/0J73/15.5 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | 13 | unsigned fpga_addr=0; 14 | 15 | // Opens the specified serial port, sets it up for binary communication, 16 | // configures its read timeouts, and sets its baud rate. 17 | // Returns a non-negative file descriptor on success, or -1 on failure. 18 | int open_serial_port(const char * device, uint32_t baud_rate) 19 | { 20 | int fd = open(device, O_RDWR | O_NOCTTY); 21 | if (fd == -1) 22 | { 23 | perror(device); 24 | return -1; 25 | } 26 | 27 | // Flush away any bytes previously read or written. 28 | int result = tcflush(fd, TCIOFLUSH); 29 | if (result) 30 | { 31 | perror("tcflush failed"); // just a warning, not a fatal error 32 | } 33 | 34 | // Get the current configuration of the serial port. 35 | struct termios options; 36 | result = tcgetattr(fd, &options); 37 | if (result) 38 | { 39 | perror("tcgetattr failed"); 40 | close(fd); 41 | return -1; 42 | } 43 | 44 | // Turn off any options that might interfere with our ability to send and 45 | // receive raw binary bytes. 46 | options.c_iflag &= ~(INLCR | IGNCR | ICRNL | IXON | IXOFF); 47 | options.c_oflag &= ~(ONLCR | OCRNL); 48 | options.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); 49 | 50 | // Set up timeouts: Calls to read() will return as soon as there is 51 | // at least one byte available or when 100 ms has passed. 52 | options.c_cc[VTIME] = 1; 53 | options.c_cc[VMIN] = 0; 54 | 55 | // This code only supports certain standard baud rates. Supporting 56 | // non-standard baud rates should be possible but takes more work. 57 | switch (baud_rate) 58 | { 59 | case 4800: cfsetospeed(&options, B4800); break; 60 | case 9600: cfsetospeed(&options, B9600); break; 61 | case 19200: cfsetospeed(&options, B19200); break; 62 | case 38400: cfsetospeed(&options, B38400); break; 63 | case 115200: cfsetospeed(&options, B115200); break; 64 | case 230400: cfsetospeed(&options, B230400); break; 65 | default: 66 | fprintf(stderr, "warning: baud rate %u is not supported, using 9600.\n", 67 | baud_rate); 68 | cfsetospeed(&options, B9600); 69 | break; 70 | } 71 | cfsetispeed(&options, cfgetospeed(&options)); 72 | 73 | result = tcsetattr(fd, TCSANOW, &options); 74 | if (result) 75 | { 76 | perror("tcsetattr failed"); 77 | close(fd); 78 | return -1; 79 | } 80 | 81 | return fd; 82 | } 83 | 84 | // Writes bytes to the serial port, returning 0 on success and -1 on failure. 85 | int write_port(int fd, uint8_t * buffer, size_t size) 86 | { 87 | ssize_t result = write(fd, buffer, size); 88 | if (result != (ssize_t)size) 89 | { 90 | perror("failed to write to port"); 91 | return -1; 92 | } 93 | return 0; 94 | } 95 | 96 | // Reads bytes from the serial port. 97 | // Returns after all the desired bytes have been read, or if there is a 98 | // timeout or other error. 99 | // Returns the number of bytes successfully read into the buffer, or -1 if 100 | // there was an error reading. 101 | ssize_t read_port(int fd, uint8_t * buffer, size_t size) 102 | { 103 | size_t received = 0; 104 | while (received < size) 105 | { 106 | ssize_t r = read(fd, buffer + received, size - received); 107 | if (r < 0) 108 | { 109 | perror("failed to read from port"); 110 | return -1; 111 | } 112 | if (r == 0) 113 | { 114 | // Timeout 115 | break; 116 | } 117 | received += r; 118 | } 119 | return received; 120 | } 121 | 122 | int try_sync(int fd) { 123 | uint8_t buf[16]; 124 | unsigned long realsize; 125 | // printf("%s\n", __PRETTY_FUNCTION__); 126 | write_port(fd, (uint8_t *)".", 1); 127 | buf[0] = 0; 128 | int bytes; 129 | if((bytes = read_port(fd, buf, 1)) < 1) { 130 | fprintf(stderr, "Timeout in %s\n", __PRETTY_FUNCTION__); 131 | return 0; 132 | } 133 | return bytes > 0 && buf[0] == '.'; 134 | } 135 | 136 | void setup_hw_address(int fd, unsigned addr) { 137 | unsigned char buf[16] = { "A_B_C_D_" }; 138 | buf[1] = addr; 139 | buf[3] = addr >> 8; 140 | buf[5] = addr >> 16; 141 | buf[7] = addr >> 24; 142 | write_port(fd, buf, 8); 143 | fpga_addr = addr; 144 | } 145 | 146 | unsigned read_hw_address(int fd, int *ok) { 147 | uint8_t buf[8] = { "EFGH" }; 148 | *ok = 0; 149 | // printf("%s\n", __PRETTY_FUNCTION__); 150 | write_port(fd, &buf[0], 1); 151 | read_port(fd, buf, 1); 152 | write_port(fd, &buf[1], 1); 153 | read_port(fd, buf+1, 1); 154 | write_port(fd, &buf[2], 1); 155 | read_port(fd, buf+2, 1); 156 | write_port(fd, &buf[3], 1); 157 | read_port(fd, buf+3, 1); 158 | *ok = 1; 159 | return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); 160 | } 161 | 162 | 163 | void set_repeat_counter_16(int fd, int len) { 164 | unsigned char cmd[3] = { 'T', 0, 0 }; 165 | cmd[1] = len & 0xFF; 166 | cmd[2] = len >> 8; 167 | write_port(fd, cmd, 3); 168 | } 169 | 170 | unsigned get_repeat_counter_16(int fd) { 171 | unsigned char buf[2]; 172 | unsigned k; 173 | // Send read command 174 | write_port(fd, (uint8_t *)"P", 1); 175 | // Receive our bytes 176 | read_port(fd, buf, 2); 177 | k = buf[0] | (buf[1] << 8); 178 | return k; 179 | } 180 | 181 | int receive_block_complete(int fd, void *block, size_t size, unsigned timeout) { 182 | unsigned realsize = 0, read; 183 | uint8_t *result; 184 | int loops = 0; 185 | unsigned int u; 186 | unsigned char *up; 187 | // unsigned now = GetTickCount(); 188 | result = (uint8_t *) block; 189 | // SerialTimeoutSet(timeout); 190 | 191 | do { 192 | ssize_t read = read_port(fd, result + realsize, size - realsize); 193 | realsize += read; 194 | loops++; 195 | 196 | if (realsize < size) 197 | sleep(1); 198 | 199 | } while(realsize < size); 200 | // while ((realsize < size) && (SerialTimeoutCheck() == 0)); 201 | return realsize == size ? 0 : 1; 202 | } 203 | 204 | void read_memory_block(int fd, unsigned char *dest, unsigned address, int len) { 205 | setup_hw_address(fd, address); 206 | // Enable autoincrement mode and configure length 207 | write_port(fd, (uint8_t *)"M3", 2); 208 | set_repeat_counter_16(fd, len); 209 | // Send read command and read our stuff 210 | write_port(fd, (uint8_t *)"@", 1); 211 | receive_block_complete(fd, dest, len, 2000); 212 | } 213 | 214 | int write_memory_block(int fd, unsigned char *source, unsigned address, int len) { 215 | int chunk = len > 1024 ? 1024 : len; 216 | setup_hw_address(fd, address); 217 | // Enable autoincrement mode and configure length 218 | if(write_port(fd, (uint8_t *)"M3", 2)) 219 | return -1; 220 | set_repeat_counter_16(fd, chunk); 221 | // Send write command and write our stuff 222 | if(write_port(fd, (uint8_t *)"!", 1)) 223 | return -2; 224 | if(write_port(fd, source, chunk)) 225 | return -3; 226 | try_sync(fd); 227 | return chunk; 228 | } 229 | 230 | int load_file(int fd, char *filename, unsigned addr) { 231 | FILE *f = fopen(filename, "rb"); 232 | if(!f) { 233 | fprintf(stderr, "Unable to open source file\n"); 234 | return -1; 235 | } 236 | uint8_t buf[1024]; 237 | int total = 0; 238 | int n; 239 | do { 240 | n = fread(buf, sizeof(uint8_t), sizeof(buf), f); 241 | if(n > 0) { 242 | int r = write_memory_block(fd, buf, addr, n); 243 | if(r < 0) { 244 | fprintf(stderr, "write_memory_block failed %d\n", r); 245 | fclose(f); 246 | return r; 247 | } 248 | addr += n; 249 | total += n; 250 | } 251 | } while(n > 0); 252 | printf("load_file done, wrote %d bytes, final address %X\n", total, addr); 253 | return 0; 254 | } 255 | 256 | int main(int argc, char *argv[]) 257 | { 258 | // Choose the serial port name. If the Jrk is connected directly via USB, 259 | // you can run "jrk2cmd --cmd-port" to get the right name to use here. 260 | // Linux USB example: "/dev/ttyACM0" (see also: /dev/serial/by-id) 261 | // macOS USB example: "/dev/cu.usbmodem001234562" 262 | // Cygwin example: "/dev/ttyS7" 263 | const char * device = "/dev/ttyACM0"; 264 | if(argc > 1) 265 | device = argv[1]; 266 | 267 | uint32_t baud_rate = 230400; 268 | 269 | printf("open_serial_port\n"); 270 | int fd = open_serial_port(device, baud_rate); 271 | if (fd < 0) { return 1; } 272 | 273 | 274 | if(argc > 3 && !strcmp(argv[2], "-l")) { 275 | // argv[1] = port 276 | // argv[2] = -l 277 | // argv[3] = filename 278 | // argv[4] = address (in hex) 279 | unsigned a; 280 | int r = sscanf(argv[4], "%x", &a); 281 | printf("r, a %X %X\n", a, r); 282 | 283 | } else { 284 | 285 | if(try_sync(fd)) { 286 | printf("Sync succeeded\n"); 287 | } 288 | 289 | 290 | int ok = 0; 291 | unsigned a = read_hw_address(fd, &ok); 292 | printf("hw addr=0x%X ok=%d\n", a, ok); 293 | printf("Repeat counter: %d\n", get_repeat_counter_16(fd)); 294 | setup_hw_address(fd, 0x123456); 295 | set_repeat_counter_16(fd, 0x2112); 296 | a = read_hw_address(fd, &ok); 297 | printf("hw addr=0x%X ok=%d\n", a, ok); 298 | printf("Repeat counter: 0x%X\n", get_repeat_counter_16(fd)); 299 | 300 | if(try_sync(fd)) { 301 | printf("Sync succeeded\n"); 302 | } 303 | } 304 | 305 | close(fd); 306 | return 0; 307 | } 308 | 309 | -------------------------------------------------------------------------------- /top_blackice2.v: -------------------------------------------------------------------------------- 1 | // top_blackice2.v 2 | // EP (C) 2019 3 | // This is the toplevel for the platform neutral sys.v which 4 | // implements the TI-99/4A. 5 | 6 | //------------------------------------------------------------------- 7 | // PLL added by EP 2019-08-30 8 | //------------------------------------------------------------------- 9 | // icepll -i 100 -o 25 -m -f erik_pll.v 10 | // PLL configuration written to: erik_pll.v 11 | //------------------------------------------------------------------- 12 | 13 | module top_blackice2( 14 | input wire clk100, 15 | output wire [3:0] LED, 16 | output wire UART_TX, input wire UART_RX, 17 | output wire RAMOE, output wire RAMWE, output wire RAMCS, // SRAM pins 18 | output wire RAMLB, output wire RAMUB, 19 | output [17:0] ADR, input [15:0] DAT, 20 | input wire QSPICSN, input wire QSPICK, // QUAD SPI pins 21 | output wire [3:0] QSPIDQ, 22 | input wire B1, // buttons 23 | input wire B2, 24 | input wire GRESET, input wire DONE, 25 | input wire DIG16, // These are normally high 26 | input wire DIG17, 27 | input wire DIG18, 28 | input wire DIG19, 29 | output wire [3:0] red, output wire [3:0] green, output wire [3:0] blue, 30 | output wire hsync, output wire vsync, 31 | output wire PMOD5_1, output wire PMOD5_2, output wire PMOD5_3, output wire PMOD5_4, 32 | output wire PMOD6_1, output wire PMOD6_2, output wire PMOD6_3, output wire PMOD6_4, 33 | input wire ps2_clk, 34 | input wire ps2_data 35 | ); 36 | 37 | // not used 38 | assign QSPIDQ[3:0] = {4{1'b0}}; // {4{1'bz}}; 39 | 40 | //------------------------------------------------------------------- 41 | 42 | wire clk; 43 | pll _pll( 44 | .clock_in(clk100), 45 | .clock_out(clk) 46 | ); 47 | 48 | // Yosys can't handle bidirectional pins directly, need to handle them differently. 49 | wire [15:0] sram_pins_din; 50 | wire [15:0] sram_pins_dout; 51 | wire sram_pins_drive; 52 | // Yosys component 53 | SB_IO #( 54 | .PIN_TYPE(6'b1010_01), 55 | ) sram_data_pins [15:0] ( 56 | .PACKAGE_PIN(DAT), 57 | .OUTPUT_ENABLE(sram_pins_drive), 58 | .D_OUT_0(sram_pins_dout), 59 | .D_IN_0(sram_pins_din) 60 | ); 61 | 62 | 63 | // Serial port assignments begin 64 | wire serloader_rx = UART_RX; // all incoming traffic goes to serloader 65 | wire serloader_tx; 66 | wire tms9902_rx = (DIG19 == 1'b1) ? UART_RX : 1'b1; // if DIG19 is low, the UART receive is disabled 67 | wire tms9902_tx; 68 | assign UART_TX = (DIG19 == 1'b1) ? tms9902_tx : serloader_tx; 69 | // Serial port assignments end 70 | wire vde; 71 | wire pin_cs, pin_sdin, pin_sclk, pin_d_cn, pin_resn, pin_vccen, pin_pmoden; 72 | wire [22:0] sys_addr; 73 | assign ADR = sys_addr[17:0]; 74 | sys ti994a( 75 | .clk(clk), 76 | .LED(LED), 77 | .tms9902_tx(tms9902_tx), 78 | .tms9902_rx(tms9902_rx), 79 | .RAMOE(RAMOE), 80 | .RAMWE(RAMWE), 81 | .RAMCS(RAMCS), 82 | .RAMLB(RAMLB), 83 | .RAMUB(RAMUB), 84 | .ADR(sys_addr), 85 | .sram_pins_din(sram_pins_din), 86 | .sram_pins_dout(sram_pins_dout), 87 | .sram_pins_drive(sram_pins_drive), 88 | .memory_busy(1'b0), 89 | .use_memory_busy(1'b0), 90 | .red(red), 91 | .green(green), 92 | .blue(blue), 93 | .hsync(hsync), 94 | .vsync(vsync), 95 | .cpu_reset_switch_n(DIG18), 96 | `ifdef LCD_SUPPORT 97 | // LCD signals 98 | .pin_cs(pin_cs), 99 | .pin_sdin(pin_sdin), 100 | .pin_sclk(pin_sclk), 101 | .pin_d_cn(pin_d_cn), 102 | .pin_resn(pin_resn), 103 | .pin_vccen(pin_vccen), 104 | .pin_pmoden(pin_pmoden), 105 | `endif 106 | .serloader_tx(serloader_tx), 107 | .serloader_rx(serloader_rx), // bootloader UART 108 | .vde(vde), // Video display enable (active area) 109 | .ps2clk(ps2_clk), 110 | .ps2dat(ps2_data) 111 | ); 112 | 113 | `ifdef LCD_SUPPORT 114 | assign PMOD5_1 = pin_cs; 115 | assign PMOD5_2 = pin_sdin; 116 | assign PMOD5_3 = 1'b0; 117 | assign PMOD5_4 = pin_sclk; 118 | assign PMOD6_1 = pin_d_cn; 119 | assign PMOD6_2 = pin_resn; 120 | assign PMOD6_3 = pin_vccen; 121 | assign PMOD6_4 = pin_pmoden; 122 | `endif 123 | 124 | endmodule 125 | 126 | -------------------------------------------------------------------------------- /top_flea.v: -------------------------------------------------------------------------------- 1 | // top_flea.v 2 | // EP (C) 2019 3 | // This is the toplevel for FleaFPGA Ohm board. 4 | // It instanciates the platform neutral sys.v which 5 | // implements the TI-99/4A. 6 | 7 | module fleatop 8 | ( 9 | input wire clk_25mhz, 10 | output wire [3:0] gpdi_dp, gpdi_dn, 11 | output wire PS2_enable, 12 | input wire usb_fpga_dp, usb_fpga_dn, 13 | output wire Dram_CKE, 14 | output wire Dram_n_cs, 15 | output wire mmc_n_cs, 16 | output wire n_led1, 17 | output wire slave_tx_o, 18 | input wire slave_rx_i, 19 | output wire GPIO_2, // pin 3 on Raspi header 20 | input wire GPIO_3, // pin 5 on Raspi header 21 | input wire ps2_clk2, 22 | input wire ps2_data2 23 | ); 24 | 25 | // Housekeeping logic for unwanted peripherals on FleaFPGA Ohm board goes here.. 26 | assign Dram_CKE = 1'b 0; // DRAM Clock disable. 27 | assign Dram_n_cs = 1'b 1; // DRAM Chip disable. 28 | assign mmc_n_cs = 1'b 1; // Micro SD card chip disable. 29 | assign PS2_enable = 1'b 1; // Configures both USB host ports for legacy PS/2 mode. 30 | 31 | // clock generation 32 | wire pll_250mhz, pll_125mhz, pll_25mhz; 33 | 34 | clk_25_250_125_25 clk_pll ( 35 | .clki(clk_25mhz), 36 | .clko(pll_250mhz), 37 | .clks1(pll_125mhz), 38 | .clks2(pll_25mhz) 39 | ); 40 | 41 | //------------------------------------------------------------ 42 | // our SRAM 43 | wire [15:0] sram_pins_din, sram_pins_dout; 44 | wire sram_pins_drive; 45 | // SRAM pins 46 | wire RAMOE; 47 | wire RAMWE; 48 | wire RAMCS; 49 | wire RAMLB; 50 | wire RAMUB; 51 | wire [17:0] ADR; 52 | // Need to populate memory map with internal SRAM: 53 | // 8K at 00000 system ROM 54 | // -- 8K at 02000 low memory expansion 55 | // 1K at 08000 scratch pad 56 | // -- 24K at 0A000 high memory expansion 57 | // 32K at 10000 GROM space (system+8K for module) 58 | // 16K at 20000 VRAM 59 | // 16K at 40000 cartridge RAM 60 | // without 32K RAM expansion this amounts to 73K. 61 | // 5 blocks in total. 62 | 63 | // Since we need byte addressability we need 10 blocks. 64 | // For the select signals, note that ADR has 16-bit word address, not byte address. 65 | // Thus ADR[14] is CPU A15. 66 | /* 67 | wire rom_sel = !RAMCS && (!RAMOE || !RAMWE) && (ADR[17:12] == 6'b000_000); // 8K @ 00000 68 | wire pad_sel = !RAMCS && (!RAMOE || !RAMWE) && (ADR[17: 9] == 9'b000_1000_00);// 1K @ 08000 69 | wire gro_sel = !RAMCS && (!RAMOE || !RAMWE) && (ADR[17:14] == 4'b001_0); // 32K @ 10000 70 | wire vra_sel = !RAMCS && (!RAMOE || !RAMWE) && (ADR[17:13] == 5'b010_00); // 16K @ 20000 71 | wire car_sel = !RAMCS && (!RAMOE || !RAMWE) && (ADR[17:13] == 5'b100_00); // 16K @ 40000 72 | */ 73 | wire rom_sel = (ADR[17:12] == 6'b000_000); // 8K @ 00000 74 | wire pad_sel = (ADR[17: 9] == 9'b000_1000_00);// 1K @ 08000 75 | wire gro_sel = (ADR[17:14] == 4'b001_0); // 32K @ 10000 76 | wire vra_sel = (ADR[17:13] == 5'b010_00); // 16K @ 20000 77 | wire car_sel = (ADR[17:13] == 5'b100_00); // 16K @ 40000 78 | // Temporarily assign to top of 64K RAM to be able to run EVMBUG 79 | // wire car_sel = (ADR[17:13] == 5'b000_11); // 16K @ 40000 80 | 81 | // ROM 82 | wire [7:0] rom_out_lo, rom_out_hi; 83 | rom16 #(16, 12, 8192/2, "roms/994arom.mem") sysrom(pll_125mhz, ADR[11:0], { rom_out_hi, rom_out_lo} ); 84 | /* 85 | wire rom_we_lo = rom_sel && !RAMLB && !RAMWE; 86 | wire rom_we_hi = rom_sel && !RAMUB && !RAMWE; 87 | dualport_par #(8,12) rom_lb(pll_125mhz, rom_we_lo, ADR[11:0], sram_pins_dout[ 7:0], pll_125mhz, ADR[11:0], rom_out_lo); 88 | dualport_par #(8,12) rom_hb(pll_125mhz, rom_we_hi, ADR[11:0], sram_pins_dout[15:8], pll_125mhz, ADR[11:0], rom_out_hi); 89 | */ 90 | // SCRATCHPAD (here 1K not 256bytes) 91 | wire pad_we_lo = pad_sel && !RAMLB && !RAMWE; 92 | wire pad_we_hi = pad_sel && !RAMUB && !RAMWE; 93 | wire [7:0] pad_out_lo, pad_out_hi; 94 | dualport_par #(8, 9) pad_lb(pll_125mhz, pad_we_lo, ADR[ 8:0], sram_pins_dout[ 7:0], pll_125mhz, ADR[ 8:0], pad_out_lo); 95 | dualport_par #(8, 9) pad_hb(pll_125mhz, pad_we_hi, ADR[ 8:0], sram_pins_dout[15:8], pll_125mhz, ADR[ 8:0], pad_out_hi); 96 | // GROM 32K 97 | wire [7:0] gro_out_lo, gro_out_hi; 98 | rom16 #(16,14,24576/2,"roms/994agrom.mem") sysgrom(pll_125mhz, ADR[13:0], {gro_out_hi, gro_out_lo } ); 99 | /* 100 | wire gro_we_lo = gro_sel && !RAMLB && !RAMWE; 101 | wire gro_we_hi = gro_sel && !RAMUB && !RAMWE; 102 | dualport_par #(8,14) gro_lb(pll_125mhz, gro_we_lo, ADR[13:0], sram_pins_dout[ 7:0], pll_125mhz, ADR[13:0], gro_out_lo); 103 | dualport_par #(8,14) gro_hb(pll_125mhz, gro_we_hi, ADR[13:0], sram_pins_dout[15:8], pll_125mhz, ADR[13:0], gro_out_hi); 104 | */ 105 | // VRAM 16K 106 | wire vra_we_lo = vra_sel && !RAMLB && !RAMWE; 107 | wire vra_we_hi = vra_sel && !RAMUB && !RAMWE; 108 | wire [7:0] vra_out_lo, vra_out_hi; 109 | dualport_par #(8,13) vra_lb(pll_125mhz, vra_we_lo, ADR[12:0], sram_pins_dout[ 7:0], pll_125mhz, ADR[12:0], vra_out_lo); 110 | dualport_par #(8,13) vra_hb(pll_125mhz, vra_we_hi, ADR[12:0], sram_pins_dout[15:8], pll_125mhz, ADR[12:0], vra_out_hi); 111 | // CARTRIDGE (paged, here 2 pages total 16K) 112 | wire car_we_lo = car_sel && !RAMLB && !RAMWE; 113 | wire car_we_hi = car_sel && !RAMUB && !RAMWE; 114 | wire [7:0] car_out_lo, car_out_hi; 115 | dualport_par #(8,13) car_lb(pll_125mhz, car_we_lo, ADR[12:0], sram_pins_dout[ 7:0], pll_125mhz, ADR[12:0], car_out_lo); 116 | dualport_par #(8,13) car_hb(pll_125mhz, car_we_hi, ADR[12:0], sram_pins_dout[15:8], pll_125mhz, ADR[12:0], car_out_hi); 117 | 118 | // Data input multiplexer 119 | assign sram_pins_din = 120 | rom_sel ? { rom_out_hi, rom_out_lo } : 121 | pad_sel ? { pad_out_hi, pad_out_lo } : 122 | gro_sel ? { gro_out_hi, gro_out_lo } : 123 | vra_sel ? { vra_out_hi, vra_out_lo } : 124 | car_sel ? { car_out_hi, car_out_lo } : 125 | 16'h0000; 126 | 127 | // VGA 128 | wire [3:0] red, green, blue; 129 | wire hsync, vsync; 130 | 131 | //------------------------------------------------------------------- 132 | 133 | wire clk = pll_25mhz; 134 | 135 | // need to implement SRAM here 136 | 137 | // Serial port assignments begin 138 | // wire serloader_rx = slave_rx_i; // all incoming traffic goes to serloader 139 | wire serloader_rx = GPIO_3; 140 | wire serloader_tx; 141 | assign GPIO_2 = serloader_tx; 142 | wire tms9902_rx = slave_rx_i; 143 | wire tms9902_tx; 144 | assign slave_tx_o = tms9902_tx; 145 | // Serial port assignments end 146 | 147 | // PS2 keyboard - if there is signals from either port go with that. 148 | // The port should be pulled up, so I guess and operation should do the trick. 149 | wire ps2clk = usb_fpga_dp & ps2_clk2; 150 | wire ps2dat = usb_fpga_dn & ps2_data2; 151 | 152 | wire [3:0] LED; 153 | wire vde; 154 | 155 | assign n_led1 = LED[3]; // stuck signal 156 | 157 | wire pin_cs, pin_sdin, pin_sclk, pin_d_cn, pin_resn, pin_vccen, pin_pmoden; 158 | sys ti994a(clk, LED, 159 | tms9902_tx, tms9902_rx, 160 | RAMOE, RAMWE, RAMCS, RAMLB, RAMUB, 161 | ADR, 162 | sram_pins_din, sram_pins_dout, 163 | sram_pins_drive, 164 | red, green, blue, hsync, vsync, 165 | 1'b1, // cpu_reset_switch_n 166 | // LCD signals 167 | pin_cs, pin_sdin, pin_sclk, pin_d_cn, pin_resn, pin_vccen, pin_pmoden, 168 | // bootloader UART 169 | serloader_tx, serloader_rx, 170 | vde, // video display enable signal 171 | ps2clk, ps2dat 172 | ); 173 | 174 | wire [7:0] red_out = { red, 4'h0 }; 175 | wire [7:0] green_out = { green, 4'h0 }; 176 | wire [7:0] blue_out = { blue, 4'h0 }; 177 | 178 | wire hsyn = ~hsync; 179 | wire vsyn = ~vsync; 180 | DVI_out out(pll_25mhz, pll_125mhz, red_out, green_out, blue_out, 181 | vde, hsyn, vsyn, gpdi_dp, gpdi_dn); 182 | 183 | endmodule 184 | 185 | module clk_25_250_125_25( 186 | input clki, 187 | output clks1, 188 | output clks2, 189 | output locked, 190 | output clko 191 | ); 192 | wire clkfb; 193 | wire clkos; 194 | wire clkop; 195 | (* ICP_CURRENT="12" *) (* LPF_RESISTOR="8" *) (* MFG_ENABLE_FILTEROPAMP="1" *) (* MFG_GMCREF_SEL="2" *) 196 | EHXPLLL #( 197 | .PLLRST_ENA("DISABLED"), 198 | .INTFB_WAKE("DISABLED"), 199 | .STDBY_ENABLE("DISABLED"), 200 | .DPHASE_SOURCE("DISABLED"), 201 | .CLKOP_FPHASE(0), 202 | .CLKOP_CPHASE(0), 203 | .OUTDIVIDER_MUXA("DIVA"), 204 | .CLKOP_ENABLE("ENABLED"), 205 | .CLKOP_DIV(2), 206 | .CLKOS_ENABLE("ENABLED"), 207 | .CLKOS_DIV(4), 208 | .CLKOS_CPHASE(0), 209 | .CLKOS_FPHASE(0), 210 | .CLKOS2_ENABLE("ENABLED"), 211 | .CLKOS2_DIV(20), 212 | .CLKOS2_CPHASE(0), 213 | .CLKOS2_FPHASE(0), 214 | .CLKFB_DIV(10), 215 | .CLKI_DIV(1), 216 | .FEEDBK_PATH("INT_OP") 217 | ) pll_i ( 218 | .CLKI(clki), 219 | .CLKFB(clkfb), 220 | .CLKINTFB(clkfb), 221 | .CLKOP(clkop), 222 | .CLKOS(clks1), 223 | .CLKOS2(clks2), 224 | .RST(1'b0), 225 | .STDBY(1'b0), 226 | .PHASESEL0(1'b0), 227 | .PHASESEL1(1'b0), 228 | .PHASEDIR(1'b0), 229 | .PHASESTEP(1'b0), 230 | .PLLWAKESYNC(1'b0), 231 | .ENCLKOP(1'b0), 232 | .LOCK(locked) 233 | ); 234 | assign clko = clkop; 235 | endmodule 236 | -------------------------------------------------------------------------------- /upload.sh: -------------------------------------------------------------------------------- 1 | # upload.sh 2 | # Shell script to move bitstream file to the ULX3S board. 3 | # EP 2020-12-05 4 | 5 | ftp $1 << EOT 6 | 7 | bin 8 | cd /sd/ti99_4a/bitstreams 9 | pwd 10 | lcd EriksMacStudio.lan 11 | put ti994a_ulx3s.bit 12 | lcd .. 13 | cd / 14 | pwd 15 | lcd esp32/osd 16 | put osd.py 17 | put ld_ti99_4a.py 18 | cd /sd/ti99_4a/cart 19 | pwd 20 | lcd ../../debugcart 21 | put VDP9938.bin 22 | lcd ../roms 23 | cd /sd/ti99_4a/rom 24 | pwd 25 | put 994aROM.Bin 26 | bye 27 | EOT 28 | 29 | 30 | -------------------------------------------------------------------------------- /upload9900.sh: -------------------------------------------------------------------------------- 1 | # upload.sh 2 | # Shell script to move bitstream file to the ULX3S board. 3 | # EP 2020-12-05 4 | 5 | ftp $1 << EOT 6 | 7 | cd /sd/ti99_4a/bitstreams 8 | pwd 9 | lcd i9900 10 | put ti994a_ulx3s.bit 11 | lcd .. 12 | cd / 13 | pwd 14 | lcd esp32/osd 15 | put osd.py 16 | put ld_ti99_4a.py 17 | cd /sd/ti99_4a/cart 18 | pwd 19 | lcd ../../debugcart 20 | put VDP9938.bin 21 | lcd ../roms 22 | cd /sd/ti99_4a/rom 23 | pwd 24 | put 99opt.bin 25 | bye 26 | EOT 27 | 28 | 29 | --------------------------------------------------------------------------------