build/sim/litex_sim: Add initial video module/support in simulation.

Simulation with framebuffer:
litex_sim --with-sdram --with-video-framebuffer

Simulation with video terminal:
litex_sim --with-sdram --with-video-terminal
This commit is contained in:
Victor Suarez Rovere 2023-02-10 18:33:45 +01:00 committed by Florent Kermarrec
parent 42f46c9c95
commit 1f08fe3286
7 changed files with 367 additions and 2 deletions

View File

@ -14,7 +14,7 @@ ifeq ($(UNAME_S),Darwin)
else
CC ?= gcc
CFLAGS += -ggdb
LDFLAGS += -lpthread -Wl,--no-as-needed -ljson-c -lz -lm -lstdc++ -Wl,--no-as-needed -ldl -levent
LDFLAGS += -lpthread -Wl,--no-as-needed -ljson-c -lz -lm -lstdc++ -Wl,--no-as-needed -ldl -levent -lSDL2
endif
CFLAGS += -Wall -$(OPT_LEVEL) $(if $(COVERAGE), -DVM_COVERAGE) $(if $(TRACE_FST), -DTRACE_FST)

View File

@ -1,5 +1,5 @@
include ../variables.mak
MODULES = xgmii_ethernet ethernet serial2console serial2tcp clocker spdeeprom gmii_ethernet
MODULES = xgmii_ethernet ethernet serial2console serial2tcp clocker spdeeprom gmii_ethernet video
.PHONY: $(MODULES) $(EXTRA_MOD_LIST)
all: $(MODULES) $(EXTRA_MOD_LIST)

View File

@ -0,0 +1,2 @@
include ../../variables.mak
include $(SRC_DIR)/modules/rules.mak

View File

