Retrocomputadores vêm em vários graus de mimação. Alguns estão contentes com emulação. Outros preferem FPGAs, porque então não acontece emulação, mas recreação. Por fim, sirva o terceiro processador.
Mas o processador precisa de muito trabalho! Novamente, o dilema: pegar chips reais dos mesmos anos ou colocar tudo no FPGA, deixando o processador de fora? No entanto, por que o FPGA é necessário? Viva a união do Arduino e do processador clássico!
Dê ao seu Arduino um "segundo cérebro" e torne-o mais inteligente.
Um verdadeiro microprocessador de oito bits executa programas, enquanto um Arduino emula ROM, RAM e periféricos simples.
Projete periféricos virtuais no Arduino IDE e execute o código do assembler no microprocessador. Não há necessidade de montar circuitos complexos e ROMs paralelas em flash.
Microprocessadores suportados: 6502, 6809 e Z80 (18581), outros estão a caminho.
Uma blindagem com um microprocessador não interfere na conexão de outras blindagens: com LCDs, cartões de memória etc.
Além da linguagem de auto-montagem, você pode tentar executar algum código clássico no microprocessador.
É verdade que o microprocessador funcionará a uma frequência muito baixa - cerca de 95 kHz, seu valor exato depende da otimização do código de emulação periférica.
A distribuição do espaço de endereço é definida programaticamente em um esboço. O microprocessador pode ser alocado de 4 a 6 kB de 8 kB de RAM disponível no Arduino Mega. A ROM pode alocar mais de 200 kB dos 256 disponíveis.
A porta serial Arduino Mega pode emular o UART.
Circuitos, desenhos de placas e arquivos Gerber estão disponíveis no CC-BY-SA 4.0
aqui . Ao mesmo tempo, há um requisito para anexar o arquivo README.md, pois contém o seguinte aviso:
Não conecte a blindagem até que o esboço da emulação periférica seja carregado! Caso contrário, é possível um curto-circuito nas linhas de saída do microprocessador.
Sim, e no próprio esboço, algo precisa ser refeito com cuidado pelo mesmo motivo.
O esquema do dispositivo em 6502:

O esquema do dispositivo em 6809:

Esquema do dispositivo no Z80:

Você já pode executar:
Em um dispositivo com 6502 -
Apple I, Woz Monitor + ROM com BASICEm um dispositivo com 6809 -
um análogo de um computador caseiro Simon6809 do mesmo desenvolvedor, um monitor de treinamento com montador e desmontadorEm um dispositivo com Z80 - até agora, apenas um
teste de eco da porta serial , permitindo verificar o desempenho do 8251 virtual (KR580VV51A).
Firmware para emular periféricos - sob a licença MIT.
Breves descrições do princípio de ação:
Para o dispositivo 6502Para o dispositivo em 6809Para o dispositivo no Z80 - em preparação.
O desenvolvedor está tentando
vender dispositivos, mas com entrega apenas nos Estados Unidos. Não há nenhuma razão específica para comprar, porque o esquema é muito simples, você pode repeti-lo em um pedaço da tábua de pão em uma hora.
Está planejado o desenvolvimento de placas semelhantes nos modelos RCA1802, 68008, 8085 (182185), 8088 (181088). Sobre o K1801BM1 não é mencionado, mas você pode lançar essa idéia ao autor.
Arquivos:
Para o dispositivo 6502:
instruções de montagem ,
serigrafia ,
diagramaPara dispositivo no 6809:
instruções de montagem ,
serigrafia ,
diagramaPara o dispositivo no Z80:
instruções de montagem ,
serigrafia ,
diagramaConsidere a interação de um Arduino e um dispositivo 6502. O Arduino altera periodicamente o nível na entrada de um microprocessador projetado para fornecer pulsos de clock de zero a um e vice-versa. A cada ciclo, ele verifica o que está acontecendo nas linhas de controle e no barramento de endereços e, dependendo da situação, lê as informações do barramento de dados ou as envia para lá. O Arduino também pode controlar as linhas de IRQ e NMI, causando interrupções. A figura mostra os tipos de dados e as direções de sua transmissão:

