Retrocomputers are of varying degrees of fastidiousness. Some are content with emulation. Others prefer FPGA, because then it turns out not emulation, but re-creation. Finally, give the third a real processor.
But the processor needs so much to work! Again, the dilemma: to take real microcircuits of the same years, or put everything in the FPGA, leaving the processor outside? However, why must FPGA? Long live the union of Arduino and the classic processor!
Give your Arduino a “second brain” and make it “smarter”.
')
This eight-bit microprocessor executes programs, and the Arduino emulates ROM, RAM, and simple peripherals.
Design the virtual peripherals in the Arduino IDE, and on the microprocessor, run the code in assembler. No need to collect complex schemes and flash parallel ROMs.
Supported microprocessors: 6502, 6809 and Z80 (18581), others are on the way.
Shield with a microprocessor does not interfere with connecting other shields: with LCD, memory cards, etc.
In addition to independent programming in assembler, you can try to run on a microprocessor some classic code.
True, the microprocessor will operate at a very small frequency - about 95 kHz, its exact value depends on the optimization of the peripheral emulation code.
The distribution of the address space is set programmatically in the sketch. The microprocessor can allocate from 4 to 6 KB of RAM of 8 KB, available on the Arduino Mega. ROM can be allocated more than 200 KB of the existing 256.
Using the Arduino Mega serial port, you can emulate a UART.
Schemes, circuit boards, Gerber files are available under CC-BY-SA 4.0
here . In this case, there is a requirement to necessarily attach the file README.md, because it contains the following warning:
Do not connect the shield until it is filled with a sketch of emulation of the periphery! Otherwise it is possible to short the output lines of the microprocessor.
Yes, and in the sketch itself, something needs to be redone carefully for the same reason.
Diagram of the device at 6502:
Diagram of the device at 6809:
Diagram of the device on the Z80:
Already you can run:
On a device with 6502 -
Apple I, Woz Monitor + ROM with BASIC
On a device with 6809 -
Analogue of the self-made computer Simon6809 of the same developer, a training monitor with assembler and disassembler
On the device with the Z80, for the time being, only
a serial port echo test allows you to test the operability of the virtual 8251 (58051).
Peripheral emulation firmware - MIT licensed.
Brief descriptions of the principle of action:
To device on 6502
To device on 6809
To the device on the Z80 - in preparation.
The developer is trying to
sell the device, but with delivery only in the USA. It makes no sense to buy, because the scheme is very simple, you can repeat it on a piece of the layout in an hour.
The development of similar boards for RCA1802, 68008, 8085 (KR1821VM85A), 8088 (KR1810VM88) is planned. About K1801VM1 not said, but you can throw the author such an idea.
Files:
To the device on 6502:
assembly instructions ,
screen printing ,
scheme
To the device at 6809:
assembly instructions ,
screen printing ,
scheme
To the device on the Z80:
assembly instructions ,
screen printing ,
scheme
Consider the interaction of the Arduino and the device at 6502. Arduino periodically changes the level at the input of the microprocessor, designed to supply clock pulses, from zero to one and back. At each time it checks what is happening on the control lines and the address bus, and, depending on the situation, it reads information from the data bus or sends it there. Arduino can also control IRQ and NMI lines, causing interrupts. The figure shows the types of data and their direction of transmission:
Conformity of ports Arduino and conclusions of the microprocessor is configured in the sketch:
/* Digital Pin Assignments */ #define DATA_OUT PORTL #define DATA_IN PINL #define ADDR_H PINC #define ADDR_L PINA #define ADDR ((unsigned int) (ADDR_H << 8 | ADDR_L)) #define uP_RESET_N 38 #define uP_RW_N 40 #define uP_RDY 39 #define uP_SO_N 41 #define uP_IRQ_N 50 #define uP_NMI_N 51 #define uP_E 52 #define uP_GPIO 53
We divide each measure into the following events:
CLK changes state from one to zero (decay)
CLK is in zero state
CLK changes state from one to zero (increment)
CLK is in unit state
CLK changes state from one to zero again ...
What happens at the time of the change of states?
6502 receives clock pulses at the CLK0 input, buffers them and sends them to two outputs: CLK1 and CLK2. Although in the microprocessor all events are tied to CLK1, we will assume that the delay is small and they are tied to CLK0 - the line through which the microprocessor receives clock pulses from the Arduino. And call the signal just CLK.
1. CLK changes state from one to zero.
2. The microprocessor outputs the new address to the address bus, and the R / W output - a switching signal between reading and writing. But he is not yet ready to exchange data.
3. CLK enters the state of one, and this means that the exchange of data has begun. If this is a read operation, the microprocessor transfers the data bus pins to the input state and receives the data, and if the write operation translates them to the output state and sends the data. And the R / W signal switches the external device to write or read mode, opposite to the corresponding state of the microprocessor.
4. CLK goes to zero state. Now neither the microprocessor nor the I / O devices output anything to the data bus. The microprocessor can set the data bus line and R / W pin to a new state.
A simple explanation, understandable and child. Who will never think about these "backstage intrigues", if only microcontrollers are programmed. Even in assembly language.
If you need to connect your peripheral device, it must have time to prepare the data before the unit appears on the CLK line (preparation time), but for now there is one unit - not to change them. If the peripheral device does not have time to prepare the data, until the CLK is zero, or it changes it when there is one, you will long wonder why your code is not working. Since the microprocessor clock frequency here is ten to fifteen times lower than the nominal one, it is easy to comply with this requirement. But it is necessary.
So, you need to “teach” Arduino to generate clock pulses, continuously checking what happens on the address bus and the R / W line, and interacting with the data bus accordingly. To do this, the timer timer interrupt is activated in the sketch, generating pulses with a frequency of 95 kHz. Arduino works much faster than a microprocessor, and therefore it has time to read and prepare everything between its cycles. It is important to ensure that after modifying the sketch this condition continues to be observed.
Here is an excerpt from the sketch, which makes it clear how the CLK goes from zero to one, and what happens next:
//////////////////////////////////////////////////////////////////// // Processor Control Loop //////////////////////////////////////////////////////////////////// // This is where the action is. // it reads processor control signals and acts accordingly. // ISR(TIMER1_COMPA_vect) { // Drive CLK high CLK_E_HIGH; // Let's capture the ADDR bus uP_ADDR = ADDR; if (STATE_RW_N) ////////////////////////////////////////////////////////////////// // HIGH = READ transaction { // uP wants to read so Arduino to drive databus to uP: DATA_DIR = DIR_OUT; // Check what device uP_ADDR corresponds to: // ROM? if ( (ROM_START <= uP_ADDR) && (uP_ADDR <= ROM_END) ) DATA_OUT = pgm_read_byte_near(rom_bin + (uP_ADDR - ROM_START)); else if ( (BASIC_START <= uP_ADDR) && (uP_ADDR <= BASIC_END) ) DATA_OUT = pgm_read_byte_near(basic_bin + (uP_ADDR - BASIC_START)); else // RAM? if ( (uP_ADDR <= RAM_END) && (RAM_START <= uP_ADDR) ) DATA_OUT = RAM[uP_ADDR - RAM_START]; else // 6821? if ( KBD <=uP_ADDR && uP_ADDR <= DSPCR ) { // KBD? if (uP_ADDR == KBD) { ... // handle KBD register } else // KBDCR? if (uP_ADDR == KBDCR) { ... // handle KBDCR register } else // DSP? if (uP_ADDR == DSP) { ... // handle DSP register } else // DSPCR? if (uP_ADDR == DSPCR) { ... // handle DSPCR register } } } else ////////////////////////////////////////////////////////////////// // R/W = LOW = WRITE { // RAM? if ( (uP_ADDR <= RAM_END) && (RAM_START <= uP_ADDR) ) RAM[uP_ADDR - RAM_START] = DATA_IN; else // 6821? if ( KBD <=uP_ADDR && uP_ADDR <= DSPCR ) { // KBD? if (uP_ADDR == KBD) { ... // handle KBD register } else // KBDCR? if (uP_ADDR == KBDCR) { ... // handle KBDCR register } else // DSP? if (uP_ADDR == DSP) { ... // handle DSP register } else // DSPCR? if (uP_ADDR == DSPCR) { ... // handle DSPCR register } } } //////////////////////////////////////////////////////////////// // We are done with this cycle. // one full cycle complete clock_cycle_count ++; // start next cycle CLK_E_LOW; // If Arduino was driving the bus, no need anymore. // natural delay for DATA Hold time after CLK goes low (t_HR) DATA_DIR = DIR_IN; }
The distribution of the address space can be done in any way; in an unmodified sketch, it is the same as in Apple 1 with 256 bytes of ROM, 8 kilobytes of ROM for BASIC, 4 kilobytes of RAM and an input-output device 6821.
// MEMORY LAYOUT // 4K MEMORY #define RAM_START 0x0000 #define RAM_END 0x0FFF byte RAM[RAM_END-RAM_START+1]; // ROMs (Monitor + Basic) #define ROM_START 0xFF00 #define ROM_END 0xFFFF #define BASIC_START 0xE000 #define BASIC_END 0xEFFF //////////////////////////////////////////////////////////////////// // Woz Monitor Code //////////////////////////////////////////////////////////////////// // PROGMEM const unsigned char rom_bin[] = { 0xd8, 0x58, 0xa0, 0x7f, 0x8c, 0x12, 0xd0, 0xa9, 0xa7, 0x8d, 0x11, 0xd0, ... 0x00, 0xff, 0x00, 0x00 }; // BASIC ROM starts at E000 PROGMEM const unsigned char basic_bin[] = { 0x4C, 0xB0, 0xE2, 0xAD, 0x11, 0xD0, 0x10, 0xFB, ... 0xE0, 0x80, 0xD0, 0x01, 0x88, 0x4C, 0x0C, 0xE0 };
RAM is emulated by the byte RAM array [RAM_END-RAM_START + 1]. Two PROGMEM keywords are needed so that the contents of the emulated ROMs are stored in the flash memory of the microcontroller.
6821 is emulated sufficiently so that the virtual keyboard and display work through the terminalku. Woz Monitor and BASIC work, which the author wanted.
To emulate any peripheral device, you need to carefully read its datasheet and find out what registers it has and what they are for. The convenience of emulation is in the flexibility with which software analogs of the periphery can be made.
I / O devices are located in the address space of the microprocessor, they are accessed in the same way as memory cells. To use "iron" peripherals, such as LCD display, memory card, sound output, you need to allocate them a place in the address space.
References:
www.6502.org
www.callapple.org/soft/ap1/emul.html
skilldrick.imtqy.com/easy6502
searle.hostei.com/grant/6502/Simple6502.html
wilsonminesco.com/6502primer
SB-Assembler:
www.sbprojects.net/sbasm
Go to 6809, it has:
Two eight-bit batteries A and B, which can be combined into one six-bit battery
Two 16-bit index stack pointers
Addressing the team counter
Automatic addition or subtraction of the number 1 or 2
Multiplication of two eight-bit unsigned numbers
16-bit arithmetic
Transfer and exchange of data between all registers
Record and read all registers and any combination of them
The microprocessor 6809E (external) needs an external clock, in 6809 it is internal. In Hitachi, they are called, respectively, 6309E and 6309, they differ from the usual ones in that they perform operations inside the 32-bit form, but it is possible to switch to compatibility mode with the classic version.
Actually, the whole project of RetroShield started because the author wanted to upgrade his own self-made computer Simon6809 and name the result Simon6809 Turbo. But it turned out that standard logic chips for everything he wanted to implement there would have required a lot. Therefore, the author first formulated the idea of ​​RetroShield for the 6809, and only then thought: “what if you do the same with other processors?”.
The device, of course, used 6809E, which requires an external clock generator, so that you can synchronize its work from the outside. Lines E and Q are the same for both processors, only 6809 have outputs, and 6809E have inputs.
With the 6809, the Arduino interacts the same way as with the 6502, but it has two clock inputs: E and Q, and three interrupt inputs: IRQ, FIRQ and NMI.
This time the matching of the Arduino ports and the microprocessor pins is configured as:
/* Digital Pin Assignments */ #define DATA_OUT PORTL #define DATA_IN PINL #define ADDR_H PINC #define ADDR_L PINA #define ADDR ((unsigned int) (ADDR_H << 8 | ADDR_L)) #define uP_RESET_N 38 #define uP_E 52 #define uP_Q 53 #define uP_RW_N 40 #define uP_FIRQ_N 41 #define uP_IRQ_N 50 #define uP_NMI_N 51 #define uP_GPIO 39
As can be seen from the graphs, the signal Q is shifted relative to E by a quarter of the period:
We almost will not pay attention to Q, since all events are tied to E. And everything happens like this:
- E switches to zero. The processor places a new address on the address bus and changes the state of the R / W line.
- E switches to one, the processor becomes ready for data exchange.
- It doesn’t matter what happens with the data bus, as long as there is a E unit, the main thing is that the required data is present there at the moment E goes back to zero.
- When reading data, the I / O device must supply the required data to the data bus before line E goes from one to zero (the minimum delay is indicated by the number 17 in a circle).
- When writing, the I / O device must record the data in some register as it was at the moment E goes from one to zero. The processor will supply this data on the bus even earlier - at the moment Q goes to the unit (the number 20 in a circle).
- After the transition E to zero, everything repeats.
All of the above about 6502 about the need for a peripheral device (including virtual) to generate all signals in time also concerns 6809.
Generation of signals E and Q, as in the case of 6502, with the only difference that the signals are two, and they must be switched in accordance with the graphs. And in the same way, a subroutine called by an interrupt performs data input or output at the required moments.
The address space in the unmodified sketch is distributed in the same way as in the homemade
Simon6809 computer:
// MEMORY #define RAM_START 0x0000 #define RAM_END 0x0FFF #define ROM_START 0xE000 #define ROM_END 0xFFFF byte RAM[RAM_END-RAM_START+1]; //////////////////////////////////////////////////////////////////// // Monitor Code //////////////////////////////////////////////////////////////////// // static const unsigned char PROGMEM const unsigned char simon09_bin[] = { 0x1a, 0xff, 0x4f, 0x1f, 0x8b, 0x0f, 0x36, 0x7f, 0x01, 0xa5, 0x10, 0xce, ... 0x00, 0x09, 0x00, 0x0c, 0x00, 0x0f, 0xe0, 0x00 };
RAM and ROM are stored in arrays in the same way as in the 6502 version, with the only difference being that there is one array with ROM data.
I / O devices are also allocated areas of the address space, and they can be both virtual and real. Since Simon6809 is a modern machine on a vintage element base, with a PC running the terminal, it communicates via FTDI. Here it is emulated.
References:
A lot of information on 6809 on the
Arto Page
Wikipedia article on 6809
SWTPc 6809 systems
Wikipedia article on the FLEX operating system