FPGA SRAM board
Shadertoy is the future of this project, so what am I doing ? This new board is a reprogrammable FPGA 2D pixel shader (aka fragment shader) which can control the color of pixels and know only of the coordinate of the pixel being drawn. Pixel shaders can also sample the values of nearby pixels. As a result, they can do 2D post-processing (https://en.wikipedia.org/wiki/Video_post-processing) or filtering for a video streams. Why do this on an FPGA rather than in-brower ? You are super low level, and you have constraints based on the number of LUTs, it has a live-playable interface. In any case, I think this is my last board for a while, unless I can make an AI board somehow.
On the other hand, I’m not sure how profound it is to make photoshop like filters. (You can test a 5×5 grid if you go under Filters>Custom in PS). They are all kind of superficial…
I am starting to realize that I spend loads of time debugging soldering errors (yes, I get the learning that comes with that but still). If I made boards and had them assembled elsewhere I would not have this issue. Should I also, in the same spirit, just jump ahead to what everyone else is doing, with the thinking that they are not wasting time on problems that have already been solved and are exploring an interesting space.
I also spend a lot of time designing and building boards, interfaces, and less time programming them: the SCARA robot arm that I didn’t even plug in, the covid temperature interface board, the drone, and the MIT version of the mini swarm robot. On the other side are the projects I’ve perhaps spent too must time making and incrementally remaking perhaps : 3105 power harvester + radio transciever, the Ben Eater style memory. A nice balance was perhaps the solar sunflower protytpes which were experiments with form and function.
*EDIT* I am now thinking that I must work on things that would be of interest to other people, like Machine Learning for instance. I must be humble, just because something is interesting to me doesn’t mean anyone else cares ! Turns our that FPGAs consume a lot less current than GPUs so they are not bad for implementing ML once the training is done. Here is a info about ML with FPGAs :
- https://www.youtube.com/@marcowinzker3682
- https://www.mdpi.com/2076-3417/12/1/89
- https://github.com/verimake-team/MLonFPGA
- https://qengineering.eu/deep-learning-with-fpga-aka-bnn.html
- https://qengineering.eu/embedded-vision.html#FPGA_stitching
Great post about the marble machine project : https://www.reddit.com/r/MarbleMachineX/comments/pzh5kw/an_open_letter_to_martin_on_the_state_of_the/
- The machine is not about reliability and precision, it is about imperfection and the journey
- It’s an art project, it is a project for the project’s sake and the documentation.
- The project shouldn’t run like a start-up, and we are not the sum of what we produce for capitalism
- Not specializing in one thing is what makes this project cool.
ERRATA :
- I think the 590 is not clock dividing as expected…Is it not 3.3V compatible ? I think so, even though unclear on Mouser website.
- Currently only 2 bits of each raspberry pi color input are appearing despite having selected
dpi24
in the rpi config file. - The fine pots are the wrong footprint for the pots I have.
- I could have used DEN (display enable) from the raspi.
- sometimes the rpi starts up and then restarts after playing only a few seconds of video…not sure why.
To change the PLL, there is a tool in IceCube2 under Tool>Configure>Configure PLL Module
Here is the user guide explaining how to use it : https://www.latticesemi.com/-/media/LatticeSemi/Documents/ApplicationNotes/IK2/FPGA-TN-02052-1-4-iCE40-sysCLOCK-PLL-Design-User-Guide.ashx?document_id=47778
I want the following options :
- General Purpose I/O Pad or Core Logic
- Using a feedback path internal to the PLL
- Input Frequency (MHz) : 100
- Output Frequency (MHz) : 25
Here is the new PCF file :
set_io v_sync 87
set_io h_sync 88
set_io clk_in 64
set_io reset 66
set_io r0 81
set_io r1 80
set_io r2 79
set_io g0 94
set_io g1 93
set_io g2 91
set_io b0 76
set_io b1 75
set_io b2 74
set_io led 45
set_io locked_led 37
module vga_sync_test(
input wire clk_in,
input wire reset,
output reg r0,
output reg r1,
output reg r2,
output reg b0,
output reg b1,
output reg b2,
output reg g0,
output reg g1,
output reg g2,
output wire h_sync,
output wire v_sync
);
wire display_en;
//reg [9:0] h_count;
wire [11:0] h_count;
//reg [9:0] v_count;
wire [11:0] v_count;
/*
//for 100MHZ
localparam h_pixel_max = 2560;
localparam v_pixel_max = 1920;
localparam h_pixel_half = 1280;
localparam v_pixel_half = 960;
*/
//for 50MHZ
localparam h_pixel_max = 1280;
localparam v_pixel_max = 960;
localparam h_pixel_half = 640;
localparam v_pixel_half = 480;
/*
//for 25MHZ
localparam h_pixel_max = 640;
localparam v_pixel_max = 480;
localparam h_pixel_half = 320;
localparam v_pixel_half = 240;
*/
//Check if we can create RGB colors
always @(posedge clk_in) begin
if (display_en) begin
if (h_count < h_pixel_half
&& v_count < v_pixel_half) begin
//Assign here your test color
r0 <= 1'b0;
r1 <= 1'b0;
r2 <= 1'b0;
g0 <= 1'b0;
g1 <= 1'b0;
g2 <= 1'b0;
b0 <= 1'b1;
b1 <= 1'b1;
b2 <= 1'b1;
end else if (h_count > h_pixel_half
&& v_count < v_pixel_half) begin
//Assign here your test color
r0 <= 1'b0;
r1 <= 1'b0;
r2 <= 1'b0;
g0 <= 1'b1;
g1 <= 1'b1;
g2 <= 1'b1;
b0 <= 1'b0;
b1 <= 1'b0;
b2 <= 1'b0;
end else if (h_count < h_pixel_half
&& v_count > v_pixel_half) begin
//Assign here your test color
r0 <= 1'b1;
r1 <= 1'b1;
r2 <= 1'b1;
g0 <= 1'b0;
g1 <= 1'b0;
g2 <= 1'b0;
b0 <= 1'b0;
b1 <= 1'b0;
b2 <= 1'b0;
end else begin
//Assign here your test color
r0 <= 1'b1;
r1 <= 1'b1;
r2 <= 1'b1;
g0 <= 1'b1;
g1 <= 1'b1;
g2 <= 1'b1;
b0 <= 1'b1;
b1 <= 1'b1;
b2 <= 1'b1;
end
end else begin
r0 <= 1'b0;
r1 <= 1'b0;
r2 <= 1'b0;
g0 <= 1'b0;
g1 <= 1'b0;
g2 <= 1'b0;
b0 <= 1'b0;
b1 <= 1'b0;
b2 <= 1'b0;
end
end
vga_sync vga_s(
.clk_in(clk_in), //100,50, OR 25MHz clock input
.reset(reset), // RST assigned to SW1
.h_sync(h_sync),
.v_sync(v_sync),
.h_count(h_count),
.v_count(v_count),
.display_en(display_en) // '1' => pixel region
);
endmodule
/*
640x480 VGA singal generator
============================
- Creates h_sync,v_sync signals
- Creates display enable signal and horizontal, vertical
pixel position in display (h,v)
*/
`default_nettype none
module vga_sync(
input wire clk_in,
input wire reset,
output reg h_sync,
output reg v_sync,
output wire clk_sys,
output reg [11:0] h_count,
output reg [11:0] v_count,
output reg display_en
);
// Pixel counters
reg [11:0] h_counter = 0;
reg [11:0] v_counter = 0;
/*
//FOR 100MHz
localparam h_pixel_total = 3200;
localparam h_pixel_display = 2560;
localparam h_pixel_front_porch_amount = 64;
localparam h_pixel_sync_amount = 384;
localparam h_pixel_back_porch_amount = 192;
localparam v_pixel_total = 2100;
localparam v_pixel_display = 1920;
localparam v_pixel_front_porch_amount = 40;
localparam v_pixel_sync_amount = 8;
localparam v_pixel_back_porch_amount = 132;
*/
// FOR 50MHz
localparam h_pixel_total = 1600;
localparam h_pixel_display = 1280;
localparam h_pixel_front_porch_amount = 32;
localparam h_pixel_sync_amount = 192;
localparam h_pixel_back_porch_amount = 96;
localparam v_pixel_total = 1050;
localparam v_pixel_display = 960;
localparam v_pixel_front_porch_amount = 20;
localparam v_pixel_sync_amount = 4;
localparam v_pixel_back_porch_amount = 66;
/*
//FOR 25MHz
localparam h_pixel_total = 800;
localparam h_pixel_display = 640;
localparam h_pixel_front_porch_amount = 16;
localparam h_pixel_sync_amount = 96;
localparam h_pixel_back_porch_amount = 48;
localparam v_pixel_total = 525;
localparam v_pixel_display = 480;
localparam v_pixel_front_porch_amount = 10;
localparam v_pixel_sync_amount = 2;
localparam v_pixel_back_porch_amount = 33;
*/
always @(posedge clk_in) begin
if (reset) begin
//Reset counter values
h_counter <= 0;
v_counter <= 0;
display_en <= 0;
end
else
begin
// Generate display enable signal
if (h_counter < h_pixel_display && v_counter < v_pixel_display)
display_en <= 1;
else
display_en <= 0;
//Check if horizontal has arrived to the end
if (h_counter >= h_pixel_total)
begin
h_counter <= 0;
v_counter <= v_counter + 1;
end
else
//horizontal increment pixel value
h_counter <= h_counter + 1;
// check if vertical has arrived to the end
if (v_counter >= v_pixel_total)
v_counter <= 0;
end
end
always @(posedge clk_in) begin
// Check if sync_pulse needs to be created
if (h_counter >= (h_pixel_display + h_pixel_front_porch_amount)
&& h_counter < (h_pixel_display + h_pixel_front_porch_amount + h_pixel_sync_amount) )
h_sync <= 0;
else
h_sync <= 1;
// Check if sync_pulse needs to be created
if (v_counter >= (v_pixel_display + v_pixel_front_porch_amount)
&& v_counter < (v_pixel_display + v_pixel_front_porch_amount + v_pixel_sync_amount) )
v_sync <= 0;
else
v_sync <= 1;
end
// Route h_/v_counter to out
always @ (posedge clk_in) begin
h_count <= h_counter;
v_count <= v_counter;
end
endmodule
dtparam=spi=off
dtparam=i2c_arm=off
#display_auto_detect=1 // comment this
#dtoverlay=vc4-kms-v3d // comment this
gpio=0-9=a2
gpio=12-17=a2
gpio=20-25=a2
gpio=26-27=a2
dtoverlay=dpi24
enable_dpi_lcd=1
display_default_lcd=1
extra_transpose_buffer=2
dpi_group=2
dpi_mode=87
dpi_output_format=0x7f216
dpi_timings=720 0 40 48 128 720 0 13 3 15 0 0 0 60 0 41000000 4
module vga_sync(
input wire R7IN,
input wire R6IN,
input wire R5IN,
input wire R4IN,
input wire G7IN,
input wire G6IN,
input wire G5IN,
input wire G4IN,
input wire B7IN,
input wire B6IN,
input wire B5IN,
input wire B4IN,
output wire R7OUT,
output wire R6OUT,
output wire R5OUT,
output wire R4OUT,
output wire G7OUT,
output wire G6OUT,
output wire G5OUT,
output wire G4OUT,
output wire B7OUT,
output wire B6OUT,
output wire B5OUT,
output wire B4OUT
);
assign R7OUT = R7IN;
assign R6OUT = R6IN;
assign R5OUT = R5IN;
assign R4OUT = R4IN;
assign G7OUT = G7IN;
assign G6OUT = G6IN;
assign G5OUT = G5IN;
assign G4OUT = G4IN;
assign B7OUT = B7IN;
assign B6OUT = B6IN;
assign B5OUT = B5IN;
assign B4OUT = B4IN;
I made a pcf to go with this test code :
set_io R7IN 107
set_io R6IN 106
set_io R5IN 105
set_io R4IN 104
set_io G7IN 97
set_io G6IN 96
set_io G5IN 95
set_io G4IN 112
set_io B7IN 102
set_io B6IN 101
set_io B5IN 99
set_io B4IN 98
set_io R7OUT 81
set_io R6OUT 80
set_io R5OUT 79
set_io R4OUT 78
set_io G7OUT 94
set_io G6OUT 93
set_io G5OUT 91
set_io G4OUT 90
set_io B7OUT 76
set_io B6OUT 75
set_io B5OUT 74
set_io B4OUT 73
Memory soldering done, added to the pcf :
set_io v_sync 87
set_io h_sync 88
set_io clk_in 64
set_io reset 66
set_io r0 81
set_io r1 80
set_io r2 79
set_io g0 94
set_io g1 93
set_io g2 91
set_io b0 76
set_io b1 75
set_io b2 74
set_io io0 1
set_io io1 2
set_io io2 3
set_io io3 4
set_io io4 122
set_io io5 121
set_io io6 120
set_io io7 119
set_io a0 138
set_io a1 139
set_io a2 141
set_io a3 142
set_io a4 143
set_io a5 8
set_io a6 9
set_io a7 10
set_io a8 11
set_io a9 12
set_io a10 136
set_io a11 135
set_io a12 134
set_io a13 129
set_io a14 128
set_io a15 117
set_io a16 116
set_io a17 115
set_io a18 114
set_io a19 137
set_io a20 113
set_io cs1 71
set_io we0 7
*********************
Refreshing my verilog learning from HDLbits :
A note on wire vs. reg: The right-hand-side of an assign statement must be a net type (e.g., wire), while the left-hand-side of a procedural assignment (in an always block) must be a variable type (e.g., reg).
*******
you can do assign w = a; outside of an always block
assign continuously drives a into w
assign out = a&b; makes out equal to a AND’ed with b
it’s a combinational (i.e., memory-less, with no hidden state) function
using assign is the same as using always @(*)
*****
if you don’t declare it, output [3:0] a; will be a vector of type wire
to declare a vector : type [upper:lower] vector_name;
*****
A bitwise operation between two N-bit vectors replicates the operation for each bit of the vector and produces a N-bit output, while a logical operation treats the entire vector as a boolean value (true = non-zero, false = zero) and produces a 1-bit output.
Eg.
output [2:0] out_or_bitwise,
output out_or_logical,assign out_or_bitwise = a | b;
assign out_or_logical = a || b;Beware the different symbols for bitwise or logical !
****module mod_a ( input in1, input in2, output out );
// Module body
endmoduleWhen instantiating mod_a inside top_module :
module top_module (
input a,
input b,
output out
);// Create an instance of “mod_a” named “inst1”, and connect ports by name:
mod_a inst1 (
.in1(a), // Port”in1″connects to wire “a”
.in2(b), // Port “in2” connects to wire “b”
.out(out) // Port “out” connects to wire “out”
// (Note: mod_a’s port “out” is not related to top_module’s wire “out”.
// It is simply coincidence that they have the same name)
);***
ALWAYS BLOCKS :
Clocked: always @(posedge clk)
Procedural blocks have a richer set of statements (e.g., if-then, case), cannot contain continuous assignments,
Clocked always blocks create a blob of combinational logic just like combinational always blocks, but also creates a set of flip-flops (or “registers”) at the output of the blob of combinational logic. Instead of the outputs of the blob of logic being visible immediately, the outputs are visible only immediately after the next (posedge clk).
In a locked always block, only use procedural non-blocking assignment: (x <= y;)
***
Combinational circuits must have a value assigned to all outputs under all conditions. This usually means you always need else clauses or a default value assigned to the outputs.
This can cause the Warning (10240): … inferring latch(es) error message
***
Saw some inspiring machines in Munich :
Barrels would be cool to incorporate into my next prototype.
I wonder if I could make an analog feedback set up with linear actuators, a screen and camera…
I love the scientific instrument aspect. The monitor hovering on the side could be cool to incorporate.
*****
Check out this analog video feedback setup… could I do a miniature version ? :
****
One reaction to the museum visit it to think of what makes something look like a machine.
- an agglomeration of mainly metal (especially machined aluminum and folded steel) and plastic modules
- rotation and linear motion, symmetry about a central axis or a pile of modules facing the user
If I wanted to focus on the technical object itself, I could work on a custom cooling setup, with thin curving pipes, knobs that allow for various kinds of custom fine control, screens and cameras on various linear axes for feedback loop control.
A DIY water cooler :
A custom keyboard from https://keyboards.tanebox.com/a/blog/939/ :
I like the exposed keyboard that extends into a circuit board with chips on the top. Could it be possible to make a kind of reprogrammable FPGA video synth computer ? Perhaps I could also learn things about video by taking a video file from a an SD card and then moving it into a dual port DRAM memory to be displayed.
This gets at something that feels important : I am always trying to get myself to some harder to get to place, with the help of DIY tech, before making artsy stuff. It’s almost like I feel I’m not competitive enough starting from the most accessible starting point (classical art like drawing, painting, sculpture). Sam suggested that I combine my interest in hardware with software. Can I make a kind of custom programmable computer that is my own and then program it in a low level language (like assembly or verilog)?
I love the windy wire plug found on the mechanical keyboard subreddit :
From https://spectrum.ieee.org/yugoslavia-diy-microcomputer :
Keyboard buttons from Mouser : https://www.mouser.fr/ProductDetail/E-Switch/KS1100OA1AF060?qs=gt1LBUVyoHkJwqhwHM2wRw%3D%3D
***
Or, in the totally opposite direction (not going for gimmicks or flashiness), super inexpensive DRAM chips (1 euro for 16Mbit) :
https://www.mouser.fr/ProductDetail/ISSI/IS42S16100H-7TL?qs=yfIbTn1BQ2BEUPfKEYbGCA%3D%3D
****
Had a thought : I am basically trying to “play” the VGA format itself like an instrument.
****
So making a pin that is both input and output for an FPGA is not so simple ! This post helped : https://electronics.stackexchange.com/questions/33144/birectional-i-o-pin-in-verilog
My attempts to make diagrams :
I also cleaned up the code a bit, making vectors with indices and following naming conventions :
I can see evidence of something being recorded and played back out of sync (only when rpi input provides H&V and the FPGA used the rpi 41MHz pixel clock or the 100MHz clock) but it’s far from working completely.
I can send external clocks that are divisions of 41MHz clock (20.5MHz, 10.25MHz) when rpi is supplying the H&V and it just barely works.
The FPGA works with external 25MHz clock also, but I can’t see any recordings.
*EDIT* I can get pretty clear recordings with the RPI clock and H&V after fiddling. Still no luck with recording RPI color information and then showing it at FPGA H&V with either pixel clock. I have gotten rid of the test colors and now am showing full screen FPGA. Twiddled 4 versus 3 bits on the output.
`default_nettype none module vga_sync_test( input wire clk_in, input wire reset, input wire rec, // Direction of io, 1 = set output, 0 = read input //RASPBERRY PI input wire [3:0] r_in, input wire [3:0] b_in, input wire [3:0] g_in, //VGA OUT output reg [3:0] r_out, output reg [3:0] b_out, output reg [3:0] g_out, output wire h_sync, output wire v_sync, //SRAM output reg [20:0] addr, inout wire [7:0] io, // inout must be type wire output wire cs_1, output reg we_0 ); wire [7:0] data_in; wire [7:0] data_out; reg [7:0] a, b; assign io = rec ? a : 8'bzzzzzzzz; assign data_out = b; assign data_in[1:0] = r_in[3:2]; assign data_in[3:2] = b_in[3:2]; assign data_in[5:4] = g_in[3:2]; assign data_in[7:6] = 2'b00; wire display_en; wire [11:0] h_count; wire [11:0] v_count; assign cs_1 = 0; // low to select, high to deselect localparam h_pixel_max = 1280; localparam v_pixel_max = 960; localparam h_pixel_half = 640; localparam v_pixel_half = 480; //SRAM address counter always @(posedge clk_in) begin if (reset) addr <= 0; else addr <= addr+1; end //REC control always @(posedge clk_in) begin b <= io; a <= data_in; if (rec) begin we_0 <= addr[0]; //not sure why it isn't the inverse of addr[0] but that doesn't make the inverse on 'scope end else begin we_0 <= 1; end end //VGA COLOR OUT always @(posedge clk_in) begin if (display_en) begin if (h_count < h_pixel_half && v_count < v_pixel_half) begin r_out[1:0] <= data_out[1:0]; r_out[2] <= 1'b0; g_out[1:0] <= data_out[3:2]; g_out[2] <= 1'b0; b_out[1:0] <= data_out[5:4]; b_out[2] <= 1'b0; end else if (h_count > h_pixel_half && v_count < v_pixel_half) begin r_out <= 3'b000; g_out <= 3'b111; b_out <= 3'b000; end else if (h_count < h_pixel_half && v_count > v_pixel_half) begin r_out <= 3'b111; g_out <= 3'b000; b_out <= 3'b000; end else begin r_out <= 3'b111; g_out <= 3'b111; b_out <= 3'b111; end end else begin r_out <= 3'b000; g_out <= 3'b000; b_out <= 3'b000; end end vga_sync vga_s( .clk_in(clk_in), .reset(reset), .h_sync(h_sync), .v_sync(v_sync), .h_count(h_count), .v_count(v_count), .display_en(display_en) // '1' => pixel region ); endmodule
Here is the complete pcf :
set_io v_sync 87 set_io h_sync 88 set_io clk_in 64 set_io reset 66 set_io io[0] 1 set_io io[1] 2 set_io io[2] 3 set_io io[3] 4 set_io io[4] 122 set_io io[5] 121 set_io io[6] 120 set_io io[7] 119 set_io io[8] 45 set_io io[9] 47 set_io io[10] 48 set_io io[11] 49 set_io io[12] 28 set_io io[13] 26 set_io io[14] 25 set_io io[15] 24 set_io addr[0] 138 set_io addr[1] 139 set_io addr[2] 141 set_io addr[3] 142 set_io addr[4] 143 set_io addr[5] 8 set_io addr[6] 9 set_io addr[7] 10 set_io addr[8] 11 set_io addr[9] 12 set_io addr[10] 136 set_io addr[11] 135 set_io addr[12] 134 set_io addr[13] 129 set_io addr[14] 128 set_io addr[15] 117 set_io addr[16] 116 set_io addr[17] 115 set_io addr[18] 114 set_io addr[19] 137 set_io addr[20] 113 set_io addr[21] 38 set_io addr[22] 39 set_io addr[23] 41 set_io addr[24] 42 set_io addr[25] 43 set_io addr[26] 52 set_io addr[27] 56 set_io addr[28] 58 set_io addr[29] 60 set_io addr[30] 61 set_io addr[31] 34 set_io addr[32] 33 set_io addr[33] 32 set_io addr[34] 31 set_io addr[35] 29 set_io addr[36] 23 set_io addr[37] 22 set_io addr[38] 21 set_io addr[39] 20 set_io addr[40] 37 set_io addr[41] 19 set_io cs_0 144 set_io cs_1 71 set_io cs_2 44 set_io cs_3 63 set_io we_0 7 set_io we_1 50 set_io rec 62 set_io r_in[0] 107 set_io r_in[1] 106 set_io r_in[2] 105 set_io r_in[3] 104 set_io g_in[0] 97 set_io g_in[1] 96 set_io g_in[2] 95 set_io g_in[3] 112 set_io b_in[0] 102 set_io b_in[1] 101 set_io b_in[2] 99 set_io b_in[3] 98 set_io r_out[0] 81 set_io r_out[1] 80 set_io r_out[2] 79 set_io r_out[3] 78 set_io g_out[0] 94 set_io g_out[1] 93 set_io g_out[2] 91 set_io g_out[3] 90 set_io b_out[0] 76 set_io b_out[1] 75 set_io b_out[2] 74 set_io b_out[3] 73
****
Added the third SRAM and having issues, commented out one new pin at a time and it seems like address 18 (on pin 20) is problematic. When I remove this pin everything else functions well enough for a memory with a missing pin however.
`default_nettype none module vga_sync_test( input wire clk_in, input wire reset, input wire rec, // Direction of io, 1 = set output, 0 = read input //RASPBERRY PI input wire [3:0] r_in, input wire [3:0] b_in, input wire [3:0] g_in, //VGA OUT output reg [3:0] r_out, output reg [3:0] b_out, output reg [3:0] g_out, output wire h_sync, output wire v_sync, //SRAM output reg [20:0] addr, inout wire [7:0] io, // inout must be type wire output wire cs_1, output wire cs_0, output reg we_0 ); wire [7:0] data_in; wire [7:0] data_out; reg toggle; reg [7:0] a, b; assign io = rec ? a : 8'bzzzzzzzz; assign data_out = b; assign data_in[1:0] = r_in[3:2]; assign data_in[3:2] = b_in[3:2]; assign data_in[5:4] = g_in[3:2]; assign data_in[7:6] = 2'b00; wire display_en; wire [11:0] h_count; wire [11:0] v_count; localparam h_pixel_max = 1280; localparam v_pixel_max = 960; localparam h_pixel_half = 640; localparam v_pixel_half = 480; // CS: low to select, high to deselect assign cs_0 = toggle ? 1 : 0; assign cs_1 = toggle ? 0 : 1; //SRAM address counter always @(posedge clk_in) begin if (addr == 0) begin toggle <= toggle+1; end if (reset) begin addr <= 0; end else begin addr <= addr+1; end end //REC control always @(posedge clk_in) begin b <= io; a <= data_in; if (rec) begin we_0 <= addr[0]; //not sure why it isn't the inverse of addr[0] but that doesn't make the inverse on 'scope end else begin we_0 <= 1; end end //VGA COLOR OUT always @(posedge clk_in) begin if (display_en) begin r_out[3:2] <= data_out[1:0]; r_out[1:0] <= data_out[1:0]; g_out[3:2] <= data_out[3:2]; g_out[1:0] <= data_out[3:2]; b_out[3:2] <= data_out[5:4]; b_out[1:0] <= data_out[5:4]; end else begin r_out <= 4'b0000; g_out <= 4'b0000; b_out <= 4'b0000; end end vga_sync vga_s( .clk_in(clk_in), .reset(reset), .h_sync(h_sync), .v_sync(v_sync), .h_count(h_count), .v_count(v_count), .display_en(display_en) // '1' => pixel region ); endmodule
***
I made an animated video of the assembly of the board :
When I make the next board I’ll record the eagle CAD and make another video.
Here are some images of what a new device could look like !
What about an FPGA chip holder ? And a snazzy pivotable SD card holder ? Using meanders this time? With its own fictional programming language like in that assembly video game ? With a mini PCI-E connector somewhere ?
To pre-digest the videos for the FPGA before putting them on a gigantic SD card, I could use ffmpeg to output a less compressed format like .avi or an .mkv and then erase the header and footer ?
Have a name (little Lebowski urban achiever is too long), and a format 100×80 (free Eagle max), and a rough layout (HDMI super close, USB next to FTDI and to flash memory, all the memory to the right equidistant to the chip). AND, some kind of transparent cover, a first for me, make it seem more like a legitimate product and would reduce the risk of short circuits from something conductive falling on the board. For the symbols on the keyboard, there is the Comodore 64 keyboard symbol set which is cool. Or some ancient symbols ??
I am now moving away from SDRAM, challenging and I’m not sure why I’m putting myself through it. The real interest is going to be decoding videos stored on the SD card and putting them on screen. I want to try to do it like the pros – drawing to the screen in the blanking period and using only one SRAM.
****
Still trying to resolve the A18 issue.
- I tried soldering another IO pin to pin 20, but this led to total chaos, none of the address pins working. I could have tried severing the link between the pin 20 and the SRAM but I don’t want to damage the board before I identify the problem (it may be in the code?).
- Trying to lower the size of the address counter to stop at 17 instead of going to 18.
- I connected to the rpi’s DEN and changed the code to work with it instead of the FPGAs display en (it doesn’t work anyways). It is cool as it is more likely to capture the image on the rpi if I understand correctly.
****
I am now trying some of the code I was dreaming about – modifying the input and output video stream based on other parallel data.
Here is one strategy based on two bits of the same color having an impact on recording and playback.
if (b[3]==b[2]) begin
a <= data_in;
end
else begin
a <= 8'b00000000;
end
I have the two memories working (excepted A18 on the second SRAM), and also tried reading DEN from the rpi. It makes some pretty funky glitches by touching the RAM pins with my hands !
Also check out the bitmap to program the FPGA :
`default_nettype none
module vga_sync_test(
input wire clk_in,
input wire reset,
input wire rec, // Direction of io, 1 = set output, 0 = read input
//RASPBERRY PI
input wire [3:0] r_in,
input wire [3:0] b_in,
input wire [3:0] g_in,
input wire DEN, // I SOLDERED A WIRE HERE TO ACCESS DEN ON THE RPI !!
//VGA OUT
output reg [3:0] r_out,
output reg [3:0] b_out,
output reg [3:0] g_out,
output wire h_sync,
output wire v_sync,
//SRAM
output reg [20:0] addr,
output reg [20:0] addr_copy,
inout wire [15:0] io, // inout must be type wire
output wire cs_3,
output wire cs_2,
output wire cs_1,
output wire cs_0,
output reg we_1,
output reg we_0
);
wire [15:0] data_in;
wire [15:0] data_out;
reg [1:0] toggle;
reg [15:0] a, b;
assign io = rec ? a : 16'bzzzzzzzzzzzzzzzz;
assign data_out = b;
assign data_in[1:0] = DEN ? r_in[3:2] : 0; // I SOLDERED A WIRE HERE TO ACCESS DEN ON THE RPI !!
assign data_in[3:2] = DEN ? b_in[3:2] : 0; // I SOLDERED A WIRE HERE TO ACCESS DEN ON THE RPI !!
assign data_in[5:4] = DEN ? g_in[3:2] : 0; // I SOLDERED A WIRE HERE TO ACCESS DEN ON THE RPI !!
assign data_in[7:6] = 2'b00;
assign data_in[9:8] = DEN ? r_in[3:2] : 0;
assign data_in[11:10] = DEN ? b_in[3:2] : 0;
assign data_in[13:12] = DEN ? g_in[3:2] : 0;
assign data_in[15:14] = 2'b00;
wire display_en;
wire [11:0] h_count;
wire [11:0] v_count;
localparam h_pixel_max = 1280;
localparam v_pixel_max = 960;
localparam h_pixel_half = 640;
localparam v_pixel_half = 480;
// CS: low to select, high to deselect
assign cs_0 = toggle == 2'b00 ? 1 : 0;
assign cs_1 = toggle == 2'b01 ? 1 : 0;
assign cs_2 = toggle == 2'b10 ? 1 : 0;
assign cs_3 = toggle == 2'b11 ? 1 : 0;
//SRAM address counter
always @(posedge clk_in) begin
if (addr == 0) begin
toggle <= toggle+1;
end
if (reset) begin
addr <= 0;
end else begin
addr <= addr+1;
addr_copy <= addr_copy+1;
end
end
//REC control
always @(posedge clk_in) begin
b <= io;
a <= data_in;
if (rec) begin
we_0 <= addr[0]; //not sure why it isn't the inverse of addr[0] but that doesn't make the inverse on 'scope
we_1 <= addr[0];
end
else begin
we_0 <= 1;
we_1 <= 1;
end
end
//VGA COLOR OUT
always @(posedge clk_in) begin
if (DEN) begin // I SOLDERED A WIRE HERE TO ACCESS DEN ON THE RPI !!
if (toggle==2'b00 || toggle==2'b01 ) begin
r_out[3:2] <= data_out[1:0];
r_out[1:0] <= data_out[1:0];
g_out[3:2] <= data_out[3:2];
g_out[1:0] <= data_out[3:2];
b_out[3:2] <= data_out[5:4];
b_out[1:0] <= data_out[5:4];
end else begin
r_out[3:2]<= data_out[9:8];
r_out[1:0]<= data_out[9:8];
g_out[3:2]<= data_out[11:10];
g_out[1:0]<= data_out[11:10];
b_out[3:2]<= data_out[13:12];
b_out[1:0]<= data_out[13:12];
end
end else begin
r_out <= 4'b0000;
g_out <= 4'b0000;
b_out <= 4'b0000;
end
end
vga_sync vga_s(
.clk_in(clk_in),
.reset(reset),
.h_sync(h_sync),
.v_sync(v_sync),
.h_count(h_count),
.v_count(v_count),
.display_en(display_en) // '1' => pixel region
);
endmodule
From https://www.fpga4fun.com/VerilogTips.html:
- I need to initialize verilog counters !!
- Another way of selecting 4 bits in a vector : wire [3:0] thisis4also = myvalue[16+:4];
- It might be smarter to use a PLL than to take the external clock directly.
- I need to debounce all input buttons !
****
Check out shaders that take video as input :
https://www.shadertoy.com/view/ctsyzN
https://www.shadertoy.com/view/XtcSRs
****
Tried to make some vignettes of the new board to show the impedence matching :
I think I will order it in white with the meanders exposed like on the VGA spaghettini.
Color options (I’m leaning towards white or maybe green):
***************************
I am now thinking about how I will be able to share all that I am trying to learn about verilog and FPGAs in workshops. The bottleneck is programming the FPGA without special hardware.
OLIMEX has a link to a rasberry pi utility called flashrom that can program and read FLASH memory documented here : https://github.com/OLIMEX/iCE40HX1K-EVB/tree/master/programmer/olimexino-32u4%20firmware
These raspberry pi examples program the ice40 and looks simple :
- https://j-marjanovic.io/lattice-ice40-configuration-using-raspberry-pi.html
- https://github.com/plex1/raspice40
The steps have been automated here : https://notabug.org/sagaracharya/swarajya/src/master/hdl_to_hx8k/on_pi
Also for the rpi Pico : https://github.com/dan-rodrigues/pico-hx
I could knock off the 2232 FTDI and have made something like this: https://shop.trenz-electronic.de/en/TEM0009-02-FPGA-USB-programmer-JTAG-for-development-with-Microchip-FPGAs
Most interestingly, it seems like Arduino Uno could program an FPGA using a library like SPIMemory by Prajwal Bhattaram : https://github.com/Marzogh/SPIMemory/tree/v2.2.0
Finally, I could make a board like the gameduino that has an API for the Arduino to use to talk to the FPGA which handles fast screen drawing. This would mean everything could be done within the Arduino IDE.
EDIT* Come to think of it, even if the FTDI adds cost, it would make everything so much easier for a workshop – one piece of software and no extra hardware to program after for the participants. But when I plug it in it says USB device malfunctioned…(I also realized that I think I can forgo the RS232 connections between the FTDI and the FPGA and just connect it to the EEPROM and FLASH.)
EDIT* The OLIMEX code for Arduino is also not compiling and winIceProgDuino.exe (to program FPGA by sending serial commands to Arduino firmware I suppose) is not opening either.
EDIT*2 The Arduino sketch compiles but only for MEGA, Leonardo, and Micro so far (no UNO or NANO or MINI) but only after removing the iceprog.cpp file.
******************
Some thoughts on low-level versus high-level programming :
Something unsatisfying about making something from a pre-digested meta language (fast.ai for instance). Lost the feeling of ownership of the thing you’re making.
Lots of fun could be had rearranging the keys using the same row heights :
I wonder if I should just transition to making interfaces directly – I am a designer after all !
`default_nettype none // disable implicit definitions by Verilog
//-----------------------------------------------------------------
// minimalDVID_encoder.vhd : A quick and dirty DVI-D implementation
//
// Author: Mike Field <hamster@snap.net.nz>
//
// DVI-D uses TMDS as the 'on the wire' protocol, where each 8-bit
// value is mapped to one or two 10-bit symbols, depending on how
// many 1s or 0s have been sent. This makes it a DC balanced protocol,
// as a correctly implemented stream will have (almost) an equal
// number of 1s and 0s.
//
// Because of this implementation quite complex. By restricting the
// symbols to a subset of eight symbols, all of which having have
// five ones (and therefore five zeros) this complexity drops away
// leaving a simple implementation. Combined with a DDR register to
// send the symbols the complexity is kept very low.
//-----------------------------------------------------------------
module top(
clk100, hdmi_p, hdmi_n
);
input clk100;
output [3:0] hdmi_p;
output [3:0] hdmi_n;
// For holding the outward bound TMDS symbols in the slow and fast domain
reg [9:0] c0_symbol; reg [9:0] c0_high_speed;
reg [9:0] c1_symbol; reg [9:0] c1_high_speed;
reg [9:0] c2_symbol; reg [9:0] c2_high_speed;
reg [9:0] clk_high_speed;
reg [1:0] c2_output_bits;
reg [1:0] c1_output_bits;
reg [1:0] c0_output_bits;
reg [1:0] clk_output_bits;
wire clk_x5;
reg [2:0] latch_high_speed = 3'b100; // Controlling the transfers into the high speed domain
wire vsync, hsync;
wire [1:0] syncs; // To glue the HSYNC and VSYNC into the control character
assign syncs = {vsync, hsync};
// video structure constants
parameter hpixels = 800; // horizontal pixels per line
parameter vlines = 525; // vertical lines per frame
parameter hpulse = 96; // hsync pulse length
parameter vpulse = 2; // vsync pulse length
parameter hbp = 144; // end of horizontal back porch (96 + 48)
parameter hfp = 784; // beginning of horizontal front porch (800 - 16)
parameter vbp = 35; // end of vertical back porch (2 + 33)
parameter vfp = 515; // beginning of vertical front porch (525 - 10)
// registers for storing the horizontal & vertical counters
reg [9:0] vc;
reg [9:0] hc;
// generate sync pulses (active high)
assign vsync = (vc < vpulse);
assign hsync = (hc < hpulse);
always @(posedge clk_x5) begin
//-------------------------------------------------------------
// Now take the 10-bit words and take it into the high-speed
// clock domain once every five cycles.
//
// Then send out two bits every clock cycle using DDR output
// registers.
//-------------------------------------------------------------
c0_output_bits <= c0_high_speed[1:0];
c1_output_bits <= c1_high_speed[1:0];
c2_output_bits <= c2_high_speed[1:0];
clk_output_bits <= clk_high_speed[1:0];
if (latch_high_speed[2]) begin // pixel clock 25MHz
c0_high_speed <= c0_symbol;
c1_high_speed <= c1_symbol;
c2_high_speed <= c2_symbol;
clk_high_speed <= 10'b0000011111;
latch_high_speed <= 3'b000;
if (hc < hpixels)
hc <= hc + 1;
else
begin
hc <= 0;
if (vc < vlines)
vc <= vc + 1;
else
vc <= 0;
end
end
else begin
c0_high_speed <= {2'b00, c0_high_speed[9:2]};
c1_high_speed <= {2'b00, c1_high_speed[9:2]};
c2_high_speed <= {2'b00, c2_high_speed[9:2]};
clk_high_speed <= {2'b00, clk_high_speed[9:2]};
latch_high_speed <= latch_high_speed + 1'b1;
end
end
always @(*) // display 100% saturation colourbars
begin
// first check if we're within vertical active video range
if (vc >= vbp && vc < vfp)
begin
// now display different colours every 80 pixels
// while we're within the active horizontal range
// -----------------
// display white bar
if (hc >= hbp && hc < (hbp+80))
begin
c2_symbol = 10'b1011110000; // red
c1_symbol = 10'b1011110000; // green
c0_symbol = 10'b1011110000; // blue
end
// display yellow bar
else if (hc >= (hbp+80) && hc < (hbp+160))
begin
c2_symbol = 10'b1011110000; // red
c1_symbol = 10'b1011110000; // green
c0_symbol = 10'b0111110000; // blue
end
// display cyan bar
else if (hc >= (hbp+160) && hc < (hbp+240))
begin
c2_symbol = 10'b0111110000; // red
c1_symbol = 10'b1011110000; // green
c0_symbol = 10'b1011110000; // blue
end
// display green bar
else if (hc >= (hbp+240) && hc < (hbp+320))
begin
c2_symbol = 10'b0111110000; // red
c1_symbol = 10'b1011110000; // green
c0_symbol = 10'b0111110000; // blue
end
// display magenta bar
else if (hc >= (hbp+320) && hc < (hbp+400))
begin
c2_symbol = 10'b1011110000; // red
c1_symbol = 10'b0111110000; // green
c0_symbol = 10'b1011110000; // blue
end
// display red bar
else if (hc >= (hbp+400) && hc < (hbp+480))
begin
c2_symbol = 10'b1011110000; // red
c1_symbol = 10'b0111110000; // green
c0_symbol = 10'b0111110000; // blue
end
// display blue bar
else if (hc >= (hbp+480) && hc < (hbp+560))
begin
c2_symbol = 10'b0111110000; // red
c1_symbol = 10'b0111110000; // green
c0_symbol = 10'b1011110000; // blue
end
// display black bar
else if (hc >= (hbp+560) && hc < hfp)
begin
c2_symbol = 10'b0111110000; // red
c1_symbol = 10'b0111110000; // green
c0_symbol = 10'b0111110000; // blue
end
// we're outside active horizontal range
else
begin
c2_symbol = 10'b1101010100; // red
c1_symbol = 10'b1101010100; // green
//---------------------------------------------
// Channel 0 carries the blue pixels, and also
// includes the HSYNC and VSYNCs during
// the CTL (blanking) periods.
//---------------------------------------------
case (syncs)
2'b00 : c0_symbol = 10'b1101010100;
2'b01 : c0_symbol = 10'b0010101011;
2'b10 : c0_symbol = 10'b0101010100;
default : c0_symbol = 10'b1010101011;
endcase
end
end
// we're outside active vertical range
else
begin
c2_symbol = 10'b1101010100; // red
c1_symbol = 10'b1101010100; // green
//---------------------------------------------
// Channel 0 carries the blue pixels, and also
// includes the HSYNC and VSYNCs during
// the CTL (blanking) periods.
//---------------------------------------------
case (syncs)
2'b00 : c0_symbol = 10'b1101010100;
2'b01 : c0_symbol = 10'b0010101011;
2'b10 : c0_symbol = 10'b0101010100;
default : c0_symbol = 10'b1010101011;
endcase
end
end
// red N
defparam hdmin2.PIN_TYPE = 6'b010000;
defparam hdmin2.IO_STANDARD = "SB_LVCMOS";
SB_IO hdmin2 (
.PACKAGE_PIN (hdmi_n[2]),
.CLOCK_ENABLE (1'b1),
.OUTPUT_CLK (clk_x5),
.OUTPUT_ENABLE (1'b1),
.D_OUT_0 (c2_output_bits[1]),
.D_OUT_1 (c2_output_bits[0])
);
// red P
defparam hdmip2.PIN_TYPE = 6'b010000;
defparam hdmip2.IO_STANDARD = "SB_LVCMOS";
SB_IO hdmip2 (
.PACKAGE_PIN (hdmi_p[2]),
.CLOCK_ENABLE (1'b1),
.OUTPUT_CLK (clk_x5),
.OUTPUT_ENABLE (1'b1),
.D_OUT_0 (c2_output_bits[1]),
.D_OUT_1 (c2_output_bits[0])
);
// green N
defparam hdmin1.PIN_TYPE = 6'b010000;
defparam hdmin1.IO_STANDARD = "SB_LVCMOS";
SB_IO hdmin1 (
.PACKAGE_PIN (hdmi_n[1]),
.CLOCK_ENABLE (1'b1),
.OUTPUT_CLK (clk_x5),
.OUTPUT_ENABLE (1'b1),
.D_OUT_0 (c1_output_bits[1]),
.D_OUT_1 (c1_output_bits[0])
);
// green P
defparam hdmip1.PIN_TYPE = 6'b010000;
defparam hdmip1.IO_STANDARD = "SB_LVCMOS";
SB_IO hdmip1 (
.PACKAGE_PIN (hdmi_p[1]),
.CLOCK_ENABLE (1'b1),
.OUTPUT_CLK (clk_x5),
.OUTPUT_ENABLE (1'b1),
.D_OUT_0 (c1_output_bits[1]),
.D_OUT_1 (c1_output_bits[0])
);
// blue N
defparam hdmin0.PIN_TYPE = 6'b010000;
defparam hdmin0.IO_STANDARD = "SB_LVCMOS";
SB_IO hdmin0 (
.PACKAGE_PIN (hdmi_n[0]),
.CLOCK_ENABLE (1'b1),
.OUTPUT_CLK (clk_x5),
.OUTPUT_ENABLE (1'b1),
.D_OUT_0 (c0_output_bits[1]),
.D_OUT_1 (c0_output_bits[0])
);
// blue P
defparam hdmip0.PIN_TYPE = 6'b010000;
defparam hdmip0.IO_STANDARD = "SB_LVCMOS";
SB_IO hdmip0 (
.PACKAGE_PIN (hdmi_p[0]),
.CLOCK_ENABLE (1'b1),
.OUTPUT_CLK (clk_x5),
.OUTPUT_ENABLE (1'b1),
.D_OUT_0 (c0_output_bits[1]),
.D_OUT_1 (c0_output_bits[0])
);
// clock N
defparam hdmin3.PIN_TYPE = 6'b010000;
defparam hdmin3.IO_STANDARD = "SB_LVCMOS";
SB_IO hdmin3 (
.PACKAGE_PIN (hdmi_n[3]),
.CLOCK_ENABLE (1'b1),
.OUTPUT_CLK (clk_x5),
.OUTPUT_ENABLE (1'b1),
.D_OUT_0 (clk_output_bits[1]),
.D_OUT_1 (clk_output_bits[0])
);
// clock P
defparam hdmip3.PIN_TYPE = 6'b010000;
defparam hdmip3.IO_STANDARD = "SB_LVCMOS";
SB_IO hdmip3 (
.PACKAGE_PIN (hdmi_p[3]),
.CLOCK_ENABLE (1'b1),
.OUTPUT_CLK (clk_x5),
.OUTPUT_ENABLE (1'b1),
.D_OUT_0 (clk_output_bits[1]),
.D_OUT_1 (clk_output_bits[0])
);
// D_OUT_0 and D_OUT_1 swapped?
// https://github.com/YosysHQ/yosys/issues/330
SB_PLL40_PAD #(
.FEEDBACK_PATH ("SIMPLE"),
.DIVR (4'b0000),
.DIVF (7'b0001001),
.DIVQ (3'b011),
.FILTER_RANGE (3'b101)
) uut (
.RESETB (1'b1),
.BYPASS (1'b0),
.PACKAGEPIN (clk100),
.PLLOUTGLOBAL (clk_x5) // DVI clock 125MHz
);
endmodule
set_io hdmi_p[0] 139 -io_std SB_LVCMOS
set_io hdmi_p[2] 144 -io_std SB_LVCMOS
set_io hdmi_p[1] 142 -io_std SB_LVCMOS
set_io hdmi_p[3] 137 -io_std SB_LVCMOS
set_io clk100 49
- https://projectf.io/posts/fpga-graphics/
- https://github.com/lawrie/hdmi_examples/tree/master