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:
parent
42f46c9c95
commit
1f08fe3286
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
include ../../variables.mak
|
||||
include $(SRC_DIR)/modules/rules.mak
|
|
@ -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();
|
||||
}
|
|
@ -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__
|
|
@ -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;
|
||||
}
|
|
@ -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"),
|
||||
|
|
Loading…
Reference in New Issue