8bitworkshop/presets/verilog/racing_game_cpu.v

275 lines
7.2 KiB
Verilog

`include "hvsync_generator.v"
`include "sprite_bitmap.v"
`include "sprite_renderer.v"
`include "cpu8.v"
/*
A simple racing game with two sprites and a scrolling playfield.
This version uses the 8-bit CPU.
*/
module racing_game_cpu_top(clk, reset, hsync, vsync, hpaddle, vpaddle, rgb);
input clk, reset;
input hpaddle, vpaddle;
output hsync, vsync;
wire display_on;
wire [8:0] hpos;
wire [8:0] vpos;
output [3:0] rgb;
parameter PADDLE_X = 0; // paddle X coordinate
parameter PADDLE_Y = 1; // paddle Y coordinate
parameter PLAYER_X = 2; // player X coordinate
parameter PLAYER_Y = 3; // player Y coordinate
parameter ENEMY_X = 4; // enemy X coordinate
parameter ENEMY_Y = 5; // enemy Y coordinate
parameter ENEMY_DIR = 6; // enemy direction (1, -1)
parameter SPEED = 7; // player speed
parameter TRACKPOS_LO = 8; // track position (lo byte)
parameter TRACKPOS_HI = 9; // track position (hi byte)
parameter IN_HPOS = 8'h40; // CRT horizontal position
parameter IN_VPOS = 8'h41; // CRT vertical position
// flags: [0, 0, collision, vsync, hsync, vpaddle, hpaddle, display_on]
parameter IN_FLAGS = 8'h42;
reg [7:0] ram[0:15]; // 16 bytes of RAM
reg [7:0] rom[0:127]; // 128 bytes of ROM
wire [7:0] address_bus; // CPU address bus
reg [7:0] to_cpu; // data bus to CPU
wire [7:0] from_cpu; // data bus from CPU
wire write_enable; // write enable bit from CPU
// 8-bit CPU module
CPU cpu(.clk(clk),
.reset(reset),
.address(address_bus),
.data_in(to_cpu),
.data_out(from_cpu),
.write(write_enable));
// RAM write from CPU
always @(posedge clk)
if (write_enable)
ram[address_bus[3:0]] <= from_cpu;
// RAM read from CPU
always @(*)
casez (address_bus)
// RAM
8'b00??????: to_cpu = ram[address_bus[3:0]];
// special read registers
IN_HPOS: to_cpu = hpos[7:0];
IN_VPOS: to_cpu = vpos[7:0];
IN_FLAGS: to_cpu = {2'b0, frame_collision,
vsync, hsync, vpaddle, hpaddle, display_on};
// ROM
8'b1???????: to_cpu = rom[address_bus[6:0]];
default: to_cpu = 8'bxxxxxxxx;
endcase
// sync generator module
hvsync_generator hvsync_gen(
.clk(clk),
.reset(0),
.hsync(hsync),
.vsync(vsync),
.display_on(display_on),
.hpos(hpos),
.vpos(vpos)
);
// flags for player sprite renderer module
wire player_vstart = {1'b0,ram[PLAYER_Y]} == vpos;
wire player_hstart = {1'b0,ram[PLAYER_X]} == hpos;
wire player_gfx;
wire player_is_drawing;
// flags for enemy sprite renderer module
wire enemy_vstart = {1'b0,ram[ENEMY_Y]} == vpos;
wire enemy_hstart = {1'b0,ram[ENEMY_X]} == hpos;
wire enemy_gfx;
wire enemy_is_drawing;
// select player or enemy access to ROM
wire player_load = (hpos >= 256) && (hpos < 260);
wire enemy_load = (hpos >= 260);
// wire up car sprite ROM
// multiplex between player and enemy ROM address
wire [3:0] player_sprite_yofs;
wire [3:0] enemy_sprite_yofs;
wire [3:0] car_sprite_yofs = player_load ? player_sprite_yofs : enemy_sprite_yofs;
wire [7:0] car_sprite_bits;
car_bitmap car(
.yofs(car_sprite_yofs),
.bits(car_sprite_bits));
// player sprite renderer
sprite_renderer player_renderer(
.clk(clk),
.vstart(player_vstart),
.hstart(player_hstart),
.load(player_load),
.rom_addr(player_sprite_yofs),
.rom_bits(car_sprite_bits),
.gfx(player_gfx),
.in_progress(player_is_drawing));
// enemy sprite renderer
sprite_renderer enemy_renderer(
.clk(clk),
.vstart(enemy_vstart),
.hstart(enemy_hstart),
.load(enemy_load),
.rom_addr(enemy_sprite_yofs),
.rom_bits(car_sprite_bits),
.gfx(enemy_gfx),
.in_progress(enemy_is_drawing));
// collision detection logic
reg frame_collision;
always @(posedge clk)
if (player_gfx && (enemy_gfx || track_gfx))
frame_collision <= 1;
else if (vpos==0)
frame_collision <= 0;
// track graphics
wire track_offside = (hpos[7:5]==0) || (hpos[7:5]==7);
wire track_shoulder = (hpos[7:3]==3) || (hpos[7:3]==28);
wire track_gfx = (vpos[5:1]!=ram[TRACKPOS_LO][5:1]) && track_offside;
// RGB output
wire r = display_on && (player_gfx || enemy_gfx || track_shoulder);
wire g = display_on && (player_gfx || track_gfx);
wire b = display_on && (enemy_gfx || track_shoulder);
// RGBI (in IBGR order, intensity is 4th bit)
assign rgb = {1'b0,b,g,r};
//////////// CPU program code
`ifdef EXT_INLINE_ASM
initial begin
rom = '{
__asm
.arch femto8 ; FEMTO-8 architecture
.org 128 ; origin = 128 ($80)
.len 128 ; length = 128 ($80)
; define constants
.define PADDLE_X 0
.define PADDLE_Y 1
.define PLAYER_X 2
.define PLAYER_Y 3
.define ENEMY_X 4
.define ENEMY_Y 5
.define ENEMY_DIR 6
.define SPEED 7
.define TRACKPOS_LO 8
.define TRACKPOS_HI 9
.define IN_HPOS $40
.define IN_VPOS $41
.define IN_FLAGS $42
.define F_DISPLAY 1
.define F_HPADDLE 2
.define F_VPADDLE 4
.define F_HSYNC 8
.define F_VSYNC 16
.define F_COLLIDE 32
Start:
lda #128 ; load initial positions
sta PLAYER_X ; player_x = 128
sta ENEMY_X ; enemy_x = 128
sta ENEMY_Y ; enemy_y = 128
lda #180
sta PLAYER_Y ; player_y = 180
zero A
sta SPEED ; player speed = 0
inc A
sta ENEMY_DIR ; enemy dir = 1 (right)
; read horizontal paddle position
DisplayLoop:
lda #F_HPADDLE ; paddle flag -> A
ldb #IN_FLAGS ; addr of IN_FLAGS -> B
and none,[B] ; read B, AND with A
bz DisplayLoop ; loop until paddle flag set
ldb #IN_VPOS
mov A,[B] ; load vertical position -> A
sta PLAYER_X ; store player x position
; wait for vsync
lda #F_VSYNC
ldb #IN_FLAGS
WaitForVsyncOn:
and none,[B]
bz WaitForVsyncOn ; wait until VSYNC on
WaitForVsyncOff:
and none,[B]
bnz WaitForVsyncOff ; wait until VSYNC off
; check collision
lda #F_COLLIDE
ldb #IN_FLAGS
and none,[B] ; collision flag set?
bz NoCollision ; skip ahead if not
lda #16
sta SPEED ; speed = 16
NoCollision:
; update speed
ldb #SPEED
mov A,[B] ; speed -> A
inc A ; increment speed
bz MaxSpeed ; speed wraps to 0?
sta SPEED ; no, store speed
MaxSpeed:
mov A,[B] ; reload speed -> A
lsr A
lsr A
lsr A
lsr A ; divide speed by 16
; add to lo byte of track pos
ldb #TRACKPOS_LO
add B,[B] ; B <- speed/16 + trackpos_lo
swapab ; swap A <-> B
sta TRACKPOS_LO ; A -> trackpos_lo
swapab ; swap A <-> B again
bcc NoCarry ; carry flag from earlier add op
; add to hi byte of track pos
ldb #TRACKPOS_HI
mov B,[B] ; B <- trackpos_hi
inc b ; increment B
swapab ; swap A <-> B
sta TRACKPOS_HI ; A -> trackpos_hi
swapab ; swap A <-> B again
NoCarry:
; update enemy vert pos
ldb #ENEMY_Y
add A,[B]
sta ENEMY_Y ; enemy_y = enemy_y + speed/16
; update enemy horiz pos
ldb #ENEMY_X
mov A,[B]
ldb #ENEMY_DIR
add A,[B]
sta ENEMY_X ; enemy_x = enemy_x + enemy_dir
sub A,#64
and A,#127 ; A <- (enemy_x-64) & 127
bnz SkipXReverse ; skip if enemy_x is in range
; load ENEMY_DIR and negate
zero A
sub A,[B]
sta ENEMY_DIR ; enemy_dir = -enemy_dir
SkipXReverse:
; back to display loop
jmp DisplayLoop
__endasm
};
end
`endif
endmodule