URL
https://opencores.org/ocsvn/rtftextcontroller/rtftextcontroller/trunk
Subversion Repositories rtftextcontroller
[/] [rtftextcontroller/] [trunk/] [rtl/] [verilog/] [FT_VIC.v] - Rev 32
Go to most recent revision | Compare with Previous | Blame | View Log
// ============================================================================ // __ // \\__/ o\ (C) 2017 Robert Finch, Waterloo // \ __ / All rights reserved. // \/_// robfinch<remove>@finitron.ca // || // // // FT_VIC.v // // This source file is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published // by the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This source file is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. // // // ============================================================================ `define TRUE 1'b1 `define FALSE 1'b0 `define HIGH 1'b1 `define LOW 1'b0 `define M0X 7'h00 `define M0Y 7'h01 `define M1X 7'h02 `define M1Y 7'h03 `define M2X 7'h04 `define M2Y 7'h05 `define M3X 7'h06 `define M3Y 7'h07 `define M4X 7'h08 `define M4Y 7'h09 `define M5X 7'h0A `define M5Y 7'h0B `define M6X 7'h0C `define M6Y 7'h0D `define M7X 7'h0E `define M7Y 7'h0F `define M8X 7'h10 `define M8Y 7'h11 `define M9X 7'h12 `define M9Y 7'h13 `define M10X 7'h14 `define M10Y 7'h15 `define M11X 7'h16 `define M11Y 7'h17 `define RASTER 7'h18 `define LPX 7'h19 `define LPY 7'h1A `define SE 7'h1B `define CTRL1 7'h1C `define SYE 7'h1D `define MEMPTR 7'h1E `define IRQ 7'h1F `define IRQEN 7'h20 `define MXE 7'h23 `define MMC 7'h24 `define MDC 7'h25 `define EC 7'h27 `define M0C1 7'h28 `define M1C1 7'h29 `define M2C1 7'h2A `define M3C1 7'h2B `define M4C1 7'h2C `define M5C1 7'h2D `define M6C1 7'h2E `define M7C1 7'h2F `define M8C1 7'h30 `define M9C1 7'h31 `define M10C1 7'h32 `define M11C1 7'h33 `define M0C2 7'h34 `define M1C2 7'h35 `define M2C2 7'h36 `define M3C2 7'h37 `define M4C2 7'h38 `define M5C2 7'h39 `define M6C2 7'h3A `define M7C2 7'h3B `define M8C2 7'h3C `define M9C2 7'h3D `define M10C2 7'h3E `define M11C2 7'h3F module FT_VIC( ws_rst_i, ws_clk_i, ws_cyc_i, ws_stb_i, ws_ack_o, ws_we_i, ws_adr_i, ws_dat_i, ws_dat_o, cs_i, rdy_o, irq_o, v_clk_i, v_adr_o, v_dat_i, rst, hSync, vSync, color_o, lp_n ); parameter MIBCNT = 12; parameter VIC_IDLE = 0; // idle cycle parameter VIC_SPRITE_PTR = 1; parameter VIC_SPRITE_PTR2 = 2; parameter VIC_SPRITE = 2; // sprite cycle parameter VIC_CHAR = 3; // character acccess cycle parameter VIC_CHAR2 = 4; parameter VIC_CHAR_BMP = 5; parameter VIC_BMP1 = 3; parameter VIC_BMP2 = 4; parameter VIC_BMP3 = 6; parameter VIC_BMP4 = 7; parameter phSyncOn = 8; // 8 front porch parameter phSyncOff = 104; // 96 sync parameter phBlankOff = 144; // 40 back porch parameter phBorderOff = 152; // 8 border parameter phBorderOn = 792; // 640 display parameter phBorderOff2 = 168; parameter phBorderOn2 = 776; parameter phBlankOn = 800; // 8 border parameter phTotal = 800; // 800 total clocks // parameter pvSyncOn = 5; // 5 front porch parameter pvSyncOff = 7; // 2 vertical sync parameter pvBlankOff = 35; // 28 back porch parameter pvBorderOff = 42; // 7 border 0 parameter pvBorderOn = 442; // 400 display parameter pvBorderOff2 = 50; parameter pvBorderOn2 = 434; parameter pvBlankOn = 449; // 7 border 0 parameter pvTotal = 449; // 449 total scan lines input ws_rst_i; input ws_clk_i; input ws_cyc_i; input ws_stb_i; output ws_ack_o; input ws_we_i; input [7:0] ws_adr_i; input [15:0] ws_dat_i; output [15:0] ws_dat_o; input cs_i; output rdy_o; output irq_o; input v_clk_i; output [19:0] v_adr_o; input [31:0] v_dat_i; input rst; output hSync; output vSync; input lp_n; integer n,i; wire cs = ws_cyc_i & ws_stb_i & cs_i; reg rdy; always @(posedge ws_clk_i) rdy <= cs; assign ws_ack_o = cs ? rdy : 1'b0; assign rdy_o = cs ? rdy : 1'b1; wire badline; reg [3:0] vicCycle; reg [3:0] sprite; reg [13:0] vmndx; // video matrix index reg [19:0] bmndx; // bitmap mode index reg [31:0] nextChar; reg [31:0] charbuf [78:0]; reg [31:0] bmpData; reg [2:0] scanline; reg [MIBCNT-1:0] MActive; reg MPtr [10:0]; reg [8:0] MCnt [0:MIBCNT-1]; reg [8:0] mx [0:MIBCNT-1]; reg [7:0] my [0:MIBCNT-1]; reg [7:0] mtc [0:MIBCNT-1]; reg [7:0] mc1 [0:MIBCNT-1]; reg [7:0] mc2 [0:MIBCNT-1]; reg [7:0] mc3 [0:MIBCNT-1]; reg [MIBCNT-1:0] me; reg [MIBCNT-1:0] mye, mye_ff; reg [MIBCNT-1:0] mxe, mxe_ff; reg [MIBCNT-1:0] mdp; reg [MIBCNT-1:0] MShift; reg [95:0] MPixels [MIBCNT-1:0]; reg [1:0] MCurrentPixel [MIBCNT-1:0]; reg [MIBCNT-1:0] m2m, m2d; reg m2mhit, m2dhit; reg [19:0] cb; reg [19:0] vm; reg [19:0] bma; reg [7:0] regno; // Interrupt bits reg irst; reg ilp; reg immc; reg imbc; reg irst_clr; reg imbc_clr; reg immc_clr; reg ilp_clr; reg irq_clr; reg erst; reg embc; reg emmc; reg elp; assign irq = (ilp & elp) | (immc & emmc) | (imbc & embc) | (irst & erst); assign irq_o = irq; reg rasterIRQDone; reg [9:0] rasterCmp; wire [9:0] hCtr, vCtr; reg rsel,csel; reg [2:0] xscroll,yscroll; //------------------------------------------------------------------------------ // Set the memory access cycle type //------------------------------------------------------------------------------ always @(hCtr) begin if (hCtr >= 10'd0 && hCtr < 10'd096) case(hCtr[2:0]) 3'b001: vicCycle <= VIC_SPRITE_PTR; 3'b010: vicCycle <= VIC_SPRITE_PTR2; 3'b011: vicCycle <= VIC_SPRITE_DAT; 3'b100: vicCycle <= VIC_SPRITE_DAT; 3'b101: vicCycle <= VIC_SPRITE_DAT; default: vicCycle <= VIC_IDLE; endcase else if (hCtr >= 10'd144 && hCtr < 10'd784) case(hCtr[2:0]) 3'b000: vicCycle <= (bmm || badline) ? VIC_CHAR : VIC_IDLE; 3'b001: vicCycle <= (bmm || badline) ? VIC_CHAR2 : VIC_IDLE; 3'b010: vicCycle <= !bmm ? VIC_CHAR_BMP : VIC_IDLE; 3'b011: vicCycle <= !bmm ? VIC_CHAR_BMP2 : VIC_IDLE; 3'b100: vicCycle <= bmm ? VIC_BMP3 : VIC_IDLE; 3'b101: vicCycle <= bmm ? VIC_BMP4 : VIC_IDLE; 3'b110: vicCycle <= VIC_IDLE; 3'b111: vicCycle <= VIC_IDLE; endcase else vicCycle <= VIC_IDLE; end always @(posedge v_clk_i) case(hCtr) 10'd008: sprite <= 4'd1; 10'd016: sprite <= 4'd2; 10'd024: sprite <= 4'd3; 10'd032: sprite <= 4'd4; 10'd040: sprite <= 4'd5; 10'd048: sprite <= 4'd6; 10'd056: sprite <= 4'd7; 10'd064: sprite <= 4'd8; 10'd072: sprite <= 4'd9; 10'd080: sprite <= 4'd10; 10'd088: sprite <= 4'd11; default: sprite <= 4'h0; endcase //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ assign badline = vCtr[2:0]==yscroll && den && (vCtr >= (SIM ? 12'd1 : pvBlankOff) && vCtr <= pvBlankOn); //------------------------------------------------------------------------------ // Databus loading //------------------------------------------------------------------------------ always @(posedge v_clk_i) begin case(vicCycle) VIC_SPRITE_PTR2: MPtr <= v_dat_i[19:9]; VIC_CHAR: begin waitingPixels <= readPixels; waitingChar <= readChar; end VIC_CHAR2: // also VIC_BMP2 begin bmpData <= v_dat_i; if (badline) nextChar <= v_dat_i; else nextChar <= charbuf[78]; for (n = 78; n > 0; n = n -1) charbuf[n] = charbuf[n-1]; charbuf[0] <= nextChar; end VIC_CHAR_BMP2: begin case(scanline[1:0]) 2'd0: readPixels <= v_dat_i[7:0]; 2'd1: readPixels <= v_dat_i[15:8]; 2'd2: readPixels <= v_dat_i[23:16]; 2'd3: readPixels <= v_dat_i[31:24]; endcase readChar <= nextChar; end VIC_BMP4: bmpData <= v_dat_i; default: ; endcase end //------------------------------------------------------------------------------ // Video matrix counter //------------------------------------------------------------------------------ reg [13:0] vmndxStart; always @(posedge v_clk_i) if (rst) begin vmndx <= 14'd0; vmndxStart <= 14'd0; end else begin if (vCtr==pVTotal) vmndx <= 10'd0; if (vicCycle==VIC_CHAR && badline) vmndx <= vmndx + 14'd4; if (hCtr==10'd120) begin if (scanline==3'd7) vmndxStart <= vmndx; else vmndx <= vmndxStart; end end //------------------------------------------------------------------------------ // Scanline counter // // The scanline counter provides the three LSB's of the character bitmap data. //------------------------------------------------------------------------------ always @(posedge v_clk_i) if (rst) scanline <= 3'd0; else begin if (hCtr==10'd128) begin if (badline) scanline <= 3'd0; else scanline <= scanline + 3'd1; end end //------------------------------------------------------------------------------ // Bitmapped mode counter. //------------------------------------------------------------------------------ always @(posedge v_clk_i) if (rst) bmndx <= 20'd0; else begin if (vCtr < pvBlankOn) bmndx = bma; else begin if (hCtr > phBlankOff && hCtr <= phBlankOn) bmndx <= bmndx + 20'd4; end end //------------------------------------------------------------------------------ // Collision detect logic. //------------------------------------------------------------------------------ always @* for (n = 0; n < MIBCNT; n = n + 1) MActive[n] <= MCnt[n] != 9'd504; reg [MIBCNT-1:0] collision; always @* for (n = 0; n < MIBCNT; n = n + 1) collision[n] = MCurrentPixel[n]!=2'h0; // Sprite-sprite collision logic always @(posedge v_clk_i) if (rst) m2mhit <= `FALSE; else begin if (immc_clr) immc <= `FALSE; if (ws_adr_i[7:0]==7'h48 && cs) begin m2m[11:0] <= 12'h000; m2mhit <= `FALSE; end case(collision) 12'b000000000000, 12'b000000000001, 12'b000000000010, 12'b000000000100, 12'b000000001000, 12'b000000010000, 12'b000000100000, 12'b000001000000, 12'b000010000000, 12'b000100000000, 12'b001000000000, 12'b010000000000, 12'b100000000000, ; default: begin m2m <= m2m | collision; if (!m2mhit) begin immc <= `TRUE; m2mhit <= `TRUE; end end endcase end // Sprite-background collision logic always @(posedge v_clk_i) if (rst) m2dhit <= `FALSE; else begin if (imbc_clr) imbc <= `FALSE; if (ws_adr_i[7:0]==7'h4A && cs) begin m2d[MIBCNT-1:0] <= 12'h0; m2dhit <= `FALSE; end for (n = 0; n < MIBCNT; n = n + 1) begin if (collision[n] & pixelBgFlag & ~border) begin m2d[n] <= `TRUE; if (!m2dhit) begin m2dhit <= `TRUE; imbc <= `TRUE; end end end end //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ always @(posedge v_clk_i) begin for (n = 0; n < MIBCNT; n = n + 1) begin if (hCtr == 10'd10) MShift[n] <= `FALSE; else if (hCtr == mx[n]) MShift[n] <= `TRUE; end end //------------------------------------------------------------------------------ // Manage MCnt and sprite Y expansion. //------------------------------------------------------------------------------ always @(posedge v_clk_i) if (rst & SIM) begin for (n = 0; n < MIBCNT; n = n + 1) begin MCnt[n] <= 9'd504; end end else begin // Trigger sprite accesses // if the sprite Y coordinate matches. if (hCtr==10'd120) begin for (n = 0; n < MIBCNT; n = n + 1) begin if (!MActive[n] && me[n] && vCtr == my[n]) MCnt[n] <= 9'd000; end end // Reset expansion flipflop once sprite becomes deactivated or // if no sprite Y expansion. for (n = 0; n < MIBCNT; n = n + 1) begin if (!mye[n] || !MActive[n]) mye_ff[n] <= 1'b0; end // If Y expansion is on, backup the MIB data counter by twelve every // other scanline. if (hCtr==phTotal) begin for (n = 0; n < MIBCNT; n = n + 1) begin if (MActive[n] & mye[n]) begin mye_ff[n] <= !mye_ff[n]; if (!mye_ff[n]) MCnt[n] <= MCnt[n] - 9'd12; end end end if (vicCycle==VIC_SPRITE) begin if (MActive[sprite]) MCnt[sprite] <= MCnt[sprite] + 9'd4; end end reg [3:0] sprdat; always @(posedge v_clk_i) begin for (n = 0; n < MIBCNT; n = n + 1) begin if (MShift[n]) begin mxe_ff[n] <= !mxe_ff[n] & mxe[n]; if (!mxe_ff[n]) begin MCurrentPixel[n] <= MPixels[n][95:94]; MPixels[n] <= {MPixels[n][93:0],2'h0}; end end else begin mxe_ff[n] <= 1'b0; MCurrentPixel[n] <= 2'h0; end end if (vicCycle==VIC_SPRITE_PTR2) sprdat <= 4'b0111; else sprdat <= {sprdat[2:0],1'b0}; if (sprdat[3]) begin if (MActive[sprite]) MPixels[sprite] <= {MPixels[sprite][63:0],v_dat_i}; end end //------------------------------------------------------------------------------ // Address Generation //------------------------------------------------------------------------------ always @(posedge v_clk_i) begin v_adr_o <= 20'hFFFFC; if (bmm) begin case(vicCycle) VIC_BMP1,VIC_BMP3: v_adr_o <= bmndx; VIC_SPRITE_PTR: if (MActive[sprite]) v_adr_o <= {vm[19:14],8'b11111111,sprite,2'b00}; VIC_SPRITE_DAT: if (MActive[sprite]) v_adr_o <= {MPtr,MCnt[sprite]}; default: ; endcase end else begin case(vicCycle) VIC_CHAR: v_adr_o <= {vm[19:14],vmndx}; VIC_CHAR_BMP: v_adr_o <= {cb[19:12],nextChar[8:0],scanline[2],2'b00}; VIC_SPRITE_PTR: if (MActive[sprite]) v_adr_o <= {vm[19:14],8'b11111111,sprite,2'b00}; VIC_SPRITE_DAT: if (MActive[sprite]) v_adr_o <= {MPtr,MCnt[sprite]}; default: ; endcase end end //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ always @(posedge v_clk_i) begin if (hCtr==10'd192) rasterIRQDone <= `FALSE; if (irst_clr) irst <= 1'b0; if (rasterIRQDone == 1'b0 && vCtr == rasterCmp) begin rasterIRQDone <= `TRUE; irst <= 1'b1; end end //------------------------------------------------------------------------------ // Light pen // // The light pen only allows one hit per frame. It's the first hit that counts. //------------------------------------------------------------------------------ reg lightPenHit; always @(posedge v_clk_i) begin if (ilp_clr) ilp <= `LOW; if (vCtr == pvTotal) lightPenHit <= `FALSE; else if (!lightPenHit && lp_n == `LOW) begin lightPenHit <= `TRUE; ilp <= `HIGH; lpx <= hCtr; lpy <= vCtr; end end //------------------------------------------------------------------------------ // Graphics mode pixel calc. //------------------------------------------------------------------------------ reg [31:0] shiftingChar,waitingChar,readChar; reg [7:0] shiftingPixels,waitingPixels,readPixels; always @(posedge v_clk_i) begin begin if (xscroll==hCtr[2:0]) begin shiftingChar <= waitingChar; shiftingPixels <= waitingPixels; end else shiftingPixels <= {shiftingPixels[6:0],1'b0}; pixelColor <= 8'h00; // black if (bmm) begin pixelBgFlag <= bmpData[31]; pixelColor <= bmpData[31:24]; bmpData <= {bmpData[23:0],8'h00}; end else begin pixelBgFlag <= shiftingChar[15]; pixelColor <= shiftingPixels[7] ? shiftingChar[31:24] : shiftingChar[23:16]; end end //------------------------------------------------------------------------------ // Output color selection //------------------------------------------------------------------------------ reg [7:0] color_code; always @(posedge v_clk_i) begin color_code <= pixelColor; // See if the mib overrides the output for (n = 0; n < MIBCNT; n = n + 1) begin if (!mdp[n] || !pixelBgFlag) begin case(MCurrentPixel[n]) 2'd0: ; 2'd1: color_code <= mc1[n]; 2'd2: color_code <= mc2[n]; 2'd3: color_code <= mc3[n]; endcase end end end reg [7:0] ec; always @(posedge v_clk_i) begin if (border) color_o <= ec[6:0]; else color_o <= color_code[6:0]; end //------------------------------------------------------------------------------ // Register Interface // // VIC-II offers register feedback on all registers. //------------------------------------------------------------------------------ reg [15:0] regShadow [127:0]; always @(posedge ws_clk_i) if (ws_rst_i) begin end else begin if (cs) begin if (ws_we_i) begin regShadow[ws_adr_i[7:1]] <= ws_dat_i; case(ws_adr_i[7:1]) `M0X: mx[0] <= ws_dat_i[9:0]; `M0Y: my[0] <= ws_dat_i[8:0]; `M1X: mx[1] <= ws_dat_i[9:0]; `M1Y: my[1] <= ws_dat_i[8:0]; `M2X: mx[2] <= ws_dat_i[9:0]; `M2Y: my[2] <= ws_dat_i[8:0]; `M3X: mx[3] <= ws_dat_i[9:0]; `M3Y: my[3] <= ws_dat_i[8:0]; `M4X: mx[4] <= ws_dat_i[9:0]; `M4Y: my[4] <= ws_dat_i[8:0]; `M5X: mx[5] <= ws_dat_i[9:0]; `M5Y: my[5] <= ws_dat_i[8:0]; `M6X: mx[6] <= ws_dat_i[9:0]; `M6Y: my[6] <= ws_dat_i[8:0]; `M7X: mx[7] <= ws_dat_i[9:0]; `M7Y: my[7] <= ws_dat_i[8:0]; `M8X: mx[8] <= ws_dat_i[9:0]; `M8Y: my[8] <= ws_dat_i[8:0]; `M9X: mx[9] <= ws_dat_i[9:0]; `M9Y: my[9] <= ws_dat_i[8:0]; `M10X: mx[10] <= ws_dat_i[9:0]; `M10Y: my[10] <= ws_dat_i[8:0]; `M11X: mx[11] <= ws_dat_i[9:0]; `M11Y: my[11] <= ws_dat_i[8:0]; `RASTER: rasterCmp <= {1'b0,ws_dat_i[8:0]}; `SE: me <= ws_dat_i[11:0]; `CTRL1: begin xscroll <= ws_dat_i[2:0]; csel <= ws_dat_i[3]; yscroll <= ws_dat_i[10:8]; rsel <= ws_dat_i[11]; den <= ws_dat_i[12]; bmm <= ws_dat_i[13]; bma <= {ws_dat_i[15:14],18'h00000}; end `SYE: mye <= ws_dat_i[11:0]; `MEMPTR: begin cb <= {ws_dat_i[7:0],12'h000}; vm <= {ws_dat_i[15:10],14'h0000}; end `IRQEN: begin erst <= ws_dat_i[0]; embc <= ws_dat_i[1]; emmc <= ws_dat_i[2]; elp <= ws_dat_i[3]; end `MXE: mxe <= ws_dat_i[11:0]; `EC: ec <= ws_dat_i[7:0]; `M0C1: mc1[0] <= ws_dat_i[15:8]; `M1C1: mc1[1] <= ws_dat_i[15:8]; `M2C1: mc1[2] <= ws_dat_i[15:8]; `M3C1: mc1[3] <= ws_dat_i[15:8]; `M4C1: mc1[4] <= ws_dat_i[15:8]; `M5C1: mc1[5] <= ws_dat_i[15:8]; `M6C1: mc1[6] <= ws_dat_i[15:8]; `M7C1: mc1[7] <= ws_dat_i[15:8]; `M8C1: mc1[8] <= ws_dat_i[15:8]; `M9C1: mc1[9] <= ws_dat_i[15:8]; `M10C1: mc1[10] <= ws_dat_i[15:8]; `M11C1: mc1[11] <= ws_dat_i[15:8]; `M0C2: begin mc2[0] <= ws_dat_i[7:0]; mc3[0] <= ws_dat_i[15:8]; end `M1C2: begin mc2[1] <= ws_dat_i[7:0]; mc3[1] <= ws_dat_i[15:8]; end `M2C2: begin mc2[2] <= ws_dat_i[7:0]; mc3[2] <= ws_dat_i[15:8]; end `M3C2: begin mc2[3] <= ws_dat_i[7:0]; mc3[3] <= ws_dat_i[15:8]; end `M4C2: begin mc2[4] <= ws_dat_i[7:0]; mc3[4] <= ws_dat_i[15:8]; end `M5C2: begin mc2[5] <= ws_dat_i[7:0]; mc3[5] <= ws_dat_i[15:8]; end `M6C2: begin mc2[6] <= ws_dat_i[7:0]; mc3[6] <= ws_dat_i[15:8]; end `M7C2: begin mc2[7] <= ws_dat_i[7:0]; mc3[7] <= ws_dat_i[15:8]; end `M8C2: begin mc2[8] <= ws_dat_i[7:0]; mc3[8] <= ws_dat_i[15:8]; end `M9C2: begin mc2[9] <= ws_dat_i[7:0]; mc3[9] <= ws_dat_i[15:8]; end `M10C2: begin mc2[10] <= ws_dat_i[7:0]; mc3[10] <= ws_dat_i[15:8]; end `M11C2: begin mc2[11] <= ws_dat_i[7:0]; mc3[11] <= ws_dat_i[15:8]; end endcase end else begin ws_dat_o <= regShadow[ws_adr_i[7:1]]; case(ws_adr_i[7:1]) `RASTER: ws_dat_o <= {7'd0,vCtr}; `IRQ: begin ws_dat_o[0] <= irst; ws_dat_o[1] <= imbc; ws_dat_o[2] <= immc; ws_dat_o[3] <= ilp; ws_dat_o[7] <= irq; end `MMC: ws_dat_o <= {4'h0,m2m}; `MDC: ws_dat_o <= {4'h0,m2d}; endcase end end end SyncGen640x400_70Hz sg1 ( .rst(rst), .clk(v_clk_i), .hSync(hSync), .vSync(vSync), .hCtr(hCtr), .vCtr(vCtr), .blank(blank), .border(border), .csel(csel), .rsel(rsel) ); endmodule
Go to most recent revision | Compare with Previous | Blame | View Log