uC - small microcontroller

812 views
Skip to first unread message

Democrito

unread,
Oct 12, 2023, 5:41:15 PM10/12/23
to FPGAwars: explorando el lado libre
Hola,

Abro este nuevo hilo para investigar y crear junto a vosotros un pequeño microcontrolador.

Ya tengo un esbozo, un comienzo para irlo ampliando. Pongo una imagen aquí para que lo veáis.
Initial uC.png
Tiene una memoria ROM que contendrá las instrucciones. Inicialmente se puede direccionar hasta 1KB. En el futuro crearemos ROMs más ajustadas a los programas que le metamos. El contador de programa inicialmente lo hice de 10 bits y entonces puse una ROM de ese tamaño.

El bus de datos es de 12 bits. De estos 12 bits, 8 bits son de datos y 4 bits es de microinstrucción.

data and instruction.png
Estos 12 bits, antes de llegar a la caja de código, se separa la microinstrucción y el dato. Lo he hecho así para que todo se vea más gráfico y se entienda mejor el conjunto de funcionamiento.

Entonces, en la entrada de la caja de código tenemos la microinstrucción y un dato asociado. La caja de código hace de unidad central, que es quien gestiona lo que hay que hacer en cada momento según la microinstrucción que le llegue.

Podemos crear hasta 16 microinstrucciones. Las 4 primeras y más básicas son las que verás en la caja de código. Para cada acción tiene un número asociado que va del 0 hasta el 15 (en total 16 microinstrucciones posibles).

En la caja de memoria escribiremos nuestro código.El código es código máquina y sólo se puede utilizar números hexadecimales. Veamos una imagen para identificar en la caja de memoria la microinstrucción y el dato asociado.

code separation.png

La línea roja separa la microinstrucción del dato. La microinstrucción puede ser un número hexadecimal que va del 0 hasta la F. Y el dato puede tener un valor comprendido entre 00 y FF. Siempre pondremos 3 valores hexadecimales en la caja de memoria.

En las microinstrucciones he utilizado los números 1, 2, 3 y 6 para significar una acción específica. El número '0' lo estoy dejando de momento de lado, porque quizás le dé la función de "NOOP" (No Operando). Y el salto del 3 al 6 se debe a que en próximos post veréis ahí varias instrucciones de salto condicional. En esta presentación inicial sólo he puesto 4 microinstrucciones básicas para que se entienda lo mejor posible la filosofía de funcionamiento.

De momento, esta versión inicial tiene 4 registros: pc (contador de programa), acc (acumulador o registro de trabajo), out (salida externa) y sysclk (reloj del sistema).

En las microinstrucciones 1, 2 y 3 interviene el registro acc.

Microinstrucción 1: acc <= data;
Lo que hace es cargar el valor de entrada data (este dato viene de la ROM del programa) y lo carga en el acumulador.

Microinstrucción 2: acc <= acc + data;
Suma el contenido del acumulador al valor de entrada data y lo guarda en el acumulador.

Microinstrucción 3: out <= acc;
Saca el valor del acumulador a la salida, que en este ejemplo es a 8 leds. Esta microinstrucción desaprovecha los 8 bits de datos. Esto significa que puedes escribir 300, 301... 3FF, sólo hará caso a la microinstrucción 3 y el resto de bits no se hace nada con ellos.

Microinstrucción 6: pc  <= data;
Carga el dato de entrada en el contador del programa. Se encarga de realizar los saltos a cualquier otra dirección de memoria.

Program counter verilog.png

El registro pc (contador de programa) es quien se encarga de direccionar la memoria ROM. El control de este registro es especial, porque sólo se puede manipular una sola ver por cada ciclo del reloj del sistema. Es por ello que en el código verilog no se puede incrementar y luego si llega una instrucción de salto, decirle que salte. Así que la instrucción de salto (6) se ha de separar de la instrucción de incremento de este registro (pc <= pc+1). Todo lo que tenga que ver con saltos (en el futuro, los saltos condicionales), han de agruparse juntos, y el resto de instrucciones van después del "else".

Si te has dado cuenta, sólo se puede saltar a las 256 primeras posiciones de la memoria, ya que el valor de "data" es de sólo 8 bits. Para comenzar he buscado la mayor simpleza posible y de momento va a continuar así. En el futuro ampliaremos el valor de la entrada data a 16 bits.

Finalmente tenemos el registro sysclk, que es el reloj interno del sistema. No he conseguido ejecutar instrucciones en un sólo ciclo de reloj real. Yo trabajo con la Alhambra II FPGA y funciona a 12 MHz; necesito dos ciclos de reloj real para poder ejecutar una sóla microinstrucción. Creo que esto es debido a que la memoria ROM es síncrona y necesita un ciclo para sacar el dato y otro para ejecutar la microinstrucción. Todo esto significa que cada microinstrucción consume 2 ciclos de reloj real. Esto equivale a decir que funciona a 6 Mhz, o dicho de otro modo, el cambio de estado de "sysclk" es de 6 MHz.

Si observas de nuevo el esquema (primera imagen de este post) habrás visto un "bombeo" de tics a muy baja frecuencia y sustituye el clk general. Esto lo he hecho así para poder ver el funcionamiento del programa a velocidad humana. Cuando tengamos el suficiente repertorio de microinstrucciones y podamos crear "delays", eliminaremos ese bombeo de tics y trabajaremos a la máxima frecuencia posible (la que dé tu placa entrenadora FPGA).

Sobre la máxima frecuencia hay un matiz que mencionar. Cuando programaba algo y lo hacía correr a la máxima frecuencia, unas veces funcionaba y otras no. Entonces me acordé de una cosa, y es que todo en electrónica necesita un tiempo de estabilización. De momento en este diseño no hace falta porque trabaja a muy baja frecuencia, pero en el siguiente circuito que publique llevará un retraso antes de ponerse en marcha para que cuando se trabaje a máxima frecuencia funcione siempre y a la primera. Repito que en este caso, llevando un bombeo de tics tan lento, no hace falta esta estabilización, tiene tiempo de sobra para estabilizarse.

En el ejemplo que adjunto, cuando lo subas y se ponga en marcha verás un blink de los 8 leds de la Alhambra II FPGA. El programa le dice que haga eso. Le eliminé el bombeo de tics y añadí una salida para el osciloscopio para ver cuál es la máxima frecuencia de parpadeo y este es el resultado:

signal 1.2MHz.jpg

Si quieres probar esto, ten en cuenta que este circuito no tiene implementado la temporización de estabilización de arranque. En el próximo post verás esta parte.

El circuito, con el bombeo incluido, pesa 71 LCs. Y sin el bombeo de tics pesa 41 LCs.

Propongo un ejercicio muy sencillo. Crea un programa que cuente de 1 en 1 en binario y se vea por los leds.

Saludos!
uC_phase_0.ice

Jo mo

unread,
Oct 13, 2023, 1:37:25 PM10/13/23
to FPGAwars: explorando el lado libre
Ola democrito,

It look an interesting starting point!
For the clock frequency, using pll you should be able to go above 60 Mhz (so 30 mhz if you neeed 2 cycles per instruction)

have a nice evening!

charli va

unread,
Oct 14, 2023, 3:09:46 AM10/14/23
to fpga-wars-explora...@googlegroups.com
Muy interesante Demócrito! te seguiremos en tu viaje! ;). ando con varias cosas pero en cuanto tengas algo más definido el set de instrucciones adaptaremos el compilador para tener al menos un ensamblador.

El tema de la frecuencia como dice Joaquim, puedes usar PLLs para tener mayor frecuencia de reloj y al final no mezcles lo que es el reloj del sistema y las instrucciones por segundo, yo no me obsesionaría con eso, ejecutar todo en un ciclo, necesitando leer de memoria, no sé si realmente hay algún procesador que lo haga porque en cuanto a diseño síncrono, si no me equivoco es imposible (siempre necesitarás un ciclo al menos para obtener los datos y otro para ejecutarlos).

En general todos los procesadores para medir su potencia, lo que se hace es pasar benchmarks ya establecidos que realizan una serie o conjunto de programas para medir lo rápido o lento que los ejecutas (por ejemplo el coremark), lo que pasa que al no ser un micro standar no se podrán nunca pasar XD

La velocidad máxima del circuito te lo calcula yosys en principio, esa debería ser una medidad orientativa de cuán rápido podría ir, en principio el tuyo podría correr ahora mismo a 683MHz!! está claro que está en un estadio muy primigenio e ir a esas velocidades implicaría muchas cosas más, pero bueno por lo que indica, a 12Mhz debería ir sin problema y usando PLLs subir la frecuencia no debería de dar problemas.

Captura de pantalla 2023-10-14 a las 9.00.39.png

En cuanto a la estabilización, no debería ser nada complejo , simplemente conque pusieras un sistema de reset con una máquina de la colección de Obijuan dándole, 1ms como mucho o algo similar antes de quitar el reset debería ser más que suficiente para que el sistema se estabilce eléctricamente, al no trabajar de momento con nada externo los tiempos internos de estabilización de la fpga no deberían de darte problemas, si los hay posiblemente sea debido a alguna otra cosa, posiblemente algún bug de sincronización de señales o algo así.

De echo quería comentarte por qué utilizas un registro para llevar el reloj interno en vez de usar el reloj general (curiosidad de por qué esta decisión).

De nuevo  muchas gracias Demócrito por compartir este viaje con nosotros.

Un abrazo!


--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.
Para ver esta conversación en el sitio web, visita https://groups.google.com/d/msgid/fpga-wars-explorando-el-lado-libre/3e332a7e-3f13-47aa-a5a5-fca3e668941an%40googlegroups.com.

Democrito

unread,
Oct 14, 2023, 6:04:31 AM10/14/23
to FPGAwars: explorando el lado libre
Antes de nada os quiero explicar lo que posiblemente ha llevado a confusión algunos de mis comentarios.

Yo tenía entendido que RISC-V podía ejecutar una instrucción por ciclo de reloj externo o reloj principal (el "clk" de toda la vida). De cosas "random" que leo y escucho (ya sabéis lo que significa ser autodidacta), quizás esto no lo entendí bien y se refería a otro tipo de ciclo (cada tipo de micro tiene sus historias internas). Creyendo que realmente se podía hacer estuve experimentado. Lo que sucede es que la memoria (ROM) al ser síncrona, necesita un ciclo de clk para poner el dato y otro para ejecutar la instrucción. Internamente (el verilog que hace de unidad central) ejecuta una instrucción por ciclo, el que marca el registro "sysclk".

En resumen, se necesita dos ciclos de clk para extraer el dato y ejecutarlo. Pero la ejecución de la instrucción en sí, sólo usa un ciclo.

Lo que no me ha funcionado e hice muchos intentos, es hacer que a la vez que la memoria saca un dato ejecute la instrucción asociada en ese mismo ciclo. Y claro, eso es lo que me hubiera gustado! porque funcionaría el doble de rápido. Quizás se podría resolver con ROMs asíncronas, como por ejemplo usando tablas. Esto lo experimentaré más adelante. 

Recurrir a PLLs siempre estamos a tiempo si el proyecto lo requiere. En este proyecto se trata de ir a la frecuencia que dé el clk de la placa. Y si alguien necesita que vaya más rápido, siempre puede poner un PLL. Mi intención no es ir a la fuerza a 12 MHz (mi placa da esa frecuencia), sólo buscaba leer la memoria y ejecutar la instrucción en un mismo ciclo de reloj. Lo que comenté al respecto me refería a una cuestión de eficiencia.

En ese primer post sólo puse 4 instrucciones, pero tenía en realidad casi 16. Quería presentar algo que fuese muy sencillo a la vista, e incluso que se viese obvio. Si hubiese puesto todo lo que tenía hasta ese momento, con los saltos condicionales (en especial), los desplazamientos, enmascaramientos (and, or, not), etc,  una parte del público se echaría para atrás en seguir con este proyecto. La intención es que la gente vea todo esto como una semilla para crear sus propios micros y no lo vea como algo demasiado complejo.

Antes de ayer cambié el orden de las microinstrucciones y todavía no tengo claro cómo lo voy a terminar de enfocar. Estoy pensando en enfocarlo en registros en vez de memoria RAM. El micro sería para resolver y automatizar pequeñas tareas que en electrónica pura fuese muy complejo. Cuando ya lo vea claro, entonces va a ser muy necesario tener un pequeño compilador/ensamblador. Ese tipo de herramienta facilitaría muchísimo la elaboración de programas.

Cuando publique el siguiente post sobre la evolución del micro, veréis cambios, como el orden de las instrucciones, los tamaños de los buses, etc. Pero lo importante ahora es ver la filosofía y que cada uno sea capaz de modificar y/o diseñar el suyo propio.

Saludos!

charli va

unread,
Oct 14, 2023, 6:27:45 AM10/14/23
to fpga-wars-explora...@googlegroups.com
Todo claro Demócrito ! y mucho ánimo que tiene muy buena pinta!

Lo de los ciclos por instrucción ni te preocupes, actualmente hay cientos o miles de implemtaciones de riscv en fpga, cada uno lo hace de una forma diferente y a cada uno le lleva o más o menos tiempo ejecutar cada una de las instrucciones.

Por ejemplo hay un micro muy interesante , que se llama Serv https://github.com/olofk/serv.  de Olof Kindgren que su objetivo es ser muy muy pequeñito a nivel de recursos, para ello está definido como bit serial cpu (Serial RiscV).  como todo se procesa en serie, cada instrucción le lleva decenas de ciclos, pero a cambio tienes un riscv en poquísimos recursos, que para muchas aplicaciones aunque sea lento es más que suficiente.

Te comento esto porque tu aproximación a micros muy pequeños y muy sencillos que permiten realizar taréas que a base de electrónica o verilog serían muy complejas, es una idea muy buena.

De echo te agradezco mucho que te tomes el tiempo para simplificar ejemplos para mandarlos y hacerlos accesibles.

Un gran abrazo y a darle!

--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Alfredo Carmona Mirats

unread,
Oct 14, 2023, 7:12:59 AM10/14/23
to fpga-wars-explora...@googlegroups.com
Muy interesante lo que estás haciendo Demócrito.
Estoy de acuerdo con Carlos, en el mundo industrial, para mí es más importante reducir los recursos (pequeña capacidad de ellos), que la velocidad a la que se ejecutan.
Con 12Mgh.... da y sobra para ello.
Ojalá el módulo diseñado por ti , pronto tenga un ensamblador adecuado.
¡Mil felicitaciones!...

Democrito

unread,
Oct 14, 2023, 12:21:14 PM10/14/23
to FPGAwars: explorando el lado libre
Gracias por el comentario Alfredo, a ver si conseguimos echar adelante este proyecto.

Un abrazo!

Democrito

unread,
Oct 15, 2023, 8:01:03 AM10/15/23
to FPGAwars: explorando el lado libre
Ahora vamos a ampliar el repertorio de microinstrucciones, introduciremos los saltos condicionales y por qué el salto directo va junto a los saltos condicionales. Se trata de comprender por qué unas cosas van a un lado y otras en otras.

separate jumps condicionals.png

Como ya se comentó en el primer post-tutorial, los saltos, sean condicionales o directo, han de estar separados de las demás instrucciones. Tenemos un "if" principal donde comprobamos si la microinstrucción es algún tipo de salto, y los metemos todos allí, es la zona que he marcado en azul. De no ser así ( que no es un salto) nos encontramos con un "else" principal, que los he marcado dentro de un cuadro verde. Además, en la zona verde lleva implícito el incremento del PC (enmarcado con un cuadro gris), y es por eso que se separan, ya que no se puede incrementar el contador de programa (pc <= pc+1) con los saltos, porque afectan al mismo registro.

Lo que quiero decir es que en un mismo ciclo de reloj no se puede asignar un valor, dos o más veces, a un mismo registro. Cuando se trabaja en paralelo esto no tiene sentido. Por cada ciclo de reloj sólo puede afectar un registro con un valor. Asignar varios valores a un mismo registro no tiene sentido. En este caso estamos hablando del contador de programa, el registro "pc".

Entendida esta parte, ahora vamos a hablar de los saltos condicionales.

El salto directo no tiene ningún misterio; le dices al contador de programa (pc) dónde saltar, eso es todo. Pero si queremos saltar bajo alguna condición, antes hay que preparar esas condiciones. Necesitamos un registro que guarde el valor que queremos comparar. Ese registro se llama "cmp". En ese registro guardamos el valor que queremos comparar y será cargado desde el programa (la ROM) a través del bus de entrada "data".

wire declaration and comparison assignments.png

Para ejecutar un microinstrucción de salto y pueda ser ejecutada en un mismo ciclo de reloj, necesitamos usar lógica combinacional. Es decir, que por fuera de lo secuencial [del "always @(posedge...)"], ya esté activa esta parte combinacional que además es ultrarrápida porque no requiere ciclos, pero necesitamos de un ciclo para dar por sentado que la salida es estable.

