quem dera! apenas coloquei os fontes na ferramenta para sintetizar e gerar um diagnostico... agora tem que ir simulando clock a clock e ir corrigindo os pequenos detalhes passo a passo ateh funcionar.
alguns comentarios iniciais: dentro do codigo existem muitos elementos nao-sintetizaveis, ou seja, que nao possuem modelo fisico real, como tri-state em barramentos internos, delays e uso do for(). o tri-state soh eh sintetizavel nos pinos de IO da FPGA e internamente deve-se sempre usar multiplexadores. os delays sao similares, ou seja, soh sao sintetizaveis nos pinos de IO da FPGA e internamente deve-se sempre usar flip-flops para prover atrasos quantizados de acordo com um clock. no caso de construcoes com for(), sao usados apenas em simulacao e nao produzem codigo. para produzir codigo, eh possivel usar generate e for(), mas o codigo em particular eh a inicializacao de um banco de registros (4 registros de 8 bits), entao ainda esbarra em um problema que arrays de memoria precisam ser fisicamente enderecados. na pratica, como sao poucos registros, acaba sendo melhor expressar eles como registros individuais.
quando aos elementos sintetizaveis, tem alguns detalhes chatos... no codigo temos muitas construcoes do tipo:
- Código: Selecionar todos
reg alguem;
always@(linha)
if(linha==0) alguem = algum_lugar;
a construcao eh valida e vai gerar um latch, de modo que alguem recebe algum_lugar quando linha for ativado em zero.
bom, eh uma construcao que lembra muito o process do VHDL, mas nao eh muito popular em verilog realmente. um dos problemas eh que isso gera um latch, ou seja, ele captura a entrada enquanto linha for valido. mas isso reduz a performance em comparacao a um flip-flop que captura a entrada em uma borda, de modo que a construcao mais usada seria:
- Código: Selecionar todos
reg alguem;
always@(posedge linha)
alguem <= algum_lugar;
no caso, posedge indica que o flip-flop ira capturar algum_lugar para dentro de alguem na borda de subida de linha. mas ainda aqui temos um outro problema: arvore de clock. como linha eh um sinal qualquer e pode eventualmente ter sido gerado por logica combinacional, existe a possibilidade de linha ter jitter e ruido. para evitar isso, o certo seria usar um clock global de modo que todos os sinais fossem sincronos com este clock, incluindo linha e algum_lugar, de modo que ficaria:
- Código: Selecionar todos
reg alguem;
always@(posedge clk)
if(linha==0)
alguem <= algum_lugar;
finalmente, embora externamente seja popular o uso de sinais ativados em zero, isso vem dos tempos imemoriais onde os processadores eram construidos com portas NMOS, ou seja, todas as linhas eram ligadas em nivel alto por pull-ups e transistores NMOS driveavam a linha para zero para ativar.
hoje em dia, na era CMOS temos pares de transistores PMOS e NMOS capazes de drivar tanto para zero quanto para um, de modo que os sinais podem perfeitamente ser ativos em nivel 1. outro detalhe a adicionar eh reset, de modo que temos:
- Código: Selecionar todos
reg alguem;
always@(posedge clk)
if(reset)
alguem <= 0;
else
if(linha)
alguem <= algum_lugar;
mas essa notacao com if() else etc... tambem nao eh muito boa e lembra muito o VHDL com aquele sotaque carregado de ada... ao inves disso temos algo mais c-like:
- Código: Selecionar todos
reg alguem;
always@(posedge clk)
alguem <= reset ? 0 : linha ? algum_lugar : alguem;
basicamente eh o que esta escrito: se reset estiver ativo, carrega zero, se linha estiver ativo carrega algum_lugar, senao deixa como estah.
a vantagem dessa notacao eh que possui alguma simetria com o assert, de modo que, quando temos armazenamento:
- Código: Selecionar todos
reg alguem;
always@(posedge clk) alguem <= reset ? 0 : linha ? algum_lugar : alguem;
mas quando queremos apenas logica combinacional sem armazenamento, podemos fazer o mesmo, mas como wire:
- Código: Selecionar todos
wire alguem;
assert alguem = reset ? 0 : linha ? algum_lugar : alguem;
muito similar, mas com umas sutilezas: eh um wire, portanto logica combinacional, nao tem clock, portanto atualiza continuamente e instantaneamente. a notacao fica mais compacta e, na minha opiniao, mais legivel.
o lance do reset em particular eh bastante importante: primeiro que eh comum apos o power on as coisas nao estarem funcionando direito. apenas como exemplo, na propria simulacao o M++ jah nao carrega corretamente a primeira instrucao e eh isso eh mais comum do que parece! de fato, durante anos quase todos os meus processadores, incluindo as primeiras versoes do darkriscv nao carregavam corretamente a primeira instrucao, o que obrigava a comecar todo codigo com um nop! hehehe
no caso do M++ coloquei 4x "inc a" no inicio para testar e na simulacao fiquei olhando quando ele comecava efetivamente a processar o resto do codigo. como ele nao tem reset, ele acaba perdendo a primeira instrucao sempre. creio que nao eh dificil consertar, mas comecar com um "inc a" resolve o problema tambem pq mantem a sequencia de instrucoes corretamente alinhada qdo ele comeca executar codigo.
bom, nesse meio tempo eu tambem aproveitei para dar uma lida nas dissertacoes relacionadas ao M++. pelo fato de ter vindo de um conceito de esquematico, supostamente passivel de ser implementado com logica TTL discreta, fica um pouco dolorido para passar para verilog.
outra coisa interessante eh que medindo o numero de clocks por instrucao obtive algumas poucas medidas, mas suficiente para fazer algumas contas:
inc a = 8 clocks
mov 0,a = 10 clocks
jmp label = 14 clocks
assim, se conseguirmos rodar ele a 150MHz, daria uns 15MIPS apenas, acho que meio na media de outros processadores antigos... o 68008, por exemplo, precisava de no minimo 8 clocks por instrucao (e algumas podiam demorar centenas de clocks).
uma coisa que senti dificuldades eh que no aspecto do ISA do M++ nao achei com facilidade uma referencia das instrucoes. existe algum material sobre o assembler, mas para um nivel de desenvolvimento baixo nivel seria interessante um guia de referencia com instrucoes, bitmaps das instrucoes e tabelas indicando as possibilidades de cada campo nas instrucoes, bem como quantidade de ciclos para executar. outra coisa que acho que falta eh informacoes sobre licencas dos codigos disponiveis... nao sei como estah licenciado, mas se nao estiver recomendaria adotar uma licenca BSD ou MIT. tendo uma licenca, documentacao e estando melhor organizado, poderia ateh colocar o codigo no github hein
totavia, tambem tem o outro lado da moeda: da mesma forma que eu acabei largando o 680x0 para partir para o RISCV pq eh mais simples de implementar, sera que partir para um conceito mais moderno e otimizado para FPGA nao seria vantagem?
olha o tamanho deste core RISC de 16 bits que eu montei anteriormente:
http://darklife.org/pics/xilinx/src/core.vapesar de ter apenas esse tamanho pequeno, ele tranquilamente atinge os 80MIPS e roda uma pah de instrucoes! com alguns updates, fica bem simples e didatico! hahaha
tcpipchip escreveu:Marcelo S!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Tu é rápido hein! Até parece que entende de FPGA
Brincadeira!
Aqui é um dos exemplos que executamos na nossa M++
- Código: Selecionar todos
MOV 00,A;
ESCREVE:
MOV A,OUT0;
MOV A,OUT1;
MOV A,OUT2;
MOV A,OUT3;
INC A,A;
JMP ESCREVE;
Zera acumulador, manda acumulador para as 4 saidas, incrementa acumulador, repete o ciclo.
O código de máquina é
- Código: Selecionar todos
07, C0, 00, C3, CB, D3, DB, E0, 07, 03, 00, 03
------------
MOV 00,A
-----------------------
MOV A,OUT0..3
------------------
JMP 0003
Veja se isto já roda! E depois vamos para pilha para CALL/RET
Execucao no simulador
https://www.dropbox.com/s/orgtcsjjmxuzw ... B.png?dl=0Aqui a parte interna da M++
http://www.inf.furb.br/~maw/pdf/