Mostrando postagens classificadas por relevância para a consulta pc assembler. Ordenar por data Mostrar todas as postagens
Mostrando postagens classificadas por relevância para a consulta pc assembler. Ordenar por data Mostrar todas as postagens

terça-feira, julho 25, 2017

Assembler para o CDP1802 - Parte 1

Embora existam alguns assemblers para o 1802 (como este), resolvi me aventurar a escrever um. Segue abaixo a minha motivação e alguns comentários sobre a linguagem assembly suportada.


domingo, julho 16, 2017

PC Assembler Volume III - O eBook (GRÁTIS)

Finalmente criei coragem, arregacei as mangas e, após alguns meses de trabalho formatando o texto, acertando as figuras e digitalizando as listagens, disponibilizo o terceiro livro da série PC Assembler.


terça-feira, maio 06, 2014

Post #1000

Eu tenho dificuldade em acreditar, mas o Blogger garante que este é o milésimo post do blog. Francamente, nunca pensei que fosse chegar aqui.

quarta-feira, junho 30, 2010

PC Assembler Volume I - O eBook (grátis)


Há quase três anos, coloquei aqui no blog o texto do livro "PC Assembler" que eu escrevi nos anos 80. O texto foi disponibilizado de forma bastante grosseira e sem a listagem do programa exemplo.



Desde aquela época existia o plano de gerar uma versão mais caprichada. É o que finalmente consegui concluir. O link abaixo aponta para uma versão PDF, com o texto devidamente formatado. Embora eu tenha revisado o texto ele continua sendo basicamente o mesmo (e portanto restrito à programação em 16 bits nos processadores 8088 e 8086).

Baixe aqui o eBook PC Assembler


Vamos ver se não demoro outros três anos para disponibilizar o texto dos dois livros seguintes da série (Usando o Bios e Usando o DOS).


Atualização 26/9/11: O eBook pode ser baixado  do SkyDrive através do ícone no alto à direita ("Arquivos do Blog").

segunda-feira, setembro 17, 2007

PC Assembler: A Odisséia

Como comentei no post anterior, ao escrever o post sobre a minha nova impressora eu tive a idéia de postar o texto do livro PC Assembler. Parecia fácil, mas foi uma verdadeira odisséia!

Procurando a Midia

Eu estava certo de ter uma cópia no HD. Não achei. Bem, com certeza está um dos muitos CDs de backup. Também não. Nos disquetes? Nada.