Creamos los cables para comparar, que son g, ne y l, que son las siglas en inglés de "greater" (mayor que), "not equal" (no igual) y "less" (menor que). Todo esto está en el cuadro rojo.

Y ahora asignamos la lógica combinacional, si es mayor, no igual o menor que el accumulador. Es lo que ves en el cuadro azul.

Cuando el programa vaya ejecutando las microinstrucciones, y dado que el valor de "cmp" (microinstrucción "10" ó "A" en hexadecimal) ya estará cargado antes de hacer un salto. Cuando llegue y se cumpla la condición, hará lo que le hayamos mandado, es decir, saltar a donde le hayamos dicho.

conditional jump.png

Cuando usemos las microinstrucciones 7, 8 ó 9, si se cumple la condición asociada a esa microinstrucción, hará lo que le pedimos, y saltará a donde le hayamos programado.

En este post he puesto "ne" (no igual) en vez de "igual" porque es la condición que más utilizo y en el ejemplo que adjunto sólo utilizo esa microinstrucción. En el siguiente post veremos esta parte ampliada, con las condiciones, mayor, menor, igual, no igual y cero.

No memoricéis las microinstrucciones porque no van a tener ese valor de forma definitiva. En este sentido estoy continuamente reordenando y cambiando sus valores. Lo importante es comprender cómo se estructura todo para que funcione esta pequeña máquina de Turing (todo lo que es programable es un máquina de Turing, luego en otro sentido se habla de arquitecturas, pero ese es otro tema y existen muchas).

Dejo un ejemplo donde se carga un valor al registro "cmp" (valor a comparar) y es utilizado dos veces en el mismo programa. La instrucción de salto "no igual" también es utilizada dos veces.

Por otro lado, hay dos nuevas instrucciones, que son la desplazamiento (<< y >>), según el valor que le digamos, se desplazará 1 o más veces, en este caso es 1.

El programa que lleva el circuito es el de desplazamiento de un led que se mueve arriba y abajo al igual que hace el coche Kitt de Michael Knight.
Tardará unos segundos en arrancar porque sigue llevando el bombeo de pulsos para poder ver a nivel humano los desplazamientos y el estabilizador de arranque absorbe ese tiempo inicial.

Saludos!



uC_phase_1.ice

charli va

unread,
Oct 15, 2023, 3:51:51 PM10/15/23
to fpga-wars-explora...@googlegroups.com
Buenas Demócrito! ando super enfrascado estos últimos días en unas cositas que llevo trabajando tiempo y que os voy a lanzar espero que mañana o pasado y no he podido probarlo pero tiene muy buena pinta.

En cuanto me libere de este hito miraré con ojo crítico lo que andas haciendo por si te pueden servir de algo mis opiniones, pero como te digo por encima muy buena pinta y tus explicaciones muy constructivas.

Gracias y deseando probarlo!

--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Jesus Arias

unread,
Oct 15, 2023, 4:54:32 PM10/15/23
to FPGAwars: explorando el lado libre
Hola, muy interesante. Quería hacerte unos comentarios acerca del reloj:

La ROM de programa, al ser una BRAM síncrona, la podemos ver como una ROM normal seguida de un registro. Y este registro constituye un "pipeline" en la ejecución de las instrucciones: En el mismo ciclo se está leyendo de la ROM la siguiente instrucción a la vez que se ejecuta la del ciclo anterior. O visto de otro modo: las instrucciones se ejecutan con un ciclo de retardo.
Realmente el único problema lo tienes en los saltos pues se va a ejecutar la instrucción que está después del salto. En algunos micros esto se llamaba "salto retardado" y se podía explotar para ejecutar algún código útil si fuera posible, o si no había que colocar una instrucción NOP después del salto.

Otras soluciones para evitar el divisor del reloj podrían ser:
- Invertir el reloj de la ROM, de modo que el dato lo tengas disponible en el flanco de bajada. (en el fondo sería equivalente a lo que estás haciendo pero con un reloj del doble de frecuencia)
- Usar una ROM asíncrona que se sintetizará usando LCs. Como el tamaño es pequeño esto puede ser factible.

Saludos

Democrito

unread,
Oct 15, 2023, 6:12:39 PM10/15/23
to FPGAwars: explorando el lado libre
Hola Carlos y Jesús,

Carlos: Cuando quieras nos ponemos manos a la obra. De lo último que he publicado a lo que tengo ahora hay muchas modificaciones. Y a parte de eso, he de añadir buses de entradas y de salidas. De momento esto es más bien una unidad de control que un micro en sí. Lo bueno es que teniendo un núcleo programable, por encima de él se le puede añadir lo que quieras. Si construimos un núcleo sencillo y con cierta eficiencia, el límite será la imaginación. Aunque mi enfoque de fondo es siempre el resolver pequeños problemas pero costosos electrónicamente. Y otra cosa importante para mí es que la gente de alguna manera lo pueda entender o intuir lo suficiente como para hacer modificaciones y adaptarlo a sus propias necesidades. 

Jesús, muchas gracias por los consejos, con tiempo iré probando, alguno que me has comentado ya la experimente como los flancos de subida o de bajada del CLK, pero no noté efecto. De todas formas he de volver a probar porque cuando estaba probando ese tema tenía a la vez otros problemas y me quedé con la duda porque necesitaba seguir hacia delante. Me falta por probar que la ROM sea asíncrona y lo de los saltos es algo que apunto y lo tendré presente. Gracias otra vez y me alegra mucho verte de nuevo por aquí.

Un fuerte abrazo a los dos!

Jesus Arias

unread,
Oct 16, 2023, 4:07:16 AM10/16/23
to FPGAwars: explorando el lado libre
Hola de nuevo. Vuelvo con más comentarios, esta vez relativos al juego de instrucciones y similares.
- La ejecución condicional veo que sigue el estilo MIPS / RISC-V, pero en su lugar se podrían poner registros de flags (Acarreo, Cero, Negativo...), tal como se hace en casi todo el resto de CPUs conocidas (ARM, PIC, 8086...). Eso seguramente va a necesitar de menos recursos. Las comparaciones se harían sumando (o mejor restando)
- Aparte de la instrucción OUT necesitarías también una instrucción IN. Y el campo data[7:0], que ahora no usas en esas instrucciones podría ser una dirección de un espacio de E/S de hasta 256 registros. (O tal ver se podrían llamar Load y Store en lugar de IN y OUT y la dirección apuntaría a una memoria de datos)
- Sería conveniente tener las instrucciones lógicas: AND, OR, XOR. Y tal vez la resta.

Respecto del código en Verilog, personalmente preferiría "case" a las cadenas "...else if...". La lógica combinacional necesaria se va a poder simplificar más.

En fin, que empezando a plantearse mejoras esto es un nunca acabar... Si no conoces el micro PIC10F200 te recomiendo que le eches un vistazo porque tiene un cierto parecido con tu diseño, al menos en lo tocante al tamaño de las memorias, y se pueden sacar ideas de su arquitectura y repertorio de instrucciones.

Saludos

Jesus Arias

unread,
Oct 16, 2023, 10:40:47 AM10/16/23
to FPGAwars: explorando el lado libre
Adjunto el ejemplo con la memoria síncrona / asíncrona. Solo hay que comentar / descomentar la línea "`define ASYN".
Curiosamente la versión con memoria asíncrona ocupa solo 35 LCs, incluso menos que la versión con BRAM (43 LCs). Ambas versiones corren con un reloj de 200MHz (probado).
Si se usa BRAM síncrona los saltos son del tipo retardado y se ejecuta la instrucción que sigue al salto. He cambiado el código para tener esto en cuenta.

Saludos
system.v

Democrito

unread,
Oct 16, 2023, 2:26:24 PM10/16/23
to FPGAwars: explorando el lado libre
Muy interesante todo. Acabo de mirar el PIC que mencionas, su arquitectura simplificada. Donde derrapo es en el verilog, tomaré tu ejemplo como referencia, porque pese a que me cuesta comprender algunas líneas, intuyo lo que hace. Mi verilog es de estar por casa.

De momento necesito mantener las estructuras else if porque de esa manera puedo meter el incremento del PC+1 donde se ejecutan las instrucciones que no son de salto y conseguir que cada microinstrucción sólo use un ciclo. Sé que con la sentencia "case" se vería más estructurado pero en mi caso no sabría separar los saltos (especialmente los saltos condicionales) del resto de instrucciones. He visto que tú lo has hecho con el salto directo, pero he de estudiarlo para entender cómo lo has hecho. Con tiempo, intentaré ir aplicando cosas que he visto en tu código. De hecho, cuando consiga definir lo principal del micro, lo primero que probaré es tu ROM asíncrona, aunque para que se vea todo más gráfico y comprensible, mantendré la ROM fuera del código principal como otro módulo, con su icono y todo eso.

Usar un registro de estado para los saltos condicionales fue lo primero que pensé, pero con el verilog que yo sé, me es imposible hacerlo sin usar más de un ciclo. Por eso tomé la decisión de convertir esa parte en lógica combinacional, creo que es una solución sencilla y efectiva pese a todo.

Algo que quiero evitar es poner demasiadas microinstrucciones. He visto que con 16 no es suficiente, así que lo limitaré a 32. Lo principal es tener un pequeño micro, quiero evitar que supere los 300 LCs (si fuese posible). Es por eso que se verá como un micro capado, en el sentido de que por ejemplo, sobre los estados de las operaciones no van a estar todos. Pero si veo que no pasa de cierto límite, entonces sí que podría ir incluyendo.

Los enmascaramientos ya lo tengo hecho, uso esas puertas AND, OR y XOR. La XOR servirá para hacer las negaciones de bits. También está incluida la resta. Lo de usar "data" para hacer multiplexaciones de entradas y salidas es algo que también se me ocurrió y es lo que usaré al final.

Adjunto ICE con algunas instrucciones más. El ejemplo sigue siendo un Kitt. Modifiqué el orden de las microinstrucciones y creo que voy a dejar de hacerlo, porque cada vez que añado algo, al querer agruparlas con cierta lógica, me veo obligado a cambiarlo todo.

Lo siguiente va a ser crear un bus de dato para la ROM de 21 bits, donde 5 bits sería para la microinstrucción (hasta 32 microinstrucciones) y 16 bits de datos.

Sobre la memoria volátil, he estado dándoles muchas vueltas. Estaba por crear unos pocos registros y sólo trabajar con ellos, pero me di cuenta que cuando amplíe el bus de datos se podría direccionar una memoria RAM sin problemas. En este sentido, lo que he pensado es hacer como hacen en la arquitectura Harvard, donde separan la ROM de la RAM. Cuando todo esté terminado y Carlos saque el compilador, cada celda de memoria puede llevar una etiqueta como nombre de registro. Quien maneje el micro podrá asignar tantos registros como desee y el resto de memoria podría ser usada de forma convencional.

Poquito a poco creo que podremos hacer algo interesante y útil. Muchas gracias por tus comentarios!

Saludos!

Democrito

unread,
Oct 16, 2023, 2:38:33 PM10/16/23
to FPGAwars: explorando el lado libre
Se me olvidó adjuntar el ICE. No hay mucha diferencia con lo anterior, sólo unas instrucciones más. La ROM la he reducido a 256B.

El programa que lleva la ROM sigue siendo el ejemplo Kitt.
uC_phase_2.ice

Jesus Arias

unread,
Oct 16, 2023, 4:47:12 PM10/16/23
to FPGAwars: explorando el lado libre
Hola de nuevo,
Tal vez te puedan resultar útiles algunos documentos que tengo en una web, así que te los enlazo:
Es un chuletario de circuitos digitales en Verilog que redacté este verano. Y
Es la documentación de la CPU de 16 bits que había comenzado a diseñar hace unos años y que todavía no he terminado de modificar. También van los archivos fuentes (Yo no suelo usar Icestudio y claro, todo está directamente en Verilog ;)