@ -0,0 +1,58 @@
// Simple framebuffer windows for visualizations
// Copyright (C) 2022 Victor Suarez Rovere <suarezvictor@gmail.com>
#include <SDL2/SDL.h>
#include "sim_fb.h"
bool fb_init(unsigned width, unsigned height, bool vsync, fb_handle_t *handle)
{
if(SDL_Init(SDL_INIT_VIDEO) < 0)
return false;
handle->win = SDL_CreateWindow("LiteX Sim Video Window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_SHOWN);
if (!handle->win)
return false;
handle->renderer = SDL_CreateRenderer(handle->win, -1, vsync ? SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE | SDL_RENDERER_PRESENTVSYNC : 0);
if (!handle->renderer)
return false;
handle->texture = SDL_CreateTexture(handle->renderer, SDL_PIXELFORMAT_BGRA32, SDL_TEXTUREACCESS_TARGET, width, height);
if (!handle->texture)
return false;
return true;
}
bool fb_should_quit(void)
{
SDL_Event event;
while(SDL_PollEvent(&event))
{
switch(event.type)
{
case SDL_QUIT:
return true;
case SDL_KEYDOWN:
if(event.key.keysym.sym == SDLK_ESCAPE)
return true;
}
}
return false;
}
void fb_update(fb_handle_t *handle, const void *buf, size_t stride_bytes)
{
SDL_UpdateTexture(handle->texture, NULL, buf, stride_bytes);
SDL_RenderCopy(handle->renderer, handle->texture, NULL, NULL);
SDL_RenderPresent(handle->renderer);
}
void fb_deinit(fb_handle_t *handle)
{
SDL_DestroyTexture(handle->texture);
SDL_DestroyRenderer(handle->renderer);
SDL_DestroyWindow(handle->win);
handle->win = NULL;
SDL_Quit();
}

View File

@ -0,0 +1,34 @@
// Copyright (C) 2022 Victor Suarez Rovere <suarezvictor@gmail.com>
#ifndef __SIM_FB_H__
#define __SIM_FB_H__
struct SDL_Window;
struct SDL_Renderer;
struct SDL_Texture;
typedef struct
{
SDL_Window* win;
SDL_Renderer* renderer;
SDL_Texture* texture;
} fb_handle_t;
bool fb_init(unsigned width, unsigned height, bool vsync, fb_handle_t *handle);
void fb_update(fb_handle_t *handle, const void *buf, size_t stride_bytes);
void fb_deinit(fb_handle_t *handle);
bool fb_should_quit(void);
#ifdef __cplusplus
extern "C" {
#endif
uint64_t SDL_GetPerformanceCounter(void);
uint64_t SDL_GetPerformanceFrequency(void);
#ifdef __cplusplus
}
#endif
inline uint64_t higres_ticks() { return SDL_GetPerformanceCounter(); }
inline uint64_t higres_ticks_freq() { return SDL_GetPerformanceFrequency(); }
#endif //__SIM_FB_H__

View File

@ -0,0 +1,205 @@
// Copyright (c) 2023 Victor Suarez Rovere <suarezvictor@gmail.com>
// Copyright (c) LiteX developers
// FIXME: add license
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "error.h"
#include <unistd.h>
#include <event2/listener.h>
#include <event2/util.h>
#include <event2/event.h>
#include <termios.h>
#include "modules.h"
#include "sim_fb.c" //from https://github.com/suarezvictor/CflexHDL/blob/main/src/sim_fb.c
struct session_s {
char *hsync;
char *vsync;
char *de;
char *valid;
char *pix_clk;
uint8_t *r;
uint8_t *g;
uint8_t *b;
short hres, vres;
short x, y;
unsigned frame, stride;
uint8_t *buf, *pbuf;
fb_handle_t fb;
};
static int litex_sim_module_pads_get(struct pad_s *pads, char *name, void **signal)
{
int ret = RC_OK;
void *sig = NULL;
int i;
if(!pads || !name || !signal) {
ret=RC_INVARG;
goto out;
}
i = 0;
while(pads[i].name) {
if(!strcmp(pads[i].name, name)) {
sig = (void*)pads[i].signal;
break;
}
i++;
}
out:
*signal = sig;
return ret;
}
static int videosim_start(void *b)
{
printf("[video] loaded (%p)\n", (struct event_base *)b);
return RC_OK;
}
static int videosim_new(void **sess, char *args)
{
int ret = RC_OK;
struct session_s *s = NULL;
if(!sess) {
ret = RC_INVARG;
goto out;
}
s = (struct session_s*) malloc(sizeof(struct session_s));
if(!s) {
ret=RC_NOENMEM;
goto out;
}
memset(s, 0, sizeof(struct session_s));
out:
*sess = (void*) s;
return ret;
}
static int videosim_add_pads(void *sess, struct pad_list_s *plist)
{
int ret = RC_OK;
struct session_s *s = (struct session_s*) sess;
struct pad_s *pads;
if(!sess || !plist) {
ret = RC_INVARG;
goto out;
}
pads = plist->pads;
if(!strcmp(plist->name, "vga")) {
litex_sim_module_pads_get(pads, "hsync", (void**)&s->hsync);
litex_sim_module_pads_get(pads, "vsync", (void**)&s->vsync);
litex_sim_module_pads_get(pads, "de", (void**)&s->de);
litex_sim_module_pads_get(pads, "valid", (void**)&s->valid);
litex_sim_module_pads_get(pads, "r", (void**)&s->r);
litex_sim_module_pads_get(pads, "g", (void**)&s->g);
litex_sim_module_pads_get(pads, "b", (void**)&s->b);
char *clk_pad = NULL;
litex_sim_module_pads_get(pads, "clk", (void**) &clk_pad);
if(clk_pad != NULL)
s->pix_clk = clk_pad; //overrides sys_clk if previously set
}
if(!strcmp(plist->name, "sys_clk"))
{
if(!s->pix_clk) //not selected if vga clk was already used
litex_sim_module_pads_get(pads, "sys_clk", (void**) &s->pix_clk);
}
out:
return ret;
}
static uint8_t *alloc_buf(unsigned short hres, unsigned short vres)
{
return (uint8_t *) malloc(hres*vres*sizeof(uint32_t));
}
static int videosim_tick(void *sess, uint64_t time_ps) {
static clk_edge_state_t edge;
struct session_s *s = (struct session_s*)sess;
if(!clk_pos_edge(&edge, *s->pix_clk)) {
return RC_OK;
}
if(*s->vsync)
{
if(s->y != 0) //new frame
{
if(!s->vres)
{
s->vres = s->y;
s->buf = alloc_buf(s->hres, s->vres);
//printf("[video] start, resolution %dx%d\n", s->hres, s->vres);
fb_init(s->hres, s->vres, false, &s->fb);
s->stride = s->hres*sizeof(uint32_t);
}
s->y = 0;
s->pbuf = s->buf;
++s->frame;
}
}
if(*s->de)
{
if(s->pbuf)
{
if(*s->valid) //mitigate underflow
{
*s->pbuf++ = *s->r;
*s->pbuf++ = *s->g;
*s->pbuf++ = *s->b;
s->pbuf++;
}
}
if(*s->valid)
s->x = s->x + 1;
}
else if(s->x != 0)
{
if(s->buf) //update each horizontal line
{
if(fb_should_quit())
{
fb_deinit(&s->fb);
exit(1); //FIXME: end gracefully
}
fb_update(&s->fb, s->buf, s->stride);
s->pbuf = s->buf + s->y*s->stride;
}
s->hres = s->x; //this is set many times until settled
if(!s->hres) //avoid initial counting
s->y = 0;
s->y = s->y + 1;
s->x = 0;
}
return RC_OK;
}
static struct ext_module_s ext_mod = {
"video",
videosim_start,
videosim_new,
videosim_add_pads,
NULL,
videosim_tick
};
int litex_sim_ext_module_init(int (*register_module) (struct ext_module_s *))
{
int ret = RC_OK;
ret = register_module(&ext_mod);
return ret;
}

View File

@ -6,6 +6,7 @@
# Copyright (c) 2015-2020 Florent Kermarrec <florent@enjoy-digital.fr>
# Copyright (c) 2020 Antmicro <www.antmicro.com>
# Copyright (c) 2017 Pierre-Olivier Vauboin <po@lambdaconcept>
# Copyright (c) 2023 Victor Suarez Rovere <suarezvictor@gmail.com>
# SPDX-License-Identifier: BSD-2-Clause
import sys
@ -124,6 +125,17 @@ _io = [
Subsignal("oe", Pins(32)),
Subsignal("o", Pins(32)),
Subsignal("i", Pins(32)),
),
# Video
("vga", 0,
Subsignal("hsync", Pins(1)),
Subsignal("vsync", Pins(1)),
Subsignal("de", Pins(1)),
Subsignal("valid", Pins(1)),
Subsignal("r", Pins(8)),
Subsignal("g", Pins(8)),
Subsignal("b", Pins(8)),
)
]
@ -133,6 +145,36 @@ class Platform(SimPlatform):
def __init__(self):
SimPlatform.__init__(self, "SIM", _io)
# Video
from litex.soc.cores.video import video_data_layout, video_timing_layout
class VideoPHYModel(Module, AutoCSR):
def __init__(self, pads, clock_domain="sys"):
self.sink = sink = stream.Endpoint(video_data_layout)
# # #
# Always ack Sink, no backpressure.
self.comb += sink.ready.eq(1)
# Drive Clk.
if hasattr(pads, "clk"):
self.comb += pads.clk.eq(ClockSignal(clock_domain))
# Drive Controls.
self.comb += pads.valid.eq(1) #may be overriden with underflow from the framebuffer
self.comb += pads.de.eq(sink.de)
self.comb += pads.hsync.eq(sink.hsync)
self.comb += pads.vsync.eq(sink.vsync)
# Drive Datas.
cbits = len(pads.r)
cshift = (8 - cbits)
for i in range(cbits):
self.comb += pads.r[i].eq(sink.r[cshift + i] & sink.de)
self.comb += pads.g[i].eq(sink.g[cshift + i] & sink.de)
self.comb += pads.b[i].eq(sink.b[cshift + i] & sink.de)
# Simulation SoC -----------------------------------------------------------------------------------
class SimSoC(SoCCore):
@ -155,6 +197,8 @@ class SimSoC(SoCCore):
with_spi_flash = False,
spi_flash_init = [],
with_gpio = False,
with_video_framebuffer = False,
with_video_terminal = False,
sim_debug = False,
trace_reset_on = False,
**kwargs):
@ -291,6 +335,18 @@ class SimSoC(SoCCore):
self.gpio = GPIOTristate(platform.request("gpio"), with_irq=True)
self.irq.add("gpio", use_loc_if_exists=True)
# Video Framebuffer ------------------------------------------------------------------------
if with_video_framebuffer:
video_pads = platform.request("vga")
self.submodules.videophy = VideoPHYModel(video_pads)
self.add_video_framebuffer(phy=self.videophy, timings="640x480@60Hz", format="rgb888")
self.videophy.comb += video_pads.valid.eq(~self.video_framebuffer.underflow)
# Video Terminal ---------------------------------------------------------------------------
if with_video_terminal:
self.submodules.videophy = VideoPHYModel(platform.request("vga"))
self.add_video_terminal(phy=self.videophy, timings="640x480@60Hz")
# Simulation debugging ----------------------------------------------------------------------
if sim_debug:
platform.add_debug(self, reset=1 if trace_reset_on else 0)
@ -400,6 +456,10 @@ def sim_args(parser):
# Analyzer.
parser.add_argument("--with-analyzer", action="store_true", help="Enable Analyzer support.")
# Video.
parser.add_argument("--with-video-framebuffer", action="store_true", help="Enable Video Framebuffer.")
parser.add_argument("--with-video-terminal", action="store_true", help="Enable Video Terminal.")
# Debug/Waveform.
parser.add_argument("--sim-debug", action="store_true", help="Add simulation debugging modules.")
parser.add_argument("--gtkwave-savefile", action="store_true", help="Generate GTKWave savefile.")
@ -476,6 +536,10 @@ def main():
if args.with_i2c:
sim_config.add_module("spdeeprom", "i2c")
# Video.
if args.with_video_framebuffer or args.with_video_terminal:
sim_config.add_module("video", "vga")
# SoC ------------------------------------------------------------------------------------------
soc = SimSoC(
with_sdram = args.with_sdram,
@ -488,6 +552,8 @@ def main():
with_sdcard = args.with_sdcard,
with_spi_flash = args.with_spi_flash,
with_gpio = args.with_gpio,
with_video_framebuffer = args.with_video_framebuffer,
with_video_terminal = args.with_video_terminal,
sim_debug = args.sim_debug,
trace_reset_on = int(float(args.trace_start)) > 0 or int(float(args.trace_end)) > 0,
spi_flash_init = None if args.spi_flash_init is None else get_mem_data(args.spi_flash_init, endianness="big"),