Ver código fonte

cores/video: Import new core for video generation

This contains timing generator, text mode core, hdmi helpers, ...

Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
Sylvain Munaut 6 anos atrás
pai
commit
16e60e90d9

+ 3 - 0
cores/video/Makefile

@@ -0,0 +1,3 @@
+CORE := video
+
+include ../../build/core-rules.mk

+ 13 - 0
cores/video/core.mk

@@ -0,0 +1,13 @@
+CORE := video
+
+DEPS_video := misc
+
+RTL_SRCS_video := $(addprefix rtl/, \
+	hdmi_phy_2x.v \
+	hdmi_text_2x.v \
+	vid_shared_ram.v \
+	vid_text.v \
+	vid_tgen.v \
+)
+
+include $(ROOT)/build/core-magic.mk

+ 212 - 0
cores/video/doc/text-mode.md

@@ -0,0 +1,212 @@
+iCE40 Text Mode video
+=====================
+
+Overview
+--------
+
+### Main specs
+
+ * 240x64 screen area
+ * 8x16 glyphs
+ * 2 sets of 256 possible glyphs
+ * X/Y flips of glyphs
+ * Multiple drawing / color mapping modes
+
+
+### Block diagram
+
+```
+      ,--------,            ,--------,             ,--------,
+      |        |            |        |             |        |
+X/Y ->| Screen |->[ char ]->| Glyph  |->[ pixel ]->| Color  |->[ output color ]
+      | Memory |   +attr    | Memory |      ,----->| Memory |
+      |        |     \      |        |     /       |        |
+      '--------'      \     '--------'    /        '--------'
+                       \_________________/
+
+```
+
+
+### Implementation notes
+
+ * The implementation actually produces 2 pixels at once to be able to support
+   1080p output on the UP5k
+
+ * The screen memory and glyph memory are implement using 1 SPRAM each.
+   The color memory uses two EBR block
+
+ * Because two pixels are produced at once, we need to do two lookups
+   in color memory, so we need two blocks with the same content to have two
+   read ports
+ 
+ * External read write access to those memory is allowed via a bus interface,
+   the core manages the access sharing between the bus interface and the video
+   lookups.
+
+     * During the active portion of the video area, the Screen memory only
+       requires 1 lookup every 4 cycles and Glyph memory only 1 lookup every
+       2 cycles. So those memory can be accessed without much issues at any
+       time.
+
+     * The color memory however is needed by the video core at every cycle
+       during the active portion of the video and any bus access will stall
+       until either horizontal blanking or vertical blanking.
+
+
+Sreen memory
+------------
+
+### Address mapping :
+
+```
+,---------------------------------------------------------------,
+| f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+|---------------------------------------------------------------|
+| 1 | 0 |           Y           |               X               |
+'---------------------------------------------------------------'
+```
+
+Screen memory is mapped from `0x8000` to `0xbfff` with each location
+representing one character on screen.
+
+
+### Data mapping :
+
+```
+,---------------------------------------------------------------,
+| f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+|---------------------------------------------------------------|
+|         attributes            |          char                 |
+'---------------------------------------------------------------'
+```
+
+Each data word contains the actual character to fetch from the
+glyph memory along with some attributes that will configure how
+to draw it on-screen
+
+
+Glyph memory
+------------
+
+### Address mapping :
+
+```
+,---------------------------------------------------------------,
+| f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+|---------------------------------------------------------------|
+| 1 | 1 | s |              char             |       Y       | X |
+'---------------------------------------------------------------'
+```
+
+Glyph memory is mapped from `0xC000` to `0xFFFF` with each location
+representing the color index of 4 pixels.
+
+ * `s`: Which character set the glyph belongs to
+ * `char`: Character index in that char set
+ * `Y`: line
+ * `X`: msb of the X position. 0=left half, 1=right half
+
+
+### Data mapping :
+
+```
+,---------------------------------------------------------------,
+| f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+|---------------------------------------------------------------|
+|      px0      |      px1      |      px2      |      px3      |
+'---------------------------------------------------------------'
+```
+
+`px0` being the left most pixel and `px3` being the right most one.
+
+The value of these field is what's going to be used, in combination
+with the attributes from the screen memory to perform the final
+color lookup in the color memory.
+
+
+Color Memory
+------------
+
+### Address mapping :
+
+```
+,---------------------------------------------------------------,
+| f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+|---------------------------------------------------------------|
+| 0 | 1 | 1 |       (reserved)      |  palette  |  color index  |
+'---------------------------------------------------------------'
+```
+
+The color memory contains 16 palettes of 16 colors. How the lookup
+is performed depend on which drawing mode is active for the current
+character. Refer to the 'drawing mode` section below.
+
+
+### Data mapping :
+
+The 16 bit word is directly provided to the input. In practice
+when connected to a 3-bit HDMI PMOD, only the 3 LSBs are used.
+
+
+Drawing mode
+------------
+
+### Attribute field format :
+
+```
+,-------------------------------,
+| f | e | d | c | b | a | 9 | 8 |
+|-------------------------------|
+|                       | m | s |
+'-------------------------------'
+```
+
+* `m`: Define the drawing mode for this character (see below)
+* `s`: Define which character set to use
+
+
+### Palette mode
+
+```
+,-------------------------------,
+| f | e | d | c | b | a | 9 | 8 |
+|-------------------------------|
+|    palette    | X | Y | 0 | s |
+'-------------------------------'
+```
+
+In this mode, the `palette` field of the attribute is combined with the `px?`
+value from the glyph to perform the lookup into the color memory and obtain
+the final color.
+
+This mode also allows to perform `X` and `Y` flip of the glyph.
+
+
+### Direct mode
+
+```
+,-------------------------------,
+| f | e | d | c | b | a | 9 | 8 |
+|-------------------------------|
+|    fg     |    bg     | 1 | s |
+'-------------------------------'
+```
+
+In this mode, a foreground and a background color are specified directly
+in the attributes.
+
+
+Assuming `px` is the value from the glyph, color lookup is done like this :
+
+ * `0000`: Perform lookup in palette `0` for color index `bg`
+ * `0001`: Perform lookup in palette `0` for color index `8+fg`
+ * Other value: Perform lookup in palette `1` for color index `px`
+
+This allows to have color `0` and `1` of the glyphs be mapped to 8 possible
+background and 8 possible foreground colors. Those can be freely chosen and
+defined in palette `0`, the first 8 entries being background colors and the
+last 8 entries being foreground colors.
+
+If the glyph uses other colors, then those are looked up directly in palette
+`1`. This way the glyph can also use static colors that don't depend on the
+defined foreground and background color for that character.