Respecto de los flags, algunos son bastante simples y no necesitan ni siquiera un registro:
wire negativo = acc[7];
wire zero = (acc == 0) ;  // NOR de los 8 bits de acc
El acarreo sí necesita un registro, y se puede considerar como el noveno bit de acc cuando se suma:
reg carry =0;
always @( posedge ....
...
   else if (cmd==SUMA)  {carry, acc} = acc + data;
   else if (cmd==RESTA) {carry, acc} = acc + (~data) + 1; // Suma el complemento a dos de data
...
Saludos

charli va

unread,
Oct 16, 2023, 5:04:27 PM10/16/23
to fpga-wars-explora...@googlegroups.com
Muchas gracias Jesús por aportar tanto! hace tiempo estuve mirando tu riscv larva y la verdad que me pareció una pasada.

Sobre lo de verilog y icestudio es lo de menos, tus aportaciones son fantásticas, gracias por compartir con nosotros tu conocimiento.

Este hilo promete ser muy intersante , seguro que todos aprendemos mucho.

Un abrazo!

--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Democrito

unread,
Oct 16, 2023, 6:12:23 PM10/16/23
to FPGAwars: explorando el lado libre
Muchas gracias por toda la info que has puesto. Me va a venir de perlas. Ya he leído casi todo, alguna cosas me lo saltaba, otras con atención. En alguna ocasión he sonreído un poco porque muchas de las cosas que pienso o quiero aplicar las estaba leyendo en tus documentos (Harvard, un ciclo, etc).

Ya me lo he descargado todo y puesto en una carpeta para ir consultando a medida que me surjan dudas.

Lo que tengo pensado sobre el micro de momento seguirá la misma dirección, y luego poco a poco trataré de ir aplicando tus experiencias.

Muchas gracias de nuevo.

Jesus Arias

unread,
Oct 17, 2023, 5:56:55 AM10/17/23
to FPGAwars: explorando el lado libre
El ejemplo anterior tiene una errata sutil que se me ha colado:
La línea:
   reg [11:0]romout=0;
debería ser:
   reg [11:0]romout;

Si se inicializa esta variable yosys es incapaz de mapear la ROM en un bloque BRAM y acaba sintetizándola con LCs. El problema es que si no se inicializa el valor inicial es X y no se puede simular nada. Habría que poner una señal de reset que durase al menos un ciclo para evitarlo.

Democrito

unread,
Oct 17, 2023, 1:53:59 PM10/17/23
to FPGAwars: explorando el lado libre
Ya he conseguido hacer funcionar la ROM asíncrona mediante una tabla. Ahora ya trabaja a 1 ciclo de reloj todo el circuito. He eliminado la "estabilización de arranque" y de momento parece que funciona bien y a la primera (he insistido en esta parte y arranca bien desde la puesta en marcha).

Al hacer esto, y como comenta Jesús, la ROM no se infiere en las BRAMs, sino que consume LCs de la FPGA.

Me ha dado por probar aquello de "reg [11:0]romout=0;" aunque sea una errata y sale este mensaje. En la imagen "romout" es "tabla".

requires systemverilog.png
Jesús, no sé cómo se puede crear una señal reset durante al menos un ciclo de reloj. Si tú o alguien sabe cómo inferir una ROM en las BRAMs, estaría súper agradecido.

Por otra parte he experimentado la velocidad máxima de un blink, ahora es el doble de rápido:

Async.jpg

Antes era 1.2MHz y ahora es 2.4MHz.

Adjunto circuito de prueba. El "clk" del bombeo está desconectado para que le entre la frecuencia del "clk" de 12MHz (en mi caso es esa frecuencia).

Saludos!

Democrito

unread,
Oct 17, 2023, 1:59:03 PM10/17/23
to FPGAwars: explorando el lado libre
Otra vez se me olvidó adjuntar el circuito de prueba...

Por cierto, donde digo: 

<<.... cómo inferir una ROM en las BRAMs....>> 

quise decir 

<<...cómo inferir una ROM asíncrona en las BRAMs...>>

Ahora sí, ICE de prueba adjuntado.
uC_phase_3.ice

charli va

unread,
Oct 17, 2023, 4:16:53 PM10/17/23
to fpga-wars-explora...@googlegroups.com
Que interesante se pone este hilo! nunca he experimentado con roms asíncronas!

ahora os acabo de soltar "un melón" y tengo el siguiente ya en marcha, pero no sé si voy a poder aguantar a probar esto 😅

--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Jesus Arias

unread,
Oct 17, 2023, 5:18:01 PM10/17/23
to FPGAwars: explorando el lado libre
Hola,
En mi ejemplo "romout" no es la tabla de la memoria sino el registro de salida.
Y para meter la ROM en una BRAM no hay más remedio que usar una ROM síncrona. Aunque puedes usar el truco de invertirle el reloj, como en este ejemplo en el que también utilizo un contador para generar un pulso de reset de unos 8 ciclos:

module SYSTEM (
input clk,
output [7:0]leds
);

/////////////////////
// RESET (8 ciclos)
/////////////////////
reg [3:0]resetcnt=0; // aprovecho el valor inicial para arrancar con reset activo
wire reset = ~resetcnt[3];
always @(posedge clk) resetcnt <= resetcnt + reset;

///////////////////////////////////////////
// ROM de programa BRAM, flanco bajada clk
///////////////////////////////////////////
reg [11:0]ROM[0:255];

integer i;
initial begin
ROM[0]=12'h100; // load 0
ROM[1]=12'h300; // out
ROM[2]=12'h201; // add 1
ROM[3]=12'h601; // jmp 1
for (i=4;i<256;i=i+1) ROM[i]=12'hxxx;
end
reg [11:0]romout;
always @(negedge clk) romout<=ROM[pc];

wire [7:0]data = romout[7:0];
wire [3:0]cmd  = romout[11:8];

/////////////////////////////
// micro
/////////////////////////////
reg [7:0]pc=0;
reg [7:0]acc=0;
reg [7:0]out=0;
// registro PC aparte por el reset asíncrono...
always @(posedge clk or posedge reset)
if (reset) pc <= 0; else  pc <= (cmd==6)? data : pc +1;
// El resto no tienen reset...
always @(posedge clk) begin
case (cmd)
1: acc <= data;

2: acc <= acc + data;
3: out <= acc;
default: ;
endcase

end

//////////////////
// I/O
assign leds=out;

endmodule

El problema con este apaño es que ahora la frecuencia máxima es como la mitad que cuando romout cambiaba en "posedge", aunque siguen siendo 99MHz. Y la ROM efectivamente se ha ubicado en una BRAM:
Info: Device utilisation:
Info:            ICESTORM_LC:    48/ 7680     0%
Info:           ICESTORM_RAM:     1/   32     3%

Saludos
cap1.gif

Democrito

unread,
Oct 17, 2023, 7:11:41 PM10/17/23
to FPGAwars: explorando el lado libre
He tomado una RAM y le he anulado desde fuera de la caja de código todo lo relacionado a la lectura, para asegurar una ROM síncrona. Por alguna extraña razón se ha inferido bien (si lo hacía desde el código, tenía el problema de la necesidad de dos ciclos de clk por instrucción) para crear la ROM síncrona y así inferirse en en la BRAM.

Si dentro de la ROM síncrona le pongo "posedge", la frecuencia máxima de blink medida en el osciloscopio es de 2MHz clavado. Esto me parece un poco raro, pero al parecer funciona bien.

Al sintetizar me sale esto: (mirar abajo del todo en la imagen) 

posedge.png


Luego, le cambié lo mismo dentro de la ROM por "negedge". Ahora funciona mucho mejor porque el blink medido con el osciloscopio es de 2.4MHz, es decir, lo que esperaba realmente.

Pero al sintetizar, resultó que la frecuencia máxima que soporta el circuito es un poco menos de la mitad: (Observar la parte de abajo de la imagen)

negedge.png

Todo exactamente igual, menos la frecuencia máxima.

Pese a todo, creo que lo voy a dejar así, dejando dentro de la ROM el "negedge", porque en la salida encuentro la frecuencia que espero en el osciloscopio. Y si por lo que sea, esto a la larga fuese un problema, siempre estamos a tiempo de cambiar esa opción.

En estos días voy a ampliar los buses, meter al menos una entrada externa y dos salidas, ampliar al menos un bits el registro de trabajo para detectar el overflow y los negativos, y muchas otras cosas que me recomendó Jesús.

Gracias por todo, Jesús.

Un saludo muy cordial.

Jesus Arias

unread,
Oct 18, 2023, 3:02:26 AM10/18/23
to FPGAwars: explorando el lado libre
> "Si dentro de la ROM síncrona le pongo "posedge", la frecuencia máxima de blink medida en el osciloscopio es de 2MHz clavado. Esto me parece un poco > raro, pero al parecer funciona bien."
Eso es porque estás ejecutando una instrucción más en el bucle, en concreto la de la posición 0x5 (seguramente 0x000 == NOP)
Este es un comportamiento normal cuando se tiene el "pipeline" del registro de salida de la ROM. Todas las instrucciones se ejecutan un ciclo tarde, y en el caso del salto eso supone cargar la siguiente instrucción antes de saltar.

Al cambiar al flanco de bajada lo que hacemos es simular una memoria asíncrona con un retardo de 1/2 ciclo, de ahí que la frecuencia máxima sea la mitad: todos los retardos tienen que caber en 1/2 ciclo en lugar de un ciclo completo. Pero por otra parte ya no tenemos el molesto "pipelining"
Saludos

Democrito

unread,
Oct 18, 2023, 4:35:50 AM10/18/23
to FPGAwars: explorando el lado libre
En el caso del "negedge" también lo pensé así.

Cuando has explicado el caso del "posedge" me he dado cuenta que es tal como lo cuentas. Cuando experimentaba al comienzo de este proyecto, si ponía dos "jumps" al final del programa (con distinto salto), se quedaba con el segundo. Y esto es un poco peligroso...

Gracias por la explicación!

Democrito

unread,
Oct 20, 2023, 8:48:14 PM10/20/23
to FPGAwars: explorando el lado libre
Hola otra vez.

En este post voy a tocar el tema de los puertos de salida.

Antes teníamos sólo un puerto de salida. Se le decía en código máquina: "toma el registro acumulador y ponlo a la salida". Esto significaba que sólo necesitábamos la microinstrucción (también llamada "opcode", o código operacional) que recordemos era de 4 bits (cmd) y ahora lo he subido a 5 bits para meter más instrucciones. En otras palabras; la ROM nos da un bus de datos de 13 bits, de los cuales 8 son de datos y 5 de microinstrucción (u "opcode"). Para hacer una salida de datos (hasta ahora) se desaprovechaba el dato asociado al opcode.

Podemos aprovechar el dato asociado de la ROM, que viene siempre con el opcode, para demultiplexar (lo contrario a multiplexar) salidas. Podríamos demultiplexar hasta 256 salidas de 8 bits, pero eso son demasiadas salidas, así que las dejamos en, por ejemplo, 4.

El esquema se vería así:

portout micro.png
De esas 4 salidas, en este ejemplo he tomado la salida "o1", con su validación "val1" y un registro de 8 bits lo registra a la salida. Este registro tiene una modificación, pero de ello hablaré más abajo.

En el código máquina podréis observa un programa muy sencillo (recomiendo abrir el ICE que adjunto), básicamente cargo el acumulador con un valor constante y acto seguido lo saco fuera.

Por ejemplo, si observamos el programa, vemos que en la primera línea pone: "0781" (es en hexadecimal), donde "07" significa: carga en el acumulador este valor que te doy, que es "81". Es decir, es como si fuese esto: accumulador = 'h81.

La siguiente línea es "0801", que viene a decir: "saca el valor del acumulador (08, opcode de output) por el puerto 1 (data = 01)". Hay 4 puertos, el 0, 1, 2, y 3. Y he decido sacarlo por el puerto 1.

Luego se repite todo otra vez, pero el acumulador va tomando a lo largo del programa otros valores, y siempre sale (esos valores) por el mismo puerto.

opcode 08 output.png
En esta imagen podemos observar que la unidad de control, al detectar el opcode "08" toma el acumulador y lo pone a la salida, y al mismo tiempo hace dos cosas a la vez, cambiar de estado un registro que se llama "stb" (strobe). "stb" es un registro de 1 bit, si miráis el código que hay más arriba veréis que ese registro se asigna como cable a "done"; luego por otra parte, toma el dato asociado al opcode (data) que demultiplexará la salida, en este circuitos son 4 salidas posibles.

A la derecha hay otro circuito, que se encarga de demultiplexar la salida y dar un tic (un ciclo de reloj) a la salida adecuada (a la que se haya programado). Cada vez que se ejecute el opcode "08", "stb" cambiará de estado. Para conseguirlo hacemos una xor a stb. El circuito que hay a la derecha, detectará estos cambios de estado (detecta flanco de subida y de bajada), y producirá un tic en cada cambio de estado.

reg negedge.png

Como estamos procurando ejecutar todas las instrucciones con un sólo ciclo de reloj, hemos de jugar con inconvenientes que nos podemos encontrar. El registro de 8 bits que ha de registrar la salida, no puede funcionar como normalmente lo hace. En este caso, en vez de registrar los bits de entrada con un "posedge clk", lo hace con un "negedge clk". Esto es lo mismo que hicimos con la ROM, para poder ejecutar instrucciones en un sólo ciclo. Para diferenciarlo de los registros que estamos acostumbrados, lo especifiqué en el icono del módulo.

Adjunto un ejemplo, donde veréis un Kitt doble. Es decir, dos luces que se cruzan, o visto de otra manera, dos luces que rebotan consigo mismas, todo depende de cómo se observe.

Debido a que seguimos sin poder crear pausas en el código (cuando tengamos memoria para registros, esto cambiará), uso un clk externo a muy baja frecuencia para que se pueda ver a velocidad humana.

Saludos!

Nota: En otros post dije que aumentaría a 16 bits los datos. Al ver que subía el consumo de recursos más del límite que me he puesto, voy a seguir con 8 bits de datos.
uC_phase_a.ice

Democrito

unread,
Oct 20, 2023, 9:31:53 PM10/20/23
to FPGAwars: explorando el lado libre
Por cierto, muchísimas gracias a Jesús, por todas las indicaciones que me dio. Ahora estoy re-leyendo más atentamente sus publicaciones, que por cierto, lo hace un lenguaje muy comprensible.

Indicaciones de cómo hacer bien las cosas.png

Me descargué todos sus enlaces y los guardé en una carpeta. Voy leyendo poco a poco y aplicando lo que dentro de mi capacidad puede ir absorbiendo.

Lo pongo aquí para quien esté interesado y no se haya dado cuenta:

charli va

unread,
Oct 21, 2023, 2:34:46 AM10/21/23
to fpga-wars-explora...@googlegroups.com
Gracias por compartir Demócrito! he probado tu ice y  funciona tal cual lo estás comentando, en cuanto desempantane el usb, voy a ir probando tus ice de este hilo en diferentes placas, una próxima feature que estoy cerrando es tener una "placa genérica" y que puedas seleccionar la "objetivo" para la síntesis o el upload.

Como tu diseño de cpu va a ir creciendo en el tiempo en complejidad me va a venir muy bien para ir testeando "progresivamente" si esto funciona.

Así que agradecerte mucho  todo el esfuerzo.

También agradecer a Jesús que esté participando aportando todo su saber, que es mucho, ¡Gracias Jesús!

--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Democrito

unread,
Oct 21, 2023, 6:02:07 AM10/21/23
to FPGAwars: explorando el lado libre
Perfecto Carlos! 

Que se pueda sintetizar en diferentes placas significará que el micro es válido, de lo contrario habrá que revisar la "particularidad" de que sólo funcione en algunos casos. En este proyecto está todo tan "pillado por los pelos" que me espero cualquier cosa.

En teoría, si la frecuencia del reloj principal no sobrepasa de ciertos límites, debería de funcionar bien, ya veremos lo que dice la realidad...

Por cierto, te imagino con mucho trabajo (Icestudio, HID, probar placas, etc), te envío mucha fuerza!


charli va

unread,
Oct 21, 2023, 6:26:47 AM10/21/23
to fpga-wars-explora...@googlegroups.com
Ni te lo imaginas! voy a tope! llevo todo este año trabajando en muchas cosas de icestudio y proyectos relacionados, que poco a poco van confluyendo y relacionándose entre ellas de forma que lo que hago para un lado suma en el otro, últimamente siento que es como si cada una de las cosas que llevo arrancadas desde hace tiempo que incluso no tenían relación aparente, están acabando en el mismo rio haciendo que cada vez coja más fuerza.

Sobre tu micro me está encantando tu viaje, no sabes a dónde nos va a llevar! siempre tengo un proverbio presente que me saca del agujero cuando me vengo abajo, es muy tonto pero dice algo así como que "Un viaje de mil millas comienza con un solo paso"

A mi veros usar icestudio y que compartamos en comunidad lo que vamos haciendo, me llena de energía, así que gracias por los ánimos Demócrito y a todos los demás, espero poder también transmitiros energía e ilusión por todo esto.

¡Un abrazo y buen finde fpga!



--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Jesus Arias

unread,
Oct 23, 2023, 3:33:32 AM10/23/23
to FPGAwars: explorando el lado libre
Hola, me alegra ver que mis documentos resultan útiles ;)

Y quería aprovechar para hacerte un par de comentarios. El primero que no hay necesidad de tener un reloj activo en los flancos de bajada en los registros OUT. El resto de los registros como Acc o PC se actualizan en el flanco de subida, así que la instrucción OUT se está ejecutando (modificando OUT) medio ciclo antes que el resto, y aunque esto no veo que sea un problema, tampoco creo que suponga una ventaja.

El otro era que la BRAM en realidad tiene 16 bits de ancho aunque sólo estés usando 13. Así que unas instrucciones de 16 bits no van a ocupar más espacio en la FPGA. En las FPGAs ICE40 un bloque BRAM es de 256x16 aunque se puede reconfigurar como 512x8, 1024x4, o 2048x2.

Saludos

Democrito

unread,
Oct 23, 2023, 7:03:20 AM10/23/23
to FPGAwars: explorando el lado libre
Buenas Jesús,

Lo del flanco de bajada supongo que te refieres a que puse una puerta NOT en la validación de la salida OUT, oculto dentro de un módulo. He eliminado esa puerta NOT y funciona tal como dices, es decir, bien.

El dato de salida de la ROM a 16 bits en vez de 13, lo recuerdo porque lo leí en uno de tus documentos y también Juan González lo explica en uno de sus tutoriales. Esto me lo he de mirar bien. Hace una semana hice una ampliación de este tipo y me subía los LCs más de la cuenta, entonces volví al bus actual, pero cuando lo has comentado me ha venido a la cabeza otra forma de enfocar esto.

He añadido cuatro puertos de entrada, ha sido más sencillo de lo que a priori pensaba. Todas las entradas y salidas están registradas.

Dentro de un par de días espero tener instrucciones que maneje la memoria RAM.

Adjunto nuevo ICE. No hay muchos cambios, sólo los puertos de entrada y registrar todas las salidas, quedando oculto dentro del módulo.
uC_phase_b.ice

Democrito

unread,
Oct 23, 2023, 2:53:55 PM10/23/23
to FPGAwars: explorando el lado libre
Vale, ahora he entendido lo de ampliar el bus.

Si el bus de datos que sale de la ROM fuese de 16 bits, puedo tomar 5 como opcodes, y 11 como "data", y como "data", entre otras cosas, direcciona la memoria, me daría hasta 2K (11 bits).


charli va

unread,
Oct 24, 2023, 1:15:58 PM10/24/23
to fpga-wars-explora...@googlegroups.com
note: for English speakers, if you are interested in following this thread in English, say so and I will start sending emails in both languages, it is a lot of effort and I don't know if this thread is being followed by non-Spanish speaking users, you just have to let me know and I will be happy to do so

Buenas Demócrito, te mando unas sugerencias por mi parte, no quiere decir que debas seguirlas, simplemente me apetecía mirar más a fondo tu micro porque creo que entre todos podemos aprender mucho, como te digo son sólo comentarios, coge lo que te guste o te pueda resultar interesante . Te adjunto una modificación del circuito versión A, el versión B no me funciona, se queda un led parpadeando y como ando con el tiempo pillado no quería retrasar escribirte esto por buscar el bug o avisarte y esperar.

Lo que te cuento se puede aplicar a la versión b si te gustara , así que nada aquí te voy contando:

1) Por empezar de lo más sencillo a lo complejo, comentarte sobre el sistema de reset, para mi punto de vista es algo que debería ir fuera del micro, el micro  debería simplemente de gestionar que si recibe un pulso por el reset, reinicializarse.

Cuando implantemos el micro con más periféricos, en general lo lógico es que haya un sistema de inicio y reset para todo  el conjunto, si cada cosa tiene su propio sistema y sus tiempos, al final sincronizar todo realmente será duro (cuando haya más cosas que unas dependan de otras ....). Así que te he creado un pequeño sistema de reset externo, como te digo ni será lo más óptimo, pero en principio va muy bien.

El sistema de reset inicializa a los 10ms (puedes configurar esto con el parámetro correspondiente)  y si se recibe un pulso desde el pulsador 1 de la Alhambra II.

El circuito verás que es algo así:

reset.png


