Compare commits

..

6 Commits

15 changed files with 4972 additions and 108 deletions

View File

@ -35,8 +35,8 @@ There is an experimental and unfinished quick load function using a python termi
![Pat80 Memory Monitor](/assets/media/photos/memory_monitor.jpg) ![Pat80 Memory Monitor](/assets/media/photos/memory_monitor.jpg)
## Emulator ## Emulator
The pat80 memory monitor (the os) can be run in a z80 emulator. This repository contains an emulator as submodule, already set up to run the os. The pat80 memory monitor (the os) can be run in a z80 emulator. You can find a very simple one I wrote ad hoc in `pat80-emulator`. It's based (and depends on) the excellent [Z80 emulator library by redcode](https://github.com/redcode/Z80). Follow the instructions on `pat80-emulator/README.md` to build and run it.
To try it, head to `pat80-computer/software/z80-assembly/os/` and run `make run` to build the rom from assembly and start the emulator. You will see some windows showing the emulated computer's memory and register status and the pat80 memory monitor prompt. The emulator requires a rom file to run. To obtain a pat80 rom, head to `pat80-computer/software/z80-assembly/os/` and run `make run` to build the rom from assembly and start the emulator. You will see some windows showing the emulated computer's memory and register status and the pat80 memory monitor prompt.
![Emulator running Memory Monitor](/assets/media/photos/emulator.png) ![Emulator running Memory Monitor](/assets/media/photos/emulator.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 100 KiB

View File

@ -1,3 +1,5 @@
PAT80_EMU_PATH := "../../../../pat80-emulator/build/pat80-emulator"
build: build:
@echo "Building PAT80 rom..." @echo "Building PAT80 rom..."
@z80asm -i main.asm -o rom.bin || (exit 1) @z80asm -i main.asm -o rom.bin || (exit 1)
@ -13,5 +15,9 @@ write: build
@minipro -w rom.bin -p "AT28C64B" @minipro -w rom.bin -p "AT28C64B"
run: build run: build
@echo "Starting emulator..." @echo "Starting emulator" $(PAT80_EMU_PATH)
@../../../../pat80-emulator/z80-python-emulator/src/z80sbc.py -b rom.bin @if [ -f "$(PAT80_EMU_PATH)" ]; then\
"$(PAT80_EMU_PATH)" "rom.bin";\
else\
echo -e "\e[31mYou must build the emulator first. Check pat80-emulator/README.md\e[0m";\
fi

View File

@ -1,15 +1,24 @@
# Pat80 Operating System and Memory Monitor # Pat80 Operating System and Memory Monitor
## Intro ## Intro
This folder contains the Pat80 Operating System. This folder contains the Pat80 Operating System.
It is a System Monitor that makes available also some system API to access hardware (monitor, sound, keyboard, parallel terminal...). It is a System Monitor that makes available also some system API to access hardware (monitor, sound, keyboard, parallel terminal...).
## Build ## Build
### Requirements ### Requirements
z80asm, minipro
z80asm
minipro (if you want to write to an EEPROM)
### Make ### Make
The os can be build issuing command `make`.
The os can be **built** issuing command `make build`.
Two files will be generated: Two files will be generated:
- `rom.bin` is the rom file to be flashed on the eeprom - `rom.bin` is the rom file to be flashed on the eeprom
- `abi-generated.asm` is the file to be included in any Pat80 application to access system APIs (see README.md in ../applications/) - `abi-generated.asm` is the file to be included in any Pat80 application to access system APIs (see README.md in ../applications/)
The build routine will then try to write the rom to a MiniPRO.
The os can be **written to an EEPROM** with a minipro-compatible programmer issuing command `make write`. This runs the build and then tries to write the rom to a MiniPRO.
The os can otherwise be **runned in the emulator** issuing command `make run`. This requires to have the emulator executable already built (follow the instructions on `pat80-emulator/README.md` to build it).

View File

@ -0,0 +1,102 @@
; TM404A 40x4 characters LCD display (based on SN76489 chip) driver
; @author Daniele Verducci
; @language: Z80 ASM
;
;
; This file is part of Pat80 Memory Monitor.
;
; Pat80 Memory Monitor is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; Pat80 Memory Monitor is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with Pat80 Memory Monitor. If not, see <http://www.gnu.org/licenses/>.
;
; --------------------------------------------------------------------------
;
; This is meant to be used with a 40x4 character display (containing a dual hd44780-compatible controller) like the TM404A.
; This kind of display uses two controllers to overcome the hd44780 controllers limitation of 80 addressable characters.
; It is seen as two separate displays, and thus have two EN pins. A finer implementation would apply some deconding logic to
; an address bit to use a single port, but here we will use two different IO ports to save an IC.
;
; LCD config (IO port 0)
LCD4004_TOP2LINES_PORT: EQU IO_2
LCD4004_BOTTOM2LINES_PORT: EQU IO_3
; PIN CONNECTIONS
;
; PIN DESCRIPTION TO PAT80 BUS PIN INFO
; ---------------------------------------------------------------------------------------------------
; 18 DB7DB0 DATA BUS
; 9 E1 (chip en 1) IOEN on LCD_TOP2LINES_PORT Chip enable for top 2 lines
; 10 R/W IOWR
; 11 RS A4 0: Command, 1: Data
; 12 V0 - Power supply for contrast (approx. +0.5V)
; 13 Vss - Ground
; 14 VDD - Power Supply Supply voltage for logic (+5.0V)
; 15 E2 (chip en 2) IOEN on LCD_BOTTOM2LINES_PORT Chip enable for bottom 2 lines
; 16 NC No Connect
;
; The 40x4 LCD memory map is the following:
;
; LINE CHIP EN PIN START ADDR END ADDR
; ----------------------------------------------------
; 1 9 0x00 (0) 0x27 (39)
; 2 9 0x40 (64) 0x67 (103)
; 3 15 0x00 (0) 0x27 (39)
; 4 15 0x40 (64) 0x67 (103)
;
; When we reach 0x40 on the first controller, we switch to the second.
; When we reach 0x40 on the second controller, we must shift
; all the lines up by one and position the cursor at 0x40.
; variables
LCD4004_VAR_SPACE: EQU DRV_IO_2_VAR_SPACE
LCD4004_CURSOR_POSITION_CONTROLLER: EQU DRV_IO_2_VAR_SPACE ; In which LCD controller is the cursor (0 or 1)
LCD4004_CURSOR_POSITION_CONTROLLER_MEMORY: EQU DRV_IO_2_VAR_SPACE + 1 ; Memory position of the cursor inside the controller's memory
; functions
; Initializes the driver and the LCD screen
LCD4004_Initialize:
; The following are documented as in the datasheet: RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
; Function set: 0 0 0 0 1 DL N F 0 0 (DL: 8-bit/4-bit, N: number of display lines 2/1, F: Font type 11dots/8dots)
; 0000111000 (8 bit, 2 lines)
; Display ON/OFF Control: 0 0 0 0 0 0 1 D C B (D: display on/off, C: cursor on/off, B: blinking cursor on/off)
; 0000001111 (display on, blinking cursor)
; Clear display
; 0000000001
; Move cursor (set DDRAM address in address counter)
LCD4004_MoveCursor:
; 0 0 1 AC6 AC5 AC4 AC3 AC2 AC1 AC0 (ACx: Address)
; Sends string
; @param BC Pointer to a null-terminated string first character
LCD4004_print:
ld a, (bc) ; bc is the pointer to passed string's first char
cp 0 ; compare A content with 0 (subtract 0 from value and set zero flag Z if result is 0)
ret z ; if prev compare is true (Z flag set), string is finished, return
out (TERM_DATA_REG),a ; output char
inc bc ; increment bc to move to next char
jp LCD4004_print
; Writes a single character
; @param A Value of character to print
LCD4004_printc:
out (TERM_DATA_REG),a
ret

View File

@ -23,7 +23,7 @@ TERM_DATA_REG: EQU IO_0
TERM_DATA_AVAIL_REG: EQU IO_0 + 1 TERM_DATA_AVAIL_REG: EQU IO_0 + 1
; variables ; variables
TERM_VAR_SPACE: EQU DRV_VAR_SPACE + 128 TERM_VAR_SPACE: EQU DRV_IO_0_VAR_SPACE
incoming_string: EQU TERM_VAR_SPACE incoming_string: EQU TERM_VAR_SPACE
; functions ; functions

View File

@ -30,8 +30,8 @@ jp Sysinit ; Startup vector: DO NOT MOVE! Must be the first instruction
; I/O MAP ; I/O MAP
; I/O 0 (0x00 - 0x1F) Parallel terminal (uses addr 0x00 and 0x01) ; I/O 0 (0x00 - 0x1F) Parallel terminal (uses addr 0x00 and 0x01)
; I/O 1 (0x20 - 0x3F) Sound card (uses addr 0x20 only) ; I/O 1 (0x20 - 0x3F) Sound card (uses addr 0x20 only)
; I/O 2 (0x40 - 0x5F) PS2 Keyboard (uses 0x40 and 0x41) ; I/O 2 (0x40 - 0x5F) 40x4 LCD, first controller (lines 1 and 2)
; I/O 3 (0x60 - 0x7F) ; I/O 3 (0x60 - 0x7F) 40x4 LCD, second controller (lines 3 and 4)
; I/O 4 (0x80 - 0x9F) ; I/O 4 (0x80 - 0x9F)
; I/O 5 (0xA0 - 0xBF) ; I/O 5 (0xA0 - 0xBF)
; I/O 6 (0xC0 - 0xDF) ; I/O 6 (0xC0 - 0xDF)
@ -89,12 +89,20 @@ Sys_Beep:
; MEMORY CONFIGURATION ; MEMORY CONFIGURATION
SYS_VAR_SPACE: EQU 0x8000 SYS_VAR_SPACE: EQU 0x8000 ; OS may allocate here memory for its own use
DRV_VAR_SPACE: EQU 0x9000 DRV_VAR_SPACE: EQU 0x9000 ; IO devices drivers may allocate here memory, each has a chunk of 512 bytes
APP_SPACE: EQU 0xA000 DRV_IO_0_VAR_SPACE: EQU DRV_VAR_SPACE
DRV_IO_1_VAR_SPACE: EQU DRV_IO_0_VAR_SPACE + 512
DRV_IO_2_VAR_SPACE: EQU DRV_IO_1_VAR_SPACE + 512
DRV_IO_3_VAR_SPACE: EQU DRV_IO_2_VAR_SPACE + 512
DRV_IO_4_VAR_SPACE: EQU DRV_IO_3_VAR_SPACE + 512
DRV_IO_5_VAR_SPACE: EQU DRV_IO_4_VAR_SPACE + 512
DRV_IO_6_VAR_SPACE: EQU DRV_IO_5_VAR_SPACE + 512
DRV_IO_7_VAR_SPACE: EQU DRV_IO_6_VAR_SPACE + 512
APP_SPACE: EQU 0xA000 ; App may only allocate between there and MEM_END
MEM_END: EQU 0xFFFF MEM_END: EQU 0xFFFF
; SYSTEM CONFIGURATION ; DEVICES ("cards" on the IO bus) I/O space
IO_0: EQU 0x00 IO_0: EQU 0x00
IO_1: EQU 0x20 IO_1: EQU 0x20
IO_2: EQU 0x40 IO_2: EQU 0x40
@ -111,7 +119,7 @@ IO_7: EQU 0xE0
;include 'drivers/hd44780.asm' ;include 'drivers/hd44780.asm'
;include 'drivers/keyboard.asm' ;include 'drivers/keyboard.asm'
include 'drivers/ps2_keyboard.asm' ;include 'drivers/ps2_keyboard.asm'
include 'drivers/arduino_terminal.asm' include 'drivers/arduino_terminal.asm'
include 'drivers/sn76489.asm' include 'drivers/sn76489.asm'
include 'monitor.asm' include 'monitor.asm'

View File

@ -25,7 +25,6 @@
; S (SET) $pos $val Replaces byte at $pos with $val ; S (SET) $pos $val Replaces byte at $pos with $val
; L (LOAD) $pos $val ; L (LOAD) $pos $val
; R (RUN) $pos Starts executing code from $pos ; R (RUN) $pos Starts executing code from $pos
; A (ADB) Enters in Assembly Depoy Bridge mode: loads all the incoming bytes in application memory and starts executing.
; The commands are entered with a single letter and the program completes the command ; The commands are entered with a single letter and the program completes the command
include 'libs/strings.asm' include 'libs/strings.asm'
@ -38,13 +37,13 @@ MON_COMMAND_SET: DB "SET",0
MON_COMMAND_ZERO: DB "ZERO",0 MON_COMMAND_ZERO: DB "ZERO",0
MON_COMMAND_LOAD: DB "LOAD",0 MON_COMMAND_LOAD: DB "LOAD",0
MON_COMMAND_RUN: DB "RUN",0 MON_COMMAND_RUN: DB "RUN",0
MON_COMMAND_ADB: DB "ADB",0
MON_COMMAND_MEMTEST: DB "MEMTEST",0 MON_COMMAND_MEMTEST: DB "MEMTEST",0
MON_COMMAND_QUIT: DB "QUIT",0 MON_COMMAND_QUIT: DB "QUIT",0
MON_ARG_HEX: DB " 0x",0 MON_ARG_HEX: DB " 0x",0
MON_HELP: DB 10,"Available commands:\nHELP prints this message\nDUMP [ADDR] shows memory content\nSET [ADDR] sets memory content\nZERO [ADDR] [ADDR] sets all bytes to 0 in the specified range\nLOAD\nRUN [ADDR] executes code starting from ADDR\nADB starts Assembly Deploy Bridge\nMEMTEST checks ram boundaries\nQUIT exits",0 MON_HELP: DB 10,"Available commands:\nHELP prints this message\nDUMP [ADDR] shows memory content\nSET [ADDR] sets memory content\nZERO [ADDR] [ADDR] sets all bytes to 0 in the specified range\nLOAD [ADDR] loads a program or data from tape at ADDR\nRUN [ADDR] executes code starting from ADDR\nADB starts Assembly Deploy Bridge\nMEMTEST checks ram boundaries\nQUIT exits",0
MON_MSG_ADB: DB 10,"Waiting for data.",0 MON_MSG_ADB: DB 10,"Waiting for data.",0
MON_ERR_SYNTAX: DB " Syntax error",0 MON_ERR_SYNTAX: DB " Syntax error",0
MON_COMMAND_LOAD_PRESSPLAY: DB "Press play on tape",0
; MON_RAMTEST_INTRO: DB " Checking memory... ",0 ; MON_RAMTEST_INTRO: DB " Checking memory... ",0
; MON_RAMTEST_RAMSTART: DB " Ram starts at 0x",0 ; MON_RAMTEST_RAMSTART: DB " Ram starts at 0x",0
MON_DUMP_BYTES_LINES: EQU 8 MON_DUMP_BYTES_LINES: EQU 8
@ -84,9 +83,6 @@ Monitor_main:
ld hl, MON_COMMAND_RUN ld hl, MON_COMMAND_RUN
cp (hl) cp (hl)
jp z, monitor_run jp z, monitor_run
ld hl, MON_COMMAND_ADB
cp (hl)
jp z, monitor_adb
; ld hl, MON_COMMAND_MEMTEST ; ld hl, MON_COMMAND_MEMTEST
; cp (hl) ; cp (hl)
; jp z, monitor_memtest ; jp z, monitor_memtest
@ -298,10 +294,18 @@ monitor_zero: ; TODO: bugged, doesn't exit cycle
ld (hl), 0 ; set byte to 0 in memory ld (hl), 0 ; set byte to 0 in memory
ret ret
; Asks user for a memory position, then wait for data from tape and place it
; from the specified position on until it receives 4 consecutive 0x00 (EOF)
; @uses b, c, h, l
monitor_load: monitor_load:
ld bc, MON_COMMAND_LOAD + 1 ; autocomplete command ld bc, MON_COMMAND_LOAD + 1 ; autocomplete command
call Sys_Print call Sys_Print
; TODO: When implemented, re-enable interrupts before run application ; Now read the memory address from the user
call monitor_arg_2byte ; returns the read bytes in hl
; Ask user to press play
ld bc, MON_COMMAND_LOAD_PRESSPLAY
; TODO: wait for data
jp monitor_main_loop jp monitor_main_loop
monitor_run: monitor_run:
@ -320,19 +324,6 @@ monitor_run:
; execute code ; execute code
jp (hl) jp (hl)
monitor_adb:
ld bc, MON_COMMAND_ADB + 1 ; autocomplete command
call Sys_Print
; start copying incoming data to application space
call monitor_copyTermToAppMem
; call monitor_enable_int ; re-enable interrupts
;jp APP_SPACE ; Start executing code
; ld bc, APP_SPACE
; call Sys_Print
jp monitor_main_loop
; Prints "0x" and read 1 hex byte (2 hex digits, e.g. 0x8C) ; Prints "0x" and read 1 hex byte (2 hex digits, e.g. 0x8C)
; Can be cancelled with Q/ENTER ; Can be cancelled with Q/ENTER
; @return a the read byte, b the exit code (0=valid byte in a, 1=Q, 2=ENTER) ; @return a the read byte, b the exit code (0=valid byte in a, 1=Q, 2=ENTER)
@ -524,53 +515,6 @@ monitor_printAsciiByte:
call Sys_Printc call Sys_Printc
ret ret
; Copy data from parallel terminal to application memory. This is tought to be used with the ADB function of the Pat80 Python Terminal.
; Uses TERM_DATA_AVAIL_REG to check if a byte is available before reading it.
; The first two received bytes (heading bytes) defines the stream length (MSB first), the rest of the bytes are copied to memory.
; The copy is completed when the number of bytes defined in the heading bytes are received.
; @uses a, b, c, d, h, l
monitor_copyTermToAppMem:
; d contains the current status.
; 2 = waiting for first heading byte
; 1 = waiting for second heading byte
; 0 = heading bytes received, now receiving binary stream
ld d, 2
ld hl, APP_SPACE ; we will write in APP_SPACE
monitor_copyTermToAppMem_loop:
ld a, d
cp 2 ; check if we are receiving first header byte
jp z, monitor_copyTermToAppMem_loop_rec_head_byte_1
ld a, d
cp 1 ; check if we are receiving second header byte
jp z, monitor_copyTermToAppMem_loop_rec_head_byte_2
; we are receiving binary stream: read byte and save to memory
call Term_readb ; reads a byte from terminal
ld (hl), a ; copy byte to memory
inc hl ; move to next memory position
dec bc ; decrement remaining bytes counter
; check if we reached the number of bytes to be transferred
ld a, b
cp 0
jp nz, monitor_copyTermToAppMem_loop ; continue loop
ld a, c
cp 0
jp nz, monitor_copyTermToAppMem_loop ; continue loop
; all bytes received, return
ret
monitor_copyTermToAppMem_loop_rec_head_byte_1:
; we are receiving first header byte: read byte and save to b
call Term_readb ; reads a byte from terminal
ld b, a
dec d
jp monitor_copyTermToAppMem_loop ; continue loop
monitor_copyTermToAppMem_loop_rec_head_byte_2:
; we are receiving second header byte: read byte and save to c
call Term_readb ; reads a byte from terminal
ld c, a
dec d
jp monitor_copyTermToAppMem_loop ; continue loop
; Runs a memory test to identify ram memory boundaries and check the ram is working. ; Runs a memory test to identify ram memory boundaries and check the ram is working.
; Starting from last memory position, writes 0xFF, reads it back, writes 0x00, reads it back. ; Starting from last memory position, writes 0xFF, reads it back, writes 0x00, reads it back.
; Exits when the first value differs from the written value (this may be caused by a bad ram ; Exits when the first value differs from the written value (this may be caused by a bad ram

View File

@ -0,0 +1,116 @@
jp main ; Startup vector: DO NOT MOVE! Must be the first instruction
; Tests TM404A, on ports 2 and 3
IO_2: EQU 0x40
IO_3: EQU 0x60
LCD_TOP_INSTR_REG: EQU IO_2
LCD_TOP_DATA_REG: EQU IO_2 + 1
LCD_BOTTOM_INSTR_REG: EQU IO_3
LCD_BOTTOM_DATA_REG: EQU IO_3 + 1
; Inits the lcd display
LCD4004_init:
; --- Init first controller ---
; The following are documented as in the datasheet: RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
; Function set: 0 0 0 0 1 DL N F 0 0 (DL: 8-bit/4-bit, N: number of display lines 2/1, F: Font type 11dots/8dots)
; 0000111000 (8 bit, 2 lines)
ld a,0x38
out (LCD_TOP_INSTR_REG),a
call LCD4004_wait_busy_clear
; Display ON/OFF Control: 0 0 0 0 0 0 1 D C B (D: display on/off, C: cursor on/off, B: blinking cursor on/off)
; 0000001111 (display on, blinking cursor)
ld a,0x0F
out (LCD_TOP_INSTR_REG),a
call LCD4004_wait_busy_clear
; Clear display
; 0000000001
ld a,0x01
out (LCD_TOP_INSTR_REG),a
call LCD4004_wait_busy_clear
; --- Init second controller ---
; The following are documented as in the datasheet: RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
; Function set: 0 0 0 0 1 DL N F 0 0 (DL: 8-bit/4-bit, N: number of display lines 2/1, F: Font type 11dots/8dots)
; 0000111000 (8 bit, 2 lines)
ld a,0x38
out (LCD_BOTTOM_INSTR_REG),a
call LCD4004_wait_busy_clear
; Display ON/OFF Control: 0 0 0 0 0 0 1 D C B (D: display on/off, C: cursor on/off, B: blinking cursor on/off)
; 0000001111 (display on, blinking cursor)
ld a,0x0F
out (LCD_BOTTOM_INSTR_REG),a
call LCD4004_wait_busy_clear
; Clear display
; 0000000001
ld a,0x01
out (LCD_BOTTOM_INSTR_REG),a
call LCD4004_wait_busy_clear
ret
; Prints string
; @param BC Pointer to a null-terminated string first character
LCD4004_TOP_print:
ld a, (bc) ; bc is the pointer to passed string's first char
cp 0 ; compare A content with 0 (subtract 0 from value and set zero flag Z if result is 0)
ret z ; if prev compare is true (Z flag set), string is finished, return
out (LCD_TOP_DATA_REG),a ; output char
call LCD4004_wait_busy_clear ; wait for the lcd to execute (busy signal check)
inc bc ; increment bc to move to next char
jp LCD4004_TOP_print
; Prints string
; @param BC Pointer to a null-terminated string first character
LCD4004_BOTTOM_print:
ld a, (bc) ; bc is the pointer to passed string's first char
cp 0 ; compare A content with 0 (subtract 0 from value and set zero flag Z if result is 0)
ret z ; if prev compare is true (Z flag set), string is finished, return
out (LCD_BOTTOM_DATA_REG),a ; output char
call LCD4004_wait_busy_clear ; wait for the lcd to execute (busy signal check)
inc bc ; increment bc to move to next char
jp LCD4004_BOTTOM_print
; Waits for the busy flag to be clear, indicating the LCD controllers to have finished their work
LCD4004_wait_busy_clear:
ret
in a, (LCD_TOP_INSTR_REG) ; reads the status
rla ; busy flag is DB7, so we shift it into carry to check it
jp c, LCD4004_wait_busy_clear ; if carry is set, lcd is busy
in a, (LCD_BOTTOM_INSTR_REG) ; reads the status
rla ; busy flag is DB7, so we shift it into carry to check it
jp c, LCD4004_wait_busy_clear ; if carry is set, lcd is busy
ret
; --- TEST CODE ---
main:
TEST_STR_80: DB "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam augue tortor sed.",0 ; null terminated string
TEST_STR_85: DB "2Lorem ipsum dolor sit amet, consectetur adipiscing elit praesent pellentesque nisi.",0 ; null terminated string
; Init display
call LCD4004_init
; Write string on first 2 lines
ld bc, TEST_STR_80
call LCD4004_TOP_print
; Write long string on last 2 lines (to see how it wraps)
ld bc, TEST_STR_85
call LCD4004_BOTTOM_print
halt

View File

@ -1,5 +1,7 @@
# Pat80 emulator # Pat80 emulator
![Emulator running Memory Monitor](/assets/media/photos/emulator.png)
## Setup ## Setup
Install the required Z80 emulator by redcode following the instructions for your Linux distribution at https://github.com/redcode/Z80 Install the required Z80 emulator by redcode following the instructions for your Linux distribution at https://github.com/redcode/Z80

View File

@ -2,6 +2,7 @@
* PAT80 Emulator * PAT80 Emulator
* *
* Emulates a PAT80. * Emulates a PAT80.
* Based on https://github.com/redcode/Z80
*/ */
#include <Z/constants/pointer.h> /* Z_NULL */ #include <Z/constants/pointer.h> /* Z_NULL */
@ -29,6 +30,8 @@ typedef struct {
Z80 cpu; Z80 cpu;
WINDOW *terminal_win; WINDOW *terminal_win;
WINDOW *status_win; WINDOW *status_win;
WINDOW *lcd_top_win; // 40x4 LCD, top two lines
WINDOW *lcd_bottom_win; // 40x4 LCD, bottom two lines
} Machine; } Machine;
@ -49,8 +52,8 @@ static zuint8 machine_cpu_in(Machine *self, zuint16 port) {
// so the 3 most significant IO addr bits in this case are A7, A6, A5. The bits // so the 3 most significant IO addr bits in this case are A7, A6, A5. The bits
// A4-A0 may be used by the single device, at its own discretion. // A4-A0 may be used by the single device, at its own discretion.
zuint16 bitmask = 7; // 0000000000000111 zuint16 bitmask = 7; // 0000000000000111
int decoded = port & bitmask; int ioDevice = port & bitmask;
if (decoded <= 0x1F) { if (ioDevice <= 0x1F) {
// Port 0 (0x00 to 0x1F): terminal // Port 0 (0x00 to 0x1F): terminal
// Read char from stin // Read char from stin
char c = getch(); char c = getch();
@ -65,37 +68,45 @@ static zuint8 machine_cpu_in(Machine *self, zuint16 port) {
return c; return c;
} }
} }
if (decoded <= 0x3F) { if (ioDevice <= 0x3F) {
// Port 1 (0x20 to 0x3F): sound card (sn76489) // Port 1 (0x20 to 0x3F): sound card (sn76489)
wprintw(self->status_win, "sound_cmd[IN]: Not supported!\n"); wprintw(self->status_win, "sound_cmd[IN]: Not supported!\n");
return 0x00; return 0x00;
} }
if (decoded <= 0x5F) { if (ioDevice <= 0x5F) {
// Port 2 (0x40 to 0x5F) // Port 2 (0x40 to 0x5F): LCD top 2 lines
if (port == 0x40 || port == 0x60) {
// TODO: Simulate busy flag and cursor position
return 0x00; // Busy flag clear, for now
}
wprintw(self->status_win, "IO_ERROR_IN: No device at port 2\n"); wprintw(self->status_win, "IO_ERROR_IN: No device at port 2\n");
return 0x00; return 0x00;
} }
if (decoded <= 0x7F) { if (ioDevice <= 0x7F) {
// Port 3 (0x60 to 0x7F) // Port 3 (0x60 to 0x7F): LCD bottom 2 lines
if (port == 0x0) {
// TODO: Simulate busy flag and cursor position
return 0x00;
}
wprintw(self->status_win, "IO_ERROR_IN: No device at port 3\n"); wprintw(self->status_win, "IO_ERROR_IN: No device at port 3\n");
return 0x00; return 0x00;
} }
if (decoded <= 0x9F) { if (ioDevice <= 0x9F) {
// Port 4 (0x80 to 0x9F) // Port 4 (0x80 to 0x9F)
wprintw(self->status_win, "IO_ERROR_IN: No device at port 4\n"); wprintw(self->status_win, "IO_ERROR_IN: No device at port 4\n");
return 0x00; return 0x00;
} }
if (decoded <= 0x5F) { if (ioDevice <= 0x5F) {
// Port 5 (0xA0 to 0xBF) // Port 5 (0xA0 to 0xBF)
wprintw(self->status_win, "IO_ERROR_IN: No device at port 5\n"); wprintw(self->status_win, "IO_ERROR_IN: No device at port 5\n");
return 0x00; return 0x00;
} }
if (decoded <= 0x5F) { if (ioDevice <= 0x5F) {
// Port 6 (0xC0 to 0xDF) // Port 6 (0xC0 to 0xDF)
wprintw(self->status_win, "IO_ERROR_IN: No device at port 6\n"); wprintw(self->status_win, "IO_ERROR_IN: No device at port 6\n");
return 0x00; return 0x00;
} }
if (decoded <= 0x5F) { if (ioDevice <= 0x5F) {
// Port 7 (0xE0 to 0xFF) // Port 7 (0xE0 to 0xFF)
wprintw(self->status_win, "IO_ERROR_IN: No device at port 7\n"); wprintw(self->status_win, "IO_ERROR_IN: No device at port 7\n");
} else { } else {
@ -106,6 +117,8 @@ static zuint8 machine_cpu_in(Machine *self, zuint16 port) {
refresh(); refresh();
wrefresh(self->terminal_win); wrefresh(self->terminal_win);
wrefresh(self->status_win); wrefresh(self->status_win);
wrefresh(self->lcd_top_win);
wrefresh(self->lcd_bottom_win);
} }
@ -115,29 +128,56 @@ static void machine_cpu_out(Machine *self, zuint16 port, zuint8 value) {
// so the 3 most significant IO addr bits in this case are A7, A6, A5. The bits // so the 3 most significant IO addr bits in this case are A7, A6, A5. The bits
// A4-A0 may be used by the single device, at its own discretion. // A4-A0 may be used by the single device, at its own discretion.
zuint16 bitmask = 0xE0; // 0000000011100000 zuint16 bitmask = 0xE0; // 0000000011100000
int decoded = port & bitmask; int ioDevice = port & bitmask;
if (decoded <= 0x1F) { bitmask = 0x1F; // 0000000000011111
int ioAddrInsideDevice = port & bitmask;
wprintw(self->status_win, "[%#06x]DECODED[%#04x] - ", port, ioDevice);
if (ioDevice <= 0x1F) {
// Port 0 (0x00 to 0x1F): terminal // Port 0 (0x00 to 0x1F): terminal
wprintw(self->terminal_win, "%c", value); wprintw(self->terminal_win, "%c", value);
} else if (decoded <= 0x3F) { } else if (ioDevice <= 0x3F) {
// Port 1 (0x20 to 0x3F): sound card (sn76489) // Port 1 (0x20 to 0x3F): sound card (sn76489)
wprintw(self->status_win, "sound_cmd[%#04x]\n", value); wprintw(self->status_win, "sound_cmd[%#04x]\n", value);
} else if (decoded <= 0x5F) { } else if (ioDevice <= 0x5F) {
// Port 2 (0x40 to 0x5F) // Port 2 (0x40 to 0x5F) and Port 3 (0x60 to 0x7F):
wprintw(self->status_win, "IO_ERROR_OUT: No device at port 2\n"); // lcd display 40x4 (mod. TM404A, based on 2 KS0066 chips, each one controlling 2 rows,
} else if (decoded <= 0x7F) { // top controller at port 2 and bottom at port 3).
// Port 3 (0x60 to 0x7F) // Instruction register at each port first address, data register at second address
wprintw(self->status_win, "IO_ERROR_OUT: No device at port 3\n"); if (ioAddrInsideDevice == 0) {
} else if (decoded <= 0x9F) { // Port 2, A4 LOW = talking to LCD top 2 lines instruction register
wprintw(self->status_win, "lcd_top_cmd[%#04x]\n", value);
} else if (ioAddrInsideDevice == 1) {
// Port 2, A4 HIGH = talking to LCD top 2 lines data register (writing text to screen)
wprintw(self->status_win, "lcd_top_data[%#04x](%c)\n", value, value);
wprintw(self->lcd_top_win, "%c", value);
} else {
wprintw(self->status_win, "IO_ERROR_OUT: lcd (top controller) does not listen at addr %#04x\n", ioAddrInsideDevice);
}
} else if (ioDevice <= 0x7F) {
// Port 2 (0x40 to 0x5F) and Port 3 (0x60 to 0x7F):
// lcd display 40x4 (mod. TM404A, based on 2 KS0066 chips, each one controlling 2 rows,
// top controller at port 2 and bottom at port 3).
// Instruction register at each port first address, data register at second address
if (ioAddrInsideDevice == 0) {
// Port 3, A4 LOW = talking to LCD bottom 2 lines instruction register
wprintw(self->status_win, "lcd_bottom_cmd[%#04x]\n", value);
} else if (ioAddrInsideDevice == 1) {
// Port 3, A4 HIGH = talking to LCD bottom 2 lines data register (writing text to screen)
wprintw(self->status_win, "lcd_bottom_data[%#04x](%c)\n", value, value);
wprintw(self->lcd_bottom_win, "%c", value);
} else {
wprintw(self->status_win, "IO_ERROR_OUT: lcd (bottom controller) does not listen at addr %#04x\n", ioAddrInsideDevice);
}
} else if (ioDevice <= 0x9F) {
// Port 4 (0x80 to 0x9F) // Port 4 (0x80 to 0x9F)
wprintw(self->status_win, "IO_ERROR_OUT: No device at port 4\n"); wprintw(self->status_win, "IO_ERROR_OUT: No device at port 4\n");
} else if (decoded <= 0x5F) { } else if (ioDevice <= 0xBF) {
// Port 5 (0xA0 to 0xBF) // Port 5 (0xA0 to 0xBF)
wprintw(self->status_win, "IO_ERROR_OUT: No device at port 5\n"); wprintw(self->status_win, "IO_ERROR_OUT: No device at port 5\n");
} else if (decoded <= 0x5F) { } else if (ioDevice <= 0xDF) {
// Port 6 (0xC0 to 0xDF) // Port 6 (0xC0 to 0xDF)
wprintw(self->status_win, "IO_ERROR_OUT: No device at port 6\n"); wprintw(self->status_win, "IO_ERROR_OUT: No device at port 6\n");
} else if (decoded <= 0x5F) { } else if (ioDevice <= 0xFF) {
// Port 7 (0xE0 to 0xFF) // Port 7 (0xE0 to 0xFF)
wprintw(self->status_win, "IO_ERROR_OUT: No device at port 7\n"); wprintw(self->status_win, "IO_ERROR_OUT: No device at port 7\n");
} else { } else {
@ -148,6 +188,21 @@ static void machine_cpu_out(Machine *self, zuint16 port, zuint8 value) {
refresh(); refresh();
wrefresh(self->terminal_win); wrefresh(self->terminal_win);
wrefresh(self->status_win); wrefresh(self->status_win);
wrefresh(self->lcd_top_win);
wrefresh(self->lcd_bottom_win);
}
static void machine_cpu_halt(Machine *self, unsigned char signal) {
wprintw(self->status_win, "HALTED (%d)\n", signal);
// Refresh all windows
refresh();
wrefresh(self->terminal_win);
wrefresh(self->status_win);
wrefresh(self->lcd_top_win);
wrefresh(self->lcd_bottom_win);
// Wait an ESC before exiting
while (getch() != 27) {}
exit(0);
} }
@ -160,7 +215,7 @@ void machine_initialize(Machine *self) {
self->cpu.write = (Z80Write)machine_cpu_write; self->cpu.write = (Z80Write)machine_cpu_write;
self->cpu.in = (Z80Read )machine_cpu_in; self->cpu.in = (Z80Read )machine_cpu_in;
self->cpu.out = (Z80Write)machine_cpu_out; self->cpu.out = (Z80Write)machine_cpu_out;
self->cpu.halt = Z_NULL; self->cpu.halt = (Z80Halt)machine_cpu_halt;
self->cpu.nmia = Z_NULL; self->cpu.nmia = Z_NULL;
self->cpu.inta = Z_NULL; self->cpu.inta = Z_NULL;
self->cpu.int_fetch = Z_NULL; self->cpu.int_fetch = Z_NULL;
@ -210,6 +265,7 @@ int main(int argc, char *argv[]) {
start_color(); // Use colors start_color(); // Use colors
init_pair(1, COLOR_WHITE, COLOR_BLUE); // Terminal window color init_pair(1, COLOR_WHITE, COLOR_BLUE); // Terminal window color
init_pair(2, COLOR_YELLOW, COLOR_BLACK); // Status window color init_pair(2, COLOR_YELLOW, COLOR_BLACK); // Status window color
init_pair(3, COLOR_BLACK, COLOR_GREEN); // LCD window color
int x,y; int x,y;
getmaxyx(stdscr, y,x); getmaxyx(stdscr, y,x);
@ -219,13 +275,19 @@ int main(int argc, char *argv[]) {
/*zusize*/ .cycles = 0, /*zusize*/ .cycles = 0,
/*Z80*/ .cpu = pat80Cpu, /*Z80*/ .cpu = pat80Cpu,
.terminal_win = newwin(TERMINAL_HEIGHT, TERMINAL_WIDTH, INSTRUCTION_WINDOW_HEIGHT, 0), .terminal_win = newwin(TERMINAL_HEIGHT, TERMINAL_WIDTH, INSTRUCTION_WINDOW_HEIGHT, 0),
.status_win = newwin(y, x - TERMINAL_WIDTH - SPACING_BETWEEN_WINDOWS, INSTRUCTION_WINDOW_HEIGHT, TERMINAL_WIDTH + SPACING_BETWEEN_WINDOWS) .status_win = newwin(y, x - TERMINAL_WIDTH - SPACING_BETWEEN_WINDOWS, INSTRUCTION_WINDOW_HEIGHT, TERMINAL_WIDTH + SPACING_BETWEEN_WINDOWS), // To right of terminal window
.lcd_top_win = newwin(2, 40, INSTRUCTION_WINDOW_HEIGHT + TERMINAL_HEIGHT + SPACING_BETWEEN_WINDOWS, 0), // Below terminal window
.lcd_bottom_win = newwin(2, 40, INSTRUCTION_WINDOW_HEIGHT + TERMINAL_HEIGHT + SPACING_BETWEEN_WINDOWS + 2, 0)
}; };
wbkgd(pat80.terminal_win, COLOR_PAIR(1)); // Ncurses: set terminal window color wbkgd(pat80.terminal_win, COLOR_PAIR(1)); // Ncurses: set terminal window color
wbkgd(pat80.status_win, COLOR_PAIR(2)); wbkgd(pat80.status_win, COLOR_PAIR(2));
wbkgd(pat80.lcd_top_win, COLOR_PAIR(3));
wbkgd(pat80.lcd_bottom_win, COLOR_PAIR(3));
scrollok(pat80.terminal_win, TRUE); // Ncurses: Allow scrolling when reached end of window scrollok(pat80.terminal_win, TRUE); // Ncurses: Allow scrolling when reached end of window
scrollok(pat80.status_win, TRUE); scrollok(pat80.status_win, TRUE);
scrollok(pat80.lcd_top_win, FALSE);
scrollok(pat80.lcd_bottom_win, FALSE);
attron(A_BOLD); // Print instructions attron(A_BOLD); // Print instructions
printw("Emulator commands\n"); printw("Emulator commands\n");
attroff(A_BOLD); attroff(A_BOLD);
@ -251,6 +313,8 @@ int main(int argc, char *argv[]) {
// Stop ncurses // Stop ncurses
delwin(pat80.terminal_win); delwin(pat80.terminal_win);
delwin(pat80.status_win); delwin(pat80.status_win);
delwin(pat80.lcd_top_win);
delwin(pat80.lcd_bottom_win);
endwin(); endwin();
return 0; return 0;
} }

View File

@ -0,0 +1,2 @@
(kicad_pcb (version 20240108) (generator "pcbnew") (generator_version "8.0")
)

View File

@ -0,0 +1,83 @@
{
"board": {
"active_layer": 0,
"active_layer_preset": "",
"auto_track_width": true,
"hidden_netclasses": [],
"hidden_nets": [],
"high_contrast_mode": 0,
"net_color_mode": 1,
"opacity": {
"images": 0.6,
"pads": 1.0,
"tracks": 1.0,
"vias": 1.0,
"zones": 0.6
},
"selection_filter": {
"dimensions": true,
"footprints": true,
"graphics": true,
"keepouts": true,
"lockedItems": false,
"otherItems": true,
"pads": true,
"text": true,
"tracks": true,
"vias": true,
"zones": true
},
"visible_items": [
0,
1,
2,
3,
4,
5,
8,
9,
10,
11,
12,
13,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
32,
33,
34,
35,
36,
39,
40
],
"visible_layers": "fffffff_ffffffff",
"zone_display_mode": 0
},
"git": {
"repo_password": "",
"repo_type": "",
"repo_username": "",
"ssh_key": ""
},
"meta": {
"filename": "4004_lcd_display.kicad_prl",
"version": 3
},
"project": {
"files": []
}
}

View File

@ -0,0 +1,392 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {},
"diff_pair_dimensions": [],
"drc_exclusions": [],
"rules": {},
"track_widths": [],
"via_dimensions": []
},
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"conflicting_netclasses": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"global_label_dangling": "warning",
"hier_label_mismatch": "error",
"label_dangling": "error",
"lib_symbol_issues": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"similar_labels": "warning",
"simulation_model_issue": "ignore",
"unannotated": "error",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "4004_lcd_display.kicad_pro",
"version": 1
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 3
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"plot": "",
"pos_files": "",
"specctra_dsn": "",
"step": "",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"bom_export_filename": "",
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",
"keep_line_breaks": false,
"keep_tabs": false,
"name": "CSV",
"ref_delimiter": ",",
"ref_range_delimiter": "",
"string_delimiter": "\""
},
"bom_presets": [],
"bom_settings": {
"exclude_dnp": false,
"fields_ordered": [
{
"group_by": false,
"label": "Reference",
"name": "Reference",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
},
{
"group_by": false,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": true,
"label": "DNP",
"name": "${DNP}",
"show": true
}
],
"filter_string": "",
"group_symbols": true,
"name": "Grouped By Value",
"sort_asc": true,
"sort_field": "Riferimento"
},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"operating_point_overlay_i_precision": 3,
"operating_point_overlay_i_range": "~A",
"operating_point_overlay_v_precision": 3,
"operating_point_overlay_v_range": "~V",
"overbar_offset_ratio": 1.23,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"page_layout_descr_file": "",
"plot_directory": "",
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true,
"spice_save_all_currents": false,
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [
[
"b60673c4-5448-4b81-8c25-50d47b52f4a5",
"Root"
]
],
"text_variables": {}
}