diff --git a/.gitignore b/.gitignore index 780d7651..dbd00535 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ impact.xsl impact_impact.xwbt qasm2rom register_file.sym +qnice +qasm diff --git a/doc/nice_can.pdf b/doc/nice_can.pdf new file mode 100644 index 00000000..8186e2bb Binary files /dev/null and b/doc/nice_can.pdf differ diff --git a/doc/qnice_intro.pdf b/doc/qnice_intro.pdf new file mode 100644 index 00000000..f8006df6 Binary files /dev/null and b/doc/qnice_intro.pdf differ diff --git a/emulator/ide_simulation.c b/emulator/ide_simulation.c new file mode 100644 index 00000000..48e4f1e7 --- /dev/null +++ b/emulator/ide_simulation.c @@ -0,0 +1,721 @@ +/* This programm emulates an IDE controller of a compact flash card (cf) in True IDE mode. +* +* Following restrictions apply to this simulation: +* --- The simulation doesn't care about the mapping between the I/O IDE registers in the hosts main memory +* and his internal registers (in other words the physical connection). The I/O registers of the host are +* not used. A read or write to the hosts registers results in a direct read or write of the internal drive +* registers. The signal management nessecary to provide this in reality is ignored. +*--- A IDE controller can have more than one device attached. +* The device selection is done by setting the DEV bit (bit 4) of the SDH_REGISTER. Anyway the registers are +* always written to any attached drive. The drives embbeded controller then decides wether to take action or +* not. The DEV bit is expected to be set to 0 and is further ignored. It is always assumed that device0 +* should execute the given commands. +*--- The Simulator does not support Power Management Features: The device is always expected to be powered up. +* Other modes like Sleep Mode are not supported. The power up itself, which does a lot of things like +* internal diagnostic, is not simulated either. Only the register initialization must be done by calling +* initializeIDEDevice() once. The qnice simulator should do this before interpreting any assembler code. +*--- Only a small subset of the possible and partly required (at least by the cf or ATA specification) +* commands are supported: +* Read Sector(s) +* Read Verify +* Request Sense +* Write Sector(s) +* Write Verify +*--- The BSY bit is set like in a real drive although you can never verify that in assembler: when the next +* assembler directive is interpreted the BSY bit has been reset to zero (we would need multithreading to +* implement it otherwise). Anyway the BSY bit should always been checked in the assembler code because with +* a real drive it may take some time until a command is executed or aborted and the BSY bit is reseted to 0. +*--- Currently only CHS addressing is supported even though according to cf spec LBA should be supported too. +* @Author: Kai Lutterbeck +*/ + +#include +#include +#include +#include "ide_simulation.h" + +#undef DEBUG + +/* A 32MB Compact Flash Card (CF) is defined because available memory is limited. +* The numbers for CHS are from the SanDisc CF specifiction. CFs from other vendors may +* have a diffrent partitioning. */ +#define NO_OF_CYLINDERS 490 +#define NO_OF_HEADS 4 +#define NO_OF_SECTORS 32 +#define NO_OF_BYTES_PER_SECTOR 512 +#define MAX_SECTORS_PER_ACCESS 256 + +//All registers are 8bit long except the data register which is 16bit long. +#define NO_OF_INTERNAL_IDE_REGISTERS 12 +#define DATA_REGISTER 0 //this is the only 16bit register +#define ERROR_REGISTER 1 +#define FEATURES_REGISTER 2 +#define SECTOR_COUNT_REGISTER 3 +#define SECTOR_NUMBER_REGISTER 4 +#define CYLINDER_LOW_REGISTER 5 +#define CYLINDER_HIGH_REGISTER 6 +#define SDH_REGISTER 7 //"Sector Size, Drive, Head Register" aka "Device/Head Register" +#define STATUS_REGISTER 8 +#define COMMAND_REGISTER 9 +#define ALTERNATE_STATUS_REGISTER 10 +#define DEVICE_CONTROL_REGISTER 11 + +#define READ_MODE TRUE +#define WRITE_MODE FALSE +/*All information in the ide_device struct are specific for one device.*/ +typedef struct ide_device +{ + int device[NO_OF_CYLINDERS][NO_OF_HEADS][NO_OF_SECTORS][NO_OF_BYTES_PER_SECTOR], + registers[NO_OF_INTERNAL_IDE_REGISTERS], + buffer[NO_OF_BYTES_PER_SECTOR], + current_cylinder, + current_head, + current_sector, + no_sectors_to_access, + sector_count, + pio_datain_in_progress, + pio_dataout_in_progress, + no_of_bytes_transfered, + buffer_filled, + extended_error_code; +} ide_device; + +ide_device gbl$device0; + + +void writeRegister(unsigned int address, unsigned int value); +void writeDataRegister(unsigned int value); +unsigned int readRegister(unsigned int address); +unsigned int readDataRegister(); + +void executeCommand(); +void handleReadWriteSectors(int isReadMode, int isVerifyOnly); +void verifyReadSectors(); +void prepareNextSectorForPIO(int isReadMode, int isVerifyOnly); + + + + +/* Just writes the value to the register. Ensures register size (8 or 16 bits) + * and validity of array position. */ +void writeRegister(unsigned int address, unsigned int value) +{ + if (address == DATA_REGISTER) /*Data register is 16 bits long */ + value &= 0xffff; + else /*All other registers are 8 bits long */ + value &= 0xff; + + if (address < NO_OF_INTERNAL_IDE_REGISTERS) { + gbl$device0.registers[address] = value; + } else { + printf("writeRegister: Illegal address during write: %x!\n Internal register array out of bound.\n", + address); + exit(-1); + } +} + + +/* Just reads the value from the register. Ensures register size (8 or 16 bits) + * and validity of array position. */ +unsigned int readRegister(unsigned int address) +{ + unsigned int returnValue; + if (address < NO_OF_INTERNAL_IDE_REGISTERS) { + returnValue = gbl$device0.registers[address]; + } else { + printf("readRegister: Illegal address during read: %x!\n Internal register array out of bound.\n", + address); + exit(-1); + } + //is paranoia epidemic? + if (address == DATA_REGISTER) /*Data register is 16 bits long */ + returnValue &= 0xffff; + else /*All other registers are 8 bits long */ + returnValue &= 0xff; + + return returnValue; +} + + +/* Selects the correct register to write to from the hosts I/O register address + * The result of writing to the + * Command register while the BSY bit is equal to one or the DRQ bit is equal to one is unpredictable and may + * result in data corruption. Writes to other command block registers are ignored by the device when BSY is set + * except for writing the reset flag to the Device Control Register, which immediately results in a software reset. + */ +void writeIDEDeviceRegister(unsigned int address, unsigned int value) +{ + /*write access is ignored except for the Device Control Register when BSY is set + (although this can never happen in non multithreaded simulation) */ + if ( (!(readRegister(STATUS_REGISTER) & 0x80)) || address == 8) { +#ifdef DEBUG + printf("writeIDEDeviceRegister: write to register '%X' with value '%X' requested.\n", address, value); +#endif + switch(address) + { + case 0: + writeDataRegister(value); + break; + case 1: + writeRegister(FEATURES_REGISTER,value); + break; + case 2: + writeRegister(SECTOR_COUNT_REGISTER,value); + break; + case 3: + writeRegister(SECTOR_NUMBER_REGISTER,value); + break; + case 4: + writeRegister(CYLINDER_LOW_REGISTER,value); + break; + case 5: + writeRegister(CYLINDER_HIGH_REGISTER,value); + break; + case 6: + writeRegister(SDH_REGISTER,value | 0xA0); // bit 5 and 7 must always be set + break; + case 7: + writeRegister(COMMAND_REGISTER,value); + executeCommand (); + break; + case 8: + writeRegister(DEVICE_CONTROL_REGISTER,value); + //software reset immediately results after writing reset flag to the Device Control Register + if (readRegister(DEVICE_CONTROL_REGISTER) & 0x04) { + initializeIDEDevice(); + } + break; + default: + printf("writeIDEDeviceRegister: Illegal register access in write mode at address: %x!\n", address); + exit(-1); + } + } else { +#ifdef DEBUG + printf("writeIDEDeviceRegister: write to register '%X' with value '%X' ignored because drive is busy.\n", + address, value); +#endif + } +} + +/* Selects the correct register to read from according to the hosts I/O register read */ +unsigned int readIDEDeviceRegister(unsigned int address) { + unsigned int returnCode; +#ifdef DEBUG + printf("readIDEDeviceRegister: read of register '%X' requested.\n", address); +#endif + switch(address) + { + case 0: + returnCode = readDataRegister(); + break; + case 1: + returnCode = readRegister(ERROR_REGISTER); + break; + case 2: + returnCode = readRegister(SECTOR_COUNT_REGISTER); + break; + case 3: + returnCode = readRegister(SECTOR_NUMBER_REGISTER); + break; + case 4: + returnCode = readRegister(CYLINDER_LOW_REGISTER); + break; + case 5: + returnCode = readRegister(CYLINDER_HIGH_REGISTER); + break; + case 6: + returnCode = readRegister(SDH_REGISTER); + break; + case 7: + returnCode = readRegister(STATUS_REGISTER); + break; + case 8: + returnCode = readRegister(ALTERNATE_STATUS_REGISTER); + break; + default: + printf("readIDEDeviceRegister: Illegal register access in read mode at address: %x!\n", address); + exit(-1); + } +#ifdef DEBUG + printf("readIDEDeviceRegister: read from register '%X' returns value '%X'.\n", address, returnCode); +#endif + return returnCode; +} + +/* Executes the command written to the COMMAND_REGISTER */ +void executeCommand () +{ +#ifdef DEBUG + printf("Execution of command '%X' requested.\n",readRegister(COMMAND_REGISTER)); +#endif + + //each command sets the drive status to busy as long as DRQ is not set + if (!(readRegister(STATUS_REGISTER) & 0x08)) + { + //Setting BSY (Bit 7) - No other bits in this register are valid when this bit is set. + writeRegister(STATUS_REGISTER, readRegister(STATUS_REGISTER) | 0x80); + //Setting BSY(Bit 7) - No other bits in this register are valid when this bit is set. + writeRegister(ALTERNATE_STATUS_REGISTER, readRegister(ALTERNATE_STATUS_REGISTER) | 0x80); + } + + switch(readRegister(COMMAND_REGISTER)) + { + case 0x03: + /* Request sense - cf specific command ... but maybe helpful. + * Request sense is not part of the ATA-3 specification and is not supported by IDE hard disks. + * It is part the CFA Feature Set //(CFA = The CompactFlash Association that created the + * specification for compact flash memory that uses the ATA interface) that was first mentioned + * and integrated in the ATA-4 specification. It returns an extended error code for the last + * issued command in the error register. Request sense can be called multiple times and will + * always return the extended error code of the last issued command that was not request sense + * command itself */ + + //Setting the extended error code + writeRegister(ERROR_REGISTER, gbl$device0.extended_error_code); + //Setting DRDY and DSC and clearing BSY + writeRegister(STATUS_REGISTER, 0x50); + writeRegister(ALTERNATE_STATUS_REGISTER, 0x50); + break; + case 0x20: //Read Sector(s) with retries + //Executed the same way as 0x21, because we wan't have any trouble with invalid sectors :-) + handleReadWriteSectors(READ_MODE, FALSE); + break; + case 0x21: //Read Sector(s) without retries + handleReadWriteSectors(READ_MODE, FALSE); + break; + case 0x30: //Write Sector(s) with retries + //Executed the same way as 0x31, because we wan't have any trouble with invalid sectors :-) + handleReadWriteSectors(WRITE_MODE, FALSE); + break; + case 0x31: //Write Sector(s) without retries + handleReadWriteSectors(WRITE_MODE, FALSE); + break; + case 0x40: //Read Verify Sector(s) with retries + //Executed the same way as 0x41, because we wan't have any trouble with invalid sectors :-) + handleReadWriteSectors(READ_MODE, TRUE); + break; + case 0x41: //Read Verify Sector(s) without retries + /* This command is identical to the Read Sectors command, except that DRQ is never set and no + * data is transferred to the host. See method verifySectors for more information. + */ + handleReadWriteSectors(READ_MODE, TRUE); + break; + case 0x3C: //Write Verify + /* This command is similar to the Write Sector(s) command, except each sector is verified + * immediately after being written. This command has the same protocol as the Write Sector(s) + * command. So we can execute is the same way as 0x31 because write errors can't happen in simulation. + */ + handleReadWriteSectors(WRITE_MODE, FALSE); + break; + default: +#ifdef DEBUG + printf("executeCommand: Unknown or unimplemented command: %x!\n", readRegister(COMMAND_REGISTER)); +#endif + //Setting ABRT (Command aborted) + writeRegister(ERROR_REGISTER, 0x04); + //Extended error code: invalid command + gbl$device0.extended_error_code=0x20; + //Setting DRDY, DSC and ERR and clearing BSY + writeRegister(STATUS_REGISTER, 0x51); + writeRegister(ALTERNATE_STATUS_REGISTER, 0x51); + } +} + + +void handleReadWriteSectors(int isReadMode, int isVerifyOnly) { + int error = FALSE; + /* reset error register - it still may contain extended error codes or flags from + last command execution. */ + writeRegister(ERROR_REGISTER, 0x00); + + /*check addressing mode - only CHS is supported + We have to exit here because a controller should support both modes and for this reason + no error code or behavior is defined + */ + if ((readRegister(SDH_REGISTER) & 0x40)) { + //LBA (bit 6) is set. + printf("LBA addressing is currently not supported!\n"); + exit(-1); + } + + //begin determine parameters + + //determine starting cylinder + gbl$device0.current_cylinder = + (readRegister(CYLINDER_HIGH_REGISTER)<<8) + | readRegister(CYLINDER_LOW_REGISTER); + + //determine starting head - lower 4 bits of SDH Register + gbl$device0.current_head = readRegister(SDH_REGISTER) & 0xf; + + //determine starting sector + gbl$device0.current_sector = readRegister(SECTOR_NUMBER_REGISTER); + + //determine numbers of sectors to read or write + gbl$device0.no_sectors_to_access = readRegister(SECTOR_COUNT_REGISTER); + //Zero in sector count means maximum block read or write + if (gbl$device0.no_sectors_to_access == 0) + gbl$device0.no_sectors_to_access = MAX_SECTORS_PER_ACCESS; + //end determine parameterss +#ifdef DEBUG + printf("handleReadWriteSectors: Parameters found:\nCylinder: %i\nHead: %i\nSector: %i\n", + gbl$device0.current_cylinder, gbl$device0.current_head, gbl$device0.current_sector); +#endif + //check if parameters are valid + if ( gbl$device0.current_cylinder == 0 || gbl$device0.current_cylinder > NO_OF_CYLINDERS || + gbl$device0.current_head == 0 || gbl$device0.current_head > NO_OF_HEADS || + gbl$device0.current_sector == 0 || gbl$device0.current_sector > NO_OF_SECTORS) { +#ifdef DEBUG + printf("handleReadWriteSectors: Invalid parameter(s):\nCylinder: %x\nHead: %x\nSector: %x\n", + gbl$device0.current_cylinder, gbl$device0.current_head, gbl$device0.current_sector); +#endif + //Setting ABRT (Command aborted) + writeRegister(ERROR_REGISTER, 0x04); + //Extended error code: invalid address + gbl$device0.extended_error_code=0x21; + //Setting DRDY, DSC and ERR and clearing BSY + writeRegister(STATUS_REGISTER, 0x51); + writeRegister(ALTERNATE_STATUS_REGISTER, 0x51); + error = TRUE; + } + + if (! error) { + if (isReadMode) { + if (isVerifyOnly) { + verifyReadSectors(); + } else { + gbl$device0.pio_datain_in_progress=TRUE; + gbl$device0.pio_dataout_in_progress=FALSE; + gbl$device0.sector_count=0; + prepareNextSectorForPIO(READ_MODE,FALSE); + } + + } else { + gbl$device0.pio_datain_in_progress=FALSE; + gbl$device0.pio_dataout_in_progress=TRUE; + gbl$device0.sector_count=0; + prepareNextSectorForPIO(WRITE_MODE,FALSE); + } + } +} + +/*Prepares the next sector for pio if there is one or finshes command excecution*/ +void prepareNextSectorForPIO(int isReadMode, int isVerifyOnly) { + if (gbl$device0.sector_count 0) { + //check if increasing the sector would cross a head boundary + if ((gbl$device0.current_sector + 1) > NO_OF_SECTORS){ + //setting sector to first sector of the new head + gbl$device0.current_sector = 1; + //check if increasing the head would cross a cylinder boundary + if ((gbl$device0.current_head + 1) > NO_OF_HEADS){ + //go to the first head of the next cylinder + gbl$device0.current_head = 1; + gbl$device0.current_cylinder++; + //if going to the next cylinder reaches end of disk read must be aborted! + if (gbl$device0.current_cylinder > NO_OF_CYLINDERS) { +#ifdef DEBUG + printf("prepareNextSectorForPIO: Invalid address:\nCylinder: %x\nHead: %x\nSector: %x\n", + gbl$device0.current_cylinder, gbl$device0.current_head, + gbl$device0.current_sector); +#endif + //Setting ABRT (Command aborted) + writeRegister(ERROR_REGISTER, 0x04); + //in this case CHS-Registers should contain the address the access error occured at + writeRegister(SDH_REGISTER, (gbl$device0.current_head & 0xf) | + (readRegister(SDH_REGISTER) & 0xf0)); + writeRegister(SECTOR_NUMBER_REGISTER, gbl$device0.current_sector & 0xff); + writeRegister(CYLINDER_HIGH_REGISTER, (gbl$device0.current_cylinder >> 8) & 0xff); + writeRegister(CYLINDER_LOW_REGISTER, gbl$device0.current_cylinder & 0x00ff); + //Extended error code: invalid address + gbl$device0.extended_error_code=0x21; + //Setting DRDY, DSC and ERR and clearing BSY + writeRegister(STATUS_REGISTER, 0x51); + writeRegister(ALTERNATE_STATUS_REGISTER, 0x51); + error = TRUE; + } + } else { + gbl$device0.current_head++; + } + } else { + gbl$device0.current_sector++; + } + } //end of increasing sector + + if (! error) { + if (isReadMode) { + //fill read buffer with next sector + int i; + for (i=0; i> 8) & 0xff); + writeRegister(CYLINDER_LOW_REGISTER, gbl$device0.current_cylinder & 0x00ff); + writeRegister(STATUS_REGISTER, 0x50); + writeRegister(ALTERNATE_STATUS_REGISTER, 0x50); + } +} + + +unsigned int readDataRegister(){ + unsigned int returnCode; + if (! gbl$device0.pio_datain_in_progress) { + returnCode = readRegister(DATA_REGISTER); + } else { + //expecting 256 reads in a row. + if (gbl$device0.no_of_bytes_transfered < NO_OF_BYTES_PER_SECTOR && gbl$device0.buffer_filled) { + //write bytes to the Data Register before read. + writeRegister(DATA_REGISTER, (gbl$device0.buffer[gbl$device0.no_of_bytes_transfered+1]<<8) + | gbl$device0.buffer[gbl$device0.no_of_bytes_transfered]); + returnCode = readRegister(DATA_REGISTER); + //prepare for next read + gbl$device0.no_of_bytes_transfered += 2; +#ifdef DEBUG + printf("readDataRegister: Bytes transfered: %i\n", gbl$device0.no_of_bytes_transfered); +#endif + //check if last two bytes of the sector were transfered + if (gbl$device0.no_of_bytes_transfered==NO_OF_BYTES_PER_SECTOR) { + //prepare next sector + gbl$device0.sector_count+=1; + prepareNextSectorForPIO(READ_MODE, FALSE); + } + } else { + //should never happen + printf("readDataRegister: unexpected branch!\n"); + exit(-1); + } + } + return returnCode; +} + +void writeDataRegister(unsigned int value){ + if (! gbl$device0.pio_dataout_in_progress) { + writeRegister(DATA_REGISTER,value); + } else { + //expecting 256 writes in a row. + if (gbl$device0.no_of_bytes_transfered < NO_OF_BYTES_PER_SECTOR) { + //write bytes to the buffer + gbl$device0.buffer[gbl$device0.no_of_bytes_transfered]=value & 0xff; + gbl$device0.buffer[gbl$device0.no_of_bytes_transfered+1]= (value >> 8) & 0xff; + //prepare for next write + gbl$device0.no_of_bytes_transfered += 2; +#ifdef DEBUG + printf("writeDataRegister: Bytes transfered: %i\n", gbl$device0.no_of_bytes_transfered); +#endif + //check if last two bytes of the sector were transfered + if (gbl$device0.no_of_bytes_transfered==NO_OF_BYTES_PER_SECTOR) { + //write buffer to sector + int i; + for (i=0; i 0) ; + + /* If an error occured the number of unverified sectors have to be written to the Sector Count Register + * All other register have been filled correctly by error handling of the prepareNextSectorForPIO method. + * If no error occured all registers and the BSY flag have been set correctly by the prepareNextSectorForPIO method. + */ + if ((readRegister(STATUS_REGISTER) & 0x01) != 0) { + int unverifiedSectors = gbl$device0.no_sectors_to_access - gbl$device0.sector_count; +#ifdef DEBUG + printf("verifySectors: An access error has been detected during verify sectors at sector: %i\n%i sectors remain unverified:\n", + gbl$device0.sector_count, unverifiedSectors); +#endif + + //transform 256 unverified sectors to zero + if (unverifiedSectors==MAX_SECTORS_PER_ACCESS) + unverifiedSectors=0x00; + + writeRegister(SECTOR_COUNT_REGISTER, unverifiedSectors & 0xff); + } + +} + +/* +* For testing purposes only - will be removed at end of development +* Test to read two sectors from the device and check Status Register conditions*/ +void testMe() { + //Test write verify + writeIDEDeviceRegister(6,0xA2); + writeIDEDeviceRegister(5,0x00); + writeIDEDeviceRegister(4,0x80); + writeIDEDeviceRegister(3,0x02); + writeIDEDeviceRegister(2,0x02); + writeIDEDeviceRegister(7,0x41); + if (readIDEDeviceRegister(7) == 0x50) { + printf("\nRead Verify Command successful completed!\n"); + } + + + int i; + // writes two sectors starting from Cylinder:128, Head:2, Sector:2 + writeIDEDeviceRegister(6,0xA2); + writeIDEDeviceRegister(5,0x00); + writeIDEDeviceRegister(4,0x80); + writeIDEDeviceRegister(3,0x02); + writeIDEDeviceRegister(2,0x02); + writeIDEDeviceRegister(7,0x31); + + + if ((readIDEDeviceRegister(7)& 0x08) > 0) { + printf("Ready for write!\n"); + writeIDEDeviceRegister(0,('e' << 8) | 'T'); + writeIDEDeviceRegister(0,('t' << 8) | 's'); + for (i=0;i<254;i++){ + writeIDEDeviceRegister(0,0x0000); + } + if ((readIDEDeviceRegister(7)& 0x08) > 0) + printf("Still ready for write!\n"); + writeIDEDeviceRegister(0,('e' << 8) | 'M'); + int i; + for (i=0;i<255;i++){ + writeIDEDeviceRegister(0,0x0000); + } + } + + + + // reads two sectors starting from Cylinder:128, Head:2, Sector:2 + + writeIDEDeviceRegister(6,0xA2); + writeIDEDeviceRegister(5,0x00); + writeIDEDeviceRegister(4,0x80); + writeIDEDeviceRegister(3,0x02); + writeIDEDeviceRegister(2,0x02); + writeIDEDeviceRegister(7,0x21); + + if ((readIDEDeviceRegister(7)& 0x08) > 0) { + printf("Ready for read!\n"); + int sec[512], sec2[512]; + for (i=0; i<256; i++) { + int x = readIDEDeviceRegister(0); + sec [i*2] = x & 0xff; + sec [(i * 2) + 1] = (x >> 8) & 0xff; + } + for (i=0; i<256; i++) { + int x = readIDEDeviceRegister(0); + sec2[i*2] = x & 0xff; + sec2[(i * 2) + 1] = (x >> 8) & 0xff; + } + if (readIDEDeviceRegister(7) == 0x50) { + printf("\nRead Command successful completed!\n"); + printf("Read from IDE-Device: %c%c%c%c %c%c\n",sec[0],sec[1],sec[2],sec[3],sec2[0],sec[1]); + printf("Test %i and %i should be 0\n",sec[4],sec[511]); + } else { + printf("Read Command successful completed!\n"); + } + } else { + printf("Something is wrong!\n"); + } +} + diff --git a/emulator/ide_simulation.h b/emulator/ide_simulation.h new file mode 100755 index 00000000..776f9f05 --- /dev/null +++ b/emulator/ide_simulation.h @@ -0,0 +1,13 @@ +#ifndef TRUE +# define TRUE 1 +# define FALSE !TRUE +#endif + + +void writeIDEDeviceRegister(unsigned int address, unsigned int value); +unsigned int readIDEDeviceRegister(unsigned int address); +void initializeIDEDevice(); + +//For testing purposes only - will be removed at end of development +void testMe(); + diff --git a/emulator/make.bash b/emulator/make.bash new file mode 100755 index 00000000..8221f50d --- /dev/null +++ b/emulator/make.bash @@ -0,0 +1,2 @@ +#!/bin/bash +cc qnice.c ide_simulation.c uart_2681.c -o qnice diff --git a/emulator/qnice.c b/emulator/qnice.c new file mode 100644 index 00000000..1e27947e --- /dev/null +++ b/emulator/qnice.c @@ -0,0 +1,976 @@ +/* +** QNICE emulator -- this emulator was written as a proof of concept for the +** QNICE processor. In most cases Thomas' Perl based emulator will be used. :-) +** +** B. Ulmann, 16-AUG-2006...03-SEP-2006...04-NOV-2006...29-JUN-2007... +** 16-DEC-2007...03-JUN-2008...28-DEC-2014... +** xx-AUG-2015 +** +** Known bugs: +** +** 1) Executing the simple SUM.BIN yields to a statistics display with three more memory reads than expected. +** +*/ + +#define USE_UART + +#include +#include +#include +#include +#include "ide_simulation.h" + +#ifdef USE_UART +#include "uart_2681.h" + +unsigned int uart_read_register(uart_2681 *, int); +void uart_write_register(uart_2681 *, unsigned int, unsigned int); +void uart_hardware_initialization(uart_2681 *); +void uart_run_down(); +#endif + +/* +** Some preliminaries... +*/ + +#ifndef NULL +# define NULL 0 +#endif + +#ifndef TRUE +# define TRUE 1 +# define FALSE !TRUE +#endif + +#define STRING_LENGTH 132 +#define MEMORY_SIZE 65536 +#define REGMEM_SIZE 4096 + +/* The top most 1 kW of memory is reserverd for memory mapped IO devices */ +#define IO_AREA_START 0xfc00 +#define UART0_BASE_ADDRESS 0xfc00 +#define IDE_BASE_ADDRESS 0xfc10 + +#define NO_OF_INSTRUCTIONS 19 +#define NO_OF_ADDRESSING_MODES 4 +#define READ_MEMORY 0 /* This and the following constants are used to control the access_xxx functions */ +#define WRITE_MEMORY 1 + +/* The following constants form a bit mask to allow the exclusion of several bits */ +#define MODIFY_ALL 0x0 +#define DO_NOT_MODIFY_CARRY 0x1 +#define DO_NOT_MODIFY_X 0x2 +#define DO_NOT_MODIFY_OVERFLOW 0x4 + +#define GENERIC_BRANCH_OPCODE 0xf /* All branches share this common opcode */ + +typedef struct statistic_data +{ + unsigned int instruction_frequency[NO_OF_INSTRUCTIONS], /* Count the number of executions per instruction */ + addressing_modes[2][NO_OF_ADDRESSING_MODES], /* 0 -> read, 1 -> write */ + memory_accesses[2]; /* 0 -> read, 1 -> write */ +} statistic_data; + +int gbl$memory[MEMORY_SIZE], gbl$registers[REGMEM_SIZE], gbl$debug = FALSE, gbl$verbose = FALSE, + gbl$normal_operands[] = {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}, gbl$gather_statistics = FALSE, gbl$enable_uart = TRUE, + gbl$ctrl_c = FALSE; +char *gbl$normal_mnemonics[] = {"MOVE", "ADD", "ADDC", "SUB", "SUBC", "SHL", "SHR", "SWAP", + "NOT", "AND", "OR", "XOR", "CMP", "", "HALT"}, + *gbl$branch_mnemonics[] = {"ABRA", "ASUB", "RBRA", "RSUB"}, + *gbl$sr_bits = "1XCZNVIM", + *gbl$addressing_mnemonics[] = {"rx", "@rx", "@rx++", "@--rx"}; +statistic_data gbl$stat; + +#ifdef USE_UART +uart_2681 gbl$first_uart; +#endif + +/* +** +*/ +static void signal_handler_ctrl_c(int signo) +{ + gbl$ctrl_c = TRUE; +} + +/* +** upstr converts a string into upper case. +*/ +void upstr(char *string) +{ + while (*string) + { + if (*string >= 'a' && *string <= 'z') + *string += -'a' + 'A'; + string++; + } +} + +/* +** char_in returns TRUE if the character char is an element of string. +*/ +int char_in(char c, char *string) +{ + int i; + + for (i = 0; i < strlen(string); i++) + if (c == string[i]) + return TRUE; + + return FALSE; +} + +/* +** Local variant of strtok, just better. :-) The first call expects the string to be tokenized as its first argument. +** All subsequent calls only require the second argument to be set. If there is nothing left to be tokenized, a zero pointer +** will be returned. In contrast to strtok this routine will not alter the string to be tokenized since it +** operates on a local copy of this string. +*/ +char *tokenize(char *string, char *delimiters) +{ + static char local_copy[STRING_LENGTH], *position; + char *token; + + if (string) /* Initial call, create a copy of the string pointer */ + { + strcpy(local_copy, string); + position = local_copy; + } + else /* Subsequent call, scan local copy until a delimiter character will be found */ + { + while (*position && char_in(*position, delimiters)) /* Skip delimiters if there are any at the beginning of the string */ + position++; + + token = position; /* Now we are at the beginning of a token (or the end of the string :-) ) */ + while (*position) + { + position++; + if (!*position || char_in(*position, delimiters)) /* Delimiter found */ + { + if (*position) + *position++ = (char) 0; /* Split string copy */ + return token; + } + } + } + + return NULL; +} + +/* +** str2int converts a string in base 16 or base 10 notation to an unsigned integer value. +** Base 16 values require a prefix "0x" or "$" while base 10 value do not require any prefix. +*/ +unsigned int str2int(char *string) +{ + int value; + + if (!string || !*string) /* An empty string is treated as a zero */ + return 0; + + if (!strncmp(string, "0X", 2)) + sscanf(string + 2, "%x", &value); + else if (*string == '$') + sscanf(string + 1, "%x", &value); + else + sscanf(string, "%d", &value); + + return value; +} + +/* +** Does exactly what is expected. :-) +*/ +void chomp(char *string) +{ + if (string[strlen(string) - 1] == '\n') + string[strlen(string) - 1] = (char) 0; +} + +/* +** Return the content of a register addressed by its 4 bit register address. The routine takes care of the +** necessary bank switching logic. +*/ +unsigned int read_register(unsigned int address) +{ + address &= 0xf; + if (address & 0x8) /* Upper half -> always bank 0 */ + return gbl$registers[address] | (address == 0xe ? 1 : 0); /* The LSB of SR is always 1! */ + + return gbl$registers[address | ((read_register(14) >> 4) & 0xFF0)]; +} + +/* +** Change the contents of a register with provision for bank switching logic. +*/ +void write_register(unsigned int address, unsigned int value) +{ + address &= 0xf; + value &= 0xffff; + + if ((gbl$debug)) + printf("\twrite_register: address = %04X, value = %02X\n\r", address, value); + + if (address & 0x8) + gbl$registers[address] = value | (address == 14 ? 1 : 0); /* Ensure that LSB will always be set. */ + else /* Take bank switching into account! */ + gbl$registers[address | ((read_register(14) >> 4) & 0xFF0)] = value; +} + +/* +** The following function performs all memory access operations necessary for executing code in the +** emulator. Support routines like dump, etc. may access memory directly, but in this case be aware +** of the fact that no IO device emulation will take place! +** +*/ +unsigned int access_memory(unsigned int address, unsigned int operation, unsigned int value) +{ + address &= 0xffff; + if (gbl$gather_statistics) + gbl$stat.memory_accesses[operation]++; + + if (operation == READ_MEMORY) + { + if (address < IO_AREA_START) + value = gbl$memory[address]; + else /* IO area */ + { + /* TODO: Implement emulation of IO devices here! */ + value = 0; + if ((gbl$debug)) + printf("\tread_memory: IO-area access at 0x%04X: 0x%04X\n\r", address, value); + +#ifdef USE_UART + if (address >= UART0_BASE_ADDRESS && address < UART0_BASE_ADDRESS + 8 && gbl$enable_uart) /* Some UART0 operation */ + value = uart_read_register(&gbl$first_uart, address - UART0_BASE_ADDRESS); +#endif + + if (address >= IDE_BASE_ADDRESS && address < IDE_BASE_ADDRESS + 16) /* Some IDE operation */ + value = readIDEDeviceRegister(address - IDE_BASE_ADDRESS); + } + } + else if (operation == WRITE_MEMORY) + { + if (address < IO_AREA_START) + gbl$memory[address] = value; + else /* IO area */ + { + /* TODO: Implement IO-devices! */ + if ((gbl$debug)) + printf("\twrite_memory: IO-area access at 0x%04X: 0x%04X\n\r", address, value); + +#ifdef USE_UART + if (address >= UART0_BASE_ADDRESS && address < UART0_BASE_ADDRESS + 8 && gbl$enable_uart) /* Some UART0 operation */ + { + if ((gbl$debug)) + printf("\twrite uart register: %04X, %02X\n\t", address, value & 0xff); + uart_write_register(&gbl$first_uart, address - UART0_BASE_ADDRESS, value & 0xff); + } + else +#endif + + if (address >= IDE_BASE_ADDRESS && address < IDE_BASE_ADDRESS + 16) /* Some IDE operation */ + writeIDEDeviceRegister(address - IDE_BASE_ADDRESS, value); + } + } + else + { + printf("Illegal operation code in access_memory!\n"); + exit(-1); + } + + return value; +} + +/* +** reset the processor state, registers, memory. +*/ +void reset_machine() +{ + unsigned int i; + + /* Reset main memory and registers */ + for (i = 0; i < IO_AREA_START; access_memory(i++, WRITE_MEMORY, 0)); + for (i = 0; i < REGMEM_SIZE; gbl$registers[i++] = 0); + + /* Reset statistics counters */ + for (i = 0; i < NO_OF_INSTRUCTIONS; gbl$stat.instruction_frequency[i++] = 0); + for (i = 0; i < NO_OF_ADDRESSING_MODES; i++) + gbl$stat.addressing_modes[0][i] = gbl$stat.addressing_modes[1][i] = 0; + gbl$stat.memory_accesses[0] = gbl$stat.memory_accesses[1] = 0; + + if (gbl$debug || gbl$verbose) + printf("\treset_machine: done\n"); +} + +/* +** Decode an operand specified by a 6 bit mask. Returns TRUE if the next word will be a constant, so this can +** be skipped in the next disassemble step. +*/ +int decode_operand(unsigned int operand, char *string) +{ + int mode, regaddr; + + mode = operand & 0x3; + regaddr = (operand >> 2) & 0xf; + *string = (char) 0; + + if (!mode) + { + sprintf(string, "R%02d", regaddr); + return FALSE; + } + + if (mode == 1) /* @Rxx */ + sprintf(string, "@R%02d", regaddr); + else if (mode == 2) + { + sprintf(string, "@R%02d++", regaddr); + if (regaddr == 0xf) /* PC relative addressing */ + return TRUE; + } + else /* mode == 3 */ + sprintf(string, "@--R%02d", regaddr); + + return FALSE; +} + +/* +** Disassemble the contents of a memory region +*/ +void disassemble(unsigned int start, unsigned int stop) +{ + unsigned int i, opcode, instruction, j; + int skip_addresses; + char scratch[STRING_LENGTH], operands[STRING_LENGTH], mnemonic[STRING_LENGTH]; + + printf("Disassembled contents of memory locations %04x - %04x:\n", start, stop); + for (i = start, skip_addresses = 0; i <= stop || skip_addresses; i++) + { + opcode = (instruction = access_memory(i, READ_MEMORY, 0) & 0xffff) >> 12; + if (skip_addresses) /* Do not decode this machine word -- since it was used in @R15++! */ + { + skip_addresses--; + printf("%04X: %04X\n", i, instruction); + continue; + } + + *operands = (char) 0; + if (opcode < GENERIC_BRANCH_OPCODE) /* Normal instruction */ + { + if (opcode == 0xd) /* This one is reserved for future use! */ + { + strcpy(mnemonic, "RSRVD"); + *operands = (char) 0; + } + else + { + strcpy(mnemonic, gbl$normal_mnemonics[opcode]); + if (gbl$normal_operands[opcode]) /* At least one operand */ + { + if ((skip_addresses = decode_operand((instruction >> 6) & 0x3f, scratch))) /* Constant used! */ + sprintf(scratch, "0x%04X", access_memory(i + 1, READ_MEMORY, 0)); + strcpy(operands, scratch); + } + + if (gbl$normal_operands[opcode] == 2) /* Decode second operand */ + { + if ((j = decode_operand(instruction & 0x3f, scratch))) + sprintf(scratch, "0x%04X", access_memory(i + skip_addresses + j, READ_MEMORY, 0)); + skip_addresses += j; + strcat(operands, ", "); + strcat(operands, scratch); + } + } + } + else if (opcode == GENERIC_BRANCH_OPCODE) /* Branch or Subroutine call */ + { + strcpy(mnemonic, gbl$branch_mnemonics[(instruction >> 4) & 0x3]); + if ((skip_addresses = decode_operand((instruction >> 6) & 0x3f, scratch))) + sprintf(scratch, "0x%04X", access_memory(i + 1, READ_MEMORY, 0)); + sprintf(operands, "%s, %s%c", scratch, (instruction >> 3) & 1 ? "!" : "", gbl$sr_bits[instruction & 0x7]); + } + else + { + strcpy(mnemonic, "???"); + *operands = (char) 0; + } + + printf("%04X: %04X %-6s\t%s\n", i, instruction, mnemonic, operands); + } +} + +/* +** Read a source operand specified by mode and regaddr. If suppress_increment is set (all instructions with only +** one argument should do this!), the autoincrement will not be executed since this will be the task of +** the operand update step. If this is necessary, mode == 2 can be used as a condition for this. +** Predecrement will be executed always, postincrement only conditionally. +*/ +unsigned int read_source_operand(unsigned int mode, unsigned int regaddr, int suppress_increment) +{ + unsigned int source; + + if (gbl$debug) + printf("\tread_source_operand: mode=%01X, reg=%01X, skip_increment=%d\n\r", mode, regaddr, suppress_increment); + + switch (mode) /* Mode bits of source operand */ + { + case 0: /* Rxx */ + source = read_register(regaddr); + break; + case 1: /* @Rxx */ + source = access_memory(read_register(regaddr), READ_MEMORY, 0); + break; + case 2: /* @Rxx++ */ + source = access_memory(read_register(regaddr), READ_MEMORY, 0); + if (!suppress_increment) + write_register(regaddr, read_register(regaddr) + 1); + break; + case 3: /* @--Rxx */ + write_register(regaddr, read_register(regaddr) - 1); + source = access_memory(read_register(regaddr), READ_MEMORY, 0); + break; + default: + printf("Internal error, fetch operand!\n"); + exit(-1); + } + + if (gbl$gather_statistics) + gbl$stat.addressing_modes[0][mode]++; + + if (gbl$debug) + printf("\tread_source_operand: value=%04X, r15=%04X\n\r", source, read_register(15)); + return source; +} + +/* +** This is the counterpart function to read_source_operand. The major difference (apart from writing instead of reading :-) ) +** is that predecrements can be suppressed, autoincrements will be executed always. +*/ +void write_destination(unsigned int mode, unsigned int regaddr, unsigned int value, int suppress_decrement) +{ + if (gbl$debug) + printf("\twrite_operand: mode=%01X, reg=%01X, value=%04X, skip_increment=%d\n\r", mode, regaddr, value, suppress_decrement); + + value &= 0xffff; + switch (mode) + { + case 0: /* rxx */ + write_register(regaddr, value); + break; + case 1: /* @Rxx */ + access_memory(read_register(regaddr), WRITE_MEMORY, value); + break; + case 2: /* @Rxx++ */ + access_memory(read_register(regaddr), WRITE_MEMORY, value); + write_register(regaddr, read_register(regaddr) + 1); + break; + case 3: /* @--Rxx */ + if (!suppress_decrement) + write_register(regaddr, read_register(regaddr) - 1); + access_memory(read_register(regaddr), WRITE_MEMORY, value); + break; + default: + printf("Internal error, write operand!\n"); + exit(-1); + } + + if (gbl$gather_statistics) + gbl$stat.addressing_modes[1][mode]++; + + if (gbl$debug) + printf("\twrite_destination: r15=%04X\n\r", read_register(15)); +} + +/* +** The following function updates the lower six bits of the status register R14 according to +** the result of some machine instruction. Please keep in mind that the destination (result) +** parameter may occupy 17 bits (including the carry)! Do not truncate this parameter prior +** to calling this routine! +*/ +void update_status_bits(unsigned int destination, unsigned int source_0, unsigned int source_1, unsigned int control_bitmask) +{ + unsigned int sr_bits; + + sr_bits = 1; /* LSB is always set (for unconditional branches and subroutine calls) */ + if (((destination & 0xffff) == 0xffff) & !(control_bitmask & DO_NOT_MODIFY_X)) /* X */ + sr_bits |= 0x2; + if ((destination & 0x10000) && !(control_bitmask & DO_NOT_MODIFY_CARRY)) /* C */ + sr_bits |= 0x4; + if (!(destination & 0xffff)) /* Z */ + sr_bits |= 0x8; + if (destination & 0x8000) /* N */ + sr_bits |= 0x10; + if (((!(source_0 & 0x8000) && !(source_1 & 0x8000) && (destination & 0x8000)) || + ((source_0 & 0x8000) && (source_1 & 0x8000) && !(destination & 0x8000))) && !(control_bitmask & DO_NOT_MODIFY_OVERFLOW)) + sr_bits |= 0x20; + + write_register(14, (read_register(14) & 0xffc0) | (sr_bits & 0x3f)); +} + +/* +** The following function executes a single QNICE instruction. The return value will be TRUE if an illegal instruction is found. +*/ +int execute() +{ + unsigned int instruction, address, opcode, source_mode, source_regaddr, destination_mode, destination_regaddr, + source_0, source_1, destination, scratch, i, debug_address, temp_flag; + int condition; + + debug_address = address = read_register(15); /* Get current PC */ + opcode = ((instruction = access_memory(address++, READ_MEMORY, 0)) >> 12 & 0Xf); + write_register(15, address); /* Update program counter */ + + if (gbl$debug || gbl$verbose) + printf("execute: %04X %04X %s\n\r", debug_address, instruction, + opcode == GENERIC_BRANCH_OPCODE ? gbl$branch_mnemonics[(instruction >> 4) & 0x3] + : gbl$normal_mnemonics[opcode]); + + source_mode = (instruction >> 6) & 0x3; + source_regaddr = (instruction >> 8) & 0xf; + + destination_mode = instruction & 0x3; + destination_regaddr = (instruction >> 2) & 0xf; + + /* Update the statistics counters */ + if (opcode < GENERIC_BRANCH_OPCODE && gbl$gather_statistics) + gbl$stat.instruction_frequency[opcode]++; + else if (opcode == GENERIC_BRANCH_OPCODE && gbl$gather_statistics) + gbl$stat.instruction_frequency[opcode + ((instruction >> 4) & 0x3)]++; + + switch (opcode) + { + case 0: /* MOVE */ + destination = read_source_operand(source_mode, source_regaddr, FALSE); + update_status_bits(destination, destination, destination, DO_NOT_MODIFY_CARRY | DO_NOT_MODIFY_OVERFLOW); + write_destination(destination_mode, destination_regaddr, destination, FALSE); + break; + case 1: /* ADD */ + source_0 = read_source_operand(destination_mode, destination_regaddr, TRUE); + source_1 = read_source_operand(source_mode, source_regaddr, FALSE); + destination = source_0 + source_1; + update_status_bits(destination, source_0, source_1, MODIFY_ALL); + write_destination(destination_mode, destination_regaddr, destination, FALSE); + break; + case 2: /* ADDC */ + source_0 = read_source_operand(destination_mode, destination_regaddr, TRUE); + source_1 = read_source_operand(source_mode, source_regaddr, FALSE); + destination = source_0 + source_1 + ((read_register(14) >> 2) & 1); /* Take carry into account */ + update_status_bits(destination, source_0, source_1, MODIFY_ALL); + write_destination(destination_mode, destination_regaddr, destination, FALSE); + break; + case 3: /* SUB */ + source_0 = read_source_operand(destination_mode, destination_regaddr, TRUE); + source_1 = read_source_operand(source_mode, source_regaddr, FALSE); + destination = source_0 - source_1; + update_status_bits(destination, source_0, source_1, MODIFY_ALL); + write_destination(destination_mode, destination_regaddr, destination, FALSE); + break; + case 4: /* SUBC */ + source_0 = read_source_operand(destination_mode, destination_regaddr, TRUE); + source_1 = read_source_operand(source_mode, source_regaddr, FALSE); + destination = source_0 - source_1 - ((read_register(14) >> 2) & 1); /* Take carry into account */ + update_status_bits(destination, source_0, source_1, MODIFY_ALL); + write_destination(destination_mode, destination_regaddr, destination, FALSE); + break; + case 5: /* SHL */ + source_0 = read_source_operand(source_mode, source_regaddr, FALSE); + destination = read_source_operand(destination_mode, destination_regaddr, TRUE); + for (i = 0; i < source_0; i++) + { + temp_flag = (destination & 0x8000) >> 13; + destination = (destination << 1) | ((read_register(14) >> 1) & 1); /* Fill with X bit */ + } + write_register(14, (read_register(14) & 0xfffb) | temp_flag); /* Shift into C bit */ + write_destination(destination_mode, destination_regaddr, destination, FALSE); + break; + case 6: /* SHR */ + scratch = source_0 = read_source_operand(source_mode, source_regaddr, FALSE); + destination = read_source_operand(destination_mode, destination_regaddr, TRUE); + for (i = 0; i < source_0; i++) + { + temp_flag = (destination & 1) << 1; + destination = ((destination >> 1) & 0xffff) | ((read_register(14) & 4) << 13); /* Fill with C bit */ + } + write_register(14, (read_register(14) & 0xfffd) | temp_flag); /* Shift into X bit */ + write_destination(destination_mode, destination_regaddr, destination, FALSE); + break; + case 7: /* SWAP */ + source_0 = read_source_operand(source_mode, source_regaddr, FALSE); + destination = (source_0 >> 8) | ((source_0 << 8) & 0xff00); + update_status_bits(destination, source_0, source_0, DO_NOT_MODIFY_CARRY | DO_NOT_MODIFY_OVERFLOW); + write_destination(destination_mode, destination_regaddr, destination, FALSE); + break; + case 8: /* NOT */ + source_0 = read_source_operand(source_mode, source_regaddr, FALSE); + destination = ~source_0 & 0xffff; + update_status_bits(destination, source_0, source_0, DO_NOT_MODIFY_CARRY | DO_NOT_MODIFY_OVERFLOW); + write_destination(destination_mode, destination_regaddr, destination, FALSE); + break; + case 9: /* AND */ + source_0 = read_source_operand(destination_mode, destination_regaddr, TRUE); + source_1 = read_source_operand(source_mode, source_regaddr, FALSE); + destination = source_0 & source_1; + update_status_bits(destination, source_0, source_1, DO_NOT_MODIFY_CARRY | DO_NOT_MODIFY_OVERFLOW); + write_destination(destination_mode, destination_regaddr, destination, FALSE); + break; + case 10: /* OR */ + source_0 = read_source_operand(destination_mode, destination_regaddr, TRUE); + source_1 = read_source_operand(source_mode, source_regaddr, FALSE); + destination = source_0 | source_1; + update_status_bits(destination, source_0, source_1, DO_NOT_MODIFY_CARRY | DO_NOT_MODIFY_OVERFLOW); + write_destination(destination_mode, destination_regaddr, destination, FALSE); + break; + case 11: /* XOR */ + source_0 = read_source_operand(destination_mode, destination_regaddr, TRUE); + source_1 = read_source_operand(source_mode, source_regaddr, FALSE); + destination = source_0 ^ source_1; + update_status_bits(destination, source_0, source_1, DO_NOT_MODIFY_CARRY | DO_NOT_MODIFY_OVERFLOW); + write_destination(destination_mode, destination_regaddr, destination, FALSE); + break; + case 12: /* CMP */ + source_0 = read_source_operand(destination_mode, destination_regaddr, FALSE); + source_1 = read_source_operand(source_mode, source_regaddr, FALSE); + destination = source_0 - source_1; + update_status_bits(destination, source_0, source_1, MODIFY_ALL); + break; + case 13: /* This opcode intentionally left blank :-) */ + printf("Trying to execute opcode D -- this is reserved for future use!\n"); + return TRUE; + case 14: /* HALT */ + return TRUE; + case 15: /* Branch or subroutine call */ + /* Determine destination address in case the branch/subroutine instruction will be performed */ + destination = read_source_operand(source_mode, source_regaddr, FALSE); /* Perform autoincrement since no write back occurs! */ + + /* Determine which SR bit to use, etc. */ + condition = (read_register(14) >> (instruction & 0x7)) & 1; + if (instruction & 0x0008) /* Invert bit to be checked? */ + condition = 1 - condition; + + /* Now it is time to determine which branch resp. subroutine call type to execute if the condition is satisfied */ + if (condition) + { + switch((instruction >> 4) & 0x3) + { + case 0: /* ABRA */ + write_register(15, destination); + break; + case 1: /* ASUB */ + write_register(13, read_register(13) - 1); + access_memory(read_register(13), WRITE_MEMORY, read_register(15)); + write_register(15, destination); + break; + case 2: /* RBRA */ + write_register(15, (read_register(15) + destination) & 0xffff); + break; + case 3: /* RSUB */ + write_register(13, read_register(13) - 1); + access_memory(read_register(13), WRITE_MEMORY, read_register(15)); + write_register(15, (read_register(15) + destination) & 0xffff); + break; + } + } + /* We must increment the PC in case of a constant destination address even if the branch is not taken! */ +// NO, we must not since the PC has already been incremented during the fetch operation! +// else if (source_mode == 0x2 && source_regaddr == 0xf) /* This is mode @R15++ */ +// write_register(15, read_register(15) + 1); + + break; + default: + printf("PANIK: Illegal instruction found: Opcode %0X at address %04X.\n", opcode, address); + return TRUE; + } + +/* write_register(15, read_register(15) + 1); */ /* Update program counter */ + return FALSE; /* No HALT instruction executed */ +} + +void run() +{ + gbl$ctrl_c = FALSE; +#ifdef USE_UART + if (gbl$enable_uart) + uart_hardware_initialization(&gbl$first_uart); +#endif + gbl$gather_statistics = TRUE; + while (!execute() && !gbl$ctrl_c); + if (gbl$ctrl_c) + printf("\n\tAborted by CTRL-C!\n"); + gbl$gather_statistics = FALSE; +#ifdef USE_UART + if (gbl$enable_uart) + uart_run_down(); +#endif +} + +void print_statistics() +{ + unsigned int i, value; + + for (i = value = 0; i < NO_OF_INSTRUCTIONS; value += gbl$stat.instruction_frequency[i++]); + if (!value) + printf("No statistics have been gathered so far!\n"); + else + { + printf("\n%d memory reads, %d memory writes and\n%d instructions have been executed so far:\n\n\ +INSTR ABSOLUTE RELATIVE INSTR ABSOLUTE RELATIVE\n\ +-----------------------------------------------\n", + gbl$stat.memory_accesses[READ_MEMORY], gbl$stat.memory_accesses[WRITE_MEMORY], value); + for (i = 0; i < NO_OF_INSTRUCTIONS; i++) + printf("%s%-4s: %8d (%5.2f%%)\t", + !(i & 1) && i ? "\n" : "", /* New line every second round */ + i < GENERIC_BRANCH_OPCODE ? gbl$normal_mnemonics[i] + : gbl$branch_mnemonics[i - GENERIC_BRANCH_OPCODE], + gbl$stat.instruction_frequency[i], + (float) (100 * gbl$stat.instruction_frequency[i]) / (float) value); + + for (i = value = 0; i < NO_OF_ADDRESSING_MODES; i++) + value += gbl$stat.addressing_modes[0][i] + gbl$stat.addressing_modes[1][i]; + if (!value) + printf("\n\nThere have not been any memory references so far!\n"); + else + { + printf("\n\n READ ACCESSES WRITE ACCESSES\n\ +MODE ABSOLUTE RELATIVE MODE ABSOLUTE RELATIVE\n\ +-----------------------------------------------------------\n"); + for (i = 0; i < NO_OF_ADDRESSING_MODES; i++) + printf("%-5s: %8d (%5.2f%%) \t%-5s: %8d (%5.2f%%)\n", + gbl$addressing_mnemonics[i], gbl$stat.addressing_modes[0][i], + (float) (100 * gbl$stat.addressing_modes[0][i]) / (float) value, + gbl$addressing_mnemonics[i], gbl$stat.addressing_modes[1][i], + (float) (100 * gbl$stat.addressing_modes[1][i]) / (float) value); + } + printf("\n"); + } +} + +int load_binary_file(char *file_name) +{ + unsigned int address; + char scratch[STRING_LENGTH], *token; + FILE *handle; + + if (!(handle = fopen(file_name, "r"))) + { + printf("Unable to open file >>%s<<\n", file_name); + return -1; + } + else + { + fgets(scratch, STRING_LENGTH, handle); + upstr(scratch); + chomp(scratch); + while(!feof(handle)) + { + tokenize(scratch, NULL); + if (!(token = tokenize(NULL, " "))) + break; + address = str2int(token); + if (address >= MEMORY_SIZE) + { + printf("Address out of range in load file: >>%s<<\n", scratch); + return -1; + } + + if (!(token = tokenize(NULL, " "))) + { + printf("Illegal line in load file! Line: >>%s<<\n", scratch); + return -1; + } + access_memory(address, WRITE_MEMORY, str2int(token)); + + fgets(scratch, STRING_LENGTH, handle); + upstr(scratch); + chomp(scratch); + } + fclose(handle); + } + + return 0; +} + +void dump_registers() +{ + unsigned int i, value; + + printf("Register dump: BANK = %02x, SR = ", read_register(14) >> 8); + for (i = 7, value = read_register(14); i + 1; i--) + printf("%c", value & (1 << i) ? gbl$sr_bits[i] : '_'); + + printf("\n"); + for (i = 0; i < 0x10; i++) + { + if (!(i % 4)) /* New row */ + printf("\n\tR%02d-R%02d: ", i, i + 3); + + printf("%04x ", read_register(i)); + } + printf("\n\n"); +} + +int main(int argc, char **argv) +{ + char command[STRING_LENGTH], *token, *delimiters = " ,", scratch[STRING_LENGTH]; + unsigned int start, stop, i, address, value, last_command_was_step = 0; + FILE *handle; + + signal(SIGINT, signal_handler_ctrl_c); + reset_machine(); + initializeIDEDevice(); + + if (argc > 1) + { + if (!strcmp(argv[1], "-h")) + printf("\nUsage:\n\ + \"qnice\" without arguments will start an interactive session\n\ + \"qnice -h\" will print this help text\n\ + \"qnice \" will run in batch mode and print statistics\n\n"); + else /* Assume that the first argument is a file name */ + { + if (load_binary_file(argv[1])) + return -1; + + run(); + dump_registers(); + print_statistics(); + } + + return 0; + } + + for (;;) + { + printf("Q> "); + fgets(command, STRING_LENGTH, stdin); + chomp(command); + if (feof(stdin)) + return 0; + + if (last_command_was_step && !strlen(command)) /* If STEP was the last command and this is empty, perform the next step. */ + strcpy(command, "STEP"); + + upstr(command); + + last_command_was_step = 0; + tokenize(command, NULL); /* Initialize tokenizing */ + if ((token = tokenize(NULL, delimiters))) + { + if (!strcmp(token, "QUIT") || !strcmp(token, "EXIT")) + return 0; + else if (!strcmp(token, "DUMP")) + { + start = str2int(tokenize(NULL, delimiters)); + stop = str2int(tokenize(NULL, delimiters)); + for (i = start; i <= stop; i++) + { + if (!((i - start) % 8)) /* New row */ + printf("\n%04x: ", i); + + printf("%04x ", access_memory(i, READ_MEMORY, 0)); + } + printf("\n"); + } + else if (!strcmp(token, "SAVE")) /* Create a loadable binary file with data from memory */ + { + if (!(token = tokenize(NULL, delimiters))) + printf("SAVE expects at least a filename as its 1st parameter!\n"); + else + { + if (!(handle = fopen(token, "w"))) + printf("Unable to create file >>%s<<\n", token); + else + { + start = str2int(tokenize(NULL, delimiters)); + stop = str2int(tokenize(NULL, delimiters)); + for (i = start; i <= stop; i++) + fprintf(handle, "0x%04X 0x%04X\n", i, access_memory(i, READ_MEMORY, 0)); + + fclose(handle); + } + } + } + else if (!strcmp(token, "LOAD")) /* Load expects a file with a row format like " \n", etc. */ + { + if (!(token = tokenize(NULL, delimiters))) + printf("LOAD expects a filename as its 1st parameter!\n"); + else + load_binary_file(token); + } + else if (!strcmp(token, "RDUMP")) + dump_registers(); + else if (!strcmp(token, "SET")) + { + token = tokenize(NULL, delimiters); + value = str2int(tokenize(NULL, delimiters)); + if (*token == 'R') /* Set a register */ + write_register(str2int(token + 1), value); + else + access_memory(str2int(token), WRITE_MEMORY, value & 0xffff); + } + else if (!strcmp(token, "RESET")) + reset_machine(); + else if (!strcmp(token, "DEBUG")) + { + if ((gbl$debug = TRUE - gbl$debug)) + printf("New mode: verbose\n"); + else + printf("New mode: quiet\n"); + } + else if (!strcmp(token, "VERBOSE")) + { + if ((gbl$verbose = TRUE - gbl$verbose)) + printf("New mode: verbose\n"); + } + else if (!strcmp(token, "UART")) + { + gbl$enable_uart = TRUE - gbl$enable_uart; + printf("New UART-mode: %sabled\n", gbl$enable_uart ? "en" : "dis"); + } + else if (!strcmp(token, "DIS")) + { + start = str2int(tokenize(NULL, delimiters)); + disassemble(start, str2int(tokenize(NULL, delimiters))); + } + else if (!strcmp(token, "STAT")) + print_statistics(); + else if (!strcmp(token, "STEP")) + { + last_command_was_step = 1; + if ((token = tokenize(NULL, delimiters))) + write_register(15, str2int(token)); + execute(); + } + else if (!strcmp(token, "RUN")) + { + if ((token = tokenize(NULL, delimiters))) + write_register(15, str2int(token)); + run(); + } + else if (!strcmp(token, "HELP")) + printf("\n\ +DEBUG Toggle debug mode (for development only)\n\ +DIS , Disassemble a memory region\n\ +DUMP , Dump a memory area, START and STOP can be\n\ + hexadecimal or plain decimal\n\ +LOAD Loads a binary file into main memory\n\ +QUIT/EXIT Stop the emulator and return to the shell\n\ +RESET Reset the whole machine\n\ +RDUMP Print a register dump\n\ +RUN [] Run a program beginning at ADDR\n\ +SET Either set a register of a memory cell\n\ +SAVE Create a loadable binary file\n\ +STAT Displays some execution statistics\n\ +STEP [] Executes a single instruction at address\n\ + ADDR. If not address is specified the current\n\ + program counter will be used instead.\n\ + If the last command was step, an empty command\n\ + string will perform the next step!\n\ +UART Toggle UART disable/enable\n\ +VERBOSE Toggle verbosity mode\n\ +"); + else + printf("main: Unknown command >>%s<<\n", token); + } + } +} diff --git a/emulator/uart_2681.c b/emulator/uart_2681.c new file mode 100644 index 00000000..8faafb2d --- /dev/null +++ b/emulator/uart_2681.c @@ -0,0 +1,236 @@ +/* +** This module implements the emulation of a 2681 UART which will be eventually used in the actual hardware implementation +** of QNICE. +** +** 02-JUN-2008, B. Ulmann fecit. +** 03-AUG-2015, B. Ulmann Changed from curses to select-calls. +*/ + +#undef TEST /* Define to perform stand alone test */ +#define VERBOSE +#define DEBUG + +#include "uart_2681.h" +#include + +#include +#include +#include + +/* Ugly global variable to hold the original tty state in order to restore it during rundown */ +struct termios tty_state_old, tty_state; + +unsigned int uart_read_register(uart_2681 *state, unsigned int address) +{ + unsigned int value; + char last_character; + fd_set fd; + struct timeval tv; + int ret_val; + + /* Initialize data structures for following select calls */ + FD_ZERO(&fd); + FD_SET(STDIN_FILENO, &fd); + tv.tv_sec = 0; + tv.tv_usec = 1000; /* Wait 1 ms */ + + switch (address) + { + case MR1A: + /* case MR2A: */ + value = state->mr1a; + break; + case SRA: + /* Check if there is a character in the input buffer */ + if ((ret_val = select(1, &fd, NULL, NULL, &tv)) == -1) + { + /* Don't stop here as it might be caused by a catched CTRL-C signal! */ + } + else if (!ret_val) /* No data available */ + state->sra &= 0xfe; + else /* Data available */ + state->sra |= 1; + + value = state->sra; + break; + case BRG_TEST: + value = state->brg_test; + break; + case RHRA: + if ((ret_val = select(1, &fd, NULL, NULL, &tv)) == -1) + { + /* Don't stop here as it might be caused by a catched CTRL-C signal! */ + } + else if (!ret_val) /* No data available */ + state->rhra = 0; + else /* Data available */ + state->rhra = getchar() & 0xff; + + value = state->rhra; + break; + case IPCR: + value = state->ipcr; + break; + case ISR: + value = state->isr; + break; + case CTU: + value = state->ctu; + break; + case CTL: + value = state->ctl; + break; + case MR1B: + /* case MR2B: */ + value = state->mr1b; + break; + case SRB: + value = state->srb; + break; + case X_X_TEST: + value = state->x_x_test; + break; + case RHRB: + value = state->rhrb; + break; + case INPUT_PORTS: + value = state->input_ports; + break; + case START_COUNTER: + value = state->start_counter; + break; + case STOP_COUNTER: + value = state->stop_counter; + break; + default: +#ifdef VERBOSE + printf("uart_read_register: attempt to read illegal register %d!\n", address); +#endif + value = 0xff; + } + + return value & 0xff; +} + +void uart_write_register(uart_2681 *state, unsigned int address, unsigned int value) +{ + value &= 0xff; + switch (address) + { + case MR1A: + /* case MR1B: */ + state->mr1a = value; + break; + case CSRA: + state->csra = value; + break; + case CRA: + state->cra = value; + break; + case THRA: + state->thra = value; + putchar((int) value); + fflush(stdout); + break; + case ACR: + state->acr = value; + break; + case IMR: + state->imr = value; + break; + case CRUR: + state->crur = value; + break; + case CTLR: + state->ctlr = value; + break; + case CSRB: + state->csrb = value; + break; + case CRB: + state->crb = value; + break; + case THRB: + state->thrb = value; + break; + case OPCR: + state->opcr = value; + break; + case SET_OUTPUT_PORT: + state->set_output_port = value; + break; + case RESET_OUTPUT_PORT: + state->reset_output_port = value; + break; + default: +#ifdef VERBOSE + printf("uart_write_register: attempt to write into illegal register %d!", address); +#endif + } +} + +void uart_hardware_initialization(uart_2681 *state) +{ + /* Turn off buffering on STDIN and save original state for later */ + tcgetattr(STDIN_FILENO, &tty_state_old); + tty_state = tty_state_old; + tty_state.c_lflag &= ~ICANON; + tty_state.c_lflag &= ~ECHO; + tcsetattr(STDIN_FILENO, TCSANOW, &tty_state); + + /* + ** bit 1, 0: 11 -> 8 bits/character + ** bit 2 : 0 -> even parity + ** bit 4, 3: 10 -> no parity + ** bit 5 : 0 -> error mode char + ** bit 6 : 0 -> RxRDY + ** bit 7 : 0 -> no RxRTS control + */ + state->mr1a = state->mr1b = 0x13; + + state->sra = state->srb = state->brg_test = state->rhra = state->ipcr = state->isr = state->ctu = state->ctl = + state->x_x_test = state->rhrb = state->input_ports = state->start_counter = state->stop_counter = + state->csra = state->cra = state->thra = state->acr = state->imr = state->crur = state->ctlr = state->csrb = state->crb = + state->thrb = state->opcr = state->set_output_port = state->reset_output_port = (unsigned int) 0; +} + +void uart_run_down() +{ + /* Reset the terminal to its original settings */ + tcsetattr(STDIN_FILENO, TCSANOW, &tty_state_old); +} + +/* +** The main function serves test purposes only and will be invisible under normal circumstances. +*/ + +#ifdef TEST +int main() +{ + uart_2681 my_uart; + unsigned int sra, rhra = 0, i; + + /* Initialize UART (hardware reset) */ + uart_hardware_initialization(&my_uart); + + /* First simple test: Write the characters A to Z followed by CR/LF/LF to the first emulated serial line */ + for (i = 'A'; i <= 'Z'; i++) + uart_write_register(&my_uart, THRA, i); + uart_write_register(&my_uart, THRA, 10); + uart_write_register(&my_uart, THRA, 13); + + /* Second simple test: Read characters from the first simulated serial line and echo them, 'q' will quit */ + for (; rhra != 'q';) + { + do + { + sra = uart_read_register(&my_uart, SRA); + } while (!(sra & 1)); + rhra = uart_read_register(&my_uart, RHRA); + uart_write_register(&my_uart, THRA, rhra); + } + + uart_run_down(); + return 0; +} +#endif diff --git a/emulator/uart_2681.h b/emulator/uart_2681.h new file mode 100644 index 00000000..9a81bd06 --- /dev/null +++ b/emulator/uart_2681.h @@ -0,0 +1,48 @@ +/* +** This is the header file for the 2681 UART-emulation for the QNICE-emulator. +** +** 02-JUN-2008, B. Ulmann fecit +*/ + +/* This structure contains all register data of the emulated UART */ +typedef struct uart_2681 +{ + unsigned int mr1a, sra, brg_test, rhra, ipcr, isr, ctu, ctl, mr1b, srb, + x_x_test, rhrb, input_ports, start_counter, stop_counter, + csra, cra, thra, acr, imr, crur, ctlr, csrb, crb, thrb, opcr, + set_output_port, reset_output_port; +} uart_2681; + +/* The following registers can be read from and partially written to the UART */ +#define MR1A 0 /* Read/write */ +#define MR2A 0 /* Read/write */ +#define SRA 1 +#define BRG_TEST 2 +#define RHRA 3 +#define IPCR 4 +#define ISR 5 +#define CTU 6 +#define CTL 7 +#define MR1B 8 /* Read/write */ +#define MR2B 8 /* Read/write */ +#define SRB 9 +#define X_X_TEST 10 +#define RHRB 11 +#define INPUT_PORTS 13 +#define START_COUNTER 14 +#define STOP_COUNTER 15 + +/* The following registers can be written to */ +#define CSRA 1 +#define CRA 2 +#define THRA 3 +#define ACR 4 +#define IMR 5 +#define CRUR 6 +#define CTLR 7 +#define CSRB 9 +#define CRB 10 +#define THRB 11 +#define OPCR 13 +#define SET_OUTPUT_PORT 14 +#define RESET_OUTPUT_PORT 15 diff --git a/monitor/io_library.asm b/monitor/io_library.asm new file mode 100644 index 00000000..b6e0dbcb --- /dev/null +++ b/monitor/io_library.asm @@ -0,0 +1,216 @@ +; +;;======================================================================================= +;; The collection of input/output related function starts here +;;======================================================================================= +; +; Define io system specific constants and memory areas etc. It is expected that the +; basic definitions from sysdef.asm have been included somewhere before! +; +;*************************************************************************************** +;* IO$DUMP_MEMORY prints a hexadecimal memory dump of a specified memory region. +;* +;* R8: Contains the start address +;* R9: Contains the end address (inclusive) +;* +;* The contents of R8 and R9 are preserved during the run of this function. +;*************************************************************************************** +; +IO$DUMP_MEMORY INCRB ; Get a new register page + MOVE R8, R0 ; R0 will be the loop counter + MOVE R8, R1 ; This will be needed to restore R8 later + MOVE R9, R3 + ADD 0x0001, R3 ; That is necessary since we want the last + ; address printed, too + MOVE 0xFFFF, R4 ; Set R4 - this is the column counter - to -1 +_IO$DUMP_MEMORY_LOOP MOVE R0, R2 ; Have we reached the end of the memory area? + SUB R3, R2 + RBRA _IO$DUMP_MEMORY_EXIT, Z ; Yes - that is it, so exit this routine + ADD 0x0001, R4 ; Next column + AND 0x0007, R4 ; We compute mod 8 + RBRA _IO$DUMP_MEMORY_CONTENT, !Z; if the result is not equal 0 we do not + ; need an address printed + RSUB IO$PUT_CRLF, 1 ; Print a CR/LF pair + MOVE R0, R8 ; Print address + RSUB IO$PUT_W_HEX, 1 + MOVE IO$COLON_DELIMITER, R8 ; Print a colon followed by a space + RSUB IO$PUTS, 1 +_IO$DUMP_MEMORY_CONTENT MOVE @R0++, R8 ; Print the memory contents of this location + RSUB IO$PUT_W_HEX, 1 + MOVE ' ', R8 ; Print a space + RSUB IO$PUTCHAR, 1 + RBRA _IO$DUMP_MEMORY_LOOP, 1 ; Continue the print loop +_IO$DUMP_MEMORY_EXIT RSUB IO$PUT_CRLF, 1 ; Print a last CR/LF pair + MOVE R1, R8 ; Restore R8, + DECRB ; switch back to the correct register page + RET +; +;*************************************************************************************** +;* IO$GET_W_HEX reads four hex nibbles from stdin and returns the corresponding +;* value in R8 +;* +;* Illegal characters (not 1-9A-F or a-f) will generate a bell signal. The only +;* exception to this behaviour is the character 'x' which will erase any input +;* up to this point. This has the positive effect that a hexadecimal value can +;* be entered as 0x.... or just as .... +;*************************************************************************************** +; +IO$GET_W_HEX INCRB ; Get a new register page +_IO$GET_W_HEX_REDO XOR R0, R0 ; Clear R0 + MOVE 4, R1 ; We need four characters + MOVE IO$HEX_NIBBLES, R9 ; Pointer to list of valid chars +_IO$GET_W_HEX_INPUT RSUB IO$GETCHAR, 1 ; Read a character into R8 + RBRA QMON$WARMSTART, Z + RSUB CHR$TO_UPPER, 1 ; Convert to upper case + CMP 'X', R8 ; Was it an 'X'? + RBRA _IO$GET_W_HEX_REDO, Z ; Yes - redo from start :-) + RSUB STR$STRCHR, 1 ; Is it a valid character? + MOVE R10, R10 ; Result equal zero? + RBRA _IO$GET_W_HEX_VALID, !Z ; No + MOVE 7, R8 ; Yes - generate a beep :-) + RSUB IO$PUTCHAR, 1 + RBRA _IO$GET_W_HEX_INPUT, 1 ; Retry +_IO$GET_W_HEX_VALID RSUB IO$PUTCHAR, 1 ; Echo character + SUB IO$HEX_NIBBLES, R10 ; Get number of character + SHL 4, R0 + ADD R10, R0 + SUB 0x0001, R1 + RBRA _IO$GET_W_HEX_INPUT, !Z ; Read next character + MOVE R0, R8 + DECRB ; Restore previous register page + RET +; +;*************************************************************************************** +;* IO$PUT_W_HEX prints a machine word in hexadecimal notation. +;* +;* R8: Contains the machine word to be printed in hex notation. +;* +;* The contents of R8 are being preserved during the run of this function. +;*************************************************************************************** +; +IO$PUT_W_HEX INCRB ; Get a new register page + MOVE 0x0004, R0 ; Save constant for nibble shifting + MOVE R0, R4 ; Set loop counter to four + MOVE R8, R5 ; Copy contents of R8 for later restore + MOVE IO$HEX_NIBBLES, R1 ; Create a pointer to the list of nibbles + ; Push four ASCII characters to the stack +_IO$PWH_SCAN MOVE R1, R2 ; and create a scratch copy of this pointer + MOVE R8, R3 ; Create a local copy of the machine word + AND 0x000f, R3 ; Only the four LSBs are of interest + ADD R3, R2 ; Adjust pointer to the desired nibble + MOVE @R2, @--SP ; and save the ASCII character to the stack + SHR 4, R8 ; Shift R8 four places right + SUB 0x0001, R4 ; Decrement loop counter + RBRA _IO$PWH_SCAN, !Z ; and continue with the next nibble + ; Now read these characters back and print them + MOVE R0, R4 ; Initialize loop counter +_IO$PWH_PRINT MOVE @SP++, R8 ; Fetch a character from the stack + RSUB IO$PUTCHAR, 1 ; and print it + SUB 0x0001, R4 ; Decrement loop counter + RBRA _IO$PWH_PRINT, !Z ; and continue with the next character + ; That is all... + MOVE R5, R8 ; Restore contents of R8 + DECRB ; Restore correct register page + RET +; +;*************************************************************************************** +;* IO$GETCHAR reads a character from the first UART in the system. +;* +;* R8 will contain the character read in its lower eight bits +;*************************************************************************************** +; +IO$GETCHAR INCRB + MOVE IO$UART0_BASE, R0 + MOVE R0, R1 + ADD IO$UART_SRA, R0 ; R0 contains the address of the status register + ADD IO$UART_RHRA, R1 ; R1 contains the address of the receiver reg. +_IO$GETC_LOOP MOVE @R0, R2 ; Read status register + AND 0x0001, R2 ; Only bit 0 is of interest + RBRA _IO$GETC_LOOP, Z ; Loop until a character has been received + MOVE @R1, R8 ; Get the character from the receiver register + DECRB + CMP 0x0005, R8 ; CTRL-E? + RBRA QMON$WARMSTART, Z + RET +; +;*************************************************************************************** +;* IO$GETS reads a CR/LF terminated string from the serial line +;* +;* R8 has to point to a preallocated memory area to store the input line +;*************************************************************************************** +; +IO$GETS INCRB ; Get a new register page + MOVE R8, R0 ; Save parameters + MOVE R8, R1 +_IO$GETS_LOOP RSUB IO$GETCHAR, 1 ; Get a single character from the serial line + MOVE R8, @R0++ ; Store it into the buffer area + RSUB IO$PUTCHAR, 1 ; Echo the character + SUB 0x000A, R8 ; Was it a LF character? + RBRA _IO$GETS_LOOP, !Z ; No -> continue reading characters + MOVE 0x000D, @R0++ ; Extend the string with a CR and + MOVE 0x0000, @R0 ; terminate it with a null word + MOVE R1, R8 ; Restore R8 which will now point to the string + DECRB ; Restore the register page + RET +; +;*************************************************************************************** +;* IO$PUTS prints a null terminated string. +;* +;* R8: Pointer to the string to be printed. Of each word only the lower eight bits +;* will be printed. The terminating word has to be zero. +;* +;* The contents of R8 are being preserved during the run of this function. +;*************************************************************************************** +; +IO$PUTS INCRB ; Get a new register page + MOVE R8, R1 ; Save contents of R8 + MOVE R8, R0 ; Local copy of the string pointer +_IO$PUTS_LOOP MOVE @R0++, R8 ; Get a character from the string + AND 0x00FF, R8 ; Only the lower eight bits are relevant + RBRA _IO$PUTS_END, Z ; Return when the string end has been reached + RSUB IO$PUTCHAR, 1 ; Print this character + RBRA _IO$PUTS_LOOP, 1 ; Continue with the next character +_IO$PUTS_END MOVE R1, R8 ; Restore contents of R8 + DECRB ; Restore correct register page + RET +; +;*************************************************************************************** +;* IO$PUT_CRLF prints actually a LF/CR (the reason for this is that curses on the +;* MAC, where the emulation currently runs, has problems with CR/LF, but +;* not with LF/CR) +;*************************************************************************************** +; +IO$PUT_CRLF INCRB ; Get a new register page + MOVE R8, R0 ; Save contents of R8 + MOVE 0x0A, R8 + RSUB IO$PUTCHAR, 1 + MOVE 0x0D, R8 + RSUB IO$PUTCHAR, 1 + MOVE R0, R8 ; Restore contents of R8 + DECRB ; Return to previous register page + RET +; +;*************************************************************************************** +;* IO$PUTCHAR prints a single character. +;* +;* R8: Contains the character to be printed +; +;* The contents of R8 are being preserved during the run of this function. +;* +;* TODO: This routine is way too simple and only works with the simple +;* UART emulation. To use a real 16550 this routine will require a complete +;* rewrite! +;*************************************************************************************** +; +IO$PUTCHAR INCRB ; Get a new register page + MOVE IO$UART0_BASE, R0 + ADD IO$UART_THRA, R0 ; R0 now points to the THRA register + MOVE R8, @R0 ; Print character + DECRB ; Restore the old page + RET +; +;*************************************************************************************** +; Constants, etc. +;*************************************************************************************** +; +IO$HEX_NIBBLES .ASCII_W "0123456789ABCDEF" +IO$COLON_DELIMITER .ASCII_W ": " diff --git a/monitor/math_library.asm b/monitor/math_library.asm new file mode 100644 index 00000000..d98e2188 --- /dev/null +++ b/monitor/math_library.asm @@ -0,0 +1,50 @@ +; +;;============================================================================= +;; The collection of math related functions starts here +;;============================================================================= +; +;****************************************************************************** +;* MTH$MUL performs a highly unelegant signed 16 x 16 multiplication of the +;* form R11(H)/R10(L) = R8 * R9. +;* +;* (R2 = XL, R3 = XH) = (R8 = |A|), initally +;* R4 = |B| +;* R10 = RL, R11 = RH (result) +;* R5 = counter +;****************************************************************************** +MTH$MUL INCRB + XOR R0, R0 ; Clear negative flags + XOR R1, R1 + XOR R3, R3 ; Clear XH + MOVE R8, R2 ; Negative A? + RBRA _MTH$MUL_A_POS, !N ; No + XOR R2, R2 ; A is negative + SUB R8, R2 ; R2 = |R8| + MOVE 1, R0 ; Remember that A was negative +_MTH$MUL_A_POS MOVE R9, R4 ; Negative B? + RBRA _MTH$MUL_B_POS, !N ; No + XOR R4, R4 ; B is negative + SUB R9, R4 ; R4 = |R9| + MOVE 1, R1 ; Remember that B was negative +_MTH$MUL_B_POS XOR R10, R10 ; Clear the two result registers + XOR R11, R11 + MOVE 0x0010, R5 ; Initialize counter to 16 +_MTH$MUL_LOOP SHR 1, R4 ; Determine LSB of B + RBRA _MTH$MUL_LAST, !X ; Nothing to add, LSB was 0 + ADD R2, R10 ; Add to low result word + ADDC R3, R11 ; Add to high result word + C +_MTH$MUL_LAST SHL 1, R2 ; Shift XL/XH one bit left + RBRA _MTH$MUL_ZERO, !C ; No carry shifted out + OR 0x0002, SR ; There was a carry, set X bit +_MTH$MUL_ZERO SHL 1, R3 ; No shift XH one to the left + SUB 1, R5 ; Decrement counter + RBRA _MTH$MUL_LOOP, !Z ; Still not done? + CMP R0, R1 ; Are the negative flags equal? + RBRA _MTH$MUL_EXIT, Z ; Yes, nothing further to do + NOT R10, R10 ; 2s-complement of the result + NOT R11, R11 + ADD 1, R10 + ADDC 0, R11 +_MTH$MUL_EXIT DECRB + RET + diff --git a/monitor/mem_library.asm b/monitor/mem_library.asm new file mode 100644 index 00000000..13201bfb --- /dev/null +++ b/monitor/mem_library.asm @@ -0,0 +1,38 @@ +; +;;============================================================================= +;; The collection of memory related functions starts here +;;============================================================================= +; +;****************************************************************************** +;* MEM$FILL fills a block of memory running from the address stored in R8. +;* R9 contains the number of words to be written. R10 contains the value to +;* be stored in the memory area. +;****************************************************************************** +MEM$FILL INCRB + MOVE R8, R0 + MOVE R9, R1 +_MEM$FILL_LOOP MOVE R1, R1 ; Zero length left? + RBRA _MEM$FILL_EXIT, Z ; Yes, done... + MOVE R10, @R0++ + SUB 0x0001, R1 + RBRA _MEM$FILL_LOOP, 1 +_MEM$FILL_EXIT DECRB + RET +; +;****************************************************************************** +;* MEM$MOVE moves the memory area starting at the address contained in R8 +;* to the area starting at the address contained in R9. R10 contains the +;* number of words to be moved. +;****************************************************************************** +; +MEM$MOVE INCRB + MOVE R8, R0 + MOVE R9, R1 + MOVE R10, R2 +_MEM$MOVE_LOOP MOVE R2, R2 ; Zero length left? + RBRA _MEM$MOVE_EXIT, Z ; Yes, done... + MOVE @R0++, @R1++ + SUB 0x0001, R2 + RBRA _MEM$MOVE_LOOP, 1 +_MEM$MOVE_EXIT DECRB + RET diff --git a/monitor/monitor.asm b/monitor/monitor.asm new file mode 100644 index 00000000..12e539d0 --- /dev/null +++ b/monitor/monitor.asm @@ -0,0 +1,8 @@ +#include "sysdef.asm" +#include "qmon.asm" +#include "io_library.asm" +#include "string_library.asm" +#include "mem_library.asm" +;; +QMON$LAST_ADDR HALT + diff --git a/monitor/monitor.lis b/monitor/monitor.lis new file mode 100644 index 00000000..ba67c6c7 --- /dev/null +++ b/monitor/monitor.lis @@ -0,0 +1,1796 @@ +000001 +000002 ; +000003 ; This file contains the necessary definitions for the simple QNICE-monitor. +000004 ; +000005 +000006 ; +000007 ; Some assembler macros which make life much easier: +000008 ; +000009 +000010 +000011 +000012 +000013 ; +000014 ; Some register short names: +000015 ; +000016 +000017 +000018 +000019 +000020 ; +000021 ; IO-page addresses: +000022 ; +000023 IO$BASE .EQU 0xFC00 +000024 IO$UART0_BASE .EQU 0xFC00 +000025 +000026 ; +000027 ; UART-registers: +000028 ; +000029 IO$UART_SRA .EQU 0x0001 ; Status register (relative to base address) +000030 IO$UART_RHRA .EQU 0x0003 ; Receiving register (relative to base address) +000031 IO$UART_THRA .EQU 0x0003 ; Transmitting register (relative to base address) +000032 +000033 +000034 ;; +000035 ;; QMON - a simple monitor for the QNICE processor +000036 ;; +000037 ;; The labels and constants of each subsystem are prefixed with a short name denoting the particular +000038 ;; subsystem, followed by a dollar sign. Examples for this are IO$BASE or STR$STRMP etc. Labels +000039 ;; within a routine follow this prefix style but have an additional underscore following the dollar +000040 ;; sign to denote that these labels should normally not be the target of a branch or subroutine call +000041 ;; from outside code areas. +000042 ;; +000043 ;; B. Ulmann fecit +000044 ;; +000045 ;; 17-DEC-2007: Begin of coding +000046 ;; 03-AUG-2015: After upgrading the emulator and fixing some (serious) bugs the work on the +000047 ;; monitor continues +000048 ;; 06-AUG-2015: Basic monitor functions implemented +000049 ;; +000050 ;; Known bugs: +000051 ;; +000052 ;; Bits and pieces: +000053 ;; - All functions expect their input parameters in the registers R8, R9 and maybe R10. +000054 ;; - The result of a function is returned in the first non-used high numbered register, so if a +000055 ;; function expects its parameters in R8 and R9, it will return its result in R10. If it only +000056 ;; expects one parameter, the result will be +000057 ;; returned in R9 respectively. +000058 ;; - Every function name starts with its subsection name followed by a dollar sign, so all string +000059 ;; routines have names starting with "STR$". +000060 ;; - Labels within a function always have an underscore following the subsystem name, so a label +000061 ;; within the routine STR$CMP would have the form "STR$_CMP...". So never jump to a label of the +000062 ;; form "$_..." since this will be a label buried inside a function. +000063 ;; - Every subsystem (string routines, IO routines etc.) has its own constants which are always +000064 ;; located after the code for the routines. +000065 ;; - To assemble this monitor just call the "asm" shell script which will use the C preprocessor +000066 ;; to include the necessary library files. +000067 ;; +000068 .ORG 0x0000 ; The monitor begins at address 0x0000, so the lower +000069 ; address EPROMs should be mapped into memory by hardware +000070 ; default on power up. +000071 ; +000072 ; Some useful constants +000073 ; +000074 +000075 ; +000076 ; Main program +000077 ; +000078 0000 0FB4 FC00 QMON$COLDSTART MOVE IO$BASE, R13 ; Set up stack pointer +000079 0002 0FA0 0119 MOVE QMON$WELCOME, R8 ; Print welcome message +000080 0004 FFB0 0601 RSUB IO$PUTS, 1 +000081 0006 0FA0 06E7 MOVE QMON$LAST_ADDR, R8 ; Clear memory after the monitor +000082 0008 1FA0 0001 ADD 0x0001, R8 ; Start address +000083 000A 0FA4 FC00 MOVE IO$BASE, R9 ; Determine length of memory area +000084 000C 3824 SUB R8, R9 ; to be cleared +000085 000D 3FA4 0001 SUB 0x0001, R9 ; We need one stack cell for the following call +000086 000F BA28 XOR R10, R10 ; Clear with zero words +000087 0010 FFB0 06B6 RSUB MEM$FILL, 1 ; Clear +000088 ;;TODO: Clear registers +000089 0012 0FB4 FC00 QMON$WARMSTART MOVE IO$BASE, R13 ; Set up stack pointer +000090 0014 9FB8 00FF AND 0x00FF, R14 ; Reset register bank to zero +000091 0016 FFB0 0600 RSUB IO$PUT_CRLF, 1 +000092 0018 0FA0 01A0 QMON$MAIN_LOOP MOVE QMON$PROMPT, R8 ; Print monitor prompt +000093 001A FFB0 05EB RSUB IO$PUTS, 1 +000094 001C FFB0 05BE RSUB IO$GETCHAR, 1 ; Wait for a key being pressed +000095 001E FFB0 0625 RSUB CHR$TO_UPPER, 1 ; Convert it into an uppercase letter +000096 0020 FFB0 0605 RSUB IO$PUTCHAR, 1 ; Echo the character +000097 0022 CFA0 0043 CMP 'C', R8 ; Control group? +000098 0024 FFAB 002E RBRA QMON$MAYBE_M, !Z ; No +000099 ; Control group +000100 0026 0FA0 035A MOVE QMON$CG_C, R8 +000101 0028 FFB0 05DD RSUB IO$PUTS, 1 +000102 002A FFB0 05B0 RSUB IO$GETCHAR, 1 ; Get command character +000103 002C FFB0 0617 RSUB CHR$TO_UPPER, 1 +000104 002E CFA0 0043 CMP 'C', R8 ; Cold start? +000105 0030 FFAB 0006 RBRA QMON$C_MAYBE_H, !Z ; No... +000106 ; CONTROL/COLDSTART: +000107 0032 0FA0 0362 MOVE QMON$CG_C_C, R8 +000108 0034 FFB0 05D1 RSUB IO$PUTS, 1 +000109 0036 FFA0 FFC8 RBRA QMON$COLDSTART, 1 ; Yes! +000110 0038 CFA0 0048 QMON$C_MAYBE_H CMP 'H', R8 ; Halt? +000111 003A FFAB 0005 RBRA QMON$MAYBE_R, !Z +000112 ; CONTROL/HALT: +000113 003C 0FA0 036D MOVE QMON$CG_C_H, R8 +000114 003E FFB0 05C7 RSUB IO$PUTS, 1 +000115 0040 E000 HALT +000116 0041 CFA0 0052 QMON$MAYBE_R CMP 'R', R8 ; Run? +000117 0043 FFAB 0009 RBRA QMON$C_ILLEGAL, !Z ; No +000118 ; CONTROL/RUN: +000119 0045 0FA0 0376 MOVE QMON$CG_C_R, R8 +000120 0047 FFB0 05BE RSUB IO$PUTS, 1 +000121 0049 FFB0 0546 RSUB IO$GET_W_HEX, 1 ; Get address +000122 004B FFB0 05CB RSUB IO$PUT_CRLF, 1 +000123 004D F800 ABRA R8, 1 ; Jump to address specified +000124 004E 0FA0 01C8 QMON$C_ILLEGAL MOVE QMON$ILLCMD, R8 ; Control group C, illegal command +000125 0050 FFB0 05B5 RSUB IO$PUTS, 1 +000126 0052 FFA0 FFC4 RBRA QMON$MAIN_LOOP, 1 +000127 0054 CFA0 004D QMON$MAYBE_M CMP 'M', R8 ; Compare with 'M' +000128 0056 FFAB 00B1 RBRA QMON$MAYBE_H, !Z ; No M, try next... +000129 ; Memory control group: +000130 0058 0FA0 0383 MOVE QMON$CG_M, R8 ; Print control group name +000131 005A FFB0 05AB RSUB IO$PUTS, 1 +000132 005C FFB0 057E RSUB IO$GETCHAR, 1 ; Get command character +000133 005E FFB0 05E5 RSUB CHR$TO_UPPER, 1 ; ...convert it to upper case +000134 0060 CFA0 0043 CMP 'C', R8 ; 'Change'? +000135 0062 FFAB 0019 RBRA QMON$M_MAYBE_D, !Z +000136 ; MEMORY/CHANGE: +000137 0064 0FA0 038A MOVE QMON$CG_M_C, R8 ; Print prompt for address +000138 0066 FFB0 059F RSUB IO$PUTS, 1 +000139 0068 FFB0 0527 RSUB IO$GET_W_HEX, 1 ; Read in address +000140 006A 0800 MOVE R8, R0 +000141 006B 0FA0 039A MOVE QMON$CG_M_C1, R8 ; Prepare output of current value +000142 006D FFB0 0598 RSUB IO$PUTS, 1 +000143 006F 0060 MOVE @R0, R8 ; Get current value +000144 0070 FFB0 054A RSUB IO$PUT_W_HEX, 1 ; Print current value +000145 0072 0FA0 03AA MOVE QMON$CG_M_C2, R8 ; Prompt for new value +000146 0074 FFB0 0591 RSUB IO$PUTS, 1 +000147 0076 FFB0 0519 RSUB IO$GET_W_HEX, 1 +000148 0078 0801 MOVE R8, @R0 +000149 0079 FFB0 059D RSUB IO$PUT_CRLF, 1 +000150 007B FFA0 FF9B RBRA QMON$MAIN_LOOP, 1 +000151 007D CFA0 0044 QMON$M_MAYBE_D CMP 'D', R8 +000152 007F FFAB 0017 RBRA QMON$M_MAYBE_E, !Z ; No D, try next... +000153 ; MEMORY/DUMP: +000154 0081 0FA0 03B6 MOVE QMON$CG_M_D, R8 ; Print prompt for start address +000155 0083 FFB0 0582 RSUB IO$PUTS, 1 +000156 0085 FFB0 050A RSUB IO$GET_W_HEX, 1 ; Get start address +000157 0087 0800 MOVE R8, R0 ; Remember start address in R8 +000158 0088 0FA0 03CA MOVE QMON$CG_M_D2, R8 ; Print prompt for end address +000159 008A FFB0 057B RSUB IO$PUTS, 1 +000160 008C FFB0 0503 RSUB IO$GET_W_HEX, 1 ; Get end address +000161 008E FFB0 0588 RSUB IO$PUT_CRLF, 1 +000162 0090 0824 MOVE R8, R9 +000163 0091 0020 MOVE R0, R8 +000164 0092 FFB0 04D2 RSUB IO$DUMP_MEMORY, 1 ; Dump memory contents +000165 0094 FFB0 0582 RSUB IO$PUT_CRLF, 1 +000166 0096 FFA0 FF80 RBRA QMON$MAIN_LOOP, 1 +000167 0098 CFA0 0045 QMON$M_MAYBE_E CMP 'E', R8 ; Is it an 'E'? +000168 009A FFAB 0012 RBRA QMON$M_MAYBE_F, !Z ; No... +000169 ; MEMORY/EXAMINE: +000170 009C 0FA0 03D8 MOVE QMON$CG_M_E, R8 ; Print prompt for address +000171 009E FFB0 0567 RSUB IO$PUTS, 1 +000172 00A0 FFB0 04EF RSUB IO$GET_W_HEX, 1 ; Read address +000173 00A2 0800 MOVE R8, R0 +000174 00A3 0FA0 0020 MOVE ' ', R8 +000175 00A5 FFB0 0580 RSUB IO$PUTCHAR, 1 +000176 00A7 0060 MOVE @R0, R8 +000177 00A8 FFB0 0512 RSUB IO$PUT_W_HEX, 1 +000178 00AA FFB0 056C RSUB IO$PUT_CRLF, 1 +000179 00AC FFA0 FF6A RBRA QMON$MAIN_LOOP, 1 +000180 00AE CFA0 0046 QMON$M_MAYBE_F CMP 'F', R8 +000181 00B0 FFAB 0020 RBRA QMON$M_MAYBE_L, !Z +000182 ; MEMORY/FILL: +000183 00B2 0FA0 03E9 MOVE QMON$CG_M_F, R8 +000184 00B4 FFB0 0551 RSUB IO$PUTS, 1 +000185 00B6 FFB0 04D9 RSUB IO$GET_W_HEX, 1 +000186 00B8 0800 MOVE R8, R0 +000187 00B9 0FA0 03FD MOVE QMON$CG_M_F2, R8 +000188 00BB FFB0 054A RSUB IO$PUTS, 1 +000189 00BD FFB0 04D2 RSUB IO$GET_W_HEX, 1 +000190 00BF 0804 MOVE R8, R1 +000191 00C0 0FA0 040B MOVE QMON$CG_M_F3, R8 +000192 00C2 FFB0 0543 RSUB IO$PUTS, 1 +000193 00C4 FFB0 04CB RSUB IO$GET_W_HEX, 1 +000194 00C6 0828 MOVE R8, R10 +000195 00C7 0020 MOVE R0, R8 +000196 00C8 3004 SUB R0, R1 +000197 00C9 1F84 0001 ADD 0x0001, R1 +000198 00CB 0124 MOVE R1, R9 +000199 00CC FFB0 05FA RSUB MEM$FILL, 1 +000200 00CE FFB0 0548 RSUB IO$PUT_CRLF, 1 +000201 00D0 FFA0 FF46 RBRA QMON$MAIN_LOOP, 1 +000202 00D2 CFA0 004C QMON$M_MAYBE_L CMP 'L', R8 +000203 00D4 FFAB 000C RBRA QMON$M_MAYBE_M, !Z +000204 ; MEMORY/LOAD: +000205 00D6 0FA0 0413 MOVE QMON$CG_M_L, R8 +000206 00D8 FFB0 052D RSUB IO$PUTS, 1 +000207 00DA FFB0 04B5 _QMON$ML_LOOP RSUB IO$GET_W_HEX, 1 ; Get address +000208 00DC 0800 MOVE R8, R0 +000209 00DD FFB0 04B2 RSUB IO$GET_W_HEX, 1 ; Get value +000210 00DF 0801 MOVE R8, @R0 +000211 00E0 FFA0 FFF8 RBRA _QMON$ML_LOOP, 1 +000212 00E2 CFA0 004D QMON$M_MAYBE_M CMP 'M', R8 +000213 00E4 FFAB 001D RBRA QMON$M_ILLEGAL, !Z +000214 ; MEMORY/MOVE: +000215 00E6 0FA0 044D MOVE QMON$CG_M_M, R8 +000216 00E8 FFB0 051D RSUB IO$PUTS, 1 +000217 00EA FFB0 04A5 RSUB IO$GET_W_HEX, 1 +000218 00EC 0800 MOVE R8, R0 +000219 00ED 0FA0 0458 MOVE QMON$CG_M_M2, R8 +000220 00EF FFB0 0516 RSUB IO$PUTS, 1 +000221 00F1 FFB0 049E RSUB IO$GET_W_HEX, 1 +000222 00F3 0804 MOVE R8, R1 +000223 00F4 0FA0 045D MOVE QMON$CG_M_M3, R8 +000224 00F6 FFB0 050F RSUB IO$PUTS, 1 +000225 00F8 FFB0 0497 RSUB IO$GET_W_HEX, 1 +000226 00FA 0828 MOVE R8, R10 +000227 00FB 0020 MOVE R0, R8 +000228 00FC 0124 MOVE R1, R9 +000229 00FD FFB0 05D8 RSUB MEM$MOVE, 1 +000230 00FF FFB0 0517 RSUB IO$PUT_CRLF, 1 +000231 0101 FFA0 FF15 RBRA QMON$MAIN_LOOP, 1 +000232 0103 0FA0 01C8 QMON$M_ILLEGAL MOVE QMON$ILLCMD, R8 +000233 0105 FFB0 0500 RSUB IO$PUTS, 1 +000234 0107 FFA0 FF0F RBRA QMON$MAIN_LOOP, 1 +000235 0109 CFA0 0048 QMON$MAYBE_H CMP 'H', R8 +000236 010B FFAB 0006 RBRA QMON$NOT_H, !Z ; No H, try next... +000237 ; HELP: +000238 010D 0FA0 01E3 MOVE QMON$HELP, R8 ; H(elp) - print help text +000239 010F FFB0 04F6 RSUB IO$PUTS, 1 +000240 0111 FFA0 FF05 RBRA QMON$MAIN_LOOP, 1 +000241 0113 0FA0 01A7 QMON$NOT_H MOVE QMON$ILLCMDGRP, R8A ; Illegal command group +000242 0115 FFB0 04F0 RSUB IO$PUTS, 1 +000243 0117 FFA0 FEFF RBRA QMON$MAIN_LOOP, 1 +000244 +000245 0119 000D QMON$WELCOME .ASCII_P "\n\nSimple QNICE-monitor - Version 0.2 (Bernd Ulmann, August 2015)\n" + 011A 000A + 011B 000D + 011C 000A + 011D 0053 + 011E 0069 + 011F 006D + 0120 0070 + 0121 006C + 0122 0065 + 0123 0020 + 0124 0051 + 0125 004E + 0126 0049 + 0127 0043 + 0128 0045 + 0129 002D + 012A 006D + 012B 006F + 012C 006E + 012D 0069 + 012E 0074 + 012F 006F + 0130 0072 + 0131 0020 + 0132 002D + 0133 0020 + 0134 0056 + 0135 0065 + 0136 0072 + 0137 0073 + 0138 0069 + 0139 006F + 013A 006E + 013B 0020 + 013C 0030 + 013D 002E + 013E 0032 + 013F 0020 + 0140 0028 + 0141 0042 + 0142 0065 + 0143 0072 + 0144 006E + 0145 0064 + 0146 0020 + 0147 0055 + 0148 006C + 0149 006D + 014A 0061 + 014B 006E + 014C 006E + 014D 002C + 014E 0020 + 014F 0041 + 0150 0075 + 0151 0067 + 0152 0075 + 0153 0073 + 0154 0074 + 0155 0020 + 0156 0032 + 0157 0030 + 0158 0031 + 0159 0035 + 015A 0029 + 015B 000D + 015C 000A +000246 015D 002D .ASCII_W "--------------------------------------------------------------\n\n" + 015E 002D + 015F 002D + 0160 002D + 0161 002D + 0162 002D + 0163 002D + 0164 002D + 0165 002D + 0166 002D + 0167 002D + 0168 002D + 0169 002D + 016A 002D + 016B 002D + 016C 002D + 016D 002D + 016E 002D + 016F 002D + 0170 002D + 0171 002D + 0172 002D + 0173 002D + 0174 002D + 0175 002D + 0176 002D + 0177 002D + 0178 002D + 0179 002D + 017A 002D + 017B 002D + 017C 002D + 017D 002D + 017E 002D + 017F 002D + 0180 002D + 0181 002D + 0182 002D + 0183 002D + 0184 002D + 0185 002D + 0186 002D + 0187 002D + 0188 002D + 0189 002D + 018A 002D + 018B 002D + 018C 002D + 018D 002D + 018E 002D + 018F 002D + 0190 002D + 0191 002D + 0192 002D + 0193 002D + 0194 002D + 0195 002D + 0196 002D + 0197 002D + 0198 002D + 0199 002D + 019A 002D + 019B 000D + 019C 000A + 019D 000D + 019E 000A + 019F 0000 +000247 01A0 0051 QMON$PROMPT .ASCII_W "QMON> " + 01A1 004D + 01A2 004F + 01A3 004E + 01A4 003E + 01A5 0020 + 01A6 0000 +000248 01A7 0020 QMON$ILLCMDGRP .ASCII_W " *** Illegal command group ***\n" + 01A8 002A + 01A9 002A + 01AA 002A + 01AB 0020 + 01AC 0049 + 01AD 006C + 01AE 006C + 01AF 0065 + 01B0 0067 + 01B1 0061 + 01B2 006C + 01B3 0020 + 01B4 0063 + 01B5 006F + 01B6 006D + 01B7 006D + 01B8 0061 + 01B9 006E + 01BA 0064 + 01BB 0020 + 01BC 0067 + 01BD 0072 + 01BE 006F + 01BF 0075 + 01C0 0070 + 01C1 0020 + 01C2 002A + 01C3 002A + 01C4 002A + 01C5 000D + 01C6 000A + 01C7 0000 +000249 01C8 0020 QMON$ILLCMD .ASCII_W " *** Illegal command ***\n" + 01C9 002A + 01CA 002A + 01CB 002A + 01CC 0020 + 01CD 0049 + 01CE 006C + 01CF 006C + 01D0 0065 + 01D1 0067 + 01D2 0061 + 01D3 006C + 01D4 0020 + 01D5 0063 + 01D6 006F + 01D7 006D + 01D8 006D + 01D9 0061 + 01DA 006E + 01DB 0064 + 01DC 0020 + 01DD 002A + 01DE 002A + 01DF 002A + 01E0 000D + 01E1 000A + 01E2 0000 +000250 01E3 0045 QMON$HELP .ASCII_P "ELP:\n\n" + 01E4 004C + 01E5 0050 + 01E6 003A + 01E7 000D + 01E8 000A + 01E9 000D + 01EA 000A +000251 01EB 0020 .ASCII_P " C(control group):\n" + 01EC 0020 + 01ED 0020 + 01EE 0020 + 01EF 0043 + 01F0 0028 + 01F1 0063 + 01F2 006F + 01F3 006E + 01F4 0074 + 01F5 0072 + 01F6 006F + 01F7 006C + 01F8 0020 + 01F9 0067 + 01FA 0072 + 01FB 006F + 01FC 0075 + 01FD 0070 + 01FE 0029 + 01FF 003A + 0200 000D + 0201 000A +000252 0202 0020 .ASCII_P " C(old start) H(alt) R(un)\n" + 0203 0020 + 0204 0020 + 0205 0020 + 0206 0020 + 0207 0020 + 0208 0020 + 0209 0020 + 020A 0043 + 020B 0028 + 020C 006F + 020D 006C + 020E 0064 + 020F 0020 + 0210 0073 + 0211 0074 + 0212 0061 + 0213 0072 + 0214 0074 + 0215 0029 + 0216 0020 + 0217 0048 + 0218 0028 + 0219 0061 + 021A 006C + 021B 0074 + 021C 0029 + 021D 0020 + 021E 0052 + 021F 0028 + 0220 0075 + 0221 006E + 0222 0029 + 0223 000D + 0224 000A +000253 0225 0020 .ASCII_P " H(elp)\n" + 0226 0020 + 0227 0020 + 0228 0020 + 0229 0048 + 022A 0028 + 022B 0065 + 022C 006C + 022D 0070 + 022E 0029 + 022F 000D + 0230 000A +000254 0231 0020 .ASCII_P " M(emory group):\n" + 0232 0020 + 0233 0020 + 0234 0020 + 0235 004D + 0236 0028 + 0237 0065 + 0238 006D + 0239 006F + 023A 0072 + 023B 0079 + 023C 0020 + 023D 0067 + 023E 0072 + 023F 006F + 0240 0075 + 0241 0070 + 0242 0029 + 0243 003A + 0244 000D + 0245 000A +000255 0246 0020 .ASCII_P " C(hange) D(ump) E(xamine) F(ill) L(oad) M(ove)\n" + 0247 0020 + 0248 0020 + 0249 0020 + 024A 0020 + 024B 0020 + 024C 0020 + 024D 0020 + 024E 0043 + 024F 0028 + 0250 0068 + 0251 0061 + 0252 006E + 0253 0067 + 0254 0065 + 0255 0029 + 0256 0020 + 0257 0044 + 0258 0028 + 0259 0075 + 025A 006D + 025B 0070 + 025C 0029 + 025D 0020 + 025E 0045 + 025F 0028 + 0260 0078 + 0261 0061 + 0262 006D + 0263 0069 + 0264 006E + 0265 0065 + 0266 0029 + 0267 0020 + 0268 0046 + 0269 0028 + 026A 0069 + 026B 006C + 026C 006C + 026D 0029 + 026E 0020 + 026F 004C + 0270 0028 + 0271 006F + 0272 0061 + 0273 0064 + 0274 0029 + 0275 0020 + 0276 004D + 0277 0028 + 0278 006F + 0279 0076 + 027A 0065 + 027B 0029 + 027C 000D + 027D 000A +000256 027E 000D .ASCII_P "\n General: CTRL-E performs a warm start whenever an\n" + 027F 000A + 0280 0020 + 0281 0020 + 0282 0020 + 0283 0020 + 0284 0047 + 0285 0065 + 0286 006E + 0287 0065 + 0288 0072 + 0289 0061 + 028A 006C + 028B 003A + 028C 0020 + 028D 0043 + 028E 0054 + 028F 0052 + 0290 004C + 0291 002D + 0292 0045 + 0293 0020 + 0294 0070 + 0295 0065 + 0296 0072 + 0297 0066 + 0298 006F + 0299 0072 + 029A 006D + 029B 0073 + 029C 0020 + 029D 0061 + 029E 0020 + 029F 0077 + 02A0 0061 + 02A1 0072 + 02A2 006D + 02A3 0020 + 02A4 0073 + 02A5 0074 + 02A6 0061 + 02A7 0072 + 02A8 0074 + 02A9 0020 + 02AA 0077 + 02AB 0068 + 02AC 0065 + 02AD 006E + 02AE 0065 + 02AF 0076 + 02B0 0065 + 02B1 0072 + 02B2 0020 + 02B3 0061 + 02B4 006E + 02B5 000D + 02B6 000A +000257 02B7 0020 .ASCII_P " input from keyboard is expected.\n" + 02B8 0020 + 02B9 0020 + 02BA 0020 + 02BB 0020 + 02BC 0020 + 02BD 0020 + 02BE 0020 + 02BF 0069 + 02C0 006E + 02C1 0070 + 02C2 0075 + 02C3 0074 + 02C4 0020 + 02C5 0066 + 02C6 0072 + 02C7 006F + 02C8 006D + 02C9 0020 + 02CA 006B + 02CB 0065 + 02CC 0079 + 02CD 0062 + 02CE 006F + 02CF 0061 + 02D0 0072 + 02D1 0064 + 02D2 0020 + 02D3 0069 + 02D4 0073 + 02D5 0020 + 02D6 0065 + 02D7 0078 + 02D8 0070 + 02D9 0065 + 02DA 0063 + 02DB 0074 + 02DC 0065 + 02DD 0064 + 02DE 002E + 02DF 000D + 02E0 000A +000258 02E1 000D .ASCII_P "\n M(emory)L(oad) can be used to load assembler output\n" + 02E2 000A + 02E3 0020 + 02E4 0020 + 02E5 0020 + 02E6 0020 + 02E7 004D + 02E8 0028 + 02E9 0065 + 02EA 006D + 02EB 006F + 02EC 0072 + 02ED 0079 + 02EE 0029 + 02EF 004C + 02F0 0028 + 02F1 006F + 02F2 0061 + 02F3 0064 + 02F4 0029 + 02F5 0020 + 02F6 0063 + 02F7 0061 + 02F8 006E + 02F9 0020 + 02FA 0062 + 02FB 0065 + 02FC 0020 + 02FD 0075 + 02FE 0073 + 02FF 0065 + 0300 0064 + 0301 0020 + 0302 0074 + 0303 006F + 0304 0020 + 0305 006C + 0306 006F + 0307 0061 + 0308 0064 + 0309 0020 + 030A 0061 + 030B 0073 + 030C 0073 + 030D 0065 + 030E 006D + 030F 0062 + 0310 006C + 0311 0065 + 0312 0072 + 0313 0020 + 0314 006F + 0315 0075 + 0316 0074 + 0317 0070 + 0318 0075 + 0319 0074 + 031A 000D + 031B 000A +000259 031C 0020 .ASCII_P " by pasting it to the terminal. CTRL-E terminates.\n" + 031D 0020 + 031E 0020 + 031F 0020 + 0320 0020 + 0321 0020 + 0322 0020 + 0323 0020 + 0324 0062 + 0325 0079 + 0326 0020 + 0327 0070 + 0328 0061 + 0329 0073 + 032A 0074 + 032B 0069 + 032C 006E + 032D 0067 + 032E 0020 + 032F 0069 + 0330 0074 + 0331 0020 + 0332 0074 + 0333 006F + 0334 0020 + 0335 0074 + 0336 0068 + 0337 0065 + 0338 0020 + 0339 0074 + 033A 0065 + 033B 0072 + 033C 006D + 033D 0069 + 033E 006E + 033F 0061 + 0340 006C + 0341 002E + 0342 0020 + 0343 0043 + 0344 0054 + 0345 0052 + 0346 004C + 0347 002D + 0348 0045 + 0349 0020 + 034A 0074 + 034B 0065 + 034C 0072 + 034D 006D + 034E 0069 + 034F 006E + 0350 0061 + 0351 0074 + 0352 0065 + 0353 0073 + 0354 002E + 0355 000D + 0356 000A +000260 0357 000D .ASCII_W "\n" + 0358 000A + 0359 0000 +000261 035A 004F QMON$CG_C .ASCII_W "ONTROL/" + 035B 004E + 035C 0054 + 035D 0052 + 035E 004F + 035F 004C + 0360 002F + 0361 0000 +000262 0362 0043 QMON$CG_C_C .ASCII_W "COLD START" + 0363 004F + 0364 004C + 0365 0044 + 0366 0020 + 0367 0053 + 0368 0054 + 0369 0041 + 036A 0052 + 036B 0054 + 036C 0000 +000263 036D 0048 QMON$CG_C_H .ASCII_W "HALT\n\n" + 036E 0041 + 036F 004C + 0370 0054 + 0371 000D + 0372 000A + 0373 000D + 0374 000A + 0375 0000 +000264 0376 0052 QMON$CG_C_R .ASCII_W "RUN ADDRESS=" + 0377 0055 + 0378 004E + 0379 0020 + 037A 0041 + 037B 0044 + 037C 0044 + 037D 0052 + 037E 0045 + 037F 0053 + 0380 0053 + 0381 003D + 0382 0000 +000265 0383 0045 QMON$CG_M .ASCII_W "EMORY/" + 0384 004D + 0385 004F + 0386 0052 + 0387 0059 + 0388 002F + 0389 0000 +000266 038A 0043 QMON$CG_M_C .ASCII_W "CHANGE ADDRESS=" + 038B 0048 + 038C 0041 + 038D 004E + 038E 0047 + 038F 0045 + 0390 0020 + 0391 0041 + 0392 0044 + 0393 0044 + 0394 0052 + 0395 0045 + 0396 0053 + 0397 0053 + 0398 003D + 0399 0000 +000267 039A 0020 QMON$CG_M_C1 .ASCII_W " CURRENT VALUE=" + 039B 0043 + 039C 0055 + 039D 0052 + 039E 0052 + 039F 0045 + 03A0 004E + 03A1 0054 + 03A2 0020 + 03A3 0056 + 03A4 0041 + 03A5 004C + 03A6 0055 + 03A7 0045 + 03A8 003D + 03A9 0000 +000268 03AA 0020 QMON$CG_M_C2 .ASCII_W " NEW VALUE=" + 03AB 004E + 03AC 0045 + 03AD 0057 + 03AE 0020 + 03AF 0056 + 03B0 0041 + 03B1 004C + 03B2 0055 + 03B3 0045 + 03B4 003D + 03B5 0000 +000269 03B6 0044 QMON$CG_M_D .ASCII_W "DUMP START ADDRESS=" + 03B7 0055 + 03B8 004D + 03B9 0050 + 03BA 0020 + 03BB 0053 + 03BC 0054 + 03BD 0041 + 03BE 0052 + 03BF 0054 + 03C0 0020 + 03C1 0041 + 03C2 0044 + 03C3 0044 + 03C4 0052 + 03C5 0045 + 03C6 0053 + 03C7 0053 + 03C8 003D + 03C9 0000 +000270 03CA 0020 QMON$CG_M_D2 .ASCII_W " END ADDRESS=" + 03CB 0045 + 03CC 004E + 03CD 0044 + 03CE 0020 + 03CF 0041 + 03D0 0044 + 03D1 0044 + 03D2 0052 + 03D3 0045 + 03D4 0053 + 03D5 0053 + 03D6 003D + 03D7 0000 +000271 03D8 0045 QMON$CG_M_E .ASCII_W "EXAMINE ADDRESS=" + 03D9 0058 + 03DA 0041 + 03DB 004D + 03DC 0049 + 03DD 004E + 03DE 0045 + 03DF 0020 + 03E0 0041 + 03E1 0044 + 03E2 0044 + 03E3 0052 + 03E4 0045 + 03E5 0053 + 03E6 0053 + 03E7 003D + 03E8 0000 +000272 03E9 0046 QMON$CG_M_F .ASCII_W "FILL START ADDRESS=" + 03EA 0049 + 03EB 004C + 03EC 004C + 03ED 0020 + 03EE 0053 + 03EF 0054 + 03F0 0041 + 03F1 0052 + 03F2 0054 + 03F3 0020 + 03F4 0041 + 03F5 0044 + 03F6 0044 + 03F7 0052 + 03F8 0045 + 03F9 0053 + 03FA 0053 + 03FB 003D + 03FC 0000 +000273 03FD 0020 QMON$CG_M_F2 .ASCII_W " END ADDRESS=" + 03FE 0045 + 03FF 004E + 0400 0044 + 0401 0020 + 0402 0041 + 0403 0044 + 0404 0044 + 0405 0052 + 0406 0045 + 0407 0053 + 0408 0053 + 0409 003D + 040A 0000 +000274 040B 0020 QMON$CG_M_F3 .ASCII_W " VALUE=" + 040C 0056 + 040D 0041 + 040E 004C + 040F 0055 + 0410 0045 + 0411 003D + 0412 0000 +000275 0413 004C QMON$CG_M_L .ASCII_W "LOAD - ENTER ADDRESS/VALUE PAIRS, TERMINATE WITH CTRL-E\n" + 0414 004F + 0415 0041 + 0416 0044 + 0417 0020 + 0418 002D + 0419 0020 + 041A 0045 + 041B 004E + 041C 0054 + 041D 0045 + 041E 0052 + 041F 0020 + 0420 0041 + 0421 0044 + 0422 0044 + 0423 0052 + 0424 0045 + 0425 0053 + 0426 0053 + 0427 002F + 0428 0056 + 0429 0041 + 042A 004C + 042B 0055 + 042C 0045 + 042D 0020 + 042E 0050 + 042F 0041 + 0430 0049 + 0431 0052 + 0432 0053 + 0433 002C + 0434 0020 + 0435 0054 + 0436 0045 + 0437 0052 + 0438 004D + 0439 0049 + 043A 004E + 043B 0041 + 043C 0054 + 043D 0045 + 043E 0020 + 043F 0057 + 0440 0049 + 0441 0054 + 0442 0048 + 0443 0020 + 0444 0043 + 0445 0054 + 0446 0052 + 0447 004C + 0448 002D + 0449 0045 + 044A 000D + 044B 000A + 044C 0000 +000276 044D 004D QMON$CG_M_M .ASCII_W "MOVE FROM=" + 044E 004F + 044F 0056 + 0450 0045 + 0451 0020 + 0452 0046 + 0453 0052 + 0454 004F + 0455 004D + 0456 003D + 0457 0000 +000277 0458 0020 QMON$CG_M_M2 .ASCII_W " TO=" + 0459 0054 + 045A 004F + 045B 003D + 045C 0000 +000278 045D 0020 QMON$CG_M_M3 .ASCII_W " LENGTH=" + 045E 004C + 045F 0045 + 0460 004E + 0461 0047 + 0462 0054 + 0463 0048 + 0464 003D + 0465 0000 +000279 ; +000280 0466 0000 QMON$COMMAND .BLOCK 0x0100 ; Reserve some memory for holding a command line + 0467 0000 + 0468 0000 + 0469 0000 + 046A 0000 + 046B 0000 + 046C 0000 + 046D 0000 + 046E 0000 + 046F 0000 + 0470 0000 + 0471 0000 + 0472 0000 + 0473 0000 + 0474 0000 + 0475 0000 + 0476 0000 + 0477 0000 + 0478 0000 + 0479 0000 + 047A 0000 + 047B 0000 + 047C 0000 + 047D 0000 + 047E 0000 + 047F 0000 + 0480 0000 + 0481 0000 + 0482 0000 + 0483 0000 + 0484 0000 + 0485 0000 + 0486 0000 + 0487 0000 + 0488 0000 + 0489 0000 + 048A 0000 + 048B 0000 + 048C 0000 + 048D 0000 + 048E 0000 + 048F 0000 + 0490 0000 + 0491 0000 + 0492 0000 + 0493 0000 + 0494 0000 + 0495 0000 + 0496 0000 + 0497 0000 + 0498 0000 + 0499 0000 + 049A 0000 + 049B 0000 + 049C 0000 + 049D 0000 + 049E 0000 + 049F 0000 + 04A0 0000 + 04A1 0000 + 04A2 0000 + 04A3 0000 + 04A4 0000 + 04A5 0000 + 04A6 0000 + 04A7 0000 + 04A8 0000 + 04A9 0000 + 04AA 0000 + 04AB 0000 + 04AC 0000 + 04AD 0000 + 04AE 0000 + 04AF 0000 + 04B0 0000 + 04B1 0000 + 04B2 0000 + 04B3 0000 + 04B4 0000 + 04B5 0000 + 04B6 0000 + 04B7 0000 + 04B8 0000 + 04B9 0000 + 04BA 0000 + 04BB 0000 + 04BC 0000 + 04BD 0000 + 04BE 0000 + 04BF 0000 + 04C0 0000 + 04C1 0000 + 04C2 0000 + 04C3 0000 + 04C4 0000 + 04C5 0000 + 04C6 0000 + 04C7 0000 + 04C8 0000 + 04C9 0000 + 04CA 0000 + 04CB 0000 + 04CC 0000 + 04CD 0000 + 04CE 0000 + 04CF 0000 + 04D0 0000 + 04D1 0000 + 04D2 0000 + 04D3 0000 + 04D4 0000 + 04D5 0000 + 04D6 0000 + 04D7 0000 + 04D8 0000 + 04D9 0000 + 04DA 0000 + 04DB 0000 + 04DC 0000 + 04DD 0000 + 04DE 0000 + 04DF 0000 + 04E0 0000 + 04E1 0000 + 04E2 0000 + 04E3 0000 + 04E4 0000 + 04E5 0000 + 04E6 0000 + 04E7 0000 + 04E8 0000 + 04E9 0000 + 04EA 0000 + 04EB 0000 + 04EC 0000 + 04ED 0000 + 04EE 0000 + 04EF 0000 + 04F0 0000 + 04F1 0000 + 04F2 0000 + 04F3 0000 + 04F4 0000 + 04F5 0000 + 04F6 0000 + 04F7 0000 + 04F8 0000 + 04F9 0000 + 04FA 0000 + 04FB 0000 + 04FC 0000 + 04FD 0000 + 04FE 0000 + 04FF 0000 + 0500 0000 + 0501 0000 + 0502 0000 + 0503 0000 + 0504 0000 + 0505 0000 + 0506 0000 + 0507 0000 + 0508 0000 + 0509 0000 + 050A 0000 + 050B 0000 + 050C 0000 + 050D 0000 + 050E 0000 + 050F 0000 + 0510 0000 + 0511 0000 + 0512 0000 + 0513 0000 + 0514 0000 + 0515 0000 + 0516 0000 + 0517 0000 + 0518 0000 + 0519 0000 + 051A 0000 + 051B 0000 + 051C 0000 + 051D 0000 + 051E 0000 + 051F 0000 + 0520 0000 + 0521 0000 + 0522 0000 + 0523 0000 + 0524 0000 + 0525 0000 + 0526 0000 + 0527 0000 + 0528 0000 + 0529 0000 + 052A 0000 + 052B 0000 + 052C 0000 + 052D 0000 + 052E 0000 + 052F 0000 + 0530 0000 + 0531 0000 + 0532 0000 + 0533 0000 + 0534 0000 + 0535 0000 + 0536 0000 + 0537 0000 + 0538 0000 + 0539 0000 + 053A 0000 + 053B 0000 + 053C 0000 + 053D 0000 + 053E 0000 + 053F 0000 + 0540 0000 + 0541 0000 + 0542 0000 + 0543 0000 + 0544 0000 + 0545 0000 + 0546 0000 + 0547 0000 + 0548 0000 + 0549 0000 + 054A 0000 + 054B 0000 + 054C 0000 + 054D 0000 + 054E 0000 + 054F 0000 + 0550 0000 + 0551 0000 + 0552 0000 + 0553 0000 + 0554 0000 + 0555 0000 + 0556 0000 + 0557 0000 + 0558 0000 + 0559 0000 + 055A 0000 + 055B 0000 + 055C 0000 + 055D 0000 + 055E 0000 + 055F 0000 + 0560 0000 + 0561 0000 + 0562 0000 + 0563 0000 + 0564 0000 + 0565 0000 +000281 +000282 ; +000283 ;;======================================================================================= +000284 ;; The collection of input/output related function starts here +000285 ;;======================================================================================= +000286 ; +000287 ; Define io system specific constants and memory areas etc. It is expected that the +000288 ; basic definitions from sysdef.asm have been included somewhere before! +000289 ; +000290 ;*************************************************************************************** +000291 ;* IO$DUMP_MEMORY prints a hexadecimal memory dump of a specified memory region. +000292 ;* +000293 ;* R8: Contains the start address +000294 ;* R9: Contains the end address (inclusive) +000295 ;* +000296 ;* The contents of R8 and R9 are preserved during the run of this function. +000297 ;*************************************************************************************** +000298 ; +000299 0566 1FB8 0100 IO$DUMP_MEMORY ADD 0x0100, R14 ; Get a new register page +000300 0568 0800 MOVE R8, R0 ; R0 will be the loop counter +000301 0569 0804 MOVE R8, R1 ; This will be needed to restore R8 later +000302 056A 090C MOVE R9, R3 +000303 056B 1F8C 0001 ADD 0x0001, R3 ; That is necessary since we want the last +000304 ; address printed, too +000305 056D 0F90 FFFF MOVE 0xFFFF, R4 ; Set R4 - this is the column counter - to -1 +000306 056F 0008 _IO$DUMP_MEMORY_LOOP MOVE R0, R2 ; Have we reached the end of the memory area? +000307 0570 3308 SUB R3, R2 +000308 0571 FFA3 0018 RBRA _IO$DUMP_MEMORY_EXIT, Z ; Yes - that is it, so exit this routine +000309 0573 1F90 0001 ADD 0x0001, R4 ; Next column +000310 0575 9F90 0007 AND 0x0007, R4 ; We compute mod 8 +000311 0577 FFAB 0009 RBRA _IO$DUMP_MEMORY_CONTENT, !Z; if the result is not equal 0 we do not +000312 ; need an address printed +000313 0579 FFB0 009D RSUB IO$PUT_CRLF, 1 ; Print a CR/LF pair +000314 057B 0020 MOVE R0, R8 ; Print address +000315 057C FFB0 003E RSUB IO$PUT_W_HEX, 1 +000316 057E 0FA0 0642 MOVE IO$COLON_DELIMITER, R8 ; Print a colon followed by a space +000317 0580 FFB0 0085 RSUB IO$PUTS, 1 +000318 0582 00A0 _IO$DUMP_MEMORY_CONTENT MOVE @R0++, R8 ; Print the memory contents of this location +000319 0583 FFB0 0037 RSUB IO$PUT_W_HEX, 1 +000320 0585 0FA0 0020 MOVE ' ', R8 ; Print a space +000321 0587 FFB0 009E RSUB IO$PUTCHAR, 1 +000322 0589 FFA0 FFE4 RBRA _IO$DUMP_MEMORY_LOOP, 1 ; Continue the print loop +000323 058B FFB0 008B _IO$DUMP_MEMORY_EXIT RSUB IO$PUT_CRLF, 1 ; Print a last CR/LF pair +000324 058D 0120 MOVE R1, R8 ; Restore R8, +000325 058E 3FB8 0100 SUB 0x0100, R14 ; switch back to the correct register page +000326 0590 0DBC MOVE @R13++, R15 +000327 ; +000328 ;*************************************************************************************** +000329 ;* IO$GET_W_HEX reads four hex nibbles from stdin and returns the corresponding +000330 ;* value in R8 +000331 ;* +000332 ;* Illegal characters (not 1-9A-F or a-f) will generate a bell signal. The only +000333 ;* exception to this behaviour is the character 'x' which will erase any input +000334 ;* up to this point. This has the positive effect that a hexadecimal value can +000335 ;* be entered as 0x.... or just as .... +000336 ;*************************************************************************************** +000337 ; +000338 0591 1FB8 0100 IO$GET_W_HEX ADD 0x0100, R14 ; Get a new register page +000339 0593 B000 _IO$GET_W_HEX_REDO XOR R0, R0 ; Clear R0 +000340 0594 0F84 0004 MOVE 4, R1 ; We need four characters +000341 0596 0FA4 0631 MOVE IO$HEX_NIBBLES, R9 ; Pointer to list of valid chars +000342 0598 FFB0 0042 _IO$GET_W_HEX_INPUT RSUB IO$GETCHAR, 1 ; Read a character into R8 +000343 059A FFA3 FA76 RBRA QMON$WARMSTART, Z +000344 059C FFB0 00A7 RSUB CHR$TO_UPPER, 1 ; Convert to upper case +000345 059E CFA0 0058 CMP 'X', R8 ; Was it an 'X'? +000346 05A0 FFA3 FFF1 RBRA _IO$GET_W_HEX_REDO, Z ; Yes - redo from start :-) +000347 05A2 FFB0 010F RSUB STR$STRCHR, 1 ; Is it a valid character? +000348 05A4 0A28 MOVE R10, R10 ; Result equal zero? +000349 05A5 FFAB 0006 RBRA _IO$GET_W_HEX_VALID, !Z ; No +000350 05A7 0FA0 0007 MOVE 7, R8 ; Yes - generate a beep :-) +000351 05A9 FFB0 007C RSUB IO$PUTCHAR, 1 +000352 05AB FFA0 FFEB RBRA _IO$GET_W_HEX_INPUT, 1 ; Retry +000353 05AD FFB0 0078 _IO$GET_W_HEX_VALID RSUB IO$PUTCHAR, 1 ; Echo character +000354 05AF 3FA8 0631 SUB IO$HEX_NIBBLES, R10 ; Get number of character +000355 05B1 5F80 0004 SHL 4, R0 +000356 05B3 1A00 ADD R10, R0 +000357 05B4 3F84 0001 SUB 0x0001, R1 +000358 05B6 FFAB FFE0 RBRA _IO$GET_W_HEX_INPUT, !Z ; Read next character +000359 05B8 0020 MOVE R0, R8 +000360 05B9 3FB8 0100 SUB 0x0100, R14 ; Restore previous register page +000361 05BB 0DBC MOVE @R13++, R15 +000362 ; +000363 ;*************************************************************************************** +000364 ;* IO$PUT_W_HEX prints a machine word in hexadecimal notation. +000365 ;* +000366 ;* R8: Contains the machine word to be printed in hex notation. +000367 ;* +000368 ;* The contents of R8 are being preserved during the run of this function. +000369 ;*************************************************************************************** +000370 ; +000371 05BC 1FB8 0100 IO$PUT_W_HEX ADD 0x0100, R14 ; Get a new register page +000372 05BE 0F80 0004 MOVE 0x0004, R0 ; Save constant for nibble shifting +000373 05C0 0010 MOVE R0, R4 ; Set loop counter to four +000374 05C1 0814 MOVE R8, R5 ; Copy contents of R8 for later restore +000375 05C2 0F84 0631 MOVE IO$HEX_NIBBLES, R1 ; Create a pointer to the list of nibbles +000376 ; Push four ASCII characters to the stack +000377 05C4 0108 _IO$PWH_SCAN MOVE R1, R2 ; and create a scratch copy of this pointer +000378 05C5 080C MOVE R8, R3 ; Create a local copy of the machine word +000379 05C6 9F8C 000F AND 0x000f, R3 ; Only the four LSBs are of interest +000380 05C8 1308 ADD R3, R2 ; Adjust pointer to the desired nibble +000381 05C9 0277 MOVE @R2, @--R13 ; and save the ASCII character to the stack +000382 05CA 6FA0 0004 SHR 4, R8 ; Shift R8 four places right +000383 05CC 3F90 0001 SUB 0x0001, R4 ; Decrement loop counter +000384 05CE FFAB FFF4 RBRA _IO$PWH_SCAN, !Z ; and continue with the next nibble +000385 ; Now read these characters back and print them +000386 05D0 0010 MOVE R0, R4 ; Initialize loop counter +000387 05D1 0DA0 _IO$PWH_PRINT MOVE @R13++, R8 ; Fetch a character from the stack +000388 05D2 FFB0 0053 RSUB IO$PUTCHAR, 1 ; and print it +000389 05D4 3F90 0001 SUB 0x0001, R4 ; Decrement loop counter +000390 05D6 FFAB FFF9 RBRA _IO$PWH_PRINT, !Z ; and continue with the next character +000391 ; That is all... +000392 05D8 0520 MOVE R5, R8 ; Restore contents of R8 +000393 05D9 3FB8 0100 SUB 0x0100, R14 ; Restore correct register page +000394 05DB 0DBC MOVE @R13++, R15 +000395 ; +000396 ;*************************************************************************************** +000397 ;* IO$GETCHAR reads a character from the first UART in the system. +000398 ;* +000399 ;* R8 will contain the character read in its lower eight bits +000400 ;*************************************************************************************** +000401 ; +000402 05DC 1FB8 0100 IO$GETCHAR ADD 0x0100, R14 +000403 05DE 0F80 FC00 MOVE IO$UART0_BASE, R0 +000404 05E0 0004 MOVE R0, R1 +000405 05E1 1F80 0001 ADD IO$UART_SRA, R0 ; R0 contains the address of the status register +000406 05E3 1F84 0003 ADD IO$UART_RHRA, R1 ; R1 contains the address of the receiver reg. +000407 05E5 0048 _IO$GETC_LOOP MOVE @R0, R2 ; Read status register +000408 05E6 9F88 0001 AND 0x0001, R2 ; Only bit 0 is of interest +000409 05E8 FFA3 FFFB RBRA _IO$GETC_LOOP, Z ; Loop until a character has been received +000410 05EA 0160 MOVE @R1, R8 ; Get the character from the receiver register +000411 05EB 3FB8 0100 SUB 0x0100, R14 +000412 05ED CFA0 0005 CMP 0x0005, R8 ; CTRL-E? +000413 05EF FFA3 FA21 RBRA QMON$WARMSTART, Z +000414 05F1 0DBC MOVE @R13++, R15 +000415 ; +000416 ;*************************************************************************************** +000417 ;* IO$GETS reads a CR/LF terminated string from the serial line +000418 ;* +000419 ;* R8 has to point to a preallocated memory area to store the input line +000420 ;*************************************************************************************** +000421 ; +000422 05F2 1FB8 0100 IO$GETS ADD 0x0100, R14 ; Get a new register page +000423 05F4 0800 MOVE R8, R0 ; Save parameters +000424 05F5 0804 MOVE R8, R1 +000425 05F6 FFB0 FFE4 _IO$GETS_LOOP RSUB IO$GETCHAR, 1 ; Get a single character from the serial line +000426 05F8 0802 MOVE R8, @R0++ ; Store it into the buffer area +000427 05F9 FFB0 002C RSUB IO$PUTCHAR, 1 ; Echo the character +000428 05FB 3FA0 000A SUB 0x000A, R8 ; Was it a LF character? +000429 05FD FFAB FFF7 RBRA _IO$GETS_LOOP, !Z ; No -> continue reading characters +000430 05FF 0F82 000D MOVE 0x000D, @R0++ ; Extend the string with a CR and +000431 0601 0F81 0000 MOVE 0x0000, @R0 ; terminate it with a null word +000432 0603 0120 MOVE R1, R8 ; Restore R8 which will now point to the string +000433 0604 3FB8 0100 SUB 0x0100, R14 ; Restore the register page +000434 0606 0DBC MOVE @R13++, R15 +000435 ; +000436 ;*************************************************************************************** +000437 ;* IO$PUTS prints a null terminated string. +000438 ;* +000439 ;* R8: Pointer to the string to be printed. Of each word only the lower eight bits +000440 ;* will be printed. The terminating word has to be zero. +000441 ;* +000442 ;* The contents of R8 are being preserved during the run of this function. +000443 ;*************************************************************************************** +000444 ; +000445 0607 1FB8 0100 IO$PUTS ADD 0x0100, R14 ; Get a new register page +000446 0609 0804 MOVE R8, R1 ; Save contents of R8 +000447 060A 0800 MOVE R8, R0 ; Local copy of the string pointer +000448 060B 00A0 _IO$PUTS_LOOP MOVE @R0++, R8 ; Get a character from the string +000449 060C 9FA0 00FF AND 0x00FF, R8 ; Only the lower eight bits are relevant +000450 060E FFA3 0004 RBRA _IO$PUTS_END, Z ; Return when the string end has been reached +000451 0610 FFB0 0015 RSUB IO$PUTCHAR, 1 ; Print this character +000452 0612 FFA0 FFF7 RBRA _IO$PUTS_LOOP, 1 ; Continue with the next character +000453 0614 0120 _IO$PUTS_END MOVE R1, R8 ; Restore contents of R8 +000454 0615 3FB8 0100 SUB 0x0100, R14 ; Restore correct register page +000455 0617 0DBC MOVE @R13++, R15 +000456 ; +000457 ;*************************************************************************************** +000458 ;* IO$PUT_CRLF prints actually a LF/CR (the reason for this is that curses on the +000459 ;* MAC, where the emulation currently runs, has problems with CR/LF, but +000460 ;* not with LF/CR) +000461 ;*************************************************************************************** +000462 ; +000463 0618 1FB8 0100 IO$PUT_CRLF ADD 0x0100, R14 ; Get a new register page +000464 061A 0800 MOVE R8, R0 ; Save contents of R8 +000465 061B 0FA0 000A MOVE 0x0A, R8 +000466 061D FFB0 0008 RSUB IO$PUTCHAR, 1 +000467 061F 0FA0 000D MOVE 0x0D, R8 +000468 0621 FFB0 0004 RSUB IO$PUTCHAR, 1 +000469 0623 0020 MOVE R0, R8 ; Restore contents of R8 +000470 0624 3FB8 0100 SUB 0x0100, R14 ; Return to previous register page +000471 0626 0DBC MOVE @R13++, R15 +000472 ; +000473 ;*************************************************************************************** +000474 ;* IO$PUTCHAR prints a single character. +000475 ;* +000476 ;* R8: Contains the character to be printed +000477 ; +000478 ;* The contents of R8 are being preserved during the run of this function. +000479 ;* +000480 ;* TODO: This routine is way too simple and only works with the simple +000481 ;* UART emulation. To use a real 16550 this routine will require a complete +000482 ;* rewrite! +000483 ;*************************************************************************************** +000484 ; +000485 0627 1FB8 0100 IO$PUTCHAR ADD 0x0100, R14 ; Get a new register page +000486 0629 0F80 FC00 MOVE IO$UART0_BASE, R0 +000487 062B 1F80 0003 ADD IO$UART_THRA, R0 ; R0 now points to the THRA register +000488 062D 0801 MOVE R8, @R0 ; Print character +000489 062E 3FB8 0100 SUB 0x0100, R14 ; Restore the old page +000490 0630 0DBC MOVE @R13++, R15 +000491 ; +000492 ;*************************************************************************************** +000493 ; Constants, etc. +000494 ;*************************************************************************************** +000495 ; +000496 0631 0030 IO$HEX_NIBBLES .ASCII_W "0123456789ABCDEF" + 0632 0031 + 0633 0032 + 0634 0033 + 0635 0034 + 0636 0035 + 0637 0036 + 0638 0037 + 0639 0038 + 063A 0039 + 063B 0041 + 063C 0042 + 063D 0043 + 063E 0044 + 063F 0045 + 0640 0046 + 0641 0000 +000497 0642 003A IO$COLON_DELIMITER .ASCII_W ": " + 0643 0020 + 0644 0000 +000498 +000499 ; +000500 ;;======================================================================================= +000501 ;; The collection of string related functions starts here +000502 ;;======================================================================================= +000503 ; +000504 ;*************************************************************************************** +000505 ;* CHR$TO_UPPER expects a character to be converted to upper case in R8 +000506 ;*************************************************************************************** +000507 ; +000508 0645 1FB8 0100 CHR$TO_UPPER ADD 0x0100, R14 +000509 0647 0800 MOVE R8, R0 ; Save character +000510 0648 3F80 0061 SUB 'a', R0 ; Less than 'a'? +000511 064A FFA4 0009 RBRA _CHR$TO_UPPER_EXIT, N ; Yes - nothing to do +000512 064C 0F80 007A MOVE 'z', R0 ; Check if greater than 'z' +000513 064E 3800 SUB R8, R0 +000514 064F FFA4 0004 RBRA _CHR$TO_UPPER_EXIT, N ; Yes - nothing to do +000515 0651 3FA0 0061 SUB 'a', R8 ; Perform the conversion +000516 0653 1FA0 0041 ADD 'A', R8 +000517 0655 3FB8 0100 _CHR$TO_UPPER_EXIT SUB 0x0100, R14 +000518 0657 0DBC MOVE @R13++, R15 +000519 ; +000520 ;*************************************************************************************** +000521 ;* STR$TO_UPPER expects the address of a string to be converted to upper case in R8 +000522 ;*************************************************************************************** +000523 ; +000524 0658 1FB8 0100 STR$TO_UPPER ADD 0x0100, R14 ; Get a new scratch register page +000525 065A 0800 MOVE R8, R0 ; Do not destroy parameters +000526 065B 0044 _STR$TO_UPPER_LOOP MOVE @R0, R1 ; Null terminator found? +000527 065C FFA3 0013 RBRA _STR$TO_UPPER_END, Z ; Yes - that is it +000528 065E 0108 MOVE R1, R2 +000529 065F 3F88 0061 SUB 'a', R2 ; Less than 'a'? +000530 0661 FFA4 000A RBRA _STR$TO_UPPER_NEXT, N ; Yes +000531 0663 0F88 007A MOVE 'z', R2 ; Greater than 'z'? +000532 0665 3108 SUB R1, R2 +000533 0666 FFA4 0005 RBRA _STR$TO_UPPER_NEXT, N ; Yes +000534 0668 3F84 0061 SUB 'a', R1 ; Now convert the LC char to UC +000535 066A 1F84 0041 ADD 'A', R1 +000536 066C 0101 MOVE R1, @R0 ; Store it back into the string +000537 066D 1F80 0001 _STR$TO_UPPER_NEXT ADD 0x001, R0 +000538 066F FFA0 FFEA RBRA _STR$TO_UPPER_LOOP, 1 ; Process next character +000539 0671 3FB8 0100 _STR$TO_UPPER_END SUB 0x0100, R14 ; Restore old register page +000540 0673 0DBC MOVE @R13++, R15 +000541 ; +000542 ;*************************************************************************************** +000543 ;* STR$LEN expects the address of a string in R8 and returns its length in R9 +000544 ;*************************************************************************************** +000545 ; +000546 0674 1FB8 0100 STR$LEN ADD 0x0100, R14 ; Get a new scratch register page +000547 0676 0800 MOVE R8, R0 ; Do not work with the original pointer +000548 0677 0FA4 FFFF MOVE 0xFFFF, R9 ; R9 = -1 +000549 0679 1FA4 0001 _STR$LEN_LOOP ADD 0x0001, R9 ; One character found +000550 067B 0084 MOVE @R0++, R1 ; Was it the terminating null word? +000551 067C FFAB FFFB RBRA _STR$LEN_LOOP, !Z ; No? +000552 067E 3FB8 0100 SUB 0x0100, R14 +000553 0680 0DBC MOVE @R13++, R15 +000554 ; +000555 ;*************************************************************************************** +000556 ;* STR$CHOMP removes a trailing LF/CR from a string pointed to by R8 +000557 ;*************************************************************************************** +000558 ; +000559 0681 1FB8 0100 STR$CHOMP ADD 0x0100, R14 ; Get a new register page +000560 0683 0800 MOVE R8, R0 ; Save the start address of the string +000561 0684 0904 MOVE R9, R1 ; R9 will be used later +000562 0685 0808 MOVE R8, R2 ; R2 will be used as a working pointer +000563 0686 FFB0 FFEC RSUB STR$LEN, 1 ; Determine the length of the string +000564 0688 0924 MOVE R9, R9 ; Is the string empty? +000565 0689 FFA3 0011 RBRA _STR$CHOMP_EXIT, Z ; Yes +000566 068B 1908 ADD R9, R2 ; R2 now points to the last string character +000567 068C 02CC MOVE @--R2, R3 ; Get a character +000568 068D 3F8C 000D SUB 0x000D, R3 ; Is it a CR (we are working from right!) +000569 068F FFAB 0004 RBRA _STR$CHOMP_1, !Z ; No, so nothing to do so far +000570 0691 0F89 0000 MOVE 0x0000, @R2 ; Yes, replace it with a null word +000571 0693 3F88 0001 SUB 0x0001, R2 ; Proceed to second last character +000572 0695 024C _STR$CHOMP_1 MOVE @R2, R3 ; Now test for a line feed +000573 0696 3F8C 000A SUB 0x000A, R3 +000574 0698 FFAB 0002 RBRA _STR$CHOMP_EXIT, !Z ; Nothing to do +000575 069A 0F89 0000 MOVE 0x0000, @R2 ; Replace the LF with a null word +000576 069C 0124 _STR$CHOMP_EXIT MOVE R1, R9 ; Restore R9 +000577 069D 3FB8 0100 SUB 0x0100, R14 ; Restore register bank +000578 069F 0DBC MOVE @R13++, R15 +000579 ; +000580 ;*************************************************************************************** +000581 ;* STR$CMP compares two strings +000582 ;* +000583 ;* R8: Pointer to the first string (S0), +000584 ;* R9: Pointer to the second string (S1), +000585 ;* +000586 ;* R10: negative if (S0 < S1), zero if (S0 == S1), positive if (S0 > S1) +000587 ; +000588 ;* The contents of R8 and R9 are being preserved during the run of this function +000589 ;*************************************************************************************** +000590 ; +000591 06A0 1FB8 0100 STR$CMP ADD 0x0100, R14 ; Get a new register page +000592 06A2 0800 MOVE R8, R0 ; Save arguments +000593 06A3 0904 MOVE R9, R1 +000594 06A4 0068 _STR$CMP_LOOP MOVE @R0, R10 ; while (*s1 == *s2++) +000595 06A5 0188 MOVE @R1++, R2 +000596 06A6 3A08 SUB R10, R2 +000597 06A7 FFAB 0005 RBRA _STR$CMP_END, !Z +000598 06A9 00A8 MOVE @R0++, R10 ; if (*s1++ == 0) +000599 06AA FFA3 0004 RBRA _STR$CMP_EXIT, Z ; return 0; +000600 06AC FFA0 FFF6 RBRA _STR$CMP_LOOP, 1 ; end-of-while-loop +000601 06AE 01C8 _STR$CMP_END MOVE @--R1, R2 ; return (*s1 - (--*s2)); +000602 06AF 3228 SUB R2, R10 +000603 06B0 3FB8 0100 _STR$CMP_EXIT SUB 0x0100, R14 ; Restore previous register page +000604 06B2 0DBC MOVE @R13++, R15 +000605 ; +000606 ;*************************************************************************************** +000607 ;* STR$STRCHR seaches for the first occurrence of the character stored in R8 in a +000608 ;* string pointed to by R9. +000609 ;* +000610 ;* R8: Pointer to the string +000611 ;* R9: Character to be searched +000612 ;* +000613 ;* R10: Zero if the character has not been found, otherwise it contains a pointer +000614 ;* to the first occurrence of the character in the string +000615 ; +000616 ;* The contents of R8 and R9 are being preserved during the run of this function +000617 ;*************************************************************************************** +000618 ; +000619 06B3 1FB8 0100 STR$STRCHR ADD 0x0100, R14 +000620 06B5 0900 MOVE R9, R0 +000621 06B6 BA28 XOR R10, R10 +000622 06B7 CF81 0000 _STR$STRCHR_LOOP CMP 0x0000, @R0 ; while (*string) +000623 06B9 FFA3 000A RBRA _STR$STRCHR_EXIT, Z +000624 06BB C801 CMP R8, @R0 ; if (*string == R8) +000625 06BC FFAB 0003 RBRA _STR$STRCHR_NEXT, !Z +000626 06BE 0028 MOVE R0, R10 +000627 06BF FFA0 0004 RBRA _STR$STRCHR_EXIT, 1 +000628 06C1 1F80 0001 _STR$STRCHR_NEXT ADD 0x0001, R0 ; string++ +000629 06C3 FFA0 FFF2 RBRA _STR$STRCHR_LOOP, 1 +000630 06C5 3FB8 0100 _STR$STRCHR_EXIT SUB 0x0100, R14 +000631 06C7 0DBC MOVE @R13++, R15 +000632 +000633 ; +000634 ;;============================================================================= +000635 ;; The collection of memory related functions starts here +000636 ;;============================================================================= +000637 ; +000638 ;****************************************************************************** +000639 ;* MEM$FILL fills a block of memory running from the address stored in R8. +000640 ;* R9 contains the number of words to be written. R10 contains the value to +000641 ;* be stored in the memory area. +000642 ;****************************************************************************** +000643 06C8 1FB8 0100 MEM$FILL ADD 0x0100, R14 +000644 06CA 0800 MOVE R8, R0 +000645 06CB 0904 MOVE R9, R1 +000646 06CC 0104 _MEM$FILL_LOOP MOVE R1, R1 ; Zero length left? +000647 06CD FFA3 0005 RBRA _MEM$FILL_EXIT, Z ; Yes, done... +000648 06CF 0A02 MOVE R10, @R0++ +000649 06D0 3F84 0001 SUB 0x0001, R1 +000650 06D2 FFA0 FFF8 RBRA _MEM$FILL_LOOP, 1 +000651 06D4 3FB8 0100 _MEM$FILL_EXIT SUB 0x0100, R14 +000652 06D6 0DBC MOVE @R13++, R15 +000653 ; +000654 ;****************************************************************************** +000655 ;* MEM$MOVE moves the memory area starting at the address contained in R8 +000656 ;* to the area starting at the address contained in R9. R10 contains the +000657 ;* number of words to be moved. +000658 ;****************************************************************************** +000659 ; +000660 06D7 1FB8 0100 MEM$MOVE ADD 0x0100, R14 +000661 06D9 0800 MOVE R8, R0 +000662 06DA 0904 MOVE R9, R1 +000663 06DB 0A08 MOVE R10, R2 +000664 06DC 0208 _MEM$MOVE_LOOP MOVE R2, R2 ; Zero length left? +000665 06DD FFA3 0005 RBRA _MEM$MOVE_EXIT, Z ; Yes, done... +000666 06DF 0086 MOVE @R0++, @R1++ +000667 06E0 3F88 0001 SUB 0x0001, R2 +000668 06E2 FFA0 FFF8 RBRA _MEM$MOVE_LOOP, 1 +000669 06E4 3FB8 0100 _MEM$MOVE_EXIT SUB 0x0100, R14 +000670 06E6 0DBC MOVE @R13++, R15 +000671 ;; +000672 06E7 E000 QMON$LAST_ADDR HALT +000673 + + +EQU-list: +-------------------------------------------------------------------------------------------------------- +IO$BASE : 0xFC00 IO$UART0_BASE : 0xFC00 IO$UART_SRA : 0x0001 +IO$UART_RHRA : 0x0003 IO$UART_THRA : 0x0003 + +Label-list: +-------------------------------------------------------------------------------------------------------- +QMON$COLDSTART : 0x0000 QMON$WARMSTART : 0x0012 QMON$MAIN_LOOP : 0x0018 +QMON$C_MAYBE_H : 0x0038 QMON$MAYBE_R : 0x0041 QMON$C_ILLEGAL : 0x004E +QMON$MAYBE_M : 0x0054 QMON$M_MAYBE_D : 0x007D QMON$M_MAYBE_E : 0x0098 +QMON$M_MAYBE_F : 0x00AE QMON$M_MAYBE_L : 0x00D2 _QMON$ML_LOOP : 0x00DA +QMON$M_MAYBE_M : 0x00E2 QMON$M_ILLEGAL : 0x0103 QMON$MAYBE_H : 0x0109 +QMON$NOT_H : 0x0113 QMON$WELCOME : 0x0119 QMON$PROMPT : 0x01A0 +QMON$ILLCMDGRP : 0x01A7 QMON$ILLCMD : 0x01C8 QMON$HELP : 0x01E3 +QMON$CG_C : 0x035A QMON$CG_C_C : 0x0362 QMON$CG_C_H : 0x036D +QMON$CG_C_R : 0x0376 QMON$CG_M : 0x0383 QMON$CG_M_C : 0x038A +QMON$CG_M_C1 : 0x039A QMON$CG_M_C2 : 0x03AA QMON$CG_M_D : 0x03B6 +QMON$CG_M_D2 : 0x03CA QMON$CG_M_E : 0x03D8 QMON$CG_M_F : 0x03E9 +QMON$CG_M_F2 : 0x03FD QMON$CG_M_F3 : 0x040B QMON$CG_M_L : 0x0413 +QMON$CG_M_M : 0x044D QMON$CG_M_M2 : 0x0458 QMON$CG_M_M3 : 0x045D +QMON$COMMAND : 0x0466 IO$DUMP_MEMORY : 0x0566 _IO$DUMP_MEMORY_LOOP : 0x056F +_IO$DUMP_MEMORY_CONTENT : 0x0582 _IO$DUMP_MEMORY_EXIT : 0x058B IO$GET_W_HEX : 0x0591 +_IO$GET_W_HEX_REDO : 0x0593 _IO$GET_W_HEX_INPUT : 0x0598 _IO$GET_W_HEX_VALID : 0x05AD +IO$PUT_W_HEX : 0x05BC _IO$PWH_SCAN : 0x05C4 _IO$PWH_PRINT : 0x05D1 +IO$GETCHAR : 0x05DC _IO$GETC_LOOP : 0x05E5 IO$GETS : 0x05F2 +_IO$GETS_LOOP : 0x05F6 IO$PUTS : 0x0607 _IO$PUTS_LOOP : 0x060B +_IO$PUTS_END : 0x0614 IO$PUT_CRLF : 0x0618 IO$PUTCHAR : 0x0627 +IO$HEX_NIBBLES : 0x0631 IO$COLON_DELIMITER : 0x0642 CHR$TO_UPPER : 0x0645 +_CHR$TO_UPPER_EXIT : 0x0655 STR$TO_UPPER : 0x0658 _STR$TO_UPPER_LOOP : 0x065B +_STR$TO_UPPER_NEXT : 0x066D _STR$TO_UPPER_END : 0x0671 STR$LEN : 0x0674 +_STR$LEN_LOOP : 0x0679 STR$CHOMP : 0x0681 _STR$CHOMP_1 : 0x0695 +_STR$CHOMP_EXIT : 0x069C STR$CMP : 0x06A0 _STR$CMP_LOOP : 0x06A4 +_STR$CMP_END : 0x06AE _STR$CMP_EXIT : 0x06B0 STR$STRCHR : 0x06B3 +_STR$STRCHR_LOOP : 0x06B7 _STR$STRCHR_NEXT : 0x06C1 _STR$STRCHR_EXIT : 0x06C5 +MEM$FILL : 0x06C8 _MEM$FILL_LOOP : 0x06CC _MEM$FILL_EXIT : 0x06D4 +MEM$MOVE : 0x06D7 _MEM$MOVE_LOOP : 0x06DC _MEM$MOVE_EXIT : 0x06E4 +QMON$LAST_ADDR : 0x06E7 diff --git a/monitor/monitor.out b/monitor/monitor.out new file mode 100644 index 00000000..18b6df47 --- /dev/null +++ b/monitor/monitor.out @@ -0,0 +1,1768 @@ +0x0000 0x0FB4 +0x0001 0xFC00 +0x0002 0x0FA0 +0x0003 0x0119 +0x0004 0xFFB0 +0x0005 0x0601 +0x0006 0x0FA0 +0x0007 0x06E7 +0x0008 0x1FA0 +0x0009 0x0001 +0x000A 0x0FA4 +0x000B 0xFC00 +0x000C 0x3824 +0x000D 0x3FA4 +0x000E 0x0001 +0x000F 0xBA28 +0x0010 0xFFB0 +0x0011 0x06B6 +0x0012 0x0FB4 +0x0013 0xFC00 +0x0014 0x9FB8 +0x0015 0x00FF +0x0016 0xFFB0 +0x0017 0x0600 +0x0018 0x0FA0 +0x0019 0x01A0 +0x001A 0xFFB0 +0x001B 0x05EB +0x001C 0xFFB0 +0x001D 0x05BE +0x001E 0xFFB0 +0x001F 0x0625 +0x0020 0xFFB0 +0x0021 0x0605 +0x0022 0xCFA0 +0x0023 0x0043 +0x0024 0xFFAB +0x0025 0x002E +0x0026 0x0FA0 +0x0027 0x035A +0x0028 0xFFB0 +0x0029 0x05DD +0x002A 0xFFB0 +0x002B 0x05B0 +0x002C 0xFFB0 +0x002D 0x0617 +0x002E 0xCFA0 +0x002F 0x0043 +0x0030 0xFFAB +0x0031 0x0006 +0x0032 0x0FA0 +0x0033 0x0362 +0x0034 0xFFB0 +0x0035 0x05D1 +0x0036 0xFFA0 +0x0037 0xFFC8 +0x0038 0xCFA0 +0x0039 0x0048 +0x003A 0xFFAB +0x003B 0x0005 +0x003C 0x0FA0 +0x003D 0x036D +0x003E 0xFFB0 +0x003F 0x05C7 +0x0040 0xE000 +0x0041 0xCFA0 +0x0042 0x0052 +0x0043 0xFFAB +0x0044 0x0009 +0x0045 0x0FA0 +0x0046 0x0376 +0x0047 0xFFB0 +0x0048 0x05BE +0x0049 0xFFB0 +0x004A 0x0546 +0x004B 0xFFB0 +0x004C 0x05CB +0x004D 0xF800 +0x004E 0x0FA0 +0x004F 0x01C8 +0x0050 0xFFB0 +0x0051 0x05B5 +0x0052 0xFFA0 +0x0053 0xFFC4 +0x0054 0xCFA0 +0x0055 0x004D +0x0056 0xFFAB +0x0057 0x00B1 +0x0058 0x0FA0 +0x0059 0x0383 +0x005A 0xFFB0 +0x005B 0x05AB +0x005C 0xFFB0 +0x005D 0x057E +0x005E 0xFFB0 +0x005F 0x05E5 +0x0060 0xCFA0 +0x0061 0x0043 +0x0062 0xFFAB +0x0063 0x0019 +0x0064 0x0FA0 +0x0065 0x038A +0x0066 0xFFB0 +0x0067 0x059F +0x0068 0xFFB0 +0x0069 0x0527 +0x006A 0x0800 +0x006B 0x0FA0 +0x006C 0x039A +0x006D 0xFFB0 +0x006E 0x0598 +0x006F 0x0060 +0x0070 0xFFB0 +0x0071 0x054A +0x0072 0x0FA0 +0x0073 0x03AA +0x0074 0xFFB0 +0x0075 0x0591 +0x0076 0xFFB0 +0x0077 0x0519 +0x0078 0x0801 +0x0079 0xFFB0 +0x007A 0x059D +0x007B 0xFFA0 +0x007C 0xFF9B +0x007D 0xCFA0 +0x007E 0x0044 +0x007F 0xFFAB +0x0080 0x0017 +0x0081 0x0FA0 +0x0082 0x03B6 +0x0083 0xFFB0 +0x0084 0x0582 +0x0085 0xFFB0 +0x0086 0x050A +0x0087 0x0800 +0x0088 0x0FA0 +0x0089 0x03CA +0x008A 0xFFB0 +0x008B 0x057B +0x008C 0xFFB0 +0x008D 0x0503 +0x008E 0xFFB0 +0x008F 0x0588 +0x0090 0x0824 +0x0091 0x0020 +0x0092 0xFFB0 +0x0093 0x04D2 +0x0094 0xFFB0 +0x0095 0x0582 +0x0096 0xFFA0 +0x0097 0xFF80 +0x0098 0xCFA0 +0x0099 0x0045 +0x009A 0xFFAB +0x009B 0x0012 +0x009C 0x0FA0 +0x009D 0x03D8 +0x009E 0xFFB0 +0x009F 0x0567 +0x00A0 0xFFB0 +0x00A1 0x04EF +0x00A2 0x0800 +0x00A3 0x0FA0 +0x00A4 0x0020 +0x00A5 0xFFB0 +0x00A6 0x0580 +0x00A7 0x0060 +0x00A8 0xFFB0 +0x00A9 0x0512 +0x00AA 0xFFB0 +0x00AB 0x056C +0x00AC 0xFFA0 +0x00AD 0xFF6A +0x00AE 0xCFA0 +0x00AF 0x0046 +0x00B0 0xFFAB +0x00B1 0x0020 +0x00B2 0x0FA0 +0x00B3 0x03E9 +0x00B4 0xFFB0 +0x00B5 0x0551 +0x00B6 0xFFB0 +0x00B7 0x04D9 +0x00B8 0x0800 +0x00B9 0x0FA0 +0x00BA 0x03FD +0x00BB 0xFFB0 +0x00BC 0x054A +0x00BD 0xFFB0 +0x00BE 0x04D2 +0x00BF 0x0804 +0x00C0 0x0FA0 +0x00C1 0x040B +0x00C2 0xFFB0 +0x00C3 0x0543 +0x00C4 0xFFB0 +0x00C5 0x04CB +0x00C6 0x0828 +0x00C7 0x0020 +0x00C8 0x3004 +0x00C9 0x1F84 +0x00CA 0x0001 +0x00CB 0x0124 +0x00CC 0xFFB0 +0x00CD 0x05FA +0x00CE 0xFFB0 +0x00CF 0x0548 +0x00D0 0xFFA0 +0x00D1 0xFF46 +0x00D2 0xCFA0 +0x00D3 0x004C +0x00D4 0xFFAB +0x00D5 0x000C +0x00D6 0x0FA0 +0x00D7 0x0413 +0x00D8 0xFFB0 +0x00D9 0x052D +0x00DA 0xFFB0 +0x00DB 0x04B5 +0x00DC 0x0800 +0x00DD 0xFFB0 +0x00DE 0x04B2 +0x00DF 0x0801 +0x00E0 0xFFA0 +0x00E1 0xFFF8 +0x00E2 0xCFA0 +0x00E3 0x004D +0x00E4 0xFFAB +0x00E5 0x001D +0x00E6 0x0FA0 +0x00E7 0x044D +0x00E8 0xFFB0 +0x00E9 0x051D +0x00EA 0xFFB0 +0x00EB 0x04A5 +0x00EC 0x0800 +0x00ED 0x0FA0 +0x00EE 0x0458 +0x00EF 0xFFB0 +0x00F0 0x0516 +0x00F1 0xFFB0 +0x00F2 0x049E +0x00F3 0x0804 +0x00F4 0x0FA0 +0x00F5 0x045D +0x00F6 0xFFB0 +0x00F7 0x050F +0x00F8 0xFFB0 +0x00F9 0x0497 +0x00FA 0x0828 +0x00FB 0x0020 +0x00FC 0x0124 +0x00FD 0xFFB0 +0x00FE 0x05D8 +0x00FF 0xFFB0 +0x0100 0x0517 +0x0101 0xFFA0 +0x0102 0xFF15 +0x0103 0x0FA0 +0x0104 0x01C8 +0x0105 0xFFB0 +0x0106 0x0500 +0x0107 0xFFA0 +0x0108 0xFF0F +0x0109 0xCFA0 +0x010A 0x0048 +0x010B 0xFFAB +0x010C 0x0006 +0x010D 0x0FA0 +0x010E 0x01E3 +0x010F 0xFFB0 +0x0110 0x04F6 +0x0111 0xFFA0 +0x0112 0xFF05 +0x0113 0x0FA0 +0x0114 0x01A7 +0x0115 0xFFB0 +0x0116 0x04F0 +0x0117 0xFFA0 +0x0118 0xFEFF +0x0119 0x000D +0x011A 0x000A +0x011B 0x000D +0x011C 0x000A +0x011D 0x0053 +0x011E 0x0069 +0x011F 0x006D +0x0120 0x0070 +0x0121 0x006C +0x0122 0x0065 +0x0123 0x0020 +0x0124 0x0051 +0x0125 0x004E +0x0126 0x0049 +0x0127 0x0043 +0x0128 0x0045 +0x0129 0x002D +0x012A 0x006D +0x012B 0x006F +0x012C 0x006E +0x012D 0x0069 +0x012E 0x0074 +0x012F 0x006F +0x0130 0x0072 +0x0131 0x0020 +0x0132 0x002D +0x0133 0x0020 +0x0134 0x0056 +0x0135 0x0065 +0x0136 0x0072 +0x0137 0x0073 +0x0138 0x0069 +0x0139 0x006F +0x013A 0x006E +0x013B 0x0020 +0x013C 0x0030 +0x013D 0x002E +0x013E 0x0032 +0x013F 0x0020 +0x0140 0x0028 +0x0141 0x0042 +0x0142 0x0065 +0x0143 0x0072 +0x0144 0x006E +0x0145 0x0064 +0x0146 0x0020 +0x0147 0x0055 +0x0148 0x006C +0x0149 0x006D +0x014A 0x0061 +0x014B 0x006E +0x014C 0x006E +0x014D 0x002C +0x014E 0x0020 +0x014F 0x0041 +0x0150 0x0075 +0x0151 0x0067 +0x0152 0x0075 +0x0153 0x0073 +0x0154 0x0074 +0x0155 0x0020 +0x0156 0x0032 +0x0157 0x0030 +0x0158 0x0031 +0x0159 0x0035 +0x015A 0x0029 +0x015B 0x000D +0x015C 0x000A +0x015D 0x002D +0x015E 0x002D +0x015F 0x002D +0x0160 0x002D +0x0161 0x002D +0x0162 0x002D +0x0163 0x002D +0x0164 0x002D +0x0165 0x002D +0x0166 0x002D +0x0167 0x002D +0x0168 0x002D +0x0169 0x002D +0x016A 0x002D +0x016B 0x002D +0x016C 0x002D +0x016D 0x002D +0x016E 0x002D +0x016F 0x002D +0x0170 0x002D +0x0171 0x002D +0x0172 0x002D +0x0173 0x002D +0x0174 0x002D +0x0175 0x002D +0x0176 0x002D +0x0177 0x002D +0x0178 0x002D +0x0179 0x002D +0x017A 0x002D +0x017B 0x002D +0x017C 0x002D +0x017D 0x002D +0x017E 0x002D +0x017F 0x002D +0x0180 0x002D +0x0181 0x002D +0x0182 0x002D +0x0183 0x002D +0x0184 0x002D +0x0185 0x002D +0x0186 0x002D +0x0187 0x002D +0x0188 0x002D +0x0189 0x002D +0x018A 0x002D +0x018B 0x002D +0x018C 0x002D +0x018D 0x002D +0x018E 0x002D +0x018F 0x002D +0x0190 0x002D +0x0191 0x002D +0x0192 0x002D +0x0193 0x002D +0x0194 0x002D +0x0195 0x002D +0x0196 0x002D +0x0197 0x002D +0x0198 0x002D +0x0199 0x002D +0x019A 0x002D +0x019B 0x000D +0x019C 0x000A +0x019D 0x000D +0x019E 0x000A +0x019F 0x0000 +0x01A0 0x0051 +0x01A1 0x004D +0x01A2 0x004F +0x01A3 0x004E +0x01A4 0x003E +0x01A5 0x0020 +0x01A6 0x0000 +0x01A7 0x0020 +0x01A8 0x002A +0x01A9 0x002A +0x01AA 0x002A +0x01AB 0x0020 +0x01AC 0x0049 +0x01AD 0x006C +0x01AE 0x006C +0x01AF 0x0065 +0x01B0 0x0067 +0x01B1 0x0061 +0x01B2 0x006C +0x01B3 0x0020 +0x01B4 0x0063 +0x01B5 0x006F +0x01B6 0x006D +0x01B7 0x006D +0x01B8 0x0061 +0x01B9 0x006E +0x01BA 0x0064 +0x01BB 0x0020 +0x01BC 0x0067 +0x01BD 0x0072 +0x01BE 0x006F +0x01BF 0x0075 +0x01C0 0x0070 +0x01C1 0x0020 +0x01C2 0x002A +0x01C3 0x002A +0x01C4 0x002A +0x01C5 0x000D +0x01C6 0x000A +0x01C7 0x0000 +0x01C8 0x0020 +0x01C9 0x002A +0x01CA 0x002A +0x01CB 0x002A +0x01CC 0x0020 +0x01CD 0x0049 +0x01CE 0x006C +0x01CF 0x006C +0x01D0 0x0065 +0x01D1 0x0067 +0x01D2 0x0061 +0x01D3 0x006C +0x01D4 0x0020 +0x01D5 0x0063 +0x01D6 0x006F +0x01D7 0x006D +0x01D8 0x006D +0x01D9 0x0061 +0x01DA 0x006E +0x01DB 0x0064 +0x01DC 0x0020 +0x01DD 0x002A +0x01DE 0x002A +0x01DF 0x002A +0x01E0 0x000D +0x01E1 0x000A +0x01E2 0x0000 +0x01E3 0x0045 +0x01E4 0x004C +0x01E5 0x0050 +0x01E6 0x003A +0x01E7 0x000D +0x01E8 0x000A +0x01E9 0x000D +0x01EA 0x000A +0x01EB 0x0020 +0x01EC 0x0020 +0x01ED 0x0020 +0x01EE 0x0020 +0x01EF 0x0043 +0x01F0 0x0028 +0x01F1 0x0063 +0x01F2 0x006F +0x01F3 0x006E +0x01F4 0x0074 +0x01F5 0x0072 +0x01F6 0x006F +0x01F7 0x006C +0x01F8 0x0020 +0x01F9 0x0067 +0x01FA 0x0072 +0x01FB 0x006F +0x01FC 0x0075 +0x01FD 0x0070 +0x01FE 0x0029 +0x01FF 0x003A +0x0200 0x000D +0x0201 0x000A +0x0202 0x0020 +0x0203 0x0020 +0x0204 0x0020 +0x0205 0x0020 +0x0206 0x0020 +0x0207 0x0020 +0x0208 0x0020 +0x0209 0x0020 +0x020A 0x0043 +0x020B 0x0028 +0x020C 0x006F +0x020D 0x006C +0x020E 0x0064 +0x020F 0x0020 +0x0210 0x0073 +0x0211 0x0074 +0x0212 0x0061 +0x0213 0x0072 +0x0214 0x0074 +0x0215 0x0029 +0x0216 0x0020 +0x0217 0x0048 +0x0218 0x0028 +0x0219 0x0061 +0x021A 0x006C +0x021B 0x0074 +0x021C 0x0029 +0x021D 0x0020 +0x021E 0x0052 +0x021F 0x0028 +0x0220 0x0075 +0x0221 0x006E +0x0222 0x0029 +0x0223 0x000D +0x0224 0x000A +0x0225 0x0020 +0x0226 0x0020 +0x0227 0x0020 +0x0228 0x0020 +0x0229 0x0048 +0x022A 0x0028 +0x022B 0x0065 +0x022C 0x006C +0x022D 0x0070 +0x022E 0x0029 +0x022F 0x000D +0x0230 0x000A +0x0231 0x0020 +0x0232 0x0020 +0x0233 0x0020 +0x0234 0x0020 +0x0235 0x004D +0x0236 0x0028 +0x0237 0x0065 +0x0238 0x006D +0x0239 0x006F +0x023A 0x0072 +0x023B 0x0079 +0x023C 0x0020 +0x023D 0x0067 +0x023E 0x0072 +0x023F 0x006F +0x0240 0x0075 +0x0241 0x0070 +0x0242 0x0029 +0x0243 0x003A +0x0244 0x000D +0x0245 0x000A +0x0246 0x0020 +0x0247 0x0020 +0x0248 0x0020 +0x0249 0x0020 +0x024A 0x0020 +0x024B 0x0020 +0x024C 0x0020 +0x024D 0x0020 +0x024E 0x0043 +0x024F 0x0028 +0x0250 0x0068 +0x0251 0x0061 +0x0252 0x006E +0x0253 0x0067 +0x0254 0x0065 +0x0255 0x0029 +0x0256 0x0020 +0x0257 0x0044 +0x0258 0x0028 +0x0259 0x0075 +0x025A 0x006D +0x025B 0x0070 +0x025C 0x0029 +0x025D 0x0020 +0x025E 0x0045 +0x025F 0x0028 +0x0260 0x0078 +0x0261 0x0061 +0x0262 0x006D +0x0263 0x0069 +0x0264 0x006E +0x0265 0x0065 +0x0266 0x0029 +0x0267 0x0020 +0x0268 0x0046 +0x0269 0x0028 +0x026A 0x0069 +0x026B 0x006C +0x026C 0x006C +0x026D 0x0029 +0x026E 0x0020 +0x026F 0x004C +0x0270 0x0028 +0x0271 0x006F +0x0272 0x0061 +0x0273 0x0064 +0x0274 0x0029 +0x0275 0x0020 +0x0276 0x004D +0x0277 0x0028 +0x0278 0x006F +0x0279 0x0076 +0x027A 0x0065 +0x027B 0x0029 +0x027C 0x000D +0x027D 0x000A +0x027E 0x000D +0x027F 0x000A +0x0280 0x0020 +0x0281 0x0020 +0x0282 0x0020 +0x0283 0x0020 +0x0284 0x0047 +0x0285 0x0065 +0x0286 0x006E +0x0287 0x0065 +0x0288 0x0072 +0x0289 0x0061 +0x028A 0x006C +0x028B 0x003A +0x028C 0x0020 +0x028D 0x0043 +0x028E 0x0054 +0x028F 0x0052 +0x0290 0x004C +0x0291 0x002D +0x0292 0x0045 +0x0293 0x0020 +0x0294 0x0070 +0x0295 0x0065 +0x0296 0x0072 +0x0297 0x0066 +0x0298 0x006F +0x0299 0x0072 +0x029A 0x006D +0x029B 0x0073 +0x029C 0x0020 +0x029D 0x0061 +0x029E 0x0020 +0x029F 0x0077 +0x02A0 0x0061 +0x02A1 0x0072 +0x02A2 0x006D +0x02A3 0x0020 +0x02A4 0x0073 +0x02A5 0x0074 +0x02A6 0x0061 +0x02A7 0x0072 +0x02A8 0x0074 +0x02A9 0x0020 +0x02AA 0x0077 +0x02AB 0x0068 +0x02AC 0x0065 +0x02AD 0x006E +0x02AE 0x0065 +0x02AF 0x0076 +0x02B0 0x0065 +0x02B1 0x0072 +0x02B2 0x0020 +0x02B3 0x0061 +0x02B4 0x006E +0x02B5 0x000D +0x02B6 0x000A +0x02B7 0x0020 +0x02B8 0x0020 +0x02B9 0x0020 +0x02BA 0x0020 +0x02BB 0x0020 +0x02BC 0x0020 +0x02BD 0x0020 +0x02BE 0x0020 +0x02BF 0x0069 +0x02C0 0x006E +0x02C1 0x0070 +0x02C2 0x0075 +0x02C3 0x0074 +0x02C4 0x0020 +0x02C5 0x0066 +0x02C6 0x0072 +0x02C7 0x006F +0x02C8 0x006D +0x02C9 0x0020 +0x02CA 0x006B +0x02CB 0x0065 +0x02CC 0x0079 +0x02CD 0x0062 +0x02CE 0x006F +0x02CF 0x0061 +0x02D0 0x0072 +0x02D1 0x0064 +0x02D2 0x0020 +0x02D3 0x0069 +0x02D4 0x0073 +0x02D5 0x0020 +0x02D6 0x0065 +0x02D7 0x0078 +0x02D8 0x0070 +0x02D9 0x0065 +0x02DA 0x0063 +0x02DB 0x0074 +0x02DC 0x0065 +0x02DD 0x0064 +0x02DE 0x002E +0x02DF 0x000D +0x02E0 0x000A +0x02E1 0x000D +0x02E2 0x000A +0x02E3 0x0020 +0x02E4 0x0020 +0x02E5 0x0020 +0x02E6 0x0020 +0x02E7 0x004D +0x02E8 0x0028 +0x02E9 0x0065 +0x02EA 0x006D +0x02EB 0x006F +0x02EC 0x0072 +0x02ED 0x0079 +0x02EE 0x0029 +0x02EF 0x004C +0x02F0 0x0028 +0x02F1 0x006F +0x02F2 0x0061 +0x02F3 0x0064 +0x02F4 0x0029 +0x02F5 0x0020 +0x02F6 0x0063 +0x02F7 0x0061 +0x02F8 0x006E +0x02F9 0x0020 +0x02FA 0x0062 +0x02FB 0x0065 +0x02FC 0x0020 +0x02FD 0x0075 +0x02FE 0x0073 +0x02FF 0x0065 +0x0300 0x0064 +0x0301 0x0020 +0x0302 0x0074 +0x0303 0x006F +0x0304 0x0020 +0x0305 0x006C +0x0306 0x006F +0x0307 0x0061 +0x0308 0x0064 +0x0309 0x0020 +0x030A 0x0061 +0x030B 0x0073 +0x030C 0x0073 +0x030D 0x0065 +0x030E 0x006D +0x030F 0x0062 +0x0310 0x006C +0x0311 0x0065 +0x0312 0x0072 +0x0313 0x0020 +0x0314 0x006F +0x0315 0x0075 +0x0316 0x0074 +0x0317 0x0070 +0x0318 0x0075 +0x0319 0x0074 +0x031A 0x000D +0x031B 0x000A +0x031C 0x0020 +0x031D 0x0020 +0x031E 0x0020 +0x031F 0x0020 +0x0320 0x0020 +0x0321 0x0020 +0x0322 0x0020 +0x0323 0x0020 +0x0324 0x0062 +0x0325 0x0079 +0x0326 0x0020 +0x0327 0x0070 +0x0328 0x0061 +0x0329 0x0073 +0x032A 0x0074 +0x032B 0x0069 +0x032C 0x006E +0x032D 0x0067 +0x032E 0x0020 +0x032F 0x0069 +0x0330 0x0074 +0x0331 0x0020 +0x0332 0x0074 +0x0333 0x006F +0x0334 0x0020 +0x0335 0x0074 +0x0336 0x0068 +0x0337 0x0065 +0x0338 0x0020 +0x0339 0x0074 +0x033A 0x0065 +0x033B 0x0072 +0x033C 0x006D +0x033D 0x0069 +0x033E 0x006E +0x033F 0x0061 +0x0340 0x006C +0x0341 0x002E +0x0342 0x0020 +0x0343 0x0043 +0x0344 0x0054 +0x0345 0x0052 +0x0346 0x004C +0x0347 0x002D +0x0348 0x0045 +0x0349 0x0020 +0x034A 0x0074 +0x034B 0x0065 +0x034C 0x0072 +0x034D 0x006D +0x034E 0x0069 +0x034F 0x006E +0x0350 0x0061 +0x0351 0x0074 +0x0352 0x0065 +0x0353 0x0073 +0x0354 0x002E +0x0355 0x000D +0x0356 0x000A +0x0357 0x000D +0x0358 0x000A +0x0359 0x0000 +0x035A 0x004F +0x035B 0x004E +0x035C 0x0054 +0x035D 0x0052 +0x035E 0x004F +0x035F 0x004C +0x0360 0x002F +0x0361 0x0000 +0x0362 0x0043 +0x0363 0x004F +0x0364 0x004C +0x0365 0x0044 +0x0366 0x0020 +0x0367 0x0053 +0x0368 0x0054 +0x0369 0x0041 +0x036A 0x0052 +0x036B 0x0054 +0x036C 0x0000 +0x036D 0x0048 +0x036E 0x0041 +0x036F 0x004C +0x0370 0x0054 +0x0371 0x000D +0x0372 0x000A +0x0373 0x000D +0x0374 0x000A +0x0375 0x0000 +0x0376 0x0052 +0x0377 0x0055 +0x0378 0x004E +0x0379 0x0020 +0x037A 0x0041 +0x037B 0x0044 +0x037C 0x0044 +0x037D 0x0052 +0x037E 0x0045 +0x037F 0x0053 +0x0380 0x0053 +0x0381 0x003D +0x0382 0x0000 +0x0383 0x0045 +0x0384 0x004D +0x0385 0x004F +0x0386 0x0052 +0x0387 0x0059 +0x0388 0x002F +0x0389 0x0000 +0x038A 0x0043 +0x038B 0x0048 +0x038C 0x0041 +0x038D 0x004E +0x038E 0x0047 +0x038F 0x0045 +0x0390 0x0020 +0x0391 0x0041 +0x0392 0x0044 +0x0393 0x0044 +0x0394 0x0052 +0x0395 0x0045 +0x0396 0x0053 +0x0397 0x0053 +0x0398 0x003D +0x0399 0x0000 +0x039A 0x0020 +0x039B 0x0043 +0x039C 0x0055 +0x039D 0x0052 +0x039E 0x0052 +0x039F 0x0045 +0x03A0 0x004E +0x03A1 0x0054 +0x03A2 0x0020 +0x03A3 0x0056 +0x03A4 0x0041 +0x03A5 0x004C +0x03A6 0x0055 +0x03A7 0x0045 +0x03A8 0x003D +0x03A9 0x0000 +0x03AA 0x0020 +0x03AB 0x004E +0x03AC 0x0045 +0x03AD 0x0057 +0x03AE 0x0020 +0x03AF 0x0056 +0x03B0 0x0041 +0x03B1 0x004C +0x03B2 0x0055 +0x03B3 0x0045 +0x03B4 0x003D +0x03B5 0x0000 +0x03B6 0x0044 +0x03B7 0x0055 +0x03B8 0x004D +0x03B9 0x0050 +0x03BA 0x0020 +0x03BB 0x0053 +0x03BC 0x0054 +0x03BD 0x0041 +0x03BE 0x0052 +0x03BF 0x0054 +0x03C0 0x0020 +0x03C1 0x0041 +0x03C2 0x0044 +0x03C3 0x0044 +0x03C4 0x0052 +0x03C5 0x0045 +0x03C6 0x0053 +0x03C7 0x0053 +0x03C8 0x003D +0x03C9 0x0000 +0x03CA 0x0020 +0x03CB 0x0045 +0x03CC 0x004E +0x03CD 0x0044 +0x03CE 0x0020 +0x03CF 0x0041 +0x03D0 0x0044 +0x03D1 0x0044 +0x03D2 0x0052 +0x03D3 0x0045 +0x03D4 0x0053 +0x03D5 0x0053 +0x03D6 0x003D +0x03D7 0x0000 +0x03D8 0x0045 +0x03D9 0x0058 +0x03DA 0x0041 +0x03DB 0x004D +0x03DC 0x0049 +0x03DD 0x004E +0x03DE 0x0045 +0x03DF 0x0020 +0x03E0 0x0041 +0x03E1 0x0044 +0x03E2 0x0044 +0x03E3 0x0052 +0x03E4 0x0045 +0x03E5 0x0053 +0x03E6 0x0053 +0x03E7 0x003D +0x03E8 0x0000 +0x03E9 0x0046 +0x03EA 0x0049 +0x03EB 0x004C +0x03EC 0x004C +0x03ED 0x0020 +0x03EE 0x0053 +0x03EF 0x0054 +0x03F0 0x0041 +0x03F1 0x0052 +0x03F2 0x0054 +0x03F3 0x0020 +0x03F4 0x0041 +0x03F5 0x0044 +0x03F6 0x0044 +0x03F7 0x0052 +0x03F8 0x0045 +0x03F9 0x0053 +0x03FA 0x0053 +0x03FB 0x003D +0x03FC 0x0000 +0x03FD 0x0020 +0x03FE 0x0045 +0x03FF 0x004E +0x0400 0x0044 +0x0401 0x0020 +0x0402 0x0041 +0x0403 0x0044 +0x0404 0x0044 +0x0405 0x0052 +0x0406 0x0045 +0x0407 0x0053 +0x0408 0x0053 +0x0409 0x003D +0x040A 0x0000 +0x040B 0x0020 +0x040C 0x0056 +0x040D 0x0041 +0x040E 0x004C +0x040F 0x0055 +0x0410 0x0045 +0x0411 0x003D +0x0412 0x0000 +0x0413 0x004C +0x0414 0x004F +0x0415 0x0041 +0x0416 0x0044 +0x0417 0x0020 +0x0418 0x002D +0x0419 0x0020 +0x041A 0x0045 +0x041B 0x004E +0x041C 0x0054 +0x041D 0x0045 +0x041E 0x0052 +0x041F 0x0020 +0x0420 0x0041 +0x0421 0x0044 +0x0422 0x0044 +0x0423 0x0052 +0x0424 0x0045 +0x0425 0x0053 +0x0426 0x0053 +0x0427 0x002F +0x0428 0x0056 +0x0429 0x0041 +0x042A 0x004C +0x042B 0x0055 +0x042C 0x0045 +0x042D 0x0020 +0x042E 0x0050 +0x042F 0x0041 +0x0430 0x0049 +0x0431 0x0052 +0x0432 0x0053 +0x0433 0x002C +0x0434 0x0020 +0x0435 0x0054 +0x0436 0x0045 +0x0437 0x0052 +0x0438 0x004D +0x0439 0x0049 +0x043A 0x004E +0x043B 0x0041 +0x043C 0x0054 +0x043D 0x0045 +0x043E 0x0020 +0x043F 0x0057 +0x0440 0x0049 +0x0441 0x0054 +0x0442 0x0048 +0x0443 0x0020 +0x0444 0x0043 +0x0445 0x0054 +0x0446 0x0052 +0x0447 0x004C +0x0448 0x002D +0x0449 0x0045 +0x044A 0x000D +0x044B 0x000A +0x044C 0x0000 +0x044D 0x004D +0x044E 0x004F +0x044F 0x0056 +0x0450 0x0045 +0x0451 0x0020 +0x0452 0x0046 +0x0453 0x0052 +0x0454 0x004F +0x0455 0x004D +0x0456 0x003D +0x0457 0x0000 +0x0458 0x0020 +0x0459 0x0054 +0x045A 0x004F +0x045B 0x003D +0x045C 0x0000 +0x045D 0x0020 +0x045E 0x004C +0x045F 0x0045 +0x0460 0x004E +0x0461 0x0047 +0x0462 0x0054 +0x0463 0x0048 +0x0464 0x003D +0x0465 0x0000 +0x0466 0x0000 +0x0467 0x0000 +0x0468 0x0000 +0x0469 0x0000 +0x046A 0x0000 +0x046B 0x0000 +0x046C 0x0000 +0x046D 0x0000 +0x046E 0x0000 +0x046F 0x0000 +0x0470 0x0000 +0x0471 0x0000 +0x0472 0x0000 +0x0473 0x0000 +0x0474 0x0000 +0x0475 0x0000 +0x0476 0x0000 +0x0477 0x0000 +0x0478 0x0000 +0x0479 0x0000 +0x047A 0x0000 +0x047B 0x0000 +0x047C 0x0000 +0x047D 0x0000 +0x047E 0x0000 +0x047F 0x0000 +0x0480 0x0000 +0x0481 0x0000 +0x0482 0x0000 +0x0483 0x0000 +0x0484 0x0000 +0x0485 0x0000 +0x0486 0x0000 +0x0487 0x0000 +0x0488 0x0000 +0x0489 0x0000 +0x048A 0x0000 +0x048B 0x0000 +0x048C 0x0000 +0x048D 0x0000 +0x048E 0x0000 +0x048F 0x0000 +0x0490 0x0000 +0x0491 0x0000 +0x0492 0x0000 +0x0493 0x0000 +0x0494 0x0000 +0x0495 0x0000 +0x0496 0x0000 +0x0497 0x0000 +0x0498 0x0000 +0x0499 0x0000 +0x049A 0x0000 +0x049B 0x0000 +0x049C 0x0000 +0x049D 0x0000 +0x049E 0x0000 +0x049F 0x0000 +0x04A0 0x0000 +0x04A1 0x0000 +0x04A2 0x0000 +0x04A3 0x0000 +0x04A4 0x0000 +0x04A5 0x0000 +0x04A6 0x0000 +0x04A7 0x0000 +0x04A8 0x0000 +0x04A9 0x0000 +0x04AA 0x0000 +0x04AB 0x0000 +0x04AC 0x0000 +0x04AD 0x0000 +0x04AE 0x0000 +0x04AF 0x0000 +0x04B0 0x0000 +0x04B1 0x0000 +0x04B2 0x0000 +0x04B3 0x0000 +0x04B4 0x0000 +0x04B5 0x0000 +0x04B6 0x0000 +0x04B7 0x0000 +0x04B8 0x0000 +0x04B9 0x0000 +0x04BA 0x0000 +0x04BB 0x0000 +0x04BC 0x0000 +0x04BD 0x0000 +0x04BE 0x0000 +0x04BF 0x0000 +0x04C0 0x0000 +0x04C1 0x0000 +0x04C2 0x0000 +0x04C3 0x0000 +0x04C4 0x0000 +0x04C5 0x0000 +0x04C6 0x0000 +0x04C7 0x0000 +0x04C8 0x0000 +0x04C9 0x0000 +0x04CA 0x0000 +0x04CB 0x0000 +0x04CC 0x0000 +0x04CD 0x0000 +0x04CE 0x0000 +0x04CF 0x0000 +0x04D0 0x0000 +0x04D1 0x0000 +0x04D2 0x0000 +0x04D3 0x0000 +0x04D4 0x0000 +0x04D5 0x0000 +0x04D6 0x0000 +0x04D7 0x0000 +0x04D8 0x0000 +0x04D9 0x0000 +0x04DA 0x0000 +0x04DB 0x0000 +0x04DC 0x0000 +0x04DD 0x0000 +0x04DE 0x0000 +0x04DF 0x0000 +0x04E0 0x0000 +0x04E1 0x0000 +0x04E2 0x0000 +0x04E3 0x0000 +0x04E4 0x0000 +0x04E5 0x0000 +0x04E6 0x0000 +0x04E7 0x0000 +0x04E8 0x0000 +0x04E9 0x0000 +0x04EA 0x0000 +0x04EB 0x0000 +0x04EC 0x0000 +0x04ED 0x0000 +0x04EE 0x0000 +0x04EF 0x0000 +0x04F0 0x0000 +0x04F1 0x0000 +0x04F2 0x0000 +0x04F3 0x0000 +0x04F4 0x0000 +0x04F5 0x0000 +0x04F6 0x0000 +0x04F7 0x0000 +0x04F8 0x0000 +0x04F9 0x0000 +0x04FA 0x0000 +0x04FB 0x0000 +0x04FC 0x0000 +0x04FD 0x0000 +0x04FE 0x0000 +0x04FF 0x0000 +0x0500 0x0000 +0x0501 0x0000 +0x0502 0x0000 +0x0503 0x0000 +0x0504 0x0000 +0x0505 0x0000 +0x0506 0x0000 +0x0507 0x0000 +0x0508 0x0000 +0x0509 0x0000 +0x050A 0x0000 +0x050B 0x0000 +0x050C 0x0000 +0x050D 0x0000 +0x050E 0x0000 +0x050F 0x0000 +0x0510 0x0000 +0x0511 0x0000 +0x0512 0x0000 +0x0513 0x0000 +0x0514 0x0000 +0x0515 0x0000 +0x0516 0x0000 +0x0517 0x0000 +0x0518 0x0000 +0x0519 0x0000 +0x051A 0x0000 +0x051B 0x0000 +0x051C 0x0000 +0x051D 0x0000 +0x051E 0x0000 +0x051F 0x0000 +0x0520 0x0000 +0x0521 0x0000 +0x0522 0x0000 +0x0523 0x0000 +0x0524 0x0000 +0x0525 0x0000 +0x0526 0x0000 +0x0527 0x0000 +0x0528 0x0000 +0x0529 0x0000 +0x052A 0x0000 +0x052B 0x0000 +0x052C 0x0000 +0x052D 0x0000 +0x052E 0x0000 +0x052F 0x0000 +0x0530 0x0000 +0x0531 0x0000 +0x0532 0x0000 +0x0533 0x0000 +0x0534 0x0000 +0x0535 0x0000 +0x0536 0x0000 +0x0537 0x0000 +0x0538 0x0000 +0x0539 0x0000 +0x053A 0x0000 +0x053B 0x0000 +0x053C 0x0000 +0x053D 0x0000 +0x053E 0x0000 +0x053F 0x0000 +0x0540 0x0000 +0x0541 0x0000 +0x0542 0x0000 +0x0543 0x0000 +0x0544 0x0000 +0x0545 0x0000 +0x0546 0x0000 +0x0547 0x0000 +0x0548 0x0000 +0x0549 0x0000 +0x054A 0x0000 +0x054B 0x0000 +0x054C 0x0000 +0x054D 0x0000 +0x054E 0x0000 +0x054F 0x0000 +0x0550 0x0000 +0x0551 0x0000 +0x0552 0x0000 +0x0553 0x0000 +0x0554 0x0000 +0x0555 0x0000 +0x0556 0x0000 +0x0557 0x0000 +0x0558 0x0000 +0x0559 0x0000 +0x055A 0x0000 +0x055B 0x0000 +0x055C 0x0000 +0x055D 0x0000 +0x055E 0x0000 +0x055F 0x0000 +0x0560 0x0000 +0x0561 0x0000 +0x0562 0x0000 +0x0563 0x0000 +0x0564 0x0000 +0x0565 0x0000 +0x0566 0x1FB8 +0x0567 0x0100 +0x0568 0x0800 +0x0569 0x0804 +0x056A 0x090C +0x056B 0x1F8C +0x056C 0x0001 +0x056D 0x0F90 +0x056E 0xFFFF +0x056F 0x0008 +0x0570 0x3308 +0x0571 0xFFA3 +0x0572 0x0018 +0x0573 0x1F90 +0x0574 0x0001 +0x0575 0x9F90 +0x0576 0x0007 +0x0577 0xFFAB +0x0578 0x0009 +0x0579 0xFFB0 +0x057A 0x009D +0x057B 0x0020 +0x057C 0xFFB0 +0x057D 0x003E +0x057E 0x0FA0 +0x057F 0x0642 +0x0580 0xFFB0 +0x0581 0x0085 +0x0582 0x00A0 +0x0583 0xFFB0 +0x0584 0x0037 +0x0585 0x0FA0 +0x0586 0x0020 +0x0587 0xFFB0 +0x0588 0x009E +0x0589 0xFFA0 +0x058A 0xFFE4 +0x058B 0xFFB0 +0x058C 0x008B +0x058D 0x0120 +0x058E 0x3FB8 +0x058F 0x0100 +0x0590 0x0DBC +0x0591 0x1FB8 +0x0592 0x0100 +0x0593 0xB000 +0x0594 0x0F84 +0x0595 0x0004 +0x0596 0x0FA4 +0x0597 0x0631 +0x0598 0xFFB0 +0x0599 0x0042 +0x059A 0xFFA3 +0x059B 0xFA76 +0x059C 0xFFB0 +0x059D 0x00A7 +0x059E 0xCFA0 +0x059F 0x0058 +0x05A0 0xFFA3 +0x05A1 0xFFF1 +0x05A2 0xFFB0 +0x05A3 0x010F +0x05A4 0x0A28 +0x05A5 0xFFAB +0x05A6 0x0006 +0x05A7 0x0FA0 +0x05A8 0x0007 +0x05A9 0xFFB0 +0x05AA 0x007C +0x05AB 0xFFA0 +0x05AC 0xFFEB +0x05AD 0xFFB0 +0x05AE 0x0078 +0x05AF 0x3FA8 +0x05B0 0x0631 +0x05B1 0x5F80 +0x05B2 0x0004 +0x05B3 0x1A00 +0x05B4 0x3F84 +0x05B5 0x0001 +0x05B6 0xFFAB +0x05B7 0xFFE0 +0x05B8 0x0020 +0x05B9 0x3FB8 +0x05BA 0x0100 +0x05BB 0x0DBC +0x05BC 0x1FB8 +0x05BD 0x0100 +0x05BE 0x0F80 +0x05BF 0x0004 +0x05C0 0x0010 +0x05C1 0x0814 +0x05C2 0x0F84 +0x05C3 0x0631 +0x05C4 0x0108 +0x05C5 0x080C +0x05C6 0x9F8C +0x05C7 0x000F +0x05C8 0x1308 +0x05C9 0x0277 +0x05CA 0x6FA0 +0x05CB 0x0004 +0x05CC 0x3F90 +0x05CD 0x0001 +0x05CE 0xFFAB +0x05CF 0xFFF4 +0x05D0 0x0010 +0x05D1 0x0DA0 +0x05D2 0xFFB0 +0x05D3 0x0053 +0x05D4 0x3F90 +0x05D5 0x0001 +0x05D6 0xFFAB +0x05D7 0xFFF9 +0x05D8 0x0520 +0x05D9 0x3FB8 +0x05DA 0x0100 +0x05DB 0x0DBC +0x05DC 0x1FB8 +0x05DD 0x0100 +0x05DE 0x0F80 +0x05DF 0xFC00 +0x05E0 0x0004 +0x05E1 0x1F80 +0x05E2 0x0001 +0x05E3 0x1F84 +0x05E4 0x0003 +0x05E5 0x0048 +0x05E6 0x9F88 +0x05E7 0x0001 +0x05E8 0xFFA3 +0x05E9 0xFFFB +0x05EA 0x0160 +0x05EB 0x3FB8 +0x05EC 0x0100 +0x05ED 0xCFA0 +0x05EE 0x0005 +0x05EF 0xFFA3 +0x05F0 0xFA21 +0x05F1 0x0DBC +0x05F2 0x1FB8 +0x05F3 0x0100 +0x05F4 0x0800 +0x05F5 0x0804 +0x05F6 0xFFB0 +0x05F7 0xFFE4 +0x05F8 0x0802 +0x05F9 0xFFB0 +0x05FA 0x002C +0x05FB 0x3FA0 +0x05FC 0x000A +0x05FD 0xFFAB +0x05FE 0xFFF7 +0x05FF 0x0F82 +0x0600 0x000D +0x0601 0x0F81 +0x0602 0x0000 +0x0603 0x0120 +0x0604 0x3FB8 +0x0605 0x0100 +0x0606 0x0DBC +0x0607 0x1FB8 +0x0608 0x0100 +0x0609 0x0804 +0x060A 0x0800 +0x060B 0x00A0 +0x060C 0x9FA0 +0x060D 0x00FF +0x060E 0xFFA3 +0x060F 0x0004 +0x0610 0xFFB0 +0x0611 0x0015 +0x0612 0xFFA0 +0x0613 0xFFF7 +0x0614 0x0120 +0x0615 0x3FB8 +0x0616 0x0100 +0x0617 0x0DBC +0x0618 0x1FB8 +0x0619 0x0100 +0x061A 0x0800 +0x061B 0x0FA0 +0x061C 0x000A +0x061D 0xFFB0 +0x061E 0x0008 +0x061F 0x0FA0 +0x0620 0x000D +0x0621 0xFFB0 +0x0622 0x0004 +0x0623 0x0020 +0x0624 0x3FB8 +0x0625 0x0100 +0x0626 0x0DBC +0x0627 0x1FB8 +0x0628 0x0100 +0x0629 0x0F80 +0x062A 0xFC00 +0x062B 0x1F80 +0x062C 0x0003 +0x062D 0x0801 +0x062E 0x3FB8 +0x062F 0x0100 +0x0630 0x0DBC +0x0631 0x0030 +0x0632 0x0031 +0x0633 0x0032 +0x0634 0x0033 +0x0635 0x0034 +0x0636 0x0035 +0x0637 0x0036 +0x0638 0x0037 +0x0639 0x0038 +0x063A 0x0039 +0x063B 0x0041 +0x063C 0x0042 +0x063D 0x0043 +0x063E 0x0044 +0x063F 0x0045 +0x0640 0x0046 +0x0641 0x0000 +0x0642 0x003A +0x0643 0x0020 +0x0644 0x0000 +0x0645 0x1FB8 +0x0646 0x0100 +0x0647 0x0800 +0x0648 0x3F80 +0x0649 0x0061 +0x064A 0xFFA4 +0x064B 0x0009 +0x064C 0x0F80 +0x064D 0x007A +0x064E 0x3800 +0x064F 0xFFA4 +0x0650 0x0004 +0x0651 0x3FA0 +0x0652 0x0061 +0x0653 0x1FA0 +0x0654 0x0041 +0x0655 0x3FB8 +0x0656 0x0100 +0x0657 0x0DBC +0x0658 0x1FB8 +0x0659 0x0100 +0x065A 0x0800 +0x065B 0x0044 +0x065C 0xFFA3 +0x065D 0x0013 +0x065E 0x0108 +0x065F 0x3F88 +0x0660 0x0061 +0x0661 0xFFA4 +0x0662 0x000A +0x0663 0x0F88 +0x0664 0x007A +0x0665 0x3108 +0x0666 0xFFA4 +0x0667 0x0005 +0x0668 0x3F84 +0x0669 0x0061 +0x066A 0x1F84 +0x066B 0x0041 +0x066C 0x0101 +0x066D 0x1F80 +0x066E 0x0001 +0x066F 0xFFA0 +0x0670 0xFFEA +0x0671 0x3FB8 +0x0672 0x0100 +0x0673 0x0DBC +0x0674 0x1FB8 +0x0675 0x0100 +0x0676 0x0800 +0x0677 0x0FA4 +0x0678 0xFFFF +0x0679 0x1FA4 +0x067A 0x0001 +0x067B 0x0084 +0x067C 0xFFAB +0x067D 0xFFFB +0x067E 0x3FB8 +0x067F 0x0100 +0x0680 0x0DBC +0x0681 0x1FB8 +0x0682 0x0100 +0x0683 0x0800 +0x0684 0x0904 +0x0685 0x0808 +0x0686 0xFFB0 +0x0687 0xFFEC +0x0688 0x0924 +0x0689 0xFFA3 +0x068A 0x0011 +0x068B 0x1908 +0x068C 0x02CC +0x068D 0x3F8C +0x068E 0x000D +0x068F 0xFFAB +0x0690 0x0004 +0x0691 0x0F89 +0x0692 0x0000 +0x0693 0x3F88 +0x0694 0x0001 +0x0695 0x024C +0x0696 0x3F8C +0x0697 0x000A +0x0698 0xFFAB +0x0699 0x0002 +0x069A 0x0F89 +0x069B 0x0000 +0x069C 0x0124 +0x069D 0x3FB8 +0x069E 0x0100 +0x069F 0x0DBC +0x06A0 0x1FB8 +0x06A1 0x0100 +0x06A2 0x0800 +0x06A3 0x0904 +0x06A4 0x0068 +0x06A5 0x0188 +0x06A6 0x3A08 +0x06A7 0xFFAB +0x06A8 0x0005 +0x06A9 0x00A8 +0x06AA 0xFFA3 +0x06AB 0x0004 +0x06AC 0xFFA0 +0x06AD 0xFFF6 +0x06AE 0x01C8 +0x06AF 0x3228 +0x06B0 0x3FB8 +0x06B1 0x0100 +0x06B2 0x0DBC +0x06B3 0x1FB8 +0x06B4 0x0100 +0x06B5 0x0900 +0x06B6 0xBA28 +0x06B7 0xCF81 +0x06B8 0x0000 +0x06B9 0xFFA3 +0x06BA 0x000A +0x06BB 0xC801 +0x06BC 0xFFAB +0x06BD 0x0003 +0x06BE 0x0028 +0x06BF 0xFFA0 +0x06C0 0x0004 +0x06C1 0x1F80 +0x06C2 0x0001 +0x06C3 0xFFA0 +0x06C4 0xFFF2 +0x06C5 0x3FB8 +0x06C6 0x0100 +0x06C7 0x0DBC +0x06C8 0x1FB8 +0x06C9 0x0100 +0x06CA 0x0800 +0x06CB 0x0904 +0x06CC 0x0104 +0x06CD 0xFFA3 +0x06CE 0x0005 +0x06CF 0x0A02 +0x06D0 0x3F84 +0x06D1 0x0001 +0x06D2 0xFFA0 +0x06D3 0xFFF8 +0x06D4 0x3FB8 +0x06D5 0x0100 +0x06D6 0x0DBC +0x06D7 0x1FB8 +0x06D8 0x0100 +0x06D9 0x0800 +0x06DA 0x0904 +0x06DB 0x0A08 +0x06DC 0x0208 +0x06DD 0xFFA3 +0x06DE 0x0005 +0x06DF 0x0086 +0x06E0 0x3F88 +0x06E1 0x0001 +0x06E2 0xFFA0 +0x06E3 0xFFF8 +0x06E4 0x3FB8 +0x06E5 0x0100 +0x06E6 0x0DBC +0x06E7 0xE000 diff --git a/monitor/qmon.asm b/monitor/qmon.asm new file mode 100644 index 00000000..b00a94b8 --- /dev/null +++ b/monitor/qmon.asm @@ -0,0 +1,247 @@ +;; +;; QMON - a simple monitor for the QNICE processor +;; +;; The labels and constants of each subsystem are prefixed with a short name denoting the particular +;; subsystem, followed by a dollar sign. Examples for this are IO$BASE or STR$STRMP etc. Labels +;; within a routine follow this prefix style but have an additional underscore following the dollar +;; sign to denote that these labels should normally not be the target of a branch or subroutine call +;; from outside code areas. +;; +;; B. Ulmann fecit +;; +;; 17-DEC-2007: Begin of coding +;; 03-AUG-2015: After upgrading the emulator and fixing some (serious) bugs the work on the +;; monitor continues +;; 06-AUG-2015: Basic monitor functions implemented +;; +;; Known bugs: +;; +;; Bits and pieces: +;; - All functions expect their input parameters in the registers R8, R9 and maybe R10. +;; - The result of a function is returned in the first non-used high numbered register, so if a +;; function expects its parameters in R8 and R9, it will return its result in R10. If it only +;; expects one parameter, the result will be +;; returned in R9 respectively. +;; - Every function name starts with its subsection name followed by a dollar sign, so all string +;; routines have names starting with "STR$". +;; - Labels within a function always have an underscore following the subsystem name, so a label +;; within the routine STR$CMP would have the form "STR$_CMP...". So never jump to a label of the +;; form "$_..." since this will be a label buried inside a function. +;; - Every subsystem (string routines, IO routines etc.) has its own constants which are always +;; located after the code for the routines. +;; - To assemble this monitor just call the "asm" shell script which will use the C preprocessor +;; to include the necessary library files. +;; + .ORG 0x0000 ; The monitor begins at address 0x0000, so the lower + ; address EPROMs should be mapped into memory by hardware + ; default on power up. +; +; Some useful constants +; + +; +; Main program +; +QMON$COLDSTART MOVE IO$BASE, SP ; Set up stack pointer + MOVE QMON$WELCOME, R8 ; Print welcome message + RSUB IO$PUTS, 1 + MOVE QMON$LAST_ADDR, R8 ; Clear memory after the monitor + ADD 0x0001, R8 ; Start address + MOVE IO$BASE, R9 ; Determine length of memory area + SUB R8, R9 ; to be cleared + SUB 0x0001, R9 ; We need one stack cell for the following call + XOR R10, R10 ; Clear with zero words + RSUB MEM$FILL, 1 ; Clear +;;TODO: Clear registers +QMON$WARMSTART MOVE IO$BASE, SP ; Set up stack pointer + AND 0x00FF, SR ; Reset register bank to zero + RSUB IO$PUT_CRLF, 1 +QMON$MAIN_LOOP MOVE QMON$PROMPT, R8 ; Print monitor prompt + RSUB IO$PUTS, 1 + RSUB IO$GETCHAR, 1 ; Wait for a key being pressed + RSUB CHR$TO_UPPER, 1 ; Convert it into an uppercase letter + RSUB IO$PUTCHAR, 1 ; Echo the character + CMP 'C', R8 ; Control group? + RBRA QMON$MAYBE_M, !Z ; No +; Control group + MOVE QMON$CG_C, R8 + RSUB IO$PUTS, 1 + RSUB IO$GETCHAR, 1 ; Get command character + RSUB CHR$TO_UPPER, 1 + CMP 'C', R8 ; Cold start? + RBRA QMON$C_MAYBE_H, !Z ; No... +; CONTROL/COLDSTART: + MOVE QMON$CG_C_C, R8 + RSUB IO$PUTS, 1 + RBRA QMON$COLDSTART, 1 ; Yes! +QMON$C_MAYBE_H CMP 'H', R8 ; Halt? + RBRA QMON$MAYBE_R, !Z +; CONTROL/HALT: + MOVE QMON$CG_C_H, R8 + RSUB IO$PUTS, 1 + HALT +QMON$MAYBE_R CMP 'R', R8 ; Run? + RBRA QMON$C_ILLEGAL, !Z ; No +; CONTROL/RUN: + MOVE QMON$CG_C_R, R8 + RSUB IO$PUTS, 1 + RSUB IO$GET_W_HEX, 1 ; Get address + RSUB IO$PUT_CRLF, 1 + ABRA R8, 1 ; Jump to address specified +QMON$C_ILLEGAL MOVE QMON$ILLCMD, R8 ; Control group C, illegal command + RSUB IO$PUTS, 1 + RBRA QMON$MAIN_LOOP, 1 +QMON$MAYBE_M CMP 'M', R8 ; Compare with 'M' + RBRA QMON$MAYBE_H, !Z ; No M, try next... +; Memory control group: + MOVE QMON$CG_M, R8 ; Print control group name + RSUB IO$PUTS, 1 + RSUB IO$GETCHAR, 1 ; Get command character + RSUB CHR$TO_UPPER, 1 ; ...convert it to upper case + CMP 'C', R8 ; 'Change'? + RBRA QMON$M_MAYBE_D, !Z +; MEMORY/CHANGE: + MOVE QMON$CG_M_C, R8 ; Print prompt for address + RSUB IO$PUTS, 1 + RSUB IO$GET_W_HEX, 1 ; Read in address + MOVE R8, R0 + MOVE QMON$CG_M_C1, R8 ; Prepare output of current value + RSUB IO$PUTS, 1 + MOVE @R0, R8 ; Get current value + RSUB IO$PUT_W_HEX, 1 ; Print current value + MOVE QMON$CG_M_C2, R8 ; Prompt for new value + RSUB IO$PUTS, 1 + RSUB IO$GET_W_HEX, 1 + MOVE R8, @R0 + RSUB IO$PUT_CRLF, 1 + RBRA QMON$MAIN_LOOP, 1 +QMON$M_MAYBE_D CMP 'D', R8 + RBRA QMON$M_MAYBE_E, !Z ; No D, try next... +; MEMORY/DUMP: + MOVE QMON$CG_M_D, R8 ; Print prompt for start address + RSUB IO$PUTS, 1 + RSUB IO$GET_W_HEX, 1 ; Get start address + MOVE R8, R0 ; Remember start address in R8 + MOVE QMON$CG_M_D2, R8 ; Print prompt for end address + RSUB IO$PUTS, 1 + RSUB IO$GET_W_HEX, 1 ; Get end address + RSUB IO$PUT_CRLF, 1 + MOVE R8, R9 + MOVE R0, R8 + RSUB IO$DUMP_MEMORY, 1 ; Dump memory contents + RSUB IO$PUT_CRLF, 1 + RBRA QMON$MAIN_LOOP, 1 +QMON$M_MAYBE_E CMP 'E', R8 ; Is it an 'E'? + RBRA QMON$M_MAYBE_F, !Z ; No... +; MEMORY/EXAMINE: + MOVE QMON$CG_M_E, R8 ; Print prompt for address + RSUB IO$PUTS, 1 + RSUB IO$GET_W_HEX, 1 ; Read address + MOVE R8, R0 + MOVE ' ', R8 + RSUB IO$PUTCHAR, 1 + MOVE @R0, R8 + RSUB IO$PUT_W_HEX, 1 + RSUB IO$PUT_CRLF, 1 + RBRA QMON$MAIN_LOOP, 1 +QMON$M_MAYBE_F CMP 'F', R8 + RBRA QMON$M_MAYBE_L, !Z +; MEMORY/FILL: + MOVE QMON$CG_M_F, R8 + RSUB IO$PUTS, 1 + RSUB IO$GET_W_HEX, 1 + MOVE R8, R0 + MOVE QMON$CG_M_F2, R8 + RSUB IO$PUTS, 1 + RSUB IO$GET_W_HEX, 1 + MOVE R8, R1 + MOVE QMON$CG_M_F3, R8 + RSUB IO$PUTS, 1 + RSUB IO$GET_W_HEX, 1 + MOVE R8, R10 + MOVE R0, R8 + SUB R0, R1 + ADD 0x0001, R1 + MOVE R1, R9 + RSUB MEM$FILL, 1 + RSUB IO$PUT_CRLF, 1 + RBRA QMON$MAIN_LOOP, 1 +QMON$M_MAYBE_L CMP 'L', R8 + RBRA QMON$M_MAYBE_M, !Z +; MEMORY/LOAD: + MOVE QMON$CG_M_L, R8 + RSUB IO$PUTS, 1 +_QMON$ML_LOOP RSUB IO$GET_W_HEX, 1 ; Get address + MOVE R8, R0 + RSUB IO$GET_W_HEX, 1 ; Get value + MOVE R8, @R0 + RBRA _QMON$ML_LOOP, 1 +QMON$M_MAYBE_M CMP 'M', R8 + RBRA QMON$M_ILLEGAL, !Z +; MEMORY/MOVE: + MOVE QMON$CG_M_M, R8 + RSUB IO$PUTS, 1 + RSUB IO$GET_W_HEX, 1 + MOVE R8, R0 + MOVE QMON$CG_M_M2, R8 + RSUB IO$PUTS, 1 + RSUB IO$GET_W_HEX, 1 + MOVE R8, R1 + MOVE QMON$CG_M_M3, R8 + RSUB IO$PUTS, 1 + RSUB IO$GET_W_HEX, 1 + MOVE R8, R10 + MOVE R0, R8 + MOVE R1, R9 + RSUB MEM$MOVE, 1 + RSUB IO$PUT_CRLF, 1 + RBRA QMON$MAIN_LOOP, 1 +QMON$M_ILLEGAL MOVE QMON$ILLCMD, R8 + RSUB IO$PUTS, 1 + RBRA QMON$MAIN_LOOP, 1 +QMON$MAYBE_H CMP 'H', R8 + RBRA QMON$NOT_H, !Z ; No H, try next... +; HELP: + MOVE QMON$HELP, R8 ; H(elp) - print help text + RSUB IO$PUTS, 1 + RBRA QMON$MAIN_LOOP, 1 +QMON$NOT_H MOVE QMON$ILLCMDGRP, R8A ; Illegal command group + RSUB IO$PUTS, 1 + RBRA QMON$MAIN_LOOP, 1 + +QMON$WELCOME .ASCII_P "\n\nSimple QNICE-monitor - Version 0.2 (Bernd Ulmann, August 2015)\n" + .ASCII_W "--------------------------------------------------------------\n\n" +QMON$PROMPT .ASCII_W "QMON> " +QMON$ILLCMDGRP .ASCII_W " *** Illegal command group ***\n" +QMON$ILLCMD .ASCII_W " *** Illegal command ***\n" +QMON$HELP .ASCII_P "ELP:\n\n" + .ASCII_P " C(control group):\n" + .ASCII_P " C(old start) H(alt) R(un)\n" + .ASCII_P " H(elp)\n" + .ASCII_P " M(emory group):\n" + .ASCII_P " C(hange) D(ump) E(xamine) F(ill) L(oad) M(ove)\n" + .ASCII_P "\n General: CTRL-E performs a warm start whenever an\n" + .ASCII_P " input from keyboard is expected.\n" + .ASCII_P "\n M(emory)L(oad) can be used to load assembler output\n" + .ASCII_P " by pasting it to the terminal. CTRL-E terminates.\n" + .ASCII_W "\n" +QMON$CG_C .ASCII_W "ONTROL/" +QMON$CG_C_C .ASCII_W "COLD START" +QMON$CG_C_H .ASCII_W "HALT\n\n" +QMON$CG_C_R .ASCII_W "RUN ADDRESS=" +QMON$CG_M .ASCII_W "EMORY/" +QMON$CG_M_C .ASCII_W "CHANGE ADDRESS=" +QMON$CG_M_C1 .ASCII_W " CURRENT VALUE=" +QMON$CG_M_C2 .ASCII_W " NEW VALUE=" +QMON$CG_M_D .ASCII_W "DUMP START ADDRESS=" +QMON$CG_M_D2 .ASCII_W " END ADDRESS=" +QMON$CG_M_E .ASCII_W "EXAMINE ADDRESS=" +QMON$CG_M_F .ASCII_W "FILL START ADDRESS=" +QMON$CG_M_F2 .ASCII_W " END ADDRESS=" +QMON$CG_M_F3 .ASCII_W " VALUE=" +QMON$CG_M_L .ASCII_W "LOAD - ENTER ADDRESS/VALUE PAIRS, TERMINATE WITH CTRL-E\n" +QMON$CG_M_M .ASCII_W "MOVE FROM=" +QMON$CG_M_M2 .ASCII_W " TO=" +QMON$CG_M_M3 .ASCII_W " LENGTH=" +; +QMON$COMMAND .BLOCK 0x0100 ; Reserve some memory for holding a command line diff --git a/monitor/string_library.asm b/monitor/string_library.asm new file mode 100644 index 00000000..ea2383bc --- /dev/null +++ b/monitor/string_library.asm @@ -0,0 +1,133 @@ +; +;;======================================================================================= +;; The collection of string related functions starts here +;;======================================================================================= +; +;*************************************************************************************** +;* CHR$TO_UPPER expects a character to be converted to upper case in R8 +;*************************************************************************************** +; +CHR$TO_UPPER INCRB + MOVE R8, R0 ; Save character + SUB 'a', R0 ; Less than 'a'? + RBRA _CHR$TO_UPPER_EXIT, N ; Yes - nothing to do + MOVE 'z', R0 ; Check if greater than 'z' + SUB R8, R0 + RBRA _CHR$TO_UPPER_EXIT, N ; Yes - nothing to do + SUB 'a', R8 ; Perform the conversion + ADD 'A', R8 +_CHR$TO_UPPER_EXIT DECRB + RET +; +;*************************************************************************************** +;* STR$TO_UPPER expects the address of a string to be converted to upper case in R8 +;*************************************************************************************** +; +STR$TO_UPPER INCRB ; Get a new scratch register page + MOVE R8, R0 ; Do not destroy parameters +_STR$TO_UPPER_LOOP MOVE @R0, R1 ; Null terminator found? + RBRA _STR$TO_UPPER_END, Z ; Yes - that is it + MOVE R1, R2 + SUB 'a', R2 ; Less than 'a'? + RBRA _STR$TO_UPPER_NEXT, N ; Yes + MOVE 'z', R2 ; Greater than 'z'? + SUB R1, R2 + RBRA _STR$TO_UPPER_NEXT, N ; Yes + SUB 'a', R1 ; Now convert the LC char to UC + ADD 'A', R1 + MOVE R1, @R0 ; Store it back into the string +_STR$TO_UPPER_NEXT ADD 0x001, R0 + RBRA _STR$TO_UPPER_LOOP, 1 ; Process next character +_STR$TO_UPPER_END DECRB ; Restore old register page + RET +; +;*************************************************************************************** +;* STR$LEN expects the address of a string in R8 and returns its length in R9 +;*************************************************************************************** +; +STR$LEN INCRB ; Get a new scratch register page + MOVE R8, R0 ; Do not work with the original pointer + MOVE 0xFFFF, R9 ; R9 = -1 +_STR$LEN_LOOP ADD 0x0001, R9 ; One character found + MOVE @R0++, R1 ; Was it the terminating null word? + RBRA _STR$LEN_LOOP, !Z ; No? + DECRB + RET +; +;*************************************************************************************** +;* STR$CHOMP removes a trailing LF/CR from a string pointed to by R8 +;*************************************************************************************** +; +STR$CHOMP INCRB ; Get a new register page + MOVE R8, R0 ; Save the start address of the string + MOVE R9, R1 ; R9 will be used later + MOVE R8, R2 ; R2 will be used as a working pointer + RSUB STR$LEN, 1 ; Determine the length of the string + MOVE R9, R9 ; Is the string empty? + RBRA _STR$CHOMP_EXIT, Z ; Yes + ADD R9, R2 ; R2 now points to the last string character + MOVE @--R2, R3 ; Get a character + SUB 0x000D, R3 ; Is it a CR (we are working from right!) + RBRA _STR$CHOMP_1, !Z ; No, so nothing to do so far + MOVE 0x0000, @R2 ; Yes, replace it with a null word + SUB 0x0001, R2 ; Proceed to second last character +_STR$CHOMP_1 MOVE @R2, R3 ; Now test for a line feed + SUB 0x000A, R3 + RBRA _STR$CHOMP_EXIT, !Z ; Nothing to do + MOVE 0x0000, @R2 ; Replace the LF with a null word +_STR$CHOMP_EXIT MOVE R1, R9 ; Restore R9 + DECRB ; Restore register bank + RET +; +;*************************************************************************************** +;* STR$CMP compares two strings +;* +;* R8: Pointer to the first string (S0), +;* R9: Pointer to the second string (S1), +;* +;* R10: negative if (S0 < S1), zero if (S0 == S1), positive if (S0 > S1) +; +;* The contents of R8 and R9 are being preserved during the run of this function +;*************************************************************************************** +; +STR$CMP INCRB ; Get a new register page + MOVE R8, R0 ; Save arguments + MOVE R9, R1 +_STR$CMP_LOOP MOVE @R0, R10 ; while (*s1 == *s2++) + MOVE @R1++, R2 + SUB R10, R2 + RBRA _STR$CMP_END, !Z + MOVE @R0++, R10 ; if (*s1++ == 0) + RBRA _STR$CMP_EXIT, Z ; return 0; + RBRA _STR$CMP_LOOP, 1 ; end-of-while-loop +_STR$CMP_END MOVE @--R1, R2 ; return (*s1 - (--*s2)); + SUB R2, R10 +_STR$CMP_EXIT DECRB ; Restore previous register page + RET +; +;*************************************************************************************** +;* STR$STRCHR seaches for the first occurrence of the character stored in R8 in a +;* string pointed to by R9. +;* +;* R8: Pointer to the string +;* R9: Character to be searched +;* +;* R10: Zero if the character has not been found, otherwise it contains a pointer +;* to the first occurrence of the character in the string +; +;* The contents of R8 and R9 are being preserved during the run of this function +;*************************************************************************************** +; +STR$STRCHR INCRB + MOVE R9, R0 + XOR R10, R10 +_STR$STRCHR_LOOP CMP 0x0000, @R0 ; while (*string) + RBRA _STR$STRCHR_EXIT, Z + CMP R8, @R0 ; if (*string == R8) + RBRA _STR$STRCHR_NEXT, !Z + MOVE R0, R10 + RBRA _STR$STRCHR_EXIT, 1 +_STR$STRCHR_NEXT ADD 0x0001, R0 ; string++ + RBRA _STR$STRCHR_LOOP, 1 +_STR$STRCHR_EXIT DECRB + RET diff --git a/monitor/sysdef.asm b/monitor/sysdef.asm new file mode 100644 index 00000000..019e8d47 --- /dev/null +++ b/monitor/sysdef.asm @@ -0,0 +1,31 @@ +; +; This file contains the necessary definitions for the simple QNICE-monitor. +; + +; +; Some assembler macros which make life much easier: +; +#define RET MOVE @R13++, R15 +#define INCRB ADD 0x0100, R14 +#define DECRB SUB 0x0100, R14 + +; +; Some register short names: +; +#define PC R15 +#define SR R14 +#define SP R13 + +; +; IO-page addresses: +; +IO$BASE .EQU 0xFC00 +IO$UART0_BASE .EQU 0xFC00 + +; +; UART-registers: +; +IO$UART_SRA .EQU 0x0001 ; Status register (relative to base address) +IO$UART_RHRA .EQU 0x0003 ; Receiving register (relative to base address) +IO$UART_THRA .EQU 0x0003 ; Transmitting register (relative to base address) + diff --git a/test_programs/bram.asm b/test_programs/bram.asm index 7fe3468b..c5f95ae2 100644 --- a/test_programs/bram.asm +++ b/test_programs/bram.asm @@ -1,13 +1,47 @@ -; simple Block RAM test done by sy2002 in August 2015 +; Block RAM test that includes running an application from RAM +; done by sy2002 in August 2015 IO$TIL_BASE .EQU 0xFF10 ; address of TIL-display RAM_VARIABLE .EQU 0x8000 ; address of a variable in RAM +STACK_TOP .EQU 0x8010 ; top of the stack +EXE_START .EQU 0x8011 ; start address of code in RAM - MOVE IO$TIL_BASE, R12 - MOVE RAM_VARIABLE, R0 + .ORG 0x0000 + + ; copy source code to RAM to execute it there + ; this tests multiple things, also, if relative jumps + ; are really working and if opcode fetches also work in RAM + + MOVE CODE_END, R0 ; end of "to-be-copied-code" + MOVE CODE_START, R1 ; run variable for copying: source + MOVE EXE_START, R2 ; run variable for copying: dest + MOVE 1, R3 ; we need to subtract 1 often + SUB R1, R0 ; how many bytes to copy - 1 + ; as the last opcode 2 bytes + +COPY_CODE MOVE @R1++, @R2++ ; copy from src to dst + SUB R3, R0 ; one less item to go + RBRA COPY_CODE, !N ; R0 is #bytes-1, so check for !N + ; instead of checking for !Z + + ABRA EXE_START, 1 ; execute code from RAM + + ; this is the test code that tests BRAM operations + ; and the stack and sub routine calls + ; it should show 0x2309 on the TIL on success + +CODE_START MOVE IO$TIL_BASE, R12 ; TIL display address + MOVE RAM_VARIABLE, R0 ; address of a variable in RAM + MOVE STACK_TOP, R13 ; setup stack pointer + + MOVE 0x22EE, @R0 ; write 0x22EE to variable in BRAM + RSUB ADD_IT, 1 ; use a sub routine to add 0x09 + RSUB ADD_IT, 1 ; ... multiple times ... + RSUB ADD_IT, 1 ; ... to the variable in BRAM - MOVE 0x2300, @R0 ; write 0x2300 to BRAM's 0x8000 - ADD 0x0009, @R0 ; add 9 to BRAM's 0x8000 MOVE @R0, @R12 ; display 0x2309 on the TIL HALT + +ADD_IT ADD 0x0009, @R0 ; add 9 to BRAM's 0x8000 +CODE_END MOVE @R13++, R15 ; return from sub routine \ No newline at end of file diff --git a/test_programs/bram.lis b/test_programs/bram.lis index d014fa0e..3c6a9a76 100644 --- a/test_programs/bram.lis +++ b/test_programs/bram.lis @@ -1,21 +1,58 @@ -000001 ; simple Block RAM test done by sy2002 in August 2015 -000002 -000003 IO$TIL_BASE .EQU 0xFF10 ; address of TIL-display -000004 RAM_VARIABLE .EQU 0x8000 ; address of a variable in RAM -000005 -000006 0000 0FB0 FF10 MOVE IO$TIL_BASE, R12 -000007 0002 0F80 8000 MOVE RAM_VARIABLE, R0 +000001 ; Block RAM test that includes running an application from RAM +000002 ; done by sy2002 in August 2015 +000003 +000004 IO$TIL_BASE .EQU 0xFF10 ; address of TIL-display +000005 RAM_VARIABLE .EQU 0x8000 ; address of a variable in RAM +000006 STACK_TOP .EQU 0x8010 ; top of the stack +000007 EXE_START .EQU 0x8011 ; start address of code in RAM 000008 -000009 0004 0F81 2300 MOVE 0x2300, @R0 ; write 0x2300 to BRAM's 0x8000 -000010 0006 1F81 0009 ADD 0x0009, @R0 ; add 9 to BRAM's 0x8000 -000011 0008 0071 MOVE @R0, @R12 ; display 0x2309 on the TIL -000012 -000013 0009 E000 HALT +000009 .ORG 0x0000 +000010 +000011 ; copy source code to RAM to execute it there +000012 ; this tests multiple things, also, if relative jumps +000013 ; are really working and if opcode fetches also work in RAM +000014 +000015 0000 0F80 0021 MOVE CODE_END, R0 ; end of "to-be-copied-code" +000016 0002 0F84 000F MOVE CODE_START, R1 ; run variable for copying: source +000017 0004 0F88 8011 MOVE EXE_START, R2 ; run variable for copying: dest +000018 0006 0F8C 0001 MOVE 1, R3 ; we need to subtract 1 often +000019 0008 3100 SUB R1, R0 ; how many bytes to copy - 1 +000020 ; as the last opcode 2 bytes +000021 +000022 0009 018A COPY_CODE MOVE @R1++, @R2++ ; copy from src to dst +000023 000A 3300 SUB R3, R0 ; one less item to go +000024 000B FFAC FFFC RBRA COPY_CODE, !N ; R0 is #bytes-1, so check for !N +000025 ; instead of checking for !Z +000026 +000027 000D FF80 8011 ABRA EXE_START, 1 ; execute code from RAM +000028 +000029 ; this is the test code that tests BRAM operations +000030 ; and the stack and sub routine calls +000031 ; it should show 0x2309 on the TIL on success +000032 +000033 000F 0FB0 FF10 CODE_START MOVE IO$TIL_BASE, R12 ; TIL display address +000034 0011 0F80 8000 MOVE RAM_VARIABLE, R0 ; address of a variable in RAM +000035 0013 0FB4 8010 MOVE STACK_TOP, R13 ; setup stack pointer +000036 +000037 0015 0F81 22EE MOVE 0x22EE, @R0 ; write 0x22EE to variable in BRAM +000038 0017 FFB0 0006 RSUB ADD_IT, 1 ; use a sub routine to add 0x09 +000039 0019 FFB0 0004 RSUB ADD_IT, 1 ; ... multiple times ... +000040 001B FFB0 0002 RSUB ADD_IT, 1 ; ... to the variable in BRAM +000041 +000042 001D 0071 MOVE @R0, @R12 ; display 0x2309 on the TIL +000043 +000044 001E E000 HALT +000045 +000046 001F 1F81 0009 ADD_IT ADD 0x0009, @R0 ; add 9 to BRAM's 0x8000 +000047 0021 0DBC CODE_END MOVE @R13++, R15 ; return from sub routine EQU-list: -------------------------------------------------------------------------------------------------------- -IO$TIL_BASE : 0xFF10 RAM_VARIABLE : 0x8000 +IO$TIL_BASE : 0xFF10 RAM_VARIABLE : 0x8000 STACK_TOP : 0x8010 +EXE_START : 0x8011 Label-list: -------------------------------------------------------------------------------------------------------- +COPY_CODE : 0x0009 CODE_START : 0x000F ADD_IT : 0x001F +CODE_END : 0x0021 diff --git a/test_programs/bram.out b/test_programs/bram.out index 2ee1832a..0470e389 100644 --- a/test_programs/bram.out +++ b/test_programs/bram.out @@ -1,10 +1,34 @@ -0x0000 0x0FB0 -0x0001 0xFF10 -0x0002 0x0F80 -0x0003 0x8000 -0x0004 0x0F81 -0x0005 0x2300 -0x0006 0x1F81 -0x0007 0x0009 -0x0008 0x0071 -0x0009 0xE000 +0x0000 0x0F80 +0x0001 0x0021 +0x0002 0x0F84 +0x0003 0x000F +0x0004 0x0F88 +0x0005 0x8011 +0x0006 0x0F8C +0x0007 0x0001 +0x0008 0x3100 +0x0009 0x018A +0x000A 0x3300 +0x000B 0xFFAC +0x000C 0xFFFC +0x000D 0xFF80 +0x000E 0x8011 +0x000F 0x0FB0 +0x0010 0xFF10 +0x0011 0x0F80 +0x0012 0x8000 +0x0013 0x0FB4 +0x0014 0x8010 +0x0015 0x0F81 +0x0016 0x22EE +0x0017 0xFFB0 +0x0018 0x0006 +0x0019 0xFFB0 +0x001A 0x0004 +0x001B 0xFFB0 +0x001C 0x0002 +0x001D 0x0071 +0x001E 0xE000 +0x001F 0x1F81 +0x0020 0x0009 +0x0021 0x0DBC diff --git a/test_programs/bram.rom b/test_programs/bram.rom index 2d9bd1ee..eebf5342 100644 --- a/test_programs/bram.rom +++ b/test_programs/bram.rom @@ -1,10 +1,34 @@ +0000111110000000 +0000000000100001 +0000111110000100 +0000000000001111 +0000111110001000 +1000000000010001 +0000111110001100 +0000000000000001 +0011000100000000 +0000000110001010 +0011001100000000 +1111111110101100 +1111111111111100 +1111111110000000 +1000000000010001 0000111110110000 1111111100010000 0000111110000000 1000000000000000 +0000111110110100 +1000000000010000 0000111110000001 -0010001100000000 -0001111110000001 -0000000000001001 +0010001011101110 +1111111110110000 +0000000000000110 +1111111110110000 +0000000000000100 +1111111110110000 +0000000000000010 0000000001110001 1110000000000000 +0001111110000001 +0000000000001001 +0000110110111100 diff --git a/test_programs/moves.asm b/test_programs/moves.asm index c133dd6c..9201cef5 100644 --- a/test_programs/moves.asm +++ b/test_programs/moves.asm @@ -1,5 +1,8 @@ ; MOVE and register unit tests including bank switching and some flag tests +; by sy2002, July/August 2015 + .ORG 0x0000 ; Start address + ; in both cases, R14 should be 0x0009, i.e. Z and 1 flag set ; as you cannot clear the status register MOVE 0x0000, R0 ; first try using a register to register operation, so lead R0 ... diff --git a/test_programs/moves.lis b/test_programs/moves.lis deleted file mode 100644 index 12d4412c..00000000 --- a/test_programs/moves.lis +++ /dev/null @@ -1,66 +0,0 @@ -000001 ; MOVE and register unit tests including bank switching and some flag tests -000002 -000003 ; in both cases, R14 should be 0x0009, i.e. Z and 1 flag set -000004 ; as you cannot clear the status register -000005 0000 0F80 0000 MOVE 0x0000, R0 ; first try using a register to register operation, so lead R0 ... -000006 0002 0038 MOVE R0, R14 ; ... and store it to R14 -000007 -000008 0003 0F84 0001 MOVE 1, R1 ; dummy operation to clear the Z flag, i.e. SR should be 1 right now -000009 -000010 N32768 .EQU 0x8000 ; 2-complement of -32768 as the assembler is not supporting negatives -000011 N2 .EQU 0xFFFE ; 2-complement of -2 -000012 -000013 0005 0F88 FFFF MOVE 0xFFFF, R2 ; dummy operation to set the X flag and N flag, i.e. SR should be 0x13 right now -000014 0007 0F8C 8000 MOVE N32768, R3 ; prepare overflow, N flag is set, SR should be 0x11 now -000015 0009 0F90 FFFE MOVE N2, R4 ; second component for overflow, SR stays 0x11 -000016 000B 1310 ADD R3, R4 ; R4 should be 0x7FFE now and the C and V flag should be set, i.e. SR should be 0x25 -000017 -000018 000C 0FB8 0000 MOVE 0x0000, R14 ; second try use a @R15++ operation, SR should be 9 afterwards -000019 -000020 -000021 ; check memory reading and register bank switching -000022 000E FF80 0014 ABRA NEXT1, 1 -000023 -000024 BANK .EQU 0x0100 ; this adds 1 to the upper 8 bit, i.e. can be used for bank switching -000025 -000026 0010 0041 DATA .ASCII_W "ABC" - 0011 0042 - 0012 0043 - 0013 0000 -000027 -000028 0014 0FA8 0010 NEXT1 MOVE DATA, R10 ; upper register bank -000029 -000030 0016 1FB8 0100 ADD BANK, R14 ; next register bank via @R15++ operation -000031 0018 0F80 0001 MOVE 1, R0 -000032 001A 0F84 0002 MOVE 2, R1 -000033 001C 0F88 0003 MOVE 3, R2 ; after this, (R0, R1, R2) must be (1, 2, 3) in bank #1 -000034 -000035 001E 0FAC 0100 MOVE BANK, R11 ; next register bank via register to register operation -000036 0020 1B38 ADD R11, R14 ; after this, the current bank should be #2 -000037 -000038 0021 0A80 MOVE @R10++, R0 -000039 0022 0A84 MOVE @R10++, R1 -000040 0023 0A88 MOVE @R10++, R2 -000041 0024 3AC8 SUB @--R10, R2 ; after this, (R0, R1, R2) must be (0x41, 0x42, 0x00) in bank #2 -000042 -000043 0025 1B38 ADD R11, R14 -000044 0026 0F9C 2309 MOVE 0x2309, R7 -000045 0028 0718 MOVE R7, R6 ; R7 and R6 shall contain 0x2309 in bank #3 -000046 -000047 0029 0F84 0000 MOVE 0, R1 -000048 002B AFB8 0004 OR 4, R14 ; set carry, SR = 0x305 -000049 002D 2F84 0001 ADDC 1, R1 ; R1 shall be 2, carry cleared afterwards therefore SR = 1 -000050 -000051 002F 3FB8 0100 SUB BANK, R14 ; switch bank to bank #2 -000052 0031 0A48 MOVE @R10, R2 ; after this (R0, R1, R2) must be (0x41, 0x42, 0x43) -000053 -000054 0032 E000 HALT - - -EQU-list: --------------------------------------------------------------------------------------------------------- -N32768 : 0x8000 N2 : 0xFFFE BANK : 0x0100 - -Label-list: --------------------------------------------------------------------------------------------------------- -DATA : 0x0010 NEXT1 : 0x0014 diff --git a/test_programs/moves.out b/test_programs/moves.out deleted file mode 100644 index be93bc19..00000000 --- a/test_programs/moves.out +++ /dev/null @@ -1,51 +0,0 @@ -0x0000 0x0F80 -0x0001 0x0000 -0x0002 0x0038 -0x0003 0x0F84 -0x0004 0x0001 -0x0005 0x0F88 -0x0006 0xFFFF -0x0007 0x0F8C -0x0008 0x8000 -0x0009 0x0F90 -0x000A 0xFFFE -0x000B 0x1310 -0x000C 0x0FB8 -0x000D 0x0000 -0x000E 0xFF80 -0x000F 0x0014 -0x0010 0x0041 -0x0011 0x0042 -0x0012 0x0043 -0x0013 0x0000 -0x0014 0x0FA8 -0x0015 0x0010 -0x0016 0x1FB8 -0x0017 0x0100 -0x0018 0x0F80 -0x0019 0x0001 -0x001A 0x0F84 -0x001B 0x0002 -0x001C 0x0F88 -0x001D 0x0003 -0x001E 0x0FAC -0x001F 0x0100 -0x0020 0x1B38 -0x0021 0x0A80 -0x0022 0x0A84 -0x0023 0x0A88 -0x0024 0x3AC8 -0x0025 0x1B38 -0x0026 0x0F9C -0x0027 0x2309 -0x0028 0x0718 -0x0029 0x0F84 -0x002A 0x0000 -0x002B 0xAFB8 -0x002C 0x0004 -0x002D 0x2F84 -0x002E 0x0001 -0x002F 0x3FB8 -0x0030 0x0100 -0x0031 0x0A48 -0x0032 0xE000 diff --git a/test_programs/moves.rom b/test_programs/moves.rom deleted file mode 100644 index 1b082426..00000000 --- a/test_programs/moves.rom +++ /dev/null @@ -1,51 +0,0 @@ -0000111110000000 -0000000000000000 -0000000000111000 -0000111110000100 -0000000000000001 -0000111110001000 -1111111111111111 -0000111110001100 -1000000000000000 -0000111110010000 -1111111111111110 -0001001100010000 -0000111110111000 -0000000000000000 -1111111110000000 -0000000000010100 -0000000001000001 -0000000001000010 -0000000001000011 -0000000000000000 -0000111110101000 -0000000000010000 -0001111110111000 -0000000100000000 -0000111110000000 -0000000000000001 -0000111110000100 -0000000000000010 -0000111110001000 -0000000000000011 -0000111110101100 -0000000100000000 -0001101100111000 -0000101010000000 -0000101010000100 -0000101010001000 -0011101011001000 -0001101100111000 -0000111110011100 -0010001100001001 -0000011100011000 -0000111110000100 -0000000000000000 -1010111110111000 -0000000000000100 -0010111110000100 -0000000000000001 -0011111110111000 -0000000100000000 -0000101001001000 -1110000000000000 diff --git a/test_programs/ramstacksub.asm b/test_programs/ramstacksub.asm index d00df6f8..1d0efa42 100644 --- a/test_programs/ramstacksub.asm +++ b/test_programs/ramstacksub.asm @@ -2,6 +2,8 @@ ; expects RAM to start at $8000, so works for example in environment "env1" ; done by sy2002 on August, 2nd 2015 + .ORG 0x0000 ; Start address + MOVE 0x8020, R13 ; setup stack pointer MOVE 0x8000, R8 ; source memory area diff --git a/test_programs/ramstacksub.lis b/test_programs/ramstacksub.lis deleted file mode 100644 index c28312c8..00000000 --- a/test_programs/ramstacksub.lis +++ /dev/null @@ -1,58 +0,0 @@ -000001 ; tests RAM, stack and the sub routine calls ASUB and RSUB -000002 ; expects RAM to start at $8000, so works for example in environment "env1" -000003 ; done by sy2002 on August, 2nd 2015 -000004 -000005 0000 0FB4 8020 MOVE 0x8020, R13 ; setup stack pointer -000006 -000007 0002 0FA0 8000 MOVE 0x8000, R8 ; source memory area -000008 0004 0FA4 800A MOVE 0x800A, R9 ; destination memory area -000009 0006 FFB0 0013 RSUB MEMFILL, 1 -000010 -000011 0008 0F80 1111 MOVE 0x1111, R0 -000012 000A 0F84 2222 MOVE 0x2222, R1 -000013 000C 0F88 3333 MOVE 0x3333, R2 -000014 -000015 000E 0FA0 8020 MOVE 0x8020, R8 -000016 0010 0FA4 802A MOVE 0x802A, R9 -000017 0012 FFB0 0007 RSUB MEMFILL, 1 -000018 -000019 0014 0F8C 4444 MOVE 0x4444, R3 -000020 0016 0F90 5555 MOVE 0x5555, R4 -000021 0018 0F94 6666 MOVE 0x6666, R5 -000022 -000023 001A E000 HALT -000024 -000025 -000026 001B 1FB8 0100 MEMFILL ADD 0x0100, R14 ; save register bank -000027 -000028 ;fill some data beginning at R8 -000029 001D 0800 MOVE R8, R0 -000030 001E 0F82 5DA8 MOVE 0x5DA8, @R0++ -000031 0020 0F82 1000 MOVE 0x1000, @R0++ -000032 0022 0F82 0100 MOVE 0x0100, @R0++ -000033 0024 0F82 0010 MOVE 0x0010, @R0++ -000034 0026 0F82 0001 MOVE 0x0001, @R0++ -000035 0028 0F82 1111 MOVE 0x1111, @R0++ -000036 002A 0F82 FFFF MOVE 0xFFFF, @R0++ -000037 -000038 ; copy the data from R8 to R9 -000039 002C 0800 MOVE R8, R0 -000040 002D 0904 MOVE R9, R1 -000041 002E 0F88 0007 MOVE 0x0007, R2 -000042 0030 0F8C 0001 MOVE 0x0001, R3 -000043 -000044 0032 0086 COPY MOVE @R0++, @R1++ -000045 0033 3308 SUB R3, R2 -000046 0034 FFAB FFFC RBRA COPY, !Z -000047 -000048 0036 3FB8 0100 SUB 0x0100, R14 ; restore register bank -000049 0038 0DBC MOVE @R13++, R15 ; return from sub routine -000050 - - -EQU-list: --------------------------------------------------------------------------------------------------------- - -Label-list: --------------------------------------------------------------------------------------------------------- -MEMFILL : 0x001B COPY : 0x0032 diff --git a/test_programs/ramstacksub.out b/test_programs/ramstacksub.out deleted file mode 100644 index 092a5da7..00000000 --- a/test_programs/ramstacksub.out +++ /dev/null @@ -1,57 +0,0 @@ -0x0000 0x0FB4 -0x0001 0x8020 -0x0002 0x0FA0 -0x0003 0x8000 -0x0004 0x0FA4 -0x0005 0x800A -0x0006 0xFFB0 -0x0007 0x0013 -0x0008 0x0F80 -0x0009 0x1111 -0x000A 0x0F84 -0x000B 0x2222 -0x000C 0x0F88 -0x000D 0x3333 -0x000E 0x0FA0 -0x000F 0x8020 -0x0010 0x0FA4 -0x0011 0x802A -0x0012 0xFFB0 -0x0013 0x0007 -0x0014 0x0F8C -0x0015 0x4444 -0x0016 0x0F90 -0x0017 0x5555 -0x0018 0x0F94 -0x0019 0x6666 -0x001A 0xE000 -0x001B 0x1FB8 -0x001C 0x0100 -0x001D 0x0800 -0x001E 0x0F82 -0x001F 0x5DA8 -0x0020 0x0F82 -0x0021 0x1000 -0x0022 0x0F82 -0x0023 0x0100 -0x0024 0x0F82 -0x0025 0x0010 -0x0026 0x0F82 -0x0027 0x0001 -0x0028 0x0F82 -0x0029 0x1111 -0x002A 0x0F82 -0x002B 0xFFFF -0x002C 0x0800 -0x002D 0x0904 -0x002E 0x0F88 -0x002F 0x0007 -0x0030 0x0F8C -0x0031 0x0001 -0x0032 0x0086 -0x0033 0x3308 -0x0034 0xFFAB -0x0035 0xFFFC -0x0036 0x3FB8 -0x0037 0x0100 -0x0038 0x0DBC diff --git a/test_programs/ramstacksub.rom b/test_programs/ramstacksub.rom deleted file mode 100644 index cee5843f..00000000 --- a/test_programs/ramstacksub.rom +++ /dev/null @@ -1,57 +0,0 @@ -0000111110110100 -1000000000100000 -0000111110100000 -1000000000000000 -0000111110100100 -1000000000001010 -1111111110110000 -0000000000010011 -0000111110000000 -0001000100010001 -0000111110000100 -0010001000100010 -0000111110001000 -0011001100110011 -0000111110100000 -1000000000100000 -0000111110100100 -1000000000101010 -1111111110110000 -0000000000000111 -0000111110001100 -0100010001000100 -0000111110010000 -0101010101010101 -0000111110010100 -0110011001100110 -1110000000000000 -0001111110111000 -0000000100000000 -0000100000000000 -0000111110000010 -0101110110101000 -0000111110000010 -0001000000000000 -0000111110000010 -0000000100000000 -0000111110000010 -0000000000010000 -0000111110000010 -0000000000000001 -0000111110000010 -0001000100010001 -0000111110000010 -1111111111111111 -0000100000000000 -0000100100000100 -0000111110001000 -0000000000000111 -0000111110001100 -0000000000000001 -0000000010000110 -0011001100001000 -1111111110101011 -1111111111111100 -0011111110111000 -0000000100000000 -0000110110111100 diff --git a/test_programs/regbank.lis b/test_programs/regbank.lis deleted file mode 100644 index a321d1e8..00000000 --- a/test_programs/regbank.lis +++ /dev/null @@ -1,129 +0,0 @@ -000001 ; Checks, if all register banks are working by putting data in all registers -000002 ; then generating a check sum for each register. The validity of the check sum -000003 ; for each register is shown on the emulated TIL by cycling through all 8 -000004 ; result registers, showing the actual value, then subtracting the correct -000005 ; value, so that next a "0" should be shown. -000006 ; -000007 ; Also, a simple RAM check is included, as this test program does subtractions -000008 ; in RAM and furtheron uses RSUB to call the delay routine, therefore a super -000009 ; small stack on RAM is utilized using two nested function calls -000010 ; -000011 ; Everything works correct, if the TIL displays the following sequence in -000012 ; a loop: 8080, 0000, 1700, 0000 -000013 ; -000014 ; done by sy2002 on August 2015 -000015 -000016 IO$TIL_BASE .EQU 0xFF10 ; Address of TIL-display -000017 -000018 ; about 10.000.000 cycles are needed to delay 1 sec -000019 WAIT_CYCLES1 .EQU 0x1388 ; 0x1388 = decimal 5.000 -000020 WAIT_CYCLES2 .EQU 0x07D0 ; 0x07D0 = decimal 2.000 -000021 -000022 NEXT_BANK .EQU 0x0100 ; added to SR: switch to next bank -000023 -000024 ; expected check sum values -000025 CHECK_R0 .EQU 0x8080 ; sum(1..256) = 32.896 = 0x8080 -000026 CHECK_R1 .EQU 0x1700 ; 256 x 23 = 5.888 = 0x1700 -000027 -000028 ; memory locations -000029 STACK_TOP .EQU 0x8020 ; top of the stack, initial SP -000030 VAR_DIFF .EQU 0x8000 ; variable in RAM to store the -000031 ; difference between a register -000032 ; value and the expected value -000033 -000034 .ORG 0x0000 -000035 -000036 0000 0FB4 8020 MOVE 0x8020, R13 ; setup stack pointer -000037 -000038 0002 AFB8 FF00 OR 0xFF00, R14 ; activate highest register page -000039 0004 0FA0 0100 MOVE 0x0100, R8 ; loop through 256 banks -000040 0006 0FA4 0001 MOVE 0x0001, R9 ; we need to sub 1 often -000041 0008 0FA8 0100 MOVE NEXT_BANK, R10 ; we need to sub 0x100 often -000042 000A 0FAC 0017 MOVE 23, R11 ; we need to move 23 often -000043 -000044 ; fill registers throughout 256 registerbanks with meaningful values -000045 000C 0800 BANK_LOOP MOVE R8, R0 ; move 256 downto 1 in all R0's -000046 000D 0B04 MOVE R11, R1 ; move 23 in all R1's -000047 000E 3A38 SUB R10, R14 ; previous register bank -000048 000F 3920 SUB R9, R8 ; decrease loop counter -000049 0010 FFAB FFFA RBRA BANK_LOOP, !Z ; loop 256 downto 1 (0 exits) -000050 -000051 ; calculate check sums over all registers and store the results in bank 0 -000052 0012 0FA0 00FF MOVE 0x00FF, R8 ; loop only through 255 as we -000053 0014 9FB8 00FF AND 0x00FF, R14 ; are adding everything to bank 0 -000054 -000055 0016 1A38 CHECK_LOOP ADD R10, R14 ; next bank -000056 -000057 0017 0030 MOVE R0, R12 ; use R12 as temp for R0 -000058 0018 0E2C MOVE R14, R11 ; save current bank page -000059 0019 9FB8 00FF AND 0x00FF, R14 ; back to bank 0 -000060 001B 1C00 ADD R12, R0 ; accumulate check sum in R0 -000061 001C 0B38 MOVE R11, R14 ; restore current bank page -000062 -000063 001D 0130 MOVE R1, R12 ; use R12 as temp for R1 -000064 001E 0E2C MOVE R14, R11 ; save current bank page -000065 001F 9FB8 00FF AND 0x00FF, R14 ; back to bank 0 -000066 0021 1C04 ADD R12, R1 ; accumulate check sum in R1 -000067 0022 0B38 MOVE R11, R14 ; restore current bank page -000068 -000069 0023 3920 SUB R9, R8 ; decrease loop counter -000070 0024 FFAB FFF0 RBRA CHECK_LOOP, !Z ; loop 255 downto 1 (0 exits) -000071 -000072 -000073 ; output results to TIL -000074 0026 9FB8 00FF AND 0x00FF, R14 ; switch back to reg bank 0 -000075 0028 0FB0 FF10 MOVE IO$TIL_BASE, R12 ; TIL MMIO display address -000076 -000077 ; display register R0 and the difference to the expected value -000078 002A 0020 DISPLAY_LOOP MOVE R0, R8 ; register = R8 -000079 002B 0FA4 8080 MOVE CHECK_R0, R9 ; expected value = R9 -000080 002D FFB0 0008 RSUB DISPLAY_REG, 1 ; call sub routine -000081 -000082 ; dito R1 -000083 002F 0120 MOVE R1, R8 -000084 0030 0FA4 1700 MOVE CHECK_R1, R9 -000085 0032 FFB0 0003 RSUB DISPLAY_REG, 1 -000086 -000087 0034 FFA0 FFF4 RBRA DISPLAY_LOOP, 1 -000088 -000089 0036 E000 HALT -000090 -000091 ; sub routine to display the register value and the expected value -000092 ; the sub routine uses another sub routine so this is also a nice first -000093 ; test of stacked sub routine calls -000094 ; input: R8 = register, R9 = expected value, R12 = TIL BASE -000095 0037 1FB8 0100 DISPLAY_REG ADD NEXT_BANK, R14 ; next register bank -000096 0039 0831 MOVE R8, @R12 ; display value on TIL -000097 003A FFB0 000A RSUB DELAY, 1 ; wait 1 second -000098 003C 0F80 8000 MOVE VAR_DIFF, R0 ; memory location of variable -000099 003E 0801 MOVE R8, @R0 ; store register value in var -000100 003F 3901 SUB R9, @R0 ; subtract expected value -000101 0040 0071 MOVE @R0, @R12 ; display difference reg vs. expct -000102 0041 FFB0 0003 RSUB DELAY, 1 ; wait 1 second -000103 0043 3FB8 0100 SUB NEXT_BANK, R14 ; previous register bank -000104 0045 0DBC MOVE @R13++, R15 ; return from sub routine -000105 -000106 ; sub routine to wait for about 1sec -000107 0046 1FB8 0100 DELAY ADD NEXT_BANK, R14 ; next register bank -000108 0048 0F84 07D0 MOVE WAIT_CYCLES2, R1 ; outer wait cycles (2.000) -000109 004A 0F80 1388 WAIT_LOOP2 MOVE WAIT_CYCLES1, R0 ; inner wait cycles (5.000) -000110 004C 3F80 0001 WAIT_LOOP1 SUB 1, R0 ; dec inner wait cycles and ... -000111 004E FFAB FFFC RBRA WAIT_LOOP1, !Z ; ... repeat if not zero -000112 0050 3F84 0001 SUB 1, R1 ; dec outer wait cycles and ... -000113 0052 FFAB FFF6 RBRA WAIT_LOOP2, !Z ; ... repeat if not zero -000114 0054 3FB8 0100 SUB NEXT_BANK, R14 ; previous register bank -000115 0056 0DBC MOVE @R13++, R15 ; return from sub routine -000116 - - -EQU-list: --------------------------------------------------------------------------------------------------------- -IO$TIL_BASE : 0xFF10 WAIT_CYCLES1 : 0x1388 WAIT_CYCLES2 : 0x07D0 -NEXT_BANK : 0x0100 CHECK_R0 : 0x8080 CHECK_R1 : 0x1700 -STACK_TOP : 0x8020 VAR_DIFF : 0x8000 - -Label-list: --------------------------------------------------------------------------------------------------------- -BANK_LOOP : 0x000C CHECK_LOOP : 0x0016 DISPLAY_LOOP : 0x002A -DISPLAY_REG : 0x0037 DELAY : 0x0046 WAIT_LOOP2 : 0x004A -WAIT_LOOP1 : 0x004C diff --git a/test_programs/regbank.out b/test_programs/regbank.out deleted file mode 100644 index 55c1c735..00000000 --- a/test_programs/regbank.out +++ /dev/null @@ -1,87 +0,0 @@ -0x0000 0x0FB4 -0x0001 0x8020 -0x0002 0xAFB8 -0x0003 0xFF00 -0x0004 0x0FA0 -0x0005 0x0100 -0x0006 0x0FA4 -0x0007 0x0001 -0x0008 0x0FA8 -0x0009 0x0100 -0x000A 0x0FAC -0x000B 0x0017 -0x000C 0x0800 -0x000D 0x0B04 -0x000E 0x3A38 -0x000F 0x3920 -0x0010 0xFFAB -0x0011 0xFFFA -0x0012 0x0FA0 -0x0013 0x00FF -0x0014 0x9FB8 -0x0015 0x00FF -0x0016 0x1A38 -0x0017 0x0030 -0x0018 0x0E2C -0x0019 0x9FB8 -0x001A 0x00FF -0x001B 0x1C00 -0x001C 0x0B38 -0x001D 0x0130 -0x001E 0x0E2C -0x001F 0x9FB8 -0x0020 0x00FF -0x0021 0x1C04 -0x0022 0x0B38 -0x0023 0x3920 -0x0024 0xFFAB -0x0025 0xFFF0 -0x0026 0x9FB8 -0x0027 0x00FF -0x0028 0x0FB0 -0x0029 0xFF10 -0x002A 0x0020 -0x002B 0x0FA4 -0x002C 0x8080 -0x002D 0xFFB0 -0x002E 0x0008 -0x002F 0x0120 -0x0030 0x0FA4 -0x0031 0x1700 -0x0032 0xFFB0 -0x0033 0x0003 -0x0034 0xFFA0 -0x0035 0xFFF4 -0x0036 0xE000 -0x0037 0x1FB8 -0x0038 0x0100 -0x0039 0x0831 -0x003A 0xFFB0 -0x003B 0x000A -0x003C 0x0F80 -0x003D 0x8000 -0x003E 0x0801 -0x003F 0x3901 -0x0040 0x0071 -0x0041 0xFFB0 -0x0042 0x0003 -0x0043 0x3FB8 -0x0044 0x0100 -0x0045 0x0DBC -0x0046 0x1FB8 -0x0047 0x0100 -0x0048 0x0F84 -0x0049 0x07D0 -0x004A 0x0F80 -0x004B 0x1388 -0x004C 0x3F80 -0x004D 0x0001 -0x004E 0xFFAB -0x004F 0xFFFC -0x0050 0x3F84 -0x0051 0x0001 -0x0052 0xFFAB -0x0053 0xFFF6 -0x0054 0x3FB8 -0x0055 0x0100 -0x0056 0x0DBC diff --git a/test_programs/regbank.rom b/test_programs/regbank.rom deleted file mode 100644 index dab1ae03..00000000 --- a/test_programs/regbank.rom +++ /dev/null @@ -1,87 +0,0 @@ -0000111110110100 -1000000000100000 -1010111110111000 -1111111100000000 -0000111110100000 -0000000100000000 -0000111110100100 -0000000000000001 -0000111110101000 -0000000100000000 -0000111110101100 -0000000000010111 -0000100000000000 -0000101100000100 -0011101000111000 -0011100100100000 -1111111110101011 -1111111111111010 -0000111110100000 -0000000011111111 -1001111110111000 -0000000011111111 -0001101000111000 -0000000000110000 -0000111000101100 -1001111110111000 -0000000011111111 -0001110000000000 -0000101100111000 -0000000100110000 -0000111000101100 -1001111110111000 -0000000011111111 -0001110000000100 -0000101100111000 -0011100100100000 -1111111110101011 -1111111111110000 -1001111110111000 -0000000011111111 -0000111110110000 -1111111100010000 -0000000000100000 -0000111110100100 -1000000010000000 -1111111110110000 -0000000000001000 -0000000100100000 -0000111110100100 -0001011100000000 -1111111110110000 -0000000000000011 -1111111110100000 -1111111111110100 -1110000000000000 -0001111110111000 -0000000100000000 -0000100000110001 -1111111110110000 -0000000000001010 -0000111110000000 -1000000000000000 -0000100000000001 -0011100100000001 -0000000001110001 -1111111110110000 -0000000000000011 -0011111110111000 -0000000100000000 -0000110110111100 -0001111110111000 -0000000100000000 -0000111110000100 -0000011111010000 -0000111110000000 -0001001110001000 -0011111110000000 -0000000000000001 -1111111110101011 -1111111111111100 -0011111110000100 -0000000000000001 -1111111110101011 -1111111111110110 -0011111110111000 -0000000100000000 -0000110110111100 diff --git a/test_programs/til_count.lis b/test_programs/til_count.lis deleted file mode 100644 index 0802a1a4..00000000 --- a/test_programs/til_count.lis +++ /dev/null @@ -1,67 +0,0 @@ -000001 ;; This is the very first "real" QNICE-FPGA test program which is and was used during the -000002 ;; initial development of QNICE-FPGA by sy2002 in July 2015. -000003 ;; -000004 ;; It is inspired by vaxman's original test program "til_count.asm" that displays -000005 ;; a count on the TIL-311 display on the original QNICE/A evaluation board. -000006 -000007 IO$TIL_BASE .EQU 0xFF10 ; Address of TIL-display -000008 IO$TIL_MASK .EQU 0xFF11 ; Mask register of TIL display -000009 -000010 FLAG_C_SET .EQU 0x0004 ; bit pattern for setting the carry flag with OR -000011 FLAG_C_CLEAR .EQU 0xFFFB ; bit pattern for clearing the carry flag with AND -000012 -000013 ; QNICE-FPGA in the current early stage of development is running at about 20 MIPS. As the -000014 ; inner loop consists of two instructions, we need to count to about 10.000.000 for having -000015 ; the effect of an ~1 Hz incrementing counter on the TIL. -000016 ; So we choose WAIT_CYCLES1 as 5.000 equ 0x1388 and WAIT_CYCLES2 as 2.000 equ 0x07D0 -000017 WAIT_CYCLES1 .EQU 0x1388 -000018 WAIT_CYCLES2 .EQU 0x07D0 -000019 -000020 .ORG 0x0000 ; Start address -000021 0000 0F80 0000 MOVE 0x0000, R0 ; Clear R0 -000022 0002 0F84 FF10 MOVE IO$TIL_BASE, R1 ; Base address of TIL-display for output -000023 0004 0FA4 FF11 MOVE IO$TIL_MASK, R9 ; Mask register of TIL-display for selecting which TIL is lit -000024 -000025 ; Write contents of R0 to the TIL-display -000026 0006 0005 LOOP MOVE R0, @R1 -000027 -000028 ; Create mask for TIL digits, so that only those TILs are lit, that are displaying non zero digits -000029 0007 0F90 000F MOVE 0x000F, R4 ; R4 is the resulting mask; at first, we assume all four digits are lit -000030 0009 0F94 F000 MOVE 0xF000, R5 ; R5 is the bit parttern to check, if a certain digit shall be lit -000031 000B 0F9C 0003 MOVE 0x0003, R7 ; R7 is the loop counter -000032 -000033 000D 0518 CREATE_MASK MOVE R5, R6 ; use the pattern and ... -000034 000E 9018 AND R0, R6 ; ... check if one of the bits is set at the digit position implied the mask -000035 000F FFAB 000A RBRA MASK_READY, !Z ; if bits are set, then mask is ready -000036 0011 9FB8 FFFB AND FLAG_C_CLEAR, R14 ; clear C because SHR fills with C (not necessarry, because C is never set before) -000037 0013 6F90 0001 SHR 1, R4 ; make the mask smaller by one bit -000038 0015 6F94 0004 SHR 4, R5 ; move the "scanner pattern" to the next digit (i.e. 4 bits) -000039 0017 3F9C 0001 SUB 1, R7 ; reduce counter (counter necessary to avoid endless loop in case of R0 == 0) -000040 0019 FFAB FFF2 RBRA CREATE_MASK, !Z ; next iteration -000041 -000042 ; Set mask register of TIL-display -000043 001B 0425 MASK_READY MOVE R4, @R9 -000044 -000045 ; waste cycles to approximate a 1 Hz execution -000046 001C 0F8C 07D0 MOVE WAIT_CYCLES2, R3 -000047 001E 0F88 1388 WAIT_LOOP2 MOVE WAIT_CYCLES1, R2 -000048 0020 3F88 0001 WAIT_LOOP1 SUB 1, R2 ; Decrement loop counter -000049 0022 FFAB FFFC RBRA WAIT_LOOP1, !Z ; If not zero, perform next loop -000050 0024 3F8C 0001 SUB 1, R3 -000051 0026 FFAB FFF6 RBRA WAIT_LOOP2, !Z -000052 -000053 0028 1F80 0001 ADD 1, R0 ; Increment R0 -000054 002A FFAB FFDA RBRA LOOP, !Z ; Unconditional jump to display the next value -000055 -000056 002C E000 HALT ; stop the CPU - - -EQU-list: --------------------------------------------------------------------------------------------------------- -IO$TIL_BASE : 0xFF10 IO$TIL_MASK : 0xFF11 FLAG_C_SET : 0x0004 -FLAG_C_CLEAR : 0xFFFB WAIT_CYCLES1 : 0x1388 WAIT_CYCLES2 : 0x07D0 - -Label-list: --------------------------------------------------------------------------------------------------------- -LOOP : 0x0006 CREATE_MASK : 0x000D MASK_READY : 0x001B -WAIT_LOOP2 : 0x001E WAIT_LOOP1 : 0x0020 diff --git a/test_programs/til_count.out b/test_programs/til_count.out deleted file mode 100644 index cc8769f2..00000000 --- a/test_programs/til_count.out +++ /dev/null @@ -1,45 +0,0 @@ -0x0000 0x0F80 -0x0001 0x0000 -0x0002 0x0F84 -0x0003 0xFF10 -0x0004 0x0FA4 -0x0005 0xFF11 -0x0006 0x0005 -0x0007 0x0F90 -0x0008 0x000F -0x0009 0x0F94 -0x000A 0xF000 -0x000B 0x0F9C -0x000C 0x0003 -0x000D 0x0518 -0x000E 0x9018 -0x000F 0xFFAB -0x0010 0x000A -0x0011 0x9FB8 -0x0012 0xFFFB -0x0013 0x6F90 -0x0014 0x0001 -0x0015 0x6F94 -0x0016 0x0004 -0x0017 0x3F9C -0x0018 0x0001 -0x0019 0xFFAB -0x001A 0xFFF2 -0x001B 0x0425 -0x001C 0x0F8C -0x001D 0x07D0 -0x001E 0x0F88 -0x001F 0x1388 -0x0020 0x3F88 -0x0021 0x0001 -0x0022 0xFFAB -0x0023 0xFFFC -0x0024 0x3F8C -0x0025 0x0001 -0x0026 0xFFAB -0x0027 0xFFF6 -0x0028 0x1F80 -0x0029 0x0001 -0x002A 0xFFAB -0x002B 0xFFDA -0x002C 0xE000 diff --git a/test_programs/til_count.rom b/test_programs/til_count.rom deleted file mode 100644 index 4a5d95ee..00000000 --- a/test_programs/til_count.rom +++ /dev/null @@ -1,45 +0,0 @@ -0000111110000000 -0000000000000000 -0000111110000100 -1111111100010000 -0000111110100100 -1111111100010001 -0000000000000101 -0000111110010000 -0000000000001111 -0000111110010100 -1111000000000000 -0000111110011100 -0000000000000011 -0000010100011000 -1001000000011000 -1111111110101011 -0000000000001010 -1001111110111000 -1111111111111011 -0110111110010000 -0000000000000001 -0110111110010100 -0000000000000100 -0011111110011100 -0000000000000001 -1111111110101011 -1111111111110010 -0000010000100101 -0000111110001100 -0000011111010000 -0000111110001000 -0001001110001000 -0011111110001000 -0000000000000001 -1111111110101011 -1111111111111100 -0011111110001100 -0000000000000001 -1111111110101011 -1111111111110110 -0001111110000000 -0000000000000001 -1111111110101011 -1111111111011010 -1110000000000000 diff --git a/test_programs/til_count_rom b/test_programs/til_count_rom deleted file mode 100644 index 4a5d95ee..00000000 --- a/test_programs/til_count_rom +++ /dev/null @@ -1,45 +0,0 @@ -0000111110000000 -0000000000000000 -0000111110000100 -1111111100010000 -0000111110100100 -1111111100010001 -0000000000000101 -0000111110010000 -0000000000001111 -0000111110010100 -1111000000000000 -0000111110011100 -0000000000000011 -0000010100011000 -1001000000011000 -1111111110101011 -0000000000001010 -1001111110111000 -1111111111111011 -0110111110010000 -0000000000000001 -0110111110010100 -0000000000000100 -0011111110011100 -0000000000000001 -1111111110101011 -1111111111110010 -0000010000100101 -0000111110001100 -0000011111010000 -0000111110001000 -0001001110001000 -0011111110001000 -0000000000000001 -1111111110101011 -1111111111111100 -0011111110001100 -0000000000000001 -1111111110101011 -1111111111110110 -0001111110000000 -0000000000000001 -1111111110101011 -1111111111011010 -1110000000000000 diff --git a/tools/asm b/tools/asm new file mode 100755 index 00000000..03e3fb11 --- /dev/null +++ b/tools/asm @@ -0,0 +1,33 @@ +#!/bin/bash + +# +# It turned out that the assembler needs some kind of a preprocessor. +# Since I am too lazy to write one I decided to use the cpp which is +# the purpose of this small wrapper script. Assembling some QNICE +# sources is done invoking this script with the source file name as +# its only parameter. +# + +temp_file=__temp__.asm + +if [ $# -ne 1 ] +then + echo "Usage: asm " + exit +fi + +assembler=`dirname $0`/qasm + +destination=${1/.asm/}.out + +# +# We use the standard C-preprocessor to perform the necessary +# preprocessor steps for the QNICE-assembler. Since the preprocessor +# inserts lines starting with a '#' which the assembler doest +# not like, these are subsequently removed with sed. +# +cpp -E $1 | sed '/^#.*/d' > $temp_file + +$assembler $temp_file $destination + +rm $temp_file diff --git a/tools/qasm.c b/tools/qasm.c new file mode 100644 index 00000000..97013c74 --- /dev/null +++ b/tools/qasm.c @@ -0,0 +1,1065 @@ +/* +** QNICE assembler: This program reads QNICE assembler code from a file and generates, as expected from an assembler :-), +** valid machine code based on this input. +** +** B. Ulmann, JUN-2007, DEC-2007, APR-2008, AUG-2015 +** +** Todo: +** More constant-formats: 'xy', -0x1234, 0b1010010, -b010010 +** +** Known bugs: +** +** 04-JUN-2008: Line numbers in error messages are sometimes a bit off reality (up to -5 has been observed) +*/ + +#include +#include +#include /* For malloc. */ +#include /* For isdigit. */ + +#ifndef TRUE +# define TRUE 1 +# define FALSE !TRUE +#endif + +#undef VERBOSE +#undef DEBUG + +#define STRING_LENGTH 255 + +#define COMMENT_CHAR ';' + +#define INSTRUCTION$NORMAL 0 +#define INSTRUCTION$BRANCH 1 +#define INSTRUCTION$DIRECTIVE 2 + +#define OPERAND$ILLEGAL -1 /* An illegal operand was found */ +#define OPERAND$MISSING 0 +#define OPERAND$LABEL_EQU 1 /* Such an operand can be resolved only during the second pass */ +#define OPERAND$CONSTANT 2 /* Constants can be resolved immediately in the first pass */ +#define OPERAND$RXX 3 +#define OPERAND$AT_RXX 4 +#define OPERAND$AT_RXX_PP 5 +#define OPERAND$AT_MM_RXX 6 + +#define STATE$INITIAL 0 +#define STATE$FINISHED 1 +#define STATE$LABELS_MISSING 2 +#define STATE$NOTHING_YET_DONE 3 + +typedef struct _data_entry +{ + char source[STRING_LENGTH], /* Original source line for printout */ + label[STRING_LENGTH], /* Name of a label if there was one */ + mnemonic[STRING_LENGTH], /* Undecoded mnemonic */ + src_op[STRING_LENGTH], /* Source operand */ + dest_op[STRING_LENGTH], /* Destination operand */ + error_text[STRING_LENGTH]; /* Text of error message if something went wrong during assembly */ + int address, /* Memory address for this instruction/directive */ + number_of_words, /* How many words of data are necessary for this line? */ + *data, /* Pointer to a list of number_of_words-ints holding the resulting data */ + opcode, opcode_type, /* Which opcode and which type* */ + src_op_type, /* If the source operand is a constant, it will be stored here */ + dest_op_type, /* The same holds true for the destination */ + src_op_code, /* The six bits describing the first operand */ + dest_op_code, /* The six bits describing the second operand */ + state; /* STATE$FINISHED, STATE$LABELS_MISSING */ + struct _data_entry *next; +} data_structure; + +typedef struct _equ_entry +{ + char name[STRING_LENGTH]; + int value; + struct _equ_entry *next; +} equ_structure; + +/* +** Global variables: +*/ + +data_structure *gbl$data = 0; +equ_structure *gbl$equs = 0; + +/* +** Expand all tabs by blanks, assuming that tab stops occur every eight columns. +*/ +void expand_tabs(char *dst, char *src) +{ + int i; + + i = 0; + while (*src) + { + i++; + if (*src != '\t') + *dst++ = *src++; + else + { + *dst++ = ' '; + for ( ; (i % 8); i++, *dst++ = ' '); + src++; + } + } + + *dst = (char) 0; +} + +/* +** Convert a string to uppercase. +*/ +void string2upper(char *string) +{ + while (*string) + { + *string = (char) toupper((int) *string); + string++; + } +} + +/* +** Convert a sequence of ASCII chars to a value. +*/ +unsigned int ascii2value(char *string) +{ + unsigned int result = 0; + + string++; /* Skip leading single quote */ + while (*string != '\'') /* We can rely on a trailing single quote */ + { + result <<= 8; + result += *string++; + } + + return result & 0xffff; +} + +/* +** Convert a sequence of '0' and '1' to a value. +*/ +unsigned int binstr2value(char *string) +{ + unsigned int result = 0; + + while (*string) + { + result <<= 1; + if (*string++ == '1') + result += 1; + } + + return result & 0xffff; +} + +/* +** Translate a mnemonic to its corresponding opcode. If mnemonic does not match, FALSE will be returned, otherwise it's TRUE. +*/ +int translate_mnemonic(char *string, int *opcode, int *type) +{ + int i; + static char *normal_mnemonics[] = {"MOVE", "ADD", "ADDC", "SUB", "SUBC", "SHL", "SHR", "SWAP", + "NOT", "AND", "OR", "XOR", "CMP", "RSRVD", "HALT", 0}, + *branch_mnemonics[] = {"ABRA", "ASUB", "RBRA", "RSUB", 0}, + *directives[] = {".ORG", ".ASCII_W", ".ASCII_P", ".EQU", ".BLOCK", 0}; + + if (!string) + return FALSE; + + string2upper(string); + + for (i = 0; normal_mnemonics[i]; i++) /* First try the "normal" mnemonics, i.e. no branches */ + if (!strcmp(string, normal_mnemonics[i])) + { + *type = INSTRUCTION$NORMAL; + *opcode = i; + return TRUE; + } + + for (i = 0; branch_mnemonics[i]; i++) /* Now try the branches */ + if (!strcmp(string, branch_mnemonics[i])) + { + *type = INSTRUCTION$BRANCH; + *opcode = i; + return TRUE; + } + + for (i = 0; directives[i]; i++) + if (!strcmp(string, directives[i])) + { + *type = INSTRUCTION$DIRECTIVE; + *opcode = i; + return TRUE; + } + + return FALSE; +} + +/* +** Local variant of strtok, just better. :-) The first call expects the string to be tokenized as its first argument. +** All subsequent calls only require the second argument to be set. If there is nothing left to be tokenized, a zero pointer +** will be returned. In contrast to strtok this routine will not alter the string to be tokenized since it +** operates on a local copy of this string. +*/ +char *tokenize(char *string, char *delimiters) +{ + static char local_copy[STRING_LENGTH], *position; + char *token; + + if (string) /* Initial call, create a copy of the string pointer */ + { + strcpy(local_copy, string); + position = local_copy; + } + else /* Subsequent call, scan local copy until a delimiter character will be found */ + { + while (*position && strchr(delimiters, *position)) /* Skip delimiters if there are any at the beginning of the string */ + position++; + + token = position; /* Now we are at the beginning of a token (or the end of the string :-) ) */ + + if (*position == '\'') /* Special case: Strings delimited by single quotes won't be split! */ + { + position++; + while (*position && *position != '\'') + position++; + } + + while (*position) + { + position++; + if (!*position || strchr(delimiters, *position)) /* Delimiter found */ + { + if (*position) + *position++ = (char) 0; /* Split string copy */ + return token; + } + } + } + + return NULL; +} + +/* +** replace_extension replaces the extension of a file name with another extension. If there is no extension in the input string +** then the new extension is just concatenated to the input name. +*/ +void replace_extension(char *destination, char *source, char *new_extension) +{ + char *delimiter; + + strcpy(destination, source); + if (!(delimiter = strrchr(destination, '.'))) /* No delimiter found */ + strcat(destination, "."); + else + *(delimiter + 1) = (char) 0; + + strcat(destination, new_extension); +} + +/* +** Print a simple usage text. +*/ +void print_help() +{ + printf("\nUsage:\nqasm [ []]\n\n"); +} + +/* +** Does exactly what you would expect. :-) +*/ +void chomp(char *string) +{ + if (string[strlen(string) - 2] == 0xa || string[strlen(string) - 2] == 0xd) + string[strlen(string) - 2] = (char) 0; + else if (string[strlen(string) - 1] == 0xa || string[strlen(string) - 1] == 0xd) + string[strlen(string) - 1] = (char) 0; +} + +/* +** Remove TABs from the source code +*/ +void remove_tabs(char *cp) +{ + while (*cp++) + if (*cp == 0x9) + *cp = ' '; +} + +/* +** The two following functions remove_trailing_blanks and remove_leading_blanks do exactly what you would expect. :-) +*/ +void remove_trailing_blanks (char *cp) +{ + int i; + + for (i = strlen (cp) - 1; i >= 0 && (*(cp + i) == '\t' || *(cp + i) == ' '); *(cp + i--) = 0); +} + +void remove_leading_blanks (char *string) +{ + char *cp; + + cp = string; + while (*cp == ' ' || *cp == '\t') + cp++; + + while ((*string++ = *cp++)); /* 26.07.2015: strcpy on Mac OS X 10.10.3 traps when copying a string partially to itself... */ +} + +/* +** find_label searches for a given label and returns its address by a pointer. The return value of the function denotes +** success (0) or failure (-1). +*/ +int find_label(char *name, int *address) +{ + data_structure *entry; + + for (entry = gbl$data; entry; entry = entry->next) + if (!strcmp(entry->label, name)) + { + *address = entry->address & 0xffff; + return 0; + } + + return -1; /* No corresponding label found! */ +} + +/* +** search_equ_list searches the list of currently known EQUs for a given entry. It returns -1 if nothing could be found, +** 0 otherwise. The result is returned via the second char-pointer. +*/ +int search_equ_list(char *name, int *value) +{ + equ_structure *entry; + + for (entry = gbl$equs; entry; entry = entry->next) + if (!strcmp(entry->name, name)) + { + *value = entry->value; + return 0; + } + + return -1; +} + +/* +** insert_into_equ_list inserts a new entry into the list of currently known EQUs. If the insert was successful, 0 will be +** returned. -1 denotes a memory problem, 1 denotes a duplicate entry! +*/ +int insert_into_equ_list(char *name, int value) +{ + int i; + equ_structure *entry; + static equ_structure *last; + +#ifdef DEBUG + printf("insert_into_equ_list: >>%s<< = %d/%04X\n", name, value, value); +#endif + if (!(entry = (equ_structure *) malloc(sizeof(equ_structure)))) + { + printf("insert_into_equ_list: Out of memory!\n"); + return -1; + } + + strcpy(entry->name, name); + entry->value = value; + entry->next = (equ_structure *) 0; + + if (!gbl$equs) /* This will be the very first entry in the list! */ + gbl$equs = last = entry; + else /* Not the first entry -> append to the end of the list */ + { + if (!search_equ_list(name, &i)) + return 1; + last = last->next = entry; + } + + return 0; +} + +/* +** Read the complete source file into a simlpe linked list. This list will be +** the basis for all of the following operations and will eventually contain +** the source code as well as the corresponding binary data. +*/ +int read_source(char *file_name) +{ + int counter; + char line[STRING_LENGTH]; + FILE *handle; + data_structure *entry, *previous; + + if (!(handle = fopen(file_name, "r"))) + { + printf("read_source: Unable to open source file >>%s<address = entry->opcode = entry->opcode_type = -1; + entry->src_op_type = entry->dest_op_type = OPERAND$MISSING; + entry->number_of_words = entry->src_op_code = entry->dest_op_code = 0; + entry->data = (int *) 0; + *(entry->label) = *(entry->mnemonic) = *(entry->src_op) = *(entry->dest_op) = *(entry->error_text) = (char) 0; + entry->next = (data_structure *) 0; + + strcpy(entry->source, line); /* Remember the source code line for later analysis and print out */ + + if (previous) /* This is not the first element in the list */ + previous = previous->next = entry; + else + previous = gbl$data; + } + + fclose(handle); + +#ifdef VERBOSE + printf("read_source: %d lines read\n", counter); +#endif + + return 0; +} + +/* +** str2int converts a string in base 16 or base 10 notation to an unsigned integer value. +** Base 16 values require a prefix "0x" or "$" while base 10 value do not require any prefix. +*/ +unsigned int str2int(char *string) +{ + int value; + + if (!string || !*string) /* An empty string is treated as a zero */ + return 0; + + if (!strncmp(string, "0X", 2) || !strncmp(string, "0x", 2)) + sscanf(string + 2, "%x", &value); + else if (!strncmp(string, "-0X", 3) || !strncmp(string, "-0x", 3)) + { + sscanf(string + 3, "%x", &value); + value = -value & 0xffff; + } + else if (!strncmp(string, "0B", 2) || !strncmp(string, "0b", 2)) + value = binstr2value(string + 2); + else if (!strncmp(string, "-0B", 3) || !strncmp(string, "-0b", 3)) + value = -binstr2value(string + 3) & 0xffff; + else if (*string == '$') + sscanf(string + 1, "%x", &value); + else if (*string == '\'' && *(string + strlen(string) - 1) == '\'') + value = ascii2value(string); + else + sscanf(string, "%d", &value); + + return value; +} + +/* +** decode_operand decodes a given operand. Its return value is the type of the operand, the pointer *op_code will be used to +** return the six (!) bits describing the operand. +*/ +int decode_operand(char *operand, int *op_code) +{ + int value, auto_increment, i, flag; + char *p; + + if ((char) toupper((int) *operand) == 'R') /* Maybe a simple register */ + { + flag = 1; /* Pretend it is a register number what follows */ + for (i = 1; i < strlen(operand) - 1; i++) + if (!isdigit(*(operand + i))) + flag = 0; + + if (flag) /* OK - it looks like a register description */ + { + value = str2int(operand + 1); + if (value < 0 || value > 15) /* Maybe it wasn't a register but a label? */ + return OPERAND$LABEL_EQU; + + *op_code = value << 2; + return OPERAND$RXX; + } + else + { + *op_code = 0x3e; + return OPERAND$LABEL_EQU; + } + } + else if (!strncmp(operand, "@R", 2)) /* Simple indirect addressing */ + { + if ((auto_increment = (operand[strlen(operand) - 1] == '+' && operand[strlen(operand) - 2] == '+'))) + operand[strlen(operand) - 2] = (char) 0; + + value = str2int(operand + 2); + if (value < 0 || value > 15) + return OPERAND$ILLEGAL; + + if (auto_increment) + { + *op_code = value << 2 | 2; + return OPERAND$AT_RXX; + } + else + { + *op_code = value << 2 | 1; + return OPERAND$AT_RXX_PP; + } + } + else if (!strncmp(operand, "@--R", 4)) /* Indirect addressing with predecrement */ + { + value = str2int(operand + 4); + if (value < 0 || value > 15) + return OPERAND$ILLEGAL; + + *op_code = value << 2 | 3; + return OPERAND$AT_MM_RXX; + } + /* Constants can be of the form 0x..., 0b..., -0x..., -0b..., or '...' */ + else if (isdigit(*operand) || *operand == '\'' || *operand == '-') + { + *op_code = 0x3e; + return OPERAND$CONSTANT; + } + + *op_code = 0x3e; + return OPERAND$LABEL_EQU; /* Such an operand can only be resolved during the second pass! */ +} + +/* +** assemble does all the real work of the assembler. It reads the source contained in the linked list and fills the +** corresponding elements of the list with addresses and data words as applicable. +*/ +int assemble() +{ + int opcode, type, line_counter, address, i, error_counter = 0, number_of_operands, negate, flag, value, size, + special_char; + char line[STRING_LENGTH], *p, *delimiters = " ,", *token, *sr_bits = "1XCZNVIM"; + data_structure *entry; + + /* First pass: */ +#ifdef DEBUG + printf("assemble: Starting first pass.\n"); +#endif + for (address = line_counter = 1, entry = gbl$data; entry; entry = entry->next, line_counter++) + { + strcpy(line, entry->source); /* Get a local copy of the line and clean it up */ + entry->state = STATE$NOTHING_YET_DONE; /* Still a lot to do */ + if ((p = strchr(line, COMMENT_CHAR))) /* Remove everything after the start of a comment */ + *p = (char) 0; + remove_leading_blanks(line); + remove_trailing_blanks(line); + remove_tabs(line); + + if (!strlen(line)) /* Skip empty lines */ + continue; + + tokenize(line, (char *) 0); /* Initialize tokenizing */ + token = tokenize((char *) 0, delimiters); + + if (translate_mnemonic(token, &opcode, &type)) /* First token is a mnemonic or a directive */ + strcpy(entry->mnemonic, token); + else /* If the first token is neither a mnemonic nor an opcode, assume it is a label */ + { + strcpy(entry->label, token); + token = tokenize((char *) 0, delimiters); /* Next token has to be a valid mnemonic or directive */ + if (!translate_mnemonic(token, &opcode, &type)) + { + sprintf(entry->error_text, "Line %d: No valid mnemonic/directive found (%s)!", line_counter, entry->source); + printf("assemble: %s\n", entry->error_text); + entry->opcode = entry->opcode_type = -1; + error_counter++; + continue; + } + strcpy(entry->mnemonic, token); + } + + entry->opcode = opcode; + entry->opcode_type = type; + entry->address = address; + + if (entry->opcode_type == INSTRUCTION$DIRECTIVE) /* A directive - do something... */ + { + address--; + + /* If the directive .ORG was found, it is now time to change the address */ + if (!strcmp(entry->mnemonic, ".ORG")) + { + entry->state = STATE$FINISHED; + token = tokenize((char *) 0, delimiters); /* Get new address */ + entry->address = -1; + address = str2int(token) - 1; /* - 1 since the address will be incremented later */ + } + else if (!strcmp(entry->mnemonic, ".ASCII_W") || !strcmp(entry->mnemonic, ".ASCII_P")) + { + /* + ** .ASCII_W expects a string of ASCII characters which will be stored one character per word (only the low byte + ** of each word is used, the upper byte is 0). The string will be automatically terminated by a zero-word (quite + ** compatible with the standard way of life in C. + ** + ** .ASCII_P works quite like .ASCII_W but no terminating zero word will be written (thus the P -> Plain). + ** + ** Both variants can deal with the special character '\n' which will be translated to CR/LF. + */ + + /* ASCII constants are enclosed in double quotes! */ + if (!strcmp(entry->mnemonic, ".ASCII_W")) + p = strstr(line, ".ASCII_W") + strlen(".ASCII_W") + 1; /* Get begin of argument, including blanks, so no tokenize! */ + else + p = strstr(line, ".ASCII_P") + strlen(".ASCII_P") + 1; /* Get begin of argument, including blanks, so no tokenize! */ + + remove_leading_blanks(p); + if (*p++ != '"') /* No double quote found! */ + { + sprintf(entry->error_text, "Line %d: Did not find opening double quote!", line_counter); + printf("assemble: %s\n", entry->error_text); + error_counter++; + continue; + } + + if (!(entry->data = (int *) malloc(strlen(p) * sizeof(int) + 1))) /* Maybe one word too much due to trailing " */ + { + printf("assemble (.ASCII_W/.ASCII_P): Out of memory, could not allocate %d words of memory!", (int) strlen(p)); + return -1; + } + + for (special_char = i = 0; i < strlen(p) && *(p + i) != '"'; i++, address++) + { + if (*(p + i) == '\\') + special_char = 1; + else if (special_char && *(p + i) == 'n') + { + *(entry->data + i - 1) = (char) 13; + *(entry->data + i) = (char) 10; + special_char = 0; + } + else if (special_char && *(p + i) != 'n') + { + *(entry->data + i - 1) = *(p + i - 1); + *(entry->data + i) = *(p + i); + } + else + { + special_char = 0; + *(entry->data + i) = 0xff & *(p + i); + } + } + + if (!strcmp(entry->mnemonic, ".ASCII_W")) /* No terminating zero word in case of .ASCII_P. */ + { + *(entry->data + i) = 0; + address++; + } + + if (*(p + i) != '"') + { + sprintf(entry->error_text, "Line %d: WARNING - Did not find closing double quote!", line_counter); + printf("assembler: %s\n", entry->error_text); + } + + entry->number_of_words = i; + if (!strcmp(entry->mnemonic, ".ASCII_W")) + entry->number_of_words++; + + entry->state = STATE$FINISHED; + } + else if (!strcmp(entry->mnemonic, ".BLOCK")) /* .BLOCK expects one argument being the size of the block to be reserved */ + { + token = tokenize((char *) 0, delimiters); /* Get size of block */ + size = str2int(token); + + if (!(entry->data = (int *) malloc(size * sizeof(int)))) + { + printf("assemble (.BLOCK): Out of memory, could not allocate %d words of memory!", (int) strlen(p)); + return -1; + } + + for (i = 0; i < size; i++, address++) + *(entry->data + i) = 0; + + entry->number_of_words = size; + entry->state = STATE$FINISHED; + } + else if (!strcmp(entry->mnemonic, ".EQU")) /* Introduce a string which will equal some value */ + { + token = tokenize((char *) 0, delimiters); + if (insert_into_equ_list(entry->label, str2int(token))) + { + /* + ** Design bug: Since an EQU does not get a corresponding code entry, the following + ** error message will only printed to stdout but not occur in the resulting listing! + */ + sprintf(entry->error_text, "Line %d: Duplicate equ-entry '%s'.", line_counter, entry->label); + printf("assemble: %s\n", entry->error_text); + error_counter++; + } + *(entry->label) = (char) 0; + entry->state = STATE$FINISHED; + entry->address = -1; + } + else + { + sprintf(entry->error_text, "Line %d: Unknown directive >>%s<<. Very strange!", line_counter, entry->mnemonic); + printf("assemble: %s\n", entry->error_text); + error_counter++; + continue; + } + } + else if (entry->opcode_type == INSTRUCTION$NORMAL) /* A simple mnemonic */ + { + entry->number_of_words = 1; /* At least one word to hold the instruction! */ + number_of_operands = entry->opcode == 0xe ? 0 : 2; /* All instructions except HALT require two operands. */ + + if (number_of_operands) /* Read operands. */ + { + if (!(token = tokenize((char *) 0, delimiters))) + { + sprintf(entry->error_text, "Line %d: No first operand found! (%s)", line_counter, entry->source); + printf("assemble: %s\n", entry->error_text); + error_counter++; + continue; + } + strcpy(entry->src_op, token); + + /* Determine type of first operand. */ + if ((entry->src_op_type = decode_operand(entry->src_op, &entry->src_op_code)) == OPERAND$ILLEGAL) + { + sprintf(entry->error_text, "Line %d: Illegal first operand! (%s)", line_counter, entry->source); + printf("assemble: %s\n", entry->error_text); + error_counter++; + continue; + } + + if (entry->src_op_type == OPERAND$CONSTANT || entry->src_op_type == OPERAND$LABEL_EQU) + { + entry->number_of_words++; + address++; + } + + if (number_of_operands > 1) + { + if (!(token = tokenize((char *) 0, delimiters))) + { + sprintf(entry->error_text, "Line %d: No second operand found! (%s)", line_counter, entry->source); + printf("assemble: %s\n", entry->error_text); + error_counter++; + continue; + } + strcpy(entry->dest_op, token); + + /* Determine the type of the second operand. */ + if ((entry->dest_op_type = decode_operand(entry->dest_op, &entry->dest_op_code)) == OPERAND$ILLEGAL) + { + sprintf(entry->error_text, "Line %d: Illegal second operand! (%s)", line_counter, entry->source); + printf("assemble: %s\n", entry->error_text); + error_counter++; + continue; + } + + if ((entry->dest_op_type == OPERAND$CONSTANT || entry->dest_op_type == OPERAND$LABEL_EQU) && + strcmp(entry->mnemonic, "CMP")) /* A constant as destination is OK only for CMP! */ + { + entry->number_of_words++; + address++; + sprintf(entry->error_text, "Line %d: A constant as destination operand ('%s') may not be what you wanted.", + line_counter, entry->dest_op); + printf("assemble: %s\n", entry->error_text); + /* This is just a warning, so no increment of error_counter is necessary! */ + } + } + } + + if (!(entry->data = (int *) malloc(entry->number_of_words * sizeof(int)))) + { + printf("assemble: Out of memory, could not allocate %d words of memory!", (int) strlen(p)); + return -1; + } + + entry->data[0] = (entry->opcode << 12 | ((entry->src_op_code & 0x3f) << 6) | ((entry->dest_op_code) & 0x3f)) & 0xffff; + + i = 1; + if (entry->src_op_type == OPERAND$CONSTANT) + *(entry->data + i++) = str2int(entry->src_op) & 0xffff; + + if (entry->dest_op_type == OPERAND$CONSTANT) + *(entry->data + i) = str2int(entry->dest_op) & 0xffff; + } + else if (entry->opcode_type == INSTRUCTION$BRANCH) /* Ups, a branch! */ + { + entry->number_of_words = 1; /* At least one word to hold the instruction! */ + + /* A branch always has two operands! */ + if (!(token = tokenize((char *) 0, delimiters))) + { + sprintf(entry->error_text, "Line %d: No first branch operand found! (%s)", line_counter, entry->source); + printf("assemble: %s\n", entry->error_text); + error_counter++; + continue; + } + strcpy(entry->src_op, token); + + if (!(token = tokenize((char *) 0, delimiters))) + { + sprintf(entry->error_text, "Line %d: No second branch operand found! (%s)", line_counter, entry->source); + printf("assemble: %s\n", entry->error_text); + error_counter++; + continue; + } + strcpy(entry->dest_op, token); + + /* Now we have both operands of the branch and the branch itself as well. Decode the first operand. */ + if ((entry->src_op_type = decode_operand(entry->src_op, &entry->src_op_code)) == OPERAND$ILLEGAL) + { + sprintf(entry->error_text, "Line %d: Illegal first operand! (%s)", line_counter, entry->source); + printf("assemble: %s\n", entry->error_text); + error_counter++; + continue; + } + + /* Now decode the second operand: Is it negated? Which flag is it? */ + p = entry->dest_op; + if ((negate = (*(entry->dest_op) == '!'))) + p++; + + for (flag = 0; sr_bits[flag]; flag++) + if (sr_bits[flag] == *p) + break; + + if (flag > 7) + { + sprintf(entry->error_text, "Line %d: Illegal condition flag! (%s)", line_counter, entry->source); + printf("assemble: %s\n", entry->error_text); + error_counter++; + continue; + } + + /* Now prepare for memory allocation and construction of the instruction itself. */ + if (entry->src_op_type == OPERAND$CONSTANT || entry->src_op_type == OPERAND$LABEL_EQU) + { + entry->number_of_words++; + address++; + } + + if (!(entry->data = (int *) malloc(entry->number_of_words * sizeof(int)))) + { + printf("assemble: Out of memory, could not allocate %d words of memory!", (int) strlen(p)); + return -1; + } + + /* Assemble the instruction itself */ + entry->data[0] = (0xf000 | + ((entry->src_op_code & 0x3f) << 6) | ((entry->opcode & 3) << 4) | ((negate & 1) << 3) | (flag & 0x7)) + & 0xffff; + + if (entry->src_op_type == OPERAND$CONSTANT) /* Labels are no constants in this context since they are not known in advance */ +/* entry->data[1] = (str2int(entry->src_op) - address - 1) & 0xffff; This was the old behaviour */ + entry->data[1] = str2int(entry->src_op) & 0xffff; + } + else + { + sprintf(entry->error_text, "Line %d: Unknown opcode type %d! Very strange error!", line_counter, entry->opcode_type); + printf("assemble: %s\n", entry->error_text); + error_counter++; + } + address++; + } + + /* Second pass: */ +#ifdef DEBUG + printf("assemble: Starting second pass.\n"); +#endif + for (entry = gbl$data; entry; entry = entry -> next) + { + i = 1; /* Index for data word array */ + if (entry->src_op_type == OPERAND$LABEL_EQU) /* Still unresolved label or equ! */ + { + value = 0; + if (search_equ_list(entry->src_op, &value)) + if (find_label(entry->src_op, &value)) + { + sprintf(entry->error_text, "Line %d: Unresolved label or equ >>%s<src_op); + printf("assemble: %s\n", entry->error_text); + error_counter++; + continue; + } + + if (entry->opcode_type == INSTRUCTION$BRANCH && *entry->mnemonic == 'R') /* Relative branch or subroutine call */ + entry->data[i++] = (value - entry->address - 2) & 0xffff; /* - 2 since address is a constant and occupies the next cell */ + else + entry->data[i++] = value; + } + + if (entry->dest_op_type == OPERAND$LABEL_EQU) /* Still unresolved label or equ! */ + { + value = 0; + if (search_equ_list(entry->dest_op, &value)) + if (find_label(entry->dest_op, &value)) + { + sprintf(entry->error_text, "Line %d: Unresolved label or equ >>%s<dest_op); + printf("assemble: %s\n", entry->error_text); + error_counter++; + continue; + } + + entry->data[i] = value; + } + } + + return error_counter; +} + +/* +** write_result scans the complete linked list containing the source code as well as the resulting binary code and creates a +** (binary) output file and the corresponting listing file. +*/ +int write_result(char *output_file_name, char *listing_file_name) +{ + int line_counter, i; + char address_string[STRING_LENGTH], data_string[STRING_LENGTH], line[STRING_LENGTH], second_word[STRING_LENGTH]; + FILE *output_handle, /* file handle for binary output data */ + *listing_handle; + data_structure *entry; + equ_structure *equ; + + if (!(output_handle = fopen(output_file_name, "w"))) + { + printf("write_result: Unable to open output file >>%s<>%s<next) + { + /* Write listing */ + if (*(entry->error_text)) /* If there was an error, print it preceeding the erroneous line */ + fprintf(listing_handle, "\n*** %s ***\n", entry->error_text); + + *address_string = *data_string = *second_word = (char) 0; + if (entry->address != -1) + { + sprintf(address_string, "%04X", entry->address & 0xffff); + if (entry->number_of_words) + sprintf(data_string, "%04X", entry->data[0] & 0xffff); + } + + if (entry->number_of_words == 2) /* Many instructions require two words, but should be displayed in a single line */ + sprintf(second_word, "%04X", entry->data[1] & 0xffff); + + expand_tabs(line, entry->source); + fprintf(listing_handle, "%06d %4s %4s %4s %s\n", ++line_counter, address_string, data_string, second_word, line); + if (entry->address != -1) /* Write binary data */ + fprintf(output_handle, "0x%4s 0x%4s\n", address_string, data_string); + + for (i = 1; i < entry->number_of_words; i++) /* If there is additional data as in .ASCII_W, write it */ + { + if (entry->number_of_words > 2) + fprintf(listing_handle, " %04X %04X\n", entry->address + i, entry->data[i]); + fprintf(output_handle, "0x%04X 0x%04X\n", entry->address + i, entry->data[i]); + } + } + + /* Generate a list of defined EQUs */ + fprintf(listing_handle, + "\n\nEQU-list:\n--------------------------------------------------------------------------------------------------------"); + for (i = 0, equ = gbl$equs; equ; equ = equ->next, i++) + { + if (!(i % 3)) + fprintf(listing_handle, "\n"); + fprintf(listing_handle, "%-24s: 0x%04X ", equ->name, equ->value & 0xffff); + } + + /* Generate a list of labels */ + fprintf(listing_handle, "\n\nLabel-list:\n--------------------------------------------------------------------------------------------------------"); + for (i = 0, entry = gbl$data; entry; entry = entry->next) + { + if(!*(entry->label)) + continue; + + if (!(i++ % 3)) + fprintf(listing_handle, "\n"); + fprintf(listing_handle, "%-24s: 0x%04X ", entry->label, entry->address & 0xffff); + } + fprintf(listing_handle, "\n"); + + fclose(output_handle); + fclose(listing_handle); + + return 0; +} + +int main(int argc, char **argv) +{ + int rc; + char *source_file_name, output_file_name[STRING_LENGTH], listing_file_name[STRING_LENGTH]; + + if (argc < 2 || argc > 4) + print_help(); + else + { + source_file_name = argv[1]; + *output_file_name = *listing_file_name = (char) 0; + + if (argc > 2) /* Output file name explicitly stated */ + { + strcpy(output_file_name, argv[2]); + if (argc > 3) /* Listing file name explicitly stated */ + strcpy(listing_file_name, argv[3]); + } + + if (!*output_file_name) + replace_extension(output_file_name, source_file_name, "bin"); + + if (!*listing_file_name) + { + if (*output_file_name) + replace_extension(listing_file_name, output_file_name, "lis"); + else + replace_extension(listing_file_name, source_file_name, "lis"); + } + +#ifdef VERBOSE + printf("Reading >>%s<<, writing >>%s<< (bin) and >>%s<< (lis).\n", + source_file_name, output_file_name, listing_file_name); +#endif + + if ((rc = read_source(source_file_name))) + return rc; + + if ((rc = assemble()) > 0) + { + printf("main: There were %d errors during assembly! No files written!\n", rc); + return rc; + } + else if (rc < 0) + { + printf("main: There was an unrecoverable error during assembly (%d)! No files written!\n", rc); + return rc; + } + + if ((rc = write_result(output_file_name, listing_file_name))) + return rc; + } + + return 0; +} diff --git a/vhdl/env1_globals.vhd b/vhdl/env1_globals.vhd index 31c97c1d..063ac77a 100644 --- a/vhdl/env1_globals.vhd +++ b/vhdl/env1_globals.vhd @@ -10,16 +10,16 @@ use IEEE.STD_LOGIC_1164.all; package env1_globals is -- file name and file size (in lines) of the file that is converted to the ROM located at 0x0000 -constant ROM_FILE : string := "../test_programs/regbank.rom"; -constant ROM_SIZE : integer := 87; +constant ROM_FILE : string := "../test_programs/bram.rom"; +constant ROM_SIZE : integer := 34; -- size of lower register bank: should be 256 -- set to 16 during development for faster synthesis, routing, etc. -constant SHADOW_REGFILE_SIZE : integer := 256; +constant SHADOW_REGFILE_SIZE : integer := 16; -- size of the block RAM in 16bit words: should be 32768 -- set to 256 during development for tracability during simulation -constant BLOCK_RAM_SIZE : integer := 32768; +constant BLOCK_RAM_SIZE : integer := 256; end env1_globals; diff --git a/vhdl/qnice_cpu.vhd b/vhdl/qnice_cpu.vhd index b4ecd206..793ae69c 100644 --- a/vhdl/qnice_cpu.vhd +++ b/vhdl/qnice_cpu.vhd @@ -35,16 +35,16 @@ architecture beh of QNICE_CPU is component TriState_Buffer is generic ( DATA_WIDTH : integer range 1 to 32 -); -port ( +); +port ( I_CLK : in std_logic; -- synchronized with bidir bus - I_DIR_CTRL : in std_logic; -- 3-state enable input, high=output, low=input - + I_DIR_CTRL : in std_logic; -- 3-state enable input, high=output, low=input + IO_DATA : inout std_logic_vector(DATA_WIDTH - 1 downto 0); -- data to/from external pin on bidir bus - + I_DATA_TO_EXTERNAL : in std_logic_vector(DATA_WIDTH - 1 downto 0); -- data to send over bidir bus O_DATA_FROM_EXTERNAL : out std_logic_vector(DATA_WIDTH - 1 downto 0) -- data received over bidir bus -); +); end component; -- QNICE specific register file @@ -334,15 +334,39 @@ begin fsmCpuDataValid <= '0'; fsmNextCpuState <= cs_std_seq; fsmInstruction <= (others => '0'); - + +-- BTW: as soon as all this works, plus some more unit tests especially for some to be found complicated BRAM borderline +-- cases (e.g. ADD @R1, @R2, everything in RAM plus before and after some instructions that challenge the asyncreset), +-- I should checkin the ISA V1.2 docu PDF (create a qnice/doc folder) +-- check in the current assembler and stuff (inside the qnice folder), the emulator (within qnice folder, and the other +-- material from there) and call it "V1.0" (before nerd session), because this is then a fully fledged +-- QNICE CPU working in the initival ENV1 scenario. V1.1 could then be: plus UART plus Bernd's monitor is running. +-- Later, V2.0 might be the new ISA plus another scenario than env1, e.g. QBM-1. +-- the tagging in GIT/GITHUB is important, so that in a later "retro" session, we can compare ISA 1.2 in env1 with +-- a full "QBM-1" computer having the new ISA (e.g. new CMP command, etc.). +-- Update all tools in qnice folder to vaxman's latest material from SourceForge. + +-- @TODO (priority 2): Completely re-do the Tristate buffer topic. The lenghty wait-state and the #0000 situation when +-- writing are not necessary! As we have enough flip-flops, the Tristate handling can be done in "real-time". +-- as this has probably some pretty far reaching impact, the question is when to implement this. On the other hand, +-- the longer we wait, the bigger the impact will be. Some analysis with some MOVE, ADD & Co indirect @memory read/write +-- scenario will for sure help and create a more clear view. + -- as the previous state sets the direction control to read and the address to a meaningful value -- (i.e. 0 after cs_reset or current PC afterwards), the DATA_driver will take care, that at the -- falling edge of cs_fetch's clock cycle, DATA_From_Bus will contain the next opcode when cs_fetch => - fsmInstruction <= DATA_From_Bus; -- valid at falling edge - fsmPC <= PC + 1; - fsm_reg_read_addr1 <= DATA_From_Bus(11 downto 8); -- read Src register number - fsm_reg_read_addr2 <= DATA_From_Bus(5 downto 2); -- rest Dst register number + -- add wait cycles, if necessary (e.g. due to slow RAM) + if WAIT_FOR_DATA = '1' then + fsmNextCpuState <= cs_fetch; + + -- data from bus is available + else + fsmInstruction <= DATA_From_Bus; -- valid at falling edge + fsmPC <= PC + 1; + fsm_reg_read_addr1 <= DATA_From_Bus(11 downto 8); -- read Src register number + fsm_reg_read_addr2 <= DATA_From_Bus(5 downto 2); -- rest Dst register number + end if; when cs_decode => -- source and destination values in case of direct register addressing modes @@ -418,8 +442,7 @@ begin else -- read the indirect value from the bus and store it fsmSrc_Value <= DATA_FROM_Bus; - - + -- perform post increment if Src_Mode = amIndirPostInc then -- special handling of SR and PC as they are not stored in the register file @@ -583,7 +606,7 @@ begin end case; end if; end if; - + when cs_exepost_sub => fsmDataToBus <= DATA_To_Bus; fsmCpuDataDirCtrl <= '1';