2) Te recomendaría que te plantearas de inicio los nemotécnicos de las instrucciones te ayudará a definir bien el lenguaje y no crear instrucciones que puedan estar duplicadas , además te ayudará a que el código sea más sencillo de leer. cuando se complique estar recordando si el 0x16 es el JMP o el ADD será motivo de confusiones, yo al menos me gusta leer un código que vaya entendiendo, si pones números te obligarás si quieres mantenerlo bien a meter muchos comentarios, lo que al final redunda y pierde atención si por ejemplo hay variables que su nombre ya te ayuden a entender, además de que si usas esas variables, si cambiaras el código binario , ampliaras bits, etc casi todo el código sería inmutable, menos donde lo inicialices.

Para esto te he creado un grupo de cables que funcionan a modo de flags,según las condiciones se ponen a 1 o a 0 para decir si estás en esa instrucción, el código lo verás así:

nemos.png

como ves es tu mismo código de los ifs pero secuenciado al inicio, de esta forma si por ejemplo cambiaras el código de alguna instrucción o ampliaras bits, etc, sólo tendrías que cambiarlo aquí, en el resto de lugares como verás usaras el isXXX para saber si estás en esa instrucción.

Por otro lado algo tonto, pero te he quitado la inicialización de los registros al inicio, esto a veces hace que en la síntesis se consuman recursos para ello, yo prefiero definir bien el reset y el start y que en esa fase se inicialice todo como se debe. En este caso como  es muy sencillo, los primeros 10ms puede estar todo "loco", habría que mejorar la etapa de reset del estilo a que en vez de un pulso, sea un estado (1 activo ,0 en reset, o al revés) y que el circuito de inicio se encargue de al arrancar tener el reset en modo activo hasta que llegue el primer pulso, esto si quieres le doy una vuelta estos días para dejar uns istema de reset limpio, ando con poco tiempo.y por eso lo he simplificado en lo que te mando.

También te he simplificado la sintaxis símplemente , no afecta a rendimientos ni nada pero creo que queda más claro, no hace falta en este caso declarar el wire y luego hacer los assigns, a mi al menos me parece más sencillo de seguir que en este caso en un vistazo ves ya todo:

Captura de pantalla 2023-10-24 a las 18.27.00.png

Con esto en la parte de asignaciones continuas, el loop se simplifica muchísimo, si te fijas, dejo que se hagan operaciones en paralelo que aparentemente es absurdo, es decir en vez de hacer un if para separar por cada instrucción, agrupo como has hecho tu y ejecuto todos a la vez, esto lo hago porque como verás, la lógica de ifs es muy costosa en la fpga. Esto es un quiebro mental que hay que hacer, aunque la sintaxis se parezca a la programación secuencial a la que estamos acostumbrados, en la fpga todo se convierte en espacio y tiempo, los recursos van a. ocupar su lugar, no es como en programación secuencial que todo está en memoria y vamos iterando en ese proceso, aquí hay unos elementos físicos que se reconfiguran y van a estar ahí, el if no va a dejar que ocupes unas áreas de la fpga.

Al revés, los ifs generarán interconexiones que pueden ser innecesarias y hacer que se ocupen. más recursos y que el circuito pueda tener más limitaciones de velocidad.

Así que de este modo el loop se simplifica mucho, se agrupan las instrucciones por tipología y como ves se lee super bien (dejarás de enredarte con ifs).

loop.png

Como verás para este tipo de situaciones me gusta utilizar la nomenclatura del ? y de los :    porque como te he comentado arriba par ami es muy importante que el código se lea rápido, de este modo no tengo que andar mirando a qué if corresponde el end o leerme un comentario para entender qué pasa, dices mira el pc cambia de dos modos , vale data si  estás en estas 7 instrucciones y si no lo incrementas y así....

Esto se puede ir desenrollando si hace falta o convirtiéndose en ifs y como te comento, al menos mi experiencia es que si las cosas no interfieren hay que verlo en paralelo y no secuencial y ahorrarnos unos cuantas "rupturas" del flujo que al fin y al cabo es lo que hace el "if".

Luego otro consejo en los ifs, aunque sean de una línea pon begin y end, oí una frase que se me quedó grabada hace mucho tiempo sobre programación en C que dice que "los ifs sin paréntesis los carga el diablo", cometer un error a largo plazo porque metas una instrucción que pensabas que estaba en el bloque del if y que ha quedado fuera pueden ser horas de debug locas por un error absurdo.

Los resultados de esta refactorización, mira, por un lado aquí está el tuyo original:

resources_0.png


Luego tenemos el tuyo , con el reset sólo con el botón para poder compararlo sin circuitería adicional:
resources_1.png

Y luego tenemos el último limpiando inicialización de variables y metiendo el circuito de reset:

resources_2.png


Como ves es muy interesante, con el cambio de código hemos ahorrado entorno a 49LC . manteniendo la velocidad y aunque parezca poco piensa que según vaya creciendo el micro estas diferencias de cómo se implementan las cosas supondrá un gran ahorro de recursos físicos.

Por otro lado fíjate en el tercero al quitar el reset interno del micro, lo que hemos conseguido, aunque aumentan los lc , lógicamente por haber más circuitería, es que hemos ganado prácticamente 10MHz

Por otro lado comentarte que la versión b, por alguna razón a mi no me funciona, se queda parpadeando la luz en el bit 7, no me he parado a ver el bug que pueda tener, más que nada avisarte aunque seguro que ya lo tienes rodado.

Espero que esto te pueda ser de utilidad te iré siguiendo en tu evolución y en lo que pueda iré colaborando y aprendiendo de vosotros.

Un gran abrazo!


El lun, 23 oct 2023 a las 20:53, Democrito (<spo...@gmail.com>) escribió:
Vale, ahora he entendido lo de ampliar el bus.

Si el bus de datos que sale de la ROM fuese de 16 bits, puedo tomar 5 como opcodes, y 11 como "data", y como "data", entre otras cosas, direcciona la memoria, me daría hasta 2K (11 bits).


--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.
uC_phase_a_mod.ice

Jesus Arias

unread,
Oct 24, 2023, 4:51:31 PM10/24/23
to FPGAwars: explorando el lado libre
Hola a todos
Antes de seguir con el diseño creo que convendría pararse a pensar qué registros y qué instrucciones queremos tener.
Ahora mismo sólo se puede tener una única variable en los programas (en Acc) y eso es muy poco. Así que va a ser necesaria una memoria de datos urgentemente. Esta memoria además incluiría un rango de direcciones para E/S.
 
La memoria de datos nos daría la posibilidad de tener más modos de direccionamiento:

En las instrucciones de dos operandos (como ADD) un operando está en Acc, y el otro:
- Direccionamiento Inmediato (o Literal)(Es lo que ya tenemos): Es el dato que está en los 8 bits LSB de la instrucción. Ejemplo "ADD 4"

- Direccionamiento Directo: El dato está en la memoria de datos, en la dirección apuntada por los 8 bits LSB de la instrucción. Ejemplo "ADD (4)"

- Direccionamiento Indexado: El dato está en la memoria de datos, en la dirección apuntada por un registro puntero. No tenemos todavía ese registro, podría ser el propio registro CMP o podríamos llamarlo X: "ADD (X)"

El direccionamiento directo también sería bueno para los saltos, al menos en los que no tienen condición. Así podríamos retornar de subrutinas si guardamos la dirección de retorno previamente en una posición de memoria:

LD A,aqui ; direccion de retorno
STA (0)   ; STA () es como OUT, pero en la memoria de datos
JMP subrutina
aqui:...

subrutina:
....
JMP (0); Retorna

El resultado de la instrucción también se podría dirigir:
- Al registro Acc: ej. ADD A,3
- Al registro X:   ej. LD X,25
- A la memoria de datos, apuntada por los 8 bits LSB de la instrucción o por X. ej. ADD M,(3)   ADD M,(X)  
- Al registro PC (saltos) JMP 25 ,  JMP (25) ,  JMP (X)

Con los direccionamientos directo e indexado se pueden ejecutar algunas instrucciones con los datos de la memoria sin usar Acc: INC (25)  DEC (X)

También creo que es buena idea pensar en el sistema como un circuito con sus bloques y sus señales de control antes de ponerse a describirlo en Verilog (intentaré adjuntar un posible boceto). Esto también ayuda a simplificar. Por ejemplo, ahora mismo los saltos g, l, y e son innecesarios pues esa información se puede conseguir sumando una constante a Acc. Por ejemplo, supongamos que queremos saber si Acc es mayor que 10, Sumamos 245 a Acc, y:
-Hay acarreo si Acc >10
-El resultado es cero si Acc == 11
-El resultado es negativo (bit 7 en uno) si Acc <=10
Tan solo habría que evitar escribir el registro Acc cuando se hace la comparación, se necesitarían flip-flops para los flags, y posiblemente una instrucción de resta sea más adecuada que la suma. Al usar la ALU para hacer la comparción no es necesario sintetizar las comparaciones >, =,  y < que tienes ahora mismo en el código.

 
Saludos
CamScanner 10-24-2023 22.35.01_1.jpg

Democrito

unread,
Oct 24, 2023, 6:23:34 PM10/24/23
to FPGAwars: explorando el lado libre
Carlos, sencillamente me encanta. Ahora lo que falta es que mi pequeño cerebrillo consiga acostumbrarse a ese tipo de estructura, me cuesta un rato, pero al final le veo la lógica. Voy a procurar "imitar" todo lo que has puesto para continuar. Necesitaba ver algo así como lo que has puesto y que pueda identificar el antes y el después, eso me ayuda mucho a comprender.

Sobre versiones de micros que falla y otros no... Ufff... He visto cosas muy raras yo también. Como puse un bombeo de tics para ver a velocidad humana la ejecución de las instrucciones, me ha sucedido que por ejemplo, pongo 23Hz (en el bombeo) y funciona, lo cambio a 24Hz, y falla.
Por otro lado, hay varias versiones anteriores de micros done la ROM tiene una errata, le falta un "else", pero según parece el sintetizador interpretaba correctamente que lo que quería era una ROM.

He estado intentando leer una RAM como módulo aparte y no lo consigo. Y he probado de todo. Lo que voy a probar ahora es a meter registros en vez de RAM. Y si funciona bien, intentaré inferir una RAM en las BRAM desde la misma caja del código del micro. No me ayudéis con la memoria porque necesito primero intentarlo hacer por mí mismo, y si no lo consigo, entonces sí que necesitaré vuestra ayuda. O si lo consigo resolver, y veis otra forma de hacerlo mejor, también estaré muy agradecido.

Jesús, el ejemplo que has puesto para evitar usar los comparadores lógicos, ahora sí que lo he entendido, y son tipos de "trucos" que me encanta saber. De momento quisiera mantener los comparadores convencionales porque a simple vista los puedo identificar. Luego, cuando la base del micro esté hecha, le iré aplicando esos cambios.

Por supuesto, hay que añadir más registros. Ahora mismo estoy atascado con la memoria, pero en cuanto lo consiga resolver, añadiremos las instrucciones necesarias para que sean capaces de direccionar memoria.

Cuando consiga resolver la memoria RAM daré por finalizado lo esencial del micro. A partir de ahí creo que ya se podrá añadir y modificar lo que sea necesario.

Para todo el mundo: Hasta ahora, básicamente he trabajado con módulos pre-fabricados de Icestudio. Como ya comenté, mis conocimientos de verilog son muy limitados, pero creo que con este proyecto voy a aprender un montón. Os agradezco de todo corazón todas las correcciones, ideas, etc que me hacéis, pero necesito un poco de tiempo para ir asimilando.

Muchísimas gracias de verdad. 

charli va

unread,
Oct 25, 2023, 12:22:59 AM10/25/23
to fpga-wars-explora...@googlegroups.com
Nada Demócrito para eso estamos! todos aprendemos.

De todas formas lo que te ha comentado Jesús es muy importante, yo separaría por un lado aprender Verilog y demás detalles de la implementación que irán sobre la marcha y en sí el diseño del micro.

Seguro que sería muy chulo si te pararas a conceptualizar el micro (igual que luego el set de instrucciones) y pudieramos debatir sobre esa arquitectura antes de implementarla, dale una vuelta que mientas que andas con la RAM, puede ser interesante que nos lances algún dibujo tipo el de Jesús o conceptos de hacia dónde quieres ir.

Un fuerte abarzo y mucha energía!

--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Democrito

unread,
Nov 14, 2023, 4:20:27 AM11/14/23
to FPGAwars: explorando el lado libre
Unos post más arriba comenté que para ver la ejecución a velocidad humana le implementé un bombeo de pulsos. Después me comentó Carlos que había algún circuito que le iba y otros que no. En otro post le comenté que a mí también me sucedía, pero si le cambiaba la frecuencia volvía a funcionar (en según qué frecuencia fallaba).

Este comportamiento me traía por la calle de la amargura. Si todo estaba bien, ¿por qué según la frecuencia que le ponía funcionaba y con sólo un hercio más dejaba de funcionar?

Total, que el problema es debido a que la salida del bombeo no la hace un registro, sino un circuito lógico. En esta imagen podemos observar el interior de un bombeo de tic en hercios.

interior bombeo hercios.png



Si recordamos "las reglas de diseño síncrono", a la salida de un circuito lógico se ha de registrar esa salida.

Le he colocado un registro para registrar la salida y ha desaparecido el problema. Ahora se puede bombear a cualquier frecuencia y el micro hace lo que espero de él.

registed output.png

Jo mo

unread,
Nov 14, 2023, 7:19:57 AM11/14/23
to fpga-wars-explora...@googlegroups.com
Good catch Democrito!
Thanks for pointing us to that interesting Juan's tutorial
I will try to remember this,to avoid making the same error on my futur synchron circuits!

--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Jesus Arias

unread,
Nov 14, 2023, 12:15:04 PM11/14/23
to FPGAwars: explorando el lado libre
Hola,
También puedes tomar la salida del bit MSB del divisor y te ahorras el flipflop que has puesto en la salida:
assign o = divcounter[N-1];
Aunque ahora el "duty-cycle" va a depender del valor del divisor (antes era sólo un pulso en alto), la frecuencia no se ve afectada.
Y sería buena idea inicializar divcounter con 0, para que si lo fueras a simular no comience con una cuenta de valor desconocido (X)
Los circuitos combinacionales son muy dados a generar transitorios (glitches) Por ello cuando queremos usar una salida combinacional como señal de reloj hay que andarse con pies de plomo.
Saludos.

charli va

unread,
Nov 14, 2023, 12:41:21 PM11/14/23
to fpga-wars-explora...@googlegroups.com
Muy buena Demócrito! siempre con el ojo en el dato ;)

Gracias Jesús por tus comentarios son siempre oro puro.

--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Jesus Arias

unread,
Nov 14, 2023, 3:04:40 PM11/14/23
to FPGAwars: explorando el lado libre
Hola de nuevo.
Tal vez merezca la pena comentar la solución "con pies de plomo", que sería:

assign o = (~reset) | clk;

Aquí la salida va a estar siempre en alto salvo en el ciclo de final de cuenta que está en bajo durante el segundo semiciclo. Siempre que clk sube reset puede tener algún transitorio, pero como clk ya está en alto esos transitorios quedan enmascarados con la OR de clk y ~reset.

Saludos


El martes, 14 de noviembre de 2023 a las 10:20:27 UTC+1, Democrito escribió:

Democrito

unread,
Nov 14, 2023, 3:20:01 PM11/14/23
to FPGAwars: explorando el lado libre
¡Gracias por vuestros comentarios, Joaquim y Carlos! La verdad es que este tipo de fallos es uno de los más complicados de cazar; Juan ya lo comenta en su tutorial para este tema.

Jesús, como ya sabes, gracias por los consejos. Pondré al inicio a cero el registro que hace de contador (esta observación se me había pasado por alto). Lo de tomar el bit más alto se podría hacer, pero necesito un tic de reloj, y para cumplir con la frecuencia que asignamos, lo más sencillo es registrar la salida.

Ahora he visto que has publicado otro post, tiene buena pinta y parece una solución muy sencilla. (assign o = (~reset) | clk;) Lo probaré!

Me he estado peleando muchos días con el futuro micro. Veo que se puede enfocar de muchas formas. De este micro se podría sacar otros, dependiendo de si necesitas o no memoria RAM (en el caso de que no, sólo usando los registros necesarios). Si no se necesita memoria RAM, cada cual le podrá añadir todas las instrucciones que quiera y funcionará a la máxima velocidad (un sólo ciclo de clk por instrucción).

He intentado por todos los medios que todas las instrucciones se puedan ejecutar en un sólo ciclo de reloj, pero en la lectura (me queda por implementar la escritura) de la RAM, necesito dos ciclos de reloj (y he probado el flanco ascendente y descendente del clk de la RAM, pero sin resultado). Es decir, que todas las instrucciones podrán ejecutarse en un único ciclo con excepción de la lectura/escritura de la RAM, que necesitará dos. Aún así, creo que el micro será muy rápido en general.

