From 03370cff3401f64451ad75a73044beecd1e38af6 Mon Sep 17 00:00:00 2001 From: saji Date: Fri, 19 Apr 2024 15:13:39 +0000 Subject: [PATCH] Initial commit --- .gitignore | 4 + Makefile | 4 + README.md | 11 + flake.lock | 27 + flake.nix | 60 +++ litex/default.nix | 17 + litex/litedram.nix | 27 + litex/liteeth.nix | 36 ++ litex/litescope.nix | 27 + litex/litex.nix | 32 ++ litex/pythondata-cpu-vexriscv.nix | 16 + litex/pythondata-software-compiler_rt.nix | 16 + litex/pythondata-software-picolibc.nix | 16 + src/blink.lpf | 27 + src/blink.v | 570 ++++++++++++++++++++++ 15 files changed, 890 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 litex/default.nix create mode 100644 litex/litedram.nix create mode 100644 litex/liteeth.nix create mode 100644 litex/litescope.nix create mode 100644 litex/litex.nix create mode 100644 litex/pythondata-cpu-vexriscv.nix create mode 100644 litex/pythondata-software-compiler_rt.nix create mode 100644 litex/pythondata-software-picolibc.nix create mode 100644 src/blink.lpf create mode 100644 src/blink.v diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..afc7feb --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.bit +*.o +*.bin +build/* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..625c48b --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ + +NEXTPNR_FLAGS= --45k --package CABGA381 --speed 6 --freq 65 +YOSYS_FLAGS= + diff --git a/README.md b/README.md new file mode 100644 index 0000000..63399e0 --- /dev/null +++ b/README.md @@ -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 + diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..6294073 --- /dev/null +++ b/flake.lock @@ -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 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..48062e7 --- /dev/null +++ b/flake.nix @@ -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. + ]; + }; + }); + }; +} diff --git a/litex/default.nix b/litex/default.nix new file mode 100644 index 0000000..a754421 --- /dev/null +++ b/litex/default.nix @@ -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) { }; + }) + ]; +} + diff --git a/litex/litedram.nix b/litex/litedram.nix new file mode 100644 index 0000000..7eb6c18 --- /dev/null +++ b/litex/litedram.nix @@ -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; +} diff --git a/litex/liteeth.nix b/litex/liteeth.nix new file mode 100644 index 0000000..dfb1f43 --- /dev/null +++ b/litex/liteeth.nix @@ -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; +} diff --git a/litex/litescope.nix b/litex/litescope.nix new file mode 100644 index 0000000..a731763 --- /dev/null +++ b/litex/litescope.nix @@ -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; +} diff --git a/litex/litex.nix b/litex/litex.nix new file mode 100644 index 0000000..15255a5 --- /dev/null +++ b/litex/litex.nix @@ -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; +} diff --git a/litex/pythondata-cpu-vexriscv.nix b/litex/pythondata-cpu-vexriscv.nix new file mode 100644 index 0000000..4f93207 --- /dev/null +++ b/litex/pythondata-cpu-vexriscv.nix @@ -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; +} diff --git a/litex/pythondata-software-compiler_rt.nix b/litex/pythondata-software-compiler_rt.nix new file mode 100644 index 0000000..9629e9e --- /dev/null +++ b/litex/pythondata-software-compiler_rt.nix @@ -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; +} diff --git a/litex/pythondata-software-picolibc.nix b/litex/pythondata-software-picolibc.nix new file mode 100644 index 0000000..770b110 --- /dev/null +++ b/litex/pythondata-software-picolibc.nix @@ -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; +} diff --git a/src/blink.lpf b/src/blink.lpf new file mode 100644 index 0000000..242885f --- /dev/null +++ b/src/blink.lpf @@ -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; diff --git a/src/blink.v b/src/blink.v new file mode 100644 index 0000000..fc55ec8 --- /dev/null +++ b/src/blink.v @@ -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