Initial commit

This commit is contained in:
saji 2024-04-19 15:13:39 +00:00
commit 03370cff34
15 changed files with 890 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
*.bit
*.o
*.bin
build/*

4
Makefile Normal file
View file

@ -0,0 +1,4 @@
NEXTPNR_FLAGS= --45k --package CABGA381 --speed 6 --freq 65
YOSYS_FLAGS=

11
README.md Normal file
View file

@ -0,0 +1,11 @@
# ECP5 OSS Flow Template
This repo contains a basic setup for ECP5 development.
It's designed to serve as a modest baseline, more can be added easily
to adapt to each project.
Features:
- Nix flake for toolchains and dependencies
- Test infra
- Github Actions Workflow

27
flake.lock Normal file
View file

@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1712791164,
"narHash": "sha256-3sbWO1mbpWsLepZGbWaMovSO7ndZeFqDSdX0hZ9nVyw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "1042fd8b148a9105f3c0aca3a6177fd1d9360ba5",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

60
flake.nix Normal file
View file

@ -0,0 +1,60 @@
{
description = "ECP5 toolchain template project";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs = inputs@{ nixpkgs, ... }:
let
# litex-overlay = final: prev: {
# pythonPackagesExtensions = prev.pythonPackagesExtensions ++ [
# (python-final: python-prev: {
# litex = python-final.callPackage (import ./litex.nix) { };
# # can add more packages here!
# })
# ];
# };
litex-overlay = import ./litex;
systems = [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin" ];
forAllSystems = function:
nixpkgs.lib.genAttrs systems (system: function (
import nixpkgs {
inherit system;
config.allowUnfree = true;
overlays = [
litex-overlay
]; # patches, version pins, new pkgs here.
}
));
in {
devShells = forAllSystems (pkgs: {
default = pkgs.mkShell {
packages = with pkgs; [
(python3.withPackages (ps: with ps; [
cocotb
cocotb-bus
litex
litedram
liteeth
litescope
pythondata-cpu-vexriscv
pythondata-software-compiler_rt
pythondata-software-picolibc
amaranth
]))
yosys
nextpnr
# simulators
verilog
verilator
# support package
trellis
# loader
openfpgaloader
ecpdap # easier to poke probes.
];
};
});
};
}

17
litex/default.nix Normal file
View file

@ -0,0 +1,17 @@
# an overlay to
let
tag = "2023.12";
in final: prev: {
pythonPackagesExtensions = prev.pythonPackagesExtensions ++ [
(python-final: python-prev: {
litex = python-final.callPackage(import ./litex.nix tag) { };
litedram = python-final.callPackage(import ./litedram.nix tag) { };
liteeth = python-final.callPackage(import ./liteeth.nix tag) { };
litescope = python-final.callPackage(import ./litescope.nix tag) { };
pythondata-cpu-vexriscv = python-final.callPackage(import ./pythondata-cpu-vexriscv.nix tag) { };
pythondata-software-compiler_rt = python-final.callPackage(import ./pythondata-software-compiler_rt.nix tag) { };
pythondata-software-picolibc = python-final.callPackage(import ./pythondata-software-picolibc.nix tag) { };
})
];
}

27
litex/litedram.nix Normal file
View file

@ -0,0 +1,27 @@
tag: {
pkgs
, lib
, buildPythonPackage
, migen
, pyyaml
}: buildPythonPackage {
pname = "litedram";
version = "${tag}";
src = pkgs.fetchFromGitHub {
owner = "enjoy-digital";
repo = "litedram";
rev = "${tag}";
hash = "sha256-EaUszy0v6r5sKM5d5YmpRbR8Cf9xITObU71zUpg9cLU=";
};
buildInputs = [
pyyaml
migen
];
checkPhase = ''
python -m unittest test
'';
doCheck = true;
}

36
litex/liteeth.nix Normal file
View file

@ -0,0 +1,36 @@
tag: {
pkgs
, lib
, buildPythonPackage
, migen
, setuptools
, litex
, pyyaml
}: buildPythonPackage {
pname = "liteeth";
version = "${tag}";
src = pkgs.fetchFromGitHub {
owner = "enjoy-digital";
repo = "liteeth";
rev = "${tag}";
hash = "sha256-DUNwDzcFLVmL5F/ZWmok7T7jO7ixC9IuDr1WUarnAqk=";
};
buildInputs = [
litex
];
propagatedBuildInputs = [
migen
];
nativeCheckInputs = [
pyyaml
];
checkPhase = ''
python -m unittest test
'';
doCheck = true;
}

27
litex/litescope.nix Normal file
View file

@ -0,0 +1,27 @@
tag: {
pkgs
, lib
, buildPythonPackage
, migen
, setuptools
, litex
}: buildPythonPackage {
pname = "litescope";
version = "${tag}";
src = pkgs.fetchFromGitHub {
owner = "enjoy-digital";
repo = "litescope";
rev = "${tag}";
hash = "sha256-OWC+XwB+BzlCQkPKJCbQ0W4T6JbsInldrn9jYYjWypM=";
};
buildInputs = [
litex
];
checkPhase = ''
python -m unittest test
'';
doCheck = true;
}

32
litex/litex.nix Normal file
View file

@ -0,0 +1,32 @@
tag: {
pkgs
, lib
, buildPythonPackage
, pyserial
, migen
, requests
, packaging
, pexpect
}: buildPythonPackage {
pname = "litex";
version = "${tag}";
src = pkgs.fetchFromGitHub {
owner = "enjoy-digital";
repo = "litex";
rev = "${tag}";
hash = "sha256-OcwqYLQ7ec2vTewdIJqP/aTCJ4yI43OIOkTMD/hIKO0=";
};
buildInputs = [
migen
];
propagatedBuildInputs = [
requests
pyserial
packaging
];
doCheck = false;
}

View file

@ -0,0 +1,16 @@
tag: {
pkgs
, buildPythonPackage
}: buildPythonPackage rec {
pname = "pythondata-cpu-vexriscv";
version = "${tag}";
src = pkgs.fetchFromGitHub {
owner = "litex-hub";
repo = "pythondata-cpu-vexriscv";
rev = "${tag}";
hash = "sha256-1RgwJCYxtiP5dfRHulfTNBgu6fum6RfoqkbFIlRTgKI=";
};
doCheck = false;
}

View file

@ -0,0 +1,16 @@
tag: {
pkgs
, buildPythonPackage
}: buildPythonPackage rec {
pname = "pythondata-software-compiler_rt";
version = "${tag}";
src = pkgs.fetchFromGitHub {
owner = "litex-hub";
repo = "pythondata-software-compiler_rt";
rev = "${tag}";
hash = "sha256-s/tfxhPwYAnu1z1LxEimeYWjX6IHbF2uD/1HjvQn/xo=";
};
doCheck = false;
}

View file

@ -0,0 +1,16 @@
tag: {
pkgs
, buildPythonPackage
}: buildPythonPackage rec {
pname = "pythondata-software-picolibc";
version = "${tag}";
src = pkgs.fetchFromGitHub {
owner = "litex-hub";
repo = "pythondata-software-picolibc";
rev = "${tag}";
hash = "sha256-5OY17BA37c6aHOvUwb0gJwXxGey4TdUiTTxJD5wuSGU=";
};
doCheck = false;
}

27
src/blink.lpf Normal file
View file

@ -0,0 +1,27 @@
LOCATE COMP "clk_25mhz" SITE "P3";
IOBUF PORT "clk_25mhz" IO_TYPE=LVCMOS33;
FREQUENCY PORT "clk_25mhz" 25 MHZ;
SYSCONFIG COMPRESS_CONFIG=ON;
LOCATE COMP "led_o" SITE "L2";
IOBUF PORT "led_o" IO_TYPE=LVCMOS33;
# HDMI
LOCATE COMP "gpdi_dp[0]" SITE "G19"; # Blue +
LOCATE COMP "gpdi_dn[0]" SITE "H20"; # Blue -
LOCATE COMP "gpdi_dp[1]" SITE "E20"; # Green +
LOCATE COMP "gpdi_dn[1]" SITE "F19"; # Green -
LOCATE COMP "gpdi_dp[2]" SITE "C20"; # Red +
LOCATE COMP "gpdi_dn[2]" SITE "D19"; # Red -
LOCATE COMP "gpdi_dp[3]" SITE "J19"; # Clock +
LOCATE COMP "gpdi_dn[3]" SITE "K19"; # Clock -
IOBUF PORT "gpdi_dp[0]" IO_TYPE=LVCMOS33 DRIVE=4;
IOBUF PORT "gpdi_dn[0]" IO_TYPE=LVCMOS33 DRIVE=4;
IOBUF PORT "gpdi_dp[1]" IO_TYPE=LVCMOS33 DRIVE=4;
IOBUF PORT "gpdi_dn[1]" IO_TYPE=LVCMOS33 DRIVE=4;
IOBUF PORT "gpdi_dp[2]" IO_TYPE=LVCMOS33 DRIVE=4;
IOBUF PORT "gpdi_dn[2]" IO_TYPE=LVCMOS33 DRIVE=4;
IOBUF PORT "gpdi_dp[3]" IO_TYPE=LVCMOS33 DRIVE=4;
IOBUF PORT "gpdi_dn[3]" IO_TYPE=LVCMOS33 DRIVE=4;

570
src/blink.v Normal file
View file

@ -0,0 +1,570 @@
`default_nettype none
module blink (
input clk_i,
output reg led_o
);
localparam MAX = 25_000_000;
localparam WIDTH = $clog2(MAX);
wire rst_s;
wire clk_s;
assign clk_s = clk_i;
//pll_12_16 pll_inst (.clki(clk_i), .clko(clk_s), .rst(rst_s));
// rst_gen rst_inst (.clk_i(clk_s), .rst_i(1'b0), .rst_o(rst_s));
reg [WIDTH-1:0] cpt_s;
wire [WIDTH-1:0] cpt_next_s = cpt_s + 1'b1;
wire end_s = cpt_s == MAX-1;
initial begin
led_o <= 0;
end
always @(posedge clk_s) begin
// if we're at the end, reset to 0, else set to next state.
cpt_s <= end_s ? {WIDTH{1'b0}} : cpt_next_s;
if (end_s)
led_o <= ~led_o;
end
endmodule
module topcore (
input clk_25mhz,
output [3:0] gpdi_dp, gpdi_dn,
);
wire clk_25MHz, clk_250MHz;
clock clock_instance(
.clkin_25MHz(clk_25mhz),
.clk_25MHz(clk_25MHz),
.clk_250MHz(clk_250MHz)
);
wire [7:0] red, grn, blu;
wire [23:0] pixel;
assign red= pixel[23:16];
assign grn= pixel[15:8];
assign blu= pixel[7:0];
wire o_red;
wire o_grn;
wire o_blu;
wire o_rd, o_newline, o_newframe;
// A reset line that goes low after 16 ticks
reg [2:0] reset_cnt = 0;
wire reset = ~reset_cnt[2];
always @(posedge clk_25mhz)
if (reset) reset_cnt <= reset_cnt + 1;
llhdmi llhdmi_instance(
.i_tmdsclk(clk_250MHz), .i_pixclk(clk_25MHz),
.i_reset(reset), .i_red(red), .i_grn(grn), .i_blu(blu),
.o_rd(o_rd), .o_newline(o_newline), .o_newframe(o_newframe),
.o_red(o_red), .o_grn(o_grn), .o_blu(o_blu));
vgatestsrc #(.BITS_PER_COLOR(8))
vgatestsrc_instance(
.i_pixclk(clk_25MHz), .i_reset(reset),
.i_width(640), .i_height(480),
.i_rd(o_rd), .i_newline(o_newline), .i_newframe(o_newframe),
.o_pixel(pixel));
OBUFDS OBUFDS_red(.I(o_red), .O(gpdi_dp[2]), .OB(gpdi_dn[2]));
OBUFDS OBUFDS_grn(.I(o_grn), .O(gpdi_dp[1]), .OB(gpdi_dn[1]));
OBUFDS OBUFDS_blu(.I(o_blu), .O(gpdi_dp[0]), .OB(gpdi_dn[0]));
OBUFDS OBUFDS_clock(.I(clk_25MHz), .O(gpdi_dp[3]), .OB(gpdi_dn[3]));
endmodule
module OBUFDS(
input I, // input
output O, // positive output
output OB // negative output
);
assign O = I;
assign OB = ~I;
endmodule
module clock
(
input clkin_25MHz,
output clk_125MHz,
output clk_250MHz,
output clk_25MHz,
output clk_83M333Hz,
output locked
);
wire int_locked;
(* ICP_CURRENT="9" *) (* LPF_RESISTOR="8" *) (* MFG_ENABLE_FILTEROPAMP="1" *) (* MFG_GMCREF_SEL="2" *)
EHXPLLL
#(
.PLLRST_ENA("DISABLED"),
.INTFB_WAKE("DISABLED"),
.STDBY_ENABLE("DISABLED"),
.DPHASE_SOURCE("DISABLED"),
.CLKOS_FPHASE(0),
.CLKOP_FPHASE(0),
.CLKOS3_CPHASE(5),
.CLKOS2_CPHASE(0),
.CLKOS_CPHASE(1),
.CLKOP_CPHASE(3),
.OUTDIVIDER_MUXD("DIVD"),
.OUTDIVIDER_MUXC("DIVC"),
.OUTDIVIDER_MUXB("DIVB"),
.OUTDIVIDER_MUXA("DIVA"),
.CLKOS3_ENABLE("ENABLED"),
.CLKOS2_ENABLE("ENABLED"),
.CLKOS_ENABLE("ENABLED"),
.CLKOP_ENABLE("ENABLED"),
.CLKOS3_DIV(0),
.CLKOS2_DIV(20),
.CLKOS_DIV(2),
.CLKOP_DIV(4),
.CLKFB_DIV(5),
.CLKI_DIV(1),
.FEEDBK_PATH("CLKOP")
)
pll_i
(
.CLKI(clkin_25MHz),
.CLKFB(clk_125MHz),
.CLKOP(clk_125MHz),
.CLKOS(clk_250MHz),
.CLKOS2(clk_25MHz),
.CLKOS3(clk_83M333Hz),
.RST(1'b0),
.STDBY(1'b0),
.PHASESEL0(1'b0),
.PHASESEL1(1'b0),
.PHASEDIR(1'b0),
.PHASESTEP(1'b0),
.PLLWAKESYNC(1'b0),
.ENCLKOP(1'b0),
.ENCLKOS(1'b0),
.ENCLKOS2(1'b0),
.ENCLKOS3(1'b0),
.LOCK(locked),
.INTLOCK(int_locked)
);
endmodule
module vgatestsrc(i_pixclk, i_reset,
// External connections
i_width, i_height,
i_rd, i_newline, i_newframe,
// VGA connections
o_pixel);
parameter BITS_PER_COLOR = 4,
HW=12, VW=12;
//HW=13,VW=11;
localparam BPC = BITS_PER_COLOR,
BITS_PER_PIXEL = 3 * BPC,
BPP = BITS_PER_PIXEL;
//
input wire i_pixclk, i_reset;
input wire [HW-1:0] i_width;
input wire [VW-1:0] i_height;
//
input wire i_rd, i_newline, i_newframe;
//
output reg [(BPP-1):0] o_pixel;
wire [BPP-1:0] white, black, purplish_blue, purple, dark_gray,
darkest_gray, mid_white, mid_cyan, mid_magenta,
mid_red, mid_green, mid_blue, mid_yellow;
wire [BPC-1:0] midv, mid_off;
assign midv = { 2'b11, {(BPC-2){1'b0}} };
assign mid_off = { (BPC){1'b0} };
assign white = {(BPP){1'b1}};
assign black = {(BPP){1'b0}};
assign purplish_blue = {
{(BPC){1'b0}},
3'b001, {(BPC-3){1'b0}},
2'b01, {(BPC-2){1'b0}} };
assign purple = { {2'b00, {(BPC-2){1'b1}} }, {(BPC){1'b0}},
{ 1'b0, {(BPC-1){1'b1}} } };
assign dark_gray = {(3){ { 4'b0010, {(BPC-4){1'b0}} } }};
assign darkest_gray = {(3){ { 4'b0001, {(BPC-4){1'b0}} } }};
assign mid_white = { midv, midv, midv };
assign mid_yellow = { midv, midv, mid_off };
assign mid_red = { midv, mid_off, mid_off };
assign mid_green = { mid_off, midv, mid_off };
assign mid_blue = { mid_off, mid_off, midv };
assign mid_cyan = { mid_off, midv, midv };
assign mid_magenta = { midv, mid_off, midv };
reg [HW-1:0] hpos, hedge;
reg [VW-1:0] ypos, yedge;
reg [3:0] yline, hbar;
//
//
// 1 Border
// 8 BARS
// 1 short bar
// 3 fat bars
// 1 border
// 1 gradient bar
// 1 border
//
reg dline;
always @(posedge i_pixclk)
if ((i_reset)||(i_newframe)||(i_newline))
dline <= 1'b0;
else if (i_rd)
dline <= 1'b1;
always @(posedge i_pixclk)
if ((i_reset)||(i_newframe))
begin
ypos <= 0;
yline <= 0;
yedge <= { 4'h0, i_height[(VW-1):4] };
end else if (i_newline)
begin
ypos <= ypos + { {(VW-1){1'h0}}, dline };
if (ypos >= yedge)
begin
yline <= yline + 1'b1;
yedge <= yedge + { 4'h0, i_height[(VW-1):4] };
end
end
initial hpos = 0;
initial hbar = 0;
initial hedge = 0; // { 4'h0, i_width[(HW-1):4] };
always @(posedge i_pixclk)
if ((i_reset)||(i_newline))
begin
hpos <= 0;
hbar <= 0;
hedge <= { 4'h0, i_width[(HW-1):4] };
end else if (i_rd)
begin
hpos <= hpos + 1'b1;
if (hpos >= hedge)
begin
hbar <= hbar + 1'b1;
hedge <= hedge + { 4'h0, i_width[(HW-1):4] };
end
end
reg [BPP-1:0] topbar, midbar, fatbar, gradient, pattern;
always @(posedge i_pixclk)
case(hbar[3:0])
4'h0: topbar <= black;
4'h1: topbar <= mid_white;
4'h2: topbar <= mid_white;
4'h3: topbar <= mid_yellow;
4'h4: topbar <= mid_yellow;
4'h5: topbar <= mid_cyan;
4'h6: topbar <= mid_cyan;
4'h7: topbar <= mid_green;
4'h8: topbar <= mid_green;
4'h9: topbar <= mid_magenta;
4'ha: topbar <= mid_magenta;
4'hb: topbar <= mid_red;
4'hc: topbar <= mid_red;
4'hd: topbar <= mid_blue;
4'he: topbar <= mid_blue;
4'hf: topbar <= black;
endcase
always @(posedge i_pixclk)
case(hbar[3:0])
4'h0: midbar <= black;
4'h1: midbar <= mid_blue;
4'h2: midbar <= mid_blue;
4'h3: midbar <= black;
4'h4: midbar <= black;
4'h5: midbar <= mid_magenta;
4'h6: midbar <= mid_magenta;
4'h7: midbar <= black;
4'h8: midbar <= black;
4'h9: midbar <= mid_cyan;
4'ha: midbar <= mid_cyan;
4'hb: midbar <= black;
4'hc: midbar <= black;
4'hd: midbar <= mid_white;
4'he: midbar <= mid_white;
4'hf: midbar <= black;
endcase
always @(posedge i_pixclk)
case(hbar[3:0])
4'h0: fatbar <= black;
4'h1: fatbar <= purplish_blue;
4'h2: fatbar <= purplish_blue;
4'h3: fatbar <= purplish_blue;
4'h4: fatbar <= white;
4'h5: fatbar <= white;
4'h6: fatbar <= white;
4'h7: fatbar <= purple;
4'h8: fatbar <= purple;
4'h9: fatbar <= purple;
4'ha: fatbar <= darkest_gray;
4'hb: fatbar <= black;
4'hc: fatbar <= dark_gray;
4'hd: fatbar <= darkest_gray;
4'he: fatbar <= black;
4'hf: fatbar <= black;
endcase
reg [(HW-1):0] last_width;
always @(posedge i_pixclk)
last_width <= i_width;
// Attempt to discover 1/i_width in h_step
localparam FRACB=16;
//
reg [(FRACB-1):0] hfrac, h_step;
always @(posedge i_pixclk)
if ((i_reset)||(i_newline))
hfrac <= 0;
else if (i_rd)
hfrac <= hfrac + h_step;
always @(posedge i_pixclk)
if ((i_reset)||(i_width != last_width))
h_step <= 1;
else if ((i_newline)&&(hfrac > 0))
begin
if (hfrac < {(FRACB){1'b1}} - { {(FRACB-HW){1'b0}}, i_width })
h_step <= h_step + 1'b1;
else if (hfrac < { {(FRACB-HW){1'b0}}, i_width })
h_step <= h_step - 1'b1;
end
always @(posedge i_pixclk)
case(hfrac[FRACB-1:FRACB-4])
4'h0: gradient <= black;
// Red
4'h1: gradient <= { 1'b0, hfrac[(FRACB-5):(FRACB-3-BPC)], {(2){mid_off}} };
4'h2: gradient <= { 1'b1, hfrac[(FRACB-5):(FRACB-3-BPC)], {(2){mid_off}} };
4'h3: gradient <= black;
// Green
4'h4: gradient <= { mid_off, 1'b0, hfrac[(FRACB-5):(FRACB-3-BPC)], mid_off };
4'h5: gradient <= { mid_off, 1'b1, hfrac[(FRACB-5):(FRACB-3-BPC)], mid_off };
4'h6: gradient <= black;
// Blue
4'h7: gradient <= { {(2){mid_off}}, 1'b0, hfrac[(FRACB-5):(FRACB-3-BPC)] };
4'h8: gradient <= { {(2){mid_off}}, 1'b1, hfrac[(FRACB-5):(FRACB-3-BPC)] };
4'h9: gradient <= black;
// Gray
4'ha: gradient <= {(3){ 2'b00, hfrac[(FRACB-5):(FRACB-2-BPC)] }};
4'hb: gradient <= {(3){ 2'b01, hfrac[(FRACB-5):(FRACB-2-BPC)] }};
4'hc: gradient <= {(3){ 2'b10, hfrac[(FRACB-5):(FRACB-2-BPC)] }};
4'hd: gradient <= {(3){ 2'b11, hfrac[(FRACB-5):(FRACB-2-BPC)] }};
4'he: gradient <= black;
//
4'hf: gradient <= black;
endcase
always @(posedge i_pixclk)
case(yline)
4'h0: pattern <= black;
4'h1: pattern <= topbar; //
4'h2: pattern <= topbar;
4'h3: pattern <= topbar;
4'h4: pattern <= topbar;
4'h5: pattern <= topbar;
4'h6: pattern <= topbar;
4'h7: pattern <= topbar;
4'h8: pattern <= topbar;
4'h9: pattern <= midbar; //
4'ha: pattern <= fatbar; //
4'hb: pattern <= fatbar;
4'hc: pattern <= fatbar;
4'hd: pattern <= black;
4'he: pattern <= gradient;
4'hf: pattern <= black;
endcase
always @(posedge i_pixclk)
if (i_newline)
o_pixel <= white;
else if (i_rd)
begin
if (hpos == i_width-12'd3)
o_pixel <= white;
else if ((ypos == 0)||(ypos == i_height-1))
o_pixel <= white;
else
o_pixel <= pattern;
end
endmodule
module llhdmi(
i_tmdsclk, i_pixclk,
i_reset, i_red, i_grn, i_blu,
o_rd, o_newline, o_newframe,
`ifdef VERILATOR
o_TMDS_red, o_TMDS_grn, o_TMDS_blu,
`endif
o_red, o_grn, o_blu);
input wire i_tmdsclk; // TMDS clock
input wire i_pixclk; // Pixel clock, 10 times slower than i_tmdsclk
input wire i_reset; // Reset this module when strobed high
input wire [7:0] i_red; // Red green and blue colour values
input wire [7:0] i_grn; // for each pixel
input wire [7:0] i_blu;
output wire o_rd; // True when we can accept pixel data
output reg o_newline; // True on last pixel of each line
output reg o_newframe; // True on last pixel of each frame
output wire o_red; // Red TMDS pixel stream
output wire o_grn; // Green TMDS pixel stream
output wire o_blu; // Blue TMDS pixel stream
`ifdef VERILATOR
output wire [9:0] o_TMDS_red, o_TMDS_grn, o_TMDS_blu;
assign o_TMDS_red= TMDS_red;
assign o_TMDS_grn= TMDS_grn;
assign o_TMDS_blu= TMDS_blu;
`endif
reg [9:0] CounterX, CounterY;
reg hSync, vSync, DrawArea;
// Keep track of the current X/Y pixel position
always @(posedge i_pixclk)
if (i_reset)
CounterX <= 0;
else
CounterX <= (CounterX==799) ? 0 : CounterX+1;
always @(posedge i_pixclk)
if (i_reset)
CounterY <= 0;
else if (CounterX==799) begin
CounterY <= (CounterY==524) ? 0 : CounterY+1;
end
// Signal end of line, end of frame
always @(posedge i_pixclk) begin
o_newline <= (CounterX==639) ? 1 : 0;
o_newframe <= (CounterX==639) && (CounterY==479) ? 1 : 0;
end
// Determine when we are in a drawable area
always @(posedge i_pixclk)
DrawArea <= (CounterX<640) && (CounterY<480);
assign o_rd= ~i_reset & DrawArea;
// Generate horizontal and vertical sync pulses
always @(posedge i_pixclk)
hSync <= (CounterX>=656) && (CounterX<752);
always @(posedge i_pixclk)
vSync <= (CounterY>=490) && (CounterY<492);
// Convert the 8-bit colours into 10-bit TMDS values
wire [9:0] TMDS_red, TMDS_grn, TMDS_blu;
TMDS_encoder encode_R(.clk(i_pixclk), .VD(i_red), .CD(2'b00),
.VDE(DrawArea), .TMDS(TMDS_red));
TMDS_encoder encode_G(.clk(i_pixclk), .VD(i_grn), .CD(2'b00),
.VDE(DrawArea), .TMDS(TMDS_grn));
TMDS_encoder encode_B(.clk(i_pixclk), .VD(i_blu), .CD({vSync,hSync}),
.VDE(DrawArea), .TMDS(TMDS_blu));
// Strobe the TMDS_shift_load once every 10 i_tmdsclks
// i.e. at the start of new pixel data
reg [3:0] TMDS_mod10=0;
reg TMDS_shift_load=0;
always @(posedge i_tmdsclk) begin
if (i_reset) begin
TMDS_mod10 <= 0;
TMDS_shift_load <= 0;
end else begin
TMDS_mod10 <= (TMDS_mod10==4'd9) ? 4'd0 : TMDS_mod10+4'd1;
TMDS_shift_load <= (TMDS_mod10==4'd9);
end
end
// Latch the TMDS colour values into three shift registers
// at the start of the pixel, then shift them one bit each i_tmdsclk.
// We will then output the LSB on each i_tmdsclk.
reg [9:0] TMDS_shift_red=0, TMDS_shift_grn=0, TMDS_shift_blu=0;
always @(posedge i_tmdsclk) begin
if (i_reset) begin
TMDS_shift_red <= 0;
TMDS_shift_grn <= 0;
TMDS_shift_blu <= 0;
end else begin
TMDS_shift_red <= TMDS_shift_load ? TMDS_red: {1'b0, TMDS_shift_red[9:1]};
TMDS_shift_grn <= TMDS_shift_load ? TMDS_grn: {1'b0, TMDS_shift_grn[9:1]};
TMDS_shift_blu <= TMDS_shift_load ? TMDS_blu: {1'b0, TMDS_shift_blu[9:1]};
end
end
// Finally output the LSB of each color bitstream
assign o_red= TMDS_shift_red[0];
assign o_grn= TMDS_shift_grn[0];
assign o_blu= TMDS_shift_blu[0];
endmodule
module TMDS_encoder(
input clk, // 250 MHz
input [7:0] VD, // video data (red, green or blue)
input [1:0] CD, // control data
input VDE, // video data enable, to choose between CD (when VDE=0) and VD (when VDE=1)
output reg [9:0] TMDS = 0
);
wire [3:0] Nb1s = {3'b0, VD[0]} + {3'b0, VD[1]} + {3'b0, VD[2]}
+ {3'b0, VD[3]} + {3'b0, VD[4]} + {3'b0, VD[5]}
+ {3'b0, VD[6]} + {3'b0, VD[7]};
wire XNOR = (Nb1s>4'd4) || (Nb1s==4'd4 && VD[0]==1'b0);
// To keep Verilator happy, we create individual wires, determine
// their values and then merge them into q_m[]
wire QM0, QM1, QM2, QM3, QM4, QM5, QM6, QM7, QM8;
assign QM0= VD[0];
assign QM1= QM0 ^ VD[1] ^ XNOR;
assign QM2= QM1 ^ VD[2] ^ XNOR;
assign QM3= QM2 ^ VD[3] ^ XNOR;
assign QM4= QM3 ^ VD[4] ^ XNOR;
assign QM5= QM4 ^ VD[5] ^ XNOR;
assign QM6= QM5 ^ VD[6] ^ XNOR;
assign QM7= QM6 ^ VD[7] ^ XNOR;
assign QM8= ~XNOR;
wire [8:0] q_m = { QM8, QM7, QM6, QM5, QM4, QM3, QM2, QM1, QM0 };
reg [3:0] balance_acc = 0;
wire [3:0] balance = {3'b0, q_m[0]} + {3'b0, q_m[1]} + {3'b0, q_m[2]}
+ {3'b0, q_m[3]} + {3'b0, q_m[4]} + {3'b0, q_m[5]}
+ {3'b0, q_m[6]} + {3'b0, q_m[7]} - 4'd4;
wire balance_sign_eq = (balance[3] == balance_acc[3]);
wire invert_q_m = (balance==0 || balance_acc==0) ? ~q_m[8] : balance_sign_eq;
wire [3:0] balance_acc_inc = balance
- {3'b0,
({q_m[8] ^ ~balance_sign_eq} & ~(balance==0 || balance_acc==0)) };
wire [3:0] balance_acc_new = invert_q_m ? balance_acc-balance_acc_inc : balance_acc+balance_acc_inc;
wire [9:0] TMDS_data = {invert_q_m, q_m[8], q_m[7:0] ^ {8{invert_q_m}}};
wire [9:0] TMDS_code = CD[1] ? (CD[0] ? 10'b1010101011 : 10'b0101010100) : (CD[0] ? 10'b0010101011 : 10'b1101010100);
always @(posedge clk) TMDS <= VDE ? TMDS_data : TMDS_code;
always @(posedge clk) balance_acc <= VDE ? balance_acc_new : 4'h0;
endmodule