Un abrazo!

charli va

unread,
Nov 14, 2023, 3:38:26 PM11/14/23
to fpga-wars-explora...@googlegroups.com
Deseando probarlo Demócrito!, plantéate si es posible que realmente el micro sea super modular, que en un momento dado no necesite ni ram (que tire sólo de registros).

En cuanto lo sueltes le echamos entre todos un ojo e intentaremos aportar lo mejor que senos ocurra.

Al final lo importante es no perder el foco objetivo de tu micro, si es hacer un micro pequeño para poder hacer micro taréas con un bajo coste de luts (como un controlador spi o similar) pues no intentar hacer un micro mega genérico que pueda hacer de todo que para eso mejor tirar a una arquitecturas riscv para poder utilizar todas las toolchains y herramientas del ecosistema.

Así que lo dicho mucho ánimo en este último tirón!


--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Democrito

unread,
Nov 14, 2023, 3:52:00 PM11/14/23
to FPGAwars: explorando el lado libre
@Jesús, he probado tu solución: 

assign o = (~reset) | clk;

Y lo he puesto a prueba con frecuencias conocidas que me daba error anteriormente y de momento ha funcionado perfectamente. Así que ahora se sustituye un flip-flop D, por una simple puerta Or.

Muchas gracias!

@Carlos, y así lo haré, habrá dos versiones y esa será una (sin RAM será mucho más sencilla de entender para quien quiera modificar/añadir el micro, que es mi objetivo principal). La implementación de una RAM, más que nada es por ponerme retos y coger experiencia.

Gracias a los dos por orientarme!

Jesus Arias

unread,
Nov 14, 2023, 5:17:54 PM11/14/23
to FPGAwars: explorando el lado libre
Hola,


>"he probado el flanco ascendente y descendente del clk de la RAM, pero sin resultado"

Supongo que estarás usando una versión reciente de "nextpnr", porque yo también me encontré con problemas por usar una vieja cuando la BRAM tenía un flanco de reloj diferente en lectura que en escritura (algo de lo que es perfectamente capaz)

De todos modos lo que sospecho es que la ROM se lee en los flancos de bajada, con lo que no tenemos nada de tiempo para presentar una dirección en la BRAM antes de leerla.

Para ejecutar una instrucción que lea y escriba la BRAM en un único ciclo la ROM se tendría que leer en los flancos de subida (aunque eso nos va a afectar a las instrucciones de salto) Adjunto un diagrama de tiempos.


Por descontado, si en lugar de BRAM ponemos un banco con unos pocos registros esto ya no pasa porque la lectura de los registros sería asíncrona. Lo malo es que estos registros van a costar bastantes celdas lógicas (unas 2 LC por cada bit, me temo)
CamScanner 11-14-2023 22.42.23_1.jpg

Democrito

unread,
Nov 14, 2023, 6:25:40 PM11/14/23
to FPGAwars: explorando el lado libre
Estoy utilizando la última versión, en mi caso la toolchain/Apio 0.9.0. No estoy seguro de lo que voy a decir, pero creo que Apio y toolchain son la misma versión; es decir que si se actualiza las Toolchain, cuando se actualiza Apio adquiere la misma versión. Esto es una creencia o dicho de otro modo hablo desde la ignorancia en estos temas.

Me sale esto desde Icestudio:

apio vs toolchain version.png

Comentas:

<<
De todos modos lo que sospecho es que la ROM se lee en los flancos de bajada, con lo que no tenemos nada de tiempo para presentar una dirección en la BRAM antes de leerla.

Para ejecutar una instrucción que lea y escriba la BRAM en un único ciclo la ROM se tendría que leer en los flancos de subida (aunque eso nos va a afectar a las instrucciones de salto) Adjunto un diagrama de tiempos.
>>

Seguramente sea por eso. De multitud de pruebas que he hecho, por intuición también llegué a pensar en eso mismo. O eso, o que las BRAM sean un poco más lentas, pero has comentado << (...) algo de lo que es perfectamente capaz (...) >>, entonces he descartado esto último.

Y efectivamente, si la ROM y el micro actúan en el mismo ciclo, el problema se traslada a los salto. Los saltos necesitarían dos ciclos... Lo recuerdo cuando lo probé y lo comentamos en un post de arriba.

De momento me voy a quedar a que "pague el pato" la memoria BRAM, necesitando dos ciclos. A priori no creo que llegue a usar una BRAM con este tipo de micro, pero trato de aprender situaciones en las que nos podemos encontrar y ver qué solución se le puede dar (en este caso, usando dos ciclos).

En general esto lo veo como (es un metáfora) si tratásemos de colar un puesto un bit dentro de un registro de desplazamiento. Según esta lógica, para lograrlo sólo se podría hacer consumiendo un ciclo de más.

Hace un rato he probado a escribir en la memoria BRAM, y ha funcionado perfectamente, eso sí, utilizando dos ciclos. De todas formas haré más pruebas por si "salta la rana".

Si todo va bien y no me salen situaciones personales, creo que para el lunes de madrugada podría sacar las dos versiones del micro, y a partir de ahí ver cómo optimizarlo y los enfoques que se le pueda dar.

Siempre muy agradecido por quien se implica y aporta ideas y soluciones a este proyecto.

charli va

unread,
Nov 14, 2023, 10:04:40 PM11/14/23
to fpga-wars-explora...@googlegroups.com
Buenas! intentaré investigar por mi lado entorno a los flancos en las bram y yosys y si encuentro algo esclarecedor os lo cuento.

Sobre las versiones de yosys y icestudio os lo cuento por clarificar. La pila tecnológica actual es así:

Icestudio wip
Apio 0.9.0
icestudio-Toolchain. (0.0.9)
System-tools + oss-cad-suite (yosys es sólo una parte de la suite,pero llamémosle yosys a partir de hora pra simplificar)


System tools es un paquete  que tiene varios ejecutables (grabar a flash para alguna tarjeta y utilidades variadas antiguas, esto va a desaparecer en breve).
Lo que llamamos  "toolchain" es una selección de las herramientas de la ossc cad suite de yosys, básicamente es un paquete para minimizar la descarga de toda la oss-cad-suite

Las versiones de apio y la toolchain no tienen por qué coincidir, como icestudio utiliza apio como interfaz para Yosys, siempre hablamos de "toolchain" pero realmente nos referimos a la versión de Apio.

De echo la Toochain como tal en breve será uan versión más, ahora mismo Yosys está en modo similar a "rolling release",básicamente no hay releases, hay una versión nueva cada día, compilan por la noche y al día siguiente hay nueva versión , incluso hay días que ha habido muchas cosas rotas.

Mi idea es automatizar nuestra toolchain para una vez al mes actualicemos la última versión más estable de Yosys, para ir actualizados en cuanto a mejoras en los motores de síntesis y características pero no sufrir posibles roturas importantes, ahora estamos usando una de las releases de finales de Septiembre.

Un abrazo!

--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Democrito

unread,
Nov 15, 2023, 2:17:15 AM11/15/23
to FPGAwars: explorando el lado libre
Gracias por aclararlo Carlos. Para mí ese mundo es completamente opaco, pero he comprendido mejor la dinámica de las versiones.

Un abrazo!
Message has been deleted

Democrito

unread,
Nov 15, 2023, 12:55:51 PM11/15/23
to FPGAwars: explorando el lado libre
@Jesús,

He estado pensando en la solución que me dio, y cuanto más lo pienso, más me vuela la cabeza. Hay una cosa que no comprendo.

not reset or clk is o.png
Interpreto la solución que me dio con el esquema de la imagen. Pero si lo pienso así, por la salida 'o' debería de salir los 12MHz (la señal de reloj). No lo he medido con el osciloscopio todavía, pero me pregunto, cómo es posible que esté funcionando el bombeo de pulsos a los hercios que le ponga, y no dar 12MHz constante por la salida 'o'?

Si no me he explicado bien en mi pregunta, por favor, dímelo y trataré de reformularla.

Saludos!

Jesus Arias

unread,
Nov 15, 2023, 1:21:01 PM11/15/23
to FPGAwars: explorando el lado libre
Hola,
Espero que se vea el funcionamiento en la foto adjuntada.
El reloj de 12MHz solo pasa a la salida cuando ~reset vale 0, y eso solo pasa en el ciclo en el que el contador llegar a su valor máximo.
Por otra parte el flanco de subida del reloj ocurre antes que los glitches en reset, con lo que estos desaparecen de la salida.

Saludos
CamScanner 11-15-2023 19.14.19_1.jpg

Jo mo

unread,
Nov 15, 2023, 1:57:03 PM11/15/23
to FPGAwars: explorando el lado libre
yes, the ~reset stays high during 10ms  (100HZ) and just goes to 0 during one sys clock cycle (when divcounter==M-1).
Also during that clock cycle,  clk re-shapes ~reset ! as showed Jesus with his time diagram.

Nice, simple circuit for removing those crappy glitches. That will even be shitty to see with my slow digital analyzer (100Mhz sampling rate)
@ Jesus: Congratulation
@ Democrito: keep up with our good work on this uC ! Unfortunately i can help you a lot (fault to my lack of knowledge on the subject ;-) )

Democrito

unread,
Nov 15, 2023, 2:19:04 PM11/15/23
to FPGAwars: explorando el lado libre
He estado mirando con el osciloscopio y efectivamente es tal como comentas. Lo que sucede es que sale inversa a como yo esperaba (por eso la duda, esperaba lo contrario), pero esto no es un problema (luego comprendí) porque tanto la RAM, ROM y micro detectan flancos (sea de subida o de bajada)

La salida sale así:

0.jpg

Y para mantener la lógica (un pulso positivo) negué la salida (en vez de una Or, una Nor), y sale lo esperado, aunque dado el diseño (actuar por flancos), no importa.

1.jpg

Este tipo de solución (darse cuenta dónde se produce el glitch y apañarlo con el clk) me lleva a pensar en esta frase que seguramente conocéis: (Yo no soy quién para decirla, pero es que flipé desde ayer con la solución)

"Al león se le conoce por su garras."

Un extracto de dónde procede esta famosa frase dentro de la ciencia:
<<
Al leer el artículo, Johann Bernoulli atribuyó la solución a Isaac Newton, quien no había participado activamente en la competencia por desconocer de la misma. Cuando se le preguntó a Johann como reconoció al autor, éste exclamó: “Ex ungue leonis.” Es decir, al león se le reconoce por la garra.
>>

charli va

unread,
Nov 15, 2023, 2:42:08 PM11/15/23
to fpga-wars-explora...@googlegroups.com
Una solución fantástica, Jesús muchísimas gracias, al programar las fpgas en verilog me doy cuenta que mucha gente piensa en "software" y no se da cuenta que eso acaba en algo "real", "físico" en el que el blanco y el negro primero pasan por el gris.  De nuevo gracias por implicarte.

Demócrito, me encantan tus tests, siempre aprendo un montón y por cierto lo de las citas.... de 10!



--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Jesus Arias

unread,
Nov 15, 2023, 4:58:07 PM11/15/23
to FPGAwars: explorando el lado libre
Gracias a todos por los comentarios, aunque compararme con Newton me parece  un tanto excesivo ;)
En fin, ánimo con el micro, a ver si lo tenemos pronto funcionando.

Democrito

unread,
Dec 11, 2023, 6:46:02 PM12/11/23
to FPGAwars: explorando el lado libre
moretime.jpg

Hola,

Hace mucho tiempo que no publico nada, por eso puse la imagen de un meme muy conocido. Tuve una especie de gripe o virus intestinal (pero sin fiebre) y después de trabajar me pasaba el resto del día durmiendo. Excepto en el tiempo de convalecencia he seguido experimentado con formas de abordar el micro. Y lo que voy a comentar más abajo es por donde voy.

Mientras hacía una prueba tras otra, se me ocurrió una idea. ¿Por qué no hacer que los registros internos, en vez de ser registros individuales, sea una matriz (o array)?

Me explico un poco mejor:

Supongamos que tenemos 4 registros, el A, B, C y D. Son los registros para trabajar, y el registro A suele ser el más utilizado (filosofía Z80, 8086, etc). Si trabajaba de esta manera, para cada registro y acción necesitaba un opcode nuevo. Entonces se me encendió una bombilla dentro de mi cabeza. Si los registros de trabajo (A, B, C y D) fuese un array, dentro del mismo opcode se puede elegir el registro con el que se quiere trabajar.

Por ejemplo, el opcode 03h significa sumar el propio contenido más un número entero (0..FF), por ejemplo A = A + 01;

En mi caso el opcode es un bus de 6 bits, es decir, que se puede declarar hasta 64 opcodes (0..3F). Paralelamente tenemos un bus de 12 bits, donde uso esos bit para especificar la acción a hacer.

cmd_data bus.png

En la imagen "cmd" (command) es el opcode, y "data" puede ser muchas cosas (datos, dirección de memoria, dirección de puerto de salida, selección de registro, etc).

Veamos un ejemplo para asignar un valor constante a un registro:

Uso el opcode 01h (dos nibbles) para esta acción y luego le siguen 3 nibbles más.

asignar constante a resgitros.png

Y traducido a verilog sería esto:

verilog asignar constante a resgitros.png

rg = register.
s = select register.
nn = 0..FF.

Creo que se entiende la filosofía de funcionamiento.

No tengo idea de cómo se desarrollan micros, y supongo que para ahorrar "opcodes" lo deben de hacer de una manera similar. Yo antes, si trabajaba con registros por separado, necesitaba un opcode distinto para referirme a otro registro, y de esta manera, englobo todos los registros dentro de un mismo opcode.

Dejo un ejemplo (adjunto ICE), donde en los leds se verá aparecer el número 5 en binario, y después se incrementa en 1 indefinidamente. El programa en código máquina está a propósito complicado, porque era una forma de comprobar que todas las instrucciones funcionan bien.

En el ejemplo, cargo el registro A, luego transfiero ese valor al registro D, lo muestra por el puerto (el valor del registro D), incrementa en 1 el valor del registro D, y luego hago unos saltos muy raros, pero es para comprobar que el return funciona bien.

Espero que cuando Carlos y Jesús vean el verilog no les duela mucho la vista, ya que he usado mi vieja forma de trabajar. Necesitaba hacerlo así para concentrarme en lo importante. Si esto sale adelante, ese tipo de optimizaciones de código serán aplicadas.

Estoy en fase de pruebas y más pruebas, en realidad todavía no tengo un norte de hacia dónde me dirijo. Cuando algo no me convence necesito comenzar de cero, como en esta ocasión.

Saludos.
test_matrix.ice

Jesus Arias

unread,
Dec 12, 2023, 4:00:26 AM12/12/23
to FPGAwars: explorando el lado libre
Hola, me alegra ver que este proyecto sigue activo y espero que ya estés perfectamente bien.

Una pregunta: ¿Los registros necesitan ser de 9 bits "[8:0]" o es simplemente una errata?

Y una matriz de registros es básicamente la definición de una memoria RAM ;) Sólo que en este caso es de lectura asíncrona, con lo que no se va a poder asignar a una BRAM (por otra parte es de solo 16 bytes, demasiado pequeña para un bloque BRAM de 512 bytes)

Además creo que convendría "comprimir" los opcodes para que quepan en 16bits, pues ese es un tamaño óptimo para los bloques BRAM. (Con 18 bits hay que usar 2 bloques (por cada 256 instrucciones) en lugar de uno solo por tan solo dos bits de más). El definir el repertorio de instrucciones y su codificación es seguramente la parte más difícil de estos diseños.

Otra cosa relacionada con el repertorio de instrucciones sería la definición de una sintaxis concreta para un lenguaje ensamblador, porque el codificar los programas a mano es una pesadilla que convendría evitar. Habría que desarrollar en paralelo un ensamblador para el nuevo micro. (Yo lo hice para el GUS16, y creo que podría adaptar el ensamblador al nuevo micro sin demasiados problemas una vez que tengamos claro el repertorio de instrucciones y la sintaxis)

Respecto de los fuentes en Verilog, yo preferiría una descripción más "circuital", pero esto es más una cuestión de gustos que otra cosa.

Por cierto, he instalado Icestudio (no sin alguna dificultad con la toolchain) para poder ver estos diseños. Pero no he visto la posibilidad de simularlos. ¿Está oculta en algún menú?

Saludos

charli va

unread,
Dec 12, 2023, 4:10:43 AM12/12/23
to fpga-wars-explora...@googlegroups.com
Hola Jesús, Demócrito!  estos días voy a tardar en poder mirar bien las cosas y responder, dadme unos días y estaré a tope. En cuanto lo pueda mirar te doy mi opinión/sugerencias.