A correspondência das portas do Arduino e das saídas do microprocessador é configurada no esboço:
/* 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
Dividiremos cada medida nos seguintes eventos:
CLK altera o estado de um para zero (declínio)
CLK está em um estado zero
CLK muda de estado de um para zero (aumento)
CLK está no estado da unidade
O CLK novamente altera o estado de um para zero ...
O que acontece durante momentos de transição de estado?
6502 recebe pulsos de clock na entrada CLK0, os armazena em buffer e os envia para duas saídas: CLK1 e CLK2. Embora todos os eventos no microprocessador estejam vinculados ao CLK1, supomos que o atraso não seja grande e eles estão vinculados ao CLK0 - a linha na qual o microprocessador recebe pulsos de clock do Arduino. E chame o sinal apenas CLK.

1. CLK muda de estado de um para zero.
2. O microprocessador envia um novo endereço para o barramento de endereços e um sinal de comutação entre leitura e gravação na saída R / W. Mas ele ainda não está pronto para a troca de dados.
3. O CLK entra no estado da unidade e isso significa que a troca de dados começou. Se for uma operação de leitura, o microprocessador transfere as saídas do barramento de dados para o estado de entrada e recebe dados e, se a operação de gravação, transfere-as para o estado de saída e envia os dados. E o sinal R / W muda o dispositivo externo para o modo de gravação ou leitura, o oposto do estado correspondente do microprocessador.
4. CLK vai para zero. Agora, nem o microprocessador nem os dispositivos de entrada e saída produzem nada para o barramento de dados. O microprocessador pode definir a linha do barramento de dados e o pino R / W para um novo estado.
Uma explicação simples, compreensível para a criança. Quem nunca pensa nessas "intrigas nos bastidores", se ele apenas programa microcontroladores. Mesmo em montador.
Se você precisar conectar seu dispositivo periférico, ele deverá ter tempo para preparar os dados antes que a unidade (tempo de preparação) apareça na linha CLK e, enquanto a unidade estiver lá, não a altere. Se o dispositivo periférico não tiver tempo para preparar os dados enquanto o CLK estiver zero, ou os alterar quando a unidade estiver lá, você se perguntará por um longo tempo por que seu código não funciona. Como a frequência de clock do microprocessador é dez a quinze vezes menor que a frequência nominal, é fácil atender a esse requisito. Mas é necessário.
Portanto, precisamos “ensinar” o Arduino a gerar pulsos de clock, verificando continuamente o que acontece no barramento de endereços e na linha R / W e interagindo com o barramento de dados de acordo. Para fazer isso, o esboço usa a interrupção do timer1, que gera pulsos com uma frequência de 95 kHz. O Arduino trabalha muito mais rápido que o microprocessador e, portanto, entre seus relógios, consegue ler e preparar tudo. É importante garantir que, após modificar o esboço, essa condição continue a ser atendida.
Aqui está um trecho do esboço, que mostra como o CLK vai de zero a um e o que acontece a seguir:
//////////////////////////////////////////////////////////////////// // 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; }
A alocação do espaço de endereço pode ser feita de qualquer maneira; em um esboço não modificado, é o mesmo da Apple 1 com 256 bytes de ROM, 8 kilobytes de ROM para BASIC, 4 kilobytes de RAM e dispositivo de entrada e saída 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 };
A RAM é emulada pelo conjunto de bytes da RAM [RAM_END-RAM_START + 1]. São necessárias duas palavras-chave PROGMEM para que o conteúdo das ROMs emuladas seja armazenado na memória flash do microcontrolador.
O 6821 é emulado o suficiente para que o teclado virtual e o monitor funcionem através do "terminal". Woz Monitor e BASIC trabalham, que é o que o autor procurou.
Para emular qualquer dispositivo periférico, você precisa ler atentamente sua folha de dados e descobrir quais registros ela possui e para que servem. A conveniência da emulação reside na flexibilidade com a qual você pode fazer análogos de software da periferia.
Os dispositivos de E / S estão localizados no espaço de endereço do microprocessador e são acessados da mesma maneira que as células de memória. Para usar os periféricos "de ferro", como uma tela LCD, um cartão de memória e uma saída de som, é necessário alocar um local no espaço de endereço.
Referências:
www.6502.orgwww.callapple.org/soft/ap1/emul.htmlskilldrick.imtqy.com/easy6502searle.hostei.com/grant/6502/Simple6502.htmlwilsonminesco.com/6502primerSB-Assembler:
www.sbprojects.net/sbasmVá para 6809, ele contém:
Duas baterias de oito bits A e B, que podem ser combinadas em uma bateria de seis bits
Dois índices de pilha de 16 bits
Endereçamento relativo ao contador de instruções
Adicionar ou subtrair automaticamente 1 ou 2
Multiplicação de dois números não assinados de oito dígitos
Aritmética de 16 bits
Transferência e troca de dados entre todos os registros
Escrever e ler todos os registros e qualquer combinação deles
O microprocessador 6809E (externo) precisa de um relógio externo, enquanto o 6809 possui um relógio interno. Para a Hitachi, eles são chamados, respectivamente, 6309E e 6309, diferem dos habituais porque operam na forma de 32 bits dentro da operação, mas é possível alternar para o modo de compatibilidade com a versão clássica.
Na verdade, todo o projeto RetroShield começou porque o autor queria atualizar seu computador caseiro Simon6809 e nomear o resultado Simon6809 Turbo. Mas os chips lógicos padrão para tudo o que ele queria implementar lá exigiriam muito. Portanto, o autor formulou a idéia do RetroShield pela primeira vez em relação ao 6809, e só então pensou: “e se o mesmo com outros processadores fizer o mesmo?”.
O dispositivo, é claro, usa o 6809E, que requer um relógio externo, para que ele possa sincronizar seu trabalho de fora. As linhas E e Q para ambos os processadores têm o mesmo nome, apenas 6809 têm saídas e 6809E têm entradas.
O Arduino interage com o 6809 da mesma maneira que com o 6502, mas possui duas entradas de relógio: E e Q e três entradas de interrupção: IRQ, FIRQ e NMI.

Desta vez, a correspondência entre as portas do Arduino e os pinos do microprocessador é configurada da seguinte maneira:
/* 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
Como pode ser visto nos gráficos, o sinal Q é deslocado em relação a E em um quarto do período:
Dificilmente prestaremos atenção em Q, pois todos os eventos estão vinculados a E. E tudo acontece assim:

- E muda para zero. O processador define um novo endereço no barramento de endereços e altera o estado da linha R / W.
- E muda para um, o processador fica pronto para a troca de dados.
- Não importa o que acontece com o barramento de dados, desde que E seja um, o principal é que os dados necessários estejam presentes lá no momento em que E voltar a zero.
- Ao ler dados, o dispositivo de E / S deve fornecer os dados necessários ao barramento de dados antes que a linha E passe de um para zero (o atraso mínimo é mostrado pelo número 17 no círculo).
- Ao gravar, o dispositivo de E / S deve corrigir os dados em algum registro da forma em que estava no momento E passou de um para zero. O processador fornecerá esses dados no barramento ainda mais cedo - no momento da transição de Q para um (o número 20 no círculo).
- Após a transição de E para zero, tudo se repete.
Tudo o que foi dito acima, sobre 6502, sobre a necessidade de um dispositivo periférico (incluindo um virtual) para desenvolver todos os sinais no prazo, diz respeito ao 6809.
Geração dos sinais E e Q, como no caso do 6502, com a única diferença de que existem dois sinais, e eles devem ser trocados de acordo com os gráficos. E assim, uma sub-rotina chamada interrupção realiza entrada ou saída de dados nos momentos necessários.
O espaço de endereço no esboço não modificado é distribuído da mesma maneira que no computador
caseiro Simon6809 :
// 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 };
A RAM e a ROM são armazenadas em matrizes da mesma maneira que na variante 6502, com a única diferença sendo que existe apenas uma matriz com dados da ROM.
Os dispositivos de E / S também são partes alocadas do espaço de endereço e podem ser virtuais ou reais. Como o Simon6809 é uma máquina moderna baseada em uma base elementar vintage, ele troca dados via FTDI do PC no qual o "terminal" está sendo executado. Aqui é emulado.
Referências:
Muita informação sobre 6809 na
página ArtoArtigo da Wikipedia sobre 6809Sistemas SWTPc 6809Artigo da Wikipedia sobre o sistema operacional FLEX