Hora de enfrentar o pó da última gaveta do armário de roupas, onde escondi algumas lembranças para escapar do lixo. E achei o disco aí ao lado. Alguns vão reconhecer que é um disquete de 5 1/4", mas poucos vão perceber que tem o corte de desproteção contra gravação nos dois lados. Para quem não entende de arqueologia, este é um disquete de Apple ][.
Parenteses: o mundo em geral pode idolatrar o Steve Jobs mas para os engenheiros Steve Wozniac é o cara. Uma das suas maiores criações foi a interface de disco do Apple ][, que permitiu a comercialização da unidade de disco por um preço extremamente baixo para a época (mesmo com a margem imensa colocada pelo Jobs). Embora a unidade seja para disquetes de face simples, o fato dela não usar o furo de índice (o furo pequeno redondo à direita do centro), permitia usar o segundo lado fazendo o corte na lateral e colocando o disco de ponta cabeça.

O Micro

Na época que eu escrevi o livro, meu computador principal era um Unitron apII TI. Alguns anos depois, evoluí para um TK3000//e da Microdigital que corresponde ao Apple //e. Foi este segundo equipamento que fui buscar na casa do meu pai para ler o disquete. Na foto ao lado, ele já foi devidamente limpo (?).

Uma das várias idéias que o Apple ][ popularizou (e a IBM seguiu) foi o uso de slots para placas de expansão. Como pode ser visto na foto ao lado, o meu micro tem uma quantidade grande de placas.

Separada das demais, na esquerda, está a placa "80 colunas" (o default do Apple ][ é apresentar na tela 40 caracteres por linha). No slot 1 a interface para impressora paralela. No slot 4 um clone do Softcard Z80, um dos primeiros hardwares de sucesso da Microsoft (essencial pois o disco está em formato CPM/80). No slot 6 a mencionada interface de disco, Por último, no slot 7, a minha expansão de memória de 128 KBytes (uma extravagância, que eu usava como RamDisk).

Para completar, o meu primeiro monitor: uma TV muito mal adaptada (fiz a adaptação de forma provisória quando cheguei em casa com o Unitron, funcionou e ficou deste jeito até hoje). No lado direito, duas unidades de disco.

Hora de cruzar os dedos, colocar um disco de CP/M e ver o que acontece. Ao lado está o resultado, aquele "A>" lembra alguma coisa?

Exceto por um monte de maus contatos (principalmente no teclado), está tudo funcionando!

Obs.: Para o caso de alguém estar prestando a atenção, a tela está com 40 colunas, devido a um mau-contato na placa "80 colunas". Neste momento a placa Z80 estava no slot 3 (esquecimento meu). Quando a placa "80 colunas" passou a funcionar a Z80 parou. Perdi pelo menos uma hora, até lembrar que a placa Z80 devia ir no slot 4 para não conflitar com a "80 colunas".

Recuperando os Dados

Ok, eu consigo ler os disquetes antigos, e agora? É hora de transferir os dados pela serial. A placa ao lado é uma ICA (Interface de Comunicação Assíncrona), lembrança dos tempos da Humana Informática e dos posts nas BBSs, comunicando a 300 bps ou nos esquisitos 1200/75 bps.

Com a placa no slot 2, é hora de usar o meu programa de comunicação (reparar no copyright na tela ao lado). O programa trabalha no capenga Xmodem Checksum, o que exige um pouco de esforço do Hyperterminal (que insiste em tentar primeiro o Xmodem CRC e só passa para o Checksum depois de um l_o_n_g_o tempo). Após alguns arquivos a paciência acabou e preferí alterar um dos meus inúmeros utilitários de PC para suportar o Xmodem Checksum.

Mas, Espere: Isto Não É Tudo!

Ao final tenho 20 arquivo recebidos no PC (como no CP/M são apenas 56K de Ram, o texto era quebrada em pedaços de no máximo 20K para evitar que o editor ficasse acessando o disco o tempo todo). A tela ao lado mostra (em hexa) o início de um deles (clique para ampliar). Que codificação é essa? Os textos foram gerados com o WordStar, que era um editor WYSIWYG... se você está escrevendo em inglês numa impressora não gráfica. À medida que o texto é editado, o WordStar automaticamente distribui espaços para justificar o texto dentro das margens. Para isto ele usa o bit 7 dos caracteres para marcar o final das palavras e os espaços, hifens e quebras de linha introduzidos. O que realmente estraga tudo é que para gerar as acentuações eu digitava sequências "caracter backspace acento" (felizmente tanto o Unitron como o TK3000 tinham teclados programáveis, o que me permitia digitar estas sequências de uma forma mais automática).

Reparar também no ".he xxxx". A formatação para impressora era um passo separado, as linhas começando com '.' são comandos para definir cabeçalho, rodapé, pular página, etc. Para conseguir obter um TXT com o conteúdo foi preciso fazer um programa para limpar tudo isto.

Lembrando, o resultado final pode ser baixado daqui.

01/08/13: O texto, em formato de eBook pode ser baixado  do SkyDrive através do ícone no alto à direita ("Arquivos do Blog") ou pelos ícones abaixo:



sexta-feira, janeiro 07, 2011

Testando SkyDrive

Já faz muito tempo que os downloads do blog não estão funcionando. Como a Google decidiu parar de suportar arquivos no Google Groups, o jeito é procurar outro lugar para hospedar os arquivos. Meus requisitos são acesso direto e armazenamento sem expiração; minhas necessidades de espaço e banda são baixas e estou até disposto a pagar pelo serviço (mas não muito).

A alternativa que vou testar é o SkyDrive da Microsoft, que é parte do Windows Live. Além das minhas necessidades básicas, tem um limite de 25GBytes (máximo de 50MB por arquivo) e é gratis.

Os links abaixo são de alguns arquivos que coloquei lá como teste:

eBook PC Assembler
Palestra"Programação em C para Microcontroladores"
Artigo sobre Listas Ligadas
Exemplo de algoritmos de ordenação

Se tudo parecer funcionar bem, aí será preciso rever todos os links nos posts...

Atualização 7/1/11 14:00
Os links que funcionavam ontem e hoje cedo pararam de funcionar...

Alguns testes adicionais:

PC Assembler "embed"


Link para o folder com os arquivos

Vou aguardar mais um pouco...

quinta-feira, abril 26, 2012

PC Assembler Volume II - O eBook (grátis)


Finalmente converti o texto bruto do PC Assembler Usando o BIOS para uma versão PDF, com o texto devidamente formatado.

Escrito logo após o primeiro volume, foi baseado na minha experiência de escrever parte de um "clone" do BIOS do IBM XT (daí as referências a bugs, principalmente nas rotinas de vídeo). É um livro relativamente curto, onde as listagens de programas exemplo ocupa várias páginas.

A leitura do texto me fez lembrar a uma época que agora parece muito distante. Um dos exemplos envolve a gravação de um disquete com um boot especializado para rodar diretamente uma aplicação sem necessidade do DOS. A menção aos discos rígidos de 10 Mbytes chega a ser irônica, dado que o eBook tem um pouco mais de 7 MBytes. E o que dizer do modo gráfico de "alta resolução" - 640x200 pontos monocromático.

Tenho a impressão que vale mais como curiosidade ou nostalgia (dependendo da idade do leitor), mas aqui está para quem quiser baixar:

sexta-feira, dezembro 02, 2005

Apresentação

Estou finalmente me aventurando a fazer um blog, vamos ver se o trabalho e a família vão deixar algum tempo para colocar besteiras por aqui. Neste blog vou comentar sobre hardware e software e, mais ocasionalmente, banalidades. Neste primeiro post, um resumo da minha vida com computadores.

O meu contato com computadores começou no primeiro ano da faculdade, em 1977. A minha turma teve a discutível honra de ser a última turma da Poli (Escola Politécnica da Universidade de São Paulo) a usar cartões perfurados até o último ano (no ano seguinte foi inaugurada a sala de terminais de vídeo para os alunos).

Ao final de 81 me formei em Engenharia Eletrônica Digital e fui trabalhar na Scopus Tecnologia, no auge da reserva do mercado de informática. Trabalhei lá em firmware de terminais e depois no projeto de software para um dos primeiros micros brasileiros compatíveis com o IBM PC. Nesta época escrevi o meu primeiro livro da série PC Assembler, que foi publicado pela Editora Campus.

Em 86, chateado com as limitações de ser um desenvolvedor de software numa empresa primordialmente de hardware, fui para uma pequena software house a Humana Informática. Lá trabalhei no desenvolvimento de softwares de comunicação para PC que foram bem sucedidos no mercado. Infelizmente, veio o plano Collor bem num instante de mudança do mercado, com a ascensão do Windows e redes locais.

Após alguns anos dando murros em ponta de faca, fui para a Seal Eletrônica, uma empresa especializada em sistemas para captura automática de dados (leia-se coletores de dados e códigos de barras). Lá, além de desenvolver software para equipamentos móveis com comunicação por radio frequência, trabalhei como "desbravador de tecnologias" e gerente de projetos. Na virada do milênio tive uma curta experiência como Gerente de Produto.

Em 2002, decidi arriscar a sorte como empresário, abrindo a Tamid Tecnologia junto com um amigo (não por coincidência, colega na Scopus, Humana e Seal). A Tamid se especializa em software para equipamentos móveis (principalmente coletores e PDAs).

Dependendo do meu humor, nos próximos posts vou relembrar os bons tempos em que software de verdade se fazia em Assembler ou comentar as maravilhas tecnológicas atuais ou chutar previsões sobre o que está para vir.

quarta-feira, fevereiro 21, 2007

Programação Assembly no ARM - Parte IV

Nesta parte vamos ver as instruções de acesso à memória. Como vimos na parte anterior, as instruções lógicas e aritméticas do ARM trabalham com operandos nos registradores. As instruções de acesso à memória no ARM são primordialmente a carga de um registrador em uma posição de memória e o armazenamento do conteúdo do registrador em uma posição da memória, conforme dita a filosofia RISC.

Estas instruções, denominadas de Single Data Transfer, são codificadas conforme a figura abaixo:

Note que embora sejam apenas duas instruções assembly, LDR e STR, existem uma grande quantidade de opções. Como sempre, o opcode contém uma condição. O bit L diferencia a instrução de Load (carga do registrador a partir da memória) da instrução de Store (armazenamento na memória do conteudo do registrador). Rd indica o registrador que receberá o valor lido (LDR) ou que contém o valor a ser armazenado (STR). O bit B indica se estamos manipulando um word (32 bits) ou byte (8 bits); existem instruções separadas para manipular valores de 16 bits (half-words). O resto dos campos do opcode determinam o endereço da posição de memória a ser manipulada.

Este endereço é determinado por duas partes: a base (que fica no registrador Rn) e o offset (que, conforme o bit I, pode ser um valor imediato de 12 bits ou o conteúdo de um registrador deslocado). O deslocamento do registrador usado para o offset é determinado de forma semelhante ao usado para o valor imediato nas instruções lógicas e aritméticas. O bit U indica se o offset deve ser somado ou subtraído da base.

O bit P determina se a combinação da base com o offset deve ser feita antes (pré-indexação) ou depois (pós-indexação) do acesso à memória. O bit W permite atualizar o registrador base com o valor final do endereço. Quando se especifica a pós-indexação, o ARM atualiza sempre o registrador base, para não alterá-lo deve-se colocar um offset de zero. Na prática, isto significa:
  • P = 0, W = 0: normalmente tratado pelo ARM como P = 0, W = 1. (para ser preciso, no modo privelegiado em um processador com hardware de gerenciamento de memória esta combinação faz com que o endereço seja tratado como um endereço de usuário ao invés de endereço de sistema).
  • P = 1, W = 0: combina base e offset para determinar o endereço, o registrador base fica inalterado. Útil, por exemplo, para acessar um item de um vetor (base é o endereço inicial e o offset é o deslocamento em relação ao início).
  • P = 0, W = 1: usa somente a base para determinar o endereço, após o acesso à memória a base é atualizada com a combinação da base com o offset. Permite implementar em uma única instrução a construção *p++ do C (acessa a posição apontada pelo registrador base e o avança para o próximo item).
  • P = 1, W = 1: combina base e offset para determinar o endereço, o registrador base é atualizado para o endereço usado. Permite implementar em uma única instrução a construção *++p do C (avança o registrador base para o próximo item e o acessa).
A codificação da instrução em Assembly é a seguinte:

LDR{cond}{B} Rd,<endereço>
STR{cond}{B} Rd,<endereço>

Onde B indica que é um acesso a byte.

<endereço> pode ser
  • uma expressão, que resulta em um endereço. O Assembler tenta montar uma instrução que combine um offset ao PC para obter este endereço, se não conseguir gerará um erro. Esta forma é útil para carregar em registradores constantes que estão armazenadas junto ao código.

  • uma especificação de endereçamento pré-indexado (combina base e offset antes de acessar a memória):

    • [Rn]

    • [Rn,#expressão] {!}

    • [Rn,{+/-}Rm{,<shift>}] {!}

    • A presença de ! indica que o registrador base deve ser atualizado (W = 1).

  • uma especificação de endereçamento pós-indexado (combina base e offset depois de acessar a memória):
    • [Rn],#expressão

    • [Rn],{+/-}Rm{,}

    • nestes casos o registrador base é sempre atualizado.
Alguns exemplos

; coloca valores conhecidos em R1 e R2
MOV R1,#0
MOV R2,#4

; coloca em R0 o conteúdo da posição 0 da memória
LDR R0,[R1]

; coloca em R0 o conteúdo da posição 4 da memória
LDR R0,[R1,R2]

; coloca em R0 o conteúdo da posição 32 da memória
LDR R0,[R1,R2,LSL #3]

; idem
LDR R0,[R1+#32]

; coloca em R0 o conteúdo da posição 4 da memória
; R1 passa a ser 4
LDR R0,[R1,R2]!
; coloca em R0 o conteúdo da posição 4 da memória
; R1 passa a ser 8
LDR R0,[R1],R2

Reparar que não é possível especificar diretamente um endereço qualquer da memória, pois o offset imediato tem 12 bits e um endereço tem 32 bits. Além disso, já vimos que as instruções lógicas e aritméticas são limitadas quanto aos valores imediatos que podem ser usados. Para carregar em um registrador um endereço ou constante qualquer, é preciso armazená-lo em memória em uma posição que possa ser acessada por um LDR. Isto torna frequente a presença de constantes no meio do código:

...
LDR Rd,X ; usa endereçamento relativo ao PC
...
X .long valor ; em algum lugar próximo

Para facilitar isto, o Assembler permite a seguinte construção:

LDR Rd,=valor

Se possível, o assembler gera uma instrução MOV ou MVN para carregar o valor no registrador. Se isto não for possível, o assembler coloca a constante em um trecho próximo e gera uma instrução LDR usando endereçamento relativo ao PC.

Outras Instruções de Acesso à Memória

As instruções LDRH, LDRSH e STRH permitem carregar e armazenar valores de 16 bits (half word). A instrução LDRSH faz a "extensão do sinal", isto é, preenche os 16 bits mais significativos do registrador com o bit mais significativo do valor de 16 bits, de forma a manter o sinal do valor. O mesmo opcode permite também carregar em um registrador um valor de 8 bits com extensão de sinal (LDRSB). Estas instruções oferecem os mesmos recursos de endereçamento que LDR e STR.

As instruções LDM e STM (Block Data Transfer) permitem carregar ou armazenar um conjunto de registradores em posições contiguas de memória. A codificação destas instruções é apresentada abaixo:


Register list possui 16 bits, um para cada registrador. Os bits com 1 indicam os registradores a serem transferidos. O endereço inicial é obtido a partir de um registrador, que será automaticamente incrementado ou decrementado, antes ou depois de transferir cada registrador. O registrador utilizado pode ou não ser atualizado ao final da transferência.

A codificação em assembly destas instruções é:

LDM{cond}<fd|ed|fa|ea|ia|ib|da|db> Rn{!},<rlist>
STM{cond}<fd|ed|fa|ea|ia|ib|da|db> Rn{!},<rlist>

Onde
  • {cond} é a condição em que a instrução será executada.

  • FD, ED,..DB determinam os bits P e U. FD, ED, FA e EA são (respectivamente) idênticos a IA, IB, DA e DB; os primeiros são utilizados para documentar quando os registradores estão sendo transferidos para uma pilha.

  • ! indica que o registrador Rn deve ser atualizado ao final
Alguns exemplos:

; salva todos os registradores em posições
; consecutivas a partir da apontada por R0
STMIA R0,{R0-R15}

; empilha os registradores (exceto R15/PC)
STMFD SP!,{R0-R15}

; Salva na pilha os registradores R1, R3 e R5
STMFD SP!,{R1, R3, R5}

; desempilha os registradores
LDMED SP!,{R0-R14}

domingo, dezembro 11, 2005

Desenvolvimento nos Anos 80

O meu post anterior me fez lembrar sobre as condições de desenvolvimento nos meus primeiros empregos, nos anos 80.

Uma diferença em relação aos dias de hoje era a inexistência de micros sobre as mesas. Os micros ficavam normalmente em uma sala separada, o laboratório. Na foto, o laborátório da Humana Informática, em junho de 88.

No meu estágio na Scopus Tecnologia (1981), trabalhei no desenvolvimento de uma aplicação em Assembler para o microcomputador de 8 bits (MicroScopus). O desenvolvimento era feito no próprio MicroScopus; quando entrei já existia uma quantidade bem razoável deles no laboratório. Exceto por alguns problemas com a novíssima tecnologia de dupla densidade nos disquetes de 8", o desenvolvimento ocorria bastante bem. O MicroScopus trabalha apenas com disquetes de 8", winchester para micro ainda era algo novo (se bem que tinha um protótipo com disco de 5 MBytes) e os disquetes de 5 1/4" eram vistos como não profissionais.

Quando fui contratado fui alocado para o desenvolvimento do firmware de duas nova família de terminais de vídeo (Lepus e TVA 2000). O firmware também era desenvolvido em Assembler, porém em sistemas de desenvolvimento da Intel (Intelect). A Scopus possuia somente dois equipamentos (com disquetes de 8", é claro), por este motivo as edições mais pesadas eram feitas nos MicroScopus. Uma dificuldade adicional era que o hardware estava sendo desenvolvido e testado em paralelo ao software.

Posteriormente, ainda na Scopus, fui trabalhar no software do Nexus 1600, um dos primeiros PC-compatíveis nacionais. Novamente o hardware foi desenvolvido em paralelo com o software, porém neste caso tínhamos dois IBM PC (8088 4.7MHz, disquetes de 5 1/4") para desenvolvimento (em Assembler e Pascal) e teste. No final do desenvolvimento chegou um recem lançado IBM XT, com HD de 10MBytes. Depois que o projeto de hardware ficou estável (uma história interessante para um outro post) e a fábrica conseguiu alcançar a demanda, o laboratório passou a contar com uma quantidade razoável de Nexus. Para surpresa do marketing, só se usava o disquete de 5 1/4" (apesar de uma unidade externa de 8" ter sido desenvolvida) Em 85 a linguagem C começou a ser usada.

Em 1986 fui para a Humana. Uma empresa menor, incialmente tinha somente um Nexus 1600 (8088 8Mhz, 256K e dois disquetes de 5 1/4") para desenvolvimento, demonstração e treinamento. Dois computadores de 8 bits (um MicroScopus e um compatível Apple II) eram às vezes usados para edição. Com a minha chegada foi necessário adquirir mais um micro, no caso um Monydata (8088 8Mhz, 704K mas só um disquete de 5 1/4"). Para impressionar nas demonstrações do software para clientes, o monitor CGA era colorido (um luxo para a época). A Humana desenvolvia primariamente em C, usando Assembler apenas para trechos mais críticos.

Na foto de 88, da esquerda para a direita, um Itautec, o Nexus 1600, o Monydata e um Nexus 2600 (a única máquina com HD). Nos anos seguintes iriam se juntar a eles dois Microtec e um Proceda, todos com HD. Em 1989, embalada por um bom ano, a Humana comprou um lote de micros da Itautec para a sala de treinamento (com disquete) e para o desenvolvimento (com disquete e HD). A novidade destes micros era o vídeo EGA (porém os monitores eram monocromáticos). Com a chegada deles, um dos micros antigos (Proceda) foi para a minha mesa (afinal, eu era o chefe do desenvolvimento!).

quinta-feira, novembro 26, 2020

Memórias dos Anos 80 - Nexus 1600, parte 5 (consequências)

 Apesar de todos os percalços, o Nexus 1600 foi apresentado na "Feira Internacional de Informática" (mais conhecida como SUCESU) de outubro de 1983 e "salvou" financeiramente a Scopus (pelo menos por alguns anos). Todos viveram felizes para sempre? Não exatamente...

O Nexus 2600, o sucessor do Nexus 1600

segunda-feira, setembro 17, 2007

PC Assember Volume I

Ao escrever o post sobre a minha nova impressora, me veio à lembrança o motivo para a compra da minha primeira impressora: eu estava escrevendo um livro.
Um Breve Histórico

Nos idos de 1985, eu era um alegre proprietário de um TK-82C. Em um encontro de usuários, conheci o Nelson Santos que trabalhava como editor na Editora Campus. A idéia inicial, o livro "Jogos em Linguagem de Máquina para a Linha Sinclair" não foi em frente e o Nelson perguntou se eu tinha alguma idéia para um outro livro. Mencionei que estava trabalhando com o PC IBM e que talvez existisse interesse em um livro sovre programação assembly para ele. Assim nasceu o livro PC Assembler, que deu origem a mais outros três livros ("Usando o BIOS", "Usando o DOS" e "Gráficos e Sons").

O Conteúdo

O livro descreve a programação em Assembly no 8086/8088 (naquilo que hoje em dia se chama de "real mode 16 bits"). A maior parte é uma descrição detalhada de cada uma das instruções disponíveis.

As Vendas

No total foram impressos 6253 livros e vendidos 6066 exemplares, em quatro edições. o gráfico abaixo mostra a evolução das vendas semetrais, do segundo semestre de 86 até o primeiro semestre de 93, quando a Editora Campus decidiu não mais reeditar a obra e me devolveu os direitos de publicação.


Para minha total surpresa, o livro foi até considerado um "best-seller":


O Texto

Existe uma longa história sobre a recuperação deste texto, o que será assunto do próximo post.

Estou disponibilizando o texto do livro aqui. Aviso que (por enquanto?) está em formato texto (TXT) pré formatado e falta a listagem do programa exemplo.

30/06/2010: Finalmente o texto completo e devidamente formatado está no blog: veja aqui.

segunda-feira, outubro 02, 2006

Gerenciamento de Memória - 8086, 8088, o PC-IBM e o MS-DOS


Com o sucesso dos microprocessadores de 8 bits e o constante avanço da micro-eletrônica, os fabricantes partiram para projetos mais ousados.

A Motorola, por exemplo, abandonou a arquitetura de 8 bits do 6800 e desenvolveu o 68000, com 16 registradores de 32 bits de uso geral e modos usuário e supervisor para proteção do sistema operacional. A Zilog partiu para o sofisticado Z8000.

A Intel, entretanto, se mostrou bastante conservadora com o 8086. Os registradores de uso geral do 8086 (AX, BX, CS e DX) são uma expansão direta dos registradores do 8080. Inclusive, cada registrador ?X pode ser acessado como dois registrados de 8 bit, ?H e ?L.

Isto, mais alguns cuidados na definição do conjunto de instruções, permitiu a conversão automática de código fonte assembler do 8080 para o 8086. Vários programas de sucesso no CPM/80, como WordStar e Dbase II utilizaram este processo para sair na frente no lançamento do PC IBM (por outro lado, estes programas convertidos ficaram em desvantagem quando concorrentes feitos especificamente para o PC ficaram disponíveis).

Como vimos anteriormente, embora o 8080 seja um processador de 8 bits, ele possui algumas instruções para manipular 16 bits, tipicamente endereços. O 8086, entretanto, manipula quase que exclusivamente 16 bits. Isto criou um problema, pois, mesmo no final dos anos 70, era evidente que 16 bits de endereçamento (64Kbytes) era pouco. A gamb^H^H^H^B adaptação técnica (TM Rodrigo Strauss) adotada pela Intel custou muitas noites de sono para os desenvolvedores de aplicações e compiladores. As instruções normais trabalham com endereços de 16 bits, estes endereços são convertidos para 20 bits somando com o valor de registradores especiais também de 16 bits, porém deslocados de 4 bits para a esquerda. Parece confuso? É confuso mesmo.

Estes registradores especiais são chamados registradores de segmentos. O 8086 tem 4 destes registradores, chamados CS, DS, SS e ES. Por default, estes registradores são usados, respectivamente para acesso a código, dados e pilha (o registrador ES só é usado implicitamente por algumas instruções especiais). É possível (com certas restrições) usar explicitamente um registrador de segmento diferente colocando um byte de prefixo na frente da instrução.

Vamos tentar esclarecer com um exemplo. Vamos supor que os registradores BX, DS e ES contenham, respectivamente, 0x1234, 0x9000 e 0x4321 (0x indica número hexadecimal, se você não sabia disso provavelmente está no blog errado). A instrução assembler MOV AX,[BX] carrega em AX o valor na posição de memória de endereço DS*16 + BX (ou seja, 0x91234). A instrução MOV AX,ES:[BX] usa o registrador ES no lugar do DS, carregando em AX o valor na posição de memória de endereço 0x44444.

É costume no 8086 falar em endereços na forma segmento:offset, onde segmento e offset são valores de 16 bits. No nosso exemplo anterior, o endereço 0x44444 poderia ser referenciado como 0x4321:0x1234. Ou 0x4000:0x4444. Ou outras 64K-2 formas diferentes (o 8086 ignora o "vai um" quando soma o segmento deslocado com o offset, guarde esta informação para quando formos falar do 286).

O que tudo isto significa na prática? Em primeiro lugar, que o 8086 é capaz de endereçar até 1M de memória. Em segundo lugar, que uma aplicação precisa fazer uma certa ginástica para usar mais que 64K de código ou dados. No caso do código, existem o JUMP e CALL intersegmento, mas são instruções mais longas e mais demoradas. No caso de dados, para acessar uma estrutura que ocupe mais de 64K é preciso fazer umas contas medonhas. Os compiladores C para o 8086 costumam suportar alguns modelos de endereçamento:
  • Tiny: Tudo fica em um único segmento de 64K. Os quatro registradores de segmento apontam para o início do segmento no início da execução e não são mais alterados. Altamente eficiente e simples, até que o seu programa não caiba em 64K.
  • Small: Um segmento de código (apontado por CS) e um segmento de dados (apontado por DS e SS). Novamente os registradores não precisam ser alterados durante a execução.
  • Medium: Um segmento de código para cada rotina e um segmento único de dados. Cada chamada de rotina passa de 3 para 5 bytes; o seu código que não cabia por pouco em 64K agora ocupa 70K.
  • Large: Múltiplos segmentos de código e dados, mas nenhuma estrutura passa de 64K. Os registradores CS e DS mudam a toda instante, o tamanho do código cresce e o desempenho cai.
  • Huge: Múltiplos segmentos de código e dados, permite estruturas com mais de 64K. Avançar um ponteiro de dados passa a ser uma operação lenta.
Neste altura vocês devem estar achando que a Intel fez uma grande besteira. Entretanto, simplificando o processador ela simplificou o processo de fabricação, saindo na frente dos concorrentes e obtendo mais chips bons por waffer de silício e portanto chips mais baratos. Além disso, a compatibilidade parcial com o 8080 permitiu ter rapidamente uma boa oferta de software. Enquanto os outros processadores de 16 bits eram usados apenas em projetos mais sofisticados, o 8086 foi ganhando terreno. Quando a IBM foi ouvir os conselhos da Microsoft (que aliás tinha muito software para 8080 e CPM/80, a ponto de ter desenvolvido uma placa de expansão com Z80 para ter penetração no mercado Apple) a estratégia deu frutos.

Um ponto interessante no interior do 8086 é a divisão do processador em uma unidade de execução e uma unidade de acesso de memória. Esta segunda unidade é a responsável por acionar os pinos de endereçamento do chip e faz algumas coisas especiais:
  • no caso de um acesso a uma valor de 16 bits que está em um endereço impar, gera dois acessos à memória. Desta forma existe uma penalidade de tempo, mas o software é executado quase que normalmente.
  • aproveita quando a unidade de execução está processando a instrução para pegar as instruções seguintes e colocá-las numa pequena fila. Não chega a ser um cache, mas dá um pequeno ganho de performance e permite usar memórias mais lentas.
A existência da unidade de acesso à memória simplificou criar o 8088, que é identico ao 8086 porém usa uma memória de 8 bits. Do ponto de vista de software, nada muda. Do ponto de vista de hardware, o custo de um sistema simples cai, pois os chips de memória típicos eram de 16K ou 64K posições de 1 bit. Com o 8088, dava para fazer um sistema usando 8 chips de memória, com o 8086 era preciso no mínimo 16 chips.

Ao contrário do 8080, o 8086 inicia a execução no final da memória, no endereço 0xFFFF0. Novamente, isto simplifica o projeto, pois pode-se colocar uma memória não volátil no fim da memória e Ram no começo. No início da memória existe uma tabela importante o vetor de interrupções. Esta tabela contém os endereços das rotinas que tratam as interrupções de hardware e software.

O PC-IBM

Por sugestão da Microsoft, a IBM utilizou o 8088 no PC IBM. Na placa principal havia lugar para até 64K de Ram, na forma de 32 chips de 16Kx1. No PC XT eram usados chips de 64Kx1, permitindo 256K na placa mãe.

O 1M de endereçamento foi dividido em três grandes partes: os primeiros 640K (0x00000 a 0x9FFFF) para a Ram, os últimos 64K (0xF0000 a 0xFFFFF) para a memória não volátil com o BASIC e o BIOS e o resto (0xA0000 a 0xEFFFF) para uso pelas placas de expansão (notadamente a Ram das placas de vídeo em 0xB0000 e 0xB8000 e o BIOS da controladora de HD em 0xC0000).

Uma curiosidade é que o chip de DMA da Intel (responsável entre outras coisas por transferir para a memória os dados das controladoras de disco) também era limitado a 16 bits. Para endereçar toda a memória, a IBM precisou acrescentar um registrador externo de 4 bits. Isto criou uma nova barreira de 64K: não era possível ler diretamente do disco para um trecho de memória que contivesse uma mudança nos 4 bits mais significativos do endereço. O BIOS se limitava a recusar este tipo de transferências, deixando por conta do sistema operacional ou aplicação contornar esta limitação.

O BIOS possui as rotinas de iniciação do hardware e rotinas básicas de entrada e saída. A memória de 0x00000 a 0x003FF contem os vetores de interrupção, de 0x00400 a 0x004FF estão as variáveis do BIOS.

O MS-DOS

A Seattle Computer foi uma das primeiras empresas a fazer um micro usando o 8086. Entretanto o único software disponível era o BASIC da Microsoft. Cansados de esperar pelo CP/M-86, eles desenvolveram o QDOS (Quick and Dirty Operationg System). Quando a IBM também se cansou de esperar pelo CP/M-86, a Microsoft licenciou o QDOS e o ofereceu para a IBM. O resto, como dizem, é história. (Recomendo para quem quiser os detalhes o livro Gates de Stephen Manes e Paul Andrews).

Apesar das inúmeras diferenças em relação ao CP/M-80, o MS-DOS suporta uma interface de programação muito semelhante. Como vimos na parte anterior, no CP/M-80 a interface entre a aplicação e o sistema operacional é feita através de uma estrutura no início da memória. No MS-DOS esta estrutura é simulada no Prefixo de Segmento de Programa (PSP). Lá estão a linha de comando, as estruturas de acesso a arquivos (FCBs) e até mesmo a chamada ao SO. Nas versões futuras do MS-DOS algumas destas características foram perdendo importâncio, mas continuam lá e sobrevivem até no Windows.

Ao contrário do CP/M-80, o MS-DOS reside nos endereços baixos. A memória disponível para os programas começa após o final do SO (mais rigorosamente, após a parte residente do COMMAND.COM) e vai até o fim da Ram (o final da Ram é também usado pelo parte transiente do COMMAND.COM). Na versão 1 o DOS não oferecia nenhuma função de gerenciamento de memória, exceto o Terminate and Stay Resident, através da qual um programa podia encerrar deixando uma parte de si na memória (ou seja, controle de memória do DOS se limitava ao endereço ondem os programas eram carregados). A partir da versão 2, o DOS passou a oferecer funções simples de alocação de memória, colocando um pequeno prefixo no final de cada bloco alocado.

O MS-DOS suporta dois formatos de arquivos executáveis: COM e EXE.

Um arquivo COM é uma imagem binária de um programa. Esta imagem é carregada no offset 0x100 de um segmento, os registradores CS, DS, ES e SS apontam para o início do segmento e a execução é iniciada em CS:0x100. No começo do segmento é colocado o PSP. Este esquema, muito semelhante ao CP/M-80, é apropriado para programas no modelo tiny.

O arquivo EXE suporta programas com múltiplos segmentos. Para isto ele possui um cabeçalho para para acertar as referências inter-segmentos conforme o segmento em que o programa foi carregado. O PSP é criado na memória antes do primeiro segmento do programa. No início da execução, os registradores DS e ES apontam para o PSP, os registradores CS, IP, SS e SP são carregados com informações obtidas do cabeçalho do EXE.

Do ponto de vista de gerenciamento de memória, o 8086 e o MS-DOS não trazem grandes vantagens em relação ao 8080 e CP/M-80 (exceto por suportarem uma memória maior). Isto ficou para a geração seguinte. Infelizmente, o 286 seguiu na direção errada, como veremos no próximo post desta série.

terça-feira, janeiro 24, 2012

Display Gráfico Nokia 5110 - Parte 5

No post anterior nos usamos o display gráfico para apresentar texto. Vejamos agora como mostrar gráficos, continuando a usar o display conectado a um Arduino.


sábado, dezembro 02, 2017

Doze Anos do Blog

Segue o tradicional balanço anual, em um ano que considero de transição para o blog.




quinta-feira, dezembro 08, 2011

Balanço Anual do Blog: 2011

No começo do mês o DQSoft completou seis anos de vida. É hora do tradicional balanço anual.

sábado, dezembro 04, 2010

Cinco Anos de Blog

É difícil acreditar mas na quinta passada (2/12/10) o blog completou 5 anos. Do ano passado para cá não teve muitas novidades e a correria neste final de ano impediu uma comemoração muito especial.

De qualquer forma, aqui estão alguns gráficos mais recentes (clique para ampliar):





Os destaques deste último ano foram:
  • Disponibilização (finalmente) do livro PC Assembler em formato de ebook.
  • A série "Som Aposentado" (1 2 3 4 5 6)
  • A série "Interfaceando Microcontroladores", uma primeira experiência com vídeos no YouTube (1 2 3 4 5 ... falta ainda a última parte!)
  • O Google Groups me deixa na mão (1 2). Está na lista de "urgentes" achar um outro lugar para hospedar os arquivos e acertar todos os links.

sexta-feira, julho 31, 2009

Compactação - Parte 3

Demorou um pouco mais que eu pretendia, mas segue o exemplo do algorítimo LZW em C. Como parte da preparação, corrigi a descrição das tabelas de compactação e descompactação no post anterior.

Esta versão é bastante simplista, usando códigos de tamanho fixo de 12 bits e parando de atualizar a tabela de compactação quando ela fica cheia. Mesmo assim, consegue reduzir o meu arquivo de teste (o texto do meu livro PC Assembler) de 172KB para 74KB (para uma comparação, o 7-zip consegue reduzir o arquivo para 36KB).

Coloquei no fonte comentários em formato Doxygen. O programa completo e a documentação no formato de help podem ser baixados daqui.

Vamos à parte que interessa. A rotina de compactação é praticamente igual ao pseudo-código que vimos anteriormente:

/************************************************************************/
/**
* \brief Compacta o arquivo origem, gerando o arquivo destino
* \ingroup LZW
*/
/************************************************************************/
static void Compacta (void)
{
int prox; /**< proximo código de saída */
int w; /**< código da sequência atualmente conhecida */
int nw; /**< código da nova sequência */
int c; /**< caracter sendo examinado */

if (!IniciaES ())
return;

// Inicia a tabela
for (prox = 0; prox < PRIM_CODE; prox++)
TabComp [prox].cod = TabComp[prox].car = prox;
for (; prox < T_TABCOMP; prox++)
TabComp[prox].cod = LIVRE;

// Compacta
prox = PRIM_CODE;
w = LeCarEntrada();
while (TRUE)
{
c = LeCarEntrada();
if (c == EOF)
break;
nw = Procura (c, w);
if (nw != 0)
{
// achou na tabela
w = nw;
}
else
{
// não está na tabela
EscreveCodSaida (w);
if (prox <= ULT_CODE)
{
Guarda (c, w, prox);
prox++;
}
w = c;
}
}
EscreveCodSaida (w);
EncerraES();
}


A tabela de códigos de compactação e rotinas associadas ficaram assim:

#define T_TABCOMP 5021 /**< tamanho da tabela de compactação \ingroup TabComp */
#define LIVRE 0xFFFF /**< marca de posição livre \ingroup TabComp */
static struct
{
int cod; /**< código armazenado nesta posição */
int prefixo; /**< código do prefixo */
uchar car; /**< caracter */
} TabComp [T_TABCOMP]; /**< Tabela de códigos de compactação \ingroup TabComp */

/************************************************************************/
/**
* \brief Procura um par (caracter, prefixo) na tabela de código de
* compactação
* \ingroup TabComp
* \param c caracter
* \param w código do prefixo
* \return código correspondente ao par ou 0 se não encontrar
*/
/************************************************************************/
static int Procura (uchar c, int w)
{
int i;

i = Hash (c, w);
while (TRUE)
{
if (TabComp [i].cod == LIVRE)
return 0;
else if ((TabComp [i].prefixo == w) && (TabComp[i].car == c))
return TabComp[i].cod;
else if (++i == T_TABCOMP)
i = 0;
}
}

/************************************************************************/
/**
* \brief Guarda na tabela de código de compactação o código
* correspondente a um par (caracter, prefixo)
* \ingroup TabComp
* \param c caracter
* \param p código do prefixo
* \param cod código correspondente a (c, p)
*/
/************************************************************************/
static void Guarda (uchar c, int p, int cod)
{
int i;

i = Hash (c, p); // lugar preferido
while (TRUE)
{
// insiste até achar uma entrada livre
if (TabComp [i].cod == LIVRE)
{
TabComp [i].cod = cod;
TabComp [i].prefixo = p;
TabComp[i].car = c;
break;
}
if (++i == T_TABCOMP)
i = 0;
}
}

/************************************************************************/
/**
* \brief Calcula o hash correspondente ao par (caracter, prefixo)
* \ingroup TabComp
* \param c caracter
* \param w código do prefixo
* \return hash correspondente
*/
/************************************************************************/
static int Hash (uchar c, int w)
{
return w ^(c << 4);
}


Os códigos gerados tem 12 bits; cada dois códigos são gravados em 3 bytes:

/************************************************************************/
/**
* \brief Escreve um código no arquivo de saída
* \ingroup ES
* \param w Código a escrever
*/
/************************************************************************/
static void EscreveCodSaida (int w)
{
if (pos == 0)
{
fputc (w & 0xFF, fpOut);
buf = w >> 4;
pos = 1;
}
else
{
fputc ((buf & 0xF0) | (w & 0x0F), fpOut);
fputc (w >> 4, fpOut);
pos = 0;
}
}


A rotina descompactação fica bem simples:

/// Tabela para expansão
static struct
{
int prefixo; /**< código do prefixo */
uchar car; /**< caracter */
} TabExp [ULT_CODE+1]; /**< Tabela de códigos de expansão \ingroup TabExp */

/// Pilha de Inversão da Expansão
static uchar pilha [ULT_CODE+1]; /**< pilha para inverter saída da expansão \ingroup LZW */

/************************************************************************/
/**
* \brief Expande o arquivo origem, gerando o arquivo destino
* \ingroup LZW
*/
/************************************************************************/
static void Expande (void)
{
int prox; /**< proximo código na tabela */
int w; /**< código a descompactar */
int lw; /**< último código a descompactar */
uchar c; /**< último caracter escrito na saída */

if (!IniciaES ())
return;

prox = PRIM_CODE;
w = LeCodEntrada();
EscreveCarSaida (w);
lw = w;
while (TRUE)
{
w = LeCodEntrada();
if (w == EOF)
break;
if (w >= prox)
{
// caso especial
uchar nc;

nc = EscreveSeqSaida(lw);
EscreveCarSaida (c);
c = nc;
}
else
c = EscreveSeqSaida (w);

if (prox <= ULT_CODE)
{
// acrescenta o código na tabela
TabExp [prox].car = c;
TabExp [prox].prefixo = lw;
prox++;
}
lw = w;
}
}

/************************************************************************/
/**
* \brief Escreve na saída os caracteres correspondentes a um código
* \ingroup LZW
* \param w código de compactação
* \return último caracter escrito na saída
*/
/************************************************************************/
static uchar EscreveSeqSaida (int w)
{
int topo; /**< próxima posição livre da pilha */

topo = 0;
while (w >= 256)
{
if (topo >= sizeof(pilha))
{
printf ("Pilha insuficiente!\n");
exit (1);
}
pilha[topo++] = TabExp[w].car;
w = TabExp[w].prefixo;
}
pilha[topo++] = (uchar) w;
while (topo > 0)
EscreveCarSaida (pilha[--topo]);
return (uchar) w;
}

No próximo post vou mostrar pequenas alterações neste código para melhorar a compactação e ajudar a ver o que ele faz.

terça-feira, dezembro 02, 2014

Nove Anos de Blog

Mais um aniversário do blog, mais um balanço anual com os números, observações e planos.