@@ -390,6 +390,99 @@ em_vlog(const rt_mod_t *M, const lb_lib_t *lib,
390390 }
391391 fprintf (fp , "\n" );
392392
393+ /* ---- Inferred memories ---- */
394+ for (i = 0 ; i < M -> n_mem ; i ++ ) {
395+ const char * mnam = M -> strs + M -> mems [i ].name_off ;
396+ uint32_t dw = M -> mems [i ].data_w ;
397+ uint32_t dp = M -> mems [i ].depth ;
398+
399+ fprintf (fp , "// Memory: %.*s (%ux%u)\n" ,
400+ (int )M -> mems [i ].name_len , mnam , dp , dw );
401+ fprintf (fp , "reg [%u:0] %.*s [0:%u];\n" ,
402+ dw - 1 , (int )M -> mems [i ].name_len , mnam , dp - 1 );
403+
404+ /* Memory writes are synchronous, so we need a clock.
405+ * Borrow it from the first DFF we find — they all
406+ * share the same clock in a single-clock design. */
407+ {
408+ uint32_t clk_net = 0 ;
409+ uint32_t ci ;
410+ for (ci = 1 ; ci < M -> n_cell ; ci ++ ) {
411+ if ((M -> cells [ci ].type == RT_DFF ||
412+ M -> cells [ci ].type == RT_DFFR ) &&
413+ M -> cells [ci ].n_in >= 2 ) {
414+ clk_net = M -> cells [ci ].ins [1 ];
415+ break ;
416+ }
417+ }
418+
419+ /* Reconstruct the always block from MEMWR cells.
420+ * The third input, if present, is the write-enable. */
421+ {
422+ uint32_t waddr = 0 , wdata = 0 , we_net = 0 ;
423+ uint32_t ci2 ;
424+ for (ci2 = 1 ; ci2 < M -> n_cell ; ci2 ++ ) {
425+ if (M -> cells [ci2 ].type == RT_MEMWR &&
426+ M -> cells [ci2 ].param == (int64_t )i ) {
427+ waddr = M -> cells [ci2 ].ins [0 ];
428+ wdata = M -> cells [ci2 ].ins [1 ];
429+ if (M -> cells [ci2 ].n_in >= 3 )
430+ we_net = M -> cells [ci2 ].ins [2 ];
431+ break ;
432+ }
433+ }
434+
435+ if (waddr > 0 && wdata > 0 ) {
436+ char ab [64 ], db [64 ], cb [64 ];
437+
438+ if (clk_net > 0 )
439+ fprintf (fp , "always @(posedge %s) begin\n" ,
440+ em_cin (M , clk_net , cb , 64 ));
441+ else
442+ fprintf (fp , "always @(*) begin\n" );
443+
444+ if (we_net > 0 ) {
445+ char wb [64 ];
446+ fprintf (fp , " if (%s)\n " ,
447+ em_cin (M , we_net , wb , 64 ));
448+ }
449+
450+ fprintf (fp , " %.*s[%s] <= %s;\n" ,
451+ (int )M -> mems [i ].name_len , mnam ,
452+ em_cin (M , waddr , ab , 64 ),
453+ em_cin (M , wdata , db , 64 ));
454+
455+ fprintf (fp , "end\n" );
456+ }
457+ }
458+
459+ /* Read port is combinational — assign, not always.
460+ * The DFF on the output handles the registration. */
461+ {
462+ uint32_t raddr = 0 , rout = 0 ;
463+ uint32_t ci3 ;
464+ for (ci3 = 1 ; ci3 < M -> n_cell ; ci3 ++ ) {
465+ if (M -> cells [ci3 ].type == RT_MEMRD &&
466+ M -> cells [ci3 ].param == (int64_t )i ) {
467+ raddr = M -> cells [ci3 ].ins [0 ];
468+ rout = M -> cells [ci3 ].out ;
469+ break ;
470+ }
471+ }
472+
473+ if (raddr > 0 && rout > 0 ) {
474+ char ab2 [64 ], ob [64 ];
475+ fprintf (fp , "assign %s = %.*s[%s];\n" ,
476+ em_cnet (M , rout , ob , 64 ),
477+ (int )M -> mems [i ].name_len , mnam ,
478+ em_cin (M , raddr , ab2 , 64 ));
479+ }
480+ }
481+ }
482+
483+ fprintf (fp , "\n" );
484+ }
485+
393486 /* ---- Cell instances ---- */
394487 for (i = 1 ; i < M -> n_cell ; i ++ ) {
395488 const rt_cell_t * c = & M -> cells [i ];
@@ -403,6 +496,9 @@ em_vlog(const rt_mod_t *M, const lb_lib_t *lib,
403496 /* Skip CONST cells — inlined at consumers */
404497 if (ct == RT_CONST ) continue ;
405498
499+ /* Already handled above as reg arrays + always blocks */
500+ if (ct == RT_MEMRD || ct == RT_MEMWR ) continue ;
501+
406502 /* Look up mapped cell */
407503 if (!tbl [ct ].valid ) {
408504 fprintf (fp , "// UNMAPPED: %s w=%u\n" ,
0 commit comments