+ 139 - 0
cores/video/rtl/hdmi_phy_2x.v

@@ -0,0 +1,139 @@
+/*
+ * hdmi_phy_2x.v
+ *
+ * HDMI PHY using DDR output to push 2 pixels at once allowing FPGA code
+ * to run at half the pixel clock.
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * BSD 3-clause, see LICENSE.bsd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * vim: ts=4 sw=4
+ */
+
+`default_nettype none
+
+module hdmi_phy_2x #(
+	parameter integer DW = 4
+)(
+	// HDMI pads
+	output wire [DW-1:0] hdmi_data,
+	output wire hdmi_hsync,
+	output wire hdmi_vsync,
+	output wire hdmi_de,
+	output wire hdmi_clk,
+
+	// Input from fabric
+	input  wire [DW-1:0] in_data0,
+	input  wire [DW-1:0] in_data1,
+	input  wire in_hsync,
+	input  wire in_vsync,
+	input  wire in_de,
+
+	// Clocks
+	input  wire clk_1x,
+	input  wire clk_2x
+);
+	reg [DW-1:0] in_data1d;
+
+	// Delay second pixel (falling edge one)
+	always @(posedge clk_1x)
+		in_data1d <= in_data1;
+
+	// Data bits
+	genvar i;
+	generate
+		for (i=0; i<DW; i=i+1)
+		begin : bit
+			SB_IO #(
+				.PIN_TYPE(6'b010000),
+				.PULLUP(1'b0),
+				.NEG_TRIGGER(1'b0),
+				.IO_STANDARD("SB_LVCMOS")
+			) iob_hdmi_data_I (
+				.PACKAGE_PIN(hdmi_data[i]),
+				.CLOCK_ENABLE(1'b1),
+				.OUTPUT_CLK(clk_1x),
+				.D_OUT_0(in_data0[i]),
+				.D_OUT_1(in_data1d[i])
+			);
+		end
+	endgenerate
+
+	// H-Sync
+	SB_IO #(
+		.PIN_TYPE(6'b010100),
+		.PULLUP(1'b0),
+		.NEG_TRIGGER(1'b0),
+		.IO_STANDARD("SB_LVCMOS")
+	) iob_hdmi_hsync_I (
+		.PACKAGE_PIN(hdmi_hsync),
+		.CLOCK_ENABLE(1'b1),
+		.OUTPUT_CLK(clk_1x),
+		.D_OUT_0(in_hsync)
+	);
+
+	// V-Sync
+	SB_IO #(
+		.PIN_TYPE(6'b010100),
+		.PULLUP(1'b0),
+		.NEG_TRIGGER(1'b0),
+		.IO_STANDARD("SB_LVCMOS")
+	) iob_hdmi_vsync_I (
+		.PACKAGE_PIN(hdmi_vsync),
+		.CLOCK_ENABLE(1'b1),
+		.OUTPUT_CLK(clk_1x),
+		.D_OUT_0(in_vsync)
+	);
+
+	// DE
+	SB_IO #(
+		.PIN_TYPE(6'b010100),
+		.PULLUP(1'b0),
+		.NEG_TRIGGER(1'b0),
+		.IO_STANDARD("SB_LVCMOS")
+	) iob_hdmi_de_I (
+		.PACKAGE_PIN(hdmi_de),
+		.CLOCK_ENABLE(1'b1),
+		.OUTPUT_CLK(clk_1x),
+		.D_OUT_0(in_de)
+	);
+
+	// Clock
+	SB_IO #(
+		.PIN_TYPE(6'b010000),
+		.PULLUP(1'b0),
+		.NEG_TRIGGER(1'b0),
+		.IO_STANDARD("SB_LVCMOS")
+	) iob_hdmi_clk_I (
+		.PACKAGE_PIN(hdmi_clk),
+		.CLOCK_ENABLE(1'b1),
+		.OUTPUT_CLK(clk_2x),
+		.D_OUT_0(1'b0),
+		.D_OUT_1(1'b1)
+	);
+
+endmodule // hdmi_phy_2x

+ 162 - 0
cores/video/rtl/hdmi_text_2x.v

@@ -0,0 +1,162 @@
+/*
+ * hdmi_text_2x.v
+ *
+ * HDMI text generator core top level running in 1:2 mode
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * BSD 3-clause, see LICENSE.bsd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * vim: ts=4 sw=4
+ */
+
+`default_nettype none
+
+module hdmi_text_2x #(
+	parameter integer DW = 4
+)(
+	// HDMI pads
+	output wire [DW-1:0] hdmi_data,
+	output wire hdmi_hsync,
+	output wire hdmi_vsync,
+	output wire hdmi_de,
+	output wire hdmi_clk,
+
+	// Bus interface
+	input  wire [15:0] bus_addr,
+	input  wire [15:0] bus_din,
+	output wire [15:0] bus_dout,
+	input  wire bus_cyc,
+	input  wire bus_we,
+	output wire bus_ack,
+
+	// Clock / Reset
+	input  wire clk_1x,
+	input  wire clk_2x,
+	input  wire rst
+);
+
+	// Signals
+	// -------
+
+	// Timing generator
+	wire tg_hsync;
+	wire tg_vsync;
+	wire tg_active;
+	wire tg_h_first;
+	wire tg_h_last;
+	wire tg_v_first;
+	wire tg_v_last;
+
+	// Text generator pixels
+	wire [15:0] txt_data0;
+	wire [15:0] txt_data1;
+
+	// Video output
+	wire vo_hsync;
+	wire vo_vsync;
+	wire vo_active;
+	reg  vo_toggle = 1'b0;
+	reg  [ 3:0] vo_data0;
+	reg  [ 3:0] vo_data1;
+
+
+	// Timing generation
+	// -----------------
+
+	vid_tgen tgen_I (
+		.vid_hsync(tg_hsync),
+		.vid_vsync(tg_vsync),
+		.vid_active(tg_active),
+		.vid_h_first(tg_h_first),
+		.vid_h_last(tg_h_last),
+		.vid_v_first(tg_v_first),
+		.vid_v_last(tg_v_last),
+		.clk(clk_1x),
+		.rst(rst)
+	);
+
+
+	// Video text mode
+	// ---------------
+
+	vid_text text_I (
+		.vid_active_0(tg_active),
+		.vid_h_first_0(tg_h_first),
+		.vid_h_last_0(tg_h_last),
+		.vid_v_first_0(tg_v_first),
+		.vid_v_last_0(tg_v_last),
+		.vid_pix0_11(txt_data0),
+		.vid_pix1_11(txt_data1),
+		.bus_addr(bus_addr),
+		.bus_din(bus_din),
+		.bus_dout(bus_dout),
+		.bus_cyc(bus_cyc),
+		.bus_we(bus_we),
+		.bus_ack(bus_ack),
+		.clk(clk_1x),
+		.rst(rst)
+	);
+
+
+	// Video output
+	// ------------
+
+	// Align required sync signals
+	delay_bit #(12) dly_hsync  ( .d(tg_hsync),  .q(vo_hsync),  .clk(clk_1x) );
+	delay_bit #(12) dly_vsync  ( .d(tg_vsync),  .q(vo_vsync),  .clk(clk_1x) );
+	delay_bit #(12) dly_active ( .d(tg_active), .q(vo_active), .clk(clk_1x) );
+
+	// Pixel color map
+	always @(posedge clk_1x)
+	begin
+		vo_toggle <= ~rst & (vo_toggle ^ (tg_v_first & tg_h_first));
+		vo_data0  <= vo_toggle ? txt_data0[7:4] : txt_data0[3:0];
+		vo_data1  <= vo_toggle ? txt_data1[7:4] : txt_data1[3:0];
+	end
+
+
+	// PHY
+	// ---
+
+	hdmi_phy_2x #(
+		.DW(DW)
+	) phy_I (
+		.hdmi_data(hdmi_data),
+		.hdmi_hsync(hdmi_hsync),
+		.hdmi_vsync(hdmi_vsync),
+		.hdmi_de(hdmi_de),
+		.hdmi_clk(hdmi_clk),
+		.in_data0(vo_data0),
+		.in_data1(vo_data1),
+		.in_hsync(vo_hsync),
+		.in_vsync(vo_vsync),
+		.in_de(vo_active),
+		.clk_1x(clk_1x),
+		.clk_2x(clk_2x)
+	);
+
+endmodule // hdmi_text_2x