Sobre la simulación, actualmente no tenemos simulación, es una funcionalidad que tengo planeado trabajar de cara a inicios de 2024, hasta ahora Icestudio ha sido siempre usado muy "en tiempo real", depurando con el propio circuito ya en la fpga.

Agradecerte Jesús que le hayas echado tiempo y ganas a instalarte Icestudio, para usuarios avanzados como tu, se puede hacer tosco y farragoso pero poco a poco la idea es conseguir que pueda ser una gran ayuda para ver los diseños en forma de circuito y que eso sea útil incluso para usuarios avanzados. Todos los comentarios y sugerencias que tengas no dudes en soltarlas.



--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Democrito

unread,
Dec 12, 2023, 4:27:25 AM12/12/23
to FPGAwars: explorando el lado libre
Jesús:

En teoría todos los registros son de 9 bits porque se podrán operar con ellos para contemplar el carry/signo, es decir, que todos los registros son de trabajo. Ya no hay un sólo registro de trabajo (por ejemplo, el registro A del Z80 es el de trabajo), sino que todos pueden serlo. De la misma manera todos los registros pueden ser de salida (output). Se me olvidó poner el array en el post anterior, que es declarar una memoria RAM en forma de registros (sin usar la BRAM).

reg [8:0]   rg [0:3];  // 7 bytes memory in array register mode.

el "7 bytes" es una errata, porque reduje la cantidad de registros a 4 (rg[0:3])

Entonces nos referimos a los registros como una matriz o array, donde rg[0], rg[1], rg[2] y rg[3], equivale a decir, A, B, C y D. A nivel humano es más sencillo designar a los registros como letras, pero esto es sólo una convención, en realidad es la posición 0, 1, 2 y 3 de la memoria declarada.

Luego continúo, tengo que salir. 

Jesus Arias

unread,
Dec 12, 2023, 5:48:07 AM12/12/23
to FPGAwars: explorando el lado libre
Ah, los registros son de 9 bits a causa del acarreo.
Pero se podría tener un único registro de acarreo común y dejar el resto con 8 bits, que es un tamaño muy recomendable:
reg [7:0]   rg [0:3];
reg acarreo;
...
if (cmd == 'h03) {acarreo,rg[data[11:8]]} <= rg[data[11:8]] + data[7:0]; // rg[s]  = rg[s] + nn;

Democrito

unread,
Dec 12, 2023, 6:07:57 AM12/12/23
to FPGAwars: explorando el lado libre
He continuado leyendo y la mayor parte queda explicada. Entonces queda claro que los registros no se infieren en la BRAM, y como bien dices, queda como una RAM asíncrona, de la cual tomo 4 posiciones (esto es arbitrario) y los trato como registros. Trabajar de esta manera permite que dentro de un opcode (en realidad estoy usando la palabra "opcode" demasiado a la ligera) podamos referirnos a cualquier registro. Pongo otro ejemplo.

De forma convencional, si queremos cargar el valor de un registro a otro registro, teníamos que crear un opcode para cada combinación. Suponiendo que sólo tenemos 4 registros, sería así:

Imaginemos que equivale a LD R1, R2; // R2 =R1.

A = B
A = C
A = D
B = A
B = C
B = D
C = A
C = B
C = D
D = A
D = B
D = C

Tendríamos que definir 12 opcodes diferentes.

Sin embargo, si tratamos los registros como un array, sólo se necesita un opcode principal (6 bits), y luego le sigue 12 bits que define la acción.

En el ejemplo que adjunté, el opcode 02h significa intercambiar el valor de un registro por otro, es decir, un sólo opcode define la acción, y los siguientes nibbles, define qué registro carga el valor en otro registro. Al ser tratado como un array se puede hacer esto sin problemas.

El cargar el valor de un registro en otro registros en código máquina que puse en mi ejemplo es:

interchanger register.png

La línea de verilog que se encarga de esta acción es:

<<
if (cmd == 'h02) rg[data[11:8]] <= rg[data[7:4]];              // rg[s]  = rg[t];
>>

Comentas que puede ser infernal trabajar así, y lo cierto es que es realmente infernal. Necesito mucha concentración para aclararme yo mismo mientras trato de definir instrucciones. Y para crear un simple programa necesito ver el verilog.

Estoy explorando un camino, eso es todo, de momento me atrae mucho esta forma de trabajar porque de la misma manera que es "infernal" por otra parte tiene una forma de solucionar y resumir instrucciones de una forma muy flexible.

No sé dónde terminará todo esto, quizás con el tiempo veo algo que no me gusta y decido cambiar de filosofía, o aplicar ciertas partes que he ido experimentando y aprendiendo por el camino.

Democrito

unread,
Dec 12, 2023, 6:22:31 AM12/12/23
to FPGAwars: explorando el lado libre
Tomo nota de tu comentario sobre el carry, es muy buena idea.

Jesus Arias

unread,
Dec 12, 2023, 9:28:54 AM12/12/23
to FPGAwars: explorando el lado libre
>Comentas que puede ser infernal trabajar así

Me refería al contenido de la ROM de programa, que ahora es sólo una colección de números hexadecimales (código máquina), y que sería deseable generar usando un ensamblador:
        "ADD R3,1"  en lugar de "03301"

charli va

unread,
Dec 12, 2023, 9:46:59 AM12/12/23
to fpga-wars-explora...@googlegroups.com
Yo soy de la misma opinión que Jesús, creo Demócrito que te sería de mucha ayuda si piensas primer bien la sintaxis del ensamblador, te ayudará a ver qué necesidades mínimas de lenguaje necesitas y por ende que recursos físicos (registros, etc). Con eso se podríamontar un compilador para como dice Jesús, no trabajar con nemónicos que al final te limitará mucho las pruebas.

Para la versión de ATTO inicial preparé también un ensamblador que e puede adaptar bastante bien, de echo llevo en la cabeza una idea para crear una especie de metacompilador fácil de modificar para este tipo de micros a medida y que no se necesite montar un tinglado de toolchains ni herramientas léxicas.

Si os motiva podríamos ahora o más adelante abrir un hilo sobre compiladores, yo en este área he trabajado bastante y es un tema que me apasiona, una cosa que voy a tener en breve para icestudio es un sistema de pipeline para integrar compiladores o preprocesadores de roms,memorias, etc de forma que por ejemplo antes de sintetizar, se compile un código fuente, se rellene una memoria en x formato con ese contenido, etc.

Un gran abrazo!


--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Democrito

unread,
Dec 12, 2023, 12:33:16 PM12/12/23
to FPGAwars: explorando el lado libre
Por supuesto Carlos, yo sólo me refería a que en esta fase de experimentación todavía hay que seguir con código máquina.

Si yo tuviera experiencia en ensamblador de cualquier micro ya tendría claro cuáles son las instrucciones más esenciales. Di en el pleistoceno el Z80 y apenas me acuerdo de nada. Cuando Juan sacó el Z80-FPGA hice un par de programas para ese micro, un KITT para los leds de la Alhambra II-FPGA y un PWM para esos mismos leds, y lo hice tirando de lo poco que recordaba y aplicando muchísima intuición.

Para el repertorio de instrucciones en ensamblador (primero en código máquina) me inspiraré en las que usa el Z80. Sólo busco las más esenciales. En esta web, bajando un poco, se puede ver, si no todas, casi todas las que tiene el Z80 (todo está en castellano): https://curso-cm.speccy.org/fr_ind.html (por cierto, la web es realmente buena)

Dadme un tiempo para añadir más instrucciones y conseguir hacer cosas prácticas (un pwm por ejemplo), una vez que tengamos esas instrucciones máquinas y básicas definidas ya podemos pensar en un ensamblador real y físico y abierto a ampliaciones.

Tened un poco de paciencia conmigo, hay cosas en las que os puedo seguir y en otras trato de intuir lo que queréis decir. Sé que voy como un pollo con la cabeza recién cortada, necesito equivocarme y explorar otras posibilidades, eso requiere tiempo.

Otro fuerte abrazo!

Jo mo

unread,
Dec 12, 2023, 1:10:42 PM12/12/23
to FPGAwars: explorando el lado libre
Hola Democrito,

Sorry for that bad virus, it is the season !, I am glad that you are getting rid of it!

For the microprocessors development, i am quite noob so can't really help! 
I can only encourage you to have a look on thinks that have already been done by other brilliant engineers in this last 40 years, on this heavily treated subject!  ;-)
Personally, when i attack a subject i start by :
- defining what i want to do, or what the final "machine" should be able to do! (a minimum of technical specifications).
- check if something was already done to accomplish those tasks. ( and copy-paste it)  :-)
- an finally, if nothing exist or is not exactly what i need, i do it myself !

But i know that you are not has lasy as i am!  And you like to start thinks from scratch (to get a better understanding of the guts of the machine ;-) ) !

So i wish you plenty of courage for this small micro mission, and will try to follow your progresses !

And of course, take your time for doing things !

Big hug my friend!

Jesus Arias

unread,
Dec 12, 2023, 1:26:51 PM12/12/23
to FPGAwars: explorando el lado libre
Hola,
El Z80 no me parece que sea precisamente un buen ejemplo de micro. Su repertorio de instrucciones es muy poco regular, lleno de parches, y de rarezas poco significativas (como su flag de paridad, por ejemplo). Por ello lo considero mucho menos didáctico que por ejemplo el 6502, que tampoco es que sea una maravilla.
Si hablamos de micros de 8 bits actuales creo que los más adecuados serían los PIC de opcodes de 12 o 14 bits, como el PIC10F200 (super básico) o el PIC16F84 o similares, o los AVR como el ATTINY2313, centrándonos en lo que es la CPU, no los periféricos.
O si vamos a cosas más serias tenemos los ARM7, los ARM cortex-M, o los RISC-V. El ARM7 es seguramente el que tiene la arquitectura más elegante, aunque ya está un tanto viejo (adjunto unas transparencias).
ARM2.pdf

Democrito

unread,
Dec 12, 2023, 3:40:05 PM12/12/23
to FPGAwars: explorando el lado libre
Joaquim: Thank you very much for your kind words!

Jesús: El PDF que has adjuntado es realmente bueno. Va al grano y es muy gráfico. Muchas gracias por la documentación y consejos.

Democrito

unread,
Dec 17, 2023, 8:30:24 PM12/17/23
to FPGAwars: explorando el lado libre
Debido a mi mala "praxis" en verilog en el abuso de "if... else.. if..." (en vez de usar "case") creo que he descubierto un pequeño problema en la síntesis de circuitos.

Si enlazo más de 32 if..else..if... el circuito no se sintetiza. No afirmo que suceda realmente, podría ser otra cosa y que no me doy cuenta. Sin embargo, si libero varias instrucciones verilog (borrando varios if... else.. if) que no estoy usando dentro del programa en código máquina, consigue sintetizar el circuito.

El síntoma de no sintetizar un circuito no es dar un error, sino que se queda indefinidamente en el bucle de sintetizar, pero no termina de hacerlo.

Adjunto un micro donde si tratas de subirlo o simplemente sintetizarlo no lo hace, sin embargo si se elimina este grupo de instrucciones, lo hace:

