newStep.v

This commit is contained in:
2025-11-27 04:28:54 +03:00
parent a84b8fcfde
commit 6e38a6c1af
85 changed files with 25646 additions and 6801 deletions

16
RTL/DEVICES/Buttons.v Normal file
View 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
View 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
View 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
View 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;

View 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

View 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
View 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
View 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

View 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
View 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
View 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

View 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

View 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
View 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

View 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

View 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