Finished SSD1351 emulation in FGA.
This commit is contained in:
@@ -98,7 +98,7 @@ end
|
||||
assign TMDS_rgb_p[2] = TMDS_shift_red[0];
|
||||
assign TMDS_rgb_p[1] = TMDS_shift_green[0];
|
||||
assign TMDS_rgb_p[0] = TMDS_shift_blue[0];
|
||||
assign TMDS_clock_p = pixclk;
|
||||
assign TMDS_clock_p = pixclk;
|
||||
|
||||
// Note: what's below would not work, _p and _n sides
|
||||
// require exact synchronization that could not be
|
||||
|
||||
101
Basic/ULX3S_hdmi/HDMI_test_DDR.v
Normal file
101
Basic/ULX3S_hdmi/HDMI_test_DDR.v
Normal file
@@ -0,0 +1,101 @@
|
||||
// Started from: https://www.fpga4fun.com/HDMI.html (c) fpga4fun.com & KNJN LLC 2013
|
||||
// Added comments, adapted to ECP5 / ULX3S, made small changes here and there
|
||||
//
|
||||
// - Introduced some ideas from Lawrie's code here: https://github.com/lawrie/ulx3s_examples/blob/master/hdmi/
|
||||
// See also https://github.com/sylefeb/Silice/tree/master/projects/hdmi_test (also based on Lawrie's code).
|
||||
// - I'm not using Lawrie's "fake differential" but instead I'm using LVCMOS33D mode for the HDMI pins in ulx3S.lpf,
|
||||
// that automatically generates the negative signal from the positive one.
|
||||
// See: https://www.gitmemory.com/issue/YosysHQ/nextpnr/544/751511265
|
||||
// See also LATTICE ECP5 and ECP5-5G sysI/O Usage Guide / Technical note
|
||||
// - In Lawrie's "fake differential", there is the ODRX1F trick that makes it possible to operate at half the
|
||||
// frequency for the bit clock, may be interesting/necessary to use for res higher than 640x480.
|
||||
// - I've seen also some ECP5 primitives: OLVDS (A->Z,ZN) and OBCO (I->OT,OC)
|
||||
// that I tried to use with the standard LVCMOS33 mode, without success.
|
||||
|
||||
module HDMI_test(
|
||||
input pclk,
|
||||
output [3:0] gpdi_dp
|
||||
// Note: gpdi_dn[3:0] is generated automatically by LVCMOS33D mode in ulx3s.lpf
|
||||
);
|
||||
|
||||
HDMI_gen hdmi_gen(
|
||||
.pixclk(pclk),
|
||||
.TMDS_rgb_p(gpdi_dp[2:0]),
|
||||
.TMDS_clock_p(gpdi_dp[3])
|
||||
);
|
||||
|
||||
endmodule
|
||||
|
||||
/*********************************************************************************/
|
||||
module HDMI_gen(
|
||||
input pixclk, // 25MHz
|
||||
output [2:0] TMDS_rgb_p, TMDS_rgb_n, // HDMI pins: RGB
|
||||
output TMDS_clock_p, TMDS_clock_n // HDMI pins: clock
|
||||
);
|
||||
|
||||
/******** Video generation *******************************************************/
|
||||
// This part is just like a VGA generator
|
||||
reg [9:0] CounterX, CounterY;
|
||||
reg hSync, vSync, DrawArea;
|
||||
always @(posedge pixclk) DrawArea <= (CounterX<640) && (CounterY<480);
|
||||
|
||||
always @(posedge pixclk) CounterX <= (CounterX==799) ? 0 : CounterX+1;
|
||||
always @(posedge pixclk) if(CounterX==799) CounterY <= (CounterY==524) ? 0 : CounterY+1;
|
||||
|
||||
always @(posedge pixclk) hSync <= (CounterX>=656) && (CounterX<752);
|
||||
always @(posedge pixclk) vSync <= (CounterY>=490) && (CounterY<492);
|
||||
|
||||
/******** Draw something *********************************************************/
|
||||
// Generate 8-bits red,green,blue signals from X and Y coordinates (the "shader")
|
||||
wire [7:0] W = {8{CounterX[7:0]==CounterY[7:0]}};
|
||||
wire [7:0] A = {8{CounterX[7:5]==3'h2 && CounterY[7:5]==3'h2}};
|
||||
reg [7:0] red, green, blue;
|
||||
|
||||
always @(posedge pixclk) begin
|
||||
red <= ({CounterX[5:0] & {6{CounterY[4:3]==~CounterX[4:3]}}, 2'b00} | W) & ~A;
|
||||
green <= (CounterX[7:0] & {8{CounterY[6]}} | W) & ~A;
|
||||
blue <= CounterY[7:0] | W | A;
|
||||
end
|
||||
|
||||
/******** RGB TMDS encoding ***************************************************/
|
||||
// Generate 10-bits TMDS red,green,blue signals. Blue embeds HSync/VSync in its
|
||||
// control part.
|
||||
wire [9:0] TMDS_red, TMDS_green, TMDS_blue;
|
||||
TMDS_encoder encode_R(.clk(pixclk), .VD(red ), .CD(2'b00) , .VDE(DrawArea), .TMDS(TMDS_red));
|
||||
TMDS_encoder encode_G(.clk(pixclk), .VD(green), .CD(2'b00) , .VDE(DrawArea), .TMDS(TMDS_green));
|
||||
TMDS_encoder encode_B(.clk(pixclk), .VD(blue ), .CD({vSync,hSync}), .VDE(DrawArea), .TMDS(TMDS_blue));
|
||||
|
||||
/******** 125 MHz clock *******************************************************/
|
||||
// This one needs some FPGA-specific specialized blocks (a PLL).
|
||||
wire half_clk_TMDS; // The 125 MHz clock used if using DDR mode.
|
||||
HDMI_clock hdmi_clock(.clk(pixclk), .half_hdmi_clk(half_clk_TMDS));
|
||||
|
||||
/******** Shifter *************************************************************/
|
||||
// Serialize the three 10-bits TMDS red,green,blue signals.
|
||||
// Another version of the shifter, that shifts and sends two bits per clock,
|
||||
// using the ODDRX1F block of the ULX3S.
|
||||
|
||||
// Counter now counts modulo 5 instead of modulo 10
|
||||
reg [4:0] TMDS_mod5=1;
|
||||
wire TMDS_shift_load = TMDS_mod5[4];
|
||||
always @(posedge half_clk_TMDS) TMDS_mod5 <= {TMDS_mod5[3:0],TMDS_mod5[4]};
|
||||
|
||||
// Shifter now shifts two bits at each clock
|
||||
reg [9:0] TMDS_shift_red=0, TMDS_shift_green=0, TMDS_shift_blue=0;
|
||||
always @(posedge half_clk_TMDS) begin
|
||||
TMDS_shift_red <= TMDS_shift_load ? TMDS_red : TMDS_shift_red [9:2];
|
||||
TMDS_shift_green <= TMDS_shift_load ? TMDS_green : TMDS_shift_green[9:2];
|
||||
TMDS_shift_blue <= TMDS_shift_load ? TMDS_blue : TMDS_shift_blue [9:2];
|
||||
end
|
||||
|
||||
// DDR serializers: they send D0 at the rising edge and D1 at the falling edge.
|
||||
ODDRX1F ddr_red (.D0(TMDS_shift_red[0]), .D1(TMDS_shift_red[1]), .Q(TMDS_rgb_p[2]), .SCLK(half_clk_TMDS), .RST(1'b0));
|
||||
ODDRX1F ddr_green(.D0(TMDS_shift_green[0]), .D1(TMDS_shift_green[1]), .Q(TMDS_rgb_p[1]), .SCLK(half_clk_TMDS), .RST(1'b0));
|
||||
ODDRX1F ddr_blue (.D0(TMDS_shift_blue[0]), .D1(TMDS_shift_blue[1]), .Q(TMDS_rgb_p[0]), .SCLK(half_clk_TMDS), .RST(1'b0));
|
||||
|
||||
// The pixel clock, still the same as before.
|
||||
assign TMDS_clock_p = pixclk;
|
||||
|
||||
// Note (again): gpdi_dn[3:0] is generated automatically by LVCMOS33D mode in ulx3s.lpf
|
||||
|
||||
endmodule
|
||||
@@ -1,4 +1,4 @@
|
||||
yosys -p "synth_ecp5 -abc9 -top HDMI_test -json HDMI_test.json" HDMI_test.v HDMI_clock.v TMDS_encoder.v
|
||||
yosys -p "synth_ecp5 -abc9 -top HDMI_test -json HDMI_test.json" HDMI_test_DDR.v HDMI_clock.v TMDS_encoder.v
|
||||
nextpnr-ecp5 --json HDMI_test.json --lpf ulx3s.lpf --textcfg HDMI_test_out.config --85k --freq 25 --package CABGA381
|
||||
ecppack --compress --svf-rowsize 100000 --svf HDMI_test.svf HDMI_test_out.config HDMI_test.bit
|
||||
ujprog HDMI_test.bit
|
||||
|
||||
@@ -182,7 +182,7 @@ int main() {
|
||||
// delay(50); // If GL_clear() is uncommented, uncomment as well
|
||||
// to reduce flickering.
|
||||
}
|
||||
wireframe = !wireframe;
|
||||
wireframe = !wireframe;
|
||||
fclose(F);
|
||||
}
|
||||
}
|
||||
|
||||
19
FemtoRV/FIRMWARE/LIBFEMTORV32/FGA.c
Normal file
19
FemtoRV/FIRMWARE/LIBFEMTORV32/FGA.c
Normal file
@@ -0,0 +1,19 @@
|
||||
#include <femtorv32.h>
|
||||
|
||||
#define SET_MODE 0
|
||||
#define SET_PALETTE_R 1
|
||||
#define SET_PALETTE_G 2
|
||||
#define SET_PALETTE_B 3
|
||||
#define SET_WWINDOW_X 4
|
||||
#define SET_WWINDOW_Y 5
|
||||
|
||||
void FGA_write_window(uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2) {
|
||||
IO_OUT(IO_FGA_CNTL, SET_WWINDOW_X | x1 << 8 | x2 << 20);
|
||||
IO_OUT(IO_FGA_CNTL, SET_WWINDOW_Y | y1 << 8 | y2 << 20);
|
||||
}
|
||||
|
||||
void FGA_write_data_uint8_uint8(uint8_t hi, uint8_t lo) {
|
||||
IO_OUT(IO_FGA_DAT, hi);
|
||||
IO_OUT(IO_FGA_DAT, lo);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#define IO_SDCARD_bit 8
|
||||
#define IO_BUTTONS_bit 9
|
||||
#define IO_FGA_CNTL_bit 10
|
||||
#define IO_FGA_CMD_bit 11
|
||||
#define IO_FGA_DAT_bit 11
|
||||
#define IO_HW_CONFIG_RAM_bit 17
|
||||
#define IO_HW_CONFIG_DEVICES_bit 18
|
||||
#define IO_HW_CONFIG_CPUINFO_bit 19
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
.equ IO_SDCARD_bit, 8
|
||||
.equ IO_BUTTONS_bit, 9
|
||||
.equ IO_FGA_CNTL_bit, 10
|
||||
.equ IO_FGA_CMD_bit, 11
|
||||
.equ IO_FGA_DAT_bit, 11
|
||||
.equ IO_HW_CONFIG_RAM_bit, 17
|
||||
.equ IO_HW_CONFIG_DEVICES_bit, 18
|
||||
.equ IO_HW_CONFIG_CPUINFO_bit, 19
|
||||
@@ -33,7 +33,7 @@
|
||||
.equ IO_SDCARD, 1024
|
||||
.equ IO_BUTTONS, 2048
|
||||
.equ IO_FGA_CNTL, 4096
|
||||
.equ IO_FGA_CMD, 8192
|
||||
.equ IO_FGA_DAT, 8192
|
||||
.equ IO_HW_CONFIG_RAM, 524288
|
||||
.equ IO_HW_CONFIG_DEVICES, 1048576
|
||||
.equ IO_HW_CONFIG_CPUINFO, 2097152
|
||||
|
||||
@@ -13,7 +13,8 @@ OBJECTS=femtorv32.o max7219.o ssd1351.o uart.o \
|
||||
tty_init.o max7219_text.o \
|
||||
wait_cycles.o microwait.o milliwait.o \
|
||||
spi_sd.o cycles.o \
|
||||
filesystem.o exec.o femto_elf.o
|
||||
filesystem.o exec.o femto_elf.o \
|
||||
FGA.o
|
||||
|
||||
all: libfemtorv32.a
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ void GL_fill_poly(int nb_pts, int* points, uint16_t color) {
|
||||
GL_line(x1,y1,x2,y2,color);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
char* x_buffer = ((clockwise > 0) ^ (y2 > y1)) ? x_left : x_right;
|
||||
int dx = x2 - x1;
|
||||
int sx = 1;
|
||||
@@ -189,7 +189,9 @@ void GL_fill_poly(int nb_pts, int* points, uint16_t color) {
|
||||
}
|
||||
|
||||
if(y1 == y2) {
|
||||
continue;
|
||||
x_left[y1] = MIN(x1,x2);
|
||||
x_right[y1] = MAX(x1,x2);
|
||||
continue;
|
||||
}
|
||||
|
||||
ex = (dx << 1) - dy;
|
||||
|
||||
@@ -120,7 +120,8 @@ void GL_putchar_xy(int x, int y, char c);
|
||||
#define IO_SPI_FLASH IO_BIT_TO_OFFSET(IO_SPI_FLASH_bit)
|
||||
#define IO_SDCARD IO_BIT_TO_OFFSET(IO_SDCARD_bit)
|
||||
#define IO_BUTTONS IO_BIT_TO_OFFSET(IO_BUTTONS_bit)
|
||||
#define IO_MAPPED_SPI_FLASH IO_BIT_TO_OFFSET(IO_MAPPED_SPI_FLASH_bit)
|
||||
#define IO_FGA_CNTL IO_BIT_TO_OFFSET(IO_FGA_CNTL_bit)
|
||||
#define IO_FGA_DAT IO_BIT_TO_OFFSET(IO_FGA_DAT_bit)
|
||||
#define IO_HW_CONFIG_RAM IO_BIT_TO_OFFSET(IO_HW_CONFIG_RAM_bit)
|
||||
#define IO_HW_CONFIG_DEVICES IO_BIT_TO_OFFSET(IO_HW_CONFIG_DEVICES_bit)
|
||||
#define IO_HW_CONFIG_CPUINFO IO_BIT_TO_OFFSET(IO_HW_CONFIG_CPUINFO_bit)
|
||||
@@ -141,11 +142,13 @@ extern void oled2(uint32_t cmd, uint32_t arg1, uint32_t arg2);
|
||||
extern void oled3(uint32_t cmd, uint32_t arg1, uint32_t arg2, uint32_t arg3);
|
||||
|
||||
/*
|
||||
* low-level functions to send data to the OLED display
|
||||
* low-level functions to send data to the OLED display and FGA
|
||||
* First, call oled_write_window()
|
||||
* Then send all the pixels using one of the three forms of OLED_WRITE_DATA
|
||||
*/
|
||||
#define OLED_WRITE_DATA_UINT8_UINT8(HI,LO) IO_OUT(IO_SSD1351_DAT,(HI)); IO_OUT(IO_SSD1351_DAT,(LO))
|
||||
|
||||
#define IO_GFX_DAT (IO_SSD1351_DAT | IO_FGA_DAT)
|
||||
#define OLED_WRITE_DATA_UINT8_UINT8(HI,LO) IO_OUT(IO_GFX_DAT,(HI)); IO_OUT(IO_GFX_DAT,(LO));
|
||||
#define OLED_WRITE_DATA_UINT16(RGB) OLED_WRITE_DATA_UINT8_UINT8(RGB>>8, RGB)
|
||||
#define OLED_WRITE_DATA_RGB(R,G,B) OLED_WRITE_DATA_UINT16(GL_RGB(R,G,B))
|
||||
|
||||
@@ -169,6 +172,8 @@ static inline FGA_setpixel(int x, int y, uint8_t R, uint8_t G, uint8_t B) {
|
||||
((uint16_t*)FGA_BASEMEM)[320*y+x] = GL_RGB(R,G,B);
|
||||
}
|
||||
|
||||
void FGA_emul_write_data_uint8_uint8(uint8_t HI, uint8_t LO);
|
||||
|
||||
/* Mapped SPI FLASH */
|
||||
#define SPI_FLASH_BASE ((void*)(1 << 23))
|
||||
|
||||
|
||||
@@ -106,6 +106,14 @@ oled_write_window:
|
||||
call oled2
|
||||
li a0, 0x5c
|
||||
call oled0
|
||||
|
||||
# Set write window in FGA, for SSD1351 emulation.
|
||||
mv a0,t0
|
||||
mv a1,t2
|
||||
mv a2,t1
|
||||
mv a3,t3
|
||||
call FGA_write_window
|
||||
|
||||
lw ra, 0(sp)
|
||||
add sp,sp,4
|
||||
ret
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
FIRMWARE_DIR=/home/blevy/Programming/learn-fpga/FemtoRV/FIRMWARE
|
||||
|
||||
# Configuration for larger boards (ULX3S, ECP5-EVN)
|
||||
#ARCH=rv32im
|
||||
#ABI=ilp32
|
||||
#OPTIMIZE=-O3
|
||||
ARCH=rv32im
|
||||
ABI=ilp32
|
||||
OPTIMIZE=-O3
|
||||
|
||||
# More minimalistic configurations (ICEStick, ICE40)
|
||||
# note: ICE40up5K could support rv32im but this requires some work
|
||||
# since yosys does not (yet) infer DSP blocks for ICE40).
|
||||
ARCH=rv32i
|
||||
ABI=ilp32
|
||||
OPTIMIZE=-Os
|
||||
#ARCH=rv32i
|
||||
#ABI=ilp32
|
||||
#OPTIMIZE=-Os
|
||||
# Optimize for size, on the IceStick we only have 6K of RAM !
|
||||
|
||||
# Normally you do not need to modify anything beyond this line.
|
||||
|
||||
@@ -135,6 +135,17 @@ RISC-V notes:
|
||||
can understand.
|
||||
|
||||
DebugLog:
|
||||
- FGA, emulation du SSD1351
|
||||
a) j'ai oublié de conditionner à io_wstrb
|
||||
b) quand il y a plusieurs <= dans un bloc, j'ai
|
||||
tendance à oublier qu'ils ne sont pas séquentiels entre eux, et à faire
|
||||
par exemple:
|
||||
x1 <= mem_wdata;
|
||||
x <= x1;
|
||||
en voulant dire:
|
||||
x1 <= mem_wdata;
|
||||
x <= mem_wdata;
|
||||
|
||||
- Everything seemed to be broken after I generated all IO
|
||||
register IDs from VERILOG sources, in fact I was
|
||||
reading frequency and "cycles per loop" from
|
||||
|
||||
@@ -18,23 +18,20 @@ module FGA(
|
||||
input wire [31:0] mem_wdata, // data to be written
|
||||
|
||||
input wire pixel_clk, // 25 MHz
|
||||
output wire [3:0] gpdi_dp // HDMI signals, blue, green, red, clock
|
||||
output wire [3:0] gpdi_dp, // HDMI signals, blue, green, red, clock
|
||||
// dgpi_dn generated by pins (see ulx3s.lpf)
|
||||
|
||||
input wire io_wstrb,
|
||||
input wire io_rstrb,
|
||||
input wire sel_cntl, // select control register (R/W)
|
||||
input wire sel_dat, // select data in (W)
|
||||
output wire [31:0] rdata // data read from register
|
||||
);
|
||||
|
||||
wire [14:0] vram_word_address = mem_address[16:2];
|
||||
|
||||
reg [31:0] VRAM[32767:0];
|
||||
|
||||
// write VRAM (interface with processor)
|
||||
always @(posedge clk) begin
|
||||
if(sel) begin
|
||||
if(mem_wmask[0]) VRAM[vram_word_address][ 7:0 ] <= mem_wdata[ 7:0 ];
|
||||
if(mem_wmask[1]) VRAM[vram_word_address][15:8 ] <= mem_wdata[15:8 ];
|
||||
if(mem_wmask[2]) VRAM[vram_word_address][23:16] <= mem_wdata[23:16];
|
||||
if(mem_wmask[3]) VRAM[vram_word_address][31:24] <= mem_wdata[31:24];
|
||||
end
|
||||
end
|
||||
|
||||
/************************* HDMI signal generation ***************************/
|
||||
|
||||
@@ -42,6 +39,13 @@ module FGA(
|
||||
reg [9:0] X, Y; // current pixel coordinates
|
||||
reg hSync, vSync; // horizontal and vertical synchronization
|
||||
reg DrawArea; // asserted if current pixel is in drawing area
|
||||
reg mem_busy; // asserted if memory transfer is running.
|
||||
|
||||
// read control register
|
||||
assign rdata = (io_rstrb && sel_cntl) ?
|
||||
{vSync,hSync,DrawArea,mem_busy,4'b0,2'b0,X,2'b0,Y} :
|
||||
32'b0;
|
||||
|
||||
always @(posedge pixel_clk) begin
|
||||
DrawArea <= (X<640) && (Y<480);
|
||||
X <= (X==799) ? 0 : X+1;
|
||||
@@ -118,5 +122,93 @@ module FGA(
|
||||
assign gpdi_dp[0] = TMDS_shift_B[0];
|
||||
assign gpdi_dp[3] = pixel_clk;
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
// control register - commands
|
||||
|
||||
localparam SET_MODE = 7'd0; // TODO
|
||||
localparam SET_PALETTE_R = 7'd1; // TODO
|
||||
localparam SET_PALETTE_G = 7'd2; // TODO
|
||||
localparam SET_PALETTE_B = 7'd3; // TODO
|
||||
localparam SET_WWINDOW_X = 7'd4;
|
||||
localparam SET_WWINDOW_Y = 7'd5;
|
||||
|
||||
|
||||
// Emulation of SSD1351 OLED display.
|
||||
// - write window command, two commands:
|
||||
// (send 32 bits to IO_FGA_CNTL hardware register)
|
||||
// CMD=4: SET_WWINDOW_X: X2[11:0] X1[11:0] CMD[7:0]
|
||||
// CMD=5: SET_WWINDOW_Y: Y2[11:0] Y1[11:0] CMD[7:0]
|
||||
//
|
||||
// - write data: send 8 bits to IO_FGA_DAT hardware register
|
||||
// MSB first, encoding follows SSD1351: RRRRR GGGGG 0 BBBBB
|
||||
|
||||
reg[9:0] window_x1, window_x2, window_y1, window_y2, window_x, window_y;
|
||||
reg[16:0] window_row_start;
|
||||
reg[16:0] window_byte_address;
|
||||
|
||||
|
||||
always @(posedge clk) begin
|
||||
if(io_wstrb) begin
|
||||
if(sel_cntl) begin
|
||||
case(mem_wdata[7:0])
|
||||
SET_WWINDOW_X: begin // SET_WWINDOW_X command
|
||||
window_x1 <= mem_wdata[19:8];
|
||||
window_x2 <= mem_wdata[31:20];
|
||||
window_x <= mem_wdata[19:8];
|
||||
mem_busy <= 1'b1;
|
||||
end
|
||||
SET_WWINDOW_Y: begin // SET_WWINDOW_Y command
|
||||
window_y1 <= mem_wdata[19:8];
|
||||
window_y2 <= mem_wdata[31:20];
|
||||
window_y <= mem_wdata[19:8];
|
||||
mem_busy <= 1'b1;
|
||||
window_row_start <= (mem_wdata[19:8] * 320 + window_x1) << 1;
|
||||
window_byte_address <= (mem_wdata[19:8] * 320 + window_x1) << 1;
|
||||
end
|
||||
endcase
|
||||
end else if(sel_dat) begin // one byte of data sent to IO_FGA_DAT
|
||||
// increment pixel address.
|
||||
window_byte_address <= window_byte_address + 1;
|
||||
if(window_byte_address[0]) begin
|
||||
if(window_x == window_x2) begin
|
||||
if(window_y == window_y2) begin
|
||||
mem_busy <= 1'b0;
|
||||
end else begin
|
||||
window_y <= window_y+1;
|
||||
window_x <= window_x1;
|
||||
window_byte_address <= window_row_start + 640;
|
||||
window_row_start <= window_row_start + 640;
|
||||
end
|
||||
end else begin
|
||||
window_x <= window_x + 1;
|
||||
end
|
||||
end
|
||||
end // if (sel_dat)
|
||||
end // if (wstrb)
|
||||
end // always @ (posedge clk)
|
||||
|
||||
|
||||
// write VRAM (interface with processor)
|
||||
always @(posedge clk) begin
|
||||
if(io_wstrb) begin
|
||||
if(sel_dat && mem_busy) begin // one byte of data sent to IO_FGA_DAT
|
||||
// write data to VRAM.
|
||||
case(window_byte_address[1:0])
|
||||
// Note: bytes are swapped (following SSD1351 norm)
|
||||
2'b00: VRAM[window_byte_address[16:2]][15:8 ] <= mem_wdata[7:0];
|
||||
2'b01: VRAM[window_byte_address[16:2]][ 0:7 ] <= mem_wdata[7:0];
|
||||
2'b10: VRAM[window_byte_address[16:2]][31:24] <= mem_wdata[7:0];
|
||||
2'b11: VRAM[window_byte_address[16:2]][23:16] <= mem_wdata[7:0];
|
||||
endcase
|
||||
end
|
||||
end else if(sel && !mem_busy) begin
|
||||
if(mem_wmask[0]) VRAM[vram_word_address][ 7:0 ] <= mem_wdata[ 7:0 ];
|
||||
if(mem_wmask[1]) VRAM[vram_word_address][15:8 ] <= mem_wdata[15:8 ];
|
||||
if(mem_wmask[2]) VRAM[vram_word_address][23:16] <= mem_wdata[23:16];
|
||||
if(mem_wmask[3]) VRAM[vram_word_address][31:24] <= mem_wdata[31:24];
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ localparam NRV_DEVICES = 0
|
||||
| (1 << IO_BUTTONS_bit)
|
||||
`endif
|
||||
`ifdef NRV_IO_FGA
|
||||
| (1 << IO_FGA_CNTL_bit) | (1 << IO_FGA_CMD_bit)
|
||||
| (1 << IO_FGA_CNTL_bit) | (1 << IO_FGA_DAT_bit)
|
||||
`endif
|
||||
;
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ localparam IO_SPI_FLASH_bit = 7; // RW write: SPI address (24 bits) rea
|
||||
localparam IO_SDCARD_bit = 8; // RW write: bit 0: mosi bit 1: clk bit 2: csn read: miso
|
||||
localparam IO_BUTTONS_bit = 9; // R buttons state
|
||||
localparam IO_FGA_CNTL_bit = 10; // RW RESERVED
|
||||
localparam IO_FGA_CMD_bit = 11; // W RESERVED
|
||||
localparam IO_FGA_DAT_bit = 11; // W RESERVED
|
||||
|
||||
// The three constant hardware config registers, using the three last bits of IO address space
|
||||
localparam IO_HW_CONFIG_RAM_bit = 17; // R total quantity of RAM, in bytes
|
||||
|
||||
@@ -374,27 +374,27 @@ module FemtoRV32 #(
|
||||
// Does 1-cycle ALU ops, or handles jump/branch, or transitions to waitALU, load, store
|
||||
// If linear execution flow, update instr with lookahead and prepare next lookahead
|
||||
state[EXECUTE_bit]: begin
|
||||
nextInstr <= mem_rdata; // Looked-ahead instr.
|
||||
nextInstr <= mem_rdata; // Looked-ahead instr.
|
||||
addressReg <= aluAplusB[ADDR_WIDTH-1:0]; // Needed for LOAD,STORE,jump,branch
|
||||
PC <= PCplus4;
|
||||
PC <= PCplus4;
|
||||
|
||||
(* parallel_case, full_case *)
|
||||
case (1'b1)
|
||||
error_latched: state <= ERROR;
|
||||
isLoad: state <= LOAD;
|
||||
isStore: begin
|
||||
state <= STORE;
|
||||
isStore: begin
|
||||
state <= STORE;
|
||||
wdataReg <= STORE_data_aligned_for_MEM;
|
||||
end
|
||||
needWaitALU: state <= WAIT_ALU_OR_DATA;
|
||||
jump_or_take_branch: begin
|
||||
PC <= aluAplusB[ADDR_WIDTH-1:0];
|
||||
PC <= aluAplusB[ADDR_WIDTH-1:0];
|
||||
state <= FETCH_INSTR;
|
||||
end
|
||||
default: begin // Linear execution flow, use lookahead, prepare next lookahead
|
||||
instr <= mem_rdata; // Use looked-ahead instr.
|
||||
addressReg <= PCplus8; // Look-ahead: PC+8 (PC not updated yet)
|
||||
state <= FETCH_REGS; // Cool, linear exec flow takes 2 CPIs !
|
||||
instr <= mem_rdata; // Use looked-ahead instr.
|
||||
addressReg <= PCplus8; // Look-ahead: PC+8 (PC not updated yet)
|
||||
state <= FETCH_REGS; // Cool, linear exec flow takes 2 CPIs !
|
||||
end
|
||||
endcase
|
||||
end
|
||||
@@ -422,7 +422,6 @@ module FemtoRV32 #(
|
||||
if(!mem_wbusy)
|
||||
state <= FETCH_REGS;
|
||||
end
|
||||
|
||||
|
||||
// *********************************************************************
|
||||
// Used by LOAD and by multi-cycle ALU instr (shifts and RV32M ops), writeback from ALU or memory
|
||||
|
||||
@@ -241,6 +241,7 @@ module femtosoc(
|
||||
/* verilator lint_on WIDTH */
|
||||
|
||||
`ifdef NRV_IO_FGA
|
||||
wire [31:0] FGA_rdata;
|
||||
FGA graphic_adapter(
|
||||
.clk(clk),
|
||||
.sel(mem_address_is_vram),
|
||||
@@ -248,7 +249,13 @@ module femtosoc(
|
||||
.mem_address(mem_address[16:0]),
|
||||
.mem_wdata(mem_wdata),
|
||||
.pixel_clk(pclk),
|
||||
.gpdi_dp(gpdi_dp)
|
||||
.gpdi_dp(gpdi_dp),
|
||||
|
||||
.io_rstrb(io_rstrb),
|
||||
.io_wstrb(io_wstrb),
|
||||
.sel_cntl(io_word_address[IO_FGA_CNTL_bit]),
|
||||
.sel_dat(io_word_address[IO_FGA_DAT_bit]),
|
||||
.rdata(FGA_rdata)
|
||||
);
|
||||
`endif
|
||||
|
||||
|
||||
@@ -74,14 +74,46 @@ OK, so it is rather clear, to summarize we have:
|
||||
[here](https://github.com/BrunoLevy/learn-fpga/blob/master/Basic/ULX3S_hdmi/ulx3s.lpf).
|
||||
It does exactly what I need:
|
||||
generate the negative pins from the positive ones. When it is
|
||||
active, negative pins should not be driven !
|
||||
active, negative pins should not be driven !
|
||||
|
||||
_How I figured out for `LVCMOS33D` ?_ I was very lucky, I blindly
|
||||
copied `ulx3s.lpf` from Emard's repository [here](https://github.com/emard/ulx3s/blob/master/doc/constraints/ulx3s_v20.lpf)
|
||||
then tryed my naive design with inverters (that would not have worked anyway BTW),
|
||||
but I had an assertion failure in NEXPNR: `Assertion failure: pio == "PIOA"`.
|
||||
Pasting the message in Google redirected me [here](https://github.com/YosysHQ/nextpnr/issues/544),
|
||||
that explains the story (drive only `gpdi_dp` and remove `gpdi_dn`,
|
||||
because `LVCMOS33D` implies a pseudo-differential driver that drives both
|
||||
sides of the pair.
|
||||
|
||||
Note: for higher resolution, it is possible to use a specialized ECP5
|
||||
primitive (`ODDRX1F`) that can shift two bits per clock (then using a
|
||||
|
||||
_Coming next_: for higher resolution, it is possible to use a specialized ECP5
|
||||
primitive (`ODDRX1F`) that can shift and send two bits per clock (then using a
|
||||
125MHz clock instead of 250MHz), see Lawrie's code
|
||||
[here](https://github.com/lawrie/ulx3s_examples/blob/master/hdmi/fake_differential.v).
|
||||
For now I'm not doing that, because it seems that the simpler code
|
||||
suffices.
|
||||
Without it, I think that my design can work up to 800x600 (but this will require
|
||||
changing the frequencies, resolution etc...). For higher resolution,
|
||||
it will require changing the shifter part as follows, as well as
|
||||
changing the clocks and resolutions of course (not tested yet):
|
||||
```
|
||||
// Counter now counts modulo 5 instead of modulo 10
|
||||
reg [4:0] TMDS_mod5=1;
|
||||
wire TMDS_shift_load = TMDS_mod5[4];
|
||||
always @(posedge clk_TMDS) TMDS_mod5 <= {TMDS_mod5[3:0],TMDS_mod5[4]};
|
||||
|
||||
// Shifter now shifts two bits at each clock
|
||||
reg [9:0] TMDS_shift_red=0, TMDS_shift_green=0, TMDS_shift_blue=0;
|
||||
always @(posedge clk_TMDS) begin
|
||||
TMDS_shift_red <= TMDS_shift_load ? TMDS_red : TMDS_shift_red [9:2];
|
||||
TMDS_shift_green <= TMDS_shift_load ? TMDS_green : TMDS_shift_green[9:2];
|
||||
TMDS_shift_blue <= TMDS_shift_load ? TMDS_blue : TMDS_shift_blue [9:2];
|
||||
end
|
||||
|
||||
// DDR serializers: they send D0 at the rising edge and D1 at the falling edge
|
||||
ODDRX1F ddr_red (.D0[TMDS_shift_red[0]), .D1[TMDS_shift_red[1]), .Q(TMDS_rgb_p[2]), .SCLK(), .RST(1'b0));
|
||||
ODDRX1F ddr_green(.D0[TMDS_shift_green[0]), .D1[TMDS_shift_green[1]), .Q(TMDS_rgb_p[1]), .SCLK(), .RST(1'b0));
|
||||
ODDRX1F ddr_blue (.D0[TMDS_shift_blue[0]), .D1[TMDS_shift_blue[1]), .Q(TMDS_rgb_p[0]), .SCLK(), .RST(1'b0));
|
||||
|
||||
```
|
||||
|
||||
[Complete sources for ULX3S](https://github.com/BrunoLevy/learn-fpga/tree/master/Basic/ULX3S_hdmi)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user