<<
else if (cmd == 'h0A) {carry, rg[data[11:8]]} <= rg[data[11:8]] + rg[data[7:4]]; // rg[s] = rg[s] + rg[t];
else if (cmd == 'h0B) rg[data[11:8]] <= rg[data[11:8]] - rg[data[7:4]]; // rg[s] = rg[s] - rg[t];
else if (cmd == 'h0C) rg[data[11:8]] <= rg[data[11:8]] & rg[data[7:4]]; // rg[s] = rg[s] & rg[t];
else if (cmd == 'h0D) rg[data[11:8]] <= rg[data[11:8]] | rg[data[7:4]]; // rg[s] = rg[s] | rg[t];
else if (cmd == 'h0E) rg[data[11:8]] <= rg[data[11:8]] ^ rg[data[7:4]]; // rg[s] = rg[s] ^ rg[t];
>> 

Las instrucciones entre comillas francesas no son utilizadas dentro del programa, se pueden borrar directamente y no pasa nada.

La sensación que tengo es la que he comentado, parece ser que hay un límite en los "if... else... if".

Por supuesto me voy a poner las pilas y voy a usar el formato y buenas prácticas que me comentó Carlos en un post anterior.

Con este post también quiero decir que no estoy parado, sino que en el desarrollo nos podemos encontrar con problemas que nos puede llevar muchos días sin saber qué es lo que sucede y no sale lo esperado.

Más adelante, si todo va bien os comentaré los pos y contras de usar arrays (vectores) como registros de trabajo. Tiene cosas muy buenas, realmente buenas, pero no es oro todo lo que reluce.

Del ICE que adjunto en teoría no se puede sintetizar, pero si se le elimina algún if... else.. if... lo llega a hacer (se puede eliminar todas las instrucciones que no utiliza el programa en código máquina). Es lo que me ha llevado a pensar que más de 32 ó 33 if...else..if... tiene un número de veces definido para poder sintetizar. Si esto es un error de yosys o nextprn, sería bueno comunicarlo, pero necesito saber si realmente es así antes de crear una issue.
v6.ice

charli va

unread,
Dec 18, 2023, 1:52:39 AM12/18/23
to fpga-wars-explora...@googlegroups.com
Hola Demócrito! si "esperas" lo suficiente puede que acabe pero es más síntoma con un micro relativamente simple tarde tanto, si ocurre es porque le cuesta mucho establecer una ruta válida entre todos tus elementos y al ser una fpga pequeña no encuentra una solución válida o suficientemente válida, quiero mejorar también esta parte para poder cancelar en cualquier momento una síntesis o ver la salida sin tener que cerrar icestudio.

Estoy preparando una pequeña herramienta para que podamos ver como se distribuye dentro de la fpga el circuito, creo que es muy bueno porque te hace ser consciente de la transformación del verilog a circuito real, a como se convierten  las instrucciones, los ifs, etc.

Esta herramienta está ya disponible en la oss cad suite pero no es accesible automáticamente desde icestudio, en nada lo tendré listo para que podamos verlo, con un botón.

Hoy espero recuperar la marcha y me miraré con calma lo que has preparado, como te comentó Jesús, posiblemente hay que intentar darle un giro "un poco más circuital" si esque la palabra existe. El problema del verilog es que tendemos a escribir muy a modo software y parece mentira pero eso se refleja en el sintetizado, cuanto más fácil se lo pongamos a yosys mejor.

Le echo un vistazo lo antes posible y te comento.



--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

charli va

unread,
Dec 18, 2023, 5:39:08 AM12/18/23
to fpga-wars-explora...@googlegroups.com
Buenas Demócrito! he empezado a ver tu uc, lo he sintetizado aunque ha tardado la tira de tiempo pero quería ver si acababa o no, al final ha acabado sintetizándolo , voy a intentar ver qué elementos son los que están perjudicando  el route and place y seguro que. nos ayuda a ver el micro de una forma más combinacional o (circuital).

Sólo por irte comentando, voy a intentar aprovechar esto para ir metiendo mejoras a Icestudio en esta línea (por ejemplo poder cortar el sintetizado), también estoy preparando para probar la nueva toolchain de este mes, así que os avisaré en breve con una nueva  versión para probar.

Estoy trabajando en un autoactualizador, es una taréa que tengo ahí desde hace tiempo y voy avanzándola a tirones pero la veo cada vez más necesaria.

Un abrazo!

charli va

unread,
Dec 18, 2023, 8:12:26 AM12/18/23
to fpga-wars-explora...@googlegroups.com
BUenas de nuevo Demócrito te mando un par de sugerencias, mi idea es ir echándote una mano no para decirte cómo lo haría yo si no para que vayamos aprendiendo todos.

Te cuento he echado un vistazo al código y lo más importante que he visto a cambiar y que es por lo que se engancha en muchas ocasiones el place and route, es el direccionamiento anidado.

Piensa que esto no es software, como decía Jesús cuando se refería a que le gusta una visión más a nivel de circuito, entiendo que son cosas de este tipo.

Al indexar el array de registros con un fragmento de la dirección de memoria, tienes que intentar pensar que eso se traduce en conexiones dentro de la fpa, no es una memoria ram a la que se accede con un puntero de memoria o similar, sino que  en la síntesis eso se transforma en puestas lógicas interconectadas (otra cosa es que usaras una parte de la bram como registtro y fueras indexando ahí los valores).

Así que tienes que imaginarte que en cada if, empiezan a emplazarse puertas y cables interconectando todo con todo...... total que se lo pones complicado a yosys.

Te paso un leve cambio que verás que en unmomento sintetiza, el cambio es crear una asignación contínua del segmento que necesitas de data a un bus y usar ese bus para direccionar.
Captura de pantalla 2023-12-18 a las 12.42.23.png

y luego asignar así:


Captura de pantalla 2023-12-18 a las 12.42.30.png

En software sería equivalente pero en hardware no.

Te paso vídeo de tu v6 en marcha y el ice modificado.

Espero que te ayude, un abrazo!
uc_v6mod.mp4
v6_mod.ice

Democrito

unread,
Dec 18, 2023, 10:03:54 AM12/18/23
to FPGAwars: explorando el lado libre
Muchas gracias Carlos por la sugerencia.

Le acabo de cambiar el nombre "tmpReg" por "nibble2", porque van 3 nibbles (0, 1 y 2) y cuando documente todo esto adquirirá sentido. Según el opcode que se utilice, los datos que "referencio" van en uno de esos nibbles y otras veces en dos a la vez (cuando es de registro a registro, o cuando es un dato bruto, es decir, un byte de dato).

Si todo va bien esta noche me pondré a eliminar la mayor parte de "if.. else..if" aplicando las recomendaciones que me disteis tú y Jesús.

Un abrazo!

charli va

unread,
Dec 18, 2023, 10:08:53 AM12/18/23
to fpga-wars-explora...@googlegroups.com
Genial Demócrito! ahí ya a tu gusto!   como te comentaba lo importante es "pensar en hardware" todo lo posible y mucho cuidado con ese tipo de indexaciones anidadas que en sotfware se resuelven en tiempo de compilación y acaban siendo aritmética de punteros, pero aquí implican cables de un lado para otro :)

Vamos a hacer un micro de PM! estoy deseando empezar a utilizarlo para hacer pequeños controladores.

--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Democrito

unread,
Dec 19, 2023, 2:42:30 PM12/19/23
to FPGAwars: explorando el lado libre
Buenas noches por aquí,

Carlos, cuando tengas tiempo échale un vistazo al ICE que adjunto y me dices si el verilog lo ves óptimo (la estructura). He eliminado todos los if... else...if que he podido.

Tiene unas 33 instrucciones y mide 359 LCs. Me queda por implementar algunas que tengo en la cabeza y las de manejo de memoria RAM.

Características que tiene en esta versión.

- Tiene 8 registros de trabajo de 8 bits cada uno de ellos. Se podrían extender hasta 16 registros.
- Instrucción para cargar un valor numérico (0..255) a cualquiera de los 8 registros.
- Instrucción para cargar el valor de un registro cualquiera (de los 8 que hay) en otro registro.
- Instrucciones para sumar, restar, desplazar y lógicas de un valor numérico (0.255) a un registro.
- Instrucciones para sumar, restar, desplazar y lógicas de un registro con otro registro.
- Instrucciones de comparación de un número (0..255) con un registro (mayor, menor, igual, distinto, carry, zero y no_zero).
- Instrucciones de comparación de un registro con otro registro (mayor, menor, igual, distinto, carry, zero y no_zero).
- Instrucciones de salto directo y condicionales (mencionados en la comparación).
- Instrucciones de puerto de salida y entrada. Tiene 4 para cada uno de ellos y se puede extender hasta 16.

Debido a que se maneja los registros de forma matricial (en array) permite que con una instrucción se pueda operar o comparar con todos los registros de trabajo. Esto reduce mucho la cantidad de opcodes necesarios.

Las comparaciones no la hace de forma tradicional (no se puede por la topología en matriz), entonces, para este caso, se necesita cargar los valores en dos registros comparadores antes de hacer un salto.

De momento esto es todo.

Saludos.

v7.ice

charli va

unread,
Dec 19, 2023, 3:56:16 PM12/19/23
to fpga-wars-explora...@googlegroups.com
Mañana lo miro sin falta Demócrito! de todas formas mi opinión es mi opinión! se libre siempre de hacer las cosas como tu instinto y tu cabeza te digan! ;)

Un gran abrazo y gracias por compartir tu trabajo!

--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Jesus Arias

unread,
Dec 20, 2023, 3:56:58 AM12/20/23
to FPGAwars: explorando el lado libre
Hola Democrito,
Esto ya va estando completo, y veo que has previsto un mecanismo de retorno de subrutinas, de momento con un sólo nivel de anidamiento pero se podría extender sin muchos problemas al nivel que se quiera si el registro "back" fuese una pila de registros, al estilo PIC.

No sé si te has dado cuenta, pero las instrucciones de desplazamiento implican la instanciación de al menos un "barrel-shifter" que físicamente son 3 multiplexores de 16 entradas a 8 salidas conectados en serie. Deberías limitar el numero de bits a desplazar a un máximo de 7, aunque eso seguramente ya lo haga Yosys:

(SHLnn) ? rg[nibble2] << number[2:0]:
(SHRnn) ? rg[nibble2] >> number[2:0]:

Por otra parte el bit MSB de los campos de selección de registro no se usa porque tienes sólo 8 registros. Tal vez puedas reducir esos campos a 3 bits y puede que esto ayude a meter los op-codes en 16 bits en lugar de 18, lo que supondría un mejor uso de la memoria de programa.

Y el acarreo se está escribiendo en casi todas las instrucciones (probablemente con 0) cuando sólo debería hacerlo en las de suma o resta. Esto se podría corregir incluyéndolo en los resultados de las otras instrucciones (ejemplo ANDrg):

(ANDrg) ? {carry, rg[nibble2] &  rg[nibble1]}:

Saludos

Democrito

unread,
Dec 20, 2023, 5:57:35 AM12/20/23
to FPGAwars: explorando el lado libre
Muy buenas Jesús,

En los desplazamientos me daba cuenta que lo máximo sería 8 y el resto sería un desperdicio (o una forma peculiar de poner a cero a un registro, pero aún así sigue sin sentido). Cuando visualizo los buses en mi imaginación veo que si los buses no encajan todos con todos, simplemente se desprecia lo que no se puede conectar. Tomo nota de tu comentario, además de que Carlos siempre me avisa de que hay que ponérselo todo lo fácil que se pueda al sintetizador.

Acabo de hacer el cambio (ahora mismo) y de 359 LCs ha pasado a 346!

Sobre el bus de 18 bits, le di muchas vueltas en su momento para intentar que fuese de 16 bits. El problema está en las instrucciones de salto, que para saltar en una memoria de 4KB se necesita 12 bits.

Ahora mismo los opcodes ocupan 6 bits (porque vi que con 5 bits no me daba el margen que necesitaba). Entonces, 6 bits de opcodes + 12 bits de direccionamiento (o sub-opcodes, dependiendo de la instrucción) = 18 bits en total (bus de datos de la ROM). Creo que se podría hacer de 16 bits si se utilizase dos ciclos de reloj para cargar un registro de salto, primero con 8 bits y luego con otros 8, y tendría capacidad de direccionar hasta 64KB. Pero hacerlo así, pese a que es perfectamente posible, no me seduce ahora mismo. Quiero evitar en lo posible que haya instrucciones que consuman dos ciclos de reloj, aunque va a haber dos instrucciones que van a ser inevitable.

De todas formas voy a mirar de nuevo (esta noche con más tiempo) lo que me has comentado.

Lo del carry también tomo nota.

Como siempre, muchísimas gracias por estar guiando.

charli va

unread,
Dec 20, 2023, 9:53:25 AM12/20/23
to fpga-wars-explora...@googlegroups.com
Buenas Demócrito, como dice Jesús, esto empieza a ser algo prometedor.

Los consejos de Jesús son oro puro, la verdad que en lo que he visto , de primeras no se me ocurre mucho más que decir de momento. Jugaré con ello un poco a ver si se me ocurre alguna mejora, peor como te digo los consejos de Jesús de 10.

Como ha comentado Jesús , contemplaría el tener una pila para retornos anidados, eso es muy interesante.

Por otro lado también refuerzo la idea de que sería muy bueno optimizar a 16 bits, piensa que al sintentizar en 18 bits por palabra de memoria, la síntesis ahí va a perder muchos recursos (una vez más no es software y para darte los 18 se van a desperdiciar recursos físicos).

Con 6 bits tendrías 123 opcodes que es una barbaridad, realmente piensa cuantos opcodes vas a tener y cierra ese número de bits a ver si pudiéramos bajar y dejar todo en los 16.

Otra cosa chula que se podría plantear es no tener dividido en dos memorias código y datos, esto es algo que a mucha gente no lo gusta en arquitecturas de microcontroladores pero  ya como "posibilidad" o que pudieramos comparar posibles alternativas sería un ejercicio muy interesante utilizar la misma memoria para datos y código, esto si no te apetece puedo intentar hacer yo una prueba estos días.

Una cosa que me parece muy interesante de todos estos hilos es que podamos comparar y aprender de diferentes estrategias de hacer las cosas.

Por lo demás lo dicho, enhorabuena y sigue con ello que tiene muy buena pinta!



--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Jesus Arias

unread,
Dec 20, 2023, 10:35:34 AM12/20/23
to FPGAwars: explorando el lado libre
Hola,
Prueba a poner esta línea en una instrucción case, a ver en cuántas celdas se reduce...
 {carry, rg[nibble2]} <= (LDnum) ? number:                                   // rg[s] = nn;
                            (LDreg) ? rg[nibble1]:                              // rg[s] = rg[t];
                            (ADDnn) ? rg[nibble2] +  number:                    // rg[s] = rg[s] +  nn;
                            (SUBnn) ? rg[nibble2] -  number:                    // rg[s] = rg[s] -  nn;
                            (SHLnn) ? rg[nibble2] << number:                    // rg[s] = rg[s] << nn;
                            (SHRnn) ? rg[nibble2] >> number:                    // rg[s] = rg[s] >> nn;
                            (ANDnn) ? rg[nibble2] &  number:                    // rg[s] = rg[s] &  nn;
                            (OR_nn) ? rg[nibble2] |  number:                    // rg[s] = rg[s] |  nn;
                            (XORnn) ? rg[nibble2] ^  number:                    // rg[s] = rg[s] ^  nn;
                            (ADDrg) ? rg[nibble2] +  rg[nibble1]:               // rg[s] = rg[s] +  rg[t];
                            (SUBrg) ? rg[nibble2] -  rg[nibble1]:               // rg[s] = rg[s] -  rg[t];
                            (ANDrg) ? rg[nibble2] &  rg[nibble1]:               // rg[s] = rg[s] &  rg[t];
                            (OR_rg) ? rg[nibble2] |  rg[nibble1]:               // rg[s] = rg[s] |  rg[t];
                            (XORrg) ? rg[nibble2] ^  rg[nibble1]:               // rg[s] = rg[s] ^  rg[t];
                                                     rg[nibble2];

Sería:
 case (cmd) begin
     'h01 : {carry, rg[nibble2]} <= {carry,number};
     'h02 : {carry, rg[nibble2]} <= {carry,rg[nibble1]};
     'h03 : {carry, rg[nibble2]} <= rg[nibble2] +  number;
     'h04 : {carry, rg[nibble2]} <= rg[nibble2] -  number;
     ...
   default: {carry, rg[nibble2]} <= {carry, rg[nibble2]}; // se podría quitar...
 endcase

Observa que aunque se haga lo mismo se tienen circuitos muy distintos pues en el primer caso existe una prioridad (LDnum es la señal de control más prioritaria) y en el segundo caso no. La prioridad no tiene sentido cuando sólo una señal de control está activa, pero eso Yosys a priori no lo sabe. 
Saludos

Jesus Arias

unread,
Dec 20, 2023, 10:38:03 AM12/20/23
to FPGAwars: explorando el lado libre
Perdón, se me ha colado un "begin" de más.
   "case (cmd) begin" debería ser: "case (cmd)"

Jesus Arias

unread,
Dec 20, 2023, 10:52:32 AM12/20/23
to FPGAwars: explorando el lado libre
Y tampoco tenemos que ocuparnos siempre de "carry", sólo en sumas y restas, con lo que quedaría aún más breve (espero que esta vez sea la buena):
 case (cmd)
     'h01 : rg[nibble2]} <= number;
     'h02 : rg[nibble2]} <= rg[nibble1];
     'h03 : {carry, rg[nibble2]} <= rg[nibble2] +  number;
     'h04 : {carry, rg[nibble2]} <= rg[nibble2] -  number;
     'h05 : rg[nibble2]} <= rg[nibble2] << number[2:0];
     'h06 : rg[nibble2]} <= rg[nibble2] >> number[2:0];
     'h07 : rg[nibble2]} <= rg[nibble2] &  number;
     'h08 : rg[nibble2]} <= rg[nibble2] |  number;
     'h09 : rg[nibble2]} <= rg[nibble2] ^  number;
     'h0A : {carry, rg[nibble2]} <= rg[nibble2] + rg[nibble1];
     'h0B : {carry, rg[nibble2]} <= rg[nibble2] - rg[nibble1];
     'h0C : rg[nibble2]} <= rg[nibble2] & rg[nibble1];
     'h0D : rg[nibble2]} <= rg[nibble2] | rg[nibble1];
     'h0E : rg[nibble2]} <= rg[nibble2] ^ rg[nibble1];
 endcase

Jo mo

unread,
Dec 20, 2023, 11:18:33 AM12/20/23
to FPGAwars: explorando el lado libre
Thanks Democrito, for this last version,  and Jesus an charli, for helping with you advices!

Here, i have nothing really interesting to write about the contents of this micro.
i can just comment thaht i tried to compile it for ecp5 and got!
!Captureress.JPG

it is 200 LUTS bigger (than the 359 LC on ICE40)  compilation/synthesis is always big mystery for me :-)
Maybe its just the ecp5 toolchain (yosys+next-pnrecp5), wisf is less efficient than the one for ice40 !

Also about the use "elseif" instead of "case" in our code (eg for opcodes)!
Jesus will correct me if it is wrong, but It seams to me that use of resources is the same when the numbers of possible values is close (but not higher) to the maximum that can be defined by the bit numbers of the condition: 
 eg: if cmd is 4 bits, its optimal to have 16 possible values of cmd! and if we only need 9 possible values( or if we need 17(with 5 bits on cmd of course)), than "elseif" can be more resources efficient.

The other think to take in account is that the use of "case" (even if it is not always as resources efficient as "else-if"), makes much more readable verilog code when the quantity of possible values is big. So,...

charli va

unread,
Dec 20, 2023, 11:38:41 AM12/20/23
to fpga-wars-explora...@googlegroups.com

Acabo de añadir tu código Jesús , los LCs no salen mucho mejor, esto es antes del cambio:

Captura de pantalla 2023-12-20 a las 17.35.09.png

y esto con el nuevo código del case, es curioso que aumentan los LC's y disminuye la velovidad:

Captura de pantalla 2023-12-20 a las 17.13.50.png

Lo que sí que me ha venido a la cabeza fué un gráfico una vez que vi de una síntesis de memoria anidada y de cómo la resolvían sacando a una variable temporal, lo he probado y acojonante, los LCs, salen un poquito peor (nos vamos a 379), pero la velocidad máxima, pasamos de 40-36 a 177Mhz!!!

Captura de pantalla 2023-12-20 a las 17.36.47.png

El código está basado en tu sugerencia Jesús:

 case (cmd)
     'h01 : rg[nibble2] <= number;
     'h02 : rg[nibble2] <= rg[nibble1];
     'h03 : {carry, rg[nibble2]} <= tmp +  number;
     'h04 : {carry, rg[nibble2]} <=tmp -  number;
      'h05 : rg[nibble2] <= tmp << number[2:0];
     'h06 : rg[nibble2] <= tmp >> number[2:0];
     'h07 : rg[nibble2] <= tmp &  number;
     'h08 : rg[nibble2] <= tmp |  number;
     'h09 : rg[nibble2] <= tmp ^  number;
     'h0A : {carry, rg[nibble2]} <= tmp + rg[nibble1];
     'h0B : {carry, rg[nibble2]} <=tmp - rg[nibble1];
     'h0C : rg[nibble2] <= tmp & rg[nibble1];
     'h0D : rg[nibble2] <= tmp | rg[nibble1];
     'h0E : rg[nibble2] <= tmp ^ rg[nibble1];
 endcase
 
Añadiendo fuera del always:

wire [7:0] tmp;
assign tmp =  rg[nibble2];





--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Democrito

unread,
Dec 20, 2023, 1:51:53 PM12/20/23
to FPGAwars: explorando el lado libre
Carlos: Si consigo terminar este micro, tengo pensado hacer otro, pero sería algo muy muy experimental, y en ese experimento sí que utilizaría una sola memoria. Necesito experimentar este modo (ROM y RAM separada) y en verilog porque sabía que podría aprender mucho, y gracias a vosotros es lo que está sucediendo (al menos esa es mi sensación ahora mismo).

En cuanto a la frecuencia máxima, no os fieis ni un pelo. Si haces pequeños cambios, casi insignificantes, se dispara esa información, es un dato muy voluble. Yo uso esa información para saber si voy por buen camino, sin embargo sólo contemplo la frecuencia más baja obtenida, que suele ser de alrededor de 40 MHz. 

Joaquim: Thank you very much for the test on another board other than ICE40. On the other hand, I think that both you and Carlos are synthesizing an old circuit. I attach the latest work carried out so far, and it includes some changes proposed by Jesús.

Jesús: He hecho las dos pruebas, y la segunda es la mejor. Sin embargo, era como inicialmente estaba (con respecto al último ICE que adjunté), porque ya me lo habías propuesto un día o dos antes.

LCs.png

Creo que de momento vamos por buen muy buen camino.

Si el micro no usase memoria RAM (esto es una opción), prácticamente ya está terminado sin esa opción.

Dejo una imagen para que se vea cómo uso los 18 bits del bus de datos de la ROM.

croquis opcodes and data.png
El problema de no poder eliminar dos bits para que queden 16 bits es porque los saltos requieren de 12 bits. El opcode de salto directo y condicionales estarán en la zona roja, y la dirección de memoria a la que ha de saltar estará en la parte verde.

A veces he puesto en el código verilog la dirección de salto como "nnn" (en plan representativo), son tres nibbles, es decir, 12 bits (4bits * 3).

n     = 0..15
nn   = 0..255
nnn =0..4095

[s] y [t], ambos son de 4 bits, por tanto es un sólo nibble cada uno de ellos, por tanto puede indexar registros desde el 0 al 15 (16 en total).

En la imagen se me olvidó incluir la "(n)" del inp y out. Esa "n" es también un nibble, esto significa que tanto las entradas como las salidas las podemos direccionar hasta a 16 puertos. Este nibble de direccionamiento se encuentra en el "Nibble 1". En "Nibble 2" se pone el registro que queramos sacar (si es out) o guardar en un registro (si es inp).

Sé perfectamente que todo esto de buenas a primeras es mucho lío. Yo necesito estar bien concentrado para hacer cualquier cosa.

Creo que no se me olvida nada.

Estoy muy agradecido por la gente que está participando, cada uno con su granito de arena (y algunos con rocas!)

Saludos!

Democrito

unread,
Dec 20, 2023, 1:54:53 PM12/20/23
to FPGAwars: explorando el lado libre
Adjunto la versión de micro más actual.
v7_bis.ice

charli va

unread,
Dec 20, 2023, 2:03:27 PM12/20/23
to fpga-wars-explora...@googlegroups.com
// ENGLISH below 

Buenas Demócrito! gran avance! luego lo miro con calma.

sobre lo de la frecuencia máxima, claro que tienes que fiarte!!!! :). es voluble porque la frecuencia máxima depende del rutado, depende de lo que estés sintetizando si ruta con caminos mas cortos, menos elementos intermedios etc podrá ir más rápido el micro.

Yo creo qeu este sería un valor muy importante a cuidar ya que si conseguimos un micro pequeño pero muy rápido se podrían hacer muchas cosas con el, viendo lo que afectan las indexaciones de memoria en el resultado yo personalmente aunque perdiera algunos LCs, apostaría por ganar velocidad.

De todas formas luego lo miro. Como Joaquim también se une al hilo ¿os parece si intentamos llevarlo en inglés a partir de ahora?

// ENGLISH

Hi Democrito! great advance! Then I look at him calmly.

As for the maximum frequency, of course you have to trust!!!! :). It is fickle because the maximum frequency depends on the route, it depends on what you are synthesizing, if a route with shorter distances, fewer intermediate elements, etc., the bus will be able to go faster.