+ 143 - 0
cores/video/rtl/vid_shared_ram.v

@@ -0,0 +1,143 @@
+/*
+ * vid_shared_ram.v
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * BSD 3-clause, see LICENSE.bsd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * vim: ts=4 sw=4
+ */
+
+`default_nettype none
+
+module vid_shared_ram #(
+	parameter TYPE = "EBR",	// "EBR" / "SPRAM"
+	parameter integer AW = (TYPE == "EBR") ? 8 : 14
+)(
+	// Priority read port
+	input  wire [AW-1:0] p_addr_0,
+	input  wire          p_read_0,
+	input  wire          p_zero_0,
+	output reg  [  15:0] p_dout_3,
+
+	// Aux R/W port
+	input  wire [AW-1:0] s_addr_0,
+	input  wire [  15:0] s_din_0,
+	input  wire          s_read_0,
+	input  wire          s_zero_0,
+	input  wire          s_write_0,
+	output reg  [  15:0] s_dout_3,
+	output wire          s_ready_0,
+
+	// Clock / Reset
+	input  wire clk,
+	input  wire rst
+);
+	// Signals
+	reg  [AW-1:0] addr_1;
+	reg  [15:0] din_1;
+	reg  we_1;
+	reg  p_read_1;
+	reg  p_zero_1;
+	reg  s_read_1;
+	reg  s_zero_1;
+
+	wire [15:0] dout_2;
+	reg  p_read_2;
+	reg  p_zero_2;
+	reg  s_read_2;
+	reg  s_zero_2;
+
+	// "Arbitration"
+	assign s_ready_0 = ~p_read_0;
+
+	// Stage 1 : Address mux and Write delay
+	always @(posedge clk)
+	begin
+		addr_1   <= p_read_0 ? p_addr_0 : s_addr_0;
+		we_1     <= s_write_0 & ~p_read_0;
+		din_1    <= s_din_0;
+		p_read_1 <= p_read_0;
+		p_zero_1 <= p_zero_0;
+		s_read_1 <= s_read_0 & ~p_read_0;
+		s_zero_1 <= s_zero_0 & ~p_read_0;
+	end
+
+	// Stage 2 : Delays
+	always @(posedge clk)
+	begin
+		p_read_2 <= p_read_1 | p_zero_1;
+		p_zero_2 <= p_zero_1;
+		s_read_2 <= s_read_1 | s_zero_1;
+		s_zero_2 <= s_zero_1;
+	end
+
+	// Stage 3 : Output registers
+	always @(posedge clk)
+		if (p_read_2)
+			p_dout_3 <= p_zero_2 ? 16'h0000 : dout_2;
+
+	always @(posedge clk)
+		if (s_read_2)
+			s_dout_3 <= s_zero_2 ? 16'h0000 : dout_2;
+
+	// RAM element
+	generate
+		if (TYPE == "SPRAM")
+			SB_SPRAM256KA spram_I (
+				.DATAIN(din_1),
+				.ADDRESS(addr_1),
+				.MASKWREN(4'hf),
+				.WREN(we_1),
+				.CHIPSELECT(1'b1),
+				.CLOCK(clk),
+				.STANDBY(1'b0),
+				.SLEEP(1'b0),
+				.POWEROFF(1'b1),
+				.DATAOUT(dout_2)
+			);
+
+		else if (TYPE == "EBR")
+			SB_RAM40_4K #(
+				.WRITE_MODE(0),
+				.READ_MODE(0),
+				.INIT_FILE("test.hex")
+			) ebr_I (
+				.RDATA(dout_2),
+				.RADDR({3'b000, addr_1}),
+				.RCLK(clk),
+				.RCLKE(1'b1),
+				.RE(1'b1),
+				.WDATA(din_1),
+				.WADDR({3'b000, addr_1}),
+				.MASK(16'h0000),
+				.WCLK(clk),
+				.WCLKE(we_1),
+				.WE(1'b1)
+			);
+	endgenerate
+
+endmodule // vid_shared_ram

+ 426 - 0
cores/video/rtl/vid_text.v

@@ -0,0 +1,426 @@
+/*
+ * vid_text.v
+ *
+ * Video Text Mode generator
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * BSD 3-clause, see LICENSE.bsd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * vim: ts=4 sw=4
+ */
+
+`default_nettype none
+
+module vid_text (
+	// Timing input
+	input  wire vid_active_0,
+	input  wire vid_h_first_0,
+	input  wire vid_h_last_0,
+	input  wire vid_v_first_0,
+	input  wire vid_v_last_0,
+
+	// Pixel output
+	output wire [15:0] vid_pix0_11,
+	output wire [15:0] vid_pix1_11,
+
+	// Bus interface
+	input  wire [15:0] bus_addr,
+	input  wire [15:0] bus_din,
+	output wire [15:0] bus_dout,
+	input  wire bus_cyc,
+	input  wire bus_we,
+	output wire bus_ack,
+
+	// Clock / Reset
+	input  wire clk,
+	input  wire rst
+);
+	// Signals
+	// -------
+
+	// Char look-up
+	reg  [10:0] cl_y_cnt_0;
+
+	reg  [ 9:0] cl_y_cnt_1;
+	reg  [ 9:0] cl_x_cnt_1;
+	reg  cl_fetch_1;
+	reg  cl_valid_1;
+
+	wire cl_stb_3;
+	wire cl_valid_3;
+
+	wire [15:0] cl_char_4;
+
+	// Glyph look-up
+	reg  [ 1:0] gl_x_4;
+	wire [ 3:0] gl_y_4;
+	reg  gl_fetch_4;
+
+	wire gl_flip_x_4;
+	wire gl_flip_y_4;
+
+	wire gl_flip_x_7;
+	wire [ 3:0] gl_pixa_7;
+	wire [ 3:0] gl_pixb_7;
+
+	reg  [ 3:0] gl_pix0_8;
+	reg  [ 3:0] gl_pix1_8;
+	wire [ 7:0] gl_attrs_8;
+	wire gl_valid_8;
+
+	// RAM fetch interfaces
+	wire [13:0] sm_addr_1;
+	wire [15:0] sm_data_4;
+	wire sm_read_1;
+
+	wire [13:0] gm_addr_4;
+	wire [15:0] gm_data_7;
+	wire gm_read_4;
+
+	wire [ 7:0] cm_addr0_8;
+	wire [ 7:0] cm_addr1_8;
+	wire [15:0] cm_data0_11;
+	wire [15:0] cm_data1_11;
+	wire cm_read_8;
+	wire cm_zero_8;
+
+	// RAM bus interfaces
+	wire smb_ready;
+	reg  smb_read;
+	reg  smb_zero;
+	reg  smb_write;
+
+	wire gmb_ready;
+	reg  gmb_read;
+	reg  gmb_zero;
+	reg  gmb_write;
+
+	wire cmb_ready;
+	reg  cmb_read;
+	reg  cmb_zero;
+	reg  cmb_write;
+
+	wire [15:0] smb_dout;
+	wire [15:0] gmb_dout;
+	wire [15:0] cmb_dout;
+
+	// Bus interface
+	reg  smb_req;
+	wire smb_clear;
+
+	reg  gmb_req;
+	wire gmb_clear;
+
+	reg  cmb_req;
+	wire cmb_clear;
+
+	reg  bus_ack_wait;
+	wire bus_req_ok;
+	reg  [2:0] bus_req_ok_dly;
+
+
+	// Char lookup
+	// -----------
+
+	// Y counter
+	always @(posedge clk)
+		if (vid_v_first_0)
+			// Start at -27 to center the 1024 in the 1080 of full HD
+			cl_y_cnt_0  <= 11'h7e5;
+		else
+			cl_y_cnt_0  <= cl_y_cnt_0 + vid_h_last_0;
+
+	always @(posedge clk)
+		cl_y_cnt_1 <= cl_y_cnt_0[9:0];
+
+	// X counter
+	always @(posedge clk)
+		if (vid_h_first_0)
+			cl_x_cnt_1  <= 0;
+		else
+			cl_x_cnt_1  <= cl_x_cnt_1 + 1;
+
+	// Valid flag
+	always @(posedge clk)
+		cl_valid_1 <= ~cl_y_cnt_0[10] & vid_active_0;
+
+	// Fetch
+	always @(posedge clk)
+		cl_fetch_1 <= ~cl_y_cnt_0[10] & vid_active_0 & (vid_h_first_0 | (cl_x_cnt_1[1:0] == 3'b11));
+
+	// RAM interface
+	assign sm_addr_1 = { cl_y_cnt_1[9:4], cl_x_cnt_1[9:2] };
+	assign sm_read_1 = cl_fetch_1;
+
+	assign cl_char_4 = sm_data_4;
+
+	// Provide some sync signals to the next stage
+	delay_bit #(2) dly_stb13   ( .d(cl_fetch_1), .q(cl_stb_3),   .clk(clk) );
+	delay_bit #(2) dly_valid13 ( .d(cl_valid_1), .q(cl_valid_3), .clk(clk) );
+
+
+	// Glyph lookup
+	// ------------
+
+	// X Counter
+	always @(posedge clk)
+		if (cl_stb_3)
+			gl_x_4 <= 2'b00;
+		else
+			gl_x_4 <= gl_x_4 + 1;
+
+	// Y counter
+	delay_bus #(3, 4) dly_y_cnt ( .d(cl_y_cnt_1[3:0]), .q(gl_y_4), .clk(clk) );
+
+	// Fetch
+	always @(posedge clk)
+		gl_fetch_4 <= cl_stb_3 | gl_x_4[0];
+
+	// X/Y flips attributes
+	assign gl_flip_y_4 = cl_char_4[10] & ~cl_char_4[9];
+	assign gl_flip_x_4 = cl_char_4[11] & ~cl_char_4[9];
+
+	// RAM interface
+	assign gm_addr_4 = {
+		cl_char_4[8:0],
+		gl_y_4    ^ { 4{gl_flip_y_4} },		// Handle Y-flip
+		gl_x_4[1] ^     gl_flip_x_4			// Handle X-flip
+	};
+	assign gm_read_4 = gl_fetch_4;
+
+	// Delay control signal for the mux
+	delay_bit #(3) dly_flip_h ( .d(gl_flip_x_4), .q(gl_flip_x_7), .clk(clk) );
+
+	// Mux
+	assign gl_pixa_7  = (gl_x_4[0] ^ gl_flip_x_7) ? gm_data_7[11: 8] : gm_data_7[3:0];
+	assign gl_pixb_7  = (gl_x_4[0] ^ gl_flip_x_7) ? gm_data_7[15:12] : gm_data_7[7:4];
+
+	always @(posedge clk)
+	begin
+		gl_pix0_8 <= gl_flip_x_7 ? gl_pixa_7 : gl_pixb_7;
+		gl_pix1_8 <= gl_flip_x_7 ? gl_pixb_7 : gl_pixa_7;
+	end
+
+	// Forward the attributes & validity to the next stage
+	delay_bit #(5) dly_valid38  ( .d(cl_valid_3),      .q(gl_valid_8), .clk(clk) );
+	delay_bus #(4, 8) dly_attrs ( .d(cl_char_4[15:8]), .q(gl_attrs_8), .clk(clk) );
+
+
+	// Color lookup
+	// ------------
+
+	// RAM interface
+	vid_color_map cmap0_I (
+		.attrs(gl_attrs_8),
+		.glyph(gl_pix0_8),
+		.color(cm_addr0_8)
+	);
+
+	vid_color_map cmap1_I (
+		.attrs(gl_attrs_8),
+		.glyph(gl_pix1_8),
+		.color(cm_addr1_8)
+	);
+
+	assign cm_read_8  = gl_valid_8;
+	assign cm_zero_8  = ~gl_valid_8;
+
+	assign vid_pix0_11 = cm_data0_11;
+	assign vid_pix1_11 = cm_data1_11;
+
+
+	// Memories
+	// --------
+
+	// Screen memory (contains chars and attributes)
+	vid_shared_ram #(
+		.TYPE("SPRAM")
+	) screen_mem_I (
+		.p_addr_0(sm_addr_1),
+		.p_read_0(sm_read_1),
+		.p_zero_0(1'b0),
+		.p_dout_3(sm_data_4),
+		.s_addr_0(bus_addr[13:0]),
+		.s_din_0(bus_din),
+		.s_read_0(smb_read),
+		.s_zero_0(smb_zero),
+		.s_write_0(smb_write),
+		.s_dout_3(smb_dout),
+		.s_ready_0(smb_ready),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	// Glyph memory (contains bitmap for each character)
+	vid_shared_ram #(
+		.TYPE("SPRAM")
+	) glyph_mem_I (
+		.p_addr_0(gm_addr_4),
+		.p_read_0(gm_read_4),
+		.p_zero_0(1'b0),
+		.p_dout_3(gm_data_7),
+		.s_addr_0(bus_addr[13:0]),
+		.s_din_0(bus_din),
+		.s_read_0(gmb_read),
+		.s_zero_0(gmb_zero),
+		.s_write_0(gmb_write),
+		.s_dout_3(gmb_dout),
+		.s_ready_0(gmb_ready),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	// Palette memory (contain mapping to real color)
+	// (duplicated to allow 2 looks ups in //)
+	vid_shared_ram #(
+		.TYPE("EBR")
+	) color_mem_a_I (
+		.p_addr_0(cm_addr0_8),
+		.p_read_0(cm_read_8),
+		.p_zero_0(cm_zero_8),
+		.p_dout_3(cm_data0_11),
+		.s_addr_0(bus_addr[7:0]),
+		.s_din_0(bus_din),
+		.s_read_0(cmb_read),
+		.s_zero_0(cmb_zero),
+		.s_write_0(cmb_write),
+		.s_dout_3(cmb_dout),
+		.s_ready_0(cmb_ready),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	vid_shared_ram #(
+		.TYPE("EBR")
+	) color_mem_b_I (
+		.p_addr_0(cm_addr1_8),
+		.p_read_0(cm_read_8),
+		.p_zero_0(cm_zero_8),
+		.p_dout_3(cm_data1_11),
+		.s_addr_0(bus_addr[7:0]),
+		.s_din_0(bus_din),
+		.s_read_0(1'b0),
+		.s_zero_0(1'b0),
+		.s_write_0(cmb_write),
+		.s_dout_3(),
+		.s_ready_0(),
+		.clk(clk),
+		.rst(rst)
+	);
+
+
+	// External bus interface
+	// ----------------------
+
+	// Request lines from the various memories
+	always @(posedge clk)
+		if (smb_clear) begin
+			smb_read  <= 1'b0;
+			smb_zero  <= 1'b0;
+			smb_write <= 1'b0;
+			smb_req   <= 1'b0;
+		end else begin
+			smb_read  <= (bus_addr[15:14] == 2'b10) & ~bus_we;
+			smb_zero  <= (bus_addr[15:14] != 2'b10);
+			smb_write <= (bus_addr[15:14] == 2'b10) & bus_we;
+			smb_req   <= (bus_addr[15:14] == 2'b10);
+		end
+
+	always @(posedge clk)
+		if (gmb_clear) begin
+			gmb_read  <= 1'b0;
+			gmb_zero  <= 1'b0;
+			gmb_write <= 1'b0;
+			gmb_req   <= 1'b0;
+		end else begin
+			gmb_read  <= (bus_addr[15:14] == 2'b11) & ~bus_we;
+			gmb_zero  <= (bus_addr[15:14] != 2'b11);
+			gmb_write <= (bus_addr[15:14] == 2'b11) & bus_we;
+			gmb_req   <= (bus_addr[15:14] == 2'b11);
+		end
+
+	always @(posedge clk)
+		if (cmb_clear) begin
+			cmb_read  <= 1'b0;
+			cmb_zero  <= 1'b0;
+			cmb_write <= 1'b0;
+			cmb_req   <= 1'b0;
+		end else begin
+			cmb_read  <= (bus_addr[15:13] == 3'b011) & ~bus_we;
+			cmb_zero  <= (bus_addr[15:13] != 3'b011);
+			cmb_write <= (bus_addr[15:13] == 3'b011) & bus_we;
+			cmb_req   <= (bus_addr[15:13] == 3'b011);
+		end
+
+	// Condition to force the requests to zero :
+	//  no access needed, ack pending or this cycle went through
+	assign smb_clear = ~bus_cyc | bus_ack_wait | (smb_req & smb_ready);
+	assign gmb_clear = ~bus_cyc | bus_ack_wait | (gmb_req & gmb_ready);
+	assign cmb_clear = ~bus_cyc | bus_ack_wait | (cmb_req & cmb_ready);
+
+	// Track when request are accepted by the RAM
+	assign bus_req_ok = (smb_req & smb_ready) | (gmb_req & gmb_ready) | (cmb_req & cmb_ready);
+
+	always @(posedge clk)
+		bus_req_ok_dly <= { bus_req_ok_dly[1:0], bus_req_ok & ~bus_we };
+
+	// ACK wait state tracking
+	always @(posedge clk)
+		if (rst)
+			bus_ack_wait <= 1'b0;
+		else
+			bus_ack_wait <= ((bus_ack_wait & ~bus_we) | bus_req_ok) & ~bus_req_ok_dly[2];
+
+	// Bus Ack
+	assign bus_ack = bus_ack_wait & (bus_we | bus_req_ok_dly[2]);
+
+	// Output is simply the OR of all memory since we force them to zero if
+	// they're not accessed
+	assign bus_dout = smb_dout | gmb_dout | cmb_dout;
+
+endmodule // vid_text
+
+
+module vid_color_map (
+	input  wire [7:0] attrs,
+	input  wire [3:0] glyph,
+	output reg  [7:0] color
+);
+
+	always @(*)
+	begin
+		if (attrs[1]) begin
+			if (glyph[3:1] == 3'b000)
+				color <= { 4'b0000, glyph[0], glyph[0] ? attrs[7:5] : attrs[4:2] };
+			else
+				color <= { 4'b0001, glyph };
+		end else begin
+			color <= { attrs[7:4], glyph };
+		end
+	end
+
+endmodule

+ 195 - 0
cores/video/rtl/vid_tgen.v

@@ -0,0 +1,195 @@
+/*
+ * vid_tgen.v
+ *
+ * Video Timing Generator
+ *
+ * Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * BSD 3-clause, see LICENSE.bsd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * vim: ts=4 sw=4
+ */
+
+`default_nettype none
+`define FORCE_REG		// Yosys fuckery workaround :/
+
+module vid_tgen #(
+	parameter integer H_WIDTH  = 12,
+	parameter integer H_FP     =   88 / 2,
+	parameter integer H_SYNC   =   44 / 2,
+	parameter integer H_BP     =  148 / 2,
+	parameter integer H_ACTIVE = 1920 / 2,
+	parameter integer V_WIDTH  = 12,
+	parameter integer V_FP     =    4,
+	parameter integer V_SYNC   =    5,
+	parameter integer V_BP     =   36,
+	parameter integer V_ACTIVE = 1080
+)(
+	output reg  vid_hsync,
+	output reg  vid_vsync,
+	output reg  vid_active,
+	output reg  vid_h_first,
+	output reg  vid_h_last,
+	output reg  vid_v_first,
+	output reg  vid_v_last,
+
+	input  wire clk,
+	input  wire rst
+);
+
+	localparam Z_FP     = 0;
+	localparam Z_SYNC   = 1;
+	localparam Z_BP     = 2;
+	localparam Z_ACTIVE = 3;
+
+	// Signals
+	reg  [1:0] h_zone;
+	wire [H_WIDTH:0] h_dec;
+	reg  [H_WIDTH:0] h_mux;
+	reg  [H_WIDTH:0] h_cnt;
+	reg  h_first;
+	wire h_last;
+
+	reg  [1:0] v_zone;
+	wire [V_WIDTH:0] v_dec;
+	reg  [V_WIDTH:0] v_mux;
+`ifdef FORCE_REG
+	wire [V_WIDTH:0] v_cnt;
+`else
+	reg  [V_WIDTH:0] v_cnt;
+`endif
+	reg  v_first;
+	wire v_last;
+	wire v_ce;
+	reg  v_ce_r;
+
+	// Horizontal Counter
+	assign h_dec  = h_cnt - 1;
+	assign h_last = h_cnt[H_WIDTH];
+
+	always @(posedge clk or posedge rst)
+		if (rst)
+			h_first <= 1'b1;
+		else
+			h_first <= h_last;
+
+	always @(posedge clk or posedge rst)
+		if (rst)
+			h_zone <= 2'b00;
+		else
+			h_zone <= h_zone + h_last;
+
+	always @(*)
+	begin
+		h_mux = h_dec;
+
+		if (h_last)
+			case (h_zone)
+				Z_FP:     h_mux = H_SYNC   - 2;
+				Z_SYNC:   h_mux = H_BP     - 2;
+				Z_BP:     h_mux = H_ACTIVE - 2;
+				Z_ACTIVE: h_mux = H_FP     - 2;
+			endcase
+	end
+
+	always @(posedge clk or posedge rst)
+		if (rst)
+			h_cnt <= 0;
+		else
+			h_cnt <= h_mux;
+
+	// Vertical Counter
+	assign v_dec  = v_cnt - 1;
+	assign v_last = v_cnt[V_WIDTH];
+	assign v_ce   = h_last & (h_zone == Z_ACTIVE);
+
+	always @(posedge clk)
+		v_ce_r <= v_ce;
+
+	always @(posedge clk or posedge rst)
+		if (rst)
+			v_first <= 1'b1;
+		else if (v_ce)
+			v_first <= v_last;
+
+	always @(posedge clk or posedge rst)
+		if (rst)
+			v_zone <= 2'b00;
+		else if (v_ce)
+			v_zone <= v_zone + v_last;
+
+	always @(*)
+	begin
+		v_mux = v_dec;
+
+		if (v_last)
+			case (v_zone)
+				Z_FP:     v_mux = V_SYNC   - 2;
+				Z_SYNC:   v_mux = V_BP     - 2;
+				Z_BP:     v_mux = V_ACTIVE - 2;
+				Z_ACTIVE: v_mux = V_FP     - 2;
+			endcase
+	end
+
+`ifdef FORCE_REG
+	dffer_n #(
+		.WIDTH(V_WIDTH+1)
+	) v_cnt_I (
+		.d(v_mux),
+		.q(v_cnt),
+		.ce(v_ce),
+		.clk(clk),
+		.rst(rst)
+	);
+`else
+	always @(posedge clk or posedge rst)
+		if (rst)
+			v_cnt <= 0;
+		else if (v_ce)
+			v_cnt <= v_mux;
+`endif
+
+	// Active / Sync generation
+	always @(posedge clk or posedge rst)
+		if (rst) begin
+			vid_hsync   <= 1'b0;
+			vid_vsync   <= 1'b0;
+			vid_active  <= 1'b0;
+			vid_h_first <= 1'b0;
+			vid_h_last  <= 1'b0;
+			vid_v_first <= 1'b0;
+			vid_v_last  <= 1'b0;
+		end else begin
+			vid_hsync   <= (h_zone == Z_SYNC);
+			vid_vsync   <= (v_zone == Z_SYNC);
+			vid_active  <= (h_zone == Z_ACTIVE) & (v_zone == Z_ACTIVE);
+			vid_h_first <= (h_zone == Z_ACTIVE) & h_first;
+			vid_h_last  <= (h_zone == Z_ACTIVE) & h_last;
+			vid_v_first <= (v_zone == Z_ACTIVE) & v_first;
+			vid_v_last  <= (v_zone == Z_ACTIVE) & v_last;
+		end
+
+endmodule // vid_tgen