newStep.v
This commit is contained in:
16
RTL/DEVICES/Buttons.v
Normal file
16
RTL/DEVICES/Buttons.v
Normal file
@@ -0,0 +1,16 @@
|
||||
// femtorv32, a minimalistic RISC-V RV32I core
|
||||
// Bruno Levy, 2020-2021
|
||||
//
|
||||
// This file: driver for the buttons (does nearly nothing,
|
||||
// could include some filtering here).
|
||||
|
||||
module Buttons(
|
||||
input wire sel, // select (read/write ignored if low)
|
||||
output wire [31:0] rdata, // read data
|
||||
|
||||
input wire[5:0] BUTTONS // the six pins wired to the buttons
|
||||
);
|
||||
|
||||
assign rdata = (sel ? {26'b0, BUTTONS} : 32'b0);
|
||||
|
||||
endmodule
|
||||
489
RTL/DEVICES/FGA.v
Normal file
489
RTL/DEVICES/FGA.v
Normal file
@@ -0,0 +1,489 @@
|
||||
// femtorv32, a minimalistic RISC-V RV32I core
|
||||
// Bruno Levy, 2020-2021
|
||||
//
|
||||
// This file: FGA: Femto Graphics Adapter
|
||||
// Note: VRAM is write-only ! (the read port is used by HDMI)
|
||||
//
|
||||
// sel_cntl / io_wstrb / io_rstrb gives access to the set of control
|
||||
// registers and commands:
|
||||
//
|
||||
// Write: set register: value[31:8] REG_XXX[7:0]
|
||||
// command (1 arg): arg24[31:8] 1[7] CMD_XXX[6:0]
|
||||
// command (2 args): arg12_1[31:20] arg12_2[19:8] 1[7] CMD_XXX[6:0]
|
||||
//
|
||||
// Read: the value of the register indicated by REG_READREGID
|
||||
//
|
||||
// Registers:
|
||||
// REG_STATUS (0): vblank[31] hblank[30] drawarea[29] membusy[28] XXXX[27:24] Y[23:12] X[11:0]
|
||||
// RESOLUTION (1): height[23:12] width[11:0]
|
||||
// COLORMODE (2): colormapped[3] bpp[2:0] (0:1bpp 1:2bpp 2:4bpp 3:8bpp 4:16bpp)
|
||||
// DISPLAYMODE (3): magnify[0]
|
||||
// ORIGIN (4): origin_pixel_address[23:0] (first scanline starts at this pixel address)
|
||||
// WRAP (5): wrap_pixel_address[23:0] (restart at pixel address 0 when reached)
|
||||
// READREGID (6): mapped_regid[2:0] (the register mapped for read access)
|
||||
//
|
||||
// Commands:
|
||||
// SET_PALETTE_R (1) arg12_1: cmap entry arg12_2: R
|
||||
// SET_PALETTE_G (2) arg12_1: cmap entry arg12_2: G
|
||||
// SET_PALETTE_B (3) arg12_1: cmap entry arg12_2: B
|
||||
// SET_WWINDOW_X (4) arg12_1: x1 arg12_2: x2
|
||||
// SET_WWINDOW_Y (5) arg12_1: y1 arg12_2: y2
|
||||
// FILLRECT (6) arg24: color
|
||||
//
|
||||
// The window [x1-x2] [y1-y2] can be used in two different ways:
|
||||
// - FILLRECT fills it with the specified color. Operation is
|
||||
// complete when membusy goes low in REG_STATUS.
|
||||
// - individual pixel values can be specified one by one by
|
||||
// writing to the DAT mapped IO (io_wstrb + sel_dat), pixel
|
||||
// address is incremented automatically.
|
||||
// This allows emulation of SSD1331/SSD1351 "window write"
|
||||
// command in the three modes for OLED-HDMI mirroring
|
||||
//
|
||||
// See FIRMWARE/LIBFEMTOGL/FGA.h, FGA.c and FGA_mode.c
|
||||
|
||||
// "Physical mode" sent to the HDMI (choose one of them)
|
||||
// Note: > 640x480 may make timings fail
|
||||
//`define MODE_640x480
|
||||
`define MODE_800x600
|
||||
//`define MODE_1024x768
|
||||
//`define MODE_1280x1024
|
||||
|
||||
`include "GFX_hdmi.v"
|
||||
|
||||
module FGA(
|
||||
input wire pclk, // board clock
|
||||
input wire clk, // system clock
|
||||
input wire sel, // if zero, writes are ignored
|
||||
input wire [3:0] mem_wmask, // mem write mask and strobe
|
||||
input wire [16:0] mem_address, // address in graphic memory (128K), word-aligned
|
||||
input wire [31:0] mem_wdata, // data to be written
|
||||
|
||||
output wire [3:0] gpdi_dp, // HDMI signals, blue, green, red, clock
|
||||
// dgpi_dn generated by pins (see ulx3s.lpf)
|
||||
|
||||
input wire io_wstrb,
|
||||
input wire io_rstrb,
|
||||
input wire sel_cntl, // IO: select control register (RW)
|
||||
input wire sel_dat, // IO: select data input (W)
|
||||
output wire [31:0] rdata // data read
|
||||
);
|
||||
|
||||
`include "GFX_modes.v"
|
||||
|
||||
wire pixel_clk;
|
||||
|
||||
reg [31:0] VRAM[0:32767];
|
||||
reg [23:0] PALETTE[0:255];
|
||||
|
||||
/************************* HDMI signal generation ***************************/
|
||||
|
||||
// Video mode parameters
|
||||
localparam MODE_1bpp = 3'd0;
|
||||
localparam MODE_2bpp = 3'd1;
|
||||
localparam MODE_4bpp = 3'd2;
|
||||
localparam MODE_8bpp = 3'd3;
|
||||
localparam MODE_16bpp = 3'd4;
|
||||
|
||||
reg [11:0] mode_width;
|
||||
reg [11:0] mode_height;
|
||||
reg [2:0] mode_bpp; // see MODE_xbpp constants
|
||||
reg mode_colormapped;
|
||||
reg mode_magnify; // asserted for pixel doubling
|
||||
reg [23:0] mode_origin_pix_address;
|
||||
reg [23:0] mode_wrap_pix_address;
|
||||
|
||||
// This part is just like a VGA generator.
|
||||
reg [11:0] X, Y; // current pixel coordinates
|
||||
reg hsync, vsync; // horizontal and vertical synchronization
|
||||
reg draw_area; // asserted if current pixel is in drawing area
|
||||
reg mem_busy; // asserted if memory transfer is running.
|
||||
|
||||
// Data read from control register
|
||||
reg [31:0] read_reg;
|
||||
assign rdata = (io_rstrb && sel_cntl) ? read_reg : 32'b0;
|
||||
|
||||
// We are going to fetch data from video RAM (now stored in BRAM), and then,
|
||||
// in colormapped modes, fetch colormap entry. Each fetch introduces some
|
||||
// latency -> there is a small pixel pipeline. Each stage needs to have
|
||||
// its own copy of all registers it needs (that is, copy pixel address
|
||||
// between stage 1 and stage 2 to keep it in sync with pixel data).
|
||||
//
|
||||
// Stage 0 generates the X,Y coordinates and horizontal,vertical sync signals
|
||||
// (standard in all VGA/DVI/HDMI drivers)
|
||||
// Stage 1 generates the pixel address. The unit is in number of pixels.
|
||||
// it handles pixel doubling/scanline doubling in 320x200 resolutions
|
||||
// it also handles page flipping, with the ORIGIN register.
|
||||
// Stage 2 fetches pixel data from RAM. It handles pixel address -> word address
|
||||
// translation. It creates its own copy of pixel_address to keep it in
|
||||
// sync with pixel data (1 clock latency)
|
||||
// Stage 3 generates R,G,B either from colormap lookup (mode 1 and 2) or from
|
||||
// 16 bit pixel data directly (mode 0). If colormap lookup is used,
|
||||
// it generates an additional cycle of latency.
|
||||
//
|
||||
// Note: the first two pixel columns are wrong due to latency (the image is
|
||||
// shifted two pixels to the right, with garbage in the first two columns),
|
||||
// normally we should start fetching from the previous scanline, at the end
|
||||
// of hsync, 1 clock in advance in mode 0, and two clocks in advance in mode 1.
|
||||
// I was too lazy to do that, so I just hide the first two columns !
|
||||
// (so there are two columns missing on the right side of the image).
|
||||
// I will do that properly when VRAM will be stored in SDRAM (then I'll have no
|
||||
// choice, latency will probably be significantly larger than 2 pixels).
|
||||
|
||||
// Stage 0: X,Y,vsync,hsync generation
|
||||
always @(posedge pixel_clk) begin
|
||||
if(X == GFX_line_width-1) begin
|
||||
X <= 0;
|
||||
Y <= (Y == GFX_lines-1) ? 0 : Y+1;
|
||||
end else begin
|
||||
X <= X+1;
|
||||
end
|
||||
hsync <= (X>=GFX_width+GFX_h_front_porch) &&
|
||||
(X<GFX_width+GFX_h_front_porch+GFX_h_sync_width);
|
||||
vsync <= (Y>=GFX_height+GFX_v_front_porch) &&
|
||||
(Y<GFX_height+GFX_v_front_porch+GFX_v_sync_width);
|
||||
draw_area <= (X<GFX_width) && (Y<GFX_height);
|
||||
end
|
||||
|
||||
// Stage 1: pixel address generation
|
||||
reg [23:0] pix_address;
|
||||
reg [23:0] row_start_pix_address;
|
||||
|
||||
wire [23:0] next_row_start_pix_address =
|
||||
((row_start_pix_address + {12'b0, mode_width}) <= mode_wrap_pix_address) ?
|
||||
row_start_pix_address + {12'b0, mode_width} : 0 ;
|
||||
|
||||
// Generate pixel address based on scanning coordinates (X,Y) and
|
||||
// magnify mode (that doubles the rows and doubles the pixels in
|
||||
// the rows).
|
||||
always @(posedge pixel_clk) begin
|
||||
if(X == 0) begin
|
||||
if(Y == 0) begin
|
||||
row_start_pix_address <= mode_origin_pix_address;
|
||||
pix_address <= mode_origin_pix_address;
|
||||
end else begin
|
||||
// Increment row address every 2 Y (2 because magnify)
|
||||
if(Y[0] || !mode_magnify) begin
|
||||
row_start_pix_address <= next_row_start_pix_address;
|
||||
pix_address <= next_row_start_pix_address;
|
||||
end else begin
|
||||
pix_address <= row_start_pix_address;
|
||||
end
|
||||
end
|
||||
end else begin
|
||||
if(X[0] || !mode_magnify) pix_address <= pix_address + 1;
|
||||
end
|
||||
end
|
||||
|
||||
// Stage 2: pixel data fetch
|
||||
reg [23:0] word_address;
|
||||
always @(*) begin
|
||||
case(mode_bpp)
|
||||
MODE_16bpp: word_address = pix_address >> 1;
|
||||
MODE_8bpp: word_address = pix_address >> 2;
|
||||
MODE_4bpp: word_address = pix_address >> 3;
|
||||
MODE_2bpp: word_address = pix_address >> 4;
|
||||
MODE_1bpp: word_address = pix_address >> 5;
|
||||
default: word_address = 0;
|
||||
endcase
|
||||
end
|
||||
reg [23:0] pix_address_2;
|
||||
reg [31:0] pix_word_data_2;
|
||||
always @(posedge pixel_clk) begin
|
||||
pix_address_2 <= pix_address;
|
||||
pix_word_data_2 <= VRAM[word_address[14:0]]; // TODO
|
||||
end
|
||||
|
||||
// Stage 3: generate R,G,B from pixel data
|
||||
|
||||
// combinatorial circuit to extract index from
|
||||
// pixel data.
|
||||
reg [7:0] pix_color_index_3;
|
||||
/* verilator lint_off WIDTH */
|
||||
always @(*) begin
|
||||
case(mode_bpp)
|
||||
MODE_8bpp: begin
|
||||
pix_color_index_3 = pix_word_data_2 >> {pix_address_2[1:0], 3'b0};
|
||||
end
|
||||
MODE_4bpp: begin
|
||||
pix_color_index_3[3:0] = pix_word_data_2 >> {pix_address_2[2:0], 2'b0};
|
||||
pix_color_index_3[7:4] = 4'b0;
|
||||
end
|
||||
MODE_2bpp: begin
|
||||
pix_color_index_3[1:0] = pix_word_data_2 >> {pix_address_2[3:0], 1'b0};
|
||||
pix_color_index_3[7:2] = 6'b0;
|
||||
end
|
||||
MODE_1bpp: begin
|
||||
pix_color_index_3[0] = pix_word_data_2 >> pix_address_2[4:0];
|
||||
pix_color_index_3[7:1] = 7'b0;
|
||||
end
|
||||
default: begin
|
||||
pix_color_index_3 = 0;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
/* verilator lint_on WIDTH */
|
||||
|
||||
reg [11:0] maxX;
|
||||
reg [11:0] maxY;
|
||||
|
||||
always @(posedge clk) begin
|
||||
maxX <= mode_magnify ? (mode_width << 1) : mode_width;
|
||||
maxY <= mode_magnify ? (mode_height << 1) : mode_height;
|
||||
end
|
||||
|
||||
reg [7:0] R,G,B;
|
||||
always @(posedge pixel_clk) begin
|
||||
if(mode_colormapped) begin
|
||||
{R,G,B} <= PALETTE[pix_color_index_3];
|
||||
end else begin
|
||||
if(pix_address_2[0]) begin
|
||||
R <= {pix_word_data_2[31:27],3'b000};
|
||||
G <= {pix_word_data_2[26:21],2'b00 };
|
||||
B <= {pix_word_data_2[20:16],3'b000};
|
||||
end else begin
|
||||
R <= {pix_word_data_2[15:11],3'b000};
|
||||
G <= {pix_word_data_2[10:5 ],2'b00 };
|
||||
B <= {pix_word_data_2[ 4:0 ],3'b000};
|
||||
end
|
||||
end
|
||||
// Hide what's outside the display zone.
|
||||
// Hide the first two columns (I was too lazy to properly handle my
|
||||
// pixel pipeline latency).
|
||||
if(X == 0 || X == 1 || X >= maxX || Y >= maxY) {R,G,B} <= 24'b0;
|
||||
end
|
||||
|
||||
// Video signal generation and HDMI
|
||||
|
||||
wire pixel_clk_x5; // The pixel_clk*5 freq clock used by the serializers (DDR)
|
||||
|
||||
// The graphic PLL, that generates the pixel clock (and freq*5 clock)
|
||||
GFX_PLL gfx_pll(
|
||||
.pclk(pclk),
|
||||
.pixel_clk(pixel_clk),
|
||||
.pixel_clk_x5(pixel_clk_x5)
|
||||
);
|
||||
|
||||
// The HDMI encoder
|
||||
GFX_hdmi hdmi(
|
||||
.pixel_clk(pixel_clk), .pixel_clk_x5(pixel_clk_x5),
|
||||
.R(R), .G(G), .B(B), .hsync(hsync), .vsync(vsync), .draw_area(draw_area),
|
||||
.gpdi_dp(gpdi_dp)
|
||||
);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
wire is_command = mem_wdata[7];
|
||||
wire [2:0] command = mem_wdata[2:0];
|
||||
wire [2:0] set_regid = mem_wdata[2:0];
|
||||
wire[23:0] arg24 = mem_wdata[31:8];
|
||||
wire[11:0] arg12_1 = mem_wdata[19:8];
|
||||
wire[11:0] arg12_2 = mem_wdata[31:20];
|
||||
|
||||
localparam REG_STATUS = 3'd0;
|
||||
localparam REG_RESOLUTION = 3'd1;
|
||||
localparam REG_COLORMODE = 3'd2;
|
||||
localparam REG_DISPLAYMODE = 3'd3;
|
||||
localparam REG_ORIGIN = 3'd4;
|
||||
localparam REG_WRAP = 3'd5;
|
||||
localparam REG_READREGID = 3'd6;
|
||||
|
||||
localparam CMD_SET_PALETTE_R = 3'd1;
|
||||
localparam CMD_SET_PALETTE_G = 3'd2;
|
||||
localparam CMD_SET_PALETTE_B = 3'd3;
|
||||
localparam CMD_SET_WWINDOW_X = 3'd4;
|
||||
localparam CMD_SET_WWINDOW_Y = 3'd5;
|
||||
localparam CMD_FILLRECT = 3'd6;
|
||||
|
||||
// Windowed-pixel write and fillrect command.
|
||||
//
|
||||
// - write window command, two commands:
|
||||
// (send 32 bits to IO_FGA_CNTL hardware register)
|
||||
// SET_WWINDOW_X: X1 X2
|
||||
// SET_WWINDOW_Y: Y1 Y2
|
||||
//
|
||||
// - write data: send 16 bits to IO_FGA_DAT hardware register
|
||||
// MSB first, encoding follows SSD1351: RRRRR GGGGG 0 BBBBB
|
||||
//
|
||||
// Note that once the window is properly initialized, the write
|
||||
// data command emulates the SSD1351 OLED display, then by writing
|
||||
// to both FGA and SSD1351 control registers, one clones the output
|
||||
// of the SSD1351 oled display to the HDMI screen for free !
|
||||
//
|
||||
// See in <femtorv32.h>:
|
||||
// #define IO_GFX_DAT (IO_SSD1351_DAT16 | IO_FGA_DAT)
|
||||
// #define OLED_WRITE_DATA_UINT16(RGB) IO_OUT(IO_GFX_DAT,(RGB))
|
||||
// #define OLED_WRITE_DATA_RGB(R,G,B) OLED_WRITE_DATA_UINT16(GL_RGB(R,G,B))
|
||||
//
|
||||
// This also works when FGA is in paletted mode (320x200x8bpp, 640x400x4bpp)
|
||||
// since the write data command properly interprets pixel addresses. The
|
||||
// only requirement is to have a palette that will correctly map the 8 LSBs
|
||||
// / 4 LSBs of pixel data to a color. In libfemtorv32, this maps 0 to black
|
||||
// and any non-zero to white (this is how COMMANDER is displayed in 640x400
|
||||
// on the HDMI screen).
|
||||
//
|
||||
// To generate pixel data, there are two other options:
|
||||
// - directly writing to VRAM from FemtoRV32
|
||||
// - FILLRECT (see below)
|
||||
|
||||
|
||||
reg [11:0] window_x1, window_x2, window_y1, window_y2, window_x, window_y;
|
||||
reg [23:0] window_row_start;
|
||||
reg [23:0] window_pixel_address;
|
||||
reg [15:0] fill_color;
|
||||
reg fill_rect;
|
||||
|
||||
// Data read from control register: depends on mapped register (read_regid)
|
||||
reg [2:0] read_regid;
|
||||
always @(posedge clk) begin
|
||||
case(read_regid)
|
||||
REG_RESOLUTION: read_reg <= {8'b0, mode_height, mode_width};
|
||||
REG_COLORMODE: read_reg <= {28'b0, mode_colormapped, mode_bpp};
|
||||
REG_DISPLAYMODE: read_reg <= {31'b0, mode_magnify};
|
||||
REG_ORIGIN: read_reg <= {8'b0, mode_origin_pix_address};
|
||||
REG_WRAP: read_reg <= {8'b0, mode_wrap_pix_address};
|
||||
REG_READREGID: read_reg <= {29'b0, read_regid};
|
||||
default: read_reg <= {(Y >= 400),(X >= 640),draw_area,mem_busy,4'b0,X,Y};
|
||||
endcase
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
if(mem_busy && ((io_wstrb && sel_dat) || fill_rect)) begin
|
||||
window_pixel_address <= window_pixel_address + 1;
|
||||
window_x <= window_x + 1;
|
||||
if(window_x == window_x2) begin
|
||||
if(window_y == window_y2) begin
|
||||
mem_busy <= 1'b0;
|
||||
fill_rect <= 1'b0;
|
||||
end else begin
|
||||
window_y <= window_y+1;
|
||||
window_x <= window_x1;
|
||||
window_pixel_address <= window_row_start + {12'b0, mode_width};
|
||||
window_row_start <= window_row_start + {12'b0, mode_width};
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if(io_wstrb && sel_cntl) begin
|
||||
if(is_command) begin
|
||||
case(command)
|
||||
CMD_SET_PALETTE_B: PALETTE[arg12_1[7:0]][7:0 ] <= arg12_2[7:0];
|
||||
CMD_SET_PALETTE_G: PALETTE[arg12_1[7:0]][15:8] <= arg12_2[7:0];
|
||||
CMD_SET_PALETTE_R: PALETTE[arg12_1[7:0]][23:16] <= arg12_2[7:0];
|
||||
CMD_SET_WWINDOW_X: begin
|
||||
window_x1 <= arg12_1;
|
||||
window_x2 <= arg12_2;
|
||||
window_x <= arg12_1;
|
||||
mem_busy <= 1'b1;
|
||||
end
|
||||
CMD_SET_WWINDOW_Y: begin
|
||||
window_y1 <= arg12_1;
|
||||
window_y2 <= arg12_2;
|
||||
window_y <= arg12_1;
|
||||
mem_busy <= 1'b1;
|
||||
/* verilator lint_off WIDTH */
|
||||
window_row_start <= arg12_1 * mode_width + window_x1;
|
||||
window_pixel_address <= arg12_1 * mode_width + window_x1;
|
||||
/* verilator lint_on WIDTH */
|
||||
end
|
||||
CMD_FILLRECT: begin
|
||||
fill_rect <= 1'b1;
|
||||
fill_color <= arg24[15:0];
|
||||
end
|
||||
default: begin end
|
||||
endcase
|
||||
end else begin
|
||||
case(set_regid)
|
||||
REG_RESOLUTION: {mode_height, mode_width} <= arg24;
|
||||
REG_COLORMODE: {mode_colormapped, mode_bpp} <= arg24[3:0];
|
||||
REG_DISPLAYMODE: mode_magnify <= arg24[0];
|
||||
REG_READREGID: read_regid <= arg24[2:0];
|
||||
REG_ORIGIN: mode_origin_pix_address <= arg24;
|
||||
REG_WRAP: mode_wrap_pix_address <= arg24;
|
||||
default: begin end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Write to VRAM (FILLRECT and interface with processor)
|
||||
wire [14:0] vram_word_address = mem_address[16:2];
|
||||
wire [15:0] pixel_color = fill_rect ? fill_color : mem_wdata[15:0];
|
||||
|
||||
// FILLRECT:
|
||||
// The fillrect command repeatedly sends the same pixel data to the current
|
||||
// window. It has two advantages as compared to do that by hand:
|
||||
// - fills one pixel per clock (whereas in its fastest configuration,
|
||||
// FemtoRV32 uses 6 clocks per loop iteration)
|
||||
// - execution can continue, which lets FemtoRV prepare the next drawing
|
||||
// operation. Before sending more data to FGA, FemtoRV needs to test
|
||||
// the FGA_BUSY_bit in the control register, as follows:
|
||||
// while(IO_IN(IO_FGA_CNTL) & FGA_BUSY_bit);
|
||||
// This is used in LIBFEMTORV32/FGA.c, to implement hardware-accelerated
|
||||
// polygon fill (using one FILLRECT call per polygon scanline).
|
||||
|
||||
always @(posedge clk) begin
|
||||
// FILLRECT or pixel data sent to the graphic data port
|
||||
if(fill_rect || (io_wstrb && sel_dat && mem_busy)) begin
|
||||
/* verilator lint_off CASEINCOMPLETE */
|
||||
case(mode_bpp)
|
||||
MODE_16bpp: begin
|
||||
case(window_pixel_address[0])
|
||||
1'b0: VRAM[window_pixel_address[15:1]][15:0 ] <= pixel_color;
|
||||
1'b1: VRAM[window_pixel_address[15:1]][31:16] <= pixel_color;
|
||||
endcase
|
||||
end
|
||||
MODE_8bpp: begin
|
||||
case(window_pixel_address[1:0])
|
||||
2'b00: VRAM[window_pixel_address[16:2]][ 7:0 ] <= pixel_color[7:0];
|
||||
2'b01: VRAM[window_pixel_address[16:2]][15:8 ] <= pixel_color[7:0];
|
||||
2'b10: VRAM[window_pixel_address[16:2]][23:16] <= pixel_color[7:0];
|
||||
2'b11: VRAM[window_pixel_address[16:2]][31:24] <= pixel_color[7:0];
|
||||
endcase
|
||||
end
|
||||
MODE_4bpp: begin
|
||||
case(window_pixel_address[2:0])
|
||||
3'b000: VRAM[window_pixel_address[17:3]][ 3:0 ] <= pixel_color[3:0];
|
||||
3'b001: VRAM[window_pixel_address[17:3]][ 7:4 ] <= pixel_color[3:0];
|
||||
3'b010: VRAM[window_pixel_address[17:3]][11:8 ] <= pixel_color[3:0];
|
||||
3'b011: VRAM[window_pixel_address[17:3]][15:12] <= pixel_color[3:0];
|
||||
3'b100: VRAM[window_pixel_address[17:3]][19:16] <= pixel_color[3:0];
|
||||
3'b101: VRAM[window_pixel_address[17:3]][23:20] <= pixel_color[3:0];
|
||||
3'b110: VRAM[window_pixel_address[17:3]][27:24] <= pixel_color[3:0];
|
||||
3'b111: VRAM[window_pixel_address[17:3]][31:28] <= pixel_color[3:0];
|
||||
endcase
|
||||
end
|
||||
MODE_2bpp: begin
|
||||
case(window_pixel_address[3:0])
|
||||
4'b0000: VRAM[window_pixel_address[18:4]][ 1:0 ] <= pixel_color[1:0];
|
||||
4'b0001: VRAM[window_pixel_address[18:4]][ 3:2 ] <= pixel_color[1:0];
|
||||
4'b0010: VRAM[window_pixel_address[18:4]][ 5:4 ] <= pixel_color[1:0];
|
||||
4'b0011: VRAM[window_pixel_address[18:4]][ 7:6 ] <= pixel_color[1:0];
|
||||
4'b0100: VRAM[window_pixel_address[18:4]][ 9:8 ] <= pixel_color[1:0];
|
||||
4'b0101: VRAM[window_pixel_address[18:4]][11:10] <= pixel_color[1:0];
|
||||
4'b0110: VRAM[window_pixel_address[18:4]][13:12] <= pixel_color[1:0];
|
||||
4'b0111: VRAM[window_pixel_address[18:4]][15:14] <= pixel_color[1:0];
|
||||
4'b1000: VRAM[window_pixel_address[18:4]][17:16] <= pixel_color[1:0];
|
||||
4'b1001: VRAM[window_pixel_address[18:4]][19:18] <= pixel_color[1:0];
|
||||
4'b1010: VRAM[window_pixel_address[18:4]][21:20] <= pixel_color[1:0];
|
||||
4'b1011: VRAM[window_pixel_address[18:4]][23:22] <= pixel_color[1:0];
|
||||
4'b1100: VRAM[window_pixel_address[18:4]][25:24] <= pixel_color[1:0];
|
||||
4'b1101: VRAM[window_pixel_address[18:4]][27:26] <= pixel_color[1:0];
|
||||
4'b1110: VRAM[window_pixel_address[18:4]][29:28] <= pixel_color[1:0];
|
||||
4'b1111: VRAM[window_pixel_address[18:4]][31:30] <= pixel_color[1:0];
|
||||
endcase
|
||||
end
|
||||
default: begin // 1bpp
|
||||
VRAM[window_pixel_address[19:5]][window_pixel_address[4:0]] <= pixel_color[0];
|
||||
end
|
||||
endcase
|
||||
/* verilator lint_on CASEINCOMPLETE */
|
||||
end else if(sel && !mem_busy) begin // Direct VRAM write from FemtoRV32
|
||||
if(mem_wmask[0]) VRAM[vram_word_address][ 7:0 ] <= mem_wdata[ 7:0 ];
|
||||
if(mem_wmask[1]) VRAM[vram_word_address][15:8 ] <= mem_wdata[15:8 ];
|
||||
if(mem_wmask[2]) VRAM[vram_word_address][23:16] <= mem_wdata[23:16];
|
||||
if(mem_wmask[3]) VRAM[vram_word_address][31:24] <= mem_wdata[31:24];
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
154
RTL/DEVICES/GFX_hdmi.v
Normal file
154
RTL/DEVICES/GFX_hdmi.v
Normal file
@@ -0,0 +1,154 @@
|
||||
|
||||
// Define one of:
|
||||
// MODE_640x480, MODE_800x600, MODE_1024x768, MODE_1280x1024.
|
||||
// ("physical mode" sent to the HDMI)
|
||||
|
||||
`include "TMDS_encoder.v"
|
||||
|
||||
// Generate HDMI signal from VGA signal
|
||||
module GFX_hdmi(
|
||||
input wire pixel_clk, // pixel clock
|
||||
input wire pixel_clk_x5, // 5 times pixel clock freq (used by TMDS serializer)
|
||||
// The TMDS serializers operate at (pixel_clock_freq * 10),
|
||||
// but we use DDR mode, hence (pixel_clock_freq * 5).
|
||||
input wire [7:0] R,
|
||||
input wire [7:0] G,
|
||||
input wire [7:0] B,
|
||||
input wire hsync,
|
||||
input wire vsync,
|
||||
input wire draw_area,
|
||||
|
||||
output wire [3:0] gpdi_dp // HDMI signals, blue, green, red, clock
|
||||
// dgpi_dn generated by pins (see, e.g., ulx3s.lpf)
|
||||
);
|
||||
|
||||
// RGB TMDS encoding
|
||||
// Generate 10-bits TMDS red,green,blue signals. Blue embeds HSync/VSync in its
|
||||
// control part.
|
||||
wire [9:0] TMDS_R, TMDS_G, TMDS_B;
|
||||
TMDS_encoder encode_R(.clk(pixel_clk), .VD(R), .CD(2'b00) , .VDE(draw_area), .TMDS(TMDS_R));
|
||||
TMDS_encoder encode_G(.clk(pixel_clk), .VD(G), .CD(2'b00) , .VDE(draw_area), .TMDS(TMDS_G));
|
||||
TMDS_encoder encode_B(.clk(pixel_clk), .VD(B), .CD({vsync,hsync}), .VDE(draw_area), .TMDS(TMDS_B));
|
||||
|
||||
// Modulo-5 clock divider.
|
||||
reg [4:0] TMDS_mod5=1;
|
||||
wire TMDS_shift_load = TMDS_mod5[4];
|
||||
always @(posedge pixel_clk_x5) TMDS_mod5 <= {TMDS_mod5[3:0],TMDS_mod5[4]};
|
||||
|
||||
// Shifters
|
||||
// Every 5 clocks, we get a fresh R,G,B triplet from the TMDS encoders,
|
||||
// else we shift.
|
||||
reg [9:0] TMDS_shift_R=0, TMDS_shift_G=0, TMDS_shift_B=0;
|
||||
always @(posedge pixel_clk_x5) begin
|
||||
TMDS_shift_R <= TMDS_shift_load ? TMDS_R : {2'b00,TMDS_shift_R[9:2]};
|
||||
TMDS_shift_G <= TMDS_shift_load ? TMDS_G : {2'b00,TMDS_shift_G[9:2]};
|
||||
TMDS_shift_B <= TMDS_shift_load ? TMDS_B : {2'b00,TMDS_shift_B[9:2]};
|
||||
end
|
||||
|
||||
// DDR serializers: they send D0 at the rising edge and D1 at the falling edge.
|
||||
`ifndef BENCH_OR_LINT
|
||||
`ifdef ULX3S
|
||||
ODDRX1F ddr_R (.D0(TMDS_shift_R[0]), .D1(TMDS_shift_R[1]), .Q(gpdi_dp[2]), .SCLK(pixel_clk_x5), .RST(1'b0));
|
||||
ODDRX1F ddr_G (.D0(TMDS_shift_G[0]), .D1(TMDS_shift_G[1]), .Q(gpdi_dp[1]), .SCLK(pixel_clk_x5), .RST(1'b0));
|
||||
ODDRX1F ddr_B (.D0(TMDS_shift_B[0]), .D1(TMDS_shift_B[1]), .Q(gpdi_dp[0]), .SCLK(pixel_clk_x5), .RST(1'b0));
|
||||
`endif
|
||||
`endif
|
||||
|
||||
// The pixel clock is sent through the fourth differential pair.
|
||||
assign gpdi_dp[3] = pixel_clk;
|
||||
|
||||
endmodule
|
||||
|
||||
/**************************************************************************************/
|
||||
|
||||
`ifdef BENCH_OR_LINT
|
||||
|
||||
module GFX_PLL(
|
||||
input wire pclk, // the board's clock
|
||||
output wire pixel_clk, // pixel clock
|
||||
output wire pixel_clk_x5 // 5 times pixel clock freq (used by TMDS serializer)
|
||||
);
|
||||
assign pixel_clk = pclk;
|
||||
assign pixel_clk_x5 = pclk;
|
||||
endmodule
|
||||
|
||||
`else
|
||||
|
||||
`ifdef ULX3S
|
||||
|
||||
module GFX_PLL(
|
||||
input wire pclk, // the board's clock
|
||||
output wire pixel_clk, // pixel clock
|
||||
output wire pixel_clk_x5 // 5 times pixel clock freq (used by TMDS serializer)
|
||||
// The TMDS serializers operate at (pixel_clock_freq * 10),
|
||||
// but we use DDR mode, hence (pixel_clock_freq * 5).
|
||||
);
|
||||
|
||||
// The parameters of the PLL,
|
||||
// They are found by using: ecppll -i 25 -o <5*pixel_clock> -f foobar.v
|
||||
|
||||
`ifdef MODE_640x480
|
||||
localparam CLKI_DIV = 1;
|
||||
localparam CLKOP_DIV = 5;
|
||||
localparam CLKOP_CPHASE = 2;
|
||||
localparam CLKOP_FPHASE = 0;
|
||||
localparam CLKFB_DIV = 5;
|
||||
`endif
|
||||
|
||||
`ifdef MODE_800x600
|
||||
localparam CLKI_DIV = 1;
|
||||
localparam CLKOP_DIV = 3;
|
||||
localparam CLKOP_CPHASE = 1;
|
||||
localparam CLKOP_FPHASE = 0;
|
||||
localparam CLKFB_DIV = 8;
|
||||
`endif
|
||||
|
||||
`ifdef MODE_1024x768
|
||||
localparam CLKI_DIV = 1;
|
||||
localparam CLKOP_DIV = 2;
|
||||
localparam CLKOP_CPHASE = 1;
|
||||
localparam CLKOP_FPHASE = 0;
|
||||
localparam CLKFB_DIV = 13;
|
||||
`endif
|
||||
|
||||
`ifdef MODE_1280x1024
|
||||
localparam CLKI_DIV = 3;
|
||||
localparam CLKOP_DIV = 1;
|
||||
localparam CLKOP_CPHASE = 0;
|
||||
localparam CLKOP_FPHASE = 0;
|
||||
localparam CLKFB_DIV = 65;
|
||||
`endif
|
||||
|
||||
// The PLL converts a 25 MHz clock into a (pixel_clock_freq * 5) clock
|
||||
// The (half) TMDS serializer clock is generated on pin CLKOP.
|
||||
// In addition, the pixel clock (at TMDS freq/5) is generated on
|
||||
// pin CLKOS (hence CLKOS_DIV = 5*CLKOP_DIV).
|
||||
(* ICP_CURRENT="12" *) (* LPF_RESISTOR="8" *) (* MFG_ENABLE_FILTEROPAMP="1" *) (* MFG_GMCREF_SEL="2" *)
|
||||
EHXPLLL #(
|
||||
.CLKI_DIV(CLKI_DIV),
|
||||
.CLKOP_DIV(CLKOP_DIV),
|
||||
.CLKOP_CPHASE(CLKOP_CPHASE),
|
||||
.CLKOP_FPHASE(CLKOP_FPHASE),
|
||||
.CLKOS_ENABLE("ENABLED"),
|
||||
.CLKOS_DIV(5*CLKOP_DIV),
|
||||
.CLKOS_CPHASE(CLKOP_CPHASE),
|
||||
.CLKOS_FPHASE(CLKOP_FPHASE),
|
||||
.CLKFB_DIV(CLKFB_DIV)
|
||||
) pll_i (
|
||||
.CLKI(pclk),
|
||||
.CLKOP(pixel_clk_x5),
|
||||
.CLKFB(pixel_clk_x5),
|
||||
.CLKOS(pixel_clk),
|
||||
.PHASESEL0(1'b0),
|
||||
.PHASESEL1(1'b0),
|
||||
.PHASEDIR(1'b1),
|
||||
.PHASESTEP(1'b1),
|
||||
.PHASELOADREG(1'b1),
|
||||
.PLLWAKESYNC(1'b0),
|
||||
.ENCLKOP(1'b0)
|
||||
);
|
||||
|
||||
endmodule
|
||||
|
||||
`endif
|
||||
`endif
|
||||
57
RTL/DEVICES/GFX_modes.v
Normal file
57
RTL/DEVICES/GFX_modes.v
Normal file
@@ -0,0 +1,57 @@
|
||||
|
||||
// Define one of:
|
||||
// MODE_640x480, MODE_800x600, MODE_1024x768, MODE_1280x1024.
|
||||
|
||||
/********************** Modes ****************************/
|
||||
|
||||
`ifdef MODE_640x480
|
||||
localparam GFX_pixel_clock = 25;
|
||||
localparam GFX_width = 640;
|
||||
localparam GFX_height = 480;
|
||||
localparam GFX_h_front_porch = 16;
|
||||
localparam GFX_h_sync_width = 96;
|
||||
localparam GFX_h_back_porch = 48;
|
||||
localparam GFX_v_front_porch = 10;
|
||||
localparam GFX_v_sync_width = 2;
|
||||
localparam GFX_v_back_porch = 32;
|
||||
`endif
|
||||
|
||||
`ifdef MODE_800x600
|
||||
localparam GFX_pixel_clock = 40;
|
||||
localparam GFX_width = 800;
|
||||
localparam GFX_height = 600;
|
||||
localparam GFX_h_front_porch = 40;
|
||||
localparam GFX_h_sync_width = 128;
|
||||
localparam GFX_h_back_porch = 88;
|
||||
localparam GFX_v_front_porch = 1;
|
||||
localparam GFX_v_sync_width = 4;
|
||||
localparam GFX_v_back_porch = 23;
|
||||
`endif
|
||||
|
||||
`ifdef MODE_1024x768
|
||||
localparam GFX_pixel_clock = 65;
|
||||
localparam GFX_width = 1024;
|
||||
localparam GFX_height = 768;
|
||||
localparam GFX_h_front_porch = 24;
|
||||
localparam GFX_h_sync_width = 136;
|
||||
localparam GFX_h_back_porch = 160;
|
||||
localparam GFX_v_front_porch = 3;
|
||||
localparam GFX_v_sync_width = 6;
|
||||
localparam GFX_v_back_porch = 29;
|
||||
|
||||
`endif
|
||||
|
||||
`ifdef MODE_1280x1024
|
||||
localparam GFX_pixel_clock = 108;
|
||||
localparam GFX_width = 1280;
|
||||
localparam GFX_height = 1024;
|
||||
localparam GFX_h_front_porch = 48;
|
||||
localparam GFX_h_sync_width = 112;
|
||||
localparam GFX_h_back_porch = 248;
|
||||
localparam GFX_v_front_porch = 1;
|
||||
localparam GFX_v_sync_width = 3;
|
||||
localparam GFX_v_back_porch = 38;
|
||||
`endif
|
||||
|
||||
localparam GFX_line_width = GFX_width + GFX_h_front_porch + GFX_h_sync_width + GFX_h_back_porch;
|
||||
localparam GFX_lines = GFX_height + GFX_v_front_porch + GFX_v_sync_width + GFX_v_back_porch;
|
||||
59
RTL/DEVICES/HardwareConfig.v
Normal file
59
RTL/DEVICES/HardwareConfig.v
Normal file
@@ -0,0 +1,59 @@
|
||||
// femtorv32, a minimalistic RISC-V RV32I core
|
||||
// Bruno Levy, 2020-2021
|
||||
//
|
||||
// This file: memory-mapped constants to query
|
||||
// hardware config.
|
||||
|
||||
module HardwareConfig(
|
||||
input wire clk,
|
||||
input wire sel_memory, // available RAM
|
||||
input wire sel_devices, // configured devices
|
||||
input wire sel_cpuinfo, // CPU information
|
||||
output wire [31:0] rdata // read data
|
||||
);
|
||||
|
||||
`include "HardwareConfig_bits.v"
|
||||
|
||||
`ifdef NRV_COUNTER_WIDTH
|
||||
localparam counter_width = `NRV_COUNTER_WIDTH;
|
||||
`else
|
||||
localparam counter_width = 32;
|
||||
`endif
|
||||
|
||||
|
||||
// configured devices
|
||||
localparam NRV_DEVICES = 0
|
||||
`ifdef NRV_IO_LEDS
|
||||
| (1 << IO_LEDS_bit)
|
||||
`endif
|
||||
`ifdef NRV_IO_UART
|
||||
| (1 << IO_UART_DAT_bit) | (1 << IO_UART_CNTL_bit)
|
||||
`endif
|
||||
`ifdef NRV_IO_SSD1351_1331
|
||||
| (1 << IO_SSD1351_CNTL_bit) | (1 << IO_SSD1351_CMD_bit) | (1 << IO_SSD1351_DAT_bit)
|
||||
`endif
|
||||
`ifdef NRV_IO_MAX7219
|
||||
| (1 << IO_MAX7219_DAT_bit)
|
||||
`endif
|
||||
`ifdef NRV_IO_SPI_FLASH
|
||||
| (1 << IO_SPI_FLASH_bit)
|
||||
`endif
|
||||
`ifdef NRV_MAPPED_SPI_FLASH
|
||||
| (1 << IO_MAPPED_SPI_FLASH_bit)
|
||||
`endif
|
||||
`ifdef NRV_IO_SDCARD
|
||||
| (1 << IO_SDCARD_bit)
|
||||
`endif
|
||||
`ifdef NRV_IO_BUTTONS
|
||||
| (1 << IO_BUTTONS_bit)
|
||||
`endif
|
||||
`ifdef NRV_IO_FGA
|
||||
| (1 << IO_FGA_CNTL_bit) | (1 << IO_FGA_DAT_bit)
|
||||
`endif
|
||||
;
|
||||
|
||||
assign rdata = sel_memory ? `NRV_RAM :
|
||||
sel_devices ? NRV_DEVICES :
|
||||
sel_cpuinfo ? (`NRV_FREQ << 16) | counter_width : 32'b0;
|
||||
|
||||
endmodule
|
||||
24
RTL/DEVICES/HardwareConfig_bits.v
Normal file
24
RTL/DEVICES/HardwareConfig_bits.v
Normal file
@@ -0,0 +1,24 @@
|
||||
// We got a total of 20 bits for 1-hot addressing of IO registers.
|
||||
|
||||
localparam IO_LEDS_bit = 0; // RW four leds
|
||||
localparam IO_UART_DAT_bit = 1; // RW write: data to send (8 bits) read: received data (8 bits)
|
||||
localparam IO_UART_CNTL_bit = 2; // R status. bit 8: valid read data. bit 9: busy sending
|
||||
localparam IO_SSD1351_CNTL_bit = 3; // W Oled display control
|
||||
localparam IO_SSD1351_CMD_bit = 4; // W Oled display commands (8 bits)
|
||||
localparam IO_SSD1351_DAT_bit = 5; // W Oled display data (8 bits)
|
||||
localparam IO_SSD1351_DAT16_bit = 6; // W Oled display data (16 bits)
|
||||
localparam IO_MAX7219_DAT_bit = 7; // W led matrix data (16 bits)
|
||||
localparam IO_SDCARD_bit = 8; // RW write: bit 0: mosi bit 1: clk bit 2: csn read: miso
|
||||
localparam IO_BUTTONS_bit = 9; // R buttons state
|
||||
localparam IO_FGA_CNTL_bit = 10; // RW write: send command read: get VSync/HSync/MemBusy/X/Y state
|
||||
localparam IO_FGA_DAT_bit = 11; // W write: write pixel data
|
||||
|
||||
// The three constant hardware config registers, using the three last bits of IO address space
|
||||
localparam IO_HW_CONFIG_RAM_bit = 17; // R total quantity of RAM, in bytes
|
||||
localparam IO_HW_CONFIG_DEVICES_bit = 18; // R configured devices
|
||||
localparam IO_HW_CONFIG_CPUINFO_bit = 19; // R CPU information CPL(6) FREQ(10) RESERVED(16)
|
||||
|
||||
// These devices do not have hardware registers. Just a bit set in IO_HW_CONFIG_DEVICES
|
||||
localparam IO_MAPPED_SPI_FLASH_bit = 20; // no register (just there to indicate presence)
|
||||
|
||||
|
||||
53
RTL/DEVICES/LEDs.v
Normal file
53
RTL/DEVICES/LEDs.v
Normal file
@@ -0,0 +1,53 @@
|
||||
// femtorv32, a minimalistic RISC-V RV32I core
|
||||
// Bruno Levy, 2020-2021
|
||||
//
|
||||
// This file: driver for LEDs (does nearly nothing !)
|
||||
//
|
||||
|
||||
module LEDDriver(
|
||||
`ifdef NRV_IO_IRDA
|
||||
output wire irda_TXD,
|
||||
input wire irda_RXD,
|
||||
output wire irda_SD,
|
||||
`endif
|
||||
input wire clk, // system clock
|
||||
input wire rstrb, // read strobe
|
||||
input wire wstrb, // write strobe
|
||||
input wire sel, // select (read/write ignored if low)
|
||||
input wire [31:0] wdata, // data to be written
|
||||
output wire [31:0] rdata, // read data
|
||||
output wire [3:0] LED // LED pins
|
||||
);
|
||||
|
||||
// The IceStick has an infrared reveiver/transmitter pair
|
||||
// See EXAMPLES/test_ir_sensor.c and EXAMPLES/test_ir_remote.c
|
||||
`ifdef NRV_IO_IRDA
|
||||
reg [5:0] led_state;
|
||||
assign LED = led_state[3:0];
|
||||
assign rdata = (sel ? {25'b0, irda_RXD, led_state} : 32'b0);
|
||||
assign irda_SD = led_state[5];
|
||||
assign irda_TXD = led_state[4];
|
||||
`else
|
||||
reg [3:0] led_state;
|
||||
assign LED = led_state;
|
||||
|
||||
initial begin
|
||||
led_state = 4'b0000;
|
||||
end
|
||||
|
||||
assign rdata = (sel ? {28'b0, led_state} : 32'b0);
|
||||
`endif
|
||||
|
||||
always @(posedge clk) begin
|
||||
if(sel && wstrb) begin
|
||||
`ifdef NRV_IO_IRDA
|
||||
led_state <= wdata[5:0];
|
||||
`else
|
||||
led_state <= wdata[3:0];
|
||||
`endif
|
||||
`ifdef BENCH
|
||||
$display("****************** LEDs = %b", wdata[3:0]);
|
||||
`endif
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
51
RTL/DEVICES/MAX7219.v
Normal file
51
RTL/DEVICES/MAX7219.v
Normal file
@@ -0,0 +1,51 @@
|
||||
// femtorv32, a minimalistic RISC-V RV32I core
|
||||
// Bruno Levy, 2020-2021
|
||||
//
|
||||
// This file: driver for MAX7219 led matrix display
|
||||
|
||||
module MAX7219(
|
||||
input wire clk, // system clock
|
||||
input wire wstrb, // write strobe
|
||||
input wire sel, // write ignored if low
|
||||
input wire [31:0] wdata, // data to be written
|
||||
|
||||
output wire wbusy, // asserted if the driver is busy sending data
|
||||
|
||||
// MAX7219 pins
|
||||
output wire DIN, // data in
|
||||
output wire CLK, // clock
|
||||
output wire CS // chip select
|
||||
);
|
||||
|
||||
reg [2:0] divider;
|
||||
always @(posedge clk) begin
|
||||
divider <= divider + 1;
|
||||
end
|
||||
|
||||
// clk=60MHz, slow_clk=60/8 MHz (max = 10 MHz)
|
||||
wire slow_clk = (divider == 3'b000);
|
||||
reg[4:0] bitcount; // 0 means idle
|
||||
initial bitcount = 0;
|
||||
reg[15:0] shifter;
|
||||
|
||||
assign DIN = shifter[15];
|
||||
wire sending = |bitcount;
|
||||
assign wbusy = sending;
|
||||
assign CS = !sending;
|
||||
assign CLK = sending && slow_clk;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if(wstrb) begin
|
||||
if(sel) begin
|
||||
shifter <= wdata[15:0];
|
||||
bitcount <= 16;
|
||||
end
|
||||
end else begin
|
||||
if(sending && slow_clk) begin
|
||||
bitcount <= bitcount - 5'd1;
|
||||
shifter <= {shifter[14:0], 1'b0};
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
393
RTL/DEVICES/MappedSPIFlash.v
Normal file
393
RTL/DEVICES/MappedSPIFlash.v
Normal file
@@ -0,0 +1,393 @@
|
||||
// femtorv32, a minimalistic RISC-V RV32I core
|
||||
// (minus SYSTEM and FENCE that are not implemented)
|
||||
//
|
||||
// Bruno Levy, 2020-2021
|
||||
// Matthias Koch, 2021
|
||||
//
|
||||
// This file: driver for SPI Flash, projected in memory space (readonly)
|
||||
//
|
||||
// TODO: go faster with XIP mode and dummy cycles customization
|
||||
// - send write enable command (06h)
|
||||
// - send write volatile config register command (08h REG)
|
||||
// REG=dummy_cycles[7:4]=4'b0100 XIP[3]=1'b1 reserved[2]=1'b0 wrap[1:0]=2'b11
|
||||
// (4 dummy cycles, works at up to 90 MHz according to datasheet)
|
||||
//
|
||||
// DataSheets:
|
||||
// https://media-www.micron.com/-/media/client/global/documents/products/data-sheet/nor-flash/serial-nor/n25q/n25q_32mb_3v_65nm.pdf?rev=27fc6016fc5249adb4bb8f221e72b395
|
||||
// https://www.winbond.com/resource-files/w25q128jv%20spi%20revc%2011162016.pdf (not the same chip, mostly compatible, datasheet is easier to read)
|
||||
|
||||
// The one on the ULX3S: https://www.issi.com/WW/pdf/25LP-WP128F.pdf
|
||||
// this one supports quad-SPI mode, IO0=SI, IO1=SO, IO2=WP, IO3=Hold/Reset
|
||||
|
||||
// There are four versions (from slowest to fastest)
|
||||
//
|
||||
// Version (used command) | cycles per 32-bits read | Specificity |
|
||||
// ----------------------------------------------------------|-----------------------|
|
||||
// SPI_FLASH_READ | 64 slow (50 MHz) | Standard |
|
||||
// SPI_FLASH_FAST_READ | 72 fast (100 MHz) | Uses dummy cycles |
|
||||
// SPI_FLASH_FAST_READ_DUAL_OUTPUT | 56 fast | Reverts MOSI |
|
||||
// SPI_FLASH_FAST_READ_DUAL_IO | 44 fast | Reverts MISO and MOSI |
|
||||
|
||||
// One can go even faster by configuring number of dummy cycles (can save up to 4 cycles per read)
|
||||
// and/or using XIP mode (that just requires the address to be sent, saves 16 cycles per 32-bits read)
|
||||
// (I tried both without success). This may require another mechanism to change configuration register.
|
||||
//
|
||||
// Most chips support a QUAD IO mode, using four bidirectional pins,
|
||||
// however, is not possible because the IO2 and IO3 pins
|
||||
// are not wired on the IceStick (one may solder a tiny wire and plug it
|
||||
// to a GPIO pin but I haven't soldering skills for things of that size !!)
|
||||
// It is a pity, because one could go really fast with these pins !
|
||||
|
||||
// Macros to select version and number of dummy cycles based on the board.
|
||||
|
||||
`ifdef ICE_STICK
|
||||
`define SPI_FLASH_FAST_READ_DUAL_IO
|
||||
`define SPI_FLASH_CONFIGURED
|
||||
`endif
|
||||
|
||||
`ifdef ICE4PI
|
||||
`undef SPI_FLASH_FAST_READ_DUAL_IO
|
||||
`undef SPI_FLASH_CONFIGURED
|
||||
`endif
|
||||
|
||||
`ifdef ICE_BREAKER
|
||||
`define SPI_FLASH_FAST_READ_DUAL_IO
|
||||
`define SPI_FLASH_DUMMY_CLOCKS 4 // Winbond SPI chips on icebreaker uses 4 dummy clocks
|
||||
`define SPI_FLASH_CONFIGURED
|
||||
`endif
|
||||
|
||||
`ifdef ULX3S
|
||||
`define SPI_FLASH_FAST_READ // TODO check whether dual IO mode can be done / dummy clocks
|
||||
`define SPI_FLASH_CONFIGURED
|
||||
`endif
|
||||
|
||||
`ifdef ARTY
|
||||
`define SPI_FLASH_READ
|
||||
`define SPI_FLASH_CONFIGURED
|
||||
`endif
|
||||
|
||||
`ifdef ICE_SUGAR_NANO
|
||||
`define SPI_FLASH_READ
|
||||
`define SPI_FLASH_CONFIGURED
|
||||
`endif
|
||||
|
||||
`ifndef SPI_FLASH_DUMMY_CLOCKS
|
||||
`define SPI_FLASH_DUMMY_CLOCKS 8
|
||||
`endif
|
||||
|
||||
`ifndef SPI_FLASH_CONFIGURED // Default: using slowest / simplest mode (command $03)
|
||||
`define SPI_FLASH_READ
|
||||
`endif
|
||||
|
||||
/********************************************************************************************************************************/
|
||||
|
||||
`ifdef SPI_FLASH_READ
|
||||
module MappedSPIFlash(
|
||||
input wire clk, // system clock
|
||||
input wire rstrb, // read strobe
|
||||
input wire [19:0] word_address, // address of the word to be read
|
||||
|
||||
output wire [31:0] rdata, // data read
|
||||
output wire rbusy, // asserted if busy receiving data
|
||||
|
||||
// SPI flash pins
|
||||
output wire CLK, // clock
|
||||
output reg CS_N, // chip select negated (active low)
|
||||
output wire MOSI, // master out slave in (data to be sent to flash)
|
||||
input wire MISO // master in slave out (data received from flash)
|
||||
);
|
||||
|
||||
reg [5:0] snd_bitcount;
|
||||
reg [31:0] cmd_addr;
|
||||
reg [5:0] rcv_bitcount;
|
||||
reg [31:0] rcv_data;
|
||||
wire sending = (snd_bitcount != 0);
|
||||
wire receiving = (rcv_bitcount != 0);
|
||||
wire busy = sending | receiving;
|
||||
assign rbusy = !CS_N;
|
||||
|
||||
assign MOSI = cmd_addr[31];
|
||||
initial CS_N = 1'b1;
|
||||
assign CLK = !CS_N && !clk; // CLK needs to be inverted (sample on posedge, shift of negedge)
|
||||
// and needs to be disabled when not sending/receiving (&& !CS_N).
|
||||
|
||||
// since least significant bytes are read first, we need to swizzle...
|
||||
assign rdata = {rcv_data[7:0],rcv_data[15:8],rcv_data[23:16],rcv_data[31:24]};
|
||||
|
||||
always @(posedge clk) begin
|
||||
if(rstrb) begin
|
||||
CS_N <= 1'b0;
|
||||
cmd_addr <= {8'h03, 2'b00,word_address[19:0], 2'b00};
|
||||
snd_bitcount <= 6'd32;
|
||||
end else begin
|
||||
if(sending) begin
|
||||
if(snd_bitcount == 1) begin
|
||||
rcv_bitcount <= 6'd32;
|
||||
end
|
||||
snd_bitcount <= snd_bitcount - 6'd1;
|
||||
cmd_addr <= {cmd_addr[30:0],1'b1};
|
||||
end
|
||||
if(receiving) begin
|
||||
rcv_bitcount <= rcv_bitcount - 6'd1;
|
||||
rcv_data <= {rcv_data[30:0],MISO};
|
||||
end
|
||||
if(!busy) begin
|
||||
CS_N <= 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
`endif
|
||||
|
||||
/********************************************************************************************************************************/
|
||||
|
||||
`ifdef SPI_FLASH_FAST_READ
|
||||
module MappedSPIFlash(
|
||||
input wire clk, // system clock
|
||||
input wire rstrb, // read strobe
|
||||
input wire [19:0] word_address, // address of the word to be read
|
||||
|
||||
output wire [31:0] rdata, // data read
|
||||
output wire rbusy, // asserted if busy receiving data
|
||||
|
||||
// SPI flash pins
|
||||
output wire CLK, // clock
|
||||
output reg CS_N, // chip select negated (active low)
|
||||
output wire MOSI, // master out slave in (data to be sent to flash)
|
||||
input wire MISO // master in slave out (data received from flash)
|
||||
);
|
||||
|
||||
reg [5:0] snd_bitcount;
|
||||
reg [31:0] cmd_addr;
|
||||
reg [5:0] rcv_bitcount;
|
||||
reg [31:0] rcv_data;
|
||||
wire sending = (snd_bitcount != 0);
|
||||
wire receiving = (rcv_bitcount != 0);
|
||||
wire busy = sending | receiving;
|
||||
assign rbusy = !CS_N;
|
||||
|
||||
assign MOSI = cmd_addr[31];
|
||||
initial CS_N = 1'b1;
|
||||
assign CLK = !CS_N && !clk;
|
||||
|
||||
// since least significant bytes are read first, we need to swizzle...
|
||||
assign rdata = {rcv_data[7:0],rcv_data[15:8],rcv_data[23:16],rcv_data[31:24]};
|
||||
|
||||
always @(posedge clk) begin
|
||||
if(rstrb) begin
|
||||
CS_N <= 1'b0;
|
||||
cmd_addr <= {8'h0b, 2'b00,word_address[19:0], 2'b00};
|
||||
snd_bitcount <= 6'd40; // TODO: check dummy clocks
|
||||
end else begin
|
||||
if(sending) begin
|
||||
if(snd_bitcount == 1) begin
|
||||
rcv_bitcount <= 6'd32;
|
||||
end
|
||||
snd_bitcount <= snd_bitcount - 6'd1;
|
||||
cmd_addr <= {cmd_addr[30:0],1'b1};
|
||||
end
|
||||
if(receiving) begin
|
||||
rcv_bitcount <= rcv_bitcount - 6'd1;
|
||||
rcv_data <= {rcv_data[30:0],MISO};
|
||||
end
|
||||
if(!busy) begin
|
||||
CS_N <= 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
`endif
|
||||
|
||||
/********************************************************************************************************************************/
|
||||
|
||||
`ifdef SPI_FLASH_FAST_READ_DUAL_OUTPUT
|
||||
module MappedSPIFlash(
|
||||
input wire clk, // system clock
|
||||
input wire rstrb, // read strobe
|
||||
input wire [19:0] word_address, // address of the word to be read
|
||||
|
||||
output wire [31:0] rdata, // data read
|
||||
output wire rbusy, // asserted if busy receiving data
|
||||
|
||||
// SPI flash pins
|
||||
output wire CLK, // clock
|
||||
output reg CS_N, // chip select negated (active low)
|
||||
inout wire MOSI, // master out slave in (data to be sent to flash)
|
||||
input wire MISO // master in slave out (data received from flash)
|
||||
);
|
||||
|
||||
wire MOSI_out;
|
||||
wire MOSI_in;
|
||||
wire MOSI_oe;
|
||||
|
||||
assign MOSI = MOSI_oe ? MOSI_out : 1'bZ;
|
||||
assign MOSI_in = MOSI;
|
||||
|
||||
reg [5:0] snd_bitcount;
|
||||
reg [31:0] cmd_addr;
|
||||
reg [5:0] rcv_bitcount;
|
||||
reg [31:0] rcv_data;
|
||||
wire sending = (snd_bitcount != 0);
|
||||
wire receiving = (rcv_bitcount != 0);
|
||||
wire busy = sending | receiving;
|
||||
assign rbusy = !CS_N;
|
||||
|
||||
assign MOSI_oe = !receiving;
|
||||
assign MOSI_out = sending && cmd_addr[31];
|
||||
|
||||
initial CS_N = 1'b1;
|
||||
assign CLK = !CS_N && !clk;
|
||||
|
||||
// since least significant bytes are read first, we need to swizzle...
|
||||
assign rdata = {rcv_data[7:0],rcv_data[15:8],rcv_data[23:16],rcv_data[31:24]};
|
||||
|
||||
always @(posedge clk) begin
|
||||
if(rstrb) begin
|
||||
CS_N <= 1'b0;
|
||||
cmd_addr <= {8'h3b, 2'b00,word_address[19:0], 2'b00};
|
||||
snd_bitcount <= 6'd40; // TODO: check dummy clocks
|
||||
end else begin
|
||||
if(sending) begin
|
||||
if(snd_bitcount == 1) begin
|
||||
rcv_bitcount <= 6'd32;
|
||||
end
|
||||
snd_bitcount <= snd_bitcount - 6'd1;
|
||||
cmd_addr <= {cmd_addr[30:0],1'b1};
|
||||
end
|
||||
if(receiving) begin
|
||||
rcv_bitcount <= rcv_bitcount - 6'd2;
|
||||
rcv_data <= {rcv_data[29:0],MISO,MOSI_in};
|
||||
end
|
||||
if(!busy) begin
|
||||
CS_N <= 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
`endif
|
||||
|
||||
/********************************************************************************************************************************/
|
||||
|
||||
`ifdef SPI_FLASH_FAST_READ_DUAL_IO
|
||||
|
||||
module MappedSPIFlash(
|
||||
input wire clk, // system clock
|
||||
input wire rstrb, // read strobe
|
||||
input wire [19:0] word_address, // address to be read
|
||||
|
||||
|
||||
output wire [31:0] rdata, // data read
|
||||
output wire rbusy, // asserted if busy receiving data
|
||||
|
||||
output wire CLK, // clock
|
||||
output reg CS_N, // chip select negated (active low)
|
||||
inout wire [1:0] IO // two bidirectional IO pins
|
||||
);
|
||||
|
||||
reg [4:0] clock_cnt; // send/receive clock, 2 bits per clock (dual IO)
|
||||
reg [39:0] shifter; // used for sending and receiving
|
||||
|
||||
reg dir; // 1 if sending, 0 otherwise
|
||||
|
||||
wire busy = (clock_cnt != 0);
|
||||
wire sending = (dir && busy);
|
||||
wire receiving = (!dir && busy);
|
||||
assign rbusy = !CS_N;
|
||||
|
||||
// The two data pins IO0 (=MOSI) and IO1 (=MISO) used in bidirectional mode.
|
||||
reg IO_oe = 1'b1;
|
||||
wire [1:0] IO_out = shifter[39:38];
|
||||
wire [1:0] IO_in = IO;
|
||||
assign IO = IO_oe ? IO_out : 2'bZZ;
|
||||
|
||||
initial CS_N = 1'b1;
|
||||
assign CLK = !CS_N && !clk;
|
||||
|
||||
// since least significant bytes are read first, we need to swizzle...
|
||||
assign rdata={shifter[7:0],shifter[15:8],shifter[23:16],shifter[31:24]};
|
||||
|
||||
// Duplicates the bits (used because when sending command, dual IO is
|
||||
// not active yet, and I do not want to have a separate shifter for
|
||||
// the command and for the args...).
|
||||
function [15:0] bbyyttee;
|
||||
input [7:0] x;
|
||||
begin
|
||||
bbyyttee = {
|
||||
x[7],x[7],x[6],x[6],x[5],x[5],x[4],x[4],
|
||||
x[3],x[3],x[2],x[2],x[1],x[1],x[0],x[0]
|
||||
};
|
||||
end
|
||||
endfunction
|
||||
|
||||
always @(posedge clk) begin
|
||||
if(rstrb) begin
|
||||
CS_N <= 1'b0;
|
||||
IO_oe <= 1'b1;
|
||||
dir <= 1'b1;
|
||||
shifter <= {bbyyttee(8'hbb), 2'b00, word_address[19:0], 2'b00};
|
||||
clock_cnt <= 5'd20 + `SPI_FLASH_DUMMY_CLOCKS; // cmd: 8 clocks address: 12 clocks + dummy clocks
|
||||
end else begin
|
||||
if(busy) begin
|
||||
shifter <= {shifter[37:0], (receiving ? IO_in : 2'b11)};
|
||||
clock_cnt <= clock_cnt - 5'd1;
|
||||
if(dir && clock_cnt == 1) begin
|
||||
clock_cnt <= 5'd16; // 32 bits, 2 bits per clock
|
||||
IO_oe <= 1'b0;
|
||||
dir <= 1'b0;
|
||||
end
|
||||
end else begin
|
||||
CS_N <= 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
|
||||
/*
|
||||
// 04/02/2021 This version optimized by Matthias Koch
|
||||
module MappedSPIFlash(
|
||||
input wire clk, // system clock
|
||||
input wire rstrb, // read strobe
|
||||
input wire [19:0] word_address, // read address
|
||||
|
||||
output wire [31:0] rdata, // data read
|
||||
output wire rbusy, // asserted if busy receiving data
|
||||
|
||||
output wire CLK, // clock
|
||||
output wire CS_N, // chip select negated (active low)
|
||||
inout wire [1:0] IO // two bidirectional IO pins
|
||||
);
|
||||
|
||||
reg [6:0] clock_cnt; // send/receive clock, 2 bits per clock (dual IO)
|
||||
reg [39:0] shifter; // used for sending and receiving
|
||||
|
||||
wire busy = ~clock_cnt[6];
|
||||
assign CS_N = clock_cnt[6];
|
||||
assign rbusy = busy;
|
||||
assign CLK = busy & !clk; // CLK needs to be disabled when not active.
|
||||
|
||||
// Since least significant bytes are read first, we need to swizzle...
|
||||
assign rdata={shifter[7:0],shifter[15:8],shifter[23:16],shifter[31:24]};
|
||||
|
||||
// The two data pins IO0 (=MOSI) and IO1 (=MISO) used in bidirectional mode.
|
||||
wire [1:0] IO_out = shifter[39:38];
|
||||
wire [1:0] IO_in = IO;
|
||||
assign IO = clock_cnt > 7'd15 ? IO_out : 2'bZZ;
|
||||
// assign IO = |clock_cnt[5:4] ? IO_out : 2'bZZ; // optimized version of the line above
|
||||
|
||||
always @(posedge clk) begin
|
||||
if(rstrb) begin
|
||||
shifter <= {16'hCFCF, 2'b00, word_address[19:0], 2'b00}; // 16'hCFCF is 8'hbb with bits doubled
|
||||
clock_cnt <= 7'd43; // cmd: 8 clocks address: 12 clocks dummy: 8 clocks. data: 16 clocks, 2 bits per clock
|
||||
end else begin
|
||||
if(busy) begin
|
||||
shifter <= {shifter[37:0], IO_in};
|
||||
clock_cnt <= clock_cnt - 7'd1;
|
||||
end
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
*/
|
||||
|
||||
`endif
|
||||
40
RTL/DEVICES/SDCard.v
Normal file
40
RTL/DEVICES/SDCard.v
Normal file
@@ -0,0 +1,40 @@
|
||||
// femtorv32, a minimalistic RISC-V RV32I core
|
||||
// Bruno Levy, 2020-2021
|
||||
//
|
||||
// This file: driver for SDCard (does nearly nothing,
|
||||
// for now it is just an interface for software bitbanging,
|
||||
// see FIRMWARE/LIBFEMTORV32/spi_sd.c)
|
||||
//
|
||||
|
||||
module SDCard(
|
||||
input wire clk, // system clock
|
||||
input wire rstrb, // read strobe
|
||||
input wire wstrb, // write strobe
|
||||
input wire sel, // select (read/write ignored if low)
|
||||
input wire [31:0] wdata, // data to be written
|
||||
output wire [31:0] rdata, // read data
|
||||
|
||||
output wire MOSI,
|
||||
input wire MISO,
|
||||
output wire CS_N,
|
||||
output wire CLK
|
||||
);
|
||||
reg [2:0] state; // CS_N,CLK,MOSI
|
||||
|
||||
assign CS_N = state[2];
|
||||
assign CLK = state[1];
|
||||
assign MOSI = state[0];
|
||||
|
||||
initial begin
|
||||
state = 3'b100;
|
||||
end
|
||||
|
||||
assign rdata = (sel ? {31'b0, MISO} : 32'b0);
|
||||
|
||||
always @(posedge clk) begin
|
||||
if(sel && wstrb) begin
|
||||
state <= wdata[2:0];
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
156
RTL/DEVICES/SSD1351_1331.v
Normal file
156
RTL/DEVICES/SSD1351_1331.v
Normal file
@@ -0,0 +1,156 @@
|
||||
// femtorv32, a minimalistic RISC-V RV32I core
|
||||
// Bruno Levy, 2020-2021
|
||||
//
|
||||
// This file: driver for SSD1351 and SSD1331 OLED display
|
||||
// Reference: https://www.crystalfontz.com/controllers/SolomonSystech/SSD1351/
|
||||
|
||||
//
|
||||
// TODO: we could use wmask to write directly 16 bits or 32 bits of data
|
||||
// (we could even have a 'fast clear' option that writes a number
|
||||
// of zeroes).
|
||||
|
||||
`ifdef NRV_IO_SSD1331
|
||||
`define NRV_IO_SSD1351_1331
|
||||
`endif
|
||||
|
||||
`ifdef NRV_IO_SSD1351
|
||||
`define NRV_IO_SSD1351_1331
|
||||
`endif
|
||||
|
||||
module SSD1351_clk #(
|
||||
parameter width=1
|
||||
)(
|
||||
input wire clk, // input system clock
|
||||
output wire CLK, // SSD1351 clock
|
||||
output wire CLK_falling_edge // pulses at each falling edge of CLK
|
||||
);
|
||||
reg [width-1:0] slow_cnt;
|
||||
always @(posedge clk) begin
|
||||
slow_cnt <= slow_cnt + 1;
|
||||
end
|
||||
assign CLK = slow_cnt[width-1];
|
||||
assign CLK_falling_edge = (slow_cnt == (1 << width)-1);
|
||||
endmodule
|
||||
|
||||
module SSD1351(
|
||||
input wire clk, // system clock
|
||||
input wire wstrb, // write strobe (use one of sel_xxx to select dest)
|
||||
input wire sel_cntl, // wdata[0]: !CS; wdata[1]: RST
|
||||
input wire sel_cmd, // send 8-bits command to display
|
||||
input wire sel_dat, // send 8-bits data to display
|
||||
input wire sel_dat16, // send 16-bits data to display
|
||||
|
||||
input wire [31:0] wdata, // data to be written
|
||||
|
||||
output wire wbusy, // asserted if the driver is busy sending data
|
||||
|
||||
// SSD1351 pins
|
||||
output DIN, // data in
|
||||
output CLK, // clock
|
||||
output reg CS, // chip select (active low)
|
||||
output reg DC, // data (high) / command (low)
|
||||
output reg RST // reset (active low)
|
||||
);
|
||||
|
||||
initial begin
|
||||
DC = 1'b0;
|
||||
RST = 1'b0;
|
||||
CS = 1'b1;
|
||||
end
|
||||
|
||||
/********* The clock ****************************************************/
|
||||
// Note: SSD1351 expects the raising edges of the clock in the middle of
|
||||
// the data bits.
|
||||
// TODO: try to have a 'waveform' instead, that is shifted (simpler and
|
||||
// more elegant).
|
||||
// Page 52 of the doc: 4-wire SPI timing:
|
||||
// Unclear what 'Clock Cycle Time' (220 ns) means,
|
||||
// Clock Low Time (20ns) + Clock High Time (20ns) = 40ns
|
||||
// max freq = 1/(40ns) = 25 MHz
|
||||
// experimentally, seems to work up to 30 Mhz (but not more)
|
||||
|
||||
wire CLK_falling_edge;
|
||||
|
||||
generate
|
||||
if(`NRV_FREQ <= 60) begin // Divide by 2-> 30 MHz
|
||||
SSD1351_clk #(
|
||||
.width(1)
|
||||
)slow_clk(
|
||||
.clk(clk),
|
||||
.CLK(CLK),
|
||||
.CLK_falling_edge(CLK_falling_edge)
|
||||
);
|
||||
end else if(`NRV_FREQ <= 120) begin // Divide by 4
|
||||
SSD1351_clk #(
|
||||
.width(2)
|
||||
)slow_clk(
|
||||
.clk(clk),
|
||||
.CLK(CLK),
|
||||
.CLK_falling_edge(CLK_falling_edge)
|
||||
);
|
||||
end else begin // Divide by 8
|
||||
SSD1351_clk #(
|
||||
.width(3)
|
||||
)slow_clk(
|
||||
.clk(clk),
|
||||
.CLK(CLK),
|
||||
.CLK_falling_edge(CLK_falling_edge)
|
||||
);
|
||||
end
|
||||
endgenerate
|
||||
|
||||
// Currently sent bit, 1-based index
|
||||
// (0000 config. corresponds to idle)
|
||||
reg[4:0] bitcount = 5'b0000;
|
||||
reg[15:0] shifter = 0;
|
||||
wire sending = (bitcount != 0);
|
||||
|
||||
assign DIN = shifter[15];
|
||||
assign wbusy = sending;
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
always @(posedge clk) begin
|
||||
if(wstrb) begin
|
||||
if(sel_cntl) begin
|
||||
CS <= !wdata[0];
|
||||
RST <= wdata[1];
|
||||
end
|
||||
if(sel_cmd) begin
|
||||
RST <= 1'b1;
|
||||
DC <= 1'b0;
|
||||
shifter <= {wdata[7:0],8'b0};
|
||||
bitcount <= 8;
|
||||
CS <= 1'b1;
|
||||
end
|
||||
if(sel_dat) begin
|
||||
RST <= 1'b1;
|
||||
DC <= 1'b1;
|
||||
shifter <= {wdata[7:0],8'b0};
|
||||
bitcount <= 8;
|
||||
CS <= 1'b1;
|
||||
end
|
||||
if(sel_dat16) begin
|
||||
RST <= 1'b1;
|
||||
DC <= 1'b1;
|
||||
shifter <= wdata[15:0];
|
||||
bitcount <= 16;
|
||||
CS <= 1'b1;
|
||||
end
|
||||
end else begin
|
||||
// detect falling edge of slow_clk
|
||||
if(CLK_falling_edge) begin
|
||||
if(sending) begin
|
||||
if(CS) begin // first tick activates CS (low)
|
||||
CS <= 1'b0;
|
||||
end else begin // shift on falling edge
|
||||
bitcount <= bitcount - 5'd1;
|
||||
shifter <= {shifter[14:0], 1'b0};
|
||||
end
|
||||
end else begin // last tick deactivates CS (high)
|
||||
CS <= 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
51
RTL/DEVICES/TMDS_encoder.v
Normal file
51
RTL/DEVICES/TMDS_encoder.v
Normal file
@@ -0,0 +1,51 @@
|
||||
// Taken from: https://www.fpga4fun.com/HDMI.html
|
||||
// (c) fpga4fun.com & KNJN LLC 2013
|
||||
|
||||
module TMDS_encoder(
|
||||
input clk, // Pixel clock (25 MHz for 640x480)
|
||||
input [7:0] VD, // video data (one of 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 // The generated 10-bits signal (scrambled to minimize transitions, and 0/1-balanced)
|
||||
);
|
||||
|
||||
/* verilator lint_off WIDTH */
|
||||
/* verilator lint_off UNOPTFLAT */
|
||||
|
||||
wire [3:0] Nb1s = VD[0] + VD[1] + VD[2] + VD[3] + VD[4] + VD[5] + VD[6] + VD[7];
|
||||
wire XNOR = (Nb1s>4'd4) || (Nb1s==4'd4 && VD[0]==1'b0);
|
||||
|
||||
// [Bruno Levy Jan 2021]
|
||||
// Compact writing: wire [8:0] q_m = {~XNOR, q_m[6:0] ^ VD[7:1] ^ {7{XNOR}}, VD[0]};
|
||||
// ... generates combinatorial loop warning, so I'd rather expand it (less compact,
|
||||
// less elegant, but I did not like this combinatorial loop warning).
|
||||
|
||||
wire [8:0] q_m;
|
||||
assign q_m[0] = VD[0];
|
||||
assign q_m[1] = q_m[0] ^ VD[1] ^ XNOR;
|
||||
assign q_m[2] = q_m[1] ^ VD[2] ^ XNOR;
|
||||
assign q_m[3] = q_m[2] ^ VD[3] ^ XNOR;
|
||||
assign q_m[4] = q_m[3] ^ VD[4] ^ XNOR;
|
||||
assign q_m[5] = q_m[4] ^ VD[5] ^ XNOR;
|
||||
assign q_m[6] = q_m[5] ^ VD[6] ^ XNOR;
|
||||
assign q_m[7] = q_m[6] ^ VD[7] ^ XNOR;
|
||||
assign q_m[8] = ~XNOR;
|
||||
|
||||
reg [3:0] balance_acc = 0;
|
||||
wire [3:0] balance = q_m[0] + q_m[1] + q_m[2] + q_m[3] + q_m[4] + q_m[5] + q_m[6] + 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 - ({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) begin
|
||||
TMDS <= VDE ? TMDS_data : TMDS_code;
|
||||
balance_acc <= VDE ? balance_acc_new : 4'h0;
|
||||
end
|
||||
|
||||
/* verilator lint_on UNOPTFLAT */
|
||||
/* verilator lint_on WIDTH */
|
||||
|
||||
endmodule
|
||||
106
RTL/DEVICES/ice40up5k_spram.v
Normal file
106
RTL/DEVICES/ice40up5k_spram.v
Normal file
@@ -0,0 +1,106 @@
|
||||
|
||||
/*
|
||||
* PicoSoC - A simple example SoC using PicoRV32
|
||||
*
|
||||
* Copyright (C) 2017 Clifford Wolf <clifford@clifford.at>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
module ice40up5k_spram #(
|
||||
// We current always use the whole SPRAM (128 kB)
|
||||
parameter integer WORDS = 32768
|
||||
) (
|
||||
input clk,
|
||||
input [3:0] wen,
|
||||
input [14:0] addr,
|
||||
input [31:0] wdata,
|
||||
output [31:0] rdata
|
||||
);
|
||||
|
||||
// [BL 04/2021] added simulation model
|
||||
`ifdef BENCH_OR_LINT
|
||||
reg [31:0] RAM[(WORDS/4)-1:0];
|
||||
reg [31:0] rdata_reg;
|
||||
assign rdata = rdata_reg;
|
||||
always @(posedge clk) begin
|
||||
/* verilator lint_off WIDTH */
|
||||
if(wen[0]) RAM[addr][ 7:0 ] <= wdata[ 7:0 ];
|
||||
if(wen[1]) RAM[addr][15:8 ] <= wdata[15:8 ];
|
||||
if(wen[2]) RAM[addr][23:16] <= wdata[23:16];
|
||||
if(wen[3]) RAM[addr][31:24] <= wdata[31:24];
|
||||
rdata_reg <= RAM[addr];
|
||||
/* verilator lint_on WIDTH */
|
||||
end
|
||||
`else
|
||||
wire cs_0, cs_1;
|
||||
wire [31:0] rdata_0, rdata_1;
|
||||
|
||||
assign cs_0 = !addr[14];
|
||||
assign cs_1 = addr[14];
|
||||
assign rdata = addr[14] ? rdata_1 : rdata_0;
|
||||
|
||||
SB_SPRAM256KA ram00 (
|
||||
.ADDRESS(addr[13:0]),
|
||||
.DATAIN(wdata[15:0]),
|
||||
.MASKWREN({wen[1], wen[1], wen[0], wen[0]}),
|
||||
.WREN(wen[1]|wen[0]),
|
||||
.CHIPSELECT(cs_0),
|
||||
.CLOCK(clk),
|
||||
.STANDBY(1'b0),
|
||||
.SLEEP(1'b0),
|
||||
.POWEROFF(1'b1),
|
||||
.DATAOUT(rdata_0[15:0])
|
||||
);
|
||||
|
||||
SB_SPRAM256KA ram01 (
|
||||
.ADDRESS(addr[13:0]),
|
||||
.DATAIN(wdata[31:16]),
|
||||
.MASKWREN({wen[3], wen[3], wen[2], wen[2]}),
|
||||
.WREN(wen[3]|wen[2]),
|
||||
.CHIPSELECT(cs_0),
|
||||
.CLOCK(clk),
|
||||
.STANDBY(1'b0),
|
||||
.SLEEP(1'b0),
|
||||
.POWEROFF(1'b1),
|
||||
.DATAOUT(rdata_0[31:16])
|
||||
);
|
||||
|
||||
SB_SPRAM256KA ram10 (
|
||||
.ADDRESS(addr[13:0]),
|
||||
.DATAIN(wdata[15:0]),
|
||||
.MASKWREN({wen[1], wen[1], wen[0], wen[0]}),
|
||||
.WREN(wen[1]|wen[0]),
|
||||
.CHIPSELECT(cs_1),
|
||||
.CLOCK(clk),
|
||||
.STANDBY(1'b0),
|
||||
.SLEEP(1'b0),
|
||||
.POWEROFF(1'b1),
|
||||
.DATAOUT(rdata_1[15:0])
|
||||
);
|
||||
|
||||
SB_SPRAM256KA ram11 (
|
||||
.ADDRESS(addr[13:0]),
|
||||
.DATAIN(wdata[31:16]),
|
||||
.MASKWREN({wen[3], wen[3], wen[2], wen[2]}),
|
||||
.WREN(wen[3]|wen[2]),
|
||||
.CHIPSELECT(cs_1),
|
||||
.CLOCK(clk),
|
||||
.STANDBY(1'b0),
|
||||
.SLEEP(1'b0),
|
||||
.POWEROFF(1'b1),
|
||||
.DATAOUT(rdata_1[31:16])
|
||||
);
|
||||
`endif
|
||||
endmodule
|
||||
95
RTL/DEVICES/uart.v
Normal file
95
RTL/DEVICES/uart.v
Normal file
@@ -0,0 +1,95 @@
|
||||
// femtorv32, a minimalistic RISC-V RV32I core
|
||||
//
|
||||
// Bruno Levy, 2020-2021
|
||||
//
|
||||
// This file: driver for UART (serial over USB)
|
||||
// Wrapper around modified Claire Wolf's UART
|
||||
|
||||
`ifdef BENCH
|
||||
|
||||
// If BENCH is define, using a fake UART that displays
|
||||
// each sent character.
|
||||
module UART(
|
||||
input wire clk, // system clock
|
||||
input wire rstrb, // read strobe
|
||||
input wire wstrb, // write strobe
|
||||
input wire sel_dat, // select data reg (rw)
|
||||
input wire sel_cntl, // select control reg (r)
|
||||
input wire [31:0] wdata, // data to be written
|
||||
output wire [31:0] rdata, // data read
|
||||
|
||||
input wire RXD, // UART pins (unused in bench mode)
|
||||
output wire TXD,
|
||||
|
||||
output reg brk // goes high one cycle when <ctrl><C> is pressed.
|
||||
);
|
||||
assign rdata = 32'b0;
|
||||
assign TXD = 1'b0;
|
||||
always @(posedge clk) begin
|
||||
if(sel_dat && wstrb) begin
|
||||
if(wdata == 32'd4) begin
|
||||
$display("<end of simulation> (EOT sent to UART)");
|
||||
$finish();
|
||||
end
|
||||
$write("%c",wdata[7:0]);
|
||||
$fflush(32'h8000_0001);
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
`else
|
||||
// For some reasons, our 'compressed' version of
|
||||
// the UART does not work on the ARTY, there is
|
||||
// probably a couple of bugs there...
|
||||
`ifdef ARTY
|
||||
`include "uart_picosoc.v.orig"
|
||||
`else
|
||||
`include "uart_picosoc_shrunk.v"
|
||||
`endif
|
||||
|
||||
module UART(
|
||||
input wire clk, // system clock
|
||||
input wire rstrb, // read strobe
|
||||
input wire wstrb, // write strobe
|
||||
input wire sel_dat, // select data reg (rw)
|
||||
input wire sel_cntl, // select control reg (r)
|
||||
input wire [31:0] wdata, // data to be written
|
||||
output wire [31:0] rdata, // data read
|
||||
|
||||
input wire RXD, // UART pins
|
||||
output wire TXD,
|
||||
|
||||
output reg brk // goes high one cycle when <ctrl><C> is pressed.
|
||||
);
|
||||
|
||||
wire [7:0] rx_data;
|
||||
wire [7:0] tx_data;
|
||||
wire serial_tx_busy;
|
||||
wire serial_valid;
|
||||
|
||||
buart #(
|
||||
.FREQ_MHZ(`NRV_FREQ),
|
||||
.BAUDS(115200)
|
||||
) the_buart (
|
||||
.clk(clk),
|
||||
.resetq(!brk),
|
||||
.tx(TXD),
|
||||
.rx(RXD),
|
||||
.tx_data(wdata[7:0]),
|
||||
.rx_data(rx_data),
|
||||
.busy(serial_tx_busy),
|
||||
.valid(serial_valid),
|
||||
.wr(sel_dat && wstrb),
|
||||
.rd(sel_dat && rstrb)
|
||||
);
|
||||
|
||||
assign rdata = sel_dat ? {22'b0, serial_tx_busy, serial_valid, rx_data}
|
||||
: sel_cntl ? {22'b0, serial_tx_busy, serial_valid, 8'b0 }
|
||||
: 32'b0;
|
||||
|
||||
always @(posedge clk) begin
|
||||
brk <= serial_valid && (rx_data == 8'd3);
|
||||
end
|
||||
|
||||
endmodule
|
||||
`endif
|
||||
131
RTL/DEVICES/uart_picosoc.v.orig
Normal file
131
RTL/DEVICES/uart_picosoc.v.orig
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* PicoSoC - A simple example SoC using PicoRV32
|
||||
*
|
||||
* Copyright (C) 2017 Clifford Wolf <clifford@clifford.at>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
// October 2019, Matthias Koch: Renamed wires
|
||||
// December 2020, Bruno Levy: parameterization with freq and bauds
|
||||
|
||||
module buart #(
|
||||
parameter FREQ_MHZ = 60,
|
||||
parameter BAUDS = 115200
|
||||
) (
|
||||
input clk,
|
||||
input resetq,
|
||||
|
||||
output tx,
|
||||
input rx,
|
||||
|
||||
input wr,
|
||||
input rd,
|
||||
input [7:0] tx_data,
|
||||
output [7:0] rx_data,
|
||||
|
||||
output busy,
|
||||
output valid
|
||||
);
|
||||
|
||||
parameter divider = FREQ_MHZ * 1000000 / BAUDS;
|
||||
|
||||
reg [3:0] recv_state;
|
||||
reg [$clog2(divider)-1:0] recv_divcnt; // Counts to divider. Reserve enough bytes !
|
||||
reg [7:0] recv_pattern;
|
||||
reg [7:0] recv_buf_data;
|
||||
reg recv_buf_valid;
|
||||
|
||||
reg [9:0] send_pattern;
|
||||
reg send_dummy;
|
||||
reg [3:0] send_bitcnt;
|
||||
reg [$clog2(divider)-1:0] send_divcnt; // Counts to divider. Reserve enough bytes !
|
||||
|
||||
assign rx_data = recv_buf_data;
|
||||
assign valid = recv_buf_valid;
|
||||
assign busy = (send_bitcnt || send_dummy);
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (!resetq) begin
|
||||
|
||||
recv_state <= 0;
|
||||
recv_divcnt <= 0;
|
||||
recv_pattern <= 0;
|
||||
recv_buf_data <= 0;
|
||||
recv_buf_valid <= 0;
|
||||
|
||||
end else begin
|
||||
recv_divcnt <= recv_divcnt + 1;
|
||||
|
||||
if (rd) recv_buf_valid <= 0;
|
||||
|
||||
case (recv_state)
|
||||
0: begin
|
||||
if (!rx)
|
||||
recv_state <= 1;
|
||||
end
|
||||
1: begin
|
||||
if (recv_divcnt > divider/2) begin
|
||||
recv_state <= 2;
|
||||
recv_divcnt <= 0;
|
||||
end
|
||||
end
|
||||
10: begin
|
||||
if (recv_divcnt > divider) begin
|
||||
recv_buf_data <= recv_pattern;
|
||||
recv_buf_valid <= 1;
|
||||
recv_state <= 0;
|
||||
end
|
||||
end
|
||||
default: begin
|
||||
if (recv_divcnt > divider) begin
|
||||
recv_pattern <= {rx, recv_pattern[7:1]};
|
||||
recv_state <= recv_state + 1;
|
||||
recv_divcnt <= 0;
|
||||
end
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
assign tx = send_pattern[0];
|
||||
|
||||
always @(posedge clk) begin
|
||||
send_divcnt <= send_divcnt + 1;
|
||||
if (!resetq) begin
|
||||
send_pattern <= ~0;
|
||||
send_bitcnt <= 0;
|
||||
send_divcnt <= 0;
|
||||
send_dummy <= 1;
|
||||
end else begin
|
||||
if (send_dummy && !send_bitcnt) begin
|
||||
send_pattern <= ~0;
|
||||
send_bitcnt <= 15;
|
||||
send_divcnt <= 0;
|
||||
send_dummy <= 0;
|
||||
end else if (wr && !send_bitcnt) begin
|
||||
send_pattern <= {1'b1, tx_data[7:0], 1'b0};
|
||||
send_bitcnt <= 10;
|
||||
send_divcnt <= 0;
|
||||
end else if (send_divcnt > divider && send_bitcnt) begin
|
||||
send_pattern <= {1'b1, send_pattern[9:1]};
|
||||
send_bitcnt <= send_bitcnt - 1;
|
||||
send_divcnt <= 0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
|
||||
134
RTL/DEVICES/uart_picosoc_shrunk.v
Normal file
134
RTL/DEVICES/uart_picosoc_shrunk.v
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* PicoSoC - A simple example SoC using PicoRV32
|
||||
*
|
||||
* Copyright (C) 2017 Clifford Wolf <clifford@clifford.at>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
// October 2019, Matthias Koch: Renamed wires and optimizations.
|
||||
// December 2020, Bruno Levy: parameterization with freq and bauds
|
||||
// Factorized recv_divcnt and send_divcnt
|
||||
// Additional LUT golfing tricks
|
||||
|
||||
module buart #(
|
||||
parameter FREQ_MHZ = 12,
|
||||
parameter BAUDS = 115200
|
||||
) (
|
||||
input clk,
|
||||
input resetq,
|
||||
|
||||
output tx,
|
||||
input rx,
|
||||
|
||||
input wr,
|
||||
input rd,
|
||||
input [7:0] tx_data,
|
||||
output [7:0] rx_data,
|
||||
|
||||
output busy,
|
||||
output valid
|
||||
);
|
||||
|
||||
/************** Baud frequency constants ******************/
|
||||
|
||||
parameter divider = FREQ_MHZ * 1000000 / BAUDS;
|
||||
parameter divwidth = $clog2(divider);
|
||||
|
||||
parameter baud_init = divider;
|
||||
parameter half_baud_init = divider/2+1;
|
||||
|
||||
/************* Receiver ***********************************/
|
||||
|
||||
// Trick from Olof Kindgren: use n+1 bit and decrement instead of
|
||||
// incrementing, and test the sign bit.
|
||||
|
||||
reg [divwidth:0] recv_divcnt;
|
||||
wire recv_baud_clk = recv_divcnt[divwidth];
|
||||
|
||||
reg recv_state;
|
||||
reg [8:0] recv_pattern;
|
||||
reg [7:0] recv_buf_data;
|
||||
reg recv_buf_valid;
|
||||
|
||||
assign rx_data = recv_buf_data;
|
||||
assign valid = recv_buf_valid;
|
||||
|
||||
|
||||
always @(posedge clk) begin
|
||||
|
||||
if (rd) recv_buf_valid <= 0;
|
||||
|
||||
if (!resetq) recv_buf_valid <= 0;
|
||||
|
||||
case (recv_state)
|
||||
|
||||
0: begin
|
||||
if (!rx) begin
|
||||
recv_state <= 1;
|
||||
/* verilator lint_off WIDTH */
|
||||
recv_divcnt <= half_baud_init;
|
||||
/* verilator lint_on WIDTH */
|
||||
end
|
||||
recv_pattern <= 0;
|
||||
end
|
||||
|
||||
1: begin
|
||||
if (recv_baud_clk) begin
|
||||
|
||||
// Inverted start bit shifted through the whole register
|
||||
// The idea is to use the start bit as marker
|
||||
// for "reception complete",
|
||||
// but as initialising registers to 10'b1_11111111_1
|
||||
// is more costly than using zero,
|
||||
// it is done with inverted logic.
|
||||
if (recv_pattern[0]) begin
|
||||
recv_buf_data <= ~recv_pattern[8:1];
|
||||
recv_buf_valid <= 1;
|
||||
recv_state <= 0;
|
||||
end else begin
|
||||
recv_pattern <= {~rx, recv_pattern[8:1]};
|
||||
/* verilator lint_off WIDTH */
|
||||
recv_divcnt <= baud_init;
|
||||
/* verilator lint_on WIDTH */
|
||||
end
|
||||
end else recv_divcnt <= recv_divcnt - 1;
|
||||
end
|
||||
|
||||
endcase
|
||||
end
|
||||
|
||||
/************* Transmitter ******************************/
|
||||
|
||||
reg [divwidth:0] send_divcnt;
|
||||
wire send_baud_clk = send_divcnt[divwidth];
|
||||
|
||||
reg [9:0] send_pattern = 1;
|
||||
assign tx = send_pattern[0];
|
||||
assign busy = |send_pattern[9:1];
|
||||
|
||||
// The transmitter shifts until the stop bit is on the wire,
|
||||
// and stops shifting then.
|
||||
always @(posedge clk) begin
|
||||
if (wr) send_pattern <= {1'b1, tx_data[7:0], 1'b0};
|
||||
else if (send_baud_clk & busy) send_pattern <= send_pattern >> 1;
|
||||
/* verilator lint_off WIDTH */
|
||||
if (wr | send_baud_clk) send_divcnt <= baud_init;
|
||||
else send_divcnt <= send_divcnt - 1;
|
||||
/* verilator lint_on WIDTH */
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
|
||||
Reference in New Issue
Block a user