I think this would be a very important value to take care of since if we get a small but very fast micro we could do many things with it, seeing what memory indexing affects the result, I personally, even if I lost some LC, I would bet on the winning speed .

Anyway, I'll look at it later. Since Joaquim also joins the thread, do you think we'll try to keep it in English from now on?


El mié, 20 dic 2023 a las 19:54, Democrito (<spo...@gmail.com>) escribió:
Adjunto la versión de micro más actual.

--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Democrito

unread,
Dec 20, 2023, 2:07:34 PM12/20/23
to FPGAwars: explorando el lado libre
Ok! tomo nota Carlos. Y ya seguimos en inglés.

Democrito

unread,
Dec 20, 2023, 2:19:08 PM12/20/23
to FPGAwars: explorando el lado libre
Jesús, me acabo de dar cuenta que la segunda prueba que usé no es la que me has propuesto. Voy a ver si consigo adaptarlo porque hay muchos cambios.

Te pongo por aquí el contenido de la caja de código para el micro actual, y es el mismo que la de la imagen que puse, pero aquí lo podrás ver entero.

reg [7:0]   rg [0:7];  // 8 bytes memory in array register mode.
reg [11:0]    pc = 0;  // Program counter (4KB).
reg [11:0]      back;  // Backup PC + 1.
reg [7:0]    out = 0;  // Byte output.
reg [7:0] cmpa, cmpb;
reg          stb = 0;  // Validation bit for outputs.
reg        carry = 0;
//reg       sysclk = 0;
//reg          wrt = 0;

wire [3:0] nibble2  = data[11:8];
wire [3:0] nibble1  = data[7:4];
wire [7:0] number   = data[7:0];

assign sel  = nibble1;
assign val  = stb;
assign addr = pc;
assign dout = out;
//assign mema = data;
//assign memo = out;
//assign wr   = wrt;

wire eq     = cmpa == cmpb;
wire ne     = cmpa != cmpb;
wire gt     = cmpa >  cmpb;
wire lt     = cmpa <  cmpb;
wire za     = cmpa ==    0;
wire zb     = cmpb ==    0;
wire nza    = cmpa !=    0;
wire nzb    = cmpb !=    0;

wire JUMP   = JPequ || JPneq || JPgrt || JPlss || JPzea ||
              JPnza || JPzeb || JPnzb || JPcar || JPnnn;

wire LDnum  = (cmd == 'h01);         // rg[s]  = nn
wire LDreg  = (cmd == 'h02);         // rg[s]  = rg[t]
wire ADDnn  = (cmd == 'h03);         // rg[s]  = rg[s] +  nn;
wire SUBnn  = (cmd == 'h04);         // rg[s]  = rg[s] -  nn;
wire SHLnn  = (cmd == 'h05);         // rg[s]  = rg[s] << nn;
wire SHRnn  = (cmd == 'h06);         // rg[s]  = rg[s] >> nn;
wire ANDnn  = (cmd == 'h07);         // rg[s]  = rg[s] &  nn;
wire OR_nn  = (cmd == 'h08);         // rg[s]  = rg[s] |  nn;
wire XORnn  = (cmd == 'h09);         // rg[s]  = rg[s] ^  nn;
wire ADDrg  = (cmd == 'h0A);         // rg[s]  = rg[s] +  rg[t];
wire SUBrg  = (cmd == 'h0B);         // rg[s]  = rg[s] -  rg[t];
wire ANDrg  = (cmd == 'h0C);         // rg[s]  = rg[s] &  rg[t];
wire OR_rg  = (cmd == 'h0D);         // rg[s]  = rg[s] |  rg[t];
wire XORrg  = (cmd == 'h0E);         // rg[s]  = rg[s] ^  rg[t];
wire JPnnn  = (cmd == 'h0F);         // Direct jump and backup of PC+1
wire JPequ  = (cmd == 'h10 &&    eq);// Jump if cmpa == cmpb
wire JPneq  = (cmd == 'h11 &&    ne);// Jump if cmpa != cmpb
wire JPgrt  = (cmd == 'h12 &&    gt);// Jump if cmpa >  cmpb
wire JPlss  = (cmd == 'h13 &&    lt);// Jump if cmpa <  cmpb
wire JPzea  = (cmd == 'h14 &&    za);// Jump if cmpa == 0
wire JPnza  = (cmd == 'h15 &&   nza);// Jump if cmpa != 0
wire JPcar  = (cmd == 'h16 && carry);// Jump if carry
wire isRET  = (cmd == 'h17);         // Return (pc = backup)
wire OUTrg  = (cmd == 'h18);         // Out(n) = reg[s];
wire INPrg  = (cmd == 'h19);         // reg[s] = Inp(n);
wire cp0nn  = (cmd == 'h1A);         // cmpa   = nn;
wire cp0rg  = (cmd == 'h1B);         // cmpa   = rg[s];
wire cp1nn  = (cmd == 'h1C);         // cmpb   = nn;
wire cp1rg  = (cmd == 'h1D);         // cmpb   = rg[s];
wire JPzeb  = (cmd == 'h1F &&    zb);// Jump if cmpb == 0
wire JPnzb  = (cmd == 'h20 &&   nzb);// Jump if cmpb != 0


/*
always @(posedge clk) begin
  sysclk <= ~sysclk;
end
*/

always @(posedge clk) begin

  if (rst) begin                                                                // If reset.
    pc    <= 0;
  end
  else begin
    pc                   <= (JUMP)  ?  data:                                    // jump to nnn;
                            (isRET) ?  back:  pc + 1;                           // pc = backup;
   
    back                 <= (JUMP)  ?  pc + 1: back;                            // back = pc + 1;

   
    {carry, rg[nibble2]} <= (LDnum) ? number:                                   // rg[s] = nn;
                            (LDreg) ? rg[nibble1]:                              // rg[s] = rg[t];
                            (ADDnn) ? rg[nibble2] +  number:                    // rg[s] = rg[s] +  nn;
                            (SUBnn) ? rg[nibble2] -  number:                    // rg[s] = rg[s] -  nn;
                            (ADDrg) ? rg[nibble2] +  rg[nibble1]:               // rg[s] = rg[s] +  rg[t];
                            (SUBrg) ? rg[nibble2] -  rg[nibble1]:               // rg[s] = rg[s] -  rg[t];
                            (SHLnn) ? rg[nibble2] << number[2:0]:               // rg[s] = rg[s] << nn;
                            (SHRnn) ? rg[nibble2] >> number[2:0]:               // rg[s] = rg[s] >> nn;

                            (ANDnn) ? rg[nibble2] &  number:                    // rg[s] = rg[s] &  nn;
                            (OR_nn) ? rg[nibble2] |  number:                    // rg[s] = rg[s] |  nn;
                            (XORnn) ? rg[nibble2] ^  number:                    // rg[s] = rg[s] ^  nn;
                            (ANDrg) ? rg[nibble2] &  rg[nibble1]:               // rg[s] = rg[s] &  rg[t];
                            (OR_rg) ? rg[nibble2] |  rg[nibble1]:               // rg[s] = rg[s] |  rg[t];
                            (XORrg) ? rg[nibble2] ^  rg[nibble1]:               // rg[s] = rg[s] ^  rg[t];
                                                     rg[nibble2];
   
    cmpa                 <= (cp0nn) ? number:                                   // cmpa = nn;
                            (cp0rg) ? rg[nibble2]: cmpa;                        // cmpa = rg[s];
   
    cmpb                 <= (cp1nn) ? number:                                   // cmpb = nn;
                            (cp1rg) ? rg[nibble2]: cmpb;                        // cmpb = rg[s];
   
    if (OUTrg) begin                                                            // out[t] = rg[s];
                            out <= rg[nibble2];
                            stb <= ~stb;
    end
    else if (INPrg)         rg[nibble2]  <= inp;                                // rg[s]  = inp(t);
   
    //else if (cmd == 'h1E)         rg[nibble2] <= memi;
    //else if (cmd == 'h1F)         wrt         <= ~wrt;
  end
end

charli va

unread,
Dec 20, 2023, 2:57:50 PM12/20/23
to fpga-wars-explora...@googlegroups.com
DEmócrito, si quieres pasa el .ice o incluso pega el .v generado por icestudio que aunque sea un poco "churro" a Jesús le puede valer para generar desde el verilog directamente si quiere.

--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Democrito

unread,
Dec 20, 2023, 3:12:37 PM12/20/23
to FPGAwars: explorando el lado libre
El circuito (ICE) ya lo adjunté [ 19:54 (hace 1 hora) de hoy 20/12/2023 ]. Puse el código en el mismo post por si sólo quería echar un vistazo rápido. Lo cierto es que no quedó nada bien el copy-paste.

Democrito

unread,
Dec 20, 2023, 3:52:24 PM12/20/23
to FPGAwars: explorando el lado libre
Jesús, acabo de implementar el código que me diste para probar a introducir la sentencia "case", pero se queda en un limbo intentando sintetizando. Esto es fácil que suceda. Te adjunto el código como archivo ".txt".
V7_bis_Code_verilog.txt

Democrito

unread,
Dec 20, 2023, 5:01:40 PM12/20/23
to FPGAwars: explorando el lado libre
Jesús, al final he conseguido echarlo a correr, te dejo con la información de salida:

con case.png

Sube un poco los LCs, y baja otro poco la frecuencia máxima.

charli va

unread,
Dec 20, 2023, 5:04:32 PM12/20/23
to fpga-wars-explora...@googlegroups.com
Prueba a cambiar la indexacion por el bus y veras que no se te quedara colgado y mejorara la frecuencia maxima.

--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.

Jesus Arias

unread,
Dec 20, 2023, 5:24:12 PM12/20/23
to FPGAwars: explorando el lado libre
Hola,
He encapsulado tu código en un módulo Verilog y lo tengo corriendo en una de mis placas.
Pero ciertamente le ha hecho pasar un mal rato a "nextpnr". Parece que se ha arreglado al añadir un contador para generar un pulso de reset.
Un aspecto bueno de tenerlo todo en Verilog es que ahora lo puedo simular con "iverilog" y "gtkwave"
Y me salen estos números:
Info: Device utilisation:
Info:            ICESTORM_LC:   353/ 1280    27%
Info:           ICESTORM_RAM:    11/   16    68%


Max frequency for clock            'clk_$glb_clk': 38.07 MHz (PASS at 12.00 MHz)

Jesus Arias

unread,
Dec 20, 2023, 5:31:17 PM12/20/23
to FPGAwars: explorando el lado libre
Perdón de nuevo, piqué en publicar antes de terminar.
Lo curioso es que supuestamente mi FPGA de 1K no tiene suficiente memoria para las 4096 palabras de 18 bits, (son lo justo para 4096 palabras de 16 bits) pero sólo se ocupan 11 BRAMs, y sorprendentemente funciona. Por lo visto yosys analiza los datos y de alguna manera los comprime en las BRAM disponibles ¡Las interioridades de Yosys son un misterio!

Adjunto los archivos
system.v
main.v
Makefile
tb.v
pines_icecream.pcf

Jesus Arias

unread,
Dec 20, 2023, 6:28:28 PM12/20/23
to FPGAwars: explorando el lado libre
Hi,
About the reductions for a 16-bit opcode, they could be:

opcodes_fmt.gif

- 11 bits for jump address. This limits the ROM to 2048 instructions, still a quite big space.
- 5 bits for opcodes. Currently we have 33 instructions, so we need to remove at least one instruction.
- 3 bits for register selection. We have 8 registers, so, 3 bits are enough.

Also, notice that all the register-register instructions could be fit into a single opcode if the bits [4:0], currently unused, are used as extra op-code bits only for these kind of instructions.
Regards

Democrito

unread,
Dec 20, 2023, 7:50:54 PM12/20/23
to FPGAwars: explorando el lado libre
If we settle for 2K, although it will be very narrow for a certain type of program, it is feasible.

If we get a good assembly compiler, we could eliminate some instructions, such as detecting negative numbers and doing the 2's complement directly (there would only be additions). This way we could eliminate two more instructions. On the other hand, if in a future compiler knew how to handle "greater than" and "less than", it would only need one comparison, that is, in the order of the comparison, only one would be needed.

We need to provide space (at most it would be 32 opcodes, by the way, 00h, is not included as an opcode, I started from 01h) to accommodate the RAM instructions. RAM can be very interesting. We will need at least two operation codes: read it and write it, and given the topology of the micro, two clock cycles will be necessary to manage the RAM (I see this as inevitable, which is why the maximum frequency is important, because if necessary, it can be include a PLL).

RAM can be very important if we get a fast microcontroller. For example, perhaps it could handle a controller for a TV monitor. I have no idea about this, but if I imagine an OLED controller, it needs at least 1KB of RAM.

I saw 4KB as something decent. But if we can scratch project realities with only 2KB, I'll get to work. Although I see it as too tight.

Changing the subject, you can get two types of uC. Without RAM and focusing on it (only working with registers, and many drivers can work this way), and with RAM (for possible display drivers, especially).

Despite everything I have said, in reality, and given my knowledge, this seems bigger than I can cover, but with time (and if you give it to me) I think I can get it done. Of course, always with your help, of course.
It is loading more messages.